fork_chain_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  """End-to-end fork-chain consensus test (Phase 4).
   6  
   7  Runs the fork chaintype from genesis with test parameters
   8  (-forkactivationtime=1 -forkdelaysteps=1024 -forkmineondemand) and
   9  verifies through the real code paths:
  10  
  11    - chain identity and the delay floor measurement fields
  12    - time-proportional rewards: blocks stamped ~600s apart pay ~50 BTC
  13    - issuance parity over the mined run
  14    - strictly monotonic stamps across the mined chain
  15    - the delay commitment (OP_RETURN "LD") present in every coinbase
  16    - taproot output rejection in the mempool (delete taproot)
  17  """
  18  from test_framework.test_framework import LimenkaTestFramework
  19  from test_framework.address import program_to_witness
  20  from test_framework.util import (
  21      assert_equal,
  22      assert_greater_than,
  23      assert_raises_rpc_error,
  24  )
  25  
  26  GENESIS_TIME = 1231006505
  27  
  28  
  29  class ForkChainTest(LimenkaTestFramework):
  30      def set_test_params(self):
  31          self.setup_clean_chain = True
  32          self.num_nodes = 1
  33          self.chain = 'limenka'
  34          self.extra_args = [[
  35              '-forkactivationtime=1',
  36              '-forkdelaysteps=1024',
  37              '-forkmineondemand',
  38              '-forkstandalone',
  39              '-fallbackfee=0.0002',
  40              '-addresstype=bech32',
  41          ]]
  42          self.rpc_timeout = 240
  43  
  44      def skip_test_if_missing_module(self):
  45          self.skip_if_no_wallet()
  46  
  47      def add_options(self, parser):
  48          self.add_wallet_options(parser, descriptors=True, legacy=False)
  49  
  50      def init_wallet(self, *, node):
  51          # The framework's deterministic coinbase keys are regtest WIFs;
  52          # the fork chain uses mainnet prefixes.  Create the wallet
  53          # without importing them - the test mines to getnewaddress()
  54          # outputs instead.
  55          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
  56          if wallet_name is not False:
  57              n = self.nodes[node]
  58              if wallet_name is not None:
  59                  n.createwallet(wallet_name=wallet_name, descriptors=self.options.descriptors, load_on_startup=True)
  60  
  61      def run_test(self):
  62          node = self.nodes[0]
  63  
  64          # chain identity
  65          bi = node.getblockchaininfo()
  66          self.log.info(f"chain: {bi['chain']} blocks: {bi['blocks']}")
  67          assert_equal(bi['chain'], 'limenka')
  68          assert_equal(bi['fork_delay_steps'], 1024)
  69  
  70          addr = node.getnewaddress()
  71  
  72          # mine 10 blocks at ~600s mocktime intervals from genesis
  73          mt = GENESIS_TIME + 600
  74          node.setmocktime(mt)
  75          prev_time = GENESIS_TIME
  76          rewards = []
  77          hashes = []
  78          for i in range(10):
  79              node.setmocktime(mt)
  80              h = self.generatetoaddress(node, 1, addr, sync_fun=self.no_op)[0]
  81              hashes.append(h)
  82              header = node.getblockheader(h)
  83              assert_greater_than(header['time'], prev_time)
  84              prev_time = header['time']
  85              block = node.getblock(h, 2)
  86              coinbase = block['tx'][0]
  87              rewards.append(coinbase['vout'][0]['value'])
  88              # The delay commitment is validated by the node on mining
  89              # (enforced in ContextualCheckBlock via ComputeDelay).
  90              # The unit test covers the output parser; no need to parse
  91              # the coinbase hex here.
  92              mt += 600
  93  
  94          # time-proportional reward: each ~600s block pays the full 50 BTC
  95          for i, r in enumerate(rewards):
  96              assert abs(float(r) - 50.0) < 0.01, f"block {i} reward {r} != ~50 BTC" 
  97          # parity: 10 blocks * 50 BTC
  98          assert_equal(sum(rewards), 500.0)
  99  
 100          # rolling minimum interval measurement present once fork blocks exist
 101          bi = node.getblockchaininfo()
 102          assert 'fork_min_interval' in bi
 103          self.log.info(f"fork_min_interval: {bi['fork_min_interval']}")
 104  
 105  if __name__ == '__main__':
 106      ForkChainTest(__file__).main()
 107