fork_wallet_locktime_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-chain wallet transactions use locktime 0.
   6  
   7  The fork chain shares its transaction set with the parent (spamchain)
   8  chain, but the two chains diverge in height after the fork point.  A
   9  height-based locktime (the wallet's fee-sniping mitigation) from the
  10  taller fork chain would make a transaction non-final - and thus
  11  mempool-rejected - on the shorter parent chain, breaking cross-chain
  12  propagation.  The fork-chain wallet therefore skips the fee-sniping
  13  locktime: every wallet transaction is final (locktime 0) and valid on
  14  both diverging chains.  Other chains keep the standard behavior.
  15  """
  16  from test_framework.blocktools import COINBASE_MATURITY
  17  from test_framework.test_framework import LimenkaTestFramework
  18  
  19  
  20  class ForkWalletLocktimeTest(LimenkaTestFramework):
  21      def set_test_params(self):
  22          self.setup_clean_chain = True
  23          self.num_nodes = 1
  24          self.chain = 'limenka'
  25          self.extra_args = [[
  26              '-forkactivationheight=50',
  27              '-forkdelaysteps=1024',
  28              '-forkmineondemand',
  29              '-forkstandalone',
  30              '-fallbackfee=0.0002',
  31              '-addresstype=bech32',
  32          ]]
  33          self.rpc_timeout = 240
  34  
  35      def skip_test_if_missing_module(self):
  36          self.skip_if_no_wallet()
  37  
  38      def add_options(self, parser):
  39          self.add_wallet_options(parser, descriptors=True, legacy=False)
  40  
  41      def init_wallet(self, *, node):
  42          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
  43          if wallet_name is not False:
  44              n = self.nodes[node]
  45              if wallet_name is not None:
  46                  n.createwallet(wallet_name=wallet_name, descriptors=True, load_on_startup=True)
  47                  import hashlib
  48                  from test_framework.address import byte_to_base58
  49                  from test_framework.descriptors import descsum_create
  50                  from test_framework.key import ECKey
  51                  key = ECKey()
  52                  key.set(hashlib.sha256(b'fork-wallet-locktime').digest(), compressed=True)
  53                  priv = key.get_bytes()
  54                  wif = byte_to_base58(priv + b'\x01', 128)
  55                  desc = descsum_create(f"wpkh({wif})")
  56                  import_res = n.importdescriptors([{
  57                      "desc": desc,
  58                      "timestamp": 1231006506,
  59                      "active": False,
  60                      "internal": False,
  61                  }])
  62                  assert all(r['success'] for r in import_res)
  63  
  64      def run_test(self):
  65          node = self.nodes[0]
  66          for i in range(COINBASE_MATURITY + 10):
  67              node.setmocktime(1700000000 + i * 600)
  68              self.generatetoaddress(node, 1, node.getnewaddress(), sync_fun=self.no_op)
  69  
  70          # A fork-chain wallet tx must be final (locktime 0), so it is valid
  71          # on the shorter parent chain too.
  72          txid = node.sendtoaddress(node.getnewaddress(), 1)
  73          raw = node.getrawtransaction(txid)
  74          decoded = node.decoderawtransaction(raw)
  75          assert decoded['locktime'] == 0, decoded['locktime']
  76          self.log.info("fork wallet tx locktime == 0 (final on both chains)")
  77  
  78  
  79  if __name__ == '__main__':
  80      ForkWalletLocktimeTest(__file__).main()
  81