diff --git a/docs/plans/igneum-2.0-test-registry.json b/docs/plans/igneum-2.0-test-registry.json index dbca0bdd3..39703072e 100644 --- a/docs/plans/igneum-2.0-test-registry.json +++ b/docs/plans/igneum-2.0-test-registry.json @@ -5216,21 +5216,21 @@ "manual_page": 38, "owner_lane": "enforced-proving lane (a6e8f84588b809d62)", "run_status": "PASS", - "evidence_path": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", - "run_id": "enforced-proving-20261008-02", - "updated": "2026-10-08T21:37:02.905Z", + "evidence_path": "docs/plans/proving-enforcement/cache-history-2.json", + "run_id": "enforced-proving-20261008-03", + "updated": "2026-10-08T22:24:23.945Z", "evidence_record": { "requirement_id": "EVM-08", "decision": "PASS", "method": "native", "cell": "harness:proving-enforcement", - "manifest_sha": "3f672661", - "run_id": "enforced-proving-20261008-02", - "evidence": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, "coverage": "partial: the controlled verifier upgrade as a key succession (both pairs embedded, the window, the start refusal) and its fast-time crossing; the mixed-client crossing on the live network and the failed-distribution step are not run", "release_identity": { - "commit": "3f672661", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5239,7 +5239,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T21:37:02.905Z" + "at": "2026-10-08T22:24:23.945Z" }, "dependency": "the first pin into elf/prior/ (the node lane, key-succession-pin, tonight by 21:00) and the devnet-4 succession height (main, by 22:00 under the floor rule)", "approvals": { @@ -5254,13 +5254,13 @@ "decision": "PASS", "method": "native", "cell": "harness:proving-enforcement", - "manifest_sha": "3f672661", - "run_id": "enforced-proving-20261008-02", - "evidence": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, "coverage": "partial: the controlled verifier upgrade as a key succession (both pairs embedded, the window, the start refusal) and its fast-time crossing; the mixed-client crossing on the live network and the failed-distribution step are not run", "release_identity": { - "commit": "3f672661", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5269,7 +5269,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T21:37:02.905Z" + "at": "2026-10-08T22:24:23.945Z" } } } @@ -5320,21 +5320,21 @@ "manual_page": 39, "owner_lane": "enforced-proving lane (a6e8f84588b809d62)", "run_status": "NOT RUN", - "evidence_path": "build-1:/srv/artefacts/tas/202-a284380b-5f50afd1/node-a284380b/box4-exec.log; docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json; build-1:/srv/artefacts/tas/202-a284380b-5f50afd1/node-a284380b/box4-consensus.log", - "run_id": "202-a284380b-5f50afd1", - "updated": "2026-10-08T22:11:37.839Z", + "evidence_path": "build-1:/srv/artefacts/tas/202-a284380b-5f50afd1/node-a284380b/box4-exec.log; docs/plans/proving-enforcement/cache-history-2.json; build-1:/srv/artefacts/tas/202-a284380b-5f50afd1/node-a284380b/box4-consensus.log", + "run_id": "enforced-proving-20261008-03", + "updated": "2026-10-08T22:24:23.945Z", "evidence_record": { "requirement_id": "ZKP-01", - "decision": "NOT RUN", + "decision": "PASS", "method": "native", - "cell": "suite:consensus", - "manifest_sha": "a284380b", - "run_id": "202-a284380b-5f50afd1", - "evidence": "build-1:/srv/artefacts/tas/202-a284380b-5f50afd1/node-a284380b/box4-consensus.log", + "cell": "harness:proving-enforcement", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, - "coverage": "partial: a proof-less or wrong-statement block refused by consensus", + "coverage": "partial: the seven refusals as named tests (no proof never inserted; a wrong proof and another program id invalidate the block; the honest record paid once) and the fast-time crossing with a modified producer; the genuine-proof positive control through ordinary network paths is the testnet's live read, not run here", "release_identity": { - "commit": "a284380b", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5343,7 +5343,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T22:11:37.839Z" + "at": "2026-10-08T22:24:23.945Z" }, "in_progress_since": "2026-10-08T19:32:50.856Z", "approvals": { @@ -5380,13 +5380,13 @@ "decision": "PASS", "method": "native", "cell": "harness:proving-enforcement", - "manifest_sha": "3f672661", - "run_id": "enforced-proving-20261008-02", - "evidence": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, "coverage": "partial: the seven refusals as named tests (no proof never inserted; a wrong proof and another program id invalidate the block; the honest record paid once) and the fast-time crossing with a modified producer; the genuine-proof positive control through ordinary network paths is the testnet's live read, not run here", "release_identity": { - "commit": "3f672661", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5395,7 +5395,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T21:37:02.905Z" + "at": "2026-10-08T22:24:23.945Z" }, "suite:consensus": { "requirement_id": "ZKP-01", @@ -5450,21 +5450,21 @@ "manual_page": 39, "owner_lane": "enforced-proving lane (a6e8f84588b809d62)", "run_status": "PASS", - "evidence_path": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", - "run_id": "enforced-proving-20261008-02", - "updated": "2026-10-08T21:37:02.905Z", + "evidence_path": "docs/plans/proving-enforcement/cache-history-2.json", + "run_id": "enforced-proving-20261008-03", + "updated": "2026-10-08T22:24:23.945Z", "evidence_record": { "requirement_id": "ZKP-02", "decision": "PASS", "method": "native", "cell": "harness:proving-enforcement", - "manifest_sha": "3f672661", - "run_id": "enforced-proving-20261008-02", - "evidence": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, "coverage": "partial: the pinned-id refusal on a real proof, the daemon's start refusal, key succession (the epochs, the accepted ids, a verdict counting only where its pair is accepted, the seven refusals on both sides of the height, the start refusal under a succession, the real-proof refusal of a pair the epoch does not accept) and the fast-time crossing of a scheduled succession with a real proof under each pair; the downgrade through an old node path is not run", "release_identity": { - "commit": "3f672661", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5473,7 +5473,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T21:37:02.905Z" + "at": "2026-10-08T22:24:23.945Z" }, "dependency": "the mixed-pair crossing needs the node lane's first pin into elf/prior/ (key-succession-pin, tonight by 21:00) and the next-pair proof (build-2:/home/build/enforced-fixtures/next-pair-block-56-shard-0-compressed.bin)", "approvals": { @@ -5488,13 +5488,13 @@ "decision": "PASS", "method": "native", "cell": "harness:proving-enforcement", - "manifest_sha": "3f672661", - "run_id": "enforced-proving-20261008-02", - "evidence": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, "coverage": "partial: the pinned-id refusal on a real proof, the daemon's start refusal, key succession (the epochs, the accepted ids, a verdict counting only where its pair is accepted, the seven refusals on both sides of the height, the start refusal under a succession, the real-proof refusal of a pair the epoch does not accept) and the fast-time crossing of a scheduled succession with a real proof under each pair; the downgrade through an old node path is not run", "release_identity": { - "commit": "3f672661", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5503,7 +5503,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T21:37:02.905Z" + "at": "2026-10-08T22:24:23.945Z" } } }, @@ -5536,21 +5536,21 @@ "manual_page": 39, "owner_lane": "enforced-proving lane (a6e8f84588b809d62)", "run_status": "NOT RUN", - "evidence_path": "build-1:/srv/artefacts/tas/202-a284380b-5f50afd1/node-a284380b/box4-exec.log; docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", - "run_id": "202-a284380b-5f50afd1", - "updated": "2026-10-08T22:11:37.839Z", + "evidence_path": "build-1:/srv/artefacts/tas/202-a284380b-5f50afd1/node-a284380b/box4-exec.log; docs/plans/proving-enforcement/cache-history-2.json", + "run_id": "enforced-proving-20261008-03", + "updated": "2026-10-08T22:24:23.945Z", "evidence_record": { "requirement_id": "ZKP-03", - "decision": "NOT RUN", + "decision": "PASS", "method": "native", - "cell": "suite:exec", - "manifest_sha": "a284380b", - "run_id": "202-a284380b-5f50afd1", - "evidence": "build-1:/srv/artefacts/tas/202-a284380b-5f50afd1/node-a284380b/box4-exec.log", + "cell": "harness:proving-enforcement", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, - "coverage": "partial: a snapshot under another digest refused, the epoch streams bound to the digest", + "coverage": "partial: the chain binding (a record signed for another network), the replay, a wrong block, a wrong shard, a stale record outside the window; the fork and replayed-sync steps are not run", "release_identity": { - "commit": "a284380b", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5559,7 +5559,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T22:11:37.839Z" + "at": "2026-10-08T22:24:23.945Z" }, "in_progress_since": "2026-10-08T19:32:50.856Z", "approvals": { @@ -5596,13 +5596,13 @@ "decision": "PASS", "method": "native", "cell": "harness:proving-enforcement", - "manifest_sha": "3f672661", - "run_id": "enforced-proving-20261008-02", - "evidence": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, "coverage": "partial: the chain binding (a record signed for another network), the replay, a wrong block, a wrong shard, a stale record outside the window; the fork and replayed-sync steps are not run", "release_identity": { - "commit": "3f672661", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5611,7 +5611,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T21:37:02.905Z" + "at": "2026-10-08T22:24:23.945Z" } } }, @@ -5644,21 +5644,21 @@ "manual_page": 40, "owner_lane": "enforced-proving lane (a6e8f84588b809d62)", "run_status": "PASS", - "evidence_path": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", - "run_id": "enforced-proving-20261008-02", - "updated": "2026-10-08T21:37:02.905Z", + "evidence_path": "docs/plans/proving-enforcement/cache-history-2.json", + "run_id": "enforced-proving-20261008-03", + "updated": "2026-10-08T22:24:23.945Z", "evidence_record": { "requirement_id": "ZKP-04", "decision": "PASS", "method": "native", "cell": "harness:proving-enforcement", - "manifest_sha": "3f672661", - "run_id": "enforced-proving-20261008-02", - "evidence": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, "coverage": "partial: an altered payout address (after signing and re-signed), a statement over altered rewards or payouts vetoed natively, the inputs commitment of P22 stage 1 (shard 0 and the aggregator carry the commitment the node recomputes) and the derivation commitment of stage 2 (the payouts derived in the guest from the carried records); the rewards' proved derivation is P22 stage 3, PENDING", "release_identity": { - "commit": "3f672661", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5667,7 +5667,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T21:37:02.905Z" + "at": "2026-10-08T22:24:23.945Z" }, "dependency": "the proof side of the derivation (P22 stages 1 to 3) changes both guests' public values and so both program ids, so it ships only through a key succession (docs/design/key-succession.md, key-succession-node 291ee6ae); stage 1 (the inputs commitment carried by shard 0 and the aggregator, vetoed natively) is on branch p22-stage-1 under test since 18:46 UK; the clocks (stage 1 09:00, stage 2 14:00, stage 3's design 18:00 UK on 9 October 2026) stand because the succession's first height is asked of main by 22:00 under the floor rule; without a named height the stages are built and tested on their branch and wait for the succession that carries them", "approvals": { @@ -5682,13 +5682,13 @@ "decision": "PASS", "method": "native", "cell": "harness:proving-enforcement", - "manifest_sha": "3f672661", - "run_id": "enforced-proving-20261008-02", - "evidence": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, "coverage": "partial: an altered payout address (after signing and re-signed), a statement over altered rewards or payouts vetoed natively, the inputs commitment of P22 stage 1 (shard 0 and the aggregator carry the commitment the node recomputes) and the derivation commitment of stage 2 (the payouts derived in the guest from the carried records); the rewards' proved derivation is P22 stage 3, PENDING", "release_identity": { - "commit": "3f672661", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5697,7 +5697,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T21:37:02.905Z" + "at": "2026-10-08T22:24:23.945Z" } } }, @@ -5730,21 +5730,21 @@ "manual_page": 40, "owner_lane": "enforced-proving lane (a6e8f84588b809d62)", "run_status": "PASS", - "evidence_path": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", - "run_id": "enforced-proving-20261008-02", - "updated": "2026-10-08T21:37:02.905Z", + "evidence_path": "docs/plans/proving-enforcement/cache-history-2.json", + "run_id": "enforced-proving-20261008-03", + "updated": "2026-10-08T22:24:23.945Z", "evidence_record": { "requirement_id": "ZKP-05", "decision": "PASS", "method": "native", "cell": "harness:proving-enforcement", - "manifest_sha": "3f672661", - "run_id": "enforced-proving-20261008-02", - "evidence": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, "coverage": "partial: a duplicate of a paid record pays nothing and the honest record pays once; the race, reorder and crash-recover steps are not run", "release_identity": { - "commit": "3f672661", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5753,7 +5753,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T21:37:02.905Z" + "at": "2026-10-08T22:24:23.945Z" }, "approvals": { "scope_approved": null, @@ -5767,13 +5767,13 @@ "decision": "PASS", "method": "native", "cell": "harness:proving-enforcement", - "manifest_sha": "3f672661", - "run_id": "enforced-proving-20261008-02", - "evidence": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, "coverage": "partial: a duplicate of a paid record pays nothing and the honest record pays once; the race, reorder and crash-recover steps are not run", "release_identity": { - "commit": "3f672661", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5782,7 +5782,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T21:37:02.905Z" + "at": "2026-10-08T22:24:23.945Z" } } }, @@ -5940,21 +5940,21 @@ "manual_page": 41, "owner_lane": "enforced-proving lane (a6e8f84588b809d62)", "run_status": "PASS", - "evidence_path": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", - "run_id": "enforced-proving-20261008-02", - "updated": "2026-10-08T21:37:02.905Z", + "evidence_path": "docs/plans/proving-enforcement/cache-history-2.json", + "run_id": "enforced-proving-20261008-03", + "updated": "2026-10-08T22:24:23.945Z", "evidence_record": { "requirement_id": "ZKP-08", "decision": "PASS", "method": "native", "cell": "harness:proving-enforcement", - "manifest_sha": "3f672661", - "run_id": "enforced-proving-20261008-02", - "evidence": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, "coverage": "partial: the body rule and the payment rule read one floor, a producer with its body rule off still pays nothing on honest nodes (the fast-time crossing); the replacement-prover and authority steps are not run; the proof side of the derivation is P22 stage 3, PENDING", "release_identity": { - "commit": "3f672661", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5963,7 +5963,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T21:37:02.905Z" + "at": "2026-10-08T22:24:23.945Z" }, "dependency": "the proof side of the derivation (P22 stages 1 to 3) changes both guests' public values and so both program ids, so it ships only through a key succession (docs/design/key-succession.md, key-succession-node 291ee6ae); stage 1 (the inputs commitment carried by shard 0 and the aggregator, vetoed natively) is on branch p22-stage-1 under test since 18:46 UK; the clocks (stage 1 09:00, stage 2 14:00, stage 3's design 18:00 UK on 9 October 2026) stand because the succession's first height is asked of main by 22:00 under the floor rule; without a named height the stages are built and tested on their branch and wait for the succession that carries them", "approvals": { @@ -5978,13 +5978,13 @@ "decision": "PASS", "method": "native", "cell": "harness:proving-enforcement", - "manifest_sha": "3f672661", - "run_id": "enforced-proving-20261008-02", - "evidence": "docs/plans/proving-enforcement/succession-360-window-300-vcache-7.json", + "manifest_sha": "c591e63b", + "run_id": "enforced-proving-20261008-03", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json", "in_progress": false, "coverage": "partial: the body rule and the payment rule read one floor, a producer with its body rule off still pays nothing on honest nodes (the fast-time crossing); the replacement-prover and authority steps are not run; the proof side of the derivation is P22 stage 3, PENDING", "release_identity": { - "commit": "3f672661", + "commit": "c591e63b", "lockfile": "", "binary": "", "network_object": "", @@ -5993,7 +5993,7 @@ }, "claim_impact": "", "reviewer": "", - "at": "2026-10-08T21:37:02.905Z" + "at": "2026-10-08T22:24:23.945Z" } } } @@ -6121,7 +6121,20 @@ "updated": "2026-10-08T22:22:49.946Z", "evidence_record": { "reason": "proving on the mining configuration is the fleet lane's", - "at": "2026-10-08T22:22:49.946Z" + "at": "2026-10-08T22:22:49.946Z", + "method": "static", + "requirement_id": "CAP-02", + "decision": "NOT RUN", + "reviewer": "", + "claim_impact": "", + "release_identity": { + "commit": "", + "lockfile": "", + "binary": "", + "network_object": "", + "activation": "", + "profile_hashes": "" + } }, "approvals": { "scope_approved": null, diff --git a/docs/plans/proving-enforcement/cache-history-2.json b/docs/plans/proving-enforcement/cache-history-2.json new file mode 100644 index 000000000..29cfbf49c --- /dev/null +++ b/docs/plans/proving-enforcement/cache-history-2.json @@ -0,0 +1,147 @@ +{ + "case": "cache-history", + "node": "/srv/builds/igneum-wt-cache/vendor/igneum-node/target/release/igneumd", + "proveBin": "/srv/builds/igneum-wt-cache/proving/igneum-prove/target/release", + "pair": { + "shard": "0x51cd8cba314a32b60fac393949a4571714da6d6656717d286011601b2a163fe7", + "aggregator": "0x05b395ec238f67406084f8a449d400f64c87dc62151daebf66695864727ded8a" + }, + "startedAt": "2026-10-08T21:53:32.526Z", + "phases": { + "context": { + "block": 165, + "submit": { + "accepted": true, + "block": "0x4259577a6be0f3cf3cb1f1512b8df758caa2790d5cafebc791a55076d9cd1839", + "keyHash": "0x6ce6a6cfb1e7d69f52e0764cd4ad005d244097ee9773f7918c2ab532ad73d2df", + "new": true, + "number": "0xa5", + "reason": "accepted", + "shard": 0 + }, + "observed": { + "paid": [], + "carried": [] + }, + "verifies": { + "cacheAnswers": 0, + "calls": 1, + "contextRefusals": 1, + "sp1": 0 + }, + "refusals": [ + "a carried proof record's proof does not verify: shard record block 165 shard 0 by 6ce6a6cfb1e7d69f52e0764cd4ad005d244097ee9773f7918c2ab532ad73d2df (proof f74f7f7daf2cdcb0): shard record block 165 shard 0 by 6ce6a6cfb1e7d69f52e0764cd4ad005d244097ee9773f7918c2ab532ad73d2df: the proof's public values hash to 0x58d620e493a23ebdacdec4f3c3f46e5936a18fbdcb3880341e2bb8b1555f7955, the record's statement is 0xd8191e436529a541554168fb68a9f6663485d6b743d1b49e15ec3bbf3a5c17f0, disconnecting from peer 127.0.0.1:49762." + ] + }, + "honest": { + "block": 94, + "submit": { + "accepted": true, + "block": "0x6e958a1b931c6eacce696422a6611151848bb65ca8d99c36510bd7fab2925c39", + "keyHash": "0x4a28017035561e93c93032f70dba67436b68506efd3ac761f608df142c8a7594", + "new": true, + "number": "0x5e", + "reason": "accepted", + "shard": 0 + }, + "observed": { + "paid": [ + { + "carrierNumber": "0x169", + "keyHash": "0x4a28017035561e93c93032f70dba67436b68506efd3ac761f608df142c8a7594", + "payout": "0xc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3", + "shard": 0, + "wei": "0x8cc4d14f7888000" + } + ], + "carried": [ + { + "signer": "0x4a28017035561e93c93032f70dba67436b68506efd3ac761f608df142c8a7594", + "carrier": "0x169", + "rejected": "", + "paidWei": "0x8cc4d14f7888000" + }, + { + "signer": "0x4a28017035561e93c93032f70dba67436b68506efd3ac761f608df142c8a7594", + "carrier": "0x16a", + "rejected": "shard already paid", + "paidWei": "0x0" + } + ] + }, + "verifies": { + "cacheAnswers": 1, + "calls": 2, + "contextRefusals": 1, + "sp1": 1 + } + }, + "invalid": { + "blocks": [ + 465, + 591 + ], + "submits": [ + { + "accepted": true, + "block": "0xb80c043ced2e770092ab96769fbba1533df9a9a7eb61e054cbd7a98c678f6af9", + "keyHash": "0x89470283d097209eb0287ba87717938b8642c19b12bb37b5e30ed2a4328f2aaf", + "new": true, + "number": "0x1d1", + "reason": "accepted", + "shard": 0 + }, + { + "accepted": true, + "block": "0xd64c1a9c8d6c901ae1ed2cb2c1afccfa35113dcf1d38704477dfbf6a810d79e3", + "keyHash": "0xaa474491707c6931a3d6d2fdb84e4d91fa3f3c57b7776b9c1fb1b01a1f96678a", + "new": true, + "number": "0x24f", + "reason": "accepted", + "shard": 0 + } + ], + "verifies": { + "after3a": { + "cacheAnswers": 1, + "calls": 3, + "contextRefusals": 1, + "sp1": 2 + }, + "after3b": { + "cacheAnswers": 2, + "calls": 3, + "contextRefusals": 1, + "sp1": 2 + } + }, + "refusals": { + "after3a": 2, + "after3b": 3 + }, + "reasons": [ + "a carried proof record's proof does not verify: shard record block 165 shard 0 by 6ce6a6cfb1e7d69f52e0764cd4ad005d244097ee9773f7918c2ab532ad73d2df (proof f74f7f7daf2cdcb0): shard record block 165 shard 0 by 6ce6a6cfb1e7d69f52e0764cd4ad005d244097ee9773f7918c2ab532ad73d2df: the proof's public values hash to 0x58d620e493a23ebdacdec4f3c3f46e5936a18fbdcb3880341e2bb8b1555f7955, the record's statement is 0xd8191e436529a541554168fb68a9f6663485d6b743d1b49e15ec3bbf3a5c17f0, disconnecting from peer 127.0.0.1:49762.", + "a carried proof record's proof does not verify: shard record block 465 shard 0 by 89470283d097209eb0287ba87717938b8642c19b12bb37b5e30ed2a4328f2aaf (proof 79e76573983222b5): shard record block 465 shard 0 by 89470283d097209eb0287ba87717938b8642c19b12bb37b5e30ed2a4328f2aaf: the proof bytes are not a bincode SP1 proof: invalid value: integer `4284109422`, expected variant index 0 <= i < 4, disconnecting from peer 127.0.0.1:47690.", + "a carried proof record's proof does not verify: shard record block 591 shard 0 by aa474491707c6931a3d6d2fdb84e4d91fa3f3c57b7776b9c1fb1b01a1f96678a (proof 79e76573983222b5): shard record block 465 shard 0 by 89470283d097209eb0287ba87717938b8642c19b12bb37b5e30ed2a4328f2aaf: the proof bytes are not a bincode SP1 proof: invalid value: integer `4284109422`, expected variant index 0 <= i < 4, disconnecting from peer 127.0.0.1:51924." + ] + } + }, + "proof": { + "block": 94, + "statement": "0x58d620e493a23ebdacdec4f3c3f46e5936a18fbdcb3880341e2bb8b1555f7955", + "native": "0x58d620e493a23ebdacdec4f3c3f46e5936a18fbdcb3880341e2bb8b1555f7955", + "bytes": 1272961, + "sha256": "0xf74f7f7daf2cdcb031add25f28a0787d278de1079aa81aa51ac3f2e744750df7", + "hostLine": "RESULT compressed shard 0: prove 61.0 s, proof 1272961 bytes, verify 0.080 s, VERIFIED; statement 0x58d620e493a23ebdacdec4f3c3f46e5936a18fbdcb3880341e2bb8b1555f7955 proof sha256 0xf74f7f7daf2cdcb031add25f28a0787d278de1079aa81aa51ac3f2e744750df7 prover 0xc3c3c3c3c3c3c3c3c3C3C3c3C3C3C3c3C3C3c3c3 at 2026-10-08T21:56:24Z" + }, + "verdict": { + "paidOnce": true, + "sp1Verifies": 2, + "cacheAnswers": 2, + "contextRefused": true, + "contextCheap": true, + "invalidRefusedTwiceNoVerify": true, + "pass": true + }, + "endedAt": "2026-10-08T22:05:36.331Z" +} \ No newline at end of file diff --git a/infra/fast-time/proving-cache-history.mjs b/infra/fast-time/proving-cache-history.mjs new file mode 100644 index 000000000..609c0968b --- /dev/null +++ b/infra/fast-time/proving-cache-history.mjs @@ -0,0 +1,254 @@ +#!/usr/bin/env node +// The two-node cache-history case (review B F01, Phase 1 item (c), 8 October 2026): on a node with the verdict cache +// of verdict-cache-fix-node 3f672661, a proof's bytes refused once for CONTEXT (another statement) must still pay +// when the same bytes arrive later as their own honest record, answered from the cached facts with no second SP1 +// verify; bytes refused once for being INVALID must be refused again at a later carrier, from the cache, with no +// second verify. Three local nodes at fast time as in proving-enforcement.mjs: H1 and H2 honest (the verifier in +// consensus), A the carrier (skip rule, trust verify) whose templates carry what its pool holds. +// +// The real proof is one of this chain's own blocks: after the chain has run, block n is exported from H1 +// (igneum_exportSegments), cut by igneum-prove-export and proven compressed by igneum-prove-host on the box's CPU +// (about 70 to 110 s), so its statement is H1's native statement for (n, shard 0, PAYOUT). The node binaries and the +// proving binaries must carry the SAME pair (the node's elf/ overlay and the host's embedded pin: pin C tonight), and +// the node's object names that pair (proving_shard_program_id / proving_aggregator_id from the manifest). +// +// node infra/fast-time/proving-cache-history.mjs --prove-bin +// [--before 90] [--after 120] [--slot 0] [--out ] +// +// Verdict: (1) block n shard 0 paid exactly once, to PAYOUT; (2) H1 ran the SP1 verifier exactly twice over the run (the real +// proof once, the invalid bytes once): the context refusal of phase 1 is a pre-check (the statement differs) that runs no +// verifier and is never cached, re-read at every carrier; (3) the context refusal and the invalid refusal both read in H1's +// log; (4) the second use of the invalid bytes is answered from the cache with no new verify (cacheAnswers at least 1). +import { spawn, spawnSync } from 'node:child_process'; +import { mkdirSync, rmSync, writeFileSync, readFileSync, openSync, existsSync, appendFileSync } from 'node:fs'; +import { createHash, randomBytes } from 'node:crypto'; + +const ROOT = new URL('../../', import.meta.url).pathname; +const FILE = `${ROOT}infra/fast-time/override-60x.json`; +const MANIFEST = `${ROOT}proving/igneum-prove/elf/manifest.json`; +const IGNEUMD = process.env.IGNEUMD || `${ROOT}vendor/igneum-node/target/release/igneumd`; +const CPU_MINER = process.env.IGNEUM_MINER || `${ROOT}vendor/igneum-node/target/release/igneum-miner`; +const args = process.argv.slice(2); +const flag = (name, dflt) => { const i = args.indexOf(`--${name}`); return i >= 0 ? Number(args[i + 1]) : dflt; }; +const sflag = (name, dflt) => { const i = args.indexOf(`--${name}`); return i >= 0 ? args[i + 1] : dflt; }; +const BEFORE = flag('before', 90), AFTER = flag('after', 120), SLOT = flag('slot', 0); +const PROVE_BIN = sflag('prove-bin', `${ROOT}proving/igneum-prove/target/release`); +const GENESIS_BITS = flag('genesis-bits', 0x1f010000); +const OUT = sflag('out') || `${ROOT}docs/plans/proving-enforcement/cache-history.json`; +const BASE = 30890 + SLOT * 40, SUFFIX = 985 + SLOT; +const CHAIN = `igneum-devnet-${SUFFIX}`; +const TMP = `/tmp/igneum-fast-time-ch${SLOT}`; +const PIDS = `${TMP}/pids`; +const NEVER = '18446744073709551615'; +for (const b of [IGNEUMD, CPU_MINER, `${PROVE_BIN}/igneum-prove-host`, `${PROVE_BIN}/igneum-prove-export`]) if (!existsSync(b)) { console.error(`missing ${b}`); process.exit(2); } + +const t0 = Date.now(); +const log = (s) => { const t = new Date().toISOString().slice(11, 23); console.log(`${t} ch${SLOT} ${s}`); }; +const sleep = (ms) => new Promise(r => setTimeout(r, ms)); +const hex = (b) => '0x' + Buffer.from(b).toString('hex'); +const sha256 = (b) => createHash('sha256').update(b).digest('hex'); + +// leftovers of an earlier run on this slot: by the pid file only +rmSync(TMP, { recursive: true, force: true }); mkdirSync(TMP, { recursive: true }); +const started = []; +const track = (proc) => { started.push(proc); try { appendFileSync(PIDS, `${proc.pid}\n`); } catch { } }; + +// the override: the 60x file with the pair named from the manifest, the verifier in consensus from genesis, no succession +let baseText = readFileSync(FILE, 'utf8'); +for (const k of ['proving_payment_activation_daa']) baseText = baseText.replace(new RegExp(`\\s*"${k}":\\s*[^,}\\n]+,?`), ''); +const manifest = JSON.parse(readFileSync(MANIFEST, 'utf8')); +function mergeOverrideText(text, fields) { + let out = text; + for (const k of Object.keys(fields)) out = out.replace(new RegExp(`\\s*"${k}":\\s*[^,}\\n]+,?`), ''); + const extra = Object.entries(fields).map(([k, v]) => `"${k}": ${typeof v === 'string' && !/^\d+$/.test(v) && v !== 'true' && v !== 'false' ? JSON.stringify(v) : v}`).join(', '); + return out.replace(/,?\s*}\s*$/, `,\n ${extra}\n}\n`); +} +const override = `${TMP}/override.json`; +writeFileSync(override, mergeOverrideText(baseText, { + genesis_bits: GENESIS_BITS, skip_proof_of_work: false, proving_v0_activation_daa: '0', proving_consensus_verify_daa: '0', + proving_shard_program_id: manifest.shard.program_id, proving_aggregator_id: manifest.aggregator.program_id, + verifier_in_consensus: 'true', proving_key_succession_daa: NEVER, proving_key_succession_window_daa: '120', + proving_next_shard_program_id: '', proving_next_aggregator_id: '', + program_class_v3_activation_daa: NEVER, program_class_v4_activation_daa: NEVER, +})); +const DAY_MS = (() => { const m = /"pow_day_ms":\s*([0-9]+)/.exec(baseText); return m ? +m[1] : 1440000; })(); + +class Node { + constructor(name, i, peers = [], env = {}) { + this.name = name; this.i = i; this.grpcPort = BASE + i * 10; this.p2pPort = BASE + i * 10 + 1; this.jsonPort = BASE + i * 10 + 2; this.evmPort = BASE + i * 10 + 3; + this.peers = peers; this.env = env; this.dir = `${TMP}/${name}`; this.logFile = `${this.dir}/node.log`; + } + get grpc() { return `grpc://127.0.0.1:${this.grpcPort}`; } + async start() { + mkdirSync(this.dir, { recursive: true }); + const out = openSync(this.logFile, 'a'); + const a = ['--devnet', `--devnet-suffix=${SUFFIX}`, '--nodnsseed', '--disable-upnp', '--nologfiles', '--enable-unsynced-mining', '--utxoindex', + `--appdir=${this.dir}`, `--rpclisten=127.0.0.1:${this.grpcPort}`, `--rpclisten-json=127.0.0.1:${this.jsonPort}`, `--evm-rpclisten=127.0.0.1:${this.evmPort}`, + `--listen=127.0.0.1:${this.p2pPort}`, `--override-params-file=${override}`, '--loglevel=info', '--yes']; + if (this.peers.length) for (const p of this.peers) a.push(`--addpeer=127.0.0.1:${p}`); else a.push('--outpeers=0'); + this.proc = spawn(IGNEUMD, a, { stdio: ['ignore', out, out], env: { ...process.env, ...this.env } }); + track(this.proc); + for (let k = 0; k < 60; k++) { + await sleep(1000); + try { await this.exec('igneum_getExecStatus', []); log(`${this.name} up (pid ${this.proc.pid}) after ${k + 1} s`); return; } catch { } + if (this.proc.exitCode !== null) throw new Error(`${this.name} exited ${this.proc.exitCode}: ${readFileSync(this.logFile, 'utf8').split('\n').slice(-5).join(' | ')}`); + } + throw new Error(`${this.name} did not answer in 60 s`); + } + async exec(method, params) { + const r = await fetch(`http://127.0.0.1:${this.evmPort}`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) }); + const j = await r.json(); + if (j.error) throw new Error(`${method}: ${j.error.message || JSON.stringify(j.error)}`); + return j.result; + } + logText() { try { return readFileSync(this.logFile, 'utf8'); } catch { return ''; } } + stop() { try { this.proc.kill('SIGINT'); } catch { } } +} +function startMiner(node, label, secs) { + const out = openSync(`${TMP}/${label}.log`, 'a'); + const p = spawn(CPU_MINER, ['mine', node.grpc, '1', String(secs), label, '--engine', 'igneum-pow', '--payout-label', label, '--status-secs', '30', '--no-vote'], { stdio: ['ignore', out, out], env: { ...process.env, IGNEUM_POW_DAY_MS: String(DAY_MS) } }); + track(p); + return p; +} +function signRecord(label, chain, block, number, shard, payout, statement, proofHash) { + const r = spawnSync(CPU_MINER, ['sign-record', label, chain, block, String(number), String(shard), payout, statement, proofHash], { encoding: 'utf8' }); + if (r.status !== 0) throw new Error(`sign-record failed: ${r.stderr || r.stdout}`); + return JSON.parse(r.stdout.trim().split('\n').pop()); +} +const H1 = new Node('H1', 0); +const H2 = new Node('H2', 1, [H1.p2pPort]); +const A = new Node('A', 2, [H1.p2pPort, H2.p2pPort], { IGNEUM_TEST_SKIP_PROOF_RULE: '1', IGNEUM_PROOF_VERIFY: 'trust' }); +const nodes = [H1, H2, A]; +let miners = []; +async function stopAll() { + for (const m of miners) { try { m.kill('SIGTERM'); } catch { } } + for (const n of nodes) n.stop(); + await sleep(2000); + for (const p of started) { try { p.kill('SIGKILL'); } catch { } } +} +process.on('SIGINT', async () => { await stopAll(); process.exit(130); }); +const PAYOUT = '0x' + 'c3'.repeat(20); +async function tipNumber(node) { const s = await node.exec('igneum_getExecStatus', []); return Number(s.executedTip ?? 0); } +async function sameTip(a, h) { + try { const x = await a.exec('igneum_getExecStatus', []); const y = await h.exec('igneum_getExecStatus', []); return x.executedTipHash && x.executedTipHash === y.executedTipHash; } catch { return false; } +} +async function rejoin(label) { + for (let k = 0; k < 120; k++) { if (await sameTip(A, H1)) { log(`${label}: A is on H1's tip (${k * 5} s)`); return true; } await sleep(5000); } + log(`${label}: A did not rejoin H1's tip in 600 s`); return false; +} +async function verifies(node) { const s = await node.exec('igneum_getProvingStatus', []); return s.verifies || { calls: 0, sp1: 0, contextRefusals: 0, cacheAnswers: 0 }; } +const refusals = (node) => (node.logText().match(/a carried proof record's proof does not verify: [^\n]*/g) || []); +async function submitVia(node, name, record, proof) { + let out; + try { out = await node.exec('igneum_submitProofRecord', [{ record, proof }]); } catch (e) { out = { accepted: false, reason: String(e.message) }; } + log(`${name}: A's pool ${out.accepted ? 'accepted' : 'refused'} (${out.reason || ''})`); + return out; +} +async function observe(n) { + try { + const r = await H1.exec('igneum_getProofRecords', ['0x' + n.toString(16)]); + return { paid: (r.paid || []).filter(Boolean), carried: (r.carried || []).map(c => ({ signer: c.keyHash, carrier: c.carrierNumber, rejected: c.rejected, paidWei: c.paidWei })) }; + } catch (e) { return { error: e.message }; } +} + +/// A real proof of block n of this chain: exported from H1, cut and proven by the host (CPU), returning the proof +/// bytes and the statement the host printed (which must equal H1's native statement for (n, 0, PAYOUT)). +function proveBlock(n) { + const dir = `${TMP}/prove-${n}`; mkdirSync(dir, { recursive: true }); + const body = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'igneum_exportSegments', params: ['0x0', '0x' + n.toString(16)] }); + const r = spawnSync('curl', ['-s', '-m', '600', '-X', 'POST', `http://127.0.0.1:${H1.evmPort}`, '-H', 'Content-Type: application/json', '--data-binary', body], { encoding: 'utf8', maxBuffer: 1 << 30 }); + if (r.status !== 0) throw new Error(`export failed: ${r.stderr}`); + writeFileSync(`${dir}/export.json`, JSON.stringify(JSON.parse(r.stdout).result)); + const e = spawnSync(`${PROVE_BIN}/igneum-prove-export`, [`${dir}/export.json`, String(n), `${dir}/block-${n}.json`, '--source', `fast-time cache-history ${CHAIN}`], { encoding: 'utf8' }); + if (e.status !== 0) throw new Error(`igneum-prove-export failed: ${(e.stdout + e.stderr).slice(-400)}`); + log(`block ${n} cut: ${(e.stdout.trim().split('\n').pop() || '').slice(0, 160)}`); + const h = spawnSync(`${PROVE_BIN}/igneum-prove-host`, [`${dir}/block-${n}.json`, '--mode', 'compressed', '--shard', '0', '--prover', PAYOUT, '--out', `${dir}/results.json`], { encoding: 'utf8', env: { ...process.env, SP1_PROVER: 'cpu' } }); + const line = (h.stdout.match(/RESULT compressed shard 0: [^\n]*/) || [''])[0]; + log(`host: ${line.slice(0, 200)}${h.status !== 0 ? ` (exit ${h.status}: ${(h.stderr || '').slice(-300)})` : ''}`); + if (h.status !== 0 || !/VERIFIED/.test(line)) throw new Error(`the host did not prove block ${n}`); + const file = `${dir}/block-${n}-shard-0-compressed.bin`; + if (!existsSync(file)) throw new Error(`no proof file ${file}`); + const statement = (line.match(/statement (0x[0-9a-f]{64})/) || [])[1]; + return { bytes: readFileSync(file), statement, line }; +} + +const result = { case: 'cache-history', node: IGNEUMD, proveBin: PROVE_BIN, pair: { shard: manifest.shard.program_id, aggregator: manifest.aggregator.program_id }, startedAt: new Date().toISOString(), phases: {} }; +try { + await H1.start(); await H2.start(); await A.start(); + miners = [startMiner(H1, 'h1', BEFORE + 3 * AFTER + 900), startMiner(H2, 'h2', BEFORE + 3 * AFTER + 900), startMiner(A, 'attacker', BEFORE + 3 * AFTER + 900)]; + log(`phase 0: mining ${BEFORE} s`); + await sleep(BEFORE * 1000); + const v0 = await verifies(H1); + // the block to prove: outside the exclusive window, executed on every node + let tip = await tipNumber(H1); + const n = Math.max(1, tip - 15); + const plan = await H1.exec('igneum_getShardPlan', ['0x' + n.toString(16), PAYOUT]); + const native = plan.shards[0].statement; + log(`block ${n} ${plan.hash}: H1's native statement for shard 0 and ${PAYOUT}: ${native}`); + const proof = proveBlock(n); + result.proof = { block: n, statement: proof.statement, native, bytes: proof.bytes.length, sha256: '0x' + sha256(proof.bytes), hostLine: proof.line }; + if (proof.statement !== native) throw new Error(`the host's statement ${proof.statement} is not H1's ${native} (host and node layouts or pairs differ)`); + // phase 1: the real bytes under a record for another block (the statement of block m): refused for context + await rejoin('before phase 1'); + tip = await tipNumber(A); + const m = Math.max(1, tip - 15); + const planM = await A.exec('igneum_getShardPlan', ['0x' + m.toString(16), PAYOUT]); + const recWrong = signRecord('ch-wrong', CHAIN, planM.hash, m, 0, PAYOUT, planM.shards[0].statement, '0x' + sha256(proof.bytes)).record; + const r1 = await submitVia(A, 'phase 1 (real bytes, another block\'s statement)', recWrong, hex(proof.bytes)); + await sleep(AFTER * 1000); + const v1 = await verifies(H1); + const o1 = await observe(m); + result.phases.context = { block: m, submit: r1, observed: o1, verifies: v1, refusals: refusals(H1) }; + log(`phase 1: H1 verifies ${JSON.stringify(v1)}; block ${m} paid ${JSON.stringify(o1.paid)}; refusals ${refusals(H1).length}`); + // phase 2: the same bytes as their own honest record: answered from the cached facts, paid once + await rejoin('before phase 2'); + const recHonest = signRecord('ch-honest', CHAIN, plan.hash, n, 0, PAYOUT, native, '0x' + sha256(proof.bytes)).record; + const r2 = await submitVia(A, 'phase 2 (the same bytes, the honest record)', recHonest, hex(proof.bytes)); + await sleep(AFTER * 1000); + const v2 = await verifies(H1); + const o2 = await observe(n); + result.phases.honest = { block: n, submit: r2, observed: o2, verifies: v2 }; + log(`phase 2: H1 verifies ${JSON.stringify(v2)}; block ${n} paid ${JSON.stringify(o2.paid)}; carried ${JSON.stringify(o2.carried)}`); + // phase 3: invalid bytes twice, under two carriers: verified once, refused from the cache the second time + await rejoin('before phase 3'); + const bad = randomBytes(2048); + tip = await tipNumber(A); + const p = Math.max(1, tip - 15); + const planP = await A.exec('igneum_getShardPlan', ['0x' + p.toString(16), PAYOUT]); + const recBad1 = signRecord('ch-bad1', CHAIN, planP.hash, p, 0, PAYOUT, planP.shards[0].statement, '0x' + sha256(bad)).record; + const r3 = await submitVia(A, 'phase 3a (invalid bytes)', recBad1, hex(bad)); + await sleep(AFTER * 1000); + const v3a = await verifies(H1); + const ref3a = refusals(H1).length; + await rejoin('before phase 3b'); + tip = await tipNumber(A); + const q = Math.max(1, tip - 15); + const planQ = await A.exec('igneum_getShardPlan', ['0x' + q.toString(16), PAYOUT]); + const recBad2 = signRecord('ch-bad2', CHAIN, planQ.hash, q, 0, PAYOUT, planQ.shards[0].statement, '0x' + sha256(bad)).record; + const r4 = await submitVia(A, 'phase 3b (the same invalid bytes, a later carrier)', recBad2, hex(bad)); + await sleep(AFTER * 1000); + const v3b = await verifies(H1); + const ref3b = refusals(H1).length; + result.phases.invalid = { blocks: [p, q], submits: [r3, r4], verifies: { after3a: v3a, after3b: v3b }, refusals: { after3a: ref3a, after3b: ref3b }, reasons: refusals(H1) }; + log(`phase 3: H1 verifies after 3a ${JSON.stringify(v3a)}, after 3b ${JSON.stringify(v3b)}; refusals ${ref3a} then ${ref3b}`); + // the verdict + const paidOnce = (o2.paid || []).length === 1 && (o1.paid || []).length === 0; + const runs = Number(v3b.sp1) - Number(v0.sp1); // SP1 verifies: the real proof once (phase 2), the invalid bytes once (3a); the context refusal of phase 1 runs no verifier + const cache = Number(v3b.cacheAnswers) - Number(v0.cacheAnswers); + const contextCheap = Number(v1.contextRefusals) - Number(v0.contextRefusals) >= 1 && Number(v1.sp1) === Number(v0.sp1); + const contextRefused = result.phases.context.refusals.length >= 1; + const invalidRefusedTwiceNoVerify = ref3b > ref3a && Number(v3b.sp1) === Number(v3a.sp1); + const pass = paidOnce && runs === 2 && cache >= 1 && contextRefused && contextCheap && invalidRefusedTwiceNoVerify; + result.verdict = { paidOnce, sp1Verifies: runs, cacheAnswers: cache, contextRefused, contextCheap, invalidRefusedTwiceNoVerify, pass }; + result.endedAt = new Date().toISOString(); + writeFileSync(OUT, JSON.stringify(result, null, 2)); + log(`RESULT ${pass ? 'PASS' : 'FAIL'}: paid once=${paidOnce}; SP1 verifies=${runs} (expect 2: the real proof once, the invalid bytes once); context refusal read=${contextRefused} and cheap (no verifier)=${contextCheap}; cache answers=${cache} (expect >= 1); invalid refused again without a verify=${invalidRefusedTwiceNoVerify}; ${OUT}`); + await stopAll(); + process.exit(pass ? 0 : 1); +} catch (e) { + log(`ERROR: ${e.message}`); + result.error = e.message; result.endedAt = new Date().toISOString(); + try { writeFileSync(OUT, JSON.stringify(result, null, 2)); } catch { } + await stopAll(); + process.exit(1); +} diff --git a/infra/fast-time/proving-enforcement.mjs b/infra/fast-time/proving-enforcement.mjs index 90804b6a1..daff55cc7 100644 --- a/infra/fast-time/proving-enforcement.mjs +++ b/infra/fast-time/proving-enforcement.mjs @@ -276,7 +276,7 @@ async function forge(n, phase, which = 'pn') { async function observe(n) { try { const r = await H1.exec('igneum_getProofRecords', ['0x' + n.toString(16)]); - const carried = (r.carried || []).map(c => ({ key: c.keyHash, carrier: c.carrierNumber, rejected: c.rejected, paidWei: c.paidWei })); + const carried = (r.carried || []).map(c => ({ signer: c.keyHash, carrier: c.carrierNumber, rejected: c.rejected, paidWei: c.paidWei })); // `paid` holds one entry per shard, null when unpaid: keep the paid ones only return { paid: (r.paid || []).filter(Boolean), carried }; } catch (e) { return { error: e.message }; } diff --git a/tools/ci/batches/enforced-proving-20261008-03.json b/tools/ci/batches/enforced-proving-20261008-03.json new file mode 100644 index 000000000..ed23acb5b --- /dev/null +++ b/tools/ci/batches/enforced-proving-20261008-03.json @@ -0,0 +1,17 @@ +{ + "run_id": "enforced-proving-20261008-03", + "manifest_sha": "c591e63b", + "evidence_dir": "docs/plans/proving-enforcement", + "boxes": [ + "build-9" + ], + "method": "native", + "cells": [ + { + "cell": "harness:proving-enforcement", + "status": "PASS", + "evidence": "docs/plans/proving-enforcement/cache-history-2.json" + } + ], + "note": "The two-node cache-history case (review B F01) on the fix's rule at the 2.0.2 tip ab489403 (cache-history-node c591e63b; host and node on pin C): a real proof of the chain's own block refused for context under one carrier, then paid once as its own record under a later one from the cached facts with no second SP1 verify; the invalid bytes verified once and refused again from the cache at a later carrier; a context refusal is a cheap pre-check, never cached. build-9, 22:53 to 23:05 UK." +} \ No newline at end of file