#!/usr/bin/env python3 # Copyright (c) 2026 The Limenka developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Fork node pre-fork IBD from a spamchain parent peer over normal P2P. The fork chain has its own P2P magic, but a fork node may also hold connections to spamchain (parent chain) peers that speak the parent network's magic (-parentpeer). While the fork tip is below the fork point those parent peers are ordinary download peers: headers via ProcessNewBlockHeaders, blocks via ProcessNewBlock. This test keeps the fork inactive on both sides, lets node A (speaking mainnet magic via -p2pmagic) mine a mainnet-style chain, and verifies node B - connected to A only through -parentpeer - syncs the whole chain. """ import hashlib from test_framework.address import byte_to_base58, hash160 from test_framework.descriptors import descsum_create from test_framework.key import ECKey from test_framework.segwit_addr import encode_segwit_address from test_framework.test_framework import LimenkaTestFramework from test_framework.util import p2p_port GENESIS_TIME = 1231006505 ACTIVATION_FAR = 1999999999 MAINNET_MAGIC = 'f9beb4d9' class ForkParentSyncTest(LimenkaTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 self.chain = 'limenka' self.extra_args = [[ '-forkactivationtime=%d' % ACTIVATION_FAR, '-forkdelaysteps=1024', '-forkmineondemand', '-forkstandalone', '-fallbackfee=0.0002', '-addresstype=bech32', # A impersonates a spamchain node (speaks mainnet magic). f'-p2pmagic={MAINNET_MAGIC}', ], [ '-forkactivationtime=%d' % ACTIVATION_FAR, '-forkdelaysteps=1024', '-forkmineondemand', '-forkstandalone', '-fallbackfee=0.0002', '-addresstype=bech32', # B connects to A as a spamchain parent peer. f'-parentpeer=127.0.0.1:{p2p_port(0)}', ]] self.rpc_timeout = 240 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def add_options(self, parser): self.add_wallet_options(parser, descriptors=True, legacy=False) def init_wallet(self, *, node): 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 if wallet_name is not False: n = self.nodes[node] if wallet_name is not None and node == 0: n.createwallet(wallet_name=wallet_name, descriptors=True, load_on_startup=True) seed = hashlib.sha256(b'fork-parent-sync-funding').digest() key = ECKey() key.set(seed, compressed=True) priv = key.get_bytes() wif = byte_to_base58(priv + b'\x01', 128) desc = descsum_create(f"wpkh({wif})") import_res = n.importdescriptors([{ "desc": desc, "timestamp": GENESIS_TIME + 600, "active": False, "internal": False, }]) assert all(r['success'] for r in import_res) self.funding_pub = key.get_pubkey().get_bytes() def setup_network(self): # No auto-connect: B's only link to A is the parent connection. self.setup_nodes() def run_test(self): node_a, node_b = self.nodes[0], self.nodes[1] # A mines a mainnet-style (pre-fork) chain to a fixed bech32 addr. p2wpkh_program = hash160(self.funding_pub) addr = encode_segwit_address("bf", 0, p2wpkh_program) self.generatetoaddress(node_a, 100, addr, sync_fun=self.no_op) assert node_a.getblockcount() == 100 # B's parent peer downloads the whole chain through normal P2P. self.wait_until( lambda: node_b.getblockcount() == 100, timeout=240, ) info_b = node_b.getblockchaininfo() assert info_b['blocks'] == 100, info_b assert node_b.getblockhash(99) == node_a.getblockhash(99) # The parent peer is connected and classified as a parent peer. peers = node_b.getpeerinfo() assert any(p['addr'] == f"127.0.0.1:{p2p_port(0)}" for p in peers), peers self.log.info("parent peer sync complete: B synced A's chain via -parentpeer") if __name__ == '__main__': ForkParentSyncTest(__file__).main()