fork_parent_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 """Fork node pre-fork IBD from a spamchain parent peer over normal P2P.
6
7 The fork chain has its own P2P magic, but a fork node may also hold
8 connections to spamchain (parent chain) peers that speak the parent
9 network's magic (-parentpeer). While the fork tip is below the fork
10 point those parent peers are ordinary download peers: headers via
11 ProcessNewBlockHeaders, blocks via ProcessNewBlock. This test keeps the
12 fork inactive on both sides, lets node A (speaking mainnet magic via
13 -p2pmagic) mine a mainnet-style chain, and verifies node B - connected to
14 A only through -parentpeer - syncs the whole chain.
15 """
16 import hashlib
17
18 from test_framework.address import byte_to_base58, hash160
19 from test_framework.descriptors import descsum_create
20 from test_framework.key import ECKey
21 from test_framework.segwit_addr import encode_segwit_address
22 from test_framework.test_framework import LimenkaTestFramework
23 from test_framework.util import p2p_port
24
25 GENESIS_TIME = 1231006505
26 ACTIVATION_FAR = 1999999999
27 MAINNET_MAGIC = 'f9beb4d9'
28
29
30 class ForkParentSyncTest(LimenkaTestFramework):
31 def set_test_params(self):
32 self.setup_clean_chain = True
33 self.num_nodes = 2
34 self.chain = 'limenka'
35 self.extra_args = [[
36 '-forkactivationtime=%d' % ACTIVATION_FAR,
37 '-forkdelaysteps=1024',
38 '-forkmineondemand',
39 '-forkstandalone',
40 '-fallbackfee=0.0002',
41 '-addresstype=bech32',
42 # A impersonates a spamchain node (speaks mainnet magic).
43 f'-p2pmagic={MAINNET_MAGIC}',
44 ], [
45 '-forkactivationtime=%d' % ACTIVATION_FAR,
46 '-forkdelaysteps=1024',
47 '-forkmineondemand',
48 '-forkstandalone',
49 '-fallbackfee=0.0002',
50 '-addresstype=bech32',
51 # B connects to A as a spamchain parent peer.
52 f'-parentpeer=127.0.0.1:{p2p_port(0)}',
53 ]]
54 self.rpc_timeout = 240
55
56 def skip_test_if_missing_module(self):
57 self.skip_if_no_wallet()
58
59 def add_options(self, parser):
60 self.add_wallet_options(parser, descriptors=True, legacy=False)
61
62 def init_wallet(self, *, node):
63 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
64 if wallet_name is not False:
65 n = self.nodes[node]
66 if wallet_name is not None and node == 0:
67 n.createwallet(wallet_name=wallet_name, descriptors=True, load_on_startup=True)
68 seed = hashlib.sha256(b'fork-parent-sync-funding').digest()
69 key = ECKey()
70 key.set(seed, compressed=True)
71 priv = key.get_bytes()
72 wif = byte_to_base58(priv + b'\x01', 128)
73 desc = descsum_create(f"wpkh({wif})")
74 import_res = n.importdescriptors([{
75 "desc": desc,
76 "timestamp": GENESIS_TIME + 600,
77 "active": False,
78 "internal": False,
79 }])
80 assert all(r['success'] for r in import_res)
81 self.funding_pub = key.get_pubkey().get_bytes()
82
83 def setup_network(self):
84 # No auto-connect: B's only link to A is the parent connection.
85 self.setup_nodes()
86
87 def run_test(self):
88 node_a, node_b = self.nodes[0], self.nodes[1]
89
90 # A mines a mainnet-style (pre-fork) chain to a fixed bech32 addr.
91 p2wpkh_program = hash160(self.funding_pub)
92 addr = encode_segwit_address("bf", 0, p2wpkh_program)
93 self.generatetoaddress(node_a, 100, addr, sync_fun=self.no_op)
94 assert node_a.getblockcount() == 100
95
96 # B's parent peer downloads the whole chain through normal P2P.
97 self.wait_until(
98 lambda: node_b.getblockcount() == 100,
99 timeout=240,
100 )
101 info_b = node_b.getblockchaininfo()
102 assert info_b['blocks'] == 100, info_b
103 assert node_b.getblockhash(99) == node_a.getblockhash(99)
104
105 # The parent peer is connected and classified as a parent peer.
106 peers = node_b.getpeerinfo()
107 assert any(p['addr'] == f"127.0.0.1:{p2p_port(0)}" for p in peers), peers
108
109 self.log.info("parent peer sync complete: B synced A's chain via -parentpeer")
110
111
112 if __name__ == '__main__':
113 ForkParentSyncTest(__file__).main()
114