Merge remote-tracking branch 'box/class-v5-foreign-capture-doc' into same-work-harness

This commit is contained in:
igneum-labs 2026-10-08 22:00:56 +00:00
commit f6300f1177

View file

@ -96,6 +96,7 @@ The daily dataset of section 2 proves the miner holds the chain once a day. Proo
| How a miner learns it | the template's `powEpoch` carries `stateBlock` (the hash of `C_w`) and `nextStateBlock` once the next window's cut has passed (one lead before the boundary, with `nextEpochSeed`); the miner fetches the stream for that block from its node (`igneum_getPowStateLeaves [block hash]`), checks nothing when the node is its own (its node executed it), and prepares the next pair; the pack carries `IGNEUM_STATE_BLOCK_HEX`, `IGNEUM_STATE_ROOT_HEX` and `leaves.bin` | the existing next-epoch prepare: with `W = epoch_blocks` the refresh and the program swap are one swap |
| The grace at the boundary | none in validation (a block's window is its DAA score's; a block mined with the previous window's leaves after the boundary is invalid); the grace is the lead: the next window's leaves are knowable ten minutes before it, the miner prepares the pair then, and a prepared worker swaps with no pause (the 2.0 hot-swap path). An unprepared worker rebuilds at the boundary: 32 ms of hashing lost on a 4090, under 0.001 percent of the window | no hash-rate dip by construction, as the epoch swap has none today |
| A miner whose node is behind | the node serves no template for a window whose `C_w` it has not executed (section 5's refusal), so the miner's hash stops at the boundary rather than mining invalid blocks | a node ten minutes behind the chain is already a node without a useful template |
| Several candidate streams per epoch (fix 2, 8 October 2026) | the streams a node serves are keyed by BLOCK HASH, never one per epoch: this chain's cut block, and the cut blocks of other chains whose headers this node validated (section 8's amended row), each with the retention of a capture (two epochs). Nothing in this design assumed one stream per epoch per node: the leaves are keyed by each stream's own root `R_w` and the pairing check is per stream | a node that can only serve its own chain's stream can never weigh another chain's post-cut headers, so a cut was a partition that never healed (igneum-devnet-4, 8 October 2026) |
### 2a.2 What a pool can and cannot centralise, and the farm attack, with the numbers
@ -141,6 +142,13 @@ What a pool can centralise: templates (as today), the day key (public), the stre
| Timestamp or DAA grinding at the cut | the cut is a DAA score, as the epoch seed's; a producer chooses at most whether its own block is `C_w` (one bit between two honest states) |
| Two windows of leaves in RAM on the node and the worker | the current and the next: 12 KB today, at most 4 GiB at the sample cap; the worker frees the leaves after the build (1,803 MiB resident while hashing, measured) |
### 2a.5 The cost of candidate streams (fix 2, the foreign-seed capture; the HEAL lane, 8 October 2026)
| Row | Cost | Note |
|---|---|---|
| Candidate streams held for validation | one dataset refresh `D` per candidate stream (6 KB of leaves today), held for the retention of a capture (two epochs) | the miner hashes on its own chain's stream only; a candidate is for validating another chain's headers, which is what lets their weight count and the node switch |
| The grinding and DoS face | at most 2 x epoch_blocks of EVM execution per capture, one capture in flight, a 60 s memory of a refusal; the capture runs only for a block this node holds as a VALIDATED header (its chain passed proof of work up to that block), never for an unvalidated one | anyone mining a fork off a pre-cut block makes the node execute from its snapshot base; without the bound, the one-in-flight rule and the PoW-first condition the executor is a free compute oracle, so the three are named here as properties of the rule, to keep |
## 3. A miner with a pruned node, and what a chip must hold
| Who | What they hold | How they get it |
@ -228,7 +236,7 @@ Per tier, what the 4090 rows mean: every NVIDIA card from 8 to 32 GB keeps its h
|---|---|
| A miner served stale state (yesterday's stream, a fork's stream, a tampered stream) | every item it builds is wrong, every block it submits is rejected by every node that holds the day's state, and it learns it from the first rejection; a worker that takes the stream from its own node checks nothing (its node executed it), a worker or pool miner that takes it from elsewhere runs `DayStream::check` against the `R_d` its node reports (`igneum_getPowDayState` returns the root with the block) |
| A pool serving wrong state to its miners | the pool loses every share it pays for; the pool's own node rejects its miners' solutions; there is no way to profit from a wrong dataset, only to waste the pool's hash |
| A state the verifier cannot reach (a header whose `C_d` the node never executed: a fork deeper than the lead) | the engine refuses the header with the retryable error and says which block it lacks; a fork that deep is a merge-depth-scale reorg, which the follower re-executes when the chain adopts it ("a deep reorg never resets execution", 6 October rule), after which the header validates; a chain the node never adopts is a chain it never needs the state of |
| A state the verifier cannot reach (a header whose `C_d` the node never executed: another chain's cut block, from a partition, a ban storm or a lag across the cut) | AMENDED by fix 2 of node 2.0.2 (the HEAL lane, 8 October 2026, night; igneum-devnet-4 had fragmented into 6 chains and 11 one-box islands at the epoch-3 cut because the retryable refusal never ended: the other chain's post-cut headers never validated, their weight never counted, the virtual never moved and the executor never captured the other chain's block). The refusal is now the BOUNDED EXECUTION of the foreign selected-parent chain: a miss queues the block (16, deduplicated; the provider runs inside header validation and never takes the state lock) and returns the same wait error; the follower services one block per pass: the block must be a header this node validated (PoW first), its selected-parent chain is walked down to the fork point (the first block this executor holds a record of), a scratch state starts from the nearest held state at or below it (the ring, else the newest epoch capture; the snapshot base when started from one), this chain's blocks to the fork point and the foreign chain up to the block are executed on the scratch by the follower's own chain-block function (the proof verdict by the acceptance rule, the same the live path pays by), the stream after the block is serialised, checked against its root and published under the block's hash; the IBD's 2 s retry finds it and the lighter node switches. Bounds, which are PROPERTIES and not tunables: one capture in flight, at most 2 x epoch_blocks of execution, only a validated header; a chain that cannot be executed (a body missing, a fork below every held state, the bound passed) is refused once with a line and remembered (60 s, 10 s for a body the IBD is still bringing). Measured on two real nodes split across a cut (fork docs/igneum-foreign-seed-capture.md): without it the lighter node never left its chain in 300 s; with it, it switched at +20 s and both read one executed tip and epoch seed from +60 s on. A chain the node never adopts is still a chain it executed once on a scratch and dropped |
| The executor is behind the cut when the day starts (a slow node, a node that restarted) | it refuses to validate and to mine v5 headers until it passes `cut(d)` and says so once per epoch at warn, then at debug (the class signal's `first_time` shape); with the lead at one hour this is a node an hour behind the chain, which is already a node that serves no useful template |
| A node without an executor (`--evm-disable`) | states at start that it cannot validate or mine class v5; after the flip it relays headers it cannot check as it relays blocks whose bodies it has not fetched, and accepts nothing it cannot validate (the same as a node without the day's cache slot today: `BuildQueueFull`) |
| The era draw's interaction | the era draws the layout (`t(w)`, `j(w)`, the stride and the window of each load) and nothing about the leaves; the leaf XOR sits before the first mixer of item `t`, after the layout has named `t`, so every era's dataset of a day is built from one leaf array and the hash kernel of every era is unchanged; the era draws 8 and 9 stay consumed and unused as the ladder left them |