Merge branch 'ca3-v4-node' into ca3-coord

This commit is contained in:
igneum-labs 2026-10-06 17:56:58 +00:00
commit 74054f8803
12 changed files with 2509 additions and 0 deletions

View file

@ -0,0 +1,533 @@
{
"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": false,
"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,
"blocks_on_both_sides": false,
"v4_ids_equal_the_cli_v4_id": false,
"v4_ids_differ_from_the_same_seed_v3_id": false
},
"window": 120,
"floor": "never",
"v3_activation": 60,
"epoch_blocks": 60,
"lead": 10,
"first_full_window_epoch": 3,
"floor_epoch": null,
"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": 391.2,
"final_daa": 420,
"max_epoch_seen": 7,
"epochs": {
"0": {
"class": 2,
"firstSeenDaa": 0,
"at": 4.7,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 0,
"signal_epoch": null
},
"1": {
"class": 3,
"firstSeenDaa": 60,
"at": 29.7,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 7288,
"signal_epoch": null
},
"2": {
"class": 3,
"firstSeenDaa": 120,
"at": 107.8,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 6890,
"signal_epoch": null
},
"3": {
"class": 3,
"firstSeenDaa": 180,
"at": 162.9,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 6833,
"signal_epoch": null
},
"4": {
"class": 3,
"firstSeenDaa": 240,
"at": 222,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 6666,
"signal_epoch": null
},
"5": {
"class": 3,
"firstSeenDaa": 300,
"at": 279,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 6250,
"signal_epoch": null
},
"6": {
"class": 3,
"firstSeenDaa": 360,
"at": 338.1,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 6666,
"signal_epoch": null
},
"7": {
"class": 3,
"firstSeenDaa": 420,
"at": 391.2,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 6666,
"signal_epoch": null
}
},
"blocks": {
"total": 423,
"before_boundary": 423,
"after_boundary": 0,
"version_bytes": {
"0": 1,
"3": 139,
"4": 283
},
"signal_share_bps_on_chain": 6690
},
"programs": [
{
"epoch": 0,
"class": "v2",
"program_id": "8f8806638d59850f",
"seed": "234e082d653dc69d",
"miners": 3,
"disagree": false
},
{
"epoch": 1,
"class": "v3",
"program_id": "4b212c0be9e6fe63",
"seed": "135da54d125df437",
"miners": 3,
"disagree": false
},
{
"epoch": 2,
"class": "v3",
"program_id": "ee3ac1e30bf65e70",
"seed": "c59a2e57f4b8d7a5",
"miners": 3,
"disagree": false
},
{
"epoch": 3,
"class": "v3",
"program_id": "9f029268a9045087",
"seed": "da24c710fa33c22f",
"miners": 3,
"disagree": false
},
{
"epoch": 4,
"class": "v3",
"program_id": "3b36a0088571d624",
"seed": "c99ee1f5201c6237",
"miners": 3,
"disagree": false
},
{
"epoch": 5,
"class": "v3",
"program_id": "0045b23258d669cc",
"seed": "6890cf1861d53035",
"miners": 3,
"disagree": false
},
{
"epoch": 6,
"class": "v3",
"program_id": "58f1b31878fa2f07",
"seed": "10d336969f4efd63",
"miners": 3,
"disagree": false
},
{
"epoch": 7,
"class": "v3",
"program_id": "2a0d9087b89d7a9c",
"seed": "7290dc7ee9b9c29a",
"miners": 3,
"disagree": false
}
],
"program_id_rows": [],
"accepted_per_miner": [
138,
145,
139
],
"rejected_by_miners": [
0,
0,
0
],
"rejected_by_nodes": [
0,
0,
0
],
"sinks": [
"ca58bef3d51cfa4a",
"ca58bef3d51cfa4a",
"ca58bef3d51cfa4a"
],
"block_counts": [
422,
422,
422
],
"signal_lines": [
null,
null,
null
],
"floor_lines": [
"Program class v4 from the override file: never",
"Program class v4 from the override file: never",
"Program class v4 from the override file: never"
],
"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": 7317,
"nodes": [
"42/b548603c",
"42/b548603c",
"42/b548603c"
]
},
{
"t": 34.7,
"daa": 65,
"epoch": 1,
"class": 3,
"bps": 7031,
"nodes": [
"65/1b29b2e8",
"65/1b29b2e8",
"65/1b29b2e8"
]
},
{
"t": 49.7,
"daa": 67,
"epoch": 1,
"class": 3,
"bps": 7121,
"nodes": [
"67/36bc6a1c",
"67/36bc6a1c",
"67/36bc6a1c"
]
},
{
"t": 64.7,
"daa": 76,
"epoch": 1,
"class": 3,
"bps": 7066,
"nodes": [
"76/e4a48be8",
"76/e4a48be8",
"76/e4a48be8"
]
},
{
"t": 79.8,
"daa": 91,
"epoch": 1,
"class": 3,
"bps": 7111,
"nodes": [
"91/d77402dd",
"91/d77402dd",
"91/d77402dd"
]
},
{
"t": 94.8,
"daa": 111,
"epoch": 1,
"class": 3,
"bps": 6636,
"nodes": [
"111/f0e19e53",
"111/f0e19e53",
"111/f0e19e53"
]
},
{
"t": 109.8,
"daa": 122,
"epoch": 2,
"class": 3,
"bps": 6890,
"nodes": [
"122/5adb1052",
"122/5adb1052",
"122/5adb1052"
]
},
{
"t": 124.8,
"daa": 135,
"epoch": 2,
"class": 3,
"bps": 6750,
"nodes": [
"135/a49b1340",
"135/a49b1340",
"135/a49b1340"
]
},
{
"t": 139.9,
"daa": 152,
"epoch": 2,
"class": 3,
"bps": 6500,
"nodes": [
"152/e1c73ab2",
"152/e1c73ab2",
"152/e1c73ab2"
]
},
{
"t": 154.9,
"daa": 169,
"epoch": 2,
"class": 3,
"bps": 6583,
"nodes": [
"169/e0fc0185",
"169/e0fc0185",
"169/e0fc0185"
]
},
{
"t": 169.9,
"daa": 186,
"epoch": 3,
"class": 3,
"bps": 6833,
"nodes": [
"186/2cba3315",
"186/2cba3315",
"186/2cba3315"
]
},
{
"t": 184.9,
"daa": 207,
"epoch": 3,
"class": 3,
"bps": 6583,
"nodes": [
"207/a1e09949",
"207/a1e09949",
"207/a1e09949"
]
},
{
"t": 199.9,
"daa": 223,
"epoch": 3,
"class": 3,
"bps": 6750,
"nodes": [
"223/2313964e",
"223/2313964e",
"223/2313964e"
]
},
{
"t": 215,
"daa": 230,
"epoch": 3,
"class": 3,
"bps": 6916,
"nodes": [
"230/c99ee1f5",
"230/c99ee1f5",
"230/c99ee1f5"
]
},
{
"t": 230,
"daa": 246,
"epoch": 4,
"class": 3,
"bps": 6750,
"nodes": [
"246/3500100b",
"246/3500100b",
"246/3500100b"
]
},
{
"t": 245,
"daa": 262,
"epoch": 4,
"class": 3,
"bps": 6500,
"nodes": [
"262/aecdd009",
"262/aecdd009",
"262/aecdd009"
]
},
{
"t": 260,
"daa": 274,
"epoch": 4,
"class": 3,
"bps": 6666,
"nodes": [
"274/c93ff1f0",
"274/c93ff1f0",
"274/c93ff1f0"
]
},
{
"t": 275,
"daa": 293,
"epoch": 4,
"class": 3,
"bps": 6583,
"nodes": [
"293/c1937fd0",
"293/c1937fd0",
"293/c1937fd0"
]
},
{
"t": 290.1,
"daa": 311,
"epoch": 5,
"class": 3,
"bps": 6500,
"nodes": [
"311/47520bf7",
"311/47520bf7",
"311/47520bf7"
]
},
{
"t": 305.1,
"daa": 328,
"epoch": 5,
"class": 3,
"bps": 6916,
"nodes": [
"328/ed9acfc1",
"328/ed9acfc1",
"328/ed9acfc1"
]
},
{
"t": 320.1,
"daa": 343,
"epoch": 5,
"class": 3,
"bps": 6750,
"nodes": [
"343/6553cec0",
"343/6553cec0",
"343/6553cec0"
]
},
{
"t": 335.1,
"daa": 357,
"epoch": 5,
"class": 3,
"bps": 6666,
"nodes": [
"357/f444a87b",
"357/f444a87b",
"357/f444a87b"
]
},
{
"t": 350.1,
"daa": 368,
"epoch": 6,
"class": 3,
"bps": 6583,
"nodes": [
"368/af2319fb",
"368/af2319fb",
"368/af2319fb"
]
},
{
"t": 365.2,
"daa": 381,
"epoch": 6,
"class": 3,
"bps": 6666,
"nodes": [
"381/69556871",
"381/69556871",
"381/69556871"
]
},
{
"t": 380.2,
"daa": 404,
"epoch": 6,
"class": 3,
"bps": 6750,
"nodes": [
"404/7c17470a",
"404/7c17470a",
"404/7c17470a"
]
}
]
}

View file

