|
| 1 | +--- |
| 2 | +name: kurtosis-devnet |
| 3 | +description: Run Ethereum multi-client devnets using Kurtosis and the ethpandaops/ethereum-package. Use for spinning up local testnets, validating cross-client interop, testing fork transitions, running assertoor checks, debugging CL/EL client interactions, or verifying new feature implementations across multiple consensus and execution clients. |
| 4 | +--- |
| 5 | + |
| 6 | +# Kurtosis Devnet |
| 7 | + |
| 8 | +Run Ethereum consensus/execution client devnets via [Kurtosis](https://github.com/kurtosis-tech/kurtosis) + [ethereum-package](https://github.com/ethpandaops/ethereum-package). |
| 9 | + |
| 10 | +## Prerequisites |
| 11 | + |
| 12 | +- [Kurtosis CLI](https://docs.kurtosis.com/install/) installed |
| 13 | +- Docker running with sufficient resources (8GB+ RAM recommended for multi-client devnets) |
| 14 | + |
| 15 | +## Quick Start |
| 16 | + |
| 17 | +```bash |
| 18 | +# Start devnet from config |
| 19 | +# Always use --image-download always to ensure external images are up to date |
| 20 | +kurtosis run github.com/ethpandaops/ethereum-package \ |
| 21 | + --enclave <name> \ |
| 22 | + --args-file network_params.yaml \ |
| 23 | + --image-download always |
| 24 | + |
| 25 | +# List enclaves |
| 26 | +kurtosis enclave ls |
| 27 | + |
| 28 | +# Inspect services |
| 29 | +kurtosis enclave inspect <name> |
| 30 | + |
| 31 | +# View logs |
| 32 | +kurtosis service logs <enclave> <service-name> |
| 33 | +kurtosis service logs <enclave> <service-name> --follow |
| 34 | + |
| 35 | +# Cleanup |
| 36 | +kurtosis enclave rm -f <name> |
| 37 | +# Or clean all enclaves |
| 38 | +kurtosis clean -a |
| 39 | +``` |
| 40 | + |
| 41 | +## Config File (`network_params.yaml`) |
| 42 | + |
| 43 | +See `references/config-reference.md` for the full config structure. |
| 44 | + |
| 45 | +Key sections: |
| 46 | + |
| 47 | +- `participants`: list of CL+EL client pairs with images, flags, validator counts |
| 48 | +- `network_params`: fork epochs, slot time, network-level settings |
| 49 | +- `additional_services`: dora (explorer), assertoor (testing), prometheus, grafana |
| 50 | +- `assertoor_params`: automated chain health checks |
| 51 | +- `port_publisher`: expose CL/EL ports to host |
| 52 | + |
| 53 | +## Building Custom Client Images |
| 54 | + |
| 55 | +When testing local Lodestar branches, build a Docker image first: |
| 56 | + |
| 57 | +```bash |
| 58 | +# Fast build (recommended for iteration) |
| 59 | +cd ~/lodestar && docker build -t lodestar:custom -f Dockerfile.dev . |
| 60 | + |
| 61 | +# Production build (slower, for final validation only) |
| 62 | +cd ~/lodestar && docker build -t lodestar:custom . |
| 63 | + |
| 64 | +# Then reference in config: |
| 65 | +# cl_image: lodestar:custom |
| 66 | +``` |
| 67 | + |
| 68 | +**Always use `Dockerfile.dev` for iterative development.** It caches dependency layers and rebuilds in seconds vs minutes for the production Dockerfile. Only use the production `Dockerfile` for final validation or debugging build issues. |
| 69 | + |
| 70 | +## Service Naming Convention |
| 71 | + |
| 72 | +Kurtosis names services as: `{role}-{index}-{cl_type}-{el_type}` |
| 73 | + |
| 74 | +Examples: |
| 75 | + |
| 76 | +- `cl-1-lodestar-reth` — first CL node (Lodestar with Reth EL) |
| 77 | +- `el-1-reth-lodestar` — corresponding EL node |
| 78 | +- `vc-1-lodestar-reth` — validator client |
| 79 | + |
| 80 | +## Accessing Services |
| 81 | + |
| 82 | +After `kurtosis enclave inspect <name>`, find mapped ports: |
| 83 | + |
| 84 | +```bash |
| 85 | +# CL beacon API (find actual port from inspect output) |
| 86 | +curl http://127.0.0.1:<mapped-port>/eth/v1/node/syncing |
| 87 | + |
| 88 | +# Or use port_publisher for predictable ports: |
| 89 | +# port_publisher: |
| 90 | +# cl: |
| 91 | +# enabled: true |
| 92 | +# public_port_start: 33000 # cl-1=33000, cl-2=33005, etc. |
| 93 | +# el: |
| 94 | +# enabled: true |
| 95 | +# public_port_start: 32000 |
| 96 | +``` |
| 97 | + |
| 98 | +Port publisher assigns sequential ports (step of 5 per service). |
| 99 | + |
| 100 | +## Assertoor (Automated Testing) |
| 101 | + |
| 102 | +Add to config: |
| 103 | + |
| 104 | +```yaml |
| 105 | +additional_services: |
| 106 | + - assertoor |
| 107 | + |
| 108 | +assertoor_params: |
| 109 | + run_stability_check: true # chain stability, finality, no reorgs |
| 110 | + run_block_proposal_check: true # every client pair proposes a block |
| 111 | +``` |
| 112 | +
|
| 113 | +Check results via the assertoor web UI (port shown in `kurtosis enclave inspect`). |
| 114 | + |
| 115 | +## Common Devnet Patterns |
| 116 | + |
| 117 | +### Fork Transition Testing |
| 118 | + |
| 119 | +```yaml |
| 120 | +network_params: |
| 121 | + electra_fork_epoch: 0 |
| 122 | + fulu_fork_epoch: 1 # fork at epoch 1 (slot 32) |
| 123 | + seconds_per_slot: 6 # faster for testing |
| 124 | +``` |
| 125 | + |
| 126 | +### Mixed-Client Topology (Cross-Client Interop) |
| 127 | + |
| 128 | +```yaml |
| 129 | +participants: |
| 130 | + - cl_type: lodestar |
| 131 | + el_type: reth |
| 132 | + count: 2 |
| 133 | + validator_count: 128 |
| 134 | + - cl_type: lighthouse |
| 135 | + el_type: geth |
| 136 | + count: 2 |
| 137 | + validator_count: 128 |
| 138 | +``` |
| 139 | + |
| 140 | +### Observer Nodes (No Validators) |
| 141 | + |
| 142 | +```yaml |
| 143 | +- cl_type: lodestar |
| 144 | + cl_image: lodestar:custom |
| 145 | + el_type: reth |
| 146 | + count: 1 |
| 147 | + validator_count: 0 # observer-only |
| 148 | +``` |
| 149 | + |
| 150 | +### Supernode Mode |
| 151 | + |
| 152 | +Set `supernode: true` to run beacon+validator in a single process (faster startup, simpler topology): |
| 153 | + |
| 154 | +```yaml |
| 155 | +- cl_type: lodestar |
| 156 | + el_type: reth |
| 157 | + supernode: true |
| 158 | + validator_count: 128 |
| 159 | +``` |
| 160 | + |
| 161 | +### Extra CL/VC Params |
| 162 | + |
| 163 | +```yaml |
| 164 | +cl_extra_params: |
| 165 | + - --targetPeers=8 |
| 166 | + - --logLevel=debug |
| 167 | +vc_extra_params: |
| 168 | + - --suggestedFeeRecipient=0x... |
| 169 | +``` |
| 170 | + |
| 171 | +## Monitoring & Debugging |
| 172 | + |
| 173 | +```bash |
| 174 | +# Stream logs from a specific service |
| 175 | +kurtosis service logs <enclave> cl-1-lodestar-reth --follow |
| 176 | +
|
| 177 | +# Save all CL logs for analysis |
| 178 | +for svc in $(kurtosis enclave inspect <enclave> 2>/dev/null | grep -oE 'cl-[0-9]+-[^[:space:]]+'); do |
| 179 | + kurtosis service logs <enclave> $svc > "/tmp/${svc}.log" 2>&1 |
| 180 | +done |
| 181 | +
|
| 182 | +# Dora explorer (if enabled) — find port via inspect output |
| 183 | +
|
| 184 | +# Check chain finality |
| 185 | +curl -s http://127.0.0.1:<port>/eth/v1/beacon/states/head/finality_checkpoints | jq |
| 186 | +
|
| 187 | +# Check peer count |
| 188 | +curl -s http://127.0.0.1:<port>/eth/v1/node/peers | jq '.data | length' |
| 189 | +
|
| 190 | +# Check sync status |
| 191 | +curl -s http://127.0.0.1:<port>/eth/v1/node/syncing | jq |
| 192 | +``` |
| 193 | + |
| 194 | +### Wait for Finality |
| 195 | + |
| 196 | +Finality typically takes 2-3 epochs after genesis. With `seconds_per_slot: 6` and 32 slots/epoch: |
| 197 | + |
| 198 | +- 1 epoch ≈ 192s (3.2 min) |
| 199 | +- First finalization ≈ epoch 3-4 boundary (≈10-13 min) |
| 200 | + |
| 201 | +Monitor: |
| 202 | + |
| 203 | +```bash |
| 204 | +curl -s http://<port>/eth/v1/beacon/states/head/finality_checkpoints | jq '.data.finalized.epoch' |
| 205 | +``` |
| 206 | + |
| 207 | +### Acceptance Criteria Pattern |
| 208 | + |
| 209 | +For interop validation, define acceptance criteria **before** running the soak test: |
| 210 | + |
| 211 | +```bash |
| 212 | +# Example: check for zero errors across all CL nodes |
| 213 | +for svc in cl-1-lodestar-reth cl-2-lodestar-reth; do |
| 214 | + echo "=== $svc ===" |
| 215 | + kurtosis service logs <enclave> $svc 2>&1 | grep -c "ERROR" || echo "0 errors" |
| 216 | +done |
| 217 | +``` |
| 218 | + |
| 219 | +## Troubleshooting |
| 220 | + |
| 221 | +| Issue | Fix | |
| 222 | +| ------------------------ | ------------------------------------------------------------------------- | |
| 223 | +| Peers not connecting | Increase `--targetPeers`, check `directPeers` config | |
| 224 | +| No finality | Need ≥2/3 validators attesting; check VC logs | |
| 225 | +| "discv5 has no boot enr" | Harmless startup warning, ignore | |
| 226 | +| Port conflicts | Change `public_port_start` or stop conflicting enclaves | |
| 227 | +| Image not found | Ensure Docker image is built locally or available in registry | |
| 228 | +| Slow startup | Use `Dockerfile.dev` for local builds; reduce validator count | |
| 229 | +| Docker bind error | Use non-default port ranges; `kurtosis clean -a` between runs | |
| 230 | +| Stale enclave state | Always `kurtosis clean -a` before relaunching (not `docker system prune`) | |
| 231 | + |
| 232 | +## Tips |
| 233 | + |
| 234 | +- **Start small:** 2+2 nodes for iteration, scale up for final validation |
| 235 | +- **Test both roles:** Validator nodes show different bugs than observers — always check both |
| 236 | +- **Multiple soak passes:** First clean run may miss edge cases. Run extended soaks (hours). |
| 237 | +- **`kurtosis clean -a`** between runs — never use `docker system prune` (it can remove Kurtosis engine images) |
| 238 | +- **Debug logs:** Set `global_log_level: "debug"` for verbose output from all nodes |
| 239 | +- **Per-participant log levels:** Use `cl_log_level` / `el_log_level` on individual entries for targeted debugging |
| 240 | + |
| 241 | +--- |
| 242 | + |
| 243 | +## Self-Maintenance |
| 244 | + |
| 245 | +If any commands, file paths, URLs, or configurations in this skill are outdated or no longer work, update this SKILL.md with the correct information after completing your current task. Skills should stay accurate and self-healing — fix what you find broken. |
0 commit comments