igneum/tools/harness/README.md
igneum-labs 90478b5cf4 Harness s8 steady state and the bench-log paragraph: RSS per 1,000 blocks before and after the M30 fix
s8-steady.mjs: two nodes, one honest vmine miner at 1 block/s, no flood,
RSS and cache-build count every 60 s, vmmap -summary at 0, 500, 1,000 and
1,500 blocks. Both builds ran 1,500 blocks on the 60x profile: before
41 to 1,342 MB by 514 blocks (9 cache builds, five 256 MiB chunks resident:
KEEP 4 plus one evicted chunk the allocator keeps) then flat, 27 builds in
1,529 blocks; after 319 MB at 510 blocks (1 build), 589 at 1,029 (the second
day's cache, by design), 603 at 1,526, 2 builds. Residual 30 MB per 1,000
blocks on both builds, read as the consensus database and caches filling,
not the PoW cache. JSON and vmmap files under
docs/benchmarks/memory-floods-2026-10-04/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-05 01:49:46 +00:00

96 lines
6.9 KiB
Markdown

# Igneum consensus attack harness
Plays the standard consensus-attack catalogue against a private test network of our own Igneum nodes, with a pass
criterion per scenario taken from the spec and a measured result for each. This is robustness and conformance
testing of our own devnet software, the practice upstream Kaspa (`simpa`, `testing/integration`) and the Ethereum
clients follow.
## What it is built on
- The in-process network simulator from rusty-kaspa (`kaspa_utils::sim`, the engine `simpa` uses): one real
`Consensus` per node in virtual time. The harness adds hash-share miners, a withholder, timestamp policies, a
cut-and-heal topology and reorg records. Source: `vendor/igneum-node-harness/igneum/harness-sim`.
- Real `igneumd` processes on `127.0.0.1`, driven over wRPC JSON, for the live scenarios (5, 6, 7 Part B).
- A p2p probe that speaks the fork's own protocol (handshake, `InvRelayBlock`, `RequestRelayBlocks`, `Block`) to
deliver malformed blocks on the wire. Source: `vendor/igneum-node-harness/igneum/p2p-probe`.
All nodes run with `skip_proof_of_work` (the harness never hashes), so the harness controls each miner's hash share
exactly. Every other consensus rule (timestamps, DAA, GHOSTDAG, merge depth, mass, coinbase) runs unchanged, so
the harness exercises the ordering layer, not a weakened copy of it.
## Ports and isolation
The test network uses `127.0.0.1` ports 27200 and up and data under `/tmp/igneum-harness`. It never touches the
live devnet (gRPC 26610, P2P 26611, observer 26640/26641/28640), the PC node at 192.168.68.67, or any port other
agents use (up to 27199). `IGNEUM_HARNESS_BASE_PORT=29500 IGNEUM_HARNESS_TMP=/tmp/my-harness` moves the ports (node
`i` takes base + 10i, proxies base + 900 + i) and the data and results directories, so two agents can run the
harness at once (4 October 2026). Loopback peers are never gossiped (`components/addressmanager/src/lib.rs`), so no link
forms that a scenario did not ask for. Everything the harness starts is stopped at the end, including on SIGINT.
## Build
The harness binaries live in the harness worktree of the node fork:
```
cd vendor/igneum-node/ && git worktree add -b harness ../igneum-node-harness HEAD # once
cd ../igneum-node-harness
CARGO_TARGET_DIR=target nice -n 19 ~/.cargo/bin/cargo build --release -j 4 \
-p kaspad -p igneum-miner --features kaspad/igneum-pow
CARGO_TARGET_DIR=target nice -n 19 ~/.cargo/bin/cargo build --release -j 4 \
-p igneum-p2p-probe -p igneum-harness-sim
```
This produces `target/release/igneumd`, `igneum-miner`, `igneum-p2p-probe` and `igneum-harness-sim`. The run
scripts find them there; override with `IGNEUMD`, `IGNEUM_P2P_PROBE`, `IGNEUM_HARNESS_SIM`.
## Run
```
node tools/harness/run.mjs # the full catalogue, priority order 5,2,1,3,6,4,7
node tools/harness/run.mjs s5 s2 --quick # named scenarios, short durations
node tools/harness/run.mjs --no-bench-log # do not append to docs/bench-log.md
node tools/harness/scenarios/s1-withhold.mjs --quick # one scenario on its own
node tools/harness/run.mjs s7 --quick --live-only # s7 Part B only (the vmine flood against real nodes; no simulator binary needed)
node tools/harness/run.mjs s3 s4 --fast-time # the 60x fast-time profile (infra/fast-time): merge depth 60 s, so the
# partition and eclipse cuts are 10, 30 and 62 s instead of 600, 1,800 and
# 3,700; nodes and the simulator come from vendor/igneum-node/target-integration
```
Each run appends one dated entry to `docs/bench-log.md` with a row per scenario (criterion, measured result, pass
or fail), writes full JSON per scenario under `/tmp/igneum-harness/results` and `/tmp/igneum-harness/sim`, and
leaves the test network stopped. Exit code is non-zero if any scenario failed.
## The catalogue
| # | Scenario | Where | Criterion (spec) |
|---|---|---|---|
| 1 | Withheld-block mining (10, 25, 33, 45% share, release every 5 and 20) | sim | spec 02 2.1: attacker blue-block share within 2 sigma of hash share over 2,000 blocks; reorg depth distribution recorded |
| 2 | Timestamp manipulation: past-median and future-time edges; a 33% miner stretching inside the rules | live + sim | spec 02 2.3: rejected exactly at the boundary; block-rate drift of the controller measured |
| 3 | Partition and heal (2, 10, 30 min, plus one beyond merge depth) | sim | spec 02 2.1: one chain after the merge-depth rule; reorg depth and time to heal recorded |
| 4 | Eclipse of one node (victim fed a slower side chain) | sim | spec 02 2.1: rejoins the honest chain on reconnection within the merge-depth bound |
| 5 | Malformed and boundary inputs on every p2p message and RPC method the fork touches | live + p2p | fork-divergence header and RPC rows; ledger M15: rejected without a crash or a cache build |
| 6 | Resource exhaustion (50x template, submit and mempool floods from one peer) | live | honest template p95 under 200 ms, node under its memory bound; numbers recorded |
| 7 | Fast-miner flood (50x joins at once, the devnet event) | live + sim | node stays responsive; controller trajectory recorded for the difficulty branch |
| 8 | Steady state: one honest miner at 1 block/s, no flood, 1,500 blocks (ledger M30 follow-up) | live | recorded only: RSS of the mining node and the follower at 0, 500, 1,000 and 1,500 blocks, cache builds, `vmmap -summary` at each milestone (`IGNEUM_STEADY_BLOCKS`, `IGNEUM_STEADY_MAX_MIN`) |
Scenario 5 overlaps the `r3-fixes` branch (ledger M15): that branch runs the cheap checks before the lottery
engine, caps cache builds and bans the peer. On this node branch (HEAD, `d62708a8`, before r3-fixes) the engine
keeps three caches, so a stream of bogus past-day headers can still force a build; scenario 5 records whether any
case built a 256 MiB cache, which is the quantity r3-fixes drives to zero.
## Stubs (finality and difficulty-controller branches)
Finality and the difficulty controller live on their own branches. Their scenarios are stubs here, with criteria
written and a one-line plan for running them once the branch is merged into the harness worktree. See
`scenarios/stubs.mjs`. In short: 1b (withhold vs vote weight), 3b (partition vs lock revocation, ledger F16), 4b
(eclipse vs presence window, ledger F2), 2b (timestamp stretch on the dual-lane controller), 7b (the 50x flood on
the dual-lane controller, against the kaspa-sampled baseline this harness records).
## Files
- `run.mjs` scenario runner and bench-log writer.
- `scenarios/s1..s7` one file per scenario; each exports `run({ quick })` and runs standalone.
- `scenarios/stubs.mjs` the finality and controller stubs.
- `lib/net.mjs` node processes, topology, TCP proxy for a cuttable link, cleanup.
- `lib/rpc.mjs` wRPC JSON client. `lib/miner.mjs` virtual miner and latency probe. `lib/address.mjs` devnet
address encoder. `lib/sim.mjs` runs `igneum-harness-sim`. `lib/report.mjs` results and bench-log entry.