@ -0,0 +1,433 @@
{
"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,
"blocks_on_both_sides": true,
"v4_ids_equal_the_cli_v4_id": true,
"v4_ids_differ_from_the_same_seed_v3_id": true
},
"window": 120,
"floor": "never",
"v3_activation": 60,
"epoch_blocks": 60,
"lead": 10,
"first_full_window_epoch": 3,
"floor_epoch": null,
"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": 3,
"daa": 180,
"at": 156.9
},
"run_ended_at_s": 271,
"final_daa": 301,
"max_epoch_seen": 5,
"epochs": {
"0": {
"class": 2,
"firstSeenDaa": 0,
"at": 4.6,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 0,
"signal_epoch": null
},
"1": {
"class": 3,
"firstSeenDaa": 60,
"at": 24.7,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 10000,
"signal_epoch": null
},
"2": {
"class": 3,
"firstSeenDaa": 121,
"at": 98.8,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 10000,
"signal_epoch": null
},
"3": {
"class": 4,
"firstSeenDaa": 180,
"at": 156.9,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 10000,
"signal_epoch": 3
},
"4": {
"class": 4,
"firstSeenDaa": 240,
"at": 213.9,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 10000,
"signal_epoch": null
},
"5": {
"class": 4,
"firstSeenDaa": 301,
"at": 271,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 10000,
"signal_epoch": null
}
},
"blocks": {
"total": 305,
"before_boundary": 181,
"after_boundary": 124,
"version_bytes": {
"0": 1,
"4": 304
},
"signal_share_bps_on_chain": 9967
},
"programs": [
{
"epoch": 0,
"class": "v2",
"program_id": "8f8806638d59850f",
"seed": "234e082d653dc69d",
"miners": 3,
"disagree": false
},
{
"epoch": 1,
"class": "v3",
"program_id": "96c2b5e891a1cec6",
"seed": "dc3460d3c2c7e252",
"miners": 3,
"disagree": false
},
{
"epoch": 2,
"class": "v3",
"program_id": "9210599b352bb3d9",
"seed": "c80aefcfef313159",
"miners": 3,
"disagree": false
},
{
"epoch": 3,
"class": "v4",
"program_id": "e48e6be7c6699824",
"seed": "13629115abf4b06b",
"miners": 3,
"disagree": false
},
{
"epoch": 4,
"class": "v4",
"program_id": "996db27539593c5c",
"seed": "d8820f2c76bb1011",
"miners": 3,
"disagree": false
},
{
"epoch": 5,
"class": "v4",
"program_id": "ebf1b4ee74ef5b04",
"seed": "8b8a1e7ccb10053c",
"miners": 3,
"disagree": false
}
],
"program_id_rows": [
{
"epoch": 3,
"seed": "13629115abf4b06b",
"miners_id": "e48e6be7c6699824",
"miners": 3,
"cli_v3": "6847355c88e8215f",
"cli_v4": "e48e6be7c6699824"
},
{
"epoch": 4,
"seed": "d8820f2c76bb1011",
"miners_id": "996db27539593c5c",
"miners": 3,
"cli_v3": "34725526fbe79603",
"cli_v4": "996db27539593c5c"
},
{
"epoch": 5,
"seed": "8b8a1e7ccb10053c",
"miners_id": "ebf1b4ee74ef5b04",
"miners": 3,
"cli_v3": "ed109252a3333d97",
"cli_v4": "ebf1b4ee74ef5b04"
}
],
"accepted_per_miner": [
105,
102,
97
],
"rejected_by_miners": [
0,
0,
0
],
"rejected_by_nodes": [
0,
0,
0
],
"sinks": [
"322e4a57824ab7fd",
"322e4a57824ab7fd",
"322e4a57824ab7fd"
],
"block_counts": [
304,
304,
304
],
"signal_lines": [
"Program class v4 by miner signal: epoch 3 (share 10000 bps over 120 DAA ending at seed block 13629115abf4b06bd9b07dfee66c7aab817cef268a34c02f8da6884d52f26017, threshold 9500 bps, 120 of 120 blue blocks)",
"Program class v4 by miner signal: epoch 3 (share 10000 bps over 120 DAA ending at seed block 13629115abf4b06bd9b07dfee66c7aab817cef268a34c02f8da6884d52f26017, threshold 9500 bps, 120 of 120 blue blocks)",
"Program class v4 by miner signal: epoch 3 (share 10000 bps over 120 DAA ending at seed block 13629115abf4b06bd9b07dfee66c7aab817cef268a34c02f8da6884d52f26017, threshold 9500 bps, 120 of 120 blue blocks)"
],
"floor_lines": [
"Program class v4 from the override file: never",
"Program class v4 from the override file: never",
"Program class v4 from the override file: never"
],
"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": 10000,
"nodes": [
"45/90a214d6",
"45/90a214d6",
"45/90a214d6"
]
},
{
"t": 34.7,
"daa": 64,
"epoch": 1,
"class": 3,
"bps": 10000,
"nodes": [
"64/ccc78501",
"64/ccc78501",
"64/ccc78501"
]
},
{
"t": 49.7,
"daa": 67,
"epoch": 1,
"class": 3,
"bps": 10000,
"nodes": [
"67/30734219",
"67/30734219",
"67/30734219"
]
},
{
"t": 64.7,
"daa": 77,
"epoch": 1,
"class": 3,
"bps": 10000,
"nodes": [
"77/fb4c94d8",
"77/fb4c94d8",
"77/fb4c94d8"
]
},
{
"t": 79.7,
"daa": 92,
"epoch": 1,
"class": 3,
"bps": 10000,
"nodes": [
"92/fa61d646",
"92/fa61d646",
"92/fa61d646"
]
},
{
"t": 94.8,
"daa": 113,
"epoch": 1,
"class": 3,
"bps": 10000,
"nodes": [
"113/a5072844",
"113/a5072844",
"113/a5072844"
]
},
{
"t": 109.8,
"daa": 131,
"epoch": 2,
"class": 3,
"bps": 10000,
"nodes": [
"131/d88a9f22",
"131/d88a9f22",
"131/d88a9f22"
]
},
{
"t": 124.8,
"daa": 157,
"epoch": 2,
"class": 3,
"bps": 10000,
"nodes": [
"157/65814d6e",
"157/65814d6e",
"157/65814d6e"
]
},
{
"t": 139.8,
"daa": 165,
"epoch": 2,
"class": 3,
"bps": 10000,
"nodes": [
"165/ed5654bb",
"165/ed5654bb",
"165/ed5654bb"
]
},
{
"t": 154.9,
"daa": 177,
"epoch": 2,
"class": 3,
"bps": 10000,
"nodes": [
"177/d0f60a64",
"177/d0f60a64",
"177/d0f60a64"
]
},
{
"t": 169.9,
"daa": 192,
"epoch": 3,
"class": 4,
"bps": 10000,
"nodes": [
"192/dce1a9f6",
"192/dce1a9f6",
"192/dce1a9f6"
]
},
{
"t": 184.9,
"daa": 206,
"epoch": 3,
"class": 4,
"bps": 10000,
"nodes": [
"206/617ab9be",
"206/617ab9be",
"206/617ab9be"
]
},
{
"t": 199.9,
"daa": 222,
"epoch": 3,
"class": 4,
"bps": 10000,
"nodes": [
"222/e04f63eb",
"222/e04f63eb",
"222/e04f63eb"
]
},
{
"t": 214.9,
"daa": 242,
"epoch": 4,
"class": 4,
"bps": 10000,
"nodes": [
"242/a214e080",
"242/a214e080",
"242/a214e080"
]
},
{
"t": 230,
"daa": 253,
"epoch": 4,
"class": 4,
"bps": 10000,
"nodes": [
"253/c5f37595",
"253/c5f37595",
"253/c5f37595"
]
},
{
"t": 245,
"daa": 269,
"epoch": 4,
"class": 4,
"bps": 10000,
"nodes": [
"269/0ac500c5",
"269/0ac500c5",
"269/0ac500c5"
]
},
{
"t": 260,
"daa": 285,
"epoch": 4,
"class": 4,
"bps": 10000,
"nodes": [
"285/f5f748e0",
"285/f5f748e0",
"285/f5f748e0"
]
}
]
}

View file

