release 0.3.18 plan: the cut at e69e8a39; ledger N7 carries the fix hash c6a62e00

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 06:56:04 +00:00
parent 32f02088e6
commit 7226bfe3e4
2 changed files with 5 additions and 1 deletions

View file

@ -2362,4 +2362,4 @@ Open 6 October 2026 22:43Z to the 0.3.17 publish (the fix rides 0.3.17; main's r
### N7. One block-tagged RPC request kills a node whose exec follower has no record yet
Open 7 October 2026 06:4xZ (found by the node lane's Mac headers-proof join gate: the joining node died at 66 percent of the headers stage with "index out of bounds: the len is 0 but the index is 0" at igneum/exec/src/rpc.rs, a local client having polled eth_getBlockByNumber("latest") on 127.0.0.1:26790). Class: `resolve_block` maps "latest" to `tip_number()` = `records.len().saturating_sub(1)` = 0 on an empty vector, and every method that then indexes `state.records[n]` (eth_getBlockByNumber, eth_getBlockTransactionCountByNumber, eth_getBlockReceipts, eth_getTransactionByBlockNumberAndIndex, eth_feeHistory's range, eth_getLogs' range, eth_call / eth_estimateGas / igneum_estimateGas through `simulate`) panics on a tokio worker; the node's panic hook exits the process. Every shipped tree carries it (0.3.17's 5899f603 and the 0.3.18 candidates). Exposure: a fresh or restarting node between the exec RPC binding and the follower's first record; the shipped app calls only eth_blockNumber and igneum_* methods (none index records), so no app-driven node died; a wallet or any third-party client takes the path. The testnet seeds at height 0 hold the genesis record and answered block 0 on 7 October 06:5xZ, so their window is a restart, not steady state. Mitigation on the public testnet RPC (main's order): the whole method class refused at the rpc-filter layer on seed1 until the fixed node is on every seed (BLOCKED_UNTIL_FIXED_NODE; seeds 2 and 3 expose no RPC; no devnet public RPC exists). Fix: every records index bounds-checked and "latest" on an empty state answering null, with a test on an empty state, by the node lane on ca3-v4-0318 (commit to be named), into release-0.3.18-node as its third merge; 0.3.18 carries it. The method map (7 October 07:0xZ, settled against a 0.3.17.1 cut): the dead node's site is eth_getBlockByNumber (rpc.rs:689 in 6e4ace3f). The shipped 0.3.17 Igneum Miner app sends twelve exec RPC methods, all from prover.rs: eth_blockNumber and eleven igneum_* reads and submits; no block-tagged eth_ method. Of those, igneum_getAssignedShards (records[from..=tip], from = max(tip-lookback, 1)) and igneum_getProofRecords (records[n] after resolve_block) panic on an empty vector too, but the prover loop sends them only once the node reads synced, and while unsynced sends only igneum_getExecStatus and igneum_getProvingStatus, which index nothing; on the devnet the exec follower holds records from the packaged restart block (27276) early in the block stage, before isSynced flips. The Igneum Wallet app sends eth_getBlockByNumber("latest") (igneum-wallet/src/evm.rs:58) to its own bundled node (26800). Public filter block extended to the five igneum_* allowlist reads that index records (getProofRecords, getAssignedShards, getSegment, getShardPlan, getTransactionStatus). Ruling (main): 0.3.18 carries the fix; no 0.3.17.1. Status: OPEN until 0.3.18 is live and the seeds run it.
Open 7 October 2026 06:4xZ (found by the node lane's Mac headers-proof join gate: the joining node died at 66 percent of the headers stage with "index out of bounds: the len is 0 but the index is 0" at igneum/exec/src/rpc.rs, a local client having polled eth_getBlockByNumber("latest") on 127.0.0.1:26790). Class: `resolve_block` maps "latest" to `tip_number()` = `records.len().saturating_sub(1)` = 0 on an empty vector, and every method that then indexes `state.records[n]` (eth_getBlockByNumber, eth_getBlockTransactionCountByNumber, eth_getBlockReceipts, eth_getTransactionByBlockNumberAndIndex, eth_feeHistory's range, eth_getLogs' range, eth_call / eth_estimateGas / igneum_estimateGas through `simulate`) panics on a tokio worker; the node's panic hook exits the process. Every shipped tree carries it (0.3.17's 5899f603 and the 0.3.18 candidates). Exposure: a fresh or restarting node between the exec RPC binding and the follower's first record; the shipped app calls only eth_blockNumber and igneum_* methods (none index records), so no app-driven node died; a wallet or any third-party client takes the path. The testnet seeds at height 0 hold the genesis record and answered block 0 on 7 October 06:5xZ, so their window is a restart, not steady state. Mitigation on the public testnet RPC (main's order): the whole method class refused at the rpc-filter layer on seed1 until the fixed node is on every seed (BLOCKED_UNTIL_FIXED_NODE; seeds 2 and 3 expose no RPC; no devnet public RPC exists). Fix: every records index bounds-checked and "latest" on an empty state answering null, with a test on an empty state, by the node lane on ca3-v4-0318 as c6a62e00 (every chain-block read bounds-checked; an exec state with no record answers an error; test on the empty state), into release-0.3.18-node as its third merge (e69e8a39, 06:53Z); 0.3.18 carries it. The method map (7 October 07:0xZ, settled against a 0.3.17.1 cut): the dead node's site is eth_getBlockByNumber (rpc.rs:689 in 6e4ace3f). The shipped 0.3.17 Igneum Miner app sends twelve exec RPC methods, all from prover.rs: eth_blockNumber and eleven igneum_* reads and submits; no block-tagged eth_ method. Of those, igneum_getAssignedShards (records[from..=tip], from = max(tip-lookback, 1)) and igneum_getProofRecords (records[n] after resolve_block) panic on an empty vector too, but the prover loop sends them only once the node reads synced, and while unsynced sends only igneum_getExecStatus and igneum_getProvingStatus, which index nothing; on the devnet the exec follower holds records from the packaged restart block (27276) early in the block stage, before isSynced flips. The Igneum Wallet app sends eth_getBlockByNumber("latest") (igneum-wallet/src/evm.rs:58) to its own bundled node (26800). Public filter block extended to the five igneum_* allowlist reads that index records (getProofRecords, getAssignedShards, getSegment, getShardPlan, getTransactionStatus). Ruling (main): 0.3.18 carries the fix; no 0.3.17.1. Status: OPEN until 0.3.18 is live and the seeds run it.

View file

@ -198,3 +198,7 @@ Windows run 37580969266 green (06:27Z): Igneum-Miner-Setup-0.3.18.exe 2fcaf093,
The Mac's headers-proof join gate killed its joining node mid-IBD on the canary-fix binaries: a panic at igneum/exec/src/rpc.rs ("index out of bounds: the len is 0 but the index is 0") on a tokio worker, and the node's panic hook exits the process. The site: eth_getBlockByNumber reading state.records[n] after resolve_block mapped "latest" to tip_number() = 0 on an exec state with no records (the follower still "waiting for consensus to sync"); a local client polled 127.0.0.1:26790 during the IBD and the node died at 66 percent of the headers stage. Every records index in that file is unchecked on both trees (0.3.17's 5899f603: rpc.rs lines 474, 550, 591 to 629; ae17ad00: 575, 651, 692 to 730), so any fresh or restarting node with the exec RPC bound and a client asking eth_getBlockByNumber("latest") before the follower has a record dies. The shipped app itself calls only eth_blockNumber and igneum_* methods (none index records by block number), so the live 0.3.17 risk is a wallet or third-party client on a fresh node mid-IBD; the fix (every index bounds-checked; "latest" on an empty state answers null; a test on an empty state) goes onto ca3-v4-0318 and into release-0.3.18-node as a third merge. The pin stays at ae17ad00 on origin and nothing is staged further until the third tip; the ae17ad00 canary runs on (nothing attached to its eth_ port) as an early read of the other rows.
**The 0.3.17.1 question settled (07:0xZ, main's rule: a hotfix only if the shipped app sends the panic class):** no. The dead harness node's site is eth_getBlockByNumber; the shipped app sends eth_blockNumber and eleven igneum_* methods only (grep of the whole 0.3.17 app tree); its two records-indexing polls (igneum_getAssignedShards, igneum_getProofRecords) run only after the node reads synced, when a devnet follower already holds records from the packaged restart block; the two it sends while unsynced index nothing. The wallet app sends eth_getBlockByNumber to its own node. Ledger N7 carries the method map. The public testnet filter now blocks ten eth_ and six igneum_ records-indexing methods (seed1, verified; tree copy committed).
## 16. The cut moves to release-0.3.18-node = e69e8a39 (the node lane, 06:53Z; the pin released)
ae17ad00 plus the third merge of ca3-v4-0318 (500ddd66): c6a62e00 (every chain-block read in the exec JSON-RPC bounds-checked; an exec state with no record answers an error instead of indexing; test on the empty state) and 500ddd66 (GetBlockTemplate stage timers: a debug line per request, an info line once per 10 s past 500 ms; the epoch-seed walk and the live sink tally memoised). One hand resolution: the payout parameter on igneum_getProofRecords beside the bounds-checked read. Box at e69e8a39: igneum-exec 26, kaspa-consensus 111 plus the two moved targets, p2p-flows 37, kaspad / rpc-service / testing-integration checks clean. Both chains restarted on it 06:55Z (the ae17ad00 logs kept in r0318/ae17/). The canary form gains two reads: an eth_ client polling the joining node's exec port through the IBD (the node survives), and the first slow "GetBlockTemplate N ms" line on the pool's loaded node after the publish.