# Execution-layer attacks (robustness and conformance) Adversarial tests of the Igneum execution layer (`docs/design/execution-layer.md`, `docs/spec/07-execution.md`) against a throwaway 3-node `igneumd` simnet. Each scenario is a runnable command with a pass criterion taken from the design and a measured result. This is testing of our own private software. Everything runs on ports 27600 and above under `/tmp/igneum-exec-attacks`. The live devnet (26610, 26611, 26640, 26641, 28640) and other agents' ports (up to 27599) are never touched. ## Build The node, the honest miner and the hostile injector are built in the worktree `vendor/igneum-node-exec-attacks` (branch `exec-attacks`): ``` cd vendor/igneum-node-exec-attacks export PATH="$HOME/.rustup/toolchains/stable-aarch64-apple-darwin/bin:$PATH" CARGO_TARGET_DIR=target nice -n 19 cargo build --release -j 4 -p kaspad -p igneum-miner --features igneum-pow ``` This produces `igneumd`, `igneum-miner` and `igneum-inject` under `target/release`. `igneum-inject` is the hostile miner: it fetches a block template over gRPC, replaces the EVM body with an arbitrary set of raw EIP-2718 bytes (which the mempool would never hand out), recomputes `hash_merkle_root` and resubmits, so transactions the mempool rejects reach consensus body validation and the executor directly. Several blocks built off one template share a selected parent and land in parallel on the DAG. ## Contracts `node compile.mjs` compiles `contracts/PgasBomb.sol` (modexp/keccak loops, cheap in gas and heavy in pgas) and `contracts/RegistryAbuse.sol` (a Worker and a Factory for the developer-registry tests) with solc 0.8.37. ## Network ``` ./net.sh start [1|3] # hub topology: 3 nodes, 1 or 3 honest stub miners ./net.sh start-split # partition P1={node1}, P2={node2,node3}, no link until heal (igneum-inject addpeer) ./net.sh stop ``` Nodes run `--simnet --enable-unsynced-mining --unsaferpc` (PoW skipped). eth JSON-RPC on 27690/27691/27692, gRPC on 27610/27620/27630, p2p on 27611/27621/27631. Node 1's miner pays the test `miner` account; nodes 2 and 3 pay the test accounts B and C, so rewards are spendable by the harness whichever chain wins. ## Scenarios (run in priority order 1, 2, 5, 3, 6, 4) | # | Command | What it does | Criterion | |---|---|---|---| | 1 | `node scenario1_malformed.mjs` | malformed and boundary txs over `eth_sendRawTransaction` and inside a hostile block (bad RLP, wrong chain id, oversized calldata, gas at/over the block limit, bad signature, nonce far ahead, nonce reuse, zero/max fee) | state-free faults invalidate the block; state-dependent faults skip the tx with no receipt; no panic; RSS bounded | | 2 | `node scenario2_nonce.mjs` | one sender's nonces spread across parallel blocks in different orders, duplicates in several blocks, a conflicting same-nonce pair | exactly one execution per nonce; deterministic; state roots identical on all nodes | | 5 | `node scenario5_rpcfuzz.mjs` | every `eth_*`/`igneum_*` with junk params, huge arrays, deep nesting; 50x `eth_call` flood from one client | errors not crashes; honest latency under 200 ms | | 3 | `node scenario3_pgas.mjs` | modexp loops cheap in gas, heavy in pgas, with growing loop counts | the per-block pgas budget `B_p` caps inclusion; no executed block exceeds `B_p`; execution time per block measured | | 6 | `./run_scenario6.sh` | partition/heal reorgs of several depths with hostile miners while txs flow (uses `start-split` and `igneum-inject addpeer`) | state root recomputed deterministically; displaced-tx receipts consistent on all nodes and canonical; no stuck mempool | | 4 | `node scenario4_registry.mjs` | register a payee for someone else's code; factory inheritance (CREATE, CREATE2, same-tx override, unregistered, EOA override); self-dealing (sender = payee = miner) | design 4.5: base fees burned, no positive-expectation loop; records the max share a self-dealer recovers | `run_all.sh` runs every scenario in priority order (starting and stopping the right network for each) and prints a one-line pass/fail per scenario. Per-scenario detail lands in `results/*.json`. Notes on DAG semantics observed here: a selected-chain reorg does not orphan merged blocks (design 1.2/1.3), so a "displaced" transaction re-executes exactly once in the segment that merges its block rather than losing its receipt; scenario 6 checks for a consistent, canonical outcome across nodes rather than Ethereum-style eviction.