@ -0,0 +1,583 @@
{
"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": 120,
"floor": 300,
"v3_activation": 60,
"epoch_blocks": 60,
"lead": 10,
"first_full_window_epoch": 3,
"floor_epoch": 5,
"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": 5,
"daa": 300,
"at": 286
},
"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": 32.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.9,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 0,
"signal_epoch": null
},
"4": {
"class": 3,
"firstSeenDaa": 242,
"at": 222,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 0,
"signal_epoch": null
},
"5": {
"class": 4,
"firstSeenDaa": 300,
"at": 286,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 0,
"signal_epoch": null
},
"6": {
"class": 4,
"firstSeenDaa": 360,
"at": 346.1,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 0,
"signal_epoch": null
},
"7": {
"class": 4,
"firstSeenDaa": 421,
"at": 410.2,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 0,
"signal_epoch": null
}
},
"blocks": {
"total": 425,
"before_boundary": 301,
"after_boundary": 124,
"version_bytes": {
"0": 1,
"3": 424
},
"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": "ab6cbb1e54ed5cce",
"seed": "64e53c286d8dd9fb",
"miners": 3,
"disagree": false
},
{
"epoch": 2,
"class": "v3",
"program_id": "cf71a8c93f39aba3",
"seed": "bf5fb4c810cbf7b9",
"miners": 3,
"disagree": false
},
{
"epoch": 3,
"class": "v3",
"program_id": "586eb24a6a1e956e",
"seed": "ca912ff9e0ce19eb",
"miners": 3,
"disagree": false
},
{
"epoch": 4,
"class": "v3",
"program_id": "179f30b619d3ae21",
"seed": "fd39032d68eaf42c",
"miners": 3,
"disagree": false
},
{
"epoch": 5,
"class": "v4",
"program_id": "99f8a29e69d425fd",
"seed": "55b4bb2409b9dac0",
"miners": 3,
"disagree": false
},
{
"epoch": 6,
"class": "v4",
"program_id": "13f24ad402624682",
"seed": "f6e6e2710786cb70",
"miners": 3,
"disagree": false
},
{
"epoch": 7,
"class": "v4",
"program_id": "ae4fe8f735e1e5da",
"seed": "f3106b9f844b01c3",
"miners": 3,
"disagree": false
}
],
"program_id_rows": [
{
"epoch": 5,
"seed": "55b4bb2409b9dac0",
"miners_id": "99f8a29e69d425fd",
"miners": 3,
"cli_v3": "48b8b87ff3a7e9ae",
"cli_v4": "99f8a29e69d425fd"
},
{
"epoch": 6,
"seed": "f6e6e2710786cb70",
"miners_id": "13f24ad402624682",
"miners": 3,
"cli_v3": "912bbdb803462a49",
"cli_v4": "13f24ad402624682"
},
{
"epoch": 7,
"seed": "f3106b9f844b01c3",
"miners_id": "ae4fe8f735e1e5da",
"miners": 3,
"cli_v3": "3df23fd83c687af9",
"cli_v4": "ae4fe8f735e1e5da"
}
],
"accepted_per_miner": [
130,
134,
160
],
"rejected_by_miners": [
0,
0,
0
],
"rejected_by_nodes": [
0,
0,
0
],
"sinks": [
"aa8755bf4c1cc9e2",
"aa8755bf4c1cc9e2",
"aa8755bf4c1cc9e2"
],
"block_counts": [
424,
424,
424
],
"signal_lines": [
null,
null,
null
],
"floor_lines": [
"Program class v4 from the override file: active from epoch 5 (DAA score 300 rounded up to the epoch boundary at 300, epochs of 60 DAA)",
"Program class v4 from the override file: active from epoch 5 (DAA score 300 rounded up to the epoch boundary at 300, epochs of 60 DAA)",
"Program class v4 from the override file: active from epoch 5 (DAA score 300 rounded up to the epoch boundary at 300, 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": 40,
"epoch": 0,
"class": 2,
"bps": 0,
"nodes": [
"40/bd7dc09a",
"40/bd7dc09a",
"40/bd7dc09a"
]
},
{
"t": 34.7,
"daa": 60,
"epoch": 1,
"class": 3,
"bps": 0,
"nodes": [
"60/0d0245d7",
"60/0d0245d7",
"60/0d0245d7"
]
},
{
"t": 49.7,
"daa": 66,
"epoch": 1,
"class": 3,
"bps": 0,
"nodes": [
"66/b31ac361",
"66/b31ac361",
"66/b31ac361"
]
},
{
"t": 64.7,
"daa": 75,
"epoch": 1,
"class": 3,
"bps": 0,
"nodes": [
"75/63b041c6",
"75/63b041c6",
"75/63b041c6"
]
},
{
"t": 79.7,
"daa": 94,
"epoch": 1,
"class": 3,
"bps": 0,
"nodes": [
"94/3ba72d05",
"94/3ba72d05",
"94/3ba72d05"
]
},
{
"t": 94.8,
"daa": 109,
"epoch": 1,
"class": 3,
"bps": 0,
"nodes": [
"109/08984ac2",
"109/08984ac2",
"109/08984ac2"
]
},
{
"t": 109.8,
"daa": 123,
"epoch": 2,
"class": 3,
"bps": 0,
"nodes": [
"123/735ce53a",
"123/735ce53a",
"123/735ce53a"
]
},
{
"t": 124.8,
"daa": 134,
"epoch": 2,
"class": 3,
"bps": 0,
"nodes": [
"134/b86e0b6c",
"134/b86e0b6c",
"134/b86e0b6c"
]
},
{
"t": 139.8,
"daa": 154,
"epoch": 2,
"class": 3,
"bps": 0,
"nodes": [
"154/e0278b3f",
"154/e0278b3f",
"154/e0278b3f"
]
},
{
"t": 154.9,
"daa": 167,
"epoch": 2,
"class": 3,
"bps": 0,
"nodes": [
"167/55672ada",
"167/55672ada",
"167/55672ada"
]
},
{
"t": 169.9,
"daa": 180,
"epoch": 3,
"class": 3,
"bps": 0,
"nodes": [
"180/2549aa1c",
"180/2549aa1c",
"180/2549aa1c"
]
},
{
"t": 184.9,
"daa": 199,
"epoch": 3,
"class": 3,
"bps": 0,
"nodes": [
"199/8deada07",
"199/8deada07",
"199/8deada07"
]
},
{
"t": 199.9,
"daa": 210,
"epoch": 3,
"class": 3,
"bps": 0,
"nodes": [
"210/b3c3984d",
"210/b3c3984d",
"210/b3c3984d"
]
},
{
"t": 214.9,
"daa": 228,
"epoch": 3,
"class": 3,
"bps": 0,
"nodes": [
"228/e863e711",
"228/e863e711",
"228/e863e711"
]
},
{
"t": 230,
"daa": 249,
"epoch": 4,
"class": 3,
"bps": 0,
"nodes": [
"249/30016453",
"249/30016453",
"249/30016453"
]
},
{
"t": 245,
"daa": 264,
"epoch": 4,
"class": 3,
"bps": 0,
"nodes": [
"264/7da6fa57",
"264/7da6fa57",
"264/7da6fa57"
]
},
{
"t": 260,
"daa": 285,
"epoch": 4,
"class": 3,
"bps": 0,
"nodes": [
"285/132339cf",
"285/132339cf",
"285/132339cf"
]
},
{
"t": 275,
"daa": 295,
"epoch": 4,
"class": 3,
"bps": 0,
"nodes": [
"295/f284c363",
"295/f284c363",
"295/f284c363"
]
},
{
"t": 290,
"daa": 304,
"epoch": 5,
"class": 4,
"bps": 0,
"nodes": [
"304/f7813a3d",
"304/f7813a3d",
"304/f7813a3d"
]
},
{
"t": 305.1,
"daa": 324,
"epoch": 5,
"class": 4,
"bps": 0,
"nodes": [
"324/2e0050f2",
"324/2e0050f2",
"324/2e0050f2"
]
},
{
"t": 320.1,
"daa": 337,
"epoch": 5,
"class": 4,
"bps": 0,
"nodes": [
"337/de4a3d18",
"337/de4a3d18",
"337/de4a3d18"
]
},
{
"t": 335.1,
"daa": 350,
"epoch": 5,
"class": 4,
"bps": 0,
"nodes": [
"350/f6e6e271",
"350/f6e6e271",
"350/f6e6e271"
]
},
{
"t": 350.1,
"daa": 362,
"epoch": 6,
"class": 4,
"bps": 0,
"nodes": [
"362/78e4df38",
"362/78e4df38",
"362/78e4df38"
]
},
{
"t": 365.1,
"daa": 380,
"epoch": 6,
"class": 4,
"bps": 0,
"nodes": [
"380/ad2ac8c4",
"380/ad2ac8c4",
"380/ad2ac8c4"
]
},
{
"t": 380.2,
"daa": 392,
"epoch": 6,
"class": 4,
"bps": 0,
"nodes": [
"392/fd59c498",
"392/fd59c498",
"392/fd59c498"
]
},
{
"t": 395.2,
"daa": 404,
"epoch": 6,
"class": 4,
"bps": 0,
"nodes": [
"404/179e8135",
"404/179e8135",
"404/179e8135"
]
},
{
"t": 410.2,
"daa": 421,
"epoch": 7,
"class": 4,
"bps": 0,
"nodes": [
"421/cf7d8a0f",
"421/cf7d8a0f",
"421/cf7d8a0f"
]
}
]
}

View file

