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>
61 lines
4.4 KiB
Markdown
61 lines
4.4 KiB
Markdown
# 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.
|