#!/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 stops syncing from a spamchain parent peer at the fork point. Once the hard-coded parent-chain fork-point height (nForkActivationHeight) is reached, the parent chain is no longer authoritative: parent peers stop serving blocks (they can no longer satisfy fork consensus past that height) and the fork node stops downloading from them. The parent connection is kept alive for the shared mempool. Node A impersonates a spamchain node (speaks mainnet magic) and mines a mainnet-style chain the whole time. Node B has -forkactivationheight=50, so it accepts A's blocks up to height 50 as pre-fork history and must stop there - even though A keeps mining to height 100. """ 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' FORK_HEIGHT = 50 class ForkParentHeightTest(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}', ], [ # B's fork is permanently active at height FORK_HEIGHT. '-forkactivationtime=%d' % ACTIVATION_FAR, '-forkactivationheight=%d' % FORK_HEIGHT, '-forkdelaysteps=1024', '-forkmineondemand', '-forkstandalone', '-fallbackfee=0.0002', '-addresstype=bech32', 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-height-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): self.setup_nodes() def run_test(self): node_a, node_b = self.nodes[0], self.nodes[1] 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 must stop exactly at the fork-point height, never past it. self.wait_until( lambda: node_b.getblockcount() == FORK_HEIGHT, timeout=240, ) info_b = node_b.getblockchaininfo() assert info_b['blocks'] == FORK_HEIGHT, info_b assert info_b['headers'] >= FORK_HEIGHT, info_b # The pre-fork chain matches A exactly up to the fork point. assert node_b.getblockhash(FORK_HEIGHT) == node_a.getblockhash(FORK_HEIGHT) # B's chain hash at the fork point is a pre-fork (parent-rule) block. assert info_b['bestblockhash'] == node_a.getblockhash(FORK_HEIGHT) # The parent connection stays up (kept for the shared mempool). 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 sync disengaged at fork height %d (A at %d)" % (FORK_HEIGHT, node_a.getblockcount())) if __name__ == '__main__': ForkParentHeightTest(__file__).main()