@ -0,0 +1,554 @@
{
"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": 120,
"floor": "never",
"v3_activation": 60,
"epoch_blocks": 60,
"lead": 10,
"first_full_window_epoch": 3,
"floor_epoch": null,
"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": 417.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": 23.7,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 7068,
"signal_epoch": null
},
"2": {
"class": 3,
"firstSeenDaa": 121,
"at": 98.8,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 6890,
"signal_epoch": null
},
"3": {
"class": 3,
"firstSeenDaa": 180,
"at": 160.9,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 6333,
"signal_epoch": null
},
"4": {
"class": 3,
"firstSeenDaa": 241,
"at": 226,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 6750,
"signal_epoch": null
},
"5": {
"class": 3,
"firstSeenDaa": 300,
"at": 284,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 7583,
"signal_epoch": null
},
"6": {
"class": 3,
"firstSeenDaa": 361,
"at": 343.1,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 6833,
"signal_epoch": null
},
"7": {
"class": 3,
"firstSeenDaa": 421,
"at": 417.2,
"eraSeed": "234e082d653dc69db36d875b256d0f5f4a123b353d6aa069c3c7aa9aa6c46062",
"bps": 6166,
"signal_epoch": null
}
},
"blocks": {
"total": 425,
"before_boundary": 425,
"after_boundary": 0,
"version_bytes": {
"0": 1,
"3": 138,
"4": 286
},
"signal_share_bps_on_chain": 6729
},
"programs": [
{
"epoch": 0,
"class": "v2",
"program_id": "8f8806638d59850f",
"seed": "234e082d653dc69d",
"miners": 3,
"disagree": false
},
{
"epoch": 1,
"class": "v3",
"program_id": "be8294c0126f1faf",
"seed": "b605d5200d048815",
"miners": 3,
"disagree": false
},
{
"epoch": 2,
"class": "v3",
"program_id": "ed3e4ffbf62a7424",
"seed": "c34f4c0e895a7365",
"miners": 3,
"disagree": false
},
{
"epoch": 3,
"class": "v3",
"program_id": "32b5ea953b6266b9",
"seed": "1df8b7b8f7f317c7",
"miners": 3,
"disagree": false
},
{
"epoch": 4,
"class": "v3",
"program_id": "4a936017bde53483",
"seed": "b99035c2fd14dd49",
"miners": 3,
"disagree": false
},
{
"epoch": 5,
"class": "v3",
"program_id": "9eb843a8976bad11",
"seed": "38b40c1bf554fe9c",
"miners": 3,
"disagree": false
},
{
"epoch": 6,
"class": "v3",
"program_id": "dd0155047fe53f92",
"seed": "e09a1634afda0783",
"miners": 3,
"disagree": false
},
{
"epoch": 7,
"class": "v3",
"program_id": "e21684fe7648d6b4",
"seed": "414f83716dfd9e60",
"miners": 3,
"disagree": false
}
],
"program_id_rows": [],
"accepted_per_miner": [
141,
145,
138
],
"rejected_by_miners": [
0,
0,
0
],
"rejected_by_nodes": [
0,
0,
0
],
"sinks": [
"d845fdd016a25425",
"d845fdd016a25425",
"d845fdd016a25425"
],
"block_counts": [
424,
424,
424
],
"signal_lines": [
null,
null,
null
],
"floor_lines": [
"Program class v4 from the override file: never",
"Program class v4 from the override file: never",
"Program class v4 from the override file: never"
],
"samples": [
{
"t": 4.6,
"daa": 0,
"epoch": 0,
"class": 2,
"bps": 0,
"nodes": [
"0/234e082d",
"0/234e082d",
"0/234e082d"
]
},
{
"t": 19.7,
"daa": 49,
"epoch": 0,
"class": 2,
"bps": 7659,
"nodes": [
"49/21e82fed",
"49/21e82fed",
"49/21e82fed"
]
},
{
"t": 34.7,
"daa": 64,
"epoch": 1,
"class": 3,
"bps": 7258,
"nodes": [
"64/5e7ac558",
"64/5e7ac558",
"64/5e7ac558"
]
},
{
"t": 49.7,
"daa": 75,
"epoch": 1,
"class": 3,
"bps": 7397,
"nodes": [
"75/eec9a8c7",
"75/eec9a8c7",
"75/eec9a8c7"
]
},
{
"t": 64.7,
"daa": 87,
"epoch": 1,
"class": 3,
"bps": 7176,
"nodes": [
"87/df1f9872",
"87/df1f9872",
"87/df1f9872"
]
},
{
"t": 79.8,
"daa": 106,
"epoch": 1,
"class": 3,
"bps": 6923,
"nodes": [
"106/738281d2",
"106/738281d2",
"106/738281d2"
]
},
{
"t": 94.8,
"daa": 118,
"epoch": 1,
"class": 3,
"bps": 6896,
"nodes": [
"118/98818bdc",
"118/98818bdc",
"118/98818bdc"
]
},
{
"t": 109.8,
"daa": 128,
"epoch": 2,
"class": 3,
"bps": 6750,
"nodes": [
"128/c1b1a275",
"128/c1b1a275",
"128/c1b1a275"
]
},
{
"t": 124.8,
"daa": 150,
"epoch": 2,
"class": 3,
"bps": 6250,
"nodes": [
"150/11045465",
"150/11045465",
"150/11045465"
]
},
{
"t": 139.8,
"daa": 161,
"epoch": 2,
"class": 3,
"bps": 6166,
"nodes": [
"161/161c74c3",
"161/161c74c3",
"161/161c74c3"
]
},
{
"t": 154.9,
"daa": 171,
"epoch": 2,
"class": 3,
"bps": 6333,
"nodes": [
"171/2419390d",
"171/2419390d",
"171/2419390d"
]
},
{
"t": 169.9,
"daa": 191,
"epoch": 3,
"class": 3,
"bps": 6083,
"nodes": [
"191/ae9f7aa0",
"191/ae9f7aa0",
"191/ae9f7aa0"
]
},
{
"t": 184.9,
"daa": 206,
"epoch": 3,
"class": 3,
"bps": 6583,
"nodes": [
"206/557726ec",
"206/557726ec",
"206/557726ec"
]
},
{
"t": 199.9,
"daa": 221,
"epoch": 3,
"class": 3,
"bps": 6833,
"nodes": [
"221/2458f010",
"221/2458f010",
"221/2458f010"
]
},
{
"t": 214.9,
"daa": 230,
"epoch": 3,
"class": 3,
"bps": 6554,
"nodes": [
"230/b99035c2",
"230/b99035c2",
"230/b99035c2"
]
},
{
"t": 230,
"daa": 246,
"epoch": 4,
"class": 3,
"bps": 6916,
"nodes": [
"246/8f951189",
"246/8f951189",
"246/8f951189"
]
},
{
"t": 245,
"daa": 265,
"epoch": 4,
"class": 3,
"bps": 7416,
"nodes": [
"265/6baad73f",
"265/6baad73f",
"265/6baad73f"
]
},
{
"t": 260,
"daa": 278,
"epoch": 4,
"class": 3,
"bps": 7416,
"nodes": [
"278/cb17784e",
"278/cb17784e",
"278/cb17784e"
]
},
{
"t": 275,
"daa": 289,
"epoch": 4,
"class": 3,
"bps": 7416,
"nodes": [
"289/f8628f1e",
"289/f8628f1e",
"289/f8628f1e"
]
},
{
"t": 290.1,
"daa": 308,
"epoch": 5,
"class": 3,
"bps": 7666,
"nodes": [
"308/77b769ef",
"308/77b769ef",
"308/77b769ef"
]
},
{
"t": 305.1,
"daa": 319,
"epoch": 5,
"class": 3,
"bps": 7250,
"nodes": [
"319/ac915fd6",
"319/ac915fd6",
"319/ac915fd6"
]
},
{
"t": 320.1,
"daa": 335,
"epoch": 5,
"class": 3,
"bps": 6583,
"nodes": [
"335/eef7dabd",
"335/eef7dabd",
"335/eef7dabd"
]
},
{
"t": 335.1,
"daa": 353,
"epoch": 5,
"class": 3,
"bps": 6916,
"nodes": [
"353/4bf46535",
"353/4bf46535",
"353/4bf46535"
]
},
{
"t": 350.1,
"daa": 370,
"epoch": 6,
"class": 3,
"bps": 6750,
"nodes": [
"370/1af27f0a",
"370/1af27f0a",
"370/1af27f0a"
]
},
{
"t": 365.2,
"daa": 385,
"epoch": 6,
"class": 3,
"bps": 6583,
"nodes": [
"385/b23c95d5",
"385/b23c95d5",
"385/b23c95d5"
]
},
{
"t": 380.2,
"daa": 392,
"epoch": 6,
"class": 3,
"bps": 6416,
"nodes": [
"392/678e7203",
"392/678e7203",
"392/678e7203"
]
},
{
"t": 395.2,
"daa": 400,
"epoch": 6,
"class": 3,
"bps": 6583,
"nodes": [
"400/c5ca53eb",
"400/c5ca53eb",
"400/c5ca53eb"
]
},
{
"t": 410.2,
"daa": 413,
"epoch": 6,
"class": 3,
"bps": 6000,
"nodes": [
"413/7b4f6674",
"413/7b4f6674",
"413/7b4f6674"
]
}
]
}

View file

