diff --git a/docs/plans/mission-item-12-gate/case-off-eight.log b/docs/plans/mission-item-12-gate/case-off-eight.log new file mode 100644 index 000000000..82d701bf7 --- /dev/null +++ b/docs/plans/mission-item-12-gate/case-off-eight.log @@ -0,0 +1,21 @@ +13:27:58.207 pd1 case switch-off-expect-eight: switch off, 10 listing nodes, joint 200 s, watch 180 s, expect eight +13:27:59.744 pd1 n0 up pid 690270 json 31292 p2p 31291 +13:28:01.258 pd1 n1 up pid 690864 json 31302 p2p 31301 addpeer 31291,31291 +13:28:02.766 pd1 n2 up pid 691561 json 31312 p2p 31311 addpeer 31291,31301 +13:28:04.272 pd1 n3 up pid 692232 json 31322 p2p 31321 addpeer 31291,31311 +13:28:05.777 pd1 n4 up pid 692823 json 31332 p2p 31331 addpeer 31291,31321 +13:28:07.281 pd1 n5 up pid 693470 json 31342 p2p 31341 addpeer 31291,31331 +13:28:08.785 pd1 n6 up pid 694063 json 31352 p2p 31351 addpeer 31291,31341 +13:28:10.290 pd1 n7 up pid 694656 json 31362 p2p 31361 addpeer 31291,31351 +13:28:11.797 pd1 n8 up pid 695316 json 31372 p2p 31371 addpeer 31291,31361 +13:28:13.304 pd1 n9 up pid 695947 json 31382 p2p 31381 addpeer 31291,31371 +13:31:35.437 pd1 phase 1 done at 200.1 s: node 0 at 243 blocks DAA 243; 0 directory listing line(s) on node 0 +13:31:36.962 pd1 fresh up pid 705981 json 31392 p2p 31391 addpeer 31291 +13:32:07.031 pd1 t=231.7 s fresh: 1 outbound, 0 inbound, 272 blocks, 0 draw line(s), 0 directory connection(s) +13:32:37.109 pd1 t=261.8 s fresh: 1 outbound, 0 inbound, 314 blocks, 0 draw line(s), 0 directory connection(s) +13:33:07.190 pd1 t=291.9 s fresh: 1 outbound, 0 inbound, 332 blocks, 0 draw line(s), 0 directory connection(s) +13:33:37.273 pd1 t=322.0 s fresh: 1 outbound, 0 inbound, 359 blocks, 0 draw line(s), 0 directory connection(s) +13:34:07.348 pd1 t=352.0 s fresh: 1 outbound, 0 inbound, 379 blocks, 0 draw line(s), 0 directory connection(s) +13:34:37.426 pd1 t=382.1 s fresh: 1 outbound, 0 inbound, 420 blocks, 0 draw line(s), 0 directory connection(s) +13:34:37.428 pd1 SUMMARY FAIL (switch-off-expect-eight): node 0 logged 0 listings; the fresh node never reached 8 outbound (last 1), 0 connection(s) from the directory, 0 draw line(s), 420 blocks; FAILED CHECK listing_lines_on_node_0, fresh_reached_eight_outbound, fresh_connected_from_directory +13:34:37.428 pd1 summary: /srv/builds/igneum-wt-peer-directory/docs/plans/mission-item-12-gate/peer-directory-switch-off-expect-eight.json diff --git a/docs/plans/mission-item-12-gate/case-off-one.log b/docs/plans/mission-item-12-gate/case-off-one.log new file mode 100644 index 000000000..fe4af0399 --- /dev/null +++ b/docs/plans/mission-item-12-gate/case-off-one.log @@ -0,0 +1,21 @@ +13:27:58.206 pd0 case switch-off-expect-one: switch off, 10 listing nodes, joint 200 s, watch 180 s, expect one +13:27:59.744 pd0 n0 up pid 690269 json 31092 p2p 31091 +13:28:01.255 pd0 n1 up pid 690863 json 31102 p2p 31101 addpeer 31091,31091 +13:28:02.763 pd0 n2 up pid 691557 json 31112 p2p 31111 addpeer 31091,31101 +13:28:04.270 pd0 n3 up pid 692228 json 31122 p2p 31121 addpeer 31091,31111 +13:28:05.775 pd0 n4 up pid 692819 json 31132 p2p 31131 addpeer 31091,31121 +13:28:07.280 pd0 n5 up pid 693466 json 31142 p2p 31141 addpeer 31091,31131 +13:28:08.786 pd0 n6 up pid 694058 json 31152 p2p 31151 addpeer 31091,31141 +13:28:10.292 pd0 n7 up pid 694658 json 31162 p2p 31161 addpeer 31091,31151 +13:28:11.800 pd0 n8 up pid 695318 json 31172 p2p 31171 addpeer 31091,31161 +13:28:13.306 pd0 n9 up pid 695955 json 31182 p2p 31181 addpeer 31091,31171 +13:31:35.436 pd0 phase 1 done at 200.1 s: node 0 at 250 blocks DAA 250; 0 directory listing line(s) on node 0 +13:31:36.969 pd0 fresh up pid 705982 json 31192 p2p 31191 addpeer 31091 +13:32:07.034 pd0 t=231.7 s fresh: 1 outbound, 0 inbound, 288 blocks, 0 draw line(s), 0 directory connection(s) +13:32:37.118 pd0 t=261.8 s fresh: 1 outbound, 0 inbound, 308 blocks, 0 draw line(s), 0 directory connection(s) +13:33:07.199 pd0 t=291.9 s fresh: 1 outbound, 0 inbound, 349 blocks, 0 draw line(s), 0 directory connection(s) +13:33:37.276 pd0 t=322.0 s fresh: 1 outbound, 0 inbound, 377 blocks, 0 draw line(s), 0 directory connection(s) +13:34:07.351 pd0 t=352.0 s fresh: 1 outbound, 0 inbound, 405 blocks, 0 draw line(s), 0 directory connection(s) +13:34:37.431 pd0 t=382.1 s fresh: 1 outbound, 0 inbound, 448 blocks, 0 draw line(s), 0 directory connection(s) +13:34:37.432 pd0 SUMMARY PASS (switch-off-expect-one): node 0 logged 0 listings; the fresh node never reached 8 outbound (last 1), 0 connection(s) from the directory, 0 draw line(s), 448 blocks +13:34:37.432 pd0 summary: /srv/builds/igneum-wt-peer-directory/docs/plans/mission-item-12-gate/peer-directory-switch-off-expect-one.json diff --git a/docs/plans/mission-item-12-gate/case-on-eight.log b/docs/plans/mission-item-12-gate/case-on-eight.log new file mode 100644 index 000000000..968aee53e --- /dev/null +++ b/docs/plans/mission-item-12-gate/case-on-eight.log @@ -0,0 +1,22 @@ +13:27:58.204 pd2 case switch-on-expect-eight: switch on, 10 listing nodes, joint 200 s, watch 180 s, expect eight +13:27:59.743 pd2 n0 up pid 690268 json 31492 p2p 31491 +13:28:01.254 pd2 n1 up pid 690860 json 31502 p2p 31501 addpeer 31491,31491 +13:28:02.762 pd2 n2 up pid 691556 json 31512 p2p 31511 addpeer 31491,31501 +13:28:04.268 pd2 n3 up pid 692227 json 31522 p2p 31521 addpeer 31491,31511 +13:28:05.773 pd2 n4 up pid 692818 json 31532 p2p 31531 addpeer 31491,31521 +13:28:07.279 pd2 n5 up pid 693462 json 31542 p2p 31541 addpeer 31491,31531 +13:28:08.787 pd2 n6 up pid 694062 json 31552 p2p 31551 addpeer 31491,31541 +13:28:10.291 pd2 n7 up pid 694659 json 31562 p2p 31561 addpeer 31491,31551 +13:28:11.800 pd2 n8 up pid 695317 json 31572 p2p 31571 addpeer 31491,31561 +13:28:13.304 pd2 n9 up pid 695954 json 31582 p2p 31581 addpeer 31491,31571 +13:31:35.435 pd2 phase 1 done at 200.1 s: node 0 at 233 blocks DAA 233; 10 directory listing line(s) on node 0 +13:31:36.954 pd2 fresh up pid 705980 json 31592 p2p 31591 addpeer 31491 +13:32:07.025 pd2 the fresh node holds 10 outbound peers at 231.7 s (9 from the directory) +13:32:07.025 pd2 t=231.7 s fresh: 10 outbound, 0 inbound, 262 blocks, 1 draw line(s), 9 directory connection(s) +13:32:37.102 pd2 t=261.8 s fresh: 10 outbound, 0 inbound, 290 blocks, 1 draw line(s), 9 directory connection(s) +13:33:07.182 pd2 t=291.9 s fresh: 10 outbound, 0 inbound, 323 blocks, 1 draw line(s), 9 directory connection(s) +13:33:37.265 pd2 t=322.0 s fresh: 10 outbound, 0 inbound, 357 blocks, 1 draw line(s), 9 directory connection(s) +13:34:07.342 pd2 t=352.0 s fresh: 10 outbound, 0 inbound, 391 blocks, 1 draw line(s), 9 directory connection(s) +13:34:37.418 pd2 t=382.1 s fresh: 10 outbound, 0 inbound, 414 blocks, 1 draw line(s), 9 directory connection(s) +13:34:37.418 pd2 SUMMARY PASS (switch-on-expect-eight): node 0 logged 10 listings; the fresh node reached 8 outbound at 231.7 s, 9 connection(s) from the directory, 1 draw line(s), 414 blocks +13:34:37.419 pd2 summary: /srv/builds/igneum-wt-peer-directory/docs/plans/mission-item-12-gate/peer-directory-switch-on-expect-eight.json diff --git a/docs/plans/mission-item-12-gate/peer-directory-switch-off-expect-eight.json b/docs/plans/mission-item-12-gate/peer-directory-switch-off-expect-eight.json new file mode 100644 index 000000000..7570b9d61 --- /dev/null +++ b/docs/plans/mission-item-12-gate/peer-directory-switch-off-expect-eight.json @@ -0,0 +1,370 @@ +{ + "pass": false, + "expect": "eight", + "case": "switch-off-expect-eight", + "switch": "off", + "listing": 10, + "joint": 200, + "watch": 180, + "node0": { + "blocks": 243, + "daa": 243 + }, + "listing_lines_on_node_0": 0, + "eight_outbound_at_s": null, + "last": { + "t": 382.1, + "outbound": 1, + "inbound": 0, + "blocks": 420, + "daa": 420, + "drawn": 0, + "connected_from_directory": 0 + }, + "checks": { + "listing_lines_on_node_0": false, + "fresh_synced": true, + "fresh_reached_eight_outbound": false, + "fresh_connected_from_directory": false, + "fresh_held_one_outbound": true, + "no_directory_line_on_fresh": true + }, + "failed_checks": [ + "listing_lines_on_node_0", + "fresh_reached_eight_outbound", + "fresh_connected_from_directory" + ], + "samples": [ + { + "t": 206.7, + "outbound": 1, + "inbound": 0, + "blocks": 250, + "daa": 250, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 211.7, + "outbound": 1, + "inbound": 0, + "blocks": 255, + "daa": 255, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 216.7, + "outbound": 1, + "inbound": 0, + "blocks": 258, + "daa": 258, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 221.7, + "outbound": 1, + "inbound": 0, + "blocks": 264, + "daa": 264, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 226.7, + "outbound": 1, + "inbound": 0, + "blocks": 271, + "daa": 271, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 231.7, + "outbound": 1, + "inbound": 0, + "blocks": 272, + "daa": 272, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 236.7, + "outbound": 1, + "inbound": 0, + "blocks": 276, + "daa": 276, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 241.8, + "outbound": 1, + "inbound": 0, + "blocks": 284, + "daa": 284, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 246.8, + "outbound": 1, + "inbound": 0, + "blocks": 289, + "daa": 289, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 251.8, + "outbound": 1, + "inbound": 0, + "blocks": 292, + "daa": 292, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 256.8, + "outbound": 1, + "inbound": 0, + "blocks": 302, + "daa": 302, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 261.8, + "outbound": 1, + "inbound": 0, + "blocks": 314, + "daa": 314, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 266.8, + "outbound": 1, + "inbound": 0, + "blocks": 317, + "daa": 317, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 271.8, + "outbound": 1, + "inbound": 0, + "blocks": 321, + "daa": 321, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 276.9, + "outbound": 1, + "inbound": 0, + "blocks": 322, + "daa": 322, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 281.9, + "outbound": 1, + "inbound": 0, + "blocks": 326, + "daa": 326, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 286.9, + "outbound": 1, + "inbound": 0, + "blocks": 331, + "daa": 331, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 291.9, + "outbound": 1, + "inbound": 0, + "blocks": 332, + "daa": 332, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 296.9, + "outbound": 1, + "inbound": 0, + "blocks": 338, + "daa": 338, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 301.9, + "outbound": 1, + "inbound": 0, + "blocks": 341, + "daa": 341, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 306.9, + "outbound": 1, + "inbound": 0, + "blocks": 344, + "daa": 344, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 311.9, + "outbound": 1, + "inbound": 0, + "blocks": 349, + "daa": 349, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 317, + "outbound": 1, + "inbound": 0, + "blocks": 355, + "daa": 355, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 322, + "outbound": 1, + "inbound": 0, + "blocks": 359, + "daa": 359, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 327, + "outbound": 1, + "inbound": 0, + "blocks": 361, + "daa": 361, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 332, + "outbound": 1, + "inbound": 0, + "blocks": 369, + "daa": 369, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 337, + "outbound": 1, + "inbound": 0, + "blocks": 371, + "daa": 371, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 342, + "outbound": 1, + "inbound": 0, + "blocks": 374, + "daa": 374, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 347, + "outbound": 1, + "inbound": 0, + "blocks": 378, + "daa": 378, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 352, + "outbound": 1, + "inbound": 0, + "blocks": 379, + "daa": 379, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 357.1, + "outbound": 1, + "inbound": 0, + "blocks": 389, + "daa": 389, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 362.1, + "outbound": 1, + "inbound": 0, + "blocks": 400, + "daa": 400, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 367.1, + "outbound": 1, + "inbound": 0, + "blocks": 406, + "daa": 406, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 372.1, + "outbound": 1, + "inbound": 0, + "blocks": 410, + "daa": 410, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 377.1, + "outbound": 1, + "inbound": 0, + "blocks": 416, + "daa": 416, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 382.1, + "outbound": 1, + "inbound": 0, + "blocks": 420, + "daa": 420, + "drawn": 0, + "connected_from_directory": 0 + } + ], + "node": "/srv/builds/igneum-wt-peer-directory/vendor/igneum-node-pd/target/release/igneumd", + "miner": "/srv/builds/igneum-wt-peer-directory/vendor/igneum-node-pd/target/release/igneum-miner", + "slot": 1, + "ports": { + "base": 31290, + "suffix": 976 + } +} \ No newline at end of file diff --git a/docs/plans/mission-item-12-gate/peer-directory-switch-off-expect-one.json b/docs/plans/mission-item-12-gate/peer-directory-switch-off-expect-one.json new file mode 100644 index 000000000..48d4e3288 --- /dev/null +++ b/docs/plans/mission-item-12-gate/peer-directory-switch-off-expect-one.json @@ -0,0 +1,366 @@ +{ + "pass": true, + "expect": "one", + "case": "switch-off-expect-one", + "switch": "off", + "listing": 10, + "joint": 200, + "watch": 180, + "node0": { + "blocks": 250, + "daa": 250 + }, + "listing_lines_on_node_0": 0, + "eight_outbound_at_s": null, + "last": { + "t": 382.1, + "outbound": 1, + "inbound": 0, + "blocks": 448, + "daa": 448, + "drawn": 0, + "connected_from_directory": 0 + }, + "checks": { + "listing_lines_on_node_0": false, + "fresh_synced": true, + "fresh_reached_eight_outbound": false, + "fresh_connected_from_directory": false, + "fresh_held_one_outbound": true, + "no_directory_line_on_fresh": true + }, + "failed_checks": [], + "samples": [ + { + "t": 206.7, + "outbound": 1, + "inbound": 0, + "blocks": 261, + "daa": 261, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 211.7, + "outbound": 1, + "inbound": 0, + "blocks": 271, + "daa": 271, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 216.7, + "outbound": 1, + "inbound": 0, + "blocks": 276, + "daa": 276, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 221.7, + "outbound": 1, + "inbound": 0, + "blocks": 278, + "daa": 278, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 226.7, + "outbound": 1, + "inbound": 0, + "blocks": 282, + "daa": 282, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 231.7, + "outbound": 1, + "inbound": 0, + "blocks": 288, + "daa": 288, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 236.8, + "outbound": 1, + "inbound": 0, + "blocks": 294, + "daa": 294, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 241.8, + "outbound": 1, + "inbound": 0, + "blocks": 296, + "daa": 296, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 246.8, + "outbound": 1, + "inbound": 0, + "blocks": 303, + "daa": 303, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 251.8, + "outbound": 1, + "inbound": 0, + "blocks": 305, + "daa": 305, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 256.8, + "outbound": 1, + "inbound": 0, + "blocks": 307, + "daa": 307, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 261.8, + "outbound": 1, + "inbound": 0, + "blocks": 308, + "daa": 308, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 266.8, + "outbound": 1, + "inbound": 0, + "blocks": 311, + "daa": 311, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 271.8, + "outbound": 1, + "inbound": 0, + "blocks": 315, + "daa": 315, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 276.9, + "outbound": 1, + "inbound": 0, + "blocks": 322, + "daa": 322, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 281.9, + "outbound": 1, + "inbound": 0, + "blocks": 331, + "daa": 331, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 286.9, + "outbound": 1, + "inbound": 0, + "blocks": 341, + "daa": 341, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 291.9, + "outbound": 1, + "inbound": 0, + "blocks": 349, + "daa": 349, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 296.9, + "outbound": 1, + "inbound": 0, + "blocks": 357, + "daa": 357, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 301.9, + "outbound": 1, + "inbound": 0, + "blocks": 364, + "daa": 364, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 306.9, + "outbound": 1, + "inbound": 0, + "blocks": 369, + "daa": 369, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 311.9, + "outbound": 1, + "inbound": 0, + "blocks": 372, + "daa": 372, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 317, + "outbound": 1, + "inbound": 0, + "blocks": 374, + "daa": 374, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 322, + "outbound": 1, + "inbound": 0, + "blocks": 377, + "daa": 377, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 327, + "outbound": 1, + "inbound": 0, + "blocks": 384, + "daa": 384, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 332, + "outbound": 1, + "inbound": 0, + "blocks": 393, + "daa": 393, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 337, + "outbound": 1, + "inbound": 0, + "blocks": 396, + "daa": 396, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 342, + "outbound": 1, + "inbound": 0, + "blocks": 397, + "daa": 397, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 347, + "outbound": 1, + "inbound": 0, + "blocks": 402, + "daa": 402, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 352, + "outbound": 1, + "inbound": 0, + "blocks": 405, + "daa": 405, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 357.1, + "outbound": 1, + "inbound": 0, + "blocks": 413, + "daa": 413, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 362.1, + "outbound": 1, + "inbound": 0, + "blocks": 422, + "daa": 422, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 367.1, + "outbound": 1, + "inbound": 0, + "blocks": 428, + "daa": 428, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 372.1, + "outbound": 1, + "inbound": 0, + "blocks": 433, + "daa": 433, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 377.1, + "outbound": 1, + "inbound": 0, + "blocks": 440, + "daa": 440, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 382.1, + "outbound": 1, + "inbound": 0, + "blocks": 448, + "daa": 448, + "drawn": 0, + "connected_from_directory": 0 + } + ], + "node": "/srv/builds/igneum-wt-peer-directory/vendor/igneum-node-pd/target/release/igneumd", + "miner": "/srv/builds/igneum-wt-peer-directory/vendor/igneum-node-pd/target/release/igneum-miner", + "slot": 0, + "ports": { + "base": 31090, + "suffix": 975 + } +} \ No newline at end of file diff --git a/docs/plans/mission-item-12-gate/peer-directory-switch-on-expect-eight.json b/docs/plans/mission-item-12-gate/peer-directory-switch-on-expect-eight.json new file mode 100644 index 000000000..1579a4f7e --- /dev/null +++ b/docs/plans/mission-item-12-gate/peer-directory-switch-on-expect-eight.json @@ -0,0 +1,366 @@ +{ + "pass": true, + "expect": "eight", + "case": "switch-on-expect-eight", + "switch": "on", + "listing": 10, + "joint": 200, + "watch": 180, + "node0": { + "blocks": 233, + "daa": 233 + }, + "listing_lines_on_node_0": 10, + "eight_outbound_at_s": 231.7, + "last": { + "t": 382.1, + "outbound": 10, + "inbound": 0, + "blocks": 414, + "daa": 414, + "drawn": 1, + "connected_from_directory": 9 + }, + "checks": { + "listing_lines_on_node_0": true, + "fresh_synced": true, + "fresh_reached_eight_outbound": true, + "fresh_connected_from_directory": true, + "fresh_held_one_outbound": false, + "no_directory_line_on_fresh": false + }, + "failed_checks": [], + "samples": [ + { + "t": 206.7, + "outbound": 1, + "inbound": 0, + "blocks": 242, + "daa": 242, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 211.7, + "outbound": 1, + "inbound": 0, + "blocks": 246, + "daa": 246, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 216.7, + "outbound": 1, + "inbound": 0, + "blocks": 253, + "daa": 253, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 221.7, + "outbound": 1, + "inbound": 0, + "blocks": 256, + "daa": 256, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 226.7, + "outbound": 1, + "inbound": 0, + "blocks": 260, + "daa": 260, + "drawn": 0, + "connected_from_directory": 0 + }, + { + "t": 231.7, + "outbound": 10, + "inbound": 0, + "blocks": 262, + "daa": 262, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 236.7, + "outbound": 10, + "inbound": 0, + "blocks": 269, + "daa": 269, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 241.7, + "outbound": 10, + "inbound": 0, + "blocks": 277, + "daa": 277, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 246.8, + "outbound": 10, + "inbound": 0, + "blocks": 280, + "daa": 280, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 251.8, + "outbound": 10, + "inbound": 0, + "blocks": 283, + "daa": 283, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 256.8, + "outbound": 10, + "inbound": 0, + "blocks": 286, + "daa": 286, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 261.8, + "outbound": 10, + "inbound": 0, + "blocks": 290, + "daa": 290, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 266.8, + "outbound": 10, + "inbound": 0, + "blocks": 298, + "daa": 298, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 271.8, + "outbound": 10, + "inbound": 0, + "blocks": 303, + "daa": 303, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 276.8, + "outbound": 10, + "inbound": 0, + "blocks": 311, + "daa": 311, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 281.8, + "outbound": 10, + "inbound": 0, + "blocks": 316, + "daa": 316, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 286.9, + "outbound": 10, + "inbound": 0, + "blocks": 317, + "daa": 317, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 291.9, + "outbound": 10, + "inbound": 0, + "blocks": 323, + "daa": 323, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 296.9, + "outbound": 10, + "inbound": 0, + "blocks": 334, + "daa": 334, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 301.9, + "outbound": 10, + "inbound": 0, + "blocks": 337, + "daa": 337, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 306.9, + "outbound": 10, + "inbound": 0, + "blocks": 342, + "daa": 342, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 311.9, + "outbound": 10, + "inbound": 0, + "blocks": 348, + "daa": 348, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 316.9, + "outbound": 10, + "inbound": 0, + "blocks": 350, + "daa": 350, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 322, + "outbound": 10, + "inbound": 0, + "blocks": 357, + "daa": 357, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 327, + "outbound": 10, + "inbound": 0, + "blocks": 359, + "daa": 359, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 332, + "outbound": 10, + "inbound": 0, + "blocks": 362, + "daa": 362, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 337, + "outbound": 10, + "inbound": 0, + "blocks": 364, + "daa": 364, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 342, + "outbound": 10, + "inbound": 0, + "blocks": 368, + "daa": 368, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 347, + "outbound": 10, + "inbound": 0, + "blocks": 380, + "daa": 380, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 352, + "outbound": 10, + "inbound": 0, + "blocks": 391, + "daa": 391, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 357.1, + "outbound": 10, + "inbound": 0, + "blocks": 393, + "daa": 393, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 362.1, + "outbound": 10, + "inbound": 0, + "blocks": 399, + "daa": 399, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 367.1, + "outbound": 10, + "inbound": 0, + "blocks": 401, + "daa": 401, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 372.1, + "outbound": 10, + "inbound": 0, + "blocks": 403, + "daa": 403, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 377.1, + "outbound": 10, + "inbound": 0, + "blocks": 409, + "daa": 409, + "drawn": 1, + "connected_from_directory": 9 + }, + { + "t": 382.1, + "outbound": 10, + "inbound": 0, + "blocks": 414, + "daa": 414, + "drawn": 1, + "connected_from_directory": 9 + } + ], + "node": "/srv/builds/igneum-wt-peer-directory/vendor/igneum-node-pd/target/release/igneumd", + "miner": "/srv/builds/igneum-wt-peer-directory/vendor/igneum-node-pd/target/release/igneum-miner", + "slot": 2, + "ports": { + "base": 31490, + "suffix": 977 + } +} \ No newline at end of file diff --git a/infra/fast-time/override-60x.json b/infra/fast-time/override-60x.json index 10b72275a..3561c8d23 100644 --- a/infra/fast-time/override-60x.json +++ b/infra/fast-time/override-60x.json @@ -88,5 +88,6 @@ "sig_scheme_activation_daa": 18446744073709551615, "finality_succession_activation_daa": 18446744073709551615, "latency_ladder_cache_rung": {"mib": 512, "admissible": false}, - "fees": {"pgas": {"version": 1, "cycles_per_pgas": 1000, "intrinsic_pgas_per_tx": 300, "modexp_base": 10, "modexp_per_byte_numer": 1, "modexp_per_byte_denom": 10}, "block_proving_gas_limit": 120000, "shard_proving_gas_budget": 30000, "min_execution_base_fee_wei": 100000000000, "min_proving_base_fee_wei": 10000000000000, "initial_execution_base_fee_wei": 100000000000, "initial_proving_base_fee_wei": 10000000000000, "base_fee_change_denominator": 8} + "fees": {"pgas": {"version": 1, "cycles_per_pgas": 1000, "intrinsic_pgas_per_tx": 300, "modexp_base": 10, "modexp_per_byte_numer": 1, "modexp_per_byte_denom": 10}, "block_proving_gas_limit": 120000, "shard_proving_gas_budget": 30000, "min_execution_base_fee_wei": 100000000000, "min_proving_base_fee_wei": 10000000000000, "initial_execution_base_fee_wei": 100000000000, "initial_proving_base_fee_wei": 10000000000000, "base_fee_change_denominator": 8}, + "peer_directory_activation_daa": null } diff --git a/infra/fast-time/peer-directory.mjs b/infra/fast-time/peer-directory.mjs new file mode 100644 index 000000000..d0dc92969 --- /dev/null +++ b/infra/fast-time/peer-directory.mjs @@ -0,0 +1,183 @@ +#!/usr/bin/env node +// Mission item 12, the weight-backed peer directory (docs/analysis/mission/invent.md 7.1), the fast-time gate: "a fresh +// node with genesis peers only reaches 8 outbound from the directory". Ten listing nodes mine on one fast-time network, +// each miner announcing its own node's loopback p2p address in its blocks (`igneum-miner --announce`); every node advertises +// an UNROUTABLE address as its external ip, so ordinary address gossip hands a fresh node dead addresses only and the +// directory is the one live source. After --joint seconds (the weight table has every listing key above dust) a fresh node +// starts with --outpeers=8 and one --addpeer to node 0 (its "genesis peer"), syncs, reads the announcements out of the +// blocks, and is watched for --watch seconds. +// +// --switch on peer_directory_activation_daa 0: the fresh node reaches 8 outbound peers; its log carries "drawn from +// the peer directory" +// --switch off the switch at never, the known-failed case: the fresh node holds 1 outbound (node 0) for the whole watch +// --expect eight|one which outcome is a PASS; `--switch off --expect eight` is the harness's own failed shape (must FAIL) +// +// node infra/fast-time/peer-directory.mjs --switch on --expect eight [--listing 10] [--joint 200] [--watch 180] [--slot 0] +// IGNEUMD and IGNEUM_MINER name the binaries (a build of the peer-directory-node branch). +// +// Leftovers of an earlier run of the same slot are stopped by PID FILE, never by name (CLAUDE.md, 6 October 2026). + +import { spawn } from 'node:child_process'; +import { mkdirSync, rmSync, writeFileSync, readFileSync, openSync, existsSync, appendFileSync } from 'node:fs'; +import { Rpc } from '../../tools/finality-attacks/lib/rpc.mjs'; + +const ROOT = new URL('../../', import.meta.url).pathname; +const FILE = `${ROOT}infra/fast-time/override-60x.json`; +const IGNEUMD = process.env.IGNEUMD || `${ROOT}vendor/igneum-node/target-integration/release/igneumd`; +const CPU_MINER = process.env.IGNEUM_MINER || `${ROOT}vendor/igneum-node/target-integration/release/igneum-miner`; +const args = process.argv.slice(2); +const flag = (name, dflt) => { const i = args.indexOf(`--${name}`); return i >= 0 ? Number(args[i + 1]) : dflt; }; +const sflag = (name, dflt = null) => { const i = args.indexOf(`--${name}`); return i >= 0 ? args[i + 1] : dflt; }; +const SWITCH = sflag('switch', 'on'); +const EXPECT = sflag('expect', SWITCH === 'on' ? 'eight' : 'one'); +const LISTING = flag('listing', 10), JOINT = flag('joint', 200), WATCH = flag('watch', 180), SLOT = flag('slot', 0); +const GENESIS_BITS = flag('genesis-bits', 0x1f010000); +const CASE = sflag('case') || `switch-${SWITCH}-expect-${EXPECT}`; +const OUT = sflag('out') || `${ROOT}docs/plans/mission-item-12-gate/peer-directory-${CASE}.json`; +// nodes at BASE + i x 10 (gRPC, p2p, JSON-RPC, EVM RPC), the fresh node last; a slot is 200 ports wide, clear of every other +// fast-time harness (the fork gate's slots end at 30930) +const BASE = 31090 + SLOT * 200, SUFFIX = 975 + SLOT; +const TMP = `/tmp/igneum-fast-time-pd${SLOT}`; +const NEVER = '18446744073709551615'; +if (!['on', 'off'].includes(SWITCH) || !['eight', 'one'].includes(EXPECT)) { console.error('usage: --switch on|off --expect eight|one'); process.exit(2); } +const started = []; +const log = (...a) => console.log(new Date().toISOString().slice(11, 23), `pd${SLOT}`, ...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); } + +const PIDS = `${TMP}/pids`; +function stopLeftovers() { + if (!existsSync(PIDS)) return; + const ports = Array.from({ length: 200 }, (_, k) => `127.0.0.1:${BASE + k}`); + for (const line of readFileSync(PIDS, 'utf8').split('\n').filter(Boolean)) { + const pid = Number(line); + let cmd = ''; + try { cmd = readFileSync(`/proc/${pid}/cmdline`, 'utf8'); } catch { continue; } + if (!cmd.includes(TMP) && !ports.some(p => cmd.includes(p))) continue; + try { process.kill(pid, 'SIGKILL'); log(`stopped leftover pid ${pid} of an earlier run`); } catch { } + } +} +stopLeftovers(); +await sleep(1000); +rmSync(TMP, { recursive: true, force: true }); mkdirSync(TMP, { recursive: true }); +const track = (proc) => { started.push(proc); try { appendFileSync(PIDS, `${proc.pid}\n`); } catch { } }; + +const baseText = readFileSync(FILE, 'utf8'); +const field = (name) => { const m = new RegExp(`"${name}":\\s*([0-9]+)`).exec(baseText); return m ? +m[1] : undefined; }; +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 DAY_MS = field('pow_day_ms'); +const override = `${TMP}/override.json`; +writeFileSync(override, mergeOverrideText(baseText, { + genesis_bits: GENESIS_BITS, skip_proof_of_work: false, + peer_directory_activation_daa: SWITCH === 'on' ? '0' : NEVER, + program_class_v3_activation_daa: NEVER, program_class_v4_activation_daa: NEVER, +})); +log(`case ${CASE}: switch ${SWITCH}, ${LISTING} listing nodes, joint ${JOINT} s, watch ${WATCH} s, expect ${EXPECT}`); + +class Node { + constructor(name, i, peers = [], outpeers = null) { + this.name = name; this.i = i; this.grpcPort = BASE + i * 10; this.p2pPort = BASE + i * 10 + 1; this.jsonPort = BASE + i * 10 + 2; this.evmPort = BASE + i * 10 + 3; + this.peers = peers; this.outpeers = outpeers; this.dir = `${TMP}/${name}`; this.logFile = `${this.dir}/node.log`; + } + get grpc() { return `grpc://127.0.0.1:${this.grpcPort}`; } + async start() { + mkdirSync(this.dir, { recursive: true }); + // the advertised address is unroutable on purpose (10.255.0.0/16 is not on any route from the box), so gossip is dead + const a = ['--devnet', `--devnet-suffix=${SUFFIX}`, '--nodnsseed', '--disable-upnp', '--nologfiles', '--enable-unsynced-mining', '--utxoindex', + `--appdir=${this.dir}`, `--rpclisten=127.0.0.1:${this.grpcPort}`, `--rpclisten-json=127.0.0.1:${this.jsonPort}`, `--evm-rpclisten=127.0.0.1:${this.evmPort}`, + `--listen=127.0.0.1:${this.p2pPort}`, `--externalip=10.255.${Math.floor(this.i / 250)}.${1 + (this.i % 250)}:${this.p2pPort}`, + `--override-params-file=${override}`, '--loglevel=info', '--yes']; + for (const p of this.peers) a.push(`--addpeer=127.0.0.1:${p}`); + if (this.outpeers != null) a.push(`--outpeers=${this.outpeers}`); else if (!this.peers.length) a.push('--outpeers=0'); + const out = openSync(this.logFile, 'a'); + this.proc = spawn(IGNEUMD, a, { stdio: ['ignore', out, out] }); + track(this.proc); + await sleep(1500); + if (this.proc.exitCode != null) throw new Error(`${this.name} exited ${this.proc.exitCode}: ${this.grepLog(/ERROR|Error|error|refused|invalid/).slice(-3).join(' | ')}`); + for (let i = 0; i < 20; i++) { + const rpc = new Rpc(`ws://127.0.0.1:${this.jsonPort}`); + try { if (await rpc.connect()) { await rpc.call('getBlockDagInfo'); this.rpc = rpc; break; } } catch { try { rpc.close(); } catch { } } + this.rpc = null; await sleep(500); + } + if (!this.rpc) throw new Error(`${this.name}: the RPC did not answer within 10 s`); + log(`${this.name} up pid ${this.proc.pid} json ${this.jsonPort} p2p ${this.p2pPort}${this.peers.length ? ` addpeer ${this.peers.join(',')}` : ''}`); + return this; + } + grepLog(re) { try { return readFileSync(this.logFile, 'utf8').split('\n').filter(l => re.test(l)); } catch { return []; } } + async peers_() { + const r = await this.rpc.call('getConnectedPeerInfo'); + // RpcPeerInfo is not camelCased in the fork (the response is): is_outbound, not isOutbound (read 14:3x UK, 7 October 2026) + const list = r.infos || r.peerInfo || r.peer_info || []; + const out = (p) => p.isOutbound ?? p.is_outbound ?? false; + return { outbound: list.filter(out).length, inbound: list.filter(p => !out(p)).length, total: list.length }; + } + async dag() { const d = await this.rpc.call('getBlockDagInfo'); return { blocks: +d.blockCount, daa: +d.virtualDaaScore }; } +} +function miner(name, node, threads, secs) { + const out = openSync(`${TMP}/${name}.log`, 'a'); + const p = spawn(CPU_MINER, ['mine', node.grpc, String(threads), String(secs), name, '--engine', 'igneum-pow', '--payout-label', name, '--status-secs', '60', '--no-vote', '--announce', `127.0.0.1:${node.p2pPort}`], + { stdio: ['ignore', out, out], env: { ...process.env, IGNEUM_POW_DAY_MS: String(DAY_MS) } }); + track(p); + return p; +} +async function stopAll() { + for (const p of started.slice().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); }); + +// the listing nodes: node 0 listens, every other dials node 0 and its predecessor (a mesh with two links each) +const nodes = [await new Node('n0', 0).start()]; +for (let i = 1; i < LISTING; i++) nodes.push(await new Node(`n${i}`, i, [nodes[0].p2pPort, nodes[i - 1].p2pPort]).start()); +await sleep(2000); +const t0 = Date.now(); +const since = () => ((Date.now() - t0) / 1000).toFixed(1); +for (const n of nodes) miner(`pd-${n.name}`, n, 1, JOINT + WATCH + 120); +await sleep(JOINT * 1000); +const dag0 = await nodes[0].dag(); +const listed = nodes[0].grepLog(/Peer directory: key .* lists/).length; +log(`phase 1 done at ${since()} s: node 0 at ${dag0.blocks} blocks DAA ${dag0.daa}; ${listed} directory listing line(s) on node 0`); + +// the fresh node: one genesis peer, 8 wanted +const fresh = await new Node('fresh', LISTING, [nodes[0].p2pPort], 8).start(); +const samples = []; +let eightAt = null; +const watchAt = Date.now(); +while (Date.now() - watchAt < WATCH * 1000) { + await sleep(5000); + const p = await fresh.peers_(); + const d = await fresh.dag(); + const drawn = fresh.grepLog(/drawn from the peer directory/).length; + const connectedFromDirectory = fresh.grepLog(/connected to .* from the peer directory/).length; + samples.push({ t: +since(), outbound: p.outbound, inbound: p.inbound, blocks: d.blocks, daa: d.daa, drawn, connected_from_directory: connectedFromDirectory }); + if (p.outbound >= 8 && eightAt == null) { eightAt = +since(); log(`the fresh node holds ${p.outbound} outbound peers at ${since()} s (${connectedFromDirectory} from the directory)`); } + if (samples.length % 6 === 0) log(`t=${since()} s fresh: ${p.outbound} outbound, ${p.inbound} inbound, ${d.blocks} blocks, ${drawn} draw line(s), ${connectedFromDirectory} directory connection(s)`); +} +const last = samples.at(-1); +const checks = { + listing_lines_on_node_0: listed >= LISTING - 1, + fresh_synced: last.blocks > dag0.blocks / 2, + fresh_reached_eight_outbound: eightAt != null, + fresh_connected_from_directory: last.connected_from_directory >= 7, + fresh_held_one_outbound: samples.every(s => s.outbound <= 1), + no_directory_line_on_fresh: last.drawn === 0 && last.connected_from_directory === 0, +}; +const good = EXPECT === 'eight' + ? checks.listing_lines_on_node_0 && checks.fresh_synced && checks.fresh_reached_eight_outbound && checks.fresh_connected_from_directory + : checks.fresh_synced && checks.fresh_held_one_outbound && checks.no_directory_line_on_fresh; +const needed = EXPECT === 'eight' ? ['listing_lines_on_node_0', 'fresh_synced', 'fresh_reached_eight_outbound', 'fresh_connected_from_directory'] : ['fresh_synced', 'fresh_held_one_outbound', 'no_directory_line_on_fresh']; +const fails = needed.filter(k => !checks[k]); +const summary = { pass: good, expect: EXPECT, case: CASE, switch: SWITCH, listing: LISTING, joint: JOINT, watch: WATCH, node0: dag0, listing_lines_on_node_0: listed, eight_outbound_at_s: eightAt, last, checks, failed_checks: fails, samples, node: IGNEUMD, miner: CPU_MINER, slot: SLOT, ports: { base: BASE, suffix: SUFFIX } }; +mkdirSync(OUT.replace(/\/[^/]+$/, ''), { recursive: true }); +writeFileSync(OUT, JSON.stringify(summary, null, 2)); +log(`SUMMARY ${good ? 'PASS' : 'FAIL'} (${CASE}): node 0 logged ${listed} listings; the fresh node ${eightAt == null ? `never reached 8 outbound (last ${last.outbound})` : `reached 8 outbound at ${eightAt} s`}, ${last.connected_from_directory} connection(s) from the directory, ${last.drawn} draw line(s), ${last.blocks} blocks${fails.length ? `; FAILED CHECK ${fails.join(', ')}` : ''}`); +log(`summary: ${OUT}`); +await stopAll(); +process.exit(good ? 0 : 1); diff --git a/sim/peer-directory/eclipse.py b/sim/peer-directory/eclipse.py new file mode 100644 index 000000000..7e555e6e1 --- /dev/null +++ b/sim/peer-directory/eclipse.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Mission item 12, the eclipse bound of the weight-backed peer directory (docs/analysis/mission/invent.md 7.1, model E). + +A fresh node draws its outbound peers from the directory WITHOUT replacement, each draw proportional to the listing +key's weight at the latest checkpoint (the fork's `FinalityManager::draw_peers`). An attacker holding share `a` of the +weight lists its addresses under `m` keys of equal weight (each above dust: the dust threshold is what bounds `m`, 100 +blocks a window on mainnet). The node is eclipsed when every one of its `n` outbound peers is the attacker's. + +Model E's closed form is a^n (with replacement); without replacement and with few attacker keys the odds are lower, and +with fewer than n attacker keys an eclipse is impossible. Honest keys: `h` keys with Zipf weights (a few large miners, +a long tail), the shape of a public chain's weight table. + + python3 sim/peer-directory/eclipse.py [--starts 1000] [--share 0.34] [--peers 8,16] [--attacker-keys 8,64,1000] + [--honest-keys 200] [--seed 7] +""" +import argparse +import random + + +def draw(pool, n, rng): + """Weighted draw without replacement: pool is a list of (is_attacker, weight).""" + pool = list(pool) + out = [] + while len(out) < n and pool: + total = sum(w for _, w in pool) + r = rng.uniform(0, total) + i = 0 + while i + 1 < len(pool) and r >= pool[i][1]: + r -= pool[i][1] + i += 1 + out.append(pool.pop(i)[0]) + return out + + +def run(starts, share, peers, attacker_keys, honest_keys, rng): + honest_total = 1.0 - share + zipf = [1.0 / (k + 1) for k in range(honest_keys)] + z = sum(zipf) + honest = [(False, honest_total * w / z) for w in zipf] + attacker = [(True, share / attacker_keys)] * attacker_keys + pool = honest + attacker + eclipsed = 0 + majority = 0 + for _ in range(starts): + got = draw(pool, peers, rng) + k = sum(got) + if k == len(got): + eclipsed += 1 + if k * 2 > len(got): + majority += 1 + return eclipsed, majority + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--starts", type=int, default=1000) + ap.add_argument("--share", type=float, default=0.34) + ap.add_argument("--peers", default="8,16") + ap.add_argument("--attacker-keys", default="8,64,1000") + ap.add_argument("--honest-keys", type=int, default=200) + ap.add_argument("--seed", type=int, default=7) + a = ap.parse_args() + rng = random.Random(a.seed) + print(f"starts {a.starts}, attacker share {a.share:.2f} of the weight, {a.honest_keys} honest keys (Zipf), seed {a.seed}") + print(f"{'peers':>5} {'attacker keys':>13} {'eclipsed':>9} {'rate':>10} {'a^n (model E)':>14} {'attacker majority':>17}") + for n in (int(x) for x in a.peers.split(",")): + for m in (int(x) for x in a.attacker_keys.split(",")): + e, maj = run(a.starts, a.share, n, m, a.honest_keys, rng) + print(f"{n:>5} {m:>13} {e:>9} {e / a.starts:>10.2e} {a.share ** n:>14.2e} {maj / a.starts:>17.3f}") + + +if __name__ == "__main__": + main()