fork_prefork_sync_test.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2026 The Limenka developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """Pre-fork block sync through the mempool bridge.
6
7 The fork chain has its own P2P magic, so a fresh fork node cannot
8 download the pre-fork (mainnet) history from spamchain peers through
9 normal net_processing. While the fork tip is below the activation MTP,
10 the mempool bridge's foreign peers serve that history: headers are
11 validated with ProcessNewBlockHeaders and blocks with ProcessNewBlock.
12
13 This test keeps the fork inactive (activation far in the future), lets
14 node A mine a mainnet-style chain, and verifies node B - whose ONLY link
15 to A is the bridge - syncs the whole chain through it.
16 """
17 import hashlib
18
19 from test_framework.address import byte_to_base58, hash160
20 from test_framework.descriptors import descsum_create
21 from test_framework.key import ECKey
22 from test_framework.segwit_addr import encode_segwit_address
23 from test_framework.test_framework import LimenkaTestFramework
24 from test_framework.util import p2p_port
25
26 GENESIS_TIME = 1231006505
27 # Far-future activation MTP: the fork never activates in this test, so all
28 # blocks are pre-fork (mainnet-rule) blocks.
29 ACTIVATION_FAR = 1999999999
30
31
32 class ForkPreforkSyncTest(LimenkaTestFramework):
33 def set_test_params(self):
34 self.setup_clean_chain = True
35 self.num_nodes = 2
36 self.chain = 'limenka'
37 self.extra_args = [[
38 '-forkactivationtime=%d' % ACTIVATION_FAR,
39 '-forkdelaysteps=1024',
40 '-forkmineondemand',
41 '-forkstandalone',
42 '-fallbackfee=0.0002',
43 '-addresstype=bech32',
44 ], [
45 '-forkactivationtime=%d' % ACTIVATION_FAR,
46 '-forkdelaysteps=1024',
47 '-forkmineondemand',
48 '-forkstandalone',
49 '-fallbackfee=0.0002',
50 '-addresstype=bech32',
51 '-mempoolbridge',
52 # Node B's bridge connects to node A (fork network).
53 f'-bridgepeer=fork:127.0.0.1:{p2p_port(0)}',
54 ]]
55 self.rpc_timeout = 240
56
57 def skip_test_if_missing_module(self):
58 self.skip_if_no_wallet()
59
60 def add_options(self, parser):
61 self.add_wallet_options(parser, descriptors=True, legacy=False)
62
63 def init_wallet(self, *, node):
64 wallet_name = self.default_wallet_name if self.wallet_names is None else self.wallet_names[node] if node < len(self.wallet_names) else False
65 if wallet_name is not False:
66 n = self.nodes[node]
67 if wallet_name is not None:
68 n.createwallet(wallet_name=wallet_name, descriptors=True, load_on_startup=True)
69 if node == 0:
70 seed = hashlib.sha256(b'prefork-sync-funding').digest()
71 key = ECKey()
72 key.set(seed, compressed=True)
73 priv = key.get_bytes()
74 wif = byte_to_base58(priv + b'\x01', 128)
75 desc = descsum_create(f"wpkh({wif})")
76 import_res = n.importdescriptors([{
77 "desc": desc,
78 "timestamp": GENESIS_TIME + 600,
79 "active": False,
80 "internal": False,
81 }])
82 assert all(r['success'] for r in import_res)
83 self.funding_key = priv
84 self.funding_pub = key.get_pubkey().get_bytes()
85
86 def setup_network(self):
87 # Do NOT auto-connect the nodes: the bridge must be the only link
88 # between A and B.
89 self.setup_nodes()
90
91 def run_test(self):
92 node_a, node_b = self.nodes[0], self.nodes[1]
93
94 # A mines a mainnet-style (pre-fork) chain to a fixed bech32 addr.
95 p2wpkh_program = hash160(self.funding_pub)
96 addr = encode_segwit_address("bf", 0, p2wpkh_program)
97 self.generatetoaddress(node_a, 100, addr, sync_fun=self.no_op)
98 assert node_a.getblockcount() == 100
99
100 # B's bridge connects to A and downloads the whole chain.
101 self.wait_until(
102 lambda: node_b.getblockcount() == 100,
103 timeout=240,
104 )
105 info_b = node_b.getblockchaininfo()
106 assert info_b['blocks'] == 100, info_b
107 assert info_b['headers'] == 100, info_b
108
109 # The blocks are consensus-valid on B (same chain hashes).
110 assert node_b.getblockhash(99) == node_a.getblockhash(99)
111
112 self.log.info("prefork block sync complete: B synced A's chain via the bridge")
113
114
115 if __name__ == '__main__':
116 ForkPreforkSyncTest(__file__).main()
117