@ -9,4 +9,12 @@
| G5 | the PC-built Windows workers and the Mac workers from the same commit | every sha256 listed; the two Windows workers carry the resource block; no packaging, no DMG, no manifest | GREEN on the program-id fix 7c22d0d (packfile.h and main.swift moved, so the workers were rebuilt from it; the earlier pair from a522d04 is superseded): igneum-worker-cuda.exe 3bc8ad8f69ad97d6a9491a734ef16e3cf10b3884d442fb1e5a490c751ae578c5 (1,536,512 bytes), igneum-worker-opencl.exe 16ef015470d7a45c5af5488dcc68a29c5e95f94487eb05e26f1bf418e6dd511f (478,208), `verify-exe.py`: both carry the coin icon and the version block; the Mac worker igneum-bench f9ca4b075a9a9c25afd8feeccd1339fbb6bb4c3728ffeddab9f549cd3c1725a4 (665,128, the binary of the G4 re-run); the node and app binaries of job build-20261006-155958 stand (the fork did not change). The a522d04 row for the record: the build job builds the node and the app only (`push-build-inputs.sh` writes two build units, `jobbuild.rs` has no worker unit), so the Windows workers were cross-built on the Mac from the committed tree a522d04 with `proto-cuda/nvrtc/build-windows.sh` under the build lock (mingw-w64, static, windres resource block), as 0.3.11's G5 row was: igneum-worker-cuda.exe e563126ed1d8ab8f6803ead89acca5b6fcb03203e6b87b70e0aec85e8e1046e4 (1,536,512 bytes), igneum-worker-opencl.exe 7fce1249d443aaf73a29b7474c1ef4084f3529b5bec3a2dc6bb97e624561a5e8 (478,208), `verify-exe.py`: both carry the coin icon and the version block (a first build of the same sources before the commit gave 26400edf... and 438e608a...: mingw stamps a link time, so the pair is not bit-reproducible; the sizes match the 0.3.11 pair, 1,536,512 and 478,208, and the sha256s differ from 0.3.11's 2b3b8c92... and edc4a75d...: the generator-4 rule is in them). The Mac worker from the same tree: igneum-bench 30c70754097dafbc3144a0c0f192526a0638089e5dde49ce46abb7a948dceade (665,128 bytes, built 16:47Z from `main.swift` as committed in a522d04, the binary G4b mined with). The same build job's (build-20261006-155958) node and app outputs from fork 5f7e0543, every sha256 verified against the PC's lines on fetch: igneumd.exe 140be25e486fe1634500b6e66eedfaca9fa6561d652f3e219b5a7a2bc377e766 (51,752,448), igneum-miner.exe ceb1e691ad57f58d09d5a0ff4cb5658aea0604d23ceb90b6a847a2c36a0bf81c (11,118,080), igneum-app.exe bfa3b871f17b69fd0c0c01a6f9fcac8964efb54dc10fd25db13422ba8f96d51f (3,065,344, PE and resource block ok); Linux igneumd d44b735869b129401d68f18786b4a5f16c22d00b682bd41a8e5022a01f7d7527 (49,603,112), igneum-miner a7985111d4972f4b67a9409b831a8c7af746508ebb3b19fc927aaddcfa2e2413, igneum-app 222f30c00bb9725f3127452f3d52403b380e25abaf9fba5c925839574cf1dabc; kept under the job's --out dir with --no-place. No packaging, no DMG, no manifest |
| G6 | the node change on a fork branch from the current release tip with suites green on PC 2, with the igneum-pow feature | the build job id and its SUMMARY line; the v4 engine test named in the output | GREEN. PC 2 job `build-20261006-155958` (fork ca3-v4-node 5f7e0543, main a522d04; published 15:59:58Z after the clear file and under the mkdir lock, taken 15:58:50Z, released 16:08:06Z after the closing report; the installed app untouched, the prover left on), `node tools/build-job.mjs run --target 1ccfe586 --node vendor/igneum-node-ca3v4 --node-tests "kaspa-consensus kaspa-consensus-core igneum-exec kaspa-pow igneum-miner kaspa-p2p-flows" --app-tests igneum-app --no-place`: `done exit 0 after 392 s: node ca3-v4-node 5f7e0543 app 0.3.11: every stage ok; 9 files uploaded (46 MB); 6 min of 40`; the test stage 50 s, exit 0 on both units: kaspa-consensus 98 passed (3 ignored; `ban_is_decided_by_the_carrying_block_so_nodes_agree_on_every_voter_list ... ok`, so 2.0's flake did not recur and no re-run was needed), kaspa-consensus-core 109 + 7 (`override_params_carry_the_program_class_v4_activation ... ok`), igneum-exec (its per-crate line is not in the relay's upload of the report, which keeps the stage's tail; the unit exited 0, so it passed), kaspa-pow 15 (`program_class_v3_seeds_hash_their_own_program_over_their_own_cache ... ok` and `program_class_v4_seeds_hash_the_shadow_program_over_the_v3_day_cache ... ok`, both `#[cfg(feature = "igneum-pow")]`), igneum-miner (the same: not in the upload, in the exit-0 unit), kaspa-p2p-flows 33, igneum-app 112 + 26 + 8. The feature: `build-job.mjs` and `push-build-inputs.sh` carry no `--features` for the test units (the manifest's `tests` entries are `dir` and `packages` only; the build units have `kaspad/igneum-pow`), and the PC's `jobbuild.rs` runs `cargo test --release -p ... ` in ONE invocation per unit, where `igneum-miner` depends on `kaspa-pow` with `features = ["igneum-pow"]`, so cargo's feature unification turns the feature on for kaspa-pow's test target in that invocation: the two engine tests above ran on PC 2 with the feature, named in the report, which is the evidence main asked for. The same two suites also pass on the Mac with the flag explicit (`cargo test --release -p kaspa-pow --features igneum-pow`: 15; `-p kaspa-consensus-core`: 109 + 7). What the tool lacks: a `features` key on a test unit; not added here (a change to the PC's installed app would be needed to honour it) |
## The cut preconditions (the coordinator's second brief, 6 October 2026 evening)
| # | Gate | Evidence required | State |
|---|---|---|---|
| P1 | the rehearsal plan and the v4 override object for the rented fleet | `docs/plans/counter-asic-3-rehearsal.md`; the objects with their digests | WRITTEN (not run: the fleet agent runs it): the publish object (the live 13 fields plus the floor `N6` and the window 86,400; digest `ac8e60ce205852bdda6b554f8cbfbd9dbb040187f487cbd8affe8103633dfd56` at the worked example N6 = 219,600) and the rehearsal object (every earlier switch at 0, window 3,600, floor 14,400; digest `bc2142b178ff367ae84ff0699ff523760d8878f883375da21864ed8d3ad39237`), both read from the signalling node's start lines on throwaway private networks; `override-v4-publish-example.json` and `override-v4-rehearsal.json` in this directory |
| P2 | miner-signalled class activation, implemented behind the override, the fast-time gate with three cases and the failed case | `docs/plans/counter-asic-3-node.md` section 6; `infra/fast-time/class-v4-signal.mjs` | GREEN on the Mac: two of three signalling never flips (7 epochs, 6,166 to 7,583 bps), all three flips at epoch 3 (the first full window, 10,000 bps, the same line on 3 of 3 nodes, the id assertion), nobody signalling flips at the floor epoch 5 and not before; the known-failed case fails on eight checks. Rows and summary files: node doc section 6.4; `class-v4-signal-{no-flip,flip,floor,failed-case}.json` |
| G6 (signalling) | the fork change on PC 2 with the igneum-pow feature | the job ids and their lines | GREEN on fork 0562a7f2 (main f39a8eb), three PC 2 jobs under one clearance ("PC 2 open for G6", 17:27Z), the mkdir lock taken 17:28:15Z and released 17:38:58Z, then 17:39:41Z to 17:55:31Z; prover on, app untouched. Job 1 `build-20261006-173017` (the six crates and the app): every build stage ok, the app tests 112 + 26 + 8, but kaspa-consensus 97 passed and 1 FAILED on 2.0's known flake `ban_is_decided_by_the_carrying_block_so_nodes_agree_on_every_voter_list` (`UnexpectedDifficulty(.., 487111630, 487128802)` in `mine_on_all`, the 0.3.10 cut's section 11 case, which also passed on the Mac and in this morning's job 155958), and cargo stopped the unit there. Treated as 2.0 did: job 2 `build-20261006-174027` (kaspa-consensus alone, 17:40 to 17:46Z): `RESULT test node [kaspa-consensus] exit 0 19 s`, `done exit 0 after 345 s ... every stage ok`. Job 3 `build-20261006-174823` (the five crates and the app, 17:48 to 17:55Z): `RESULT test node [kaspa-consensus-core igneum-exec kaspa-pow igneum-miner kaspa-p2p-flows] exit 0 35 s` and `RESULT test app/igneum-app [igneum-app] exit 0 5 s`: kaspa-consensus-core 110 + 7 (`override_params_carry_the_program_class_v4_activation ... ok`, the signal window test inside it), igneum-exec 18, kaspa-pow 15 (`program_class_signal_rule ... ok` is consensus-core's; kaspa-pow's `program_class_v4_seeds_hash_the_shadow_program_over_the_v3_day_cache ... ok`, the feature on), igneum-miner 18, kaspa-p2p-flows 33, igneum-app 112 + 26 + 8; the job's own closing line reads `failed ... upload incomplete` because the relay's blob service refused one of the nine uploads (`igneum-app.exe.zst FAILED: blob PUT: no url in the reply: service_unavailable`, the shipper's 17:25Z fault) AFTER both test stages had closed with exit 0, so the STAGE and RESULT lines are the evidence, as the shipper's warning said. Where the runs went: these three on PC 2 under the clearance given before the build-server rule arrived (18:0xZ); no further Linux build or suite is needed by this brief; the next one from this lane goes to igneum-build-1 through tools/build-remote.sh |
Summary files in this directory: `class-v4-20261006-1553Z-cpu.json` (G4 run 1), `class-v4-20261006-never-failed-case.json` (G4 run 2, the failed case), `class-v4-20261006-metal.json` (G4b), `class-v4-20261006-id-rerun.json` (G4 on the program-id fix, with the id assertion), `class-v4-20261006-id-failed-case.json` (the id assertion's failed case).

View file

@ -0,0 +1 @@
{"difficulty_v2_activation_daa":33000,"proving_v0_activation_daa":84100,"fees_v1_activation_daa":210000,"finality_v3_activation_daa":135200,"program_class_v3_activation_daa":154800,"proving_v1_activation_daa":154800,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":198000,"exec_restart_number":27276,"exec_restart_hash":"bb45cf0dd2d7cc97ebfa5a2701527c09a8ede5d32de74efead9caa293b15688a","exec_restart_trust_daa":200000,"program_class_v4_activation_daa":219600,"program_class_v4_signal_window_daa":86400}

View file

@ -0,0 +1 @@
{"difficulty_v2_activation_daa":0,"proving_v0_activation_daa":0,"fees_v1_activation_daa":0,"finality_v3_activation_daa":0,"program_class_v3_activation_daa":0,"proving_v1_activation_daa":0,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":0,"exec_restart_number":18446744073709551615,"exec_restart_hash":"","exec_restart_trust_daa":18446744073709551615,"program_class_v4_activation_daa":14400,"program_class_v4_signal_window_daa":3600}

View file

@ -88,3 +88,45 @@ The hash lane found the seven gate packs under `proto-cuda/packs-ca3-v4` carryin
- Wire: `RpcPowEpochInfo` gained one Borsh field (wRPC, versioned by `GetBlockTemplateResponse`) and one optional proto field; a 0.3.13 miner against this node reads the v4 height as never (the field absent) and would key epochs on the v3 switch alone, so every miner moves in the same binary sweep as the node (the rollout order of 2.0).
- The build job has no `features` key for test units (G6 row): the feature was on through unification; adding the key needs the PC's installed app to honour it, a 0.3.14 app item.
- The 48 GiB APFS clone of the 0.3.13 target dir (`vendor/igneum-node-ca3v4/target-ca3v4`, untracked) can be deleted after the cut.
## 6. PROPOSED: miner-signalled class activation (precondition 2 of the cut)
Status: PROPOSED spec text for spec 01 (a new section 1.12.2 beside the epoch rule) and spec 02; implemented behind the override on the fork branch `ca3-v4-node` (`consensus/src/processes/class_signal.rs`, the pure rule in `consensus/core/src/igneum.rs`), gated by the fast-time runs of section 6.4. Not in `docs/spec` until the project lead adopts it. The 2.0 fixed height stays, as the floor.
### 6.1 The rule
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).
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.
8. **The miner.** Nothing: the node builds the template header (the miner varies the nonce), so the signal is the node's binary; the miner takes the class of the epoch and the next from the template as before (`next_program_class` is the next epoch's decision once its seed block is known, else this epoch's class; a boundary that decides otherwise costs one refused pair and one prepare, the 2.0 era-boundary shape). `IGNEUM_CLASS_SIGNAL=<n>` on devnet and simnet only lowers a node's byte (the fast-time gate's non-signalling node); it is not read on mainnet or the testnet.
### 6.2 The devnet object changes shape
Two fields join the live object: `program_class_v4_activation_daa` is now the FLOOR (its name and its rounding unchanged: the 2.0 fixed-height form), and `program_class_v4_signal_window_daa` is the window (86,400 on the devnet; 0 turns signalling off). Both enter the digest (unconditionally, right after the v3 field), so the digest moves on the binary rollout once more; the pinned devnet digest of the fork's test is re-pinned to the value of this binary (section 6.4). The publish object and the rehearsal object are in `docs/plans/counter-asic-3-rehearsal.md` section 2. Defaults: devnet and mainnet 86,400, testnet and simnet 0 (the testnet is v4 from genesis by its floor of 0; the simnet keeps 2.0's rule).
### 6.3 What it does not cover (owed)
| Item | Why | What is done about it |
|---|---|---|
| A class-signal witness in the pruning-proof format | a node that synced from a proof holds no headers below its pruning point, so an epoch whose window reaches below it cannot be tallied; the node then takes the floor rule for that epoch and logs it (`class_signal.rs`), which can disagree with a full-history node for the epochs between a signal flip and the floor | the same class as the era witness of 2.0 section 7 (`MissingEraSeed`); the floor bounds the exposure to at most `N6 - flip`; the witness (the per-epoch decision beside the epoch seed in the proof) is the next node item |
| The first tally after a restart | one walk of `W` chain blocks' mergesets per epoch per process, memoised; a day of blocks is about 86,400 header reads, under a second on the Mac's store | measured in the fast-time runs below at `W` = 120 only; the devnet figure is owed from the rehearsal |
| A byte above 4 | accepted and counted as a v4 signal (a later object contains v4); a v5 rule would count bytes at or above 5 | nothing now |
### 6.4 The fast-time gate (three cases and the failed case)
`infra/fast-time/class-v4-signal.mjs`: three nodes on `override-60x.json` (v3 from DAA 60, window 120, the floor at `--floor`), each node's byte set by `IGNEUM_CLASS_SIGNAL` (`--signal a,b,c`), one real CPU miner each, the id assertion of G4 on every v4 epoch.
| Case | Run | SUMMARY | Summary file |
|---|---|---|---|
| Two of three signal (`--signal 4,4,3 --expect no-flip --epochs 7`) | 17:26:4x to 17:34:57Z (load average about 9) | PASS: no v4 epoch over epochs 0 to 7; the share at the sink read 6,166 to 7,583 bps epoch by epoch (two CPU miners of three, the third's blocks byte 3); on the chain 286 blocks byte 4, 138 byte 3, genesis byte 0 (6,729 bps); no node printed the signal line; 0 rejected; one sink `d845fdd016a25425` at 424/424/424; the floor line on 3 of 3 (`never`) | `class-v4-signal-no-flip.json` |
| All three signal (`--signal 4,4,4 --expect flip`) | 17:34:5x to 17:39:33Z | PASS: the share 10,000 bps from epoch 1; the class flipped at epoch 3 (DAA 180), the first epoch whose seed block has a full 120-DAA window below it, before the floor (`never`); 3 of 3 nodes printed `Program class v4 by miner signal: epoch 3 (share 10000 bps over 120 DAA ending at seed block 13629115abf4b06bd9b07dfee66c7aab817cef268a34c02f8da6884d52f26017, threshold 9500 bps, 120 of 120 blue blocks)` with the same epoch and share; 181 / 124 blocks across DAA 180; 0 rejected; one sink `322e4a57824ab7fd` at 304/304/304; the id assertion on epochs 3, 4, 5: the three miners' v4 id equals the CLI's class v4 id and differs from the same-seed v3 id (e3 `e48e6be7c6699824` against `6847355c88e8215f`) | `class-v4-signal-flip.json` |
| Nobody signals, the floor at 300 (`--signal 3,3,3 --floor 300 --expect floor`) | 17:39:3x to 17:46:28Z | PASS: the share 0 bps throughout (424 blocks byte 3); no signal line on any node; the class flipped at epoch 5 (DAA 300), the floor, and not before; the floor line `active from epoch 5` on 3 of 3; 301 / 124 blocks; 0 rejected; one sink `aa8755bf4c1cc9e2` at 424/424/424; the id assertion on epochs 5 to 7 (e5 `99f8a29e69d425fd` against `48b8b87ff3a7e9ae`) | `class-v4-signal-floor.json` |
| The known-failed case (`--signal 4,4,3 --expect flip --epochs 7`) | 17:20:34 to 17:27:04Z | FAIL as it must: no flip at 6,250 to 7,288 bps, and the harness reports `FAILED CHECK` on eight 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`, `blocks_on_both_sides`, the two id checks), exit 1 | `class-v4-signal-failed-case.json` |
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.
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`.

View file

@ -0,0 +1,78 @@
# Counter ASIC 3.0: the class v4 rehearsal on the rented fleet (precondition 1 of the cut)
6 October 2026, worker "ca3-v4-node", on the coordinator's word of about 17:40 UTC. A precondition: no live-devnet date for class v4 is named until this rehearsal has PASSED on the rented fleet. The fleet agent owns the boxes (memory `gpu-rental.md`: the Vast and RunPod accounts, the fleet SSH key, the 6 October fleet plan and budget); this file is the plan it runs and the objects it runs with. The node lane touches no box. Nothing here is published to the live devnet; the rehearsal chain is a fleet-only network that the live devnet's nodes cannot join and that cannot join them.
## 1. What is rehearsed
The class v4 activation on a chain of real boxes, in the shape the live devnet will see, with both activation rules of the v4 seam (`docs/plans/counter-asic-3-node.md`): the miner-signalled flip (section 6 there, PROPOSED) and the fixed-height floor behind it; the stale-box case, where one box runs the old binary against the new object and must be refused at the handshake without forking the chain; and the live form of the G4 checks.
| Fact | Value | Why |
|---|---|---|
| Network | `--devnet-suffix=400` (`igneum-devnet-400`), every box; the live devnet has no suffix and node 1, the observer and the seeds refuse any other network name at the handshake | fleet-only by construction; the chain starts at its own genesis state (DAA 0) because no box carries a devnet-400 database |
| Binary | one commit of ca3-v4-node (main) and ca3-v4-node (fork), the build job's Linux `igneumd`, `igneum-miner`, `igneum-app` from PC 2 (the G6 job of the signalling commit; the shas in `docs/plans/counter-asic-3-gate/node-gates.md`) on every box but the stale one; the stale box runs 0.3.13's Linux node (fork bb43e9a8, the 0.3.13 outputs) | the same objects as the cut's step 1 |
| Boxes | 12 or more mining boxes (the 50-miner wave's shape at a tenth of the size; 1 CPU miner or 1 GPU worker each), 1 seed box (`--listen`, no miner), 1 stale box | 12 keeps the per-box share near 8 percent, so one box's absence moves the tally by 8 percent: the 95 percent threshold is exercised, not trivially met (with 3 boxes it is 67 or 100 percent, the fast-time shape) |
| Override object | `rehearsal` below, the same file on every box (the stale box too) | the stale case is the digest refusal, so the file must be the same |
| Duration | about 2 h 40 min of chain at 1 block/s: the flip by signal at DAA 7,200 (epoch 2), two epochs after it, then the floor at 14,400 is NOT reached (the run ends at DAA 10,800) | the floor is the backstop; the rehearsal proves the signal path, the fast-time gate proved the floor path |
## 2. The override objects
Both objects below are JSON text to be written verbatim; a `never` height is `18446744073709551615`, which no JSON tool that goes through a double may rewrite (the fast-time harness's rule). The digests are what a node of the signalling commit prints at start (`Consensus params digest`); the fleet agent compares every box's line against them and the stale box's against its own.
### 2a. The live devnet publish object (NOT published by this plan; the shape the cut will use)
The live file today (`/tmp/igneum-devnet/override-v3.json`, 13 fields, read 16:55Z) plus the two v4 fields. `N6` is the floor: DAA at the publish + 14,400 rounded UP to a multiple of 3,600 (the 2.0 rule for N4), checked at publish (`N6 - DAA >= 10,800`). At the live DAA of 202,919 (16:57:43Z) that would be 219,600 (epoch 61); the number is set at the publish, not here.
```
{"difficulty_v2_activation_daa":33000,"proving_v0_activation_daa":84100,"fees_v1_activation_daa":210000,"finality_v3_activation_daa":135200,"program_class_v3_activation_daa":154800,"proving_v1_activation_daa":154800,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":198000,"exec_restart_number":27276,"exec_restart_hash":"bb45cf0dd2d7cc97ebfa5a2701527c09a8ede5d32de74efead9caa293b15688a","exec_restart_trust_daa":200000,"program_class_v4_activation_daa":N6,"program_class_v4_signal_window_daa":86400}
```
What the two fields do on the live devnet: every node of the signalling binary stamps object byte 4 into its templates from its first block, so the signal share climbs as the fleet updates; the class flips at the first epoch boundary whose window (the 86,400 DAA, one day, below that epoch's seed block) has 95 percent of its blue blocks signalling, which is about a day after the LAST box of 95 percent of the hash rate has updated; the floor `N6` flips it regardless at the latest. Digest of this object: `ac8e60ce205852bdda6b554f8cbfbd9dbb040187f487cbd8affe8103633dfd56` (read from the signalling node's start line, 18:05Z; the node also prints `Program class v4 from the override file: active from epoch 61 (DAA score 219600 rounded up to the epoch boundary at 219600, epochs of 3600 DAA)` and the window line) (with `N6` = 219,600 as the worked example; any other `N6` moves it).
### 2b. The rehearsal object (the fleet chain)
A fresh chain, every earlier switch at 0 (the testnet's shape: the chain is born on calibrated difficulty v1, proving v1, fees v1, finality v3, class v3), the v4 signal window one epoch, the floor four hours out:
```
{"difficulty_v2_activation_daa":0,"proving_v0_activation_daa":0,"fees_v1_activation_daa":0,"finality_v3_activation_daa":0,"program_class_v3_activation_daa":0,"proving_v1_activation_daa":0,"proving_v1_segment_blocks":8,"proving_v1_unproven_daa":600,"proving_v1_aggregator_share_bps":1000,"proving_v1_fresh_rule_daa":0,"exec_restart_number":18446744073709551615,"exec_restart_hash":"","exec_restart_trust_daa":18446744073709551615,"program_class_v4_activation_daa":14400,"program_class_v4_signal_window_daa":3600}
```
The arithmetic: epochs of 3,600 DAA, lead 600. Epoch `e`'s seed block is the last chain block below `3600 e - 600`; its window is full when that block's DAA is at least 3,600: epoch 1's seed block sits at DAA 2,999 (not full), epoch 2's at 6,599 (full). With every mining box signalling 4, the tally at epoch 2's seed block is 100 percent of the blue blocks in DAA 2,999 to 6,599, so the class flips at epoch 2, DAA 7,200, about 2 hours after genesis; the floor (epoch 4, DAA 14,400) is 2 hours later and is not reached by the run. Digest: `bc2142b178ff367ae84ff0699ff523760d8878f883375da21864ed8d3ad39237` (the signalling node's start line on this object, 18:05Z, with `Program class v4 from the override file: active from epoch 4 (DAA score 14400 ...)` and `Program class v4 signal window from the override file: 3600 DAA ...`); the devnet digest with no file at all is `7f2e49beabc253f327c5ac6bb457a674ea7f527af2971c95d3bdf65ef8bcf977` on this binary (the fork's pinned test), `c562d70e...` on 0.3.11 to 0.3.13.
## 3. The steps the fleet agent runs
| Step | What | Done when |
|---|---|---|
| 1 | Fetch the signalling commit's Linux binaries from the G6 build job (the shas in node-gates.md), verify every sha256, place `igneumd` and `igneum-miner` on every box; the 0.3.13 Linux `igneumd` on the stale box | every sha matches |
| 2 | Write the rehearsal object (2b) as `override.json` on every box, byte for byte (sha256 the file on each box and compare) | one sha on every box |
| 3 | Start the seed box: `igneumd --devnet --devnet-suffix=400 --nodnsseed --disable-upnp --listen=0.0.0.0:16411 --rpclisten=127.0.0.1:16410 --rpclisten-json=127.0.0.1:16412 --override-params-file=override.json --utxoindex --enable-unsynced-mining --yes --appdir=<fresh dir>` (ports of the box's choosing, never the live devnet's 26610/26611); read its first lines: `Consensus params digest` equals 2b's, `Program class v4 from the override file: active from epoch 4 (DAA score 14400 ...)`, `Program class v4 signal window from the override file: 3600 DAA ...` | the three lines |
| 4 | Start every mining box the same way with `--connect=<seed>:16411`, then its miner: `igneum-miner mine grpc://127.0.0.1:16410 1 100000000 <label> --engine igneum-pow --no-vote --payout-label <label>` (a CPU miner; a GPU box uses `--worker <path> --prepare-packs packs/prepare --exit-on-seed-change`, the app's shape) | every box's node prints the same digest and the two switch lines; every miner prints `epoch seed ... class v3 program id ...` for epoch 0 |
| 5 | Start the stale box last, the same command on the 0.3.13 node, `--connect=<seed>:16411` | its log shows the handshake refusal (a digest mismatch line or `0 peers` after 60 s with connection attempts in the log) and its chain stays at its own genesis (block count 1 or its own lonely blocks if it mines; it must NOT mine: no miner on it) |
| 6 | Every 15 minutes, on every mining box: `getBlockDagInfo` (block count, sink, virtual DAA) and one `getBlockTemplate` (`powEpoch.programClass`, `nextProgramClass`, `programClassV4SignalBps`, `programClassV4SignalEpoch`); keep the lines | the shares read 10,000 bps on every box from the first template; sinks agree across boxes at each sample |
| 7 | At DAA 7,200 (about 2 h): every box's node prints `Program class v4 by miner signal: epoch 2 (share 10000 bps over 3600 DAA ending at seed block <hash>, threshold 9500 bps, N of N blue blocks)` with the SAME seed block hash and the same N on every box; the templates read class 4 from epoch 2; every miner prints `epoch seed <S2> ... class v4 program id <id>` | the same `<S2>`, the same `<id>` on every box |
| 8 | Run to DAA 10,800 (epoch 3, one epoch after the flip; two epochs after is 14,400, the floor, so the run stops at 10,800 plus 600) | the final sample |
| 9 | Collect every miner's `program and 256 MiB cache ready` lines (seed, class, id) and the template's `eraSeed` for epochs 2 and 3, and send them to the node lane; the Mac computes `igneum-pow show --epoch-hex <seed> --program-class v3|v4 --era-hex <era>` for each (the id assertion of the G4 harness, no binary needed on a box) | every box's v4 id equals the CLI's v4 id and differs from the CLI's v3 id of the same seed and era |
| 10 | Stop every node and miner; destroy the instances by the fleet plan's rule | the report in section 4 is in |
Never: no live-devnet port, no live override file, no manifest, no `update-now`; the boxes' app installs (if any) are not touched (the rule of 5 October: a job never quits or restarts an app it did not start).
## 4. What the fleet agent reports back (one table, one JSON)
`docs/plans/counter-asic-3-gate/rehearsal-<time>.json` with the fields below and a row per box in `docs/plans/counter-asic-3-rehearsal.md` section 5 (this file, appended by the fleet agent):
| Field | From | Pass rule |
|---|---|---|
| `digest` per box | the node's first lines | one value on every signalling box, equal to 2b's; the stale box prints 0.3.13's digest of the same file (a different value, since its binary lacks the two fields) |
| `switch_lines` per box | the node's first lines | the floor line names epoch 4 and the window line names 3,600 DAA, on every signalling box |
| `signal_line` per box | step 7 | present on every signalling box, epoch 2, the same seed block hash, share 10,000 bps (at least 9,500) |
| `blocks_before`, `blocks_after` | `getBlocks` from any box, split at DAA 7,200 | both over 0 |
| `rejected` | every miner's `rejected=` STATUS count and every node's `PoW rejected` lines | 0 on every box |
| `sinks` at the end | `getBlockDagInfo` | one value on every signalling box |
| `program_ids` per epoch per box | the miners' lines | one id per epoch across boxes; epochs 2 and 3 class v4; the Mac's CLI check (step 9) holds |
| `stale_box` | its log and `getBlockDagInfo` | refused (no peer), block count 1, no block of its ever appears on any signalling box's chain (its own genesis-state chain and the fleet's never merge) |
| `shares` per sample | step 6 | 10,000 bps on every box at every sample |
PASS = every rule above holds. Any other outcome is RED, the file says which rule failed, and the cut waits.
## 5. Results
(appended by the fleet agent after the run)

View file

@ -69,6 +69,7 @@ Time parameters, divided by 60 (devnet value, 60x value):
| `proving_v1_unproven_daa` | 600 | 10 | a DAA clock (the unproven allowance), divided by 60; the proving agent confirms the value |
| `program_class_v3_activation_daa` | never | never | a height, not a clock: the lottery hash draws programs from class v3 (Counter ASIC 2.0, 5 Oct 2026) from the first EPOCH whose start is at or above this DAA score (rounded up to an epoch boundary: at 60 DAA per epoch, 150 means epoch 3 at DAA 180); `infra/fast-time/class-v3.mjs` sets it a few epochs ahead in its merged file |
| `program_class_v4_activation_daa` | never | never | a height, not a clock: the lottery hash draws programs from class v4 (Counter ASIC 3.0, 6 Oct 2026: class v3 plus the latency-shadow block) from the first EPOCH whose start is at or above this DAA score, rounded up like the v3 switch; `infra/fast-time/class-v4.mjs` sets it a few epochs ahead in its merged file |
| `program_class_v4_signal_window_daa` | 86,400 | 120 | a DAA window (one day of blocks), divided by 60 and rounded to two epochs: the class v4 signal tally (PROPOSED, `docs/plans/counter-asic-3-node.md` section 6) over the blue blocks below each epoch's seed block; 0 = off; `infra/fast-time/class-v4-signal.mjs` is its gate |
| `proving_v1_fresh_rule_daa`, `exec_restart_number`, `exec_restart_trust_daa`, `exec_restart_hash` | never, never, never, "" | the same | heights and a hash, not clocks (the 0.3.12 and 0.3.13 switches); present so the fork's every-field test (`fast_time_60x_file_is_the_devnet_at_60x`) holds; added 6 Oct 2026 with the class v4 field |
Unchanged, and why:

View file

@ -0,0 +1,274 @@
#!/usr/bin/env node
// Counter ASIC 3.0, miner-signalled class activation (PROPOSED, docs/plans/counter-asic-3-node.md section 6): the
// fast-time 3-node network where each node signals an object version of its own (IGNEUM_CLASS_SIGNAL, devnet only),
// 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.
//
// 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)
//
// node infra/fast-time/class-v4-signal.mjs --signal a,b,c --expect flip|no-flip|floor [--floor <daa>|never]
// [--window 120] [--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).
import { spawn, spawnSync } from 'node:child_process';
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_CA3_BIN || `${ROOT}vendor/igneum-node-ca3v4/target-ca3v4/release`;
const IGNEUMD = process.env.IGNEUMD || `${BIN}/igneumd`;
const CPU_MINER = process.env.IGNEUM_MINER || `${BIN}/igneum-miner`;
const IGNEUM_POW = process.env.IGNEUM_POW || `${ROOT}igneum-pow/target/release/igneum-pow`;
const TMP = '/tmp/igneum-fast-time-v4s';
const BASE = 29690, SUFFIX = 969;
const NEVER = '18446744073709551615';
const args = process.argv.slice(2);
const flag = (name, dflt) => { const i = args.indexOf(`--${name}`); return i >= 0 ? +args[i + 1] : dflt; };
const sflag = (name) => { const i = args.indexOf(`--${name}`); return i >= 0 ? args[i + 1] : null; };
const actflag = (name, dflt) => { const v = sflag(name); if (v == null) return dflt; return v === 'never' ? null : +v; };
const GENESIS_BITS = flag('genesis-bits', 0x1f010000);
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 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); }
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 });
const baseText = readFileSync(FILE, 'utf8');
const field = (name) => { const m = new RegExp(`"${name}":\\s*([0-9]+)`).exec(baseText); return m ? +m[1] : undefined; };
const EPOCH = field('pow_epoch_blocks');
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
let FIRST_FULL_EPOCH = 0;
while (FIRST_FULL_EPOCH * EPOCH - LEAD - 1 < 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]+,?`), '');
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`);
}
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`);
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');
// the node's own object byte: what its templates signal
this.proc = spawn(IGNEUMD, a, { stdio: ['ignore', out, out], env: { ...process.env, IGNEUM_CLASS_SIGNAL: String(SIGNAL[this.i]) } });
started.push(this.proc);
await sleep(1200);
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}, signals ${SIGNAL[this.i]}`);
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;
}
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 { } }
}
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 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+)/;
const t0 = Date.now();
const since = () => ((Date.now() - t0) / 1000).toFixed(1);
const n0 = await new Node(0).start();
const n1 = await new Node(1, [`127.0.0.1:${n0.p2pPort}`]).start();
const n2 = await new Node(2, [`127.0.0.1:${n0.p2pPort}`]).start();
const nodes = [n0, n1, n2];
for (const n of nodes) log(`n${n.i}: ${n.grepLog(WINDOW_LINE).map(l => l.replace(/^.*?(Program class v4 signal window)/, '$1'))[0] || '(no window line)'} | ${n.grepLog(OWN_LINE).map(l => l.replace(/^.*?(this node signals)/, '$1'))[0] || '(no signal line)'}`);
log(`n0 digest: ${n0.grepLog(/Consensus params digest/).map(l => l.replace(/^.*?digest: /, '').slice(0, 16)).join(' ')}`);
nodes.forEach((n, i) => miner(CPU_MINER, ['mine', n.grpc, '1', String(SECS), `cpu${i}`, '--engine', 'igneum-pow', '--payout-label', `cpu${i}`, '--status-secs', '30', '--no-vote'], `cpu${i}`, { IGNEUM_POW_DAY_MS: String(DAY_MS) }));
const pay = devAddress('fast-time-v4s');
const epochs = new Map();
let firstV4 = null, lastEpoch = -1, lastReport = 0, lastDaa = 0, endAt = null;
const samples = [];
while (Date.now() - t0 < SECS * 1000) {
await sleep(1000);
let daa = null, epoch = null, cls = null, nextCls = null, eraSeed = null, bps = null, win = null, sig = null, sigEpoch = null;
try {
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;
} 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 });
log(`epoch ${lastEpoch} -> ${epoch} at daa ${daa}, ${since()} s: template class ${cls}, next ${nextCls}, signal share at the sink ${bps} bps (window ${win}, this node signals ${sig}, decided by signal at epoch ${sigEpoch ?? 'none'})`);
if (firstV4 == null && cls === 4) { firstV4 = { epoch, daa, at: +since() }; log(`CLASS SWITCH: the template is class v4 from epoch ${epoch} (daa ${daa}) at ${since()} s wall`); }
lastEpoch = epoch;
}
lastDaa = daa ?? lastDaa;
if (Date.now() - lastReport > 15000) {
lastReport = Date.now();
const counts = await Promise.all(nodes.map(async n => { try { const d = await n.rpc.call('getBlockDagInfo'); return `${d.blockCount}/${String(d.sink).slice(0, 8)}`; } catch { return '?'; } }));
log(`t=${since()} s daa ${daa} epoch ${epoch} class ${cls} signal ${bps} bps blocks/sink per node ${counts.join(' ')}`);
samples.push({ t: +since(), daa, epoch, class: cls, bps, nodes: counts });
}
// the end: two epochs after a flip, or --epochs epochs when no flip is expected
if (firstV4 != null && daa != null && daa >= (firstV4.epoch + 2) * EPOCH) { endAt = +since(); break; }
if (firstV4 == null && daa != null && daa >= EPOCHS * EPOCH) { endAt = +since(); break; }
}
await sleep(3000);
const dag = await Promise.all(nodes.map(async n => { try { return await n.rpc.call('getBlockDagInfo'); } catch (e) { return { error: e.message }; } }));
const genesis = dag[0].pruningPointHash;
async function allBlocks(n) {
const out = []; let low = genesis; const seen = new Set();
for (let round = 0; round < 500; round++) {
const r = await n.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); out.push({ hash: h, daa: +b.header.daaScore, version: +b.header.version, chain: !!b.verboseData?.isChainBlock }); added++; }
if (!blocks.length || added === 0) break;
low = (r.blockHashes || []).at(-1) || blocks.at(-1).verboseData?.hash; if (!low) break;
}
return out;
}
let blocks = [];
try { blocks = await allBlocks(n0); } catch (e) { log(`getBlocks: ${e.message}`); }
const BOUNDARY = firstV4 ? firstV4.epoch * EPOCH : Infinity;
const before = blocks.filter(b => b.daa < BOUNDARY), after = blocks.filter(b => b.daa >= BOUNDARY);
// the signal bytes on the chain: the share of blocks whose version high byte is 4
const versionBytes = blocks.reduce((m, b) => { const v = b.version >> 8; m[v] = (m[v] || 0) + 1; return m; }, {});
const signalShareOnChain = blocks.length ? Math.round(10000 * (blocks.filter(b => (b.version >> 8) >= 4).length) / blocks.length) : 0;
const programs = new Map();
for (const i of [0, 1, 2]) for (const l of minerLog(i)) {
const m = /epoch seed ([0-9a-f]{64}) day (\d+) \(daa (\d+)\): program and 256 MiB cache ready in ([\d.]+) ms; class (v\d) program id ([0-9a-f]{16})/.exec(l);
if (!m) continue;
const k = m[1]; const e = programs.get(k) || { seed: k.slice(0, 16), epoch: Math.floor(+m[3] / EPOCH), class: m[5], id: m[6], miners: new Set() };
if (e.id !== m[6] || e.class !== m[5]) e.disagree = true;
e.miners.add(i); programs.set(k, e);
}
const programRows = [...programs.values()].sort((a, b) => a.epoch - b.epoch).map(p => ({ epoch: p.epoch, class: p.class, program_id: p.id, seed: p.seed, miners: p.miners.size, disagree: !!p.disagree }));
function cliId(seedHex, eraHex, cls) {
if (!existsSync(IGNEUM_POW)) return null;
const r = spawnSync(IGNEUM_POW, ['show', '--epoch-hex', seedHex, '--program-class', cls, '--era-hex', eraHex], { encoding: 'utf8' });
const m = /program id ([0-9a-f]{16})/.exec(r.stdout || '');
return m ? m[1] : null;
}
const idRows = [];
for (const [k, e] of programs) {
if (e.class !== 'v4') continue;
const era = epochs.get(e.epoch)?.eraSeed;
idRows.push({ epoch: e.epoch, seed: e.seed, miners_id: e.id, miners: e.miners.size, cli_v3: era ? cliId(k, era, 'v3') : null, cli_v4: era ? cliId(k, era, 'v4') : null });
}
const accepted = [0, 1, 2].map(i => minerLog(i).filter(l => /ACCEPTED block/.test(l)).length);
const rejectedMiner = [0, 1, 2].map(i => minerLog(i).filter(l => /rejected nonce=|submit error/.test(l)));
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 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 ?? '?');
const maxEpochSeen = Math.max(-1, ...epochs.keys());
const classesSeen = [...epochs.values()].map(e => e.class);
const common = {
zero_rejected_by_miners: rejectedMiner.every(r => r.length === 0),
zero_rejected_by_nodes: rejectedNode.every(r => r.length === 0),
sinks_agree: new Set(sinks).size === 1,
block_counts_agree: new Set(counts.map(String)).size === 1,
miners_agree_on_every_program: programRows.every(p => !p.disagree),
window_line_on_every_node: nodes.every(n => n.grepLog(WINDOW_LINE).length > 0),
every_node_signals_its_byte: nodes.every((n, i) => n.grepLog(OWN_LINE).some(l => +OWN_LINE.exec(l)[1] === SIGNAL[i])),
// every block's byte is one of the three nodes' (genesis, made before any node, is the one byte-0 block)
chain_carries_the_bytes: blocks.length > 0 && Object.keys(versionBytes).every(v => SIGNAL.includes(+v) || (+v === 0 && versionBytes[v] === 1)),
};
let checks;
if (EXPECT === 'flip') {
checks = {
...common,
template_switched_to_v4: firstV4 != null,
switched_at_the_first_full_window_epoch: firstV4 != null && firstV4.epoch === FIRST_FULL_EPOCH,
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),
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),
};
} else if (EXPECT === 'no-flip') {
checks = {
...common,
template_never_v4: firstV4 == null && !classesSeen.includes(4),
no_signal_line_on_any_node: signalLines.every(l => l == null),
ran_the_epochs: maxEpochSeen >= EPOCHS - 1,
v3_programs_seen: programRows.some(p => p.class === 'v3'),
signal_share_under_threshold_on_chain: signalShareOnChain < 9500,
};
} else {
checks = {
...common,
template_switched_to_v4: firstV4 != null,
switched_at_the_floor_epoch: firstV4 != null && firstV4.epoch === FLOOR_EPOCH,
no_signal_line_on_any_node: signalLines.every(l => l == null),
floor_line_names_the_floor_epoch: floorLines.every(l => l && l.includes(`active from epoch ${FLOOR_EPOCH} `)),
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),
};
}
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,
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 },
programs: programRows, program_id_rows: idRows, accepted_per_miner: accepted,
rejected_by_miners: rejectedMiner.map(r => r.length), rejected_by_nodes: rejectedNode.map(r => r.length),
sinks, block_counts: counts, signal_lines: signalLines, floor_lines: floorLines, samples,
};
writeFileSync(`${TMP}/summary.json`, JSON.stringify(summary, null, 2));
log(`SUMMARY ${pass ? 'PASS' : 'FAIL'} (expect ${EXPECT}, signals ${SIGNAL.join('/')}): ${firstV4 ? `v4 from epoch ${firstV4.epoch} at DAA ${firstV4.daa}` : 'no v4 epoch'}; epochs seen ${[...epochs.entries()].map(([e, v]) => `e${e}:${v.class}:${v.bps}bps`).join(' ')}; chain bytes ${JSON.stringify(versionBytes)} (${signalShareOnChain} bps signal v4); blocks ${before.length} / ${after.length}; rejected miners ${rejectedMiner.map(r => r.length).join('/')} nodes ${rejectedNode.map(r => r.length).join('/')}; sinks ${sinks.join(' ')} (${checks.sinks_agree ? 'agree' : 'DIFFER'}); counts ${counts.join('/')}; signal lines ${signalLines.filter(Boolean).length}/3 (epochs ${signalEpochs.join('/')}, shares ${signalShares.join('/')}); floor lines ${floorLines.filter(Boolean).length}/3`);
for (const r of idRows) log(`PROGRAM ID epoch ${r.epoch} seed ${r.seed}: miners ${r.miners_id} (${r.miners} of 3) cli v4 ${r.cli_v4} cli v3 ${r.cli_v3}`);
for (const [k, v] of Object.entries(checks)) if (!v) log(`FAILED CHECK ${k}`);
log(`summary: ${TMP}/summary.json`);
await stopAll();
process.exit(pass ? 0 : 1);

View file

@ -56,6 +56,7 @@
"finality_v3_activation_daa": 18446744073709551615,
"program_class_v3_activation_daa": 18446744073709551615,
"program_class_v4_activation_daa": 18446744073709551615,
"program_class_v4_signal_window_daa": 120,
"proving_v1_fresh_rule_daa": 18446744073709551615,
"exec_restart_number": 18446744073709551615,
"exec_restart_hash": "",