diff --git a/docs/design/execution-layer.md b/docs/design/execution-layer.md index f0466d162..9c71c0c97 100644 --- a/docs/design/execution-layer.md +++ b/docs/design/execution-layer.md @@ -367,6 +367,7 @@ Standard namespace, unchanged semantics where the table says so; RPC "blocks" ar | `igneum_getSegment(number)` | New | Mergeset in order, executed set, shard plan, proof record if any | | `igneum_getTransactionStatus(hash)` | New | `{included_in: [hashes], executing_copy, executed, proven, locked, skip_reason}` | | `igneum_getProvingFee`, `igneum_getBudgets` | New | `f_p`, `B_e`, `B_p`, backlog depth | +| `igneum_getRecentBlocks(seconds)` | New (0.3.16) | Every block the executed chain saw in the last `seconds` (default and cap 600) by chain-block header time, newest chain block first, its merged blocks after it in mergeset order: `{hash, blue_score, daa_score, timestamp_ms, vote_key_hash, is_chain_block, color, timestamp_source}`; `color` blue or red; a merged row's time and blue score come from the header store (`timestamp_source` "header") or, without consensus, from its chain block ("chain_block"). The app's live view derives its 10 minute miner count and per minute buckets from one reply | ### 8.3 Hardhat and Foundry diff --git a/docs/plans/counter-asic-3-gate/class-v4-signal7-failed-case.json b/docs/plans/counter-asic-3-gate/class-v4-signal7-failed-case.json new file mode 100644 index 000000000..5bedc5cdf --- /dev/null +++ b/docs/plans/counter-asic-3-gate/class-v4-signal7-failed-case.json @@ -0,0 +1,559 @@ +{ + "pass": false, + "expect": "flip", + "signals": [ + 4, + 4, + 3 + ], + "checks": { + "zero_rejected_by_miners": true, + "zero_rejected_by_nodes": true, + "sinks_agree": true, + "block_counts_agree": true, + "miners_agree_on_every_program": true, + "window_line_on_every_node": true, + "every_node_signals_its_byte": true, + "chain_carries_the_bytes": true, + "template_switched_to_v4": false, + "switched_at_the_first_full_window_epoch": false, + "switched_before_the_floor": false, + "signal_line_on_every_node_same_epoch": false, + "signal_share_at_or_above_threshold": false, + "signal_line_names_seven_windows": false, + "blocks_on_both_sides": false, + "v4_ids_equal_the_cli_v4_id": false, + "v4_ids_differ_from_the_same_seed_v3_id": false + }, + "window": 30, + "windows": 7, + "floor": 100000, + "v3_activation": 60, + "epoch_blocks": 60, + "lead": 10, + "first_full_window_epoch": 4, + "floor_epoch": 1667, + "node": "/Users/joshm/Projects/igneum-wt-ca3-v4-node/vendor/igneum-node-ca3v4/target-ca3v4/release/igneumd", + "miner": "/Users/joshm/Projects/igneum-wt-ca3-v4-node/vendor/igneum-node-ca3v4/target-ca3v4/release/igneum-miner", + "template_switch": null, + "run_ended_at_s": 410.2, + "final_daa": 421, + "max_epoch_seen": 7, + "epochs": { + "0": { + "class": 2, + "firstSeenDaa": 0, + "at": 4.6, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "1": { + "class": 3, + "firstSeenDaa": 60, + "at": 27.7, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "2": { + "class": 3, + "firstSeenDaa": 120, + "at": 91.8, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "3": { + "class": 3, + "firstSeenDaa": 180, + "at": 160.9, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "4": { + "class": 3, + "firstSeenDaa": 240, + "at": 233, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 5333, + "signal_epoch": null + }, + "5": { + "class": 3, + "firstSeenDaa": 300, + "at": 293.1, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 5333, + "signal_epoch": null + }, + "6": { + "class": 3, + "firstSeenDaa": 360, + "at": 350.2, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 5333, + "signal_epoch": null + }, + "7": { + "class": 3, + "firstSeenDaa": 421, + "at": 410.2, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 6333, + "signal_epoch": null + } + }, + "blocks": { + "total": 424, + "before_boundary": 424, + "after_boundary": 0, + "version_bytes": { + "0": 1, + "3": 135, + "4": 288 + }, + "signal_share_bps_on_chain": 6792 + }, + "programs": [ + { + "epoch": 0, + "class": "v2", + "program_id": "8f8806638d59850f", + "seed": "234e082d653dc69d", + "miners": 3, + "disagree": false + }, + { + "epoch": 1, + "class": "v3", + "program_id": "7e2f9a15ca615321", + "seed": "ae6cdc46ffc6ac68", + "miners": 3, + "disagree": false + }, + { + "epoch": 2, + "class": "v3", + "program_id": "2c2a98fae78f74b8", + "seed": "7d44b28f7a9c7f0a", + "miners": 3, + "disagree": false + }, + { + "epoch": 3, + "class": "v3", + "program_id": "89ae54d5b5eae763", + "seed": "6aea4563d3d935b3", + "miners": 3, + "disagree": false + }, + { + "epoch": 4, + "class": "v3", + "program_id": "5a0ca4f1ff74e70c", + "seed": "c37cf6f9e8cd33b5", + "miners": 3, + "disagree": false + }, + { + "epoch": 5, + "class": "v3", + "program_id": "e4af5e0c4d9b93c2", + "seed": "f8c304d64d5f8225", + "miners": 3, + "disagree": false + }, + { + "epoch": 6, + "class": "v3", + "program_id": "b5ca5848f6f0413b", + "seed": "1eb6aa4c17a2eed4", + "miners": 3, + "disagree": false + }, + { + "epoch": 7, + "class": "v3", + "program_id": "ab61dbf422b32bc5", + "seed": "9532201279474928", + "miners": 3, + "disagree": false + } + ], + "program_id_rows": [], + "accepted_per_miner": [ + 131, + 157, + 135 + ], + "rejected_by_miners": [ + 0, + 0, + 0 + ], + "rejected_by_nodes": [ + 0, + 0, + 0 + ], + "sinks": [ + "1c1eae6d5c82de1c", + "1c1eae6d5c82de1c", + "1c1eae6d5c82de1c" + ], + "block_counts": [ + 423, + 423, + 423 + ], + "signal_lines": [ + null, + null, + null + ], + "floor_lines": [ + "Program class v4 from the override file: active from epoch 1667 (DAA score 100000 rounded up to the epoch boundary at 100020, epochs of 60 DAA)", + "Program class v4 from the override file: active from epoch 1667 (DAA score 100000 rounded up to the epoch boundary at 100020, epochs of 60 DAA)", + "Program class v4 from the override file: active from epoch 1667 (DAA score 100000 rounded up to the epoch boundary at 100020, epochs of 60 DAA)" + ], + "samples": [ + { + "t": 4.6, + "daa": 0, + "epoch": 0, + "class": 2, + "bps": 0, + "nodes": [ + "0/234e082d", + "0/234e082d", + "0/234e082d" + ] + }, + { + "t": 19.7, + "daa": 47, + "epoch": 0, + "class": 2, + "bps": 0, + "nodes": [ + "47/9003a9d9", + "47/9003a9d9", + "47/9003a9d9" + ] + }, + { + "t": 34.7, + "daa": 64, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "64/b4c1d83f", + "64/b4c1d83f", + "64/b4c1d83f" + ] + }, + { + "t": 49.7, + "daa": 69, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "69/3dda5305", + "69/3dda5305", + "69/3dda5305" + ] + }, + { + "t": 64.8, + "daa": 88, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "88/84d103be", + "88/84d103be", + "88/84d103be" + ] + }, + { + "t": 79.8, + "daa": 107, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "107/95682fc8", + "107/95682fc8", + "107/95682fc8" + ] + }, + { + "t": 94.8, + "daa": 120, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "120/bb25b3af", + "120/bb25b3af", + "120/bb25b3af" + ] + }, + { + "t": 109.8, + "daa": 138, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "138/82254c76", + "138/82254c76", + "138/82254c76" + ] + }, + { + "t": 124.9, + "daa": 154, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "154/ab3e9744", + "154/ab3e9744", + "154/ab3e9744" + ] + }, + { + "t": 139.9, + "daa": 167, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "167/381fdc27", + "167/381fdc27", + "167/381fdc27" + ] + }, + { + "t": 154.9, + "daa": 174, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "174/b6930be6", + "174/b6930be6", + "174/b6930be6" + ] + }, + { + "t": 169.9, + "daa": 190, + "epoch": 3, + "class": 3, + "bps": 4285, + "nodes": [ + "190/3473bc22", + "190/3473bc22", + "190/3473bc22" + ] + }, + { + "t": 184.9, + "daa": 207, + "epoch": 3, + "class": 3, + "bps": 5000, + "nodes": [ + "207/01637a3f", + "207/01637a3f", + "207/01637a3f" + ] + }, + { + "t": 200, + "daa": 219, + "epoch": 3, + "class": 3, + "bps": 5666, + "nodes": [ + "219/0fda8440", + "219/0fda8440", + "219/0fda8440" + ] + }, + { + "t": 215, + "daa": 226, + "epoch": 3, + "class": 3, + "bps": 5666, + "nodes": [ + "226/8ed13229", + "226/8ed13229", + "226/8ed13229" + ] + }, + { + "t": 230, + "daa": 236, + "epoch": 3, + "class": 3, + "bps": 5000, + "nodes": [ + "236/b0be941e", + "236/b0be941e", + "236/b0be941e" + ] + }, + { + "t": 245, + "daa": 252, + "epoch": 4, + "class": 3, + "bps": 5666, + "nodes": [ + "252/a93709a4", + "252/a93709a4", + "252/a93709a4" + ] + }, + { + "t": 260, + "daa": 274, + "epoch": 4, + "class": 3, + "bps": 5666, + "nodes": [ + "274/099337fa", + "274/099337fa", + "274/099337fa" + ] + }, + { + "t": 275.1, + "daa": 282, + "epoch": 4, + "class": 3, + "bps": 5666, + "nodes": [ + "282/6bd5d923", + "282/6bd5d923", + "282/6bd5d923" + ] + }, + { + "t": 290.1, + "daa": 296, + "epoch": 4, + "class": 3, + "bps": 5000, + "nodes": [ + "296/94f37746", + "296/94f37746", + "296/94f37746" + ] + }, + { + "t": 305.1, + "daa": 313, + "epoch": 5, + "class": 3, + "bps": 5333, + "nodes": [ + "313/b76f2f75", + "313/b76f2f75", + "313/b76f2f75" + ] + }, + { + "t": 320.1, + "daa": 326, + "epoch": 5, + "class": 3, + "bps": 5000, + "nodes": [ + "326/9a5dee69", + "326/9a5dee69", + "326/9a5dee69" + ] + }, + { + "t": 335.1, + "daa": 341, + "epoch": 5, + "class": 3, + "bps": 5666, + "nodes": [ + "341/058c9422", + "341/058c9422", + "341/058c9422" + ] + }, + { + "t": 350.2, + "daa": 360, + "epoch": 6, + "class": 3, + "bps": 5333, + "nodes": [ + "360/901edc84", + "360/901edc84", + "360/901edc84" + ] + }, + { + "t": 365.2, + "daa": 377, + "epoch": 6, + "class": 3, + "bps": 6000, + "nodes": [ + "377/e681d3ba", + "377/e681d3ba", + "377/e681d3ba" + ] + }, + { + "t": 380.2, + "daa": 388, + "epoch": 6, + "class": 3, + "bps": 6333, + "nodes": [ + "388/05c84f63", + "388/05c84f63", + "388/05c84f63" + ] + }, + { + "t": 395.2, + "daa": 406, + "epoch": 6, + "class": 3, + "bps": 5666, + "nodes": [ + "406/4bc6b31c", + "406/4bc6b31c", + "406/4bc6b31c" + ] + }, + { + "t": 410.2, + "daa": 421, + "epoch": 7, + "class": 3, + "bps": 6333, + "nodes": [ + "421/6b95e961", + "421/6b95e961", + "421/6b95e961" + ] + } + ] +} \ No newline at end of file diff --git a/docs/plans/counter-asic-3-gate/class-v4-signal7-flip.json b/docs/plans/counter-asic-3-gate/class-v4-signal7-flip.json new file mode 100644 index 000000000..8fded95b9 --- /dev/null +++ b/docs/plans/counter-asic-3-gate/class-v4-signal7-flip.json @@ -0,0 +1,511 @@ +{ + "pass": true, + "expect": "flip", + "signals": [ + 4, + 4, + 4 + ], + "checks": { + "zero_rejected_by_miners": true, + "zero_rejected_by_nodes": true, + "sinks_agree": true, + "block_counts_agree": true, + "miners_agree_on_every_program": true, + "window_line_on_every_node": true, + "every_node_signals_its_byte": true, + "chain_carries_the_bytes": true, + "template_switched_to_v4": true, + "switched_at_the_first_full_window_epoch": true, + "switched_before_the_floor": true, + "signal_line_on_every_node_same_epoch": true, + "signal_share_at_or_above_threshold": true, + "signal_line_names_seven_windows": true, + "blocks_on_both_sides": true, + "v4_ids_equal_the_cli_v4_id": true, + "v4_ids_differ_from_the_same_seed_v3_id": true + }, + "window": 30, + "windows": 7, + "floor": 100000, + "v3_activation": 60, + "epoch_blocks": 60, + "lead": 10, + "first_full_window_epoch": 4, + "floor_epoch": 1667, + "node": "/Users/joshm/Projects/igneum-wt-ca3-v4-node/vendor/igneum-node-ca3v4/target-ca3v4/release/igneumd", + "miner": "/Users/joshm/Projects/igneum-wt-ca3-v4-node/vendor/igneum-node-ca3v4/target-ca3v4/release/igneum-miner", + "template_switch": { + "epoch": 4, + "daa": 241, + "at": 228.1 + }, + "run_ended_at_s": 339.4, + "final_daa": 360, + "max_epoch_seen": 6, + "epochs": { + "0": { + "class": 2, + "firstSeenDaa": 0, + "at": 4.6, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "1": { + "class": 3, + "firstSeenDaa": 60, + "at": 29.7, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "2": { + "class": 3, + "firstSeenDaa": 120, + "at": 106.8, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "3": { + "class": 3, + "firstSeenDaa": 180, + "at": 166, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "4": { + "class": 4, + "firstSeenDaa": 241, + "at": 228.1, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 10000, + "signal_epoch": 4 + }, + "5": { + "class": 4, + "firstSeenDaa": 301, + "at": 284.2, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 10000, + "signal_epoch": null + }, + "6": { + "class": 4, + "firstSeenDaa": 360, + "at": 339.4, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 10000, + "signal_epoch": null + } + }, + "blocks": { + "total": 365, + "before_boundary": 241, + "after_boundary": 124, + "version_bytes": { + "0": 1, + "4": 364 + }, + "signal_share_bps_on_chain": 9973 + }, + "programs": [ + { + "epoch": 0, + "class": "v2", + "program_id": "8f8806638d59850f", + "seed": "234e082d653dc69d", + "miners": 3, + "disagree": false + }, + { + "epoch": 1, + "class": "v3", + "program_id": "8b03d07a1875fe62", + "seed": "08625fc57bb161d4", + "miners": 3, + "disagree": false + }, + { + "epoch": 2, + "class": "v3", + "program_id": "6896042b76dabe5a", + "seed": "f74f60ade435880a", + "miners": 3, + "disagree": false + }, + { + "epoch": 3, + "class": "v3", + "program_id": "26f4071960d8aeef", + "seed": "ed4e93635f53f4e2", + "miners": 3, + "disagree": false + }, + { + "epoch": 4, + "class": "v4", + "program_id": "513bbe423698ac46", + "seed": "a5adea6f1fecb370", + "miners": 3, + "disagree": false + }, + { + "epoch": 5, + "class": "v4", + "program_id": "ee00d3fe4a0b811c", + "seed": "706cc2b5ddd6ea3c", + "miners": 3, + "disagree": false + }, + { + "epoch": 6, + "class": "v4", + "program_id": "50cbbceb1587bd20", + "seed": "cee479e6601625a5", + "miners": 3, + "disagree": false + } + ], + "program_id_rows": [ + { + "epoch": 4, + "seed": "a5adea6f1fecb370", + "miners_id": "513bbe423698ac46", + "miners": 3, + "cli_v3": "11695bdfc66258c9", + "cli_v4": "513bbe423698ac46" + }, + { + "epoch": 5, + "seed": "706cc2b5ddd6ea3c", + "miners_id": "ee00d3fe4a0b811c", + "miners": 3, + "cli_v3": "05a7e8fbadcb906f", + "cli_v4": "ee00d3fe4a0b811c" + }, + { + "epoch": 6, + "seed": "cee479e6601625a5", + "miners_id": "50cbbceb1587bd20", + "miners": 3, + "cli_v3": "2f96368ac5c87ad3", + "cli_v4": "50cbbceb1587bd20" + } + ], + "accepted_per_miner": [ + 117, + 133, + 114 + ], + "rejected_by_miners": [ + 0, + 0, + 0 + ], + "rejected_by_nodes": [ + 0, + 0, + 0 + ], + "sinks": [ + "786d8071368913fa", + "786d8071368913fa", + "786d8071368913fa" + ], + "block_counts": [ + 364, + 364, + 364 + ], + "signal_lines": [ + "Program class v4 by miner signal: epoch 4 (share 10000 bps over 30 DAA ending at seed block a5adea6f1fecb370a5a77b2c7cfd586dcb601eba8664c364c519830185159143, threshold 9500 bps in each of 7 consecutive windows, weakest 10000 bps, shares oldest first [10000, 10000, 10000, 10000, 10000, 10000, 10000], 210 of 210 blue blocks)", + "Program class v4 by miner signal: epoch 4 (share 10000 bps over 30 DAA ending at seed block a5adea6f1fecb370a5a77b2c7cfd586dcb601eba8664c364c519830185159143, threshold 9500 bps in each of 7 consecutive windows, weakest 10000 bps, shares oldest first [10000, 10000, 10000, 10000, 10000, 10000, 10000], 210 of 210 blue blocks)", + "Program class v4 by miner signal: epoch 4 (share 10000 bps over 30 DAA ending at seed block a5adea6f1fecb370a5a77b2c7cfd586dcb601eba8664c364c519830185159143, threshold 9500 bps in each of 7 consecutive windows, weakest 10000 bps, shares oldest first [10000, 10000, 10000, 10000, 10000, 10000, 10000], 210 of 210 blue blocks)" + ], + "floor_lines": [ + "Program class v4 from the override file: active from epoch 1667 (DAA score 100000 rounded up to the epoch boundary at 100020, epochs of 60 DAA)", + "Program class v4 from the override file: active from epoch 1667 (DAA score 100000 rounded up to the epoch boundary at 100020, epochs of 60 DAA)", + "Program class v4 from the override file: active from epoch 1667 (DAA score 100000 rounded up to the epoch boundary at 100020, epochs of 60 DAA)" + ], + "samples": [ + { + "t": 4.6, + "daa": 0, + "epoch": 0, + "class": 2, + "bps": 0, + "nodes": [ + "0/234e082d", + "0/234e082d", + "0/234e082d" + ] + }, + { + "t": 19.7, + "daa": 45, + "epoch": 0, + "class": 2, + "bps": 0, + "nodes": [ + "45/ed680eb3", + "45/ed680eb3", + "45/ed680eb3" + ] + }, + { + "t": 34.7, + "daa": 61, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "61/b2ac0c02", + "61/b2ac0c02", + "61/b2ac0c02" + ] + }, + { + "t": 49.7, + "daa": 67, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "67/cbd5a35e", + "67/cbd5a35e", + "67/cbd5a35e" + ] + }, + { + "t": 64.7, + "daa": 80, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "80/d88cad13", + "80/d88cad13", + "80/d88cad13" + ] + }, + { + "t": 79.8, + "daa": 88, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "88/83050ad0", + "88/83050ad0", + "88/83050ad0" + ] + }, + { + "t": 94.8, + "daa": 106, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "106/d230c5bc", + "106/d230c5bc", + "106/d230c5bc" + ] + }, + { + "t": 109.8, + "daa": 124, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "124/9c5750a4", + "124/9c5750a4", + "124/9c5750a4" + ] + }, + { + "t": 124.9, + "daa": 145, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "145/a5200bc7", + "145/a5200bc7", + "145/a5200bc7" + ] + }, + { + "t": 139.9, + "daa": 157, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "157/407eca8c", + "157/407eca8c", + "157/407eca8c" + ] + }, + { + "t": 154.9, + "daa": 172, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "172/71c56601", + "172/71c56601", + "172/71c56601" + ] + }, + { + "t": 170, + "daa": 181, + "epoch": 3, + "class": 3, + "bps": 0, + "nodes": [ + "181/bdfe2032", + "181/bdfe2032", + "181/bdfe2032" + ] + }, + { + "t": 185, + "daa": 189, + "epoch": 3, + "class": 3, + "bps": 10000, + "nodes": [ + "189/826a9db3", + "189/826a9db3", + "189/826a9db3" + ] + }, + { + "t": 200, + "daa": 207, + "epoch": 3, + "class": 3, + "bps": 10000, + "nodes": [ + "207/216fd0dc", + "207/216fd0dc", + "207/216fd0dc" + ] + }, + { + "t": 215.1, + "daa": 221, + "epoch": 3, + "class": 3, + "bps": 10000, + "nodes": [ + "221/b2e187ad", + "221/b2e187ad", + "221/b2e187ad" + ] + }, + { + "t": 230.1, + "daa": 241, + "epoch": 4, + "class": 4, + "bps": 10000, + "nodes": [ + "241/97e1a942", + "241/97e1a942", + "241/97e1a942" + ] + }, + { + "t": 245.1, + "daa": 254, + "epoch": 4, + "class": 4, + "bps": 10000, + "nodes": [ + "254/15744959", + "254/15744959", + "254/15744959" + ] + }, + { + "t": 260.2, + "daa": 270, + "epoch": 4, + "class": 4, + "bps": 10000, + "nodes": [ + "270/bf72bf67", + "270/bf72bf67", + "270/bf72bf67" + ] + }, + { + "t": 275.2, + "daa": 287, + "epoch": 4, + "class": 4, + "bps": 10000, + "nodes": [ + "287/53fb6153", + "287/53fb6153", + "287/53fb6153" + ] + }, + { + "t": 290.2, + "daa": 308, + "epoch": 5, + "class": 4, + "bps": 10000, + "nodes": [ + "308/947c64f4", + "308/947c64f4", + "308/947c64f4" + ] + }, + { + "t": 305.3, + "daa": 325, + "epoch": 5, + "class": 4, + "bps": 10000, + "nodes": [ + "325/526aa176", + "325/526aa176", + "325/526aa176" + ] + }, + { + "t": 320.3, + "daa": 336, + "epoch": 5, + "class": 4, + "bps": 10000, + "nodes": [ + "336/4fcf7134", + "336/4fcf7134", + "336/4fcf7134" + ] + }, + { + "t": 335.3, + "daa": 354, + "epoch": 5, + "class": 4, + "bps": 10000, + "nodes": [ + "354/ed96e77e", + "354/ed96e77e", + "354/ed96e77e" + ] + } + ] +} \ No newline at end of file diff --git a/docs/plans/counter-asic-3-gate/class-v4-signal7-floor.json b/docs/plans/counter-asic-3-gate/class-v4-signal7-floor.json new file mode 100644 index 000000000..bb8f2e11e --- /dev/null +++ b/docs/plans/counter-asic-3-gate/class-v4-signal7-floor.json @@ -0,0 +1,636 @@ +{ + "pass": true, + "expect": "floor", + "signals": [ + 3, + 3, + 3 + ], + "checks": { + "zero_rejected_by_miners": true, + "zero_rejected_by_nodes": true, + "sinks_agree": true, + "block_counts_agree": true, + "miners_agree_on_every_program": true, + "window_line_on_every_node": true, + "every_node_signals_its_byte": true, + "chain_carries_the_bytes": true, + "template_switched_to_v4": true, + "switched_at_the_floor_epoch": true, + "no_signal_line_on_any_node": true, + "floor_line_names_the_floor_epoch": true, + "blocks_on_both_sides": true, + "v4_ids_equal_the_cli_v4_id": true + }, + "window": 30, + "windows": 7, + "floor": 360, + "v3_activation": 60, + "epoch_blocks": 60, + "lead": 10, + "first_full_window_epoch": 4, + "floor_epoch": 6, + "node": "/Users/joshm/Projects/igneum-wt-ca3-v4-node/vendor/igneum-node-ca3v4/target-ca3v4/release/igneumd", + "miner": "/Users/joshm/Projects/igneum-wt-ca3-v4-node/vendor/igneum-node-ca3v4/target-ca3v4/release/igneum-miner", + "template_switch": { + "epoch": 6, + "daa": 362, + "at": 345.4 + }, + "run_ended_at_s": 461.7, + "final_daa": 480, + "max_epoch_seen": 8, + "epochs": { + "0": { + "class": 2, + "firstSeenDaa": 0, + "at": 4.6, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "1": { + "class": 3, + "firstSeenDaa": 60, + "at": 30.7, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "2": { + "class": 3, + "firstSeenDaa": 120, + "at": 95.9, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "3": { + "class": 3, + "firstSeenDaa": 180, + "at": 162, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "4": { + "class": 3, + "firstSeenDaa": 240, + "at": 223.1, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "5": { + "class": 3, + "firstSeenDaa": 300, + "at": 286.3, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "6": { + "class": 4, + "firstSeenDaa": 362, + "at": 345.4, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "7": { + "class": 4, + "firstSeenDaa": 420, + "at": 396.5, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "8": { + "class": 4, + "firstSeenDaa": 480, + "at": 461.7, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + } + }, + "blocks": { + "total": 486, + "before_boundary": 361, + "after_boundary": 125, + "version_bytes": { + "0": 1, + "3": 485 + }, + "signal_share_bps_on_chain": 0 + }, + "programs": [ + { + "epoch": 0, + "class": "v2", + "program_id": "8f8806638d59850f", + "seed": "234e082d653dc69d", + "miners": 3, + "disagree": false + }, + { + "epoch": 1, + "class": "v3", + "program_id": "0e47ecc08def3b3c", + "seed": "87f4edf6d5390261", + "miners": 3, + "disagree": false + }, + { + "epoch": 2, + "class": "v3", + "program_id": "e01d3d013e9d0fd0", + "seed": "6aa531dd57a2343a", + "miners": 3, + "disagree": false + }, + { + "epoch": 3, + "class": "v3", + "program_id": "34ddb6eacf5ca8d5", + "seed": "c053141073ce26a3", + "miners": 3, + "disagree": false + }, + { + "epoch": 4, + "class": "v3", + "program_id": "b03aefe32cca46b5", + "seed": "51e40d5dc38d9823", + "miners": 3, + "disagree": false + }, + { + "epoch": 5, + "class": "v3", + "program_id": "3dd9cb5971851a52", + "seed": "b4adb9c80f091964", + "miners": 3, + "disagree": false + }, + { + "epoch": 6, + "class": "v4", + "program_id": "aa0283cc2b5e5b0f", + "seed": "536c8ac2706c23be", + "miners": 3, + "disagree": false + }, + { + "epoch": 7, + "class": "v4", + "program_id": "1d4f6d8c21386868", + "seed": "116706d2320aad69", + "miners": 3, + "disagree": false + }, + { + "epoch": 8, + "class": "v4", + "program_id": "4bee3fb48487db58", + "seed": "556c3dacee5edf64", + "miners": 3, + "disagree": false + } + ], + "program_id_rows": [ + { + "epoch": 6, + "seed": "536c8ac2706c23be", + "miners_id": "aa0283cc2b5e5b0f", + "miners": 3, + "cli_v3": "25d2f5499f1c6060", + "cli_v4": "aa0283cc2b5e5b0f" + }, + { + "epoch": 7, + "seed": "116706d2320aad69", + "miners_id": "1d4f6d8c21386868", + "miners": 3, + "cli_v3": "239d20744ba996ff", + "cli_v4": "1d4f6d8c21386868" + }, + { + "epoch": 8, + "seed": "556c3dacee5edf64", + "miners_id": "4bee3fb48487db58", + "miners": 3, + "cli_v3": "a5f5e0f57f30aca7", + "cli_v4": "4bee3fb48487db58" + } + ], + "accepted_per_miner": [ + 170, + 162, + 153 + ], + "rejected_by_miners": [ + 0, + 0, + 0 + ], + "rejected_by_nodes": [ + 0, + 0, + 0 + ], + "sinks": [ + "0db7f95ee7c2d6ec", + "0db7f95ee7c2d6ec", + "0db7f95ee7c2d6ec" + ], + "block_counts": [ + 485, + 485, + 485 + ], + "signal_lines": [ + null, + null, + null + ], + "floor_lines": [ + "Program class v4 from the override file: active from epoch 6 (DAA score 360 rounded up to the epoch boundary at 360, epochs of 60 DAA)", + "Program class v4 from the override file: active from epoch 6 (DAA score 360 rounded up to the epoch boundary at 360, epochs of 60 DAA)", + "Program class v4 from the override file: active from epoch 6 (DAA score 360 rounded up to the epoch boundary at 360, epochs of 60 DAA)" + ], + "samples": [ + { + "t": 4.7, + "daa": 0, + "epoch": 0, + "class": 2, + "bps": 0, + "nodes": [ + "0/234e082d", + "0/234e082d", + "0/234e082d" + ] + }, + { + "t": 19.7, + "daa": 42, + "epoch": 0, + "class": 2, + "bps": 0, + "nodes": [ + "42/d1f90c89", + "42/d1f90c89", + "42/d1f90c89" + ] + }, + { + "t": 34.7, + "daa": 62, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "62/ecdf7c47", + "62/ecdf7c47", + "62/ecdf7c47" + ] + }, + { + "t": 49.8, + "daa": 69, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "69/04b8880d", + "69/04b8880d", + "69/04b8880d" + ] + }, + { + "t": 64.8, + "daa": 86, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "86/98eb299b", + "86/98eb299b", + "86/98eb299b" + ] + }, + { + "t": 79.8, + "daa": 108, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "108/dacf3726", + "108/dacf3726", + "108/dacf3726" + ] + }, + { + "t": 94.9, + "daa": 119, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "119/3522f373", + "119/3522f373", + "119/3522f373" + ] + }, + { + "t": 109.9, + "daa": 130, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "130/1cdfcc43", + "130/1cdfcc43", + "130/1cdfcc43" + ] + }, + { + "t": 124.9, + "daa": 150, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "150/2ed00203", + "150/2ed00203", + "150/2ed00203" + ] + }, + { + "t": 140, + "daa": 163, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "163/fdb35c12", + "163/fdb35c12", + "163/fdb35c12" + ] + }, + { + "t": 155, + "daa": 173, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "173/02d85439", + "173/02d85439", + "173/02d85439" + ] + }, + { + "t": 170, + "daa": 186, + "epoch": 3, + "class": 3, + "bps": 0, + "nodes": [ + "186/d7afd264", + "186/d7afd264", + "186/d7afd264" + ] + }, + { + "t": 185, + "daa": 200, + "epoch": 3, + "class": 3, + "bps": 0, + "nodes": [ + "200/3b87d7fc", + "200/3b87d7fc", + "200/3b87d7fc" + ] + }, + { + "t": 200.1, + "daa": 218, + "epoch": 3, + "class": 3, + "bps": 0, + "nodes": [ + "218/e79e78a2", + "218/e79e78a2", + "218/e79e78a2" + ] + }, + { + "t": 215.1, + "daa": 236, + "epoch": 3, + "class": 3, + "bps": 0, + "nodes": [ + "236/74b74819", + "236/74b74819", + "236/74b74819" + ] + }, + { + "t": 230.2, + "daa": 252, + "epoch": 4, + "class": 3, + "bps": 0, + "nodes": [ + "252/f44062e7", + "252/f44062e7", + "252/f44062e7" + ] + }, + { + "t": 245.2, + "daa": 264, + "epoch": 4, + "class": 3, + "bps": 0, + "nodes": [ + "264/b7f42006", + "264/b7f42006", + "264/b7f42006" + ] + }, + { + "t": 260.2, + "daa": 279, + "epoch": 4, + "class": 3, + "bps": 0, + "nodes": [ + "279/988cb551", + "279/988cb551", + "279/988cb551" + ] + }, + { + "t": 275.2, + "daa": 289, + "epoch": 4, + "class": 3, + "bps": 0, + "nodes": [ + "289/f38c1458", + "289/f38c1458", + "289/f38c1458" + ] + }, + { + "t": 290.3, + "daa": 305, + "epoch": 5, + "class": 3, + "bps": 0, + "nodes": [ + "305/9125bec4", + "305/9125bec4", + "305/9125bec4" + ] + }, + { + "t": 305.3, + "daa": 317, + "epoch": 5, + "class": 3, + "bps": 0, + "nodes": [ + "317/9dc60b29", + "317/9dc60b29", + "317/9dc60b29" + ] + }, + { + "t": 320.3, + "daa": 330, + "epoch": 5, + "class": 3, + "bps": 0, + "nodes": [ + "330/28eaf8e8", + "330/28eaf8e8", + "330/28eaf8e8" + ] + }, + { + "t": 335.4, + "daa": 346, + "epoch": 5, + "class": 3, + "bps": 0, + "nodes": [ + "346/2c358b1f", + "346/2c358b1f", + "346/2c358b1f" + ] + }, + { + "t": 350.4, + "daa": 367, + "epoch": 6, + "class": 4, + "bps": 0, + "nodes": [ + "367/b420706f", + "367/b420706f", + "367/b420706f" + ] + }, + { + "t": 365.4, + "daa": 381, + "epoch": 6, + "class": 4, + "bps": 0, + "nodes": [ + "381/9363f9bd", + "381/9363f9bd", + "381/9363f9bd" + ] + }, + { + "t": 380.5, + "daa": 397, + "epoch": 6, + "class": 4, + "bps": 0, + "nodes": [ + "397/49b6c6a3", + "397/49b6c6a3", + "397/49b6c6a3" + ] + }, + { + "t": 395.5, + "daa": 417, + "epoch": 6, + "class": 4, + "bps": 0, + "nodes": [ + "417/cbc74290", + "417/cbc74290", + "417/cbc74290" + ] + }, + { + "t": 410.5, + "daa": 430, + "epoch": 7, + "class": 4, + "bps": 0, + "nodes": [ + "430/08e73746", + "430/08e73746", + "430/08e73746" + ] + }, + { + "t": 425.6, + "daa": 449, + "epoch": 7, + "class": 4, + "bps": 0, + "nodes": [ + "449/6067e6b4", + "449/6067e6b4", + "449/6067e6b4" + ] + }, + { + "t": 440.6, + "daa": 460, + "epoch": 7, + "class": 4, + "bps": 0, + "nodes": [ + "460/1ae21462", + "460/1ae21462", + "460/1ae21462" + ] + }, + { + "t": 455.6, + "daa": 468, + "epoch": 7, + "class": 4, + "bps": 0, + "nodes": [ + "468/f9f3ca09", + "468/f9f3ca09", + "468/f9f3ca09" + ] + } + ] +} \ No newline at end of file diff --git a/docs/plans/counter-asic-3-gate/class-v4-signal7-no-flip.json b/docs/plans/counter-asic-3-gate/class-v4-signal7-no-flip.json new file mode 100644 index 000000000..0ab00c97d --- /dev/null +++ b/docs/plans/counter-asic-3-gate/class-v4-signal7-no-flip.json @@ -0,0 +1,555 @@ +{ + "pass": true, + "expect": "no-flip", + "signals": [ + 4, + 4, + 3 + ], + "checks": { + "zero_rejected_by_miners": true, + "zero_rejected_by_nodes": true, + "sinks_agree": true, + "block_counts_agree": true, + "miners_agree_on_every_program": true, + "window_line_on_every_node": true, + "every_node_signals_its_byte": true, + "chain_carries_the_bytes": true, + "template_never_v4": true, + "no_signal_line_on_any_node": true, + "ran_the_epochs": true, + "v3_programs_seen": true, + "signal_share_under_threshold_on_chain": true + }, + "window": 30, + "windows": 7, + "floor": 100000, + "v3_activation": 60, + "epoch_blocks": 60, + "lead": 10, + "first_full_window_epoch": 4, + "floor_epoch": 1667, + "node": "/Users/joshm/Projects/igneum-wt-ca3-v4-node/vendor/igneum-node-ca3v4/target-ca3v4/release/igneumd", + "miner": "/Users/joshm/Projects/igneum-wt-ca3-v4-node/vendor/igneum-node-ca3v4/target-ca3v4/release/igneum-miner", + "template_switch": null, + "run_ended_at_s": 414.3, + "final_daa": 420, + "max_epoch_seen": 7, + "epochs": { + "0": { + "class": 2, + "firstSeenDaa": 0, + "at": 4.6, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "1": { + "class": 3, + "firstSeenDaa": 60, + "at": 27.7, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "2": { + "class": 3, + "firstSeenDaa": 120, + "at": 107.8, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "3": { + "class": 3, + "firstSeenDaa": 182, + "at": 159.9, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 0, + "signal_epoch": null + }, + "4": { + "class": 3, + "firstSeenDaa": 240, + "at": 226, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 6333, + "signal_epoch": null + }, + "5": { + "class": 3, + "firstSeenDaa": 300, + "at": 286.1, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 6000, + "signal_epoch": null + }, + "6": { + "class": 3, + "firstSeenDaa": 361, + "at": 350.2, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 5666, + "signal_epoch": null + }, + "7": { + "class": 3, + "firstSeenDaa": 420, + "at": 414.3, + "eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062", + "bps": 6000, + "signal_epoch": null + } + }, + "blocks": { + "total": 426, + "before_boundary": 426, + "after_boundary": 0, + "version_bytes": { + "0": 1, + "3": 129, + "4": 296 + }, + "signal_share_bps_on_chain": 6948 + }, + "programs": [ + { + "epoch": 0, + "class": "v2", + "program_id": "8f8806638d59850f", + "seed": "234e082d653dc69d", + "miners": 3, + "disagree": false + }, + { + "epoch": 1, + "class": "v3", + "program_id": "769b4dd980c7f126", + "seed": "fba8aa7ae82c47e9", + "miners": 3, + "disagree": false + }, + { + "epoch": 2, + "class": "v3", + "program_id": "56335da09deca248", + "seed": "64b63eff660aaeec", + "miners": 3, + "disagree": false + }, + { + "epoch": 3, + "class": "v3", + "program_id": "f2f883996849c0e5", + "seed": "b0f742cf4d197b15", + "miners": 3, + "disagree": false + }, + { + "epoch": 4, + "class": "v3", + "program_id": "c59d7d33e4749671", + "seed": "4bec73e616207ebc", + "miners": 3, + "disagree": false + }, + { + "epoch": 5, + "class": "v3", + "program_id": "8bab4d2320643cc8", + "seed": "bf8b384490f61b1a", + "miners": 3, + "disagree": false + }, + { + "epoch": 6, + "class": "v3", + "program_id": "42a5cc579d9dc917", + "seed": "9399732456e83970", + "miners": 3, + "disagree": false + }, + { + "epoch": 7, + "class": "v3", + "program_id": "7d7f6dfb6df1b584", + "seed": "c47f438d5f83e60e", + "miners": 3, + "disagree": false + } + ], + "program_id_rows": [], + "accepted_per_miner": [ + 155, + 141, + 129 + ], + "rejected_by_miners": [ + 0, + 0, + 0 + ], + "rejected_by_nodes": [ + 0, + 0, + 0 + ], + "sinks": [ + "5d0d7f21de33c29b", + "5d0d7f21de33c29b", + "5d0d7f21de33c29b" + ], + "block_counts": [ + 425, + 425, + 425 + ], + "signal_lines": [ + null, + null, + null + ], + "floor_lines": [ + "Program class v4 from the override file: active from epoch 1667 (DAA score 100000 rounded up to the epoch boundary at 100020, epochs of 60 DAA)", + "Program class v4 from the override file: active from epoch 1667 (DAA score 100000 rounded up to the epoch boundary at 100020, epochs of 60 DAA)", + "Program class v4 from the override file: active from epoch 1667 (DAA score 100000 rounded up to the epoch boundary at 100020, epochs of 60 DAA)" + ], + "samples": [ + { + "t": 4.6, + "daa": 0, + "epoch": 0, + "class": 2, + "bps": 0, + "nodes": [ + "0/234e082d", + "0/234e082d", + "0/234e082d" + ] + }, + { + "t": 19.7, + "daa": 42, + "epoch": 0, + "class": 2, + "bps": 0, + "nodes": [ + "42/b78cd510", + "42/b78cd510", + "42/b78cd510" + ] + }, + { + "t": 34.7, + "daa": 66, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "66/e6e0e6b6", + "66/e6e0e6b6", + "66/e6e0e6b6" + ] + }, + { + "t": 49.7, + "daa": 69, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "69/3e334b41", + "69/3e334b41", + "69/3e334b41" + ] + }, + { + "t": 64.7, + "daa": 74, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "74/f6732168", + "74/f6732168", + "74/f6732168" + ] + }, + { + "t": 79.8, + "daa": 91, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "91/4c5172a4", + "91/4c5172a4", + "91/4c5172a4" + ] + }, + { + "t": 94.8, + "daa": 104, + "epoch": 1, + "class": 3, + "bps": 0, + "nodes": [ + "104/8bed6da0", + "104/8bed6da0", + "104/8bed6da0" + ] + }, + { + "t": 109.8, + "daa": 123, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "123/7c7f2a35", + "123/7c7f2a35", + "123/7c7f2a35" + ] + }, + { + "t": 124.8, + "daa": 134, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "134/11ee9640", + "134/11ee9640", + "134/11ee9640" + ] + }, + { + "t": 139.9, + "daa": 161, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "161/615ad11d", + "161/615ad11d", + "161/615ad11d" + ] + }, + { + "t": 154.9, + "daa": 172, + "epoch": 2, + "class": 3, + "bps": 0, + "nodes": [ + "172/52e6efa7", + "172/52e6efa7", + "172/52e6efa7" + ] + }, + { + "t": 169.9, + "daa": 195, + "epoch": 3, + "class": 3, + "bps": 5666, + "nodes": [ + "195/d5003862", + "195/d5003862", + "195/d5003862" + ] + }, + { + "t": 184.9, + "daa": 204, + "epoch": 3, + "class": 3, + "bps": 5666, + "nodes": [ + "204/f43a9613", + "204/f43a9613", + "204/f43a9613" + ] + }, + { + "t": 199.9, + "daa": 217, + "epoch": 3, + "class": 3, + "bps": 6333, + "nodes": [ + "217/f2899339", + "217/f2899339", + "217/f2899339" + ] + }, + { + "t": 215, + "daa": 229, + "epoch": 3, + "class": 3, + "bps": 5333, + "nodes": [ + "229/aa39dc5b", + "229/aa39dc5b", + "229/aa39dc5b" + ] + }, + { + "t": 230, + "daa": 243, + "epoch": 4, + "class": 3, + "bps": 6333, + "nodes": [ + "243/84160223", + "243/84160223", + "243/84160223" + ] + }, + { + "t": 245, + "daa": 256, + "epoch": 4, + "class": 3, + "bps": 5333, + "nodes": [ + "256/95be3208", + "256/95be3208", + "256/95be3208" + ] + }, + { + "t": 260, + "daa": 274, + "epoch": 4, + "class": 3, + "bps": 6000, + "nodes": [ + "274/17786bd2", + "274/17786bd2", + "274/17786bd2" + ] + }, + { + "t": 275.1, + "daa": 287, + "epoch": 4, + "class": 3, + "bps": 5666, + "nodes": [ + "287/5f1d09c0", + "287/5f1d09c0", + "287/5f1d09c0" + ] + }, + { + "t": 290.1, + "daa": 304, + "epoch": 5, + "class": 3, + "bps": 6000, + "nodes": [ + "304/b3b30ae6", + "304/b3b30ae6", + "304/b3b30ae6" + ] + }, + { + "t": 305.1, + "daa": 318, + "epoch": 5, + "class": 3, + "bps": 5666, + "nodes": [ + "318/f040c6e4", + "318/f040c6e4", + "318/f040c6e4" + ] + }, + { + "t": 320.1, + "daa": 341, + "epoch": 5, + "class": 3, + "bps": 6333, + "nodes": [ + "341/34fddad1", + "341/34fddad1", + "341/34fddad1" + ] + }, + { + "t": 335.2, + "daa": 350, + "epoch": 5, + "class": 3, + "bps": 5333, + "nodes": [ + "350/93997324", + "350/93997324", + "350/93997324" + ] + }, + { + "t": 350.2, + "daa": 361, + "epoch": 6, + "class": 3, + "bps": 5666, + "nodes": [ + "361/a6f64b19", + "361/a6f64b19", + "361/a6f64b19" + ] + }, + { + "t": 365.2, + "daa": 381, + "epoch": 6, + "class": 3, + "bps": 5666, + "nodes": [ + "381/c5f7f5e5", + "381/c5f7f5e5", + "381/c5f7f5e5" + ] + }, + { + "t": 380.2, + "daa": 395, + "epoch": 6, + "class": 3, + "bps": 6000, + "nodes": [ + "395/671be986", + "395/671be986", + "395/671be986" + ] + }, + { + "t": 395.3, + "daa": 404, + "epoch": 6, + "class": 3, + "bps": 5666, + "nodes": [ + "404/e5f584eb", + "404/e5f584eb", + "404/e5f584eb" + ] + }, + { + "t": 410.3, + "daa": 417, + "epoch": 6, + "class": 3, + "bps": 6000, + "nodes": [ + "417/ccbc9572", + "417/ccbc9572", + "417/ccbc9572" + ] + } + ] +} \ No newline at end of file diff --git a/docs/plans/counter-asic-3-node.md b/docs/plans/counter-asic-3-node.md index 2a0422334..9d9070ece 100644 --- a/docs/plans/counter-asic-3-node.md +++ b/docs/plans/counter-asic-3-node.md @@ -98,7 +98,7 @@ Status: PROPOSED spec text for spec 01 (a new section 1.12.2 beside the epoch ru 1. **The carrier: the block header's `version` field, 2 bytes little-endian.** The low byte is the block version as before (2 on every Igneum network; `block_version_of`). The high byte is the producer's OBJECT VERSION, the highest program class the node that built the template runs: 4 for this binary (`CLASS_SIGNAL_V4`), 0 on every block made before it (`signalled_version(2, 4) = 0x0402`; `class_signal_of`). Why the header and not the coinbase extra data: the tally is read in header validation and by a node that holds headers only (a pruning-proof sync, a headers-first IBD), the header is what GHOSTDAG orders and what the finality rule already reads for its weight (the vote key hash is a header field, `finality.rs`), and the bytes are already there: no new field, no new hash preimage, no proto change for the header. A node before this binary rejects any header whose version is not exactly 2 (`check_header_version`), which is why the signalling binary ships in the same one-sweep rollout as the digest flip it already needs (section 2); from this binary on, only the low byte is checked, so a later object (5, 6) can be signalled to it without another header rule. 2. **The weight: blue blocks, the finality rule's convention.** For epoch `e` the anchor is its seed block `S_e`, the last selected-chain block whose DAA score is below `L e - lead` (the block the epoch seed is already taken from, `HeaderProcessor::epoch_seed`; so an epoch's seed and its class are decided at the same block of the same chain). The window is the `W` DAA below and including `S_e`: the selected chain is walked down from `S_e`, every chain block's mergeset blues counted once (the `compute_weights` walk of `finality.rs`, bounded by the merge depth), a blue block counted when its DAA score lies in `(daa(S_e) - W, daa(S_e)]`, and signalling when its object byte is at least 4. Share = signalling / total, in basis points. 3. **The decision, per epoch, monotone.** Epoch `e` is class v4 when (a) `L e >= N6`, the floor (the fixed height, rounded up to the epoch boundary exactly as the v3 switch is: `program_class_for_epoch_at`), or (b) epoch `e - 1` was v4, or (c) the window ending at `S_e` is FULL (`daa(S_e) >= W`) and its share is at least 9,500 bps. A signal moves the class one step: the rule answers v4 only where the floor rule answers v3 (below the v3 switch the answer is v2 whatever is signalled). The decision is memoised per seed block, so a fork of the chain has its own entries and one tally is paid per epoch per process. The epoch-boundary rounding of the v2 to v3 switch is unchanged: a class never changes inside an epoch, every block's class is its epoch's. -4. **The window `W`: 86,400 DAA (one day of blocks at 1 block/s), `program_class_v4_signal_window_daa` in the override file, in the digest right after the floor; 0 = signalling off, the floor alone (2.0's rule, byte for byte).** Why a day: the share must mean "the fleet that mines, not the fleet that happens to be up this hour", and a day covers every box's daily pattern (the rented boxes come and go by the hour); it is long enough that 95 percent cannot be reached by a burst and short enough that the flip lands within a day of the last upgrade; the finality rule's 30-day window answers a different question (who may vote) and would hold the class for a month after the fleet was ready. On the fast-time profile `W` is 120 (two epochs of 60). +4. **The window `W`: 86,400 DAA (one day of blocks at 1 block/s), `program_class_v4_signal_window_daa` in the override file, in the digest once set (the 0.3.15 rule), right after the floor; 0 = signalling off, the floor alone (2.0's rule, byte for byte). REVISED 0.3.16 (Horizon consensus-security): the threshold must hold in EACH of `CLASS_SIGNAL_WINDOWS` = 7 CONSECUTIVE windows ending at the epoch's seed block (a week of days), because one day of 95 percent can be bought for a day (about 19 N of hash for 24 hours) and would force a flip onto a fleet not yet on the object, and seven consecutive days cannot be bought unnoticed; the floor is then set a week past the publish (DAA + 604,800 rounded up), not 14,400. The tally is one walk of `7 W` DAA bucketed per window; the decision rests on the weakest of the seven (`programClassV4SignalWeakestBps`), the console shows the newest (`programClassV4SignalBps`).** Why a day: the share must mean "the fleet that mines, not the fleet that happens to be up this hour", and a day covers every box's daily pattern (the rented boxes come and go by the hour); it is long enough that 95 percent cannot be reached by a burst and short enough that the flip lands within a day of the last upgrade; the finality rule's 30-day window answers a different question (who may vote) and would hold the class for a month after the fleet was ready. On the fast-time profile `W` is 120 (two epochs of 60). 5. **The threshold: 9,500 bps (95 percent), a constant (`CLASS_SIGNAL_THRESHOLD_BPS`), not a file field**, so no file can lower it; 95 percent of the blue blocks of a day is 95 percent of the hash rate of that day, the coordinator's figure; a box that cannot mine v4 (an old worker, section 2's wire note) is at most 5 percent of the hash rate at the flip, and the floor catches the rest. 6. **The floor `N6`: `program_class_v4_activation_daa`, set at the publish as DAA + 14,400 rounded up to the epoch boundary (the 2.0 rule for N4), checked `N6 - DAA >= 10,800`.** A stalled signal (a fleet that never reaches 95 percent) cannot hold the class forever: at `N6` v4 holds regardless. `never` is allowed in the file and means no floor (the signal alone decides; not for the devnet publish). 7. **What a node reports.** `PowEpochInfo` and the template's `powEpoch` carry `programClassV4SignalWindowDaa`, `programClassV4SignalBps` (the share of the window ending at the SINK, the live tally the next decision is heading for), `programClassSignal` (this node's byte) and `programClassV4SignalEpoch` (the epoch v4 was decided by signal on this chain, when it has been); gRPC fields 20 to 23. The daemon prints `Program class v4 signal window from the override file: W DAA ending at each epoch's seed block, threshold 9500 bps of blue blocks; the fixed height is the floor` beside the floor line, and `Program class v4 by miner signal: epoch E (share X bps over W DAA ending at seed block S, threshold 9500 bps, N of M blue blocks)` once per flip. @@ -129,4 +129,15 @@ Two fields join the live object: `program_class_v4_activation_daa` is now the FL The first pass of the three good cases (17:01 to 17:20Z) had the same chain behaviour and failed on one harness check of its own (`chain_carries_the_bytes` demanded every byte on the chain be a node's and genesis carries byte 0); the check was corrected to allow the one genesis block and the three cases re-run above. Every number here is a count, a line or an id; the Mac's load average (about 9, other agents' builds) moves the wall times only. +### 6.5 Seven windows (0.3.16, fork 0760b844): the fast-time gate re-run + +The rule changed on the 0.3.16 branch (Horizon consensus-security): 95 percent in EACH of `CLASS_SIGNAL_WINDOWS = 7` consecutive windows ending at the epoch's seed block, decided on the weakest of the seven; one day of 95 percent can be bought for a day, seven cannot unnoticed. With the harness's 30-DAA window (two per 60-DAA epoch) the seven windows are 210 DAA and the first epoch whose seed block (at 60 e - 10) has 210 DAA below it is epoch 4 (seed at DAA 230), so a flip lands at DAA 240. A floor is REQUIRED for a signalling case since the 0.3.15 canary rule (a node stamps and reads the signal only with both v4 fields set); the first 0.3.16 pass of this harness (20:56Z) ran with the floor at never and every chain byte read 0. The four cases below ran with `--floor 100000` (27 hours of fast-time chain away) or the floor under test, binaries from the fork at 0760b844 (`target-ca3v4`), 6 October 2026, load average 7 to 10 (other agents' builds on the box; counts and ids only here). + +| Case | Run (UTC) | SUMMARY | Summary file | +|---|---|---|---| +| Two of three signal (`--signal 4,4,3 --floor 100000 --expect no-flip --epochs 7`) | 20:57:5x to 21:04:45 | PASS: no v4 epoch over epochs 0 to 7; the weakest-window share at the sink 5,666 to 6,333 bps from epoch 4 (0 before: fewer than seven windows exist); on the chain 296 blocks byte 4, 129 byte 3, genesis byte 0 (6,948 bps); no signal line on any node; 0 rejected; one sink `5d0d7f21` at 425/425/425 | `counter-asic-3-gate/class-v4-signal7-no-flip.json` | +| All three signal (`--signal 4,4,4 --floor 100000 --expect flip`) | 21:04:5x to 21:10:29 | PASS: the share 10,000 bps in every window; the template flipped at epoch 4 (DAA 241 seen, boundary 240), the first epoch with seven full windows, before the floor; 3 of 3 nodes printed `Program class v4 by miner signal: epoch 4 (share 10000 bps over 30 DAA ending at seed block a5adea6f..., threshold 9500 bps in each of 7 consecutive windows, weakest 10000 bps, shares oldest first [10000 x 7], 210 of 210 blue blocks)`; 241 / 124 blocks either side; 0 rejected; one sink `786d8071` at 364/364/364; the id assertion on epochs 4, 5, 6 (miners' id = CLI v4 id, differs from CLI v3 id) | `class-v4-signal7-flip.json` | +| Nobody signals, the floor at 360 (`--signal 3,3,3 --floor 360 --expect floor`) | 21:10:3x to 21:18:15 | PASS: share 0 bps in every window (485 blocks byte 3); no signal line; the class flipped at epoch 6 (DAA 362 seen, the floor's boundary 360) and not before; `active from epoch 6` on 3 of 3; 361 / 125 blocks; 0 rejected; one sink `0db7f95e` at 485/485/485; the id assertion on epochs 6, 7, 8 | `class-v4-signal7-floor.json` | +| The known-failed case (`--signal 4,4,3 --floor 100000 --expect flip --epochs 7`) | 21:18:17 to 21:25:10 | FAIL as it must: no flip at a weakest-window share of 5,333 to 6,333 bps (two CPU miners of three signalling; 288 blocks byte 4, 135 byte 3 on the chain, 6,792 bps); the harness reports `FAILED CHECK` on nine checks (`template_switched_to_v4`, `switched_at_the_first_full_window_epoch`, `switched_before_the_floor`, `signal_line_on_every_node_same_epoch`, `signal_share_at_or_above_threshold`, `signal_line_names_seven_windows`, `blocks_on_both_sides`, `v4_ids_equal_the_cli_v4_id`, `v4_ids_differ_from_the_same_seed_v3_id`) and exits 1; one sink `1c1eae6d` at 423/423/423 | `class-v4-signal7-failed-case.json` | + Fork suites on the signalling change (0562a7f2): kaspa-consensus-core 110 + 7 (the signal-rule test, the window in the params, digest and fast-time file tests; the pinned devnet digest re-pinned from `3c505021...` to `7f2e49beabc253f327c5ac6bb457a674ea7f527af2971c95d3bdf65ef8bcf977`), kaspa-consensus lib 98 (the template test now reads the low byte and the signal byte), kaspa-pow with the feature 15; PC 2: section G6 of `node-gates.md`. diff --git a/infra/fast-time/class-v4-signal.mjs b/infra/fast-time/class-v4-signal.mjs index 84de8f5a1..79e942e34 100644 --- a/infra/fast-time/class-v4-signal.mjs +++ b/infra/fast-time/class-v4-signal.mjs @@ -4,17 +4,24 @@ // so the class v4 decision is made by the miners' blue-block share over the signal window, not by a fixed height. // Ports 29690 and up, network igneum-devnet-969, data /tmp/igneum-fast-time-v4s; the class-v4.mjs shape otherwise: // override-60x.json with CPU genesis bits, v3 from --v3-activation (default 60, epoch 1), the v4 floor at --floor -// (default never), the window at --window (default 120 DAA, two epochs; the first epoch whose seed block has DAA >= 120 -// is epoch 3, seed at DAA 169), one real CPU miner per node. +// (default never), the window at --window (default 30 DAA), one real CPU miner per node. +// +// The rule (0.3.16, Horizon consensus-security): 95 percent in EACH of CLASS_SIGNAL_WINDOWS = 7 consecutive windows +// of --window DAA ending at the epoch's seed block (one day of 95 percent can be bought for a day; seven cannot +// unnoticed). With the default 30-DAA window (two per 60-DAA epoch) the seven windows are 210 DAA, and the first +// epoch whose seed block (at 60 e - 10) has 210 DAA below it is epoch 4 (seed at DAA 230), so the flip is at DAA 240. // // The three cases and the known-failed case: -// --signal 4,4,3 --expect no-flip two of three miners signal: 67 percent, the class must stay v3 (run 7 epochs) -// --signal 4,4,4 --expect flip all three: it flips at epoch 3, the first boundary with a full window -// --signal 3,3,3 --floor 300 --expect floor nobody signals: it flips at the floor (epoch 5, DAA 300) and not before -// --signal 4,4,3 --expect flip the known-failed case: the harness must report FAIL (no flip happened) +// --signal 4,4,3 --floor 100000 --expect no-flip two of three miners signal: 67 percent, the class must stay v3 (run 7 epochs) +// --signal 4,4,4 --floor 100000 --expect flip all three: it flips at epoch 4, the first boundary with seven full windows +// --signal 3,3,3 --floor 360 --expect floor nobody signals: it flips at the floor (epoch 6, DAA 360) and not before +// --signal 4,4,3 --floor 100000 --expect flip the known-failed case: the harness must report FAIL (no flip happened) +// The floor is REQUIRED for a signalling case: since the 0.3.15 canary rule a node stamps and reads the signal only with +// both v4 fields set (the window and a floor), so a floor at never means nobody signals (the first 0.3.16 run of this +// harness, 20:56Z: chain bytes all 0); a far floor (100,000 DAA, 27 hours of fast-time chain) keeps it out of the run. // // node infra/fast-time/class-v4-signal.mjs --signal a,b,c --expect flip|no-flip|floor [--floor |never] -// [--window 120] [--v3-activation 60] [--secs 480] [--epochs 7] +// [--window 30] [--v3-activation 60] [--secs 480] [--epochs 7] // IGNEUMD, IGNEUM_MINER, IGNEUM_POW name the binaries (defaults: the ca3 fork worktree's target-ca3v4/release and // igneum-pow/target/release/igneum-pow). @@ -41,7 +48,8 @@ const SECS = flag('secs', 480); const EPOCHS = flag('epochs', 7); const FLOOR = actflag('floor', null); const V3_ACTIVATION = actflag('v3-activation', 60); -const WINDOW = flag('window', 120); +const WINDOW = flag('window', 30); +const WINDOWS = 7; // CLASS_SIGNAL_WINDOWS in the node const SIGNAL = (sflag('signal') || '4,4,4').split(',').map(Number); const EXPECT = sflag('expect') || 'flip'; if (!['flip', 'no-flip', 'floor'].includes(EXPECT) || SIGNAL.length !== 3) { console.error('usage: --signal a,b,c --expect flip|no-flip|floor'); process.exit(2); } @@ -58,9 +66,9 @@ const LEAD = field('pow_epoch_lead'); const DAY_MS = field('pow_day_ms'); const FIRST_V3_EPOCH = V3_ACTIVATION == null ? null : Math.ceil(V3_ACTIVATION / EPOCH); const FLOOR_EPOCH = FLOOR == null ? null : Math.ceil(FLOOR / EPOCH); -// the first epoch whose seed block (the last chain block below L*e - lead) can have DAA >= WINDOW: L*e - lead - 1 >= WINDOW +// the first epoch whose seed block (the last chain block below L*e - lead) has WINDOWS x WINDOW DAA below it let FIRST_FULL_EPOCH = 0; -while (FIRST_FULL_EPOCH * EPOCH - LEAD - 1 < WINDOW) FIRST_FULL_EPOCH++; +while (FIRST_FULL_EPOCH * EPOCH - LEAD - 1 < WINDOWS * WINDOW) FIRST_FULL_EPOCH++; export function mergeOverrideText(text, fields) { let out = text; for (const k of Object.keys(fields)) out = out.replace(new RegExp(`\\s*"${k}":\\s*[^,}\\n]+,?`), ''); @@ -70,7 +78,7 @@ export function mergeOverrideText(text, fields) { const asText = (v) => v == null ? NEVER : String(v); const override = `${TMP}/override.json`; writeFileSync(override, mergeOverrideText(baseText, { genesis_bits: GENESIS_BITS, skip_proof_of_work: false, program_class_v3_activation_daa: asText(V3_ACTIVATION), program_class_v4_activation_daa: asText(FLOOR), program_class_v4_signal_window_daa: String(WINDOW) })); -log(`signals ${SIGNAL.join('/')}, expect ${EXPECT}; v3 from ${V3_ACTIVATION ?? 'never'} (epoch ${FIRST_V3_EPOCH ?? 'none'}), v4 floor ${FLOOR ?? 'never'} (epoch ${FLOOR_EPOCH ?? 'none'}), window ${WINDOW} DAA (the first epoch with a full window is ${FIRST_FULL_EPOCH}); ${EPOCH} DAA per epoch, lead ${LEAD}; run ${SECS} s or ${EPOCHS} epochs`); +log(`signals ${SIGNAL.join('/')}, expect ${EXPECT}; v3 from ${V3_ACTIVATION ?? 'never'} (epoch ${FIRST_V3_EPOCH ?? 'none'}), v4 floor ${FLOOR ?? 'never'} (epoch ${FLOOR_EPOCH ?? 'none'}), window ${WINDOW} DAA x ${WINDOWS} consecutive (the first epoch with seven full windows is ${FIRST_FULL_EPOCH}); ${EPOCH} DAA per epoch, lead ${LEAD}; run ${SECS} s or ${EPOCHS} epochs`); class Node { constructor(i, connect = []) { @@ -109,7 +117,7 @@ async function stopAll() { process.on('SIGINT', async () => { await stopAll(); process.exit(130); }); process.on('unhandledRejection', async (e) => { log(`FAILED: ${e?.stack || e}`); await stopAll(); process.exit(3); }); const minerLog = (i) => { try { return readFileSync(`${TMP}/cpu${i}.log`, 'utf8').split('\n'); } catch { return []; } }; -const SIGNAL_LINE = /Program class v4 by miner signal: epoch (\d+) \(share (\d+) bps/; +const SIGNAL_LINE = /Program class v4 by miner signal: epoch (\d+) \(share (\d+) bps over \d+ DAA ending at seed block \S+, threshold \d+ bps in each of (\d+) consecutive windows, weakest (\d+) bps/; const FLOOR_LINE = /Program class v4 from the override file/; const WINDOW_LINE = /Program class v4 signal window from the override file/; const OWN_LINE = /Program class signal from IGNEUM_CLASS_SIGNAL: this node signals object version (\d+)/; @@ -135,7 +143,7 @@ while (Date.now() - t0 < SECS * 1000) { const t = await n0.rpc.call('getBlockTemplate', { payAddress: pay, extraData: [] }); const pe = t.powEpoch || t.pow_epoch || {}; daa = pe.virtualDaaScore ?? t.block?.header?.daaScore; epoch = pe.epochIndex; cls = pe.programClass; nextCls = pe.nextProgramClass; - eraSeed = pe.eraSeed; bps = pe.programClassV4SignalBps; win = pe.programClassV4SignalWindowDaa; sig = pe.programClassSignal; sigEpoch = pe.programClassV4SignalEpoch; + eraSeed = pe.eraSeed; bps = pe.programClassV4SignalWeakestBps ?? pe.programClassV4SignalBps; win = pe.programClassV4SignalWindowDaa; sig = pe.programClassSignal; sigEpoch = pe.programClassV4SignalEpoch; } catch (e) { log(`template: ${e.message}`); } if (epoch != null && epoch !== lastEpoch) { epochs.set(epoch, { class: cls, firstSeenDaa: daa, at: +since(), eraSeed: eraSeed == null ? null : String(eraSeed), bps, signal_epoch: sigEpoch ?? null }); @@ -204,7 +212,8 @@ const rejectedMiner = [0, 1, 2].map(i => minerLog(i).filter(l => /rejected nonce const rejectedNode = nodes.map(n => n.grepLog(/PoW rejected|Rejected block|rejected block/i)); const signalLines = nodes.map(n => n.grepLog(SIGNAL_LINE).map(l => l.replace(/^.*?(Program class v4 by miner signal)/, '$1'))[0] || null); const signalEpochs = signalLines.map(l => { const m = l && SIGNAL_LINE.exec(l); return m ? +m[1] : null; }); -const signalShares = signalLines.map(l => { const m = l && SIGNAL_LINE.exec(l); return m ? +m[2] : null; }); +const signalShares = signalLines.map(l => { const m = l && SIGNAL_LINE.exec(l); return m ? +m[4] : null; }); // the weakest of the seven +const signalWindows = signalLines.map(l => { const m = l && SIGNAL_LINE.exec(l); return m ? +m[3] : null; }); const floorLines = nodes.map(n => n.grepLog(FLOOR_LINE).map(l => l.replace(/^.*?(Program class v4 from)/, '$1'))[0] || null); const sinks = dag.map(d => String(d.sink || '?').slice(0, 16)); const counts = dag.map(d => d.blockCount ?? '?'); @@ -231,6 +240,7 @@ if (EXPECT === 'flip') { switched_before_the_floor: firstV4 != null && (FLOOR_EPOCH == null || firstV4.epoch < FLOOR_EPOCH), signal_line_on_every_node_same_epoch: signalEpochs.every(e => e != null) && new Set(signalEpochs).size === 1 && signalEpochs[0] === (firstV4 && firstV4.epoch), signal_share_at_or_above_threshold: signalShares.every(s => s != null && s >= 9500), + signal_line_names_seven_windows: signalWindows.every(w => w === WINDOWS), blocks_on_both_sides: before.length > 0 && after.length > 0, v4_ids_equal_the_cli_v4_id: idRows.length > 0 && idRows.every(r => r.cli_v4 != null && r.cli_v4 === r.miners_id && r.miners === 3), v4_ids_differ_from_the_same_seed_v3_id: idRows.length > 0 && idRows.every(r => r.cli_v3 != null && r.cli_v3 !== r.miners_id), @@ -257,7 +267,7 @@ if (EXPECT === 'flip') { } const pass = Object.values(checks).every(Boolean); const summary = { - pass, expect: EXPECT, signals: SIGNAL, checks, window: WINDOW, floor: FLOOR ?? 'never', v3_activation: V3_ACTIVATION ?? 'never', epoch_blocks: EPOCH, lead: LEAD, first_full_window_epoch: FIRST_FULL_EPOCH, floor_epoch: FLOOR_EPOCH, + pass, expect: EXPECT, signals: SIGNAL, checks, window: WINDOW, windows: WINDOWS, floor: FLOOR ?? 'never', v3_activation: V3_ACTIVATION ?? 'never', epoch_blocks: EPOCH, lead: LEAD, first_full_window_epoch: FIRST_FULL_EPOCH, floor_epoch: FLOOR_EPOCH, node: IGNEUMD, miner: CPU_MINER, template_switch: firstV4, run_ended_at_s: endAt, final_daa: lastDaa, max_epoch_seen: maxEpochSeen, epochs: Object.fromEntries([...epochs.entries()].map(([k, v]) => [k, v])), blocks: { total: blocks.length, before_boundary: before.length, after_boundary: after.length, version_bytes: versionBytes, signal_share_bps_on_chain: signalShareOnChain }, diff --git a/infra/fast-time/difficulty-v3.mjs b/infra/fast-time/difficulty-v3.mjs new file mode 100644 index 000000000..6449b7db3 --- /dev/null +++ b/infra/fast-time/difficulty-v3.mjs @@ -0,0 +1,279 @@ +#!/usr/bin/env node +// Horizon lane 5, proposal 2 (docs/analysis/horizon/network.md 5.4 and 6): the difficulty controller against a +// relay-delayed DAG. Three nodes in a star, the two spokes reaching the hub through a TCP proxy that holds every byte +// for --delay-ms in each direction (the handshake's first --handshake-ms pass undelayed: the p2p version exchange +// times out at 4 s, protocol/p2p/src/handshake.rs), one real CPU miner per node, override-60x.json re-rated to --bps. +// A block crosses a link in three legs (inv, request, block: Kaspa's relay flow), so the document's propagation +// delay d (--propagation-ms, 3,000 or 30,000) is one third per leg: --delay-ms defaults to --propagation-ms / 3. +// +// The claim under test: under rule v3 (difficulty_v3_activation_daa 0: every chain step counts every merged block's +// work over the real span, the clock cap and lag bound in DAA seconds, m x 20 s per merging step) the DAG rate holds +// the target and the difficulty tracks the miners' hash within 10 percent after --measure-from seconds; under the +// live rule (v2: blue work over a 20 T capped step) a 3-s delay at 10 bps settles near 6.67 blocks/s with the +// difficulty 1.5x the hash-implied value (controller.py), which is this harness's known-failed case. +// +// The cases (each --secs 900, measured from --measure-from 600): +// --bps 10 --propagation-ms 3000 --rule v3 --expect pass the proposal's first case +// --bps 1 --propagation-ms 30000 --rule v3 --expect pass the proposal's second case +// --bps 10 --propagation-ms 3000 --rule v2 --expect fail the known-failed case: the harness must report FAIL +// +// Pass = |DAG rate / target - 1| <= 0.10 over the measured window AND |expected hashes per block / (hash x T) - 1| +// <= 0.10 averaged over the window, where expected hashes per block come from the sink's bits (2^256 / (target + 1)) +// and hash is the sum of the three miners' own `hash=... MH/s` status lines (every 30 s). The red share, the chain +// step spacing and the three nodes' block counts are reported, not judged. +// +// node infra/fast-time/difficulty-v3.mjs --bps 10 --propagation-ms 3000 --rule v3 --expect pass [--secs 900] [--delay-ms ] +// [--measure-from 600] [--genesis-bits 0x1f080000] [--handshake-ms 5000] [--out docs/plans/counter-asic-3-gate/difficulty-v3-.json] +// IGNEUMD, IGNEUM_MINER name the binaries (defaults: vendor/igneum-node-0316/target-0316/release). + +import { spawn } from 'node:child_process'; +import { createServer, connect as netConnect } from 'node:net'; +import { mkdirSync, rmSync, writeFileSync, readFileSync, openSync, existsSync } from 'node:fs'; +import { connectRpc } from '../../tools/finality-attacks/lib/rpc.mjs'; +import { devAddress } from '../../tools/harness/lib/address.mjs'; + +const ROOT = new URL('../../', import.meta.url).pathname; +const FILE = `${ROOT}infra/fast-time/override-60x.json`; +const BIN = process.env.IGNEUM_0316_BIN || `${ROOT}vendor/igneum-node-0316/target-0316/release`; +const IGNEUMD = process.env.IGNEUMD || `${BIN}/igneumd`; +const CPU_MINER = process.env.IGNEUM_MINER || `${BIN}/igneum-miner`; +const TMP = '/tmp/igneum-fast-time-dv3'; +const BASE = 29790, SUFFIX = 979; +const NEVER = '18446744073709551615'; +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) => { const i = args.indexOf(`--${name}`); return i >= 0 ? args[i + 1] : null; }; +const BPS = flag('bps', 10); +const PROPAGATION_MS = flag('propagation-ms', 3000); +const DELAY_MS = flag('delay-ms', Math.round(PROPAGATION_MS / 3)); +const RULE = sflag('rule') || 'v3'; +const EXPECT = sflag('expect') || 'pass'; +const SECS = flag('secs', 900); +const MEASURE_FROM = flag('measure-from', 600); +const HANDSHAKE_MS = flag('handshake-ms', 5000); +const GENESIS_BITS = flag('genesis-bits', BPS >= 10 ? 0x1f080000 : 0x1f010000); // 2^13 hashes per block at 10 bps, 2^16 at 1 bps (mantissa x 2^224) +const CASE = sflag('case') || `${BPS}bps-d${PROPAGATION_MS}ms-${RULE}`; +const OUT = sflag('out') || `${ROOT}docs/plans/counter-asic-3-gate/difficulty-v3-${CASE}.json`; +if (!['v2', 'v3'].includes(RULE) || !['pass', 'fail'].includes(EXPECT) || ![1, 10].includes(BPS)) { console.error('usage: --bps 1|10 --delay-ms --rule v2|v3 --expect pass|fail'); process.exit(2); } +const T_MS = 1000 / BPS; +const started = []; +const log = (...a) => console.log(new Date().toISOString().slice(11, 23), ...a); +const sleep = (ms) => new Promise(r => setTimeout(r, ms)); +for (const b of [IGNEUMD, CPU_MINER]) if (!existsSync(b)) { console.error(`missing ${b}`); process.exit(2); } + +rmSync(TMP, { recursive: true, force: true }); mkdirSync(TMP, { recursive: true }); + +// The override file is merged as TEXT (a never height is beyond a JavaScript number; class-v4.mjs has the history). +const baseText = readFileSync(FILE, 'utf8'); +const field = (name) => { const m = new RegExp(`"${name}":\\s*([0-9]+)`).exec(baseText); return m ? +m[1] : undefined; }; +export 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) ? JSON.stringify(v) : v}`).join(', '); + return out.replace(/,?\s*}\s*$/, `,\n ${extra}\n}\n`); +} +// The block-rate block (the file's is 1 bps). At 10 bps: docs/override-params.md's 10 bps example for k, parents, +// mergeset limit and the sample rates; the depths are the file's x10 (merge 600, finality 7,200, pruning 138,380, +// above the Prunality bound 2F + 4 M k + 2k + 2 = 137,658); the PoW epoch 600 DAA with lead 100 so an epoch stays +// 60 s of wall time and the CPU miners regenerate their program as often as at 1 bps. +export function rerateBlockrate(text, bps) { + if (bps === 1) return text; + const block = `"blockrate": { + "target_time_per_block": 100, + "ghostdag_k": 124, + "past_median_time_sample_rate": 10, + "difficulty_sample_rate": 2, + "max_block_parents": 16, + "mergeset_size_limit": 248, + "merge_depth": 600, + "finality_depth": 7200, + "pruning_depth": 138380, + "coinbase_maturity": 20 + }`; + return text.replace(/"blockrate":\s*\{[^}]*\}/, block); +} +const EPOCH = BPS === 1 ? field('pow_epoch_blocks') : 600; +const LEAD = BPS === 1 ? field('pow_epoch_lead') : 100; +const DAY_MS = field('pow_day_ms'); +const override = `${TMP}/override.json`; +writeFileSync(override, mergeOverrideText(rerateBlockrate(baseText, BPS), { + genesis_bits: GENESIS_BITS, skip_proof_of_work: false, pow_epoch_blocks: EPOCH, pow_epoch_lead: LEAD, + difficulty_v2_activation_daa: '0', difficulty_v3_activation_daa: RULE === 'v3' ? '0' : NEVER, + program_class_v3_activation_daa: NEVER, program_class_v4_activation_daa: NEVER, +})); +log(`case ${CASE}: ${BPS} bps (T ${T_MS} ms), propagation ${PROPAGATION_MS} ms as a proxy delay of ${DELAY_MS} ms per leg each way (handshake ${HANDSHAKE_MS} ms undelayed), rule ${RULE}, genesis bits 0x${GENESIS_BITS.toString(16)} (${expectedHashes(GENESIS_BITS).toExponential(3)} hashes per block), ${SECS} s measured from ${MEASURE_FROM} s, expect ${EXPECT}`); + +// ---- the delay proxy ---- +// Every chunk in each direction is written to the other side DELAY_MS after it arrived (timers of one delay fire in +// order, so the byte order holds); the first HANDSHAKE_MS of a connection pass undelayed. Kaspa's flows wait up to +// 120 s (protocol/p2p/src/common.rs DEFAULT_TIMEOUT), so a 30-s delay (60-s round trip) fits inside them. +class DelayProxy { + constructor(listenPort, targetPort) { this.listenPort = listenPort; this.targetPort = targetPort; this.conns = 0; this.bytes = 0; } + start() { + return new Promise((resolve) => { + this.server = createServer((client) => { + this.conns++; + const opened = Date.now(); + const upstream = netConnect(this.targetPort, '127.0.0.1'); + const pipeDelayed = (from, to) => { + from.on('data', (chunk) => { + this.bytes += chunk.length; + const wait = Date.now() - opened < HANDSHAKE_MS ? 0 : DELAY_MS; + setTimeout(() => { if (!to.destroyed) to.write(chunk); }, wait); + }); + from.on('end', () => setTimeout(() => { if (!to.destroyed) to.end(); }, DELAY_MS)); + from.on('error', () => { if (!to.destroyed) to.destroy(); }); + from.on('close', () => setTimeout(() => { if (!to.destroyed) to.destroy(); }, DELAY_MS + 1000)); + }; + pipeDelayed(client, upstream); + pipeDelayed(upstream, client); + }); + this.server.listen(this.listenPort, '127.0.0.1', () => resolve(this)); + }); + } + stop() { try { this.server?.close(); } catch { } } +} + +class Node { + constructor(i, connect = []) { + this.i = i; this.grpcPort = BASE + i * 10; this.p2pPort = BASE + i * 10 + 1; this.jsonPort = BASE + i * 10 + 2; + this.connect = connect; this.dir = `${TMP}/n${i}`; this.logFile = `${this.dir}/node.log`; + } + get grpc() { return `grpc://127.0.0.1:${this.grpcPort}`; } + async start() { + mkdirSync(this.dir, { recursive: true }); + 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}`, + `--listen=127.0.0.1:${this.p2pPort}`, `--override-params-file=${override}`, '--loglevel=info', '--yes']; + if (this.connect.length) a.push(`--connect=${this.connect.join(',')}`); else a.push('--outpeers=0'); + const out = openSync(this.logFile, 'a'); + this.proc = spawn(IGNEUMD, a, { stdio: ['ignore', out, out] }); + started.push(this.proc); + await sleep(1500); + if (this.proc.exitCode != null) throw new Error(`n${this.i} exited ${this.proc.exitCode}: ${this.grepLog(/ERROR|Error|error|refused|invalid/).slice(-3).join(' | ')}`); + this.rpc = await connectRpc(`ws://127.0.0.1:${this.jsonPort}`); + log(`n${this.i} up pid ${this.proc.pid} json ${this.jsonPort} p2p ${this.p2pPort}${this.connect.length ? ` via proxy ${this.connect.join(',')}` : ''}`); + return this; + } + grepLog(re) { try { return readFileSync(this.logFile, 'utf8').split('\n').filter(l => re.test(l)); } catch { return []; } } +} +function miner(bin, argv, name, env = {}) { + const out = openSync(`${TMP}/${name}.log`, 'a'); + const p = spawn(bin, argv, { stdio: ['ignore', out, out], env: { ...process.env, ...env } }); + started.push(p); + return p; +} +const proxies = []; +async function stopAll() { + for (const p of started.reverse()) { try { p.kill('SIGINT'); } catch { } } + await sleep(1500); + for (const p of started) { try { p.kill('SIGKILL'); } catch { } } + for (const x of proxies) x.stop(); +} +process.on('SIGINT', async () => { await stopAll(); process.exit(130); }); +process.on('unhandledRejection', async (e) => { log(`FAILED: ${e?.stack || e}`); await stopAll(); process.exit(3); }); + +// bits -> expected hashes per block: target = mantissa x 2^(8 (exponent - 3)); hashes = 2^256 / (target + 1) +export function expectedHashes(bits) { + const exp = BigInt(bits >>> 24), mant = BigInt(bits & 0xffffff); + const target = exp <= 3n ? mant >> (8n * (3n - exp)) : mant << (8n * (exp - 3n)); + const ratio = (1n << 256n) / (target + 1n); + return Number(ratio); +} +const minerHash = (i) => { // the newest `hash= MH/s` status line of miner i, in hashes per second + let v = null; + try { for (const l of readFileSync(`${TMP}/cpu${i}.log`, 'utf8').split('\n')) { const m = /hash=([\d.]+) MH\/s/.exec(l); if (m) v = +m[1] * 1e6; } } catch { } + return v; +}; + +const n0 = await new Node(0).start(); +const p1 = await new DelayProxy(BASE + 101, n0.p2pPort).start(); proxies.push(p1); +const p2 = await new DelayProxy(BASE + 102, n0.p2pPort).start(); proxies.push(p2); +const n1 = await new Node(1, [`127.0.0.1:${p1.listenPort}`]).start(); +const n2 = await new Node(2, [`127.0.0.1:${p2.listenPort}`]).start(); +const nodes = [n0, n1, n2]; +await sleep(3000); +for (const n of nodes) { const d = await n.rpc.call('getBlockDagInfo'); log(`n${n.i} network ${d.network ?? d.networkName} blocks ${d.blockCount} difficulty ${d.difficulty} (bits ${d.difficulty * 2} hashes per block)`); } +const peers = await Promise.all(nodes.map(async n => { try { const r = await n.rpc.call('getConnectedPeerInfo'); const arr = Object.values(r).find(Array.isArray); return arr ? arr.length : '?'; } catch { return '?'; } })); +log(`peers per node ${peers.join(' ')} (hub 2, spokes 1)`); +nodes.forEach((n, i) => miner(CPU_MINER, ['mine', n.grpc, '1', String(SECS + 30), `cpu${i}`, '--engine', 'igneum-pow', '--payout-label', `cpu${i}`, '--status-secs', '30', '--no-vote'], `cpu${i}`, { IGNEUM_POW_DAY_MS: String(DAY_MS) })); +void devAddress('fast-time-dv3'); + +const t0 = Date.now(); +const since = () => ((Date.now() - t0) / 1000).toFixed(1); +const samples = []; +let lastReport = 0, measureStart = null; +while (Date.now() - t0 < SECS * 1000) { + await sleep(5000); + const dag = await Promise.all(nodes.map(async n => { try { return await n.rpc.call('getBlockDagInfo'); } catch (e) { return null; } })); + let bits = null; + try { const b = await n0.rpc.call('getBlock', { hash: dag[0].sink, includeTransactions: false }); bits = +b.block.header.bits; } catch { } + const hashes = [0, 1, 2].map(minerHash); + const hashSum = hashes.every(h => h != null) ? hashes.reduce((a, b) => a + b, 0) : null; + const expected = bits == null ? null : expectedHashes(bits); + const implied = hashSum == null ? null : hashSum * T_MS / 1000; + const s = { t: +since(), blocks: dag.map(d => d?.blockCount ?? null), daa: dag[0]?.virtualDaaScore ?? null, difficulty: dag[0]?.difficulty ?? null, bits, expected_hashes: expected, miner_hash: hashSum, hash_implied: implied, ratio: expected != null && implied ? expected / implied : null }; + samples.push(s); + if (measureStart == null && s.t >= MEASURE_FROM && s.blocks[0] != null) measureStart = s; + if (Date.now() - lastReport > 30000) { + lastReport = Date.now(); + const rate = samples.length > 6 ? ((s.blocks[0] - samples.at(-7).blocks[0]) / (s.t - samples.at(-7).t)).toFixed(2) : '?'; + log(`t=${since()} s blocks ${s.blocks.join('/')} daa ${s.daa} rate(30s) ${rate}/s bits 0x${(bits ?? 0).toString(16)} expected ${expected?.toExponential(3)} hashes/block, miners ${hashSum == null ? '?' : (hashSum / 1e3).toFixed(1) + ' kH/s'} implied ${implied?.toExponential(3)} ratio ${s.ratio?.toFixed(3)}`); + } +} +await sleep(2000); + +// ---- the reading ---- +const end = samples.at(-1); +const measured = samples.filter(s => s.t >= MEASURE_FROM && s.ratio != null); +const dagRate = measureStart && end.blocks[0] != null ? (end.blocks[0] - measureStart.blocks[0]) / (end.t - measureStart.t) : null; +const rateOff = dagRate == null ? null : dagRate / BPS - 1; +const ratioMean = measured.length ? measured.reduce((a, s) => a + s.ratio, 0) / measured.length : null; +const diffOff = ratioMean == null ? null : ratioMean - 1; +// the red share and the chain step spacing from the hub's blocks since the measured window began +let reds = 0, chainBlocks = 0, merged = 0, spacings = []; +try { + const info = await n0.rpc.call('getBlockDagInfo'); + let low = info.pruningPointHash; const seen = new Set(); let prevChainClock = null; + for (let round = 0; round < 400; round++) { + const r = await n0.rpc.call('getBlocks', { lowHash: low, includeBlocks: true, includeTransactions: false }); + const blocks = r.blocks || []; let added = 0; + for (const b of blocks) { + const h = b.verboseData?.hash || b.header?.hash; if (seen.has(h)) continue; seen.add(h); added++; + if (!b.verboseData?.isChainBlock) continue; + const tMs = +b.header.timestamp; + if (tMs < t0 + MEASURE_FROM * 1000) continue; + chainBlocks++; reds += (b.verboseData.mergeSetRedsHashes || []).length; merged += (b.verboseData.mergeSetBluesHashes || []).length + (b.verboseData.mergeSetRedsHashes || []).length; + if (prevChainClock != null) spacings.push(tMs - prevChainClock); prevChainClock = tMs; + } + if (!blocks.length || added === 0) break; + low = (r.blockHashes || []).at(-1) || blocks.at(-1).verboseData?.hash; if (!low) break; + } +} catch (e) { log(`getBlocks: ${e.message}`); } +spacings.sort((a, b) => a - b); +const median = spacings.length ? spacings[Math.floor(spacings.length / 2)] : null; +const checks = { + measured_window_present: measured.length >= 10, + dag_rate_within_10pct: rateOff != null && Math.abs(rateOff) <= 0.10, + difficulty_within_10pct_of_hash: diffOff != null && Math.abs(diffOff) <= 0.10, + three_nodes_within_5pct_blocks: end.blocks.every(b => b != null) && Math.max(...end.blocks) - Math.min(...end.blocks) <= 0.05 * Math.max(...end.blocks), +}; +const good = checks.measured_window_present && checks.dag_rate_within_10pct && checks.difficulty_within_10pct_of_hash; +const pass = EXPECT === 'pass' ? good : !good; +const summary = { + pass, expect: EXPECT, case: CASE, bps: BPS, target_time_ms: T_MS, propagation_ms: PROPAGATION_MS, delay_ms_per_leg: DELAY_MS, handshake_ms: HANDSHAKE_MS, rule: RULE, genesis_bits: GENESIS_BITS, secs: SECS, measure_from: MEASURE_FROM, + checks, dag_rate_measured: dagRate, dag_rate_off: rateOff, difficulty_over_hash_implied_mean: ratioMean, difficulty_off: diffOff, + measured_samples: measured.length, end_blocks: end.blocks, end_daa: end.daa, end_bits: end.bits, end_expected_hashes: end.expected_hashes, end_miner_hash: end.miner_hash, + chain_blocks_measured: chainBlocks, merged_blocks_measured: merged, red_blocks_measured: reds, red_share: merged ? reds / merged : null, + chain_step_spacing_ms: { median, p90: spacings.length ? spacings[Math.floor(spacings.length * 0.9)] : null, max: spacings.at(-1) ?? null }, + proxy: { connections: p1.conns + p2.conns, bytes: p1.bytes + p2.bytes }, + node: IGNEUMD, miner: CPU_MINER, samples, +}; +mkdirSync(OUT.replace(/\/[^/]+$/, ''), { recursive: true }); +writeFileSync(OUT, JSON.stringify(summary, null, 2)); +writeFileSync(`${TMP}/summary.json`, JSON.stringify(summary, null, 2)); +const fails = Object.entries(checks).filter(([, v]) => !v).map(([k]) => k); +log(`SUMMARY ${pass ? 'PASS' : 'FAIL'} (expect ${EXPECT}, ${CASE}): DAG rate ${dagRate?.toFixed(3)}/s against ${BPS} (${rateOff == null ? '?' : (100 * rateOff).toFixed(1) + '%'}), difficulty / hash-implied ${ratioMean?.toFixed(3)} (${diffOff == null ? '?' : (100 * diffOff).toFixed(1) + '%'}) over ${measured.length} samples; red share ${summary.red_share == null ? '?' : (100 * summary.red_share).toFixed(1) + '%'} of ${merged} merged over ${chainBlocks} chain blocks, chain step median ${median} ms p90 ${summary.chain_step_spacing_ms.p90} ms; blocks ${end.blocks.join('/')}; proxy ${summary.proxy.connections} conns ${summary.proxy.bytes} bytes${fails.length ? `; FAILED CHECK ${fails.join(', ')}` : ''}`); +log(`summary: ${OUT}`); +await stopAll(); +process.exit(pass ? 0 : 1);