igneum/tools/exec-attacks
igneum-labs 1037d06276 exec-attacks: execution-layer attack suite (tools + bench log)
Adversarial robustness and conformance tests of the execution layer against a
throwaway 3-node simnet on ports 27600+. Six scenarios, each a runnable command
with a design-derived pass criterion and a measured result: malformed/boundary
txs, nonce games across parallel blocks, RPC fuzz, pgas exhaustion, reorgs under
execution, and developer-registry abuse. 98 checks, 0 failures, 0 node panics.

Two findings filed in the bench-log entry: the mempool admits txs with gas_limit
above B_e (low), and an over-pgas-budget tx is executed natively in full before
being skipped for no fee (medium, griefing).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 22:57:19 +00:00
..
contracts exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
lib exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
.gitignore exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
compile.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
net.sh exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
README.md exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
run_all.sh exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
run_scenario6.sh exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
scenario1_malformed.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
scenario2_nonce.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
scenario3_pgas.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
scenario4_registry.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
scenario5_rpcfuzz.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00
scenario6_reorg.mjs exec-attacks: execution-layer attack suite (tools + bench log) 2026-10-03 22:57:19 +00:00

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.