Skip to content

Commit c4d76c6

Browse files
committed
tests: Tests for inactive HD chains
test cases are added for inactive HD chains: a basic case, a case where the wallet is encrypted, and a case for the 21605 segfault.
1 parent 8077862 commit c4d76c6

File tree

3 files changed

+151
-0
lines changed

3 files changed

+151
-0
lines changed

test/functional/test_framework/test_node.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,9 @@ def __init__(self, rpc, cli=False, descriptors=False):
698698
def __getattr__(self, name):
699699
return getattr(self.rpc, name)
700700

701+
def createwallet_passthrough(self, *args, **kwargs):
702+
return self.__getattr__("createwallet")(*args, **kwargs)
703+
701704
def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase='', avoid_reuse=None, descriptors=None, load_on_startup=None, external_signer=None):
702705
if descriptors is None:
703706
descriptors = self.descriptors

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@
279279
'wallet_send.py --descriptors',
280280
'wallet_create_tx.py --descriptors',
281281
'wallet_taproot.py',
282+
'wallet_inactive_hdchains.py',
282283
'p2p_fingerprint.py',
283284
'feature_uacomment.py',
284285
'feature_init.py',
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2021 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""
6+
Test Inactive HD Chains.
7+
"""
8+
import os
9+
import shutil
10+
import time
11+
12+
from test_framework.authproxy import JSONRPCException
13+
from test_framework.test_framework import BitcoinTestFramework
14+
from test_framework.wallet_util import (
15+
get_generate_key,
16+
)
17+
18+
19+
class InactiveHDChainsTest(BitcoinTestFramework):
20+
def set_test_params(self):
21+
self.setup_clean_chain = True
22+
self.num_nodes = 2
23+
self.extra_args = [["-keypool=10"], ["-nowallet", "-keypool=10"]]
24+
25+
def skip_test_if_missing_module(self):
26+
self.skip_if_no_wallet()
27+
self.skip_if_no_bdb()
28+
self.skip_if_no_previous_releases()
29+
30+
def setup_nodes(self):
31+
self.add_nodes(self.num_nodes, extra_args=self.extra_args, versions=[
32+
None,
33+
170200, # 0.17.2 Does not have the key metadata upgrade
34+
])
35+
36+
self.start_nodes()
37+
self.init_wallet(node=0)
38+
39+
def prepare_wallets(self, wallet_basename, encrypt=False):
40+
self.nodes[0].createwallet(wallet_name=f"{wallet_basename}_base", descriptors=False, blank=True)
41+
self.nodes[0].createwallet(wallet_name=f"{wallet_basename}_test", descriptors=False, blank=True)
42+
base_wallet = self.nodes[0].get_wallet_rpc(f"{wallet_basename}_base")
43+
test_wallet = self.nodes[0].get_wallet_rpc(f"{wallet_basename}_test")
44+
45+
# Setup both wallets with the same HD seed
46+
seed = get_generate_key()
47+
base_wallet.sethdseed(True, seed.privkey)
48+
test_wallet.sethdseed(True, seed.privkey)
49+
50+
if encrypt:
51+
# Encrypting will generate a new HD seed and flush the keypool
52+
test_wallet.encryptwallet("pass")
53+
else:
54+
# Generate a new HD seed on the test wallet
55+
test_wallet.sethdseed()
56+
57+
return base_wallet, test_wallet
58+
59+
def do_inactive_test(self, base_wallet, test_wallet, encrypt=False):
60+
default = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
61+
62+
# The first address should be known by both wallets.
63+
addr1 = base_wallet.getnewaddress()
64+
assert test_wallet.getaddressinfo(addr1)["ismine"]
65+
# The address at index 9 is the first address that the test wallet will not know initially
66+
for _ in range(0, 9):
67+
base_wallet.getnewaddress()
68+
addr2 = base_wallet.getnewaddress()
69+
assert not test_wallet.getaddressinfo(addr2)["ismine"]
70+
71+
# Send to first address on the old seed
72+
txid = default.sendtoaddress(addr1, 10)
73+
self.generate(self.nodes[0], 1)
74+
75+
# Wait for the test wallet to see the transaction
76+
while True:
77+
try:
78+
test_wallet.gettransaction(txid)
79+
break
80+
except JSONRPCException:
81+
time.sleep(0.1)
82+
83+
if encrypt:
84+
# The test wallet will not be able to generate the topped up keypool
85+
# until it is unlocked. So it still should not know about the second address
86+
assert not test_wallet.getaddressinfo(addr2)["ismine"]
87+
test_wallet.walletpassphrase("pass", 1)
88+
89+
# The test wallet should now know about the second address as it
90+
# should have generated it in the inactive chain's keypool
91+
assert test_wallet.getaddressinfo(addr2)["ismine"]
92+
93+
# Send to second address on the old seed
94+
txid = default.sendtoaddress(addr2, 10)
95+
self.generate(self.nodes[0], 1)
96+
test_wallet.gettransaction(txid)
97+
98+
def test_basic(self):
99+
self.log.info("Test basic case for inactive HD chains")
100+
self.do_inactive_test(*self.prepare_wallets("basic"))
101+
102+
def test_encrypted_wallet(self):
103+
self.log.info("Test inactive HD chains when wallet is encrypted")
104+
self.do_inactive_test(*self.prepare_wallets("enc", encrypt=True), encrypt=True)
105+
106+
def test_without_upgraded_keymeta(self):
107+
# Test that it is possible to top up inactive hd chains even if there is no key origin
108+
# in CKeyMetadata. This tests for the segfault reported in
109+
# https://github.com/bitcoin/bitcoin/issues/21605
110+
self.log.info("Test that topping up inactive HD chains does not need upgraded key origin")
111+
112+
self.nodes[0].createwallet(wallet_name="keymeta_base", descriptors=False, blank=True)
113+
# Createwallet is overridden in the test framework so that the descriptor option can be filled
114+
# depending on the test's cli args. However we don't want to do that when using old nodes that
115+
# do not support descriptors. So we use the createwallet_passthrough function.
116+
self.nodes[1].createwallet_passthrough(wallet_name="keymeta_test")
117+
base_wallet = self.nodes[0].get_wallet_rpc("keymeta_base")
118+
test_wallet = self.nodes[1].get_wallet_rpc("keymeta_test")
119+
120+
# Setup both wallets with the same HD seed
121+
seed = get_generate_key()
122+
base_wallet.sethdseed(True, seed.privkey)
123+
test_wallet.sethdseed(True, seed.privkey)
124+
125+
# Encrypting will generate a new HD seed and flush the keypool
126+
test_wallet.encryptwallet("pass")
127+
128+
# Copy test wallet to node 0
129+
test_wallet.unloadwallet()
130+
test_wallet_dir = os.path.join(self.nodes[1].datadir, "regtest/wallets/keymeta_test")
131+
new_test_wallet_dir = os.path.join(self.nodes[0].datadir, "regtest/wallets/keymeta_test")
132+
shutil.copytree(test_wallet_dir, new_test_wallet_dir)
133+
self.nodes[0].loadwallet("keymeta_test")
134+
test_wallet = self.nodes[0].get_wallet_rpc("keymeta_test")
135+
136+
self.do_inactive_test(base_wallet, test_wallet, encrypt=True)
137+
138+
def run_test(self):
139+
self.generate(self.nodes[0], 101)
140+
141+
self.test_basic()
142+
self.test_encrypted_wallet()
143+
self.test_without_upgraded_keymeta()
144+
145+
146+
if __name__ == '__main__':
147+
InactiveHDChainsTest().main()

0 commit comments

Comments
 (0)