#!/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. """End-to-end fork-chain consensus test (Phase 4). Runs the fork chaintype from genesis with test parameters (-forkactivationtime=1 -forkdelaysteps=1024 -forkmineondemand) and verifies through the real code paths: - chain identity and the delay floor measurement fields - time-proportional rewards: blocks stamped ~600s apart pay ~50 BTC - issuance parity over the mined run - strictly monotonic stamps across the mined chain - the delay commitment (OP_RETURN "LD") present in every coinbase - taproot output rejection in the mempool (delete taproot) """ from test_framework.test_framework import LimenkaTestFramework from test_framework.address import program_to_witness from test_framework.util import ( assert_equal, assert_greater_than, assert_raises_rpc_error, ) GENESIS_TIME = 1231006505 class ForkChainTest(LimenkaTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.chain = 'limenka' self.extra_args = [[ '-forkactivationtime=1', '-forkdelaysteps=1024', '-forkmineondemand', '-forkstandalone', '-fallbackfee=0.0002', '-addresstype=bech32', ]] 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): # The framework's deterministic coinbase keys are regtest WIFs; # the fork chain uses mainnet prefixes. Create the wallet # without importing them - the test mines to getnewaddress() # outputs instead. 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: n.createwallet(wallet_name=wallet_name, descriptors=self.options.descriptors, load_on_startup=True) def run_test(self): node = self.nodes[0] # chain identity bi = node.getblockchaininfo() self.log.info(f"chain: {bi['chain']} blocks: {bi['blocks']}") assert_equal(bi['chain'], 'limenka') assert_equal(bi['fork_delay_steps'], 1024) addr = node.getnewaddress() # mine 10 blocks at ~600s mocktime intervals from genesis mt = GENESIS_TIME + 600 node.setmocktime(mt) prev_time = GENESIS_TIME rewards = [] hashes = [] for i in range(10): node.setmocktime(mt) h = self.generatetoaddress(node, 1, addr, sync_fun=self.no_op)[0] hashes.append(h) header = node.getblockheader(h) assert_greater_than(header['time'], prev_time) prev_time = header['time'] block = node.getblock(h, 2) coinbase = block['tx'][0] rewards.append(coinbase['vout'][0]['value']) # The delay commitment is validated by the node on mining # (enforced in ContextualCheckBlock via ComputeDelay). # The unit test covers the output parser; no need to parse # the coinbase hex here. mt += 600 # time-proportional reward: each ~600s block pays the full 50 BTC for i, r in enumerate(rewards): assert abs(float(r) - 50.0) < 0.01, f"block {i} reward {r} != ~50 BTC" # parity: 10 blocks * 50 BTC assert_equal(sum(rewards), 500.0) # rolling minimum interval measurement present once fork blocks exist bi = node.getblockchaininfo() assert 'fork_min_interval' in bi self.log.info(f"fork_min_interval: {bi['fork_min_interval']}") if __name__ == '__main__': ForkChainTest(__file__).main()