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