Catalog
fauziyasin-s2m4qw/kurtosis-test

fauziyasin-s2m4qw

kurtosis-test

Run a local Kurtosis Ethereum testnet against a locally-built erigon image, monitor EL/CL/assertoor/spamoor health, triage failures with a cross-client comparison methodology, and auto-iterate fix → rebuild → rerun. Use when the user wants to reproduce, debug, or validate erigon against an `ethereum-package` config locally — equivalent to the `test-kurtosis-assertoor` CI workflow but interactive. Handles image build, enclave lifecycle, block-progress + assertoor + log watching, log dumping on failure, and the erigon-source fix loop.

v1.0LATEST
NewUpdated Aug 20, 2026

Run a local Kurtosis Ethereum testnet against erigon

This skill mirrors the CI workflow at .github/workflows/test-kurtosis-assertoor.yml but runs locally via the raw kurtosis CLI. The CI uses the ethpandaops/kurtosis-assertoor-github-action@v1 wrapper, which is not portable outside GitHub Actions; this skill drives kurtosis run, kurtosis enclave inspect, kurtosis service logs, and kurtosis enclave dump directly.

The skill takes an ethereum-package YAML config, builds the local test/erigon:current Docker image, starts a Kurtosis enclave, monitors EL/CL/assertoor/spamoor health, triages failures (challenging peer clients against erigon to identify the offender), and iterates a fix → rebuild → rerun loop until the testnet is stable or max-attempts is reached.

Inputs

The model parses these arguments and binds them to the shell variables used in the bash blocks below: $1/$2 are positional; duration=Nm → duration_secs, auto=true|false → auto, max-attempts=N → max_attempts.

