Skip to content

Commit 67e6fd0

Browse files
pinheadmzluke-jr
authored andcommitted
test: cover "ismine" and "isactive" field in rpc getaddressinfo
Github-Pull: bitcoin#27216 Rebased-From: 6765021
1 parent 9d43447 commit 67e6fd0

File tree

1 file changed

+101
-23
lines changed

1 file changed

+101
-23
lines changed

test/functional/wallet_keypool.py

Lines changed: 101 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,113 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test the wallet keypool and interaction with wallet encryption/locking."""
66

7+
import re
78
import time
89
from decimal import Decimal
910

11+
from test_framework.descriptors import descsum_create
1012
from test_framework.test_framework import BitcoinTestFramework
1113
from test_framework.util import assert_equal, assert_raises_rpc_error
1214
from test_framework.wallet_util import WalletUnlock
1315

16+
TEST_KEYPOOL_SIZE = 10
17+
TEST_NEW_KEYPOOL_SIZE = TEST_KEYPOOL_SIZE + 2
18+
1419
class KeyPoolTest(BitcoinTestFramework):
1520
def add_options(self, parser):
1621
self.add_wallet_options(parser)
1722

1823
def set_test_params(self):
1924
self.num_nodes = 1
25+
self.extra_args = [[f"-keypool={TEST_KEYPOOL_SIZE}"]]
2026

2127
def skip_test_if_missing_module(self):
2228
self.skip_if_no_wallet()
2329

2430
def run_test(self):
2531
nodes = self.nodes
32+
33+
# Derive addresses from the wallet without removing them from keypool
34+
addrs = []
35+
if not self.options.descriptors:
36+
path = str(self.nodes[0].datadir_path / 'wallet.dump')
37+
nodes[0].dumpwallet(path)
38+
file = open(path, "r", encoding="utf8")
39+
m = re.search(r"masterkey: (\w+)", file.read())
40+
file.close()
41+
xpriv = m.group(1)
42+
desc = descsum_create(f"wpkh({xpriv}/0h/0h/*h)")
43+
addrs = nodes[0].deriveaddresses(descriptor=desc, range=[0, 9])
44+
else:
45+
list_descriptors = nodes[0].listdescriptors()
46+
for desc in list_descriptors["descriptors"]:
47+
if desc['active'] and not desc["internal"] and desc["desc"][:4] == "wpkh":
48+
addrs = nodes[0].deriveaddresses(descriptor=desc["desc"], range=[0, 9])
49+
50+
addr0 = addrs[0]
51+
addr9 = addrs[9] # arbitrary future address index
52+
53+
# Address is mine and active before it is removed from keypool by getnewaddress
54+
addr0_before_getting_data = nodes[0].getaddressinfo(addr0)
55+
assert addr0_before_getting_data['ismine']
56+
assert addr0_before_getting_data['isactive']
57+
2658
addr_before_encrypting = nodes[0].getnewaddress()
2759
addr_before_encrypting_data = nodes[0].getaddressinfo(addr_before_encrypting)
60+
assert addr0 == addr_before_encrypting
61+
# Address is still mine and active even after being removed from keypool
62+
assert addr_before_encrypting_data['ismine']
63+
assert addr_before_encrypting_data['isactive']
64+
2865
wallet_info_old = nodes[0].getwalletinfo()
2966
if not self.options.descriptors:
3067
assert addr_before_encrypting_data['hdseedid'] == wallet_info_old['hdseedid']
3168

69+
# Address is mine and active before wallet is encrypted (resetting keypool)
70+
addr9_before_encrypting_data = nodes[0].getaddressinfo(addr9)
71+
assert addr9_before_encrypting_data['ismine']
72+
assert addr9_before_encrypting_data['isactive']
73+
74+
# Imported things are never considered active, no need to rescan
75+
# Imported public keys / addresses can't be mine because they are not spendable
76+
if self.options.descriptors:
77+
nodes[0].importdescriptors([{
78+
"desc": "addr(bcrt1q95gp4zeaah3qcerh35yhw02qeptlzasdtst55v)",
79+
"timestamp": "now"
80+
}])
81+
else:
82+
nodes[0].importaddress("bcrt1q95gp4zeaah3qcerh35yhw02qeptlzasdtst55v", "label", rescan=False)
83+
import_addr_data = nodes[0].getaddressinfo("bcrt1q95gp4zeaah3qcerh35yhw02qeptlzasdtst55v")
84+
assert import_addr_data["iswatchonly"] is not self.options.descriptors
85+
assert not import_addr_data["ismine"]
86+
assert not import_addr_data["isactive"]
87+
88+
if self.options.descriptors:
89+
nodes[0].importdescriptors([{
90+
"desc": "pk(02f893ca95b0d55b4ce4e72ae94982eb679158cb2ebc120ff62c17fedfd1f0700e)",
91+
"timestamp": "now"
92+
}])
93+
else:
94+
nodes[0].importpubkey("02f893ca95b0d55b4ce4e72ae94982eb679158cb2ebc120ff62c17fedfd1f0700e", "label", rescan=False)
95+
import_pub_data = nodes[0].getaddressinfo("bcrt1q4v7a8wn5vqd6fk4026s5gzzxyu7cfzz23n576h")
96+
assert import_pub_data["iswatchonly"] is not self.options.descriptors
97+
assert not import_pub_data["ismine"]
98+
assert not import_pub_data["isactive"]
99+
100+
nodes[0].importprivkey("cPMX7v5CNV1zCphFSq2hnR5rCjzAhA1GsBfD1qrJGdj4QEfu38Qx", "label", rescan=False)
101+
import_priv_data = nodes[0].getaddressinfo("bcrt1qa985v5d53qqtrfujmzq2zrw3r40j6zz4ns02kj")
102+
assert not import_priv_data["iswatchonly"]
103+
assert import_priv_data["ismine"]
104+
assert not import_priv_data["isactive"]
105+
32106
# Encrypt wallet and wait to terminate
33107
nodes[0].encryptwallet('test')
108+
addr9_after_encrypting_data = nodes[0].getaddressinfo(addr9)
109+
# Key is from unencrypted seed, no longer considered active
110+
assert not addr9_after_encrypting_data['isactive']
111+
# ...however it *IS* still mine since we can spend with this key
112+
assert addr9_after_encrypting_data['ismine']
113+
34114
if self.options.descriptors:
35115
# Import hardened derivation only descriptors
36116
nodes[0].walletpassphrase('test', 10)
@@ -76,7 +156,9 @@ def run_test(self):
76156
}
77157
])
78158
nodes[0].walletlock()
79-
# Keep creating keys
159+
# Keep creating keys until we run out
160+
for _ in range(TEST_KEYPOOL_SIZE - 1):
161+
nodes[0].getnewaddress()
80162
addr = nodes[0].getnewaddress()
81163
addr_data = nodes[0].getaddressinfo(addr)
82164
wallet_info = nodes[0].getwalletinfo()
@@ -85,24 +167,23 @@ def run_test(self):
85167
assert addr_data['hdseedid'] == wallet_info['hdseedid']
86168
assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)
87169

88-
# put six (plus 2) new keys in the keypool (100% external-, +100% internal-keys, 1 in min)
170+
# put two new keys in the keypool
89171
with WalletUnlock(nodes[0], 'test'):
90-
nodes[0].keypoolrefill(6)
172+
nodes[0].keypoolrefill(TEST_NEW_KEYPOOL_SIZE)
91173
wi = nodes[0].getwalletinfo()
92174
if self.options.descriptors:
93-
assert_equal(wi['keypoolsize_hd_internal'], 24)
94-
assert_equal(wi['keypoolsize'], 24)
175+
# Descriptors wallet: keypool size applies to both internal and external
176+
# chains and there are four of each (legacy, nested, segwit, and taproot)
177+
assert_equal(wi['keypoolsize_hd_internal'], TEST_NEW_KEYPOOL_SIZE * 4)
178+
assert_equal(wi['keypoolsize'], TEST_NEW_KEYPOOL_SIZE * 4)
95179
else:
96-
assert_equal(wi['keypoolsize_hd_internal'], 6)
97-
assert_equal(wi['keypoolsize'], 6)
180+
# Legacy wallet: keypool size applies to both internal and external HD chains
181+
assert_equal(wi['keypoolsize_hd_internal'], TEST_NEW_KEYPOOL_SIZE)
182+
assert_equal(wi['keypoolsize'], TEST_NEW_KEYPOOL_SIZE)
98183

99184
# drain the internal keys
100-
nodes[0].getrawchangeaddress()
101-
nodes[0].getrawchangeaddress()
102-
nodes[0].getrawchangeaddress()
103-
nodes[0].getrawchangeaddress()
104-
nodes[0].getrawchangeaddress()
105-
nodes[0].getrawchangeaddress()
185+
for _ in range(TEST_NEW_KEYPOOL_SIZE):
186+
nodes[0].getrawchangeaddress()
106187
# remember keypool sizes
107188
wi = nodes[0].getwalletinfo()
108189
kp_size_before = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]
@@ -115,13 +196,8 @@ def run_test(self):
115196

116197
# drain the external keys
117198
addr = set()
118-
addr.add(nodes[0].getnewaddress(address_type="bech32"))
119-
addr.add(nodes[0].getnewaddress(address_type="bech32"))
120-
addr.add(nodes[0].getnewaddress(address_type="bech32"))
121-
addr.add(nodes[0].getnewaddress(address_type="bech32"))
122-
addr.add(nodes[0].getnewaddress(address_type="bech32"))
123-
addr.add(nodes[0].getnewaddress(address_type="bech32"))
124-
assert len(addr) == 6
199+
for _ in range(TEST_NEW_KEYPOOL_SIZE):
200+
addr.add(nodes[0].getnewaddress(address_type="bech32"))
125201
# remember keypool sizes
126202
wi = nodes[0].getwalletinfo()
127203
kp_size_before = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]
@@ -132,16 +208,18 @@ def run_test(self):
132208
kp_size_after = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]
133209
assert_equal(kp_size_before, kp_size_after)
134210

135-
# refill keypool with three new addresses
211+
# refill keypool
136212
nodes[0].walletpassphrase('test', 1)
137-
nodes[0].keypoolrefill(3)
213+
# At this point the keypool has >45 keys in it
214+
# calling keypoolrefill with anything smaller than that is a noop
215+
nodes[0].keypoolrefill(50)
138216

139217
# test walletpassphrase timeout
140218
time.sleep(1.1)
141219
assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0)
142220

143221
# drain the keypool
144-
for _ in range(3):
222+
for _ in range(50):
145223
nodes[0].getnewaddress()
146224
assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getnewaddress)
147225

0 commit comments

Comments
 (0)