Argument Default Notes
$1 config path required Path to an ethereum-package args YAML. The reference set lives in .github/workflows/kurtosis/, but that directory also contains assertoor playbooks (id: / tasks: schema) — only the files whose top-level keys are participants: or participants_matrix: are valid here. To list candidates: grep -lE '^participants(_matrix)?:' .github/workflows/kurtosis/*.io.
$2 enclave name kurtosis-test-<unix-ts> Used for kurtosis run --enclave. Each rerun gets a fresh timestamp.
duration=Nm 20m Wall-clock window the monitor watches before declaring "stable" if no failures trip.
auto=true|false true If true, the fix-rebuild-rerun loop runs autonomously up to max-attempts. If false, pause for user approval before each fix.
max-attempts=N 5 Cap on fix-loop iterations. After hitting the cap, halt and surface the per-attempt triage history.

Prerequisites

  1. Docker running: docker info >/dev/null should succeed.
  2. Kurtosis CLI installed: kurtosis version. Install from https://docs.kurtosis.com/install if missing. CLI ≥ 1.18.1 is only needed when the --package@branch you run includes the GpuConfig Starlark built-in (i.e. ethereum-package main post commit 835dd9b). The pinned branches in the mapping table below — including glamsterdam's 6.1.0 — predate that change, so they work on older CLIs. See Troubleshooting if you hit a GpuConfig Starlark error.
  3. Erigon source tree at the cwd: Makefile exists and go.mod contains module github.com/erigontech/erigon.
  4. curl and jq on $PATH — used by the monitor / triage snippets below to poll the EL JSON-RPC endpoint and the assertoor API.
  5. Fork detection — skim the YAML for _fork_epoch keys. The repo's configs use the CL-side fork names: deneb_fork_epoch (Cancun on EL), electra_fork_epoch (Prague), fulu_fork_epoch (Osaka), gloas_fork_epoch (Amsterdam). Future fork keys will follow the same CL-naming convention. Feeds the spec-lookup section below.

Spec lookup (when debugging unfamiliar forks/EIPs)

If the YAML enables a fork under development, invoke /erigon-implement-eip Steps 2–4 to fetch:

  • Step 2 — referenced/dependent EIPs.
  • Step 3 — the meta EIP enumerating which EIPs the fork includes (CFI/SFI/PFI/DFI lists).
  • Step 4 — the latest devnet specification at https://notes.ethereum.org/@ethpandaops/<devnet>.

Use these as ground truth when triaging. For specific opcodes / state transitions, also pull the EIP body via Step 1. If anything in the spec is contradictory or ambiguous, stop and ask the user rather than guessing — the same rule the EIP skill enforces.

Build the erigon docker image

The image tag must be exactly test/erigon:current because every .github/workflows/kurtosis/*.io config references that tag.

docker build -t test/erigon:current --build-arg BINARIES="erigon caplin" .

caplin is required in BINARIES because some configs (e.g. caplin-minimal-assertoor.io) use erigon as the CL via the same image.

Always rebuild before each run — the same approach the CI uses. BuildKit's layer cache makes the no-op rebuild fast, and the fix → rebuild → rerun loop necessarily picks up uncommitted source edits this way (a freshness check against git log would miss them and silently run a stale image).

If the user asks for a from-scratch binary build instead of docker, point them at /erigon-build; this skill itself uses docker because the kurtosis configs reference a docker image tag.

Suite → ethereum-package branch mapping (from CI)

The CI matrix pins different package branches per suite. Use the same pinning when the config matches a known CI file; for unknown configs, default to 5.0.1 and ask the user to confirm.

Config file --package@branch
regular-assertoor.io github.com/ethpandaops/ethereum-package@5.0.1
pectra.io github.com/ethpandaops/ethereum-package@5.0.1
glamsterdam.io github.com/ethpandaops/ethereum-package@6.1.0
caplin-assertoor.io github.com/erigontech/ethereum-package@erigontech/fix-caplin-launcher
caplin-minimal-assertoor.io github.com/erigontech/ethereum-package@erigontech/fix-caplin-launcher
(other / user-supplied) default 5.0.1, prompt user if unsure

Note: glamsterdam is pinned to 6.1.0 rather than main because main introduced the GpuConfig Starlark built-in which requires kurtosis CLI ≥ 1.18.1. Caplin suites (caplin-assertoor.io, caplin-minimal-assertoor.io) require the erigontech fork — do not let them fall back to the default 5.0.1.

Start the testnet

ENCLAVE="${2:-kurtosis-test-$(date +%s)}"
CONFIG="$1"

# Map config basename → ethereum-package branch (mirrors the table above).
case "$(basename "$CONFIG")" in
  glamsterdam.io)
    PACKAGE_REF="github.com/ethpandaops/ethereum-package@6.1.0" ;;
  caplin-assertoor.io|caplin-minimal-assertoor.io)
    PACKAGE_REF="github.com/erigontech/ethereum-package@erigontech/fix-caplin-launcher" ;;
  regular-assertoor.io|pectra.io)
    PACKAGE_REF="github.com/ethpandaops/ethereum-package@5.0.1" ;;
  *)
    PACKAGE_REF="github.com/ethpandaops/ethereum-package@5.0.1" ;;
esac

kurtosis run \
  "$PACKAGE_REF" \
  --enclave "$ENCLAVE" \
  --args-file "$CONFIG" \
  --verbosity detailed --cli-log-level trace

Once kurtosis run returns, capture service names and host-mapped ports:

kurtosis enclave inspect "$ENCLAVE" --full-uuids

# Pick the first erigon EL service. `kurtosis enclave inspect` prints columnar
# rows (UUID first, then service name), so we match against field 2.
EL_SERVICE=$(kurtosis enclave inspect "$ENCLAVE" --full-uuids 2>/dev/null \
  | awk '$2 ~ /^el-[0-9]+-erigon-[a-z]+$/ {print $2; exit}')
EL_RPC_PORT=$(kurtosis port print "$ENCLAVE" "$EL_SERVICE" rpc 2>/dev/null \
  | sed -E 's|.*:([0-9]+).*|\1|')

# CL endpoint (whichever client paired with that EL)
CL_SERVICE=$(kurtosis enclave inspect "$ENCLAVE" --full-uuids 2>/dev/null \
  | awk '$2 ~ /^cl-[0-9]+-[a-z]+-erigon$/ {print $2; exit}')
CL_HTTP_PORT=$(kurtosis port print "$ENCLAVE" "$CL_SERVICE" http 2>/dev/null \
  | sed -E 's|.*:([0-9]+).*|\1|')

# Optional services
ASSERTOOR_PORT=$(kurtosis port print "$ENCLAVE" assertoor http 2>/dev/null | sed -E 's|.*:([0-9]+).*|\1|')
DORA_PORT=$(kurtosis port print "$ENCLAVE" dora http 2>/dev/null | sed -E 's|.*:([0-9]+).*|\1|')
SPAMOOR_PORT=$(kurtosis port print "$ENCLAVE" spamoor http 2>/dev/null | sed -E 's|.*:([0-9]+).*|\1|')

Print the assertoor / dora URLs so the user can open the dashboards in a browser.

Monitor

Three checks run on a polling loop until either (a) duration elapses, or (b) any failure trips. All three are captured in the run history.

Check A — Block height progress

duration_secs is the parsed duration=Nm input (default 1200). Three terminal outcomes:

  • STABLE — chain produced blocks and the duration elapsed without a stall.
  • STALL — chain produced at least one block, then stopped advancing for >3 × seconds_per_slot.
  • NO_PROGRESS — chain never produced a block within duration_secs (e.g. validators didn't start).
duration_secs=${duration_secs:-1200}
prev=0
slot_secs=$(grep -E '^\s*seconds_per_slot:' "$CONFIG" | awk '{print $2}'); slot_secs=${slot_secs:-12}
poll_interval=$(( slot_secs * 2 ))
stall_window=$(( slot_secs * 3 ))
start=$(date +%s)
end=$(( start + duration_secs ))
stall_deadline=$(( start + stall_window ))
outcome=""

while [ "$(date +%s)" -lt "$end" ]; do
  height_hex=$(curl -s --max-time 5 "http://127.0.0.1:${EL_RPC_PORT}" \
    -H 'Content-Type: application/json' \
    -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
    | jq -r '.result // empty')
  if [[ "$height_hex" =~ ^0x[0-9a-fA-F]+$ ]]; then
    height=$(printf '%d\n' "$height_hex")
    echo "[$(date -u +%H:%M:%S)] height=$height"
    if [ "$height" -gt "$prev" ]; then
      prev=$height
      stall_deadline=$(( $(date +%s) + stall_window ))
    fi
  else
    echo "[$(date -u +%H:%M:%S)] RPC unreachable or invalid response — retrying"
  fi
  # Only declare a stall after seeing at least one block. Many configs set
  # genesis_delay > stall_window (e.g. glamsterdam.io: 20s delay vs 18s window
  # at 6s slots), so the pre-genesis gap would otherwise trip a false stall.
  if [ "$prev" -gt 0 ] && [ "$(date +%s)" -gt "$stall_deadline" ]; then
    outcome="STALL: chain not progressing for >${stall_window}s (last height=$prev)"
    break
  fi
  sleep "$poll_interval"
done

if [ -z "$outcome" ]; then
  if [ "$prev" -eq 0 ]; then
    outcome="NO_PROGRESS: chain never produced a block within ${duration_secs}s"
  else
    outcome="STABLE: chain progressed for full ${duration_secs}s window (final height=$prev)"
  fi
fi
echo "$outcome"

Pass: height advances ≥1 within every 3 × seconds_per_slot (the stall window), sustained for the full duration_secs (poll cadence is 2 × seconds_per_slot). Fail: STALL or NO_PROGRESS.

Check B — Assertoor results

curl -s "http://127.0.0.1:${ASSERTOOR_PORT}/api/v1/test_runs" \
  | jq '.data[] | {name, status, result}'

Pass: every test_run has result=success. Fail: any result=failure, or any test stuck pending / running past 3× its expected duration. The assertoor web UI at http://127.0.0.1:${ASSERTOOR_PORT}/ shows per-step trees; use it for deep dives.

Check C — Erigon-focused log scan

kurtosis service logs "$ENCLAVE" "$EL_SERVICE" 2>&1 \
  | grep -iE 'panic|fatal|^ERROR|"lvl"="error"|consensus failure|invalid block' \
  | tail -200

For cross-client comparison (used by the triage section), run the same scan across every EL/CL service:

for svc in $(kurtosis enclave inspect "$ENCLAVE" --full-uuids \
             | awk '$2 ~ /^(el|cl|vc)-[0-9]+-[a-z]+-[a-z]+$/ {print $2}'); do
  echo "=== $svc ==="
  kurtosis service logs "$ENCLAVE" "$svc" 2>&1 \
    | grep -iE 'error|panic|fatal' | tail -30
done

If snooper-engine-* services exist (when snooper_enabled: true in the YAML), pull their logs too — they capture the full Engine API request/response trace, invaluable when an EL bug is suspected.

Issue detection criteria

User check Pass Fail
1. Block production / height progress eth_blockNumber advances ≥1 every 2 × seconds_per_slot, sustained for duration No advance for >3 × seconds_per_slot, OR explicit chain reorg / fork-choice loop in CL logs
2. EL/CL log errors (focus erigon) No panic, fatal, or error-level lines in any erigon service Any erigon-side panic/fatal/consensus failure. Non-erigon errors recorded but informational unless they crash the peer.
3. Assertoor test failures All assertoor test_runs reach result=success Any result=failure, OR a test stuck pending/running past 3× expected duration

A single failed check trips the triage section. Block-stall + erigon panic + assertoor fail are independent signals — record all three in the run history; do not stop at the first.

Debugging methodology — triage erigon vs peer-client vs network/config

Decision tree:

  1. Reproduce. A single one-shot failure gets one re-run before triaging. Truly intermittent failures still get triaged, but flag them as flaky.
  2. Classify the symptom. One of: block-stall, EL panic, EL invalid-payload, CL fork-choice mismatch, assertoor opcode/EIP test failure, spamoor tx-submission failure.
  3. Cross-client comparison. For each erigon-side error, find the equivalent moment in the peer-client log at the same slot/block. Three outcomes:
    • Erigon wrong: erigon rejects/panics; peer client + assertoor accept the block → erigon bug, fix locally.
    • Peer wrong: erigon accepts; peer rejects → check peer-client image tag against the fork's expected tag (often a stale image). Surface to user; do not fix erigon.
    • Both disagree with spec: clients produce different "valid" answers from what the EIP spec says → escalate to the user. Likely spec ambiguity or a misread.
  4. Cross-reference the spec. Pull the relevant EIP (/erigon-implement-eip Step 1) and the devnet spec (/erigon-implement-eip Step 4) for the failing block, opcode, or state transition.
  5. Rule out config drift. Diff the YAML's el_extra_params, network_params, and fork epochs against the equivalent CI suite under .github/workflows/kurtosis/. Mismatches there are config bugs, not erigon bugs.
  6. Rule out enclave plumbing. kurtosis service exec <enclave> <svc> "ping <other_svc>" to verify network reachability; check JWT mounting via kurtosis service exec <enclave> <el-svc> "ls -la /jwt/". The CLI takes the command as a single positional arg (multi-word commands must be quoted) — there is no -- separator.

Triage table

Symptom Likely owner Next action
Erigon panic with stack trace inside execution/... Erigon Capture stack, find offending call in repo, propose fix
eth_newPayloadV4 returns INVALID; CL logs say block is valid; assertoor passes elsewhere Erigon (likely block-validation divergence) Replay the payload via debug_traceBlockByNumber / debug_traceBlockByHash; check fork activation timestamp
All EL clients stop progressing after a specific slot Config (fork epoch wrong) or shared dep Diff YAML against working CI suite; check ethereum-package branch
Assertoor block-proposal-check fails on slot N for vc-N-erigon-… Erigon block builder Fetch block N body via RPC; replay locally
Assertoor synchronized-check fails Network plumbing Inspect peer counts; kurtosis service exec connectivity test
caplin panics but lighthouse runs fine on the same EL Caplin Edit YAML to swap CL to lighthouse for bisection; report to user
Spamoor reports persistent "insufficient funds" / "nonce too low" Spamoor config Increase funding_gas_limit; lower throughput; check prefunded keys
Snooper shows malformed Engine API request Erigon RPC layer Capture the request from snooper logs; inspect erigon engine handler
Erigon accepts a payload that lighthouse + teku both reject Erigon (single-client divergence) Almost always an erigon bug — fix locally
eth_blockNumber stays at 0x0 after >2 epochs Validators not running Check vc-* service logs; verify keystore mounting

Fix-rebuild-rerun loop

Auto-iterates by default (auto=true), capped at max-attempts=5. Per attempt:

  1. Tear down: kurtosis enclave rm -f "$ENCLAVE".
  2. Apply the fix to erigon source via Edit. Only auto-apply when the triage classified the issue as "Erigon wrong" with high confidence. If ambiguous (peer could be wrong, or spec interpretation unclear), pause and surface to the user even before the cap — that overrides auto=true.
  3. Rebuild image: docker build -t test/erigon:current --build-arg BINARIES="erigon caplin" ..
  4. Re-launch: same config, fresh timestamped enclave name (so each attempt's dump stays separate).
  5. Re-run monitor: same three checks.
  6. Record: per attempt — symptom, hypothesis, fix applied, outcome.

After max-attempts consecutive failures, halt and print the per-attempt history. Do not auto-apply more fixes once the cap is hit. Reference: /autoresearch follows the same iterate-and-record pattern.

If auto=false, pause for user approval before steps 2–4 of every attempt.

Cleanup (always run)

Run as the final step of every iteration regardless of outcome (success, failure, or user interrupt):

DUMP_DIR="/tmp/kurtosis-dump-${ENCLAVE}"
# `kurtosis enclave dump` refuses if the destination already exists — never pre-create it.
kurtosis enclave dump "$ENCLAVE" "$DUMP_DIR" || true
kurtosis enclave rm -f "$ENCLAVE" || true
echo "Logs dumped to: $DUMP_DIR"

The dump contains per-service logs (el-*, cl-*, vc-*, assertoor, spamoor, dora, snooper-*) — keep this directory until triage is complete; GitHub blob storage is not in play here so the logs are only on disk locally.

After multiple iterations, also prune dangling docker images:

docker image prune -f

Troubleshooting

Problem Solution
kurtosis run fails with Starlark error mentioning GpuConfig Use one of the pinned --package@branch values from the mapping table (e.g. 6.1.0 for glamsterdam, 5.0.1 for regular/pectra) — they predate the GpuConfig built-in. OR upgrade kurtosis CLI to ≥ 1.18.1 if you need an ethereum-package branch that includes it.
kurtosis enclave dump errors "destination exists" Use a fresh dir name or rm -rf it first
All el-*-erigon-* services missing from enclave inspect Image build failed; check docker images | grep test/erigon and rerun docker build
eth_blockNumber returns 0x0 forever Validators didn't start; check vc-* service logs; verify keystore mounting
Connection refused on assertoor port Service still booting; or assertoor not in additional_services in the YAML
kurtosis service logs truncates very long logs Use kurtosis enclave dump for the full per-service log files
caplin-minimal config fails with "binary not found" Confirm BINARIES="erigon caplin" in the docker build args
eth_blockNumber advances but assertoor reports timeout Slot time / preset mismatch; check seconds_per_slot and preset in YAML
Erigon image stale despite rebuild docker image rm test/erigon:current && docker build ... to force; check BuildKit cache scope
Port already allocated Another enclave is running — kurtosis enclave ls then kurtosis enclave rm -f <old>
Engine API JWT mismatch in EL logs Check kurtosis service exec <enclave> el-1-erigon-… "ls -la /jwt/"; restart enclave if missing

Grade adjusted by static analysis guardrails

AI scored this skill as grade A, but static analysis findings capped it to B:

  • • Recursive deletion pattern (rm -rf) (max: B)

Overall Score

86/100

Grade

B

Good

Grades are signals, not a certification. Always review a skill yourself before use.

Safety

87

Quality

88

Clarity

84

Completeness

82

Summary

This skill runs a local Kurtosis Ethereum testnet against a locally-built erigon Docker image, orchestrating the full test lifecycle: building the image, starting an enclave, monitoring block progress and assertoor health, detecting failures via cross-client comparison, and iterating a fix-rebuild-rerun loop. It is designed to mirror the CI workflow at `.github/workflows/test-kurtosis-assertoor.yml` but run interactively on the user's machine.

Static Analysis Findings

2 findings

Patterns detected by deterministic static analysis before AI scoring. Hover over any finding code for detailed information and remediation guidance.

Destructive Operation
SEC-001Recursive DeletionMax: B

Recursive deletion pattern (rm -rf)

SKILL.mdrm -rf
Command Injection
SEC-011Dynamic Shell Eval

Shell eval/exec of dynamic content

SKILL.mdexec`

Detected Capabilities

shell executionfile readfile writefile editdirectory globbingtext grepweb fetchdocker buildkurtosis CLI orchestrationlog streamingJSON parsing (jq)curl HTTP requestsrecursive directory operations

Trigger Keywords

Phrases that agents use to match this skill to user intent.

debug erigon locallyrun kurtosis testnetreproduce consensus failuretriage ethereum client bugvalidate erigon changescross-client comparison

Risk Signals

INFO

rm -rf used in cleanup section (kurtosis enclave dump refuses if destination exists, so pre-cleanup needed)

SKILL.md | Cleanup section: 'kurtosis enclave rm -f $ENCLAVE' (non-recursive context)
INFO

exec keyword in triage plumbing section (kurtosis service exec used for container diagnostics)

SKILL.md | Triage table + rule-out-enclave-plumbing section: 'kurtosis service exec' examples
INFO

Dynamic Docker image build (docker build -t test/erigon:current from source tree)

SKILL.md | Build the erigon docker image section
INFO

HTTP requests to external endpoints (curl to EL JSON-RPC, assertoor HTTP API, notes.ethereum.org)

SKILL.md | Monitor section, Spec lookup section
INFO

Outbound network fetch to notes.ethereum.org devnet specs (WebFetch allowed)

SKILL.md | Spec lookup section references https://notes.ethereum.org/@ethpandaops/<devnet>

Referenced Domains

External domains referenced in skill content, detected by static analysis.

docs.kurtosis.comnotes.ethereum.org

Use Cases

  • reproduce and debug erigon against ethereum-package configs locally
  • validate erigon changes before committing via a local testnet
  • triage consensus failures using cross-client comparison methodology
  • monitor block production and assertoor test execution in real time
  • iterate fixes to erigon source and rebuild without pushing to CI
  • detect and classify erigon bugs vs peer-client bugs vs config mismatches

Quality Notes

  • Clear prerequisites section with version constraints (Kurtosis ≥1.18.1 for GpuConfig) and fork detection guidance.
  • Excellent structured decision tree for triage (block-stall vs panic vs invalid-payload classification).
  • Comprehensive cross-reference to spec-lookup workflow via /erigon-implement-eip skill — avoids guessing on ambiguous EIP behavior.
  • Well-documented config→package-branch mapping table with CI source, reducing configuration drift risk.
  • Defensive polling logic: stall detection waits for >0 blocks to avoid false positives on genesis_delay (glamsterdam example given).
  • Thorough troubleshooting section (14 cases) with concrete remedies; e.g. GpuConfig workaround, docker cache invalidation, JWT mounting checks.
  • Per-attempt logging and max-attempts cap prevents infinite loops; auto=false mode allows user approval gates.
  • Always-run cleanup (enclave dump + rm) and dangling image pruning prevent accumulation of stale resources.
  • Detailed triage table maps symptoms (erigon panic, CL fork-choice, assertoor test failure) to likely owner and next action.
  • Argument parsing and env-variable binding documented clearly; default values and examples provided for each parameter.
Model: claude-haiku-4-5-20251001Analyzed: Aug 20, 2026

Reviews

Add this skill to your library to leave a review.

No reviews yet

Be the first to share your experience.

Use fauziyasin-s2m4qw/kurtosis-test in your dev environment

Command Palette

Search for a command to run...