Skip to content

Commit bd589dd

Browse files
Merge dashpay#6763: test: make wallet_upgradetohd.py works with descriptors
04c8c97 test: move helper get_mnemonic to test_framework/util (Konstantin Akimov) 1e70ee8 test: enable --descriptors for wallet_upgradetohd.py (Konstantin Akimov) 0322b7c test: ensure determinism of addresses for specific mnemonic (Konstantin Akimov) af4b618 test: make wallet_upgradetohd works for descriptor wallets too (Konstantin Akimov) 0267fb7 fix: copyright for wallet_upgradetohd.py (Konstantin Akimov) 62c4ec1 test: remove duplicated code from wallet_mnemonicbits.py by using helper get_mnemonic (Konstantin Akimov) Pull request description: ## Issue being fixed or feature implemented `upgradetohd` has comprehensive tests for case of blank legacy wallet -> HD legacy wallet. This PR replaces dashpay#6762 ## What was done? - add descriptor wallets to scope of wallet_updatetohd.py - add tests for deterministic of generated addresses from mnemonic (with & without mnemonic_passphrase) ## How Has This Been Tested? See `wallet_upgadetohd.py` ## Breaking Changes N/A ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone ACKs for top commit: UdjinM6: utACK 04c8c97 Tree-SHA512: 7d007b48642f938efb501ef9b36a1e3506e9f658e332a5a845b91784298935669d747c7a616c558b4727644540d9e481d94690ee05e921d6bed6aea4253a7b5a
2 parents e40a931 + 04c8c97 commit bd589dd

File tree

4 files changed

+108
-62
lines changed

4 files changed

+108
-62
lines changed

test/functional/test_framework/util.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,29 @@ def force_finish_mnsync(node):
536536
while not node.mnsync("status")['IsSynced']:
537537
node.mnsync("next")
538538

539+
540+
def get_mnemonic(node):
541+
"""
542+
Return mnemonic if known from legacy HD wallets and Descriptor Wallets
543+
Raises exception if there is none.
544+
"""
545+
if not node.getwalletinfo()['descriptors']:
546+
return node.dumphdinfo()["mnemonic"]
547+
548+
mnemonic = None
549+
descriptors = node.listdescriptors(True)['descriptors']
550+
for desc in descriptors:
551+
if desc['desc'][:4] == 'pkh(':
552+
if mnemonic is None:
553+
mnemonic = desc['mnemonic']
554+
else:
555+
assert_equal(mnemonic, desc['mnemonic'])
556+
elif desc['desc'][:6] == 'combo(':
557+
assert 'mnemonic' not in desc
558+
else:
559+
raise AssertionError(f"Unknown descriptor type: {desc['desc']}")
560+
return mnemonic
561+
539562
# Transaction/Block functions
540563
#############################
541564

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@
321321
'wallet_encryption.py --legacy-wallet',
322322
'wallet_encryption.py --descriptors',
323323
'wallet_upgradetohd.py --legacy-wallet',
324+
'wallet_upgradetohd.py --descriptors',
324325
'feature_dersig.py',
325326
'feature_cltv.py',
326327
'feature_new_quorum_type_activation.py',

test/functional/wallet_mnemonicbits.py

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from test_framework.test_framework import BitcoinTestFramework
88
from test_framework.util import (
99
assert_equal,
10+
get_mnemonic,
1011
)
1112

1213
class WalletMnemonicbitsTest(BitcoinTestFramework):
@@ -17,24 +18,6 @@ def set_test_params(self):
1718
def skip_test_if_missing_module(self):
1819
self.skip_if_no_wallet()
1920

20-
def get_mnemonic(self, node):
21-
if not self.options.descriptors:
22-
return node.dumphdinfo()["mnemonic"]
23-
24-
mnemonic = None
25-
descriptors = node.listdescriptors(True)['descriptors']
26-
for desc in descriptors:
27-
if desc['desc'][:4] == 'pkh(':
28-
if mnemonic is None:
29-
mnemonic = desc['mnemonic']
30-
else:
31-
assert_equal(mnemonic, desc['mnemonic'])
32-
elif desc['desc'][:6] == 'combo(':
33-
assert 'mnemonic' not in desc
34-
else:
35-
raise AssertionError(f"Unknown descriptor type: {desc['desc']}")
36-
return mnemonic
37-
3821
def run_test(self):
3922
self.log.info("Test -mnemonicbits")
4023

@@ -43,7 +26,7 @@ def run_test(self):
4326
self.nodes[0].assert_start_raises_init_error(['-mnemonicbits=123'], "Error: Invalid '-mnemonicbits'. Allowed values: 128, 160, 192, 224, 256.")
4427
self.start_node(0)
4528

46-
mnemonic_pre = self.get_mnemonic(self.nodes[0])
29+
mnemonic_pre = get_mnemonic(self.nodes[0])
4730

4831

4932
self.nodes[0].encryptwallet('pass')
@@ -88,22 +71,14 @@ def run_test(self):
8871
self.nodes[0].loadwallet("wallet_160")
8972
self.nodes[0].loadwallet("wallet_192")
9073
self.nodes[0].loadwallet("wallet_224")
91-
if self.options.descriptors:
92-
self.nodes[0].createwallet("wallet_256", False, True, "", False, True) # blank Descriptors
93-
self.nodes[0].get_wallet_rpc("wallet_256").upgradetohd()
94-
assert_equal(len(self.get_mnemonic(self.nodes[0].get_wallet_rpc(self.default_wallet_name)).split()), 12) # 12 words by default
95-
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_160").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 15) # 15 words
96-
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_192").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 18) # 18 words
97-
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_224").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 21) # 21 words
98-
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_256").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 24) # 24 words
99-
else:
100-
self.nodes[0].createwallet("wallet_256", False, True) # blank HD legacy
101-
self.nodes[0].get_wallet_rpc("wallet_256").upgradetohd()
102-
assert_equal(len(self.nodes[0].get_wallet_rpc(self.default_wallet_name).dumphdinfo()["mnemonic"].split()), 12) # 12 words by default
103-
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_160").dumphdinfo()["mnemonic"].split()), 15) # 15 words
104-
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_192").dumphdinfo()["mnemonic"].split()), 18) # 18 words
105-
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_224").dumphdinfo()["mnemonic"].split()), 21) # 21 words
106-
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_256").dumphdinfo()["mnemonic"].split()), 24) # 24 words
74+
self.nodes[0].createwallet("wallet_256", blank=True, descriptors=self.options.descriptors) # blank wallet
75+
self.nodes[0].get_wallet_rpc("wallet_256").upgradetohd()
76+
77+
assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc(self.default_wallet_name)).split()), 12) # 12 words by default
78+
assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_160")).split()), 15) # 15 words
79+
assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_192")).split()), 18) # 18 words
80+
assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_224")).split()), 21) # 21 words
81+
assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_256")).split()), 24) # 24 words
10782

10883

10984
if __name__ == '__main__':

test/functional/wallet_upgradetohd.py

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
# Copyright (c) 2016 The Bitcoin Core developers
2+
# Copyright (c) 2016-2025 The Dash Core developers
33
# Distributed under the MIT software license, see the accompanying
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""
@@ -15,6 +15,7 @@
1515
from test_framework.util import (
1616
assert_equal,
1717
assert_raises_rpc_error,
18+
get_mnemonic,
1819
)
1920

2021

@@ -29,15 +30,17 @@ def skip_test_if_missing_module(self):
2930
def setup_network(self):
3031
self.add_nodes(self.num_nodes, self.extra_args)
3132
self.start_nodes()
32-
self.import_deterministic_coinbase_privkeys()
33+
self.nodes[0].createwallet(self.default_wallet_name, blank=True, load_on_startup=True)
34+
self.nodes[0].importprivkey(privkey=self.nodes[0].get_deterministic_priv_key().key, label='coinbase', rescan=True)
3335

3436
def recover_non_hd(self):
3537
self.log.info("Recover non-HD wallet to check different upgrade paths")
3638
node = self.nodes[0]
3739
self.stop_node(0)
3840
shutil.copyfile(os.path.join(node.datadir, "non_hd.bak"), os.path.join(node.datadir, self.chain, self.default_wallet_name, self.wallet_data_filename))
3941
self.start_node(0)
40-
assert 'hdchainid' not in node.getwalletinfo()
42+
if not self.options.descriptors:
43+
assert 'hdchainid' not in node.getwalletinfo()
4144

4245
def run_test(self):
4346
node = self.nodes[0]
@@ -47,9 +50,10 @@ def run_test(self):
4750
assert 'hdchainid' not in node.getwalletinfo()
4851
balance_before = node.getbalance()
4952
assert node.upgradetohd()
50-
mnemonic = node.dumphdinfo()['mnemonic']
51-
chainid = node.getwalletinfo()['hdchainid']
52-
assert_equal(len(chainid), 64)
53+
mnemonic = get_mnemonic(node)
54+
if not self.options.descriptors:
55+
chainid = node.getwalletinfo()['hdchainid']
56+
assert_equal(len(chainid), 64)
5357
assert_equal(balance_before, node.getbalance())
5458

5559
self.log.info("Should be spendable and should use correct paths")
@@ -82,8 +86,9 @@ def run_test(self):
8286

8387
self.log.info("No mnemonic, no mnemonic passphrase, no wallet passphrase, should result in completely different keys")
8488
assert node.upgradetohd()
85-
assert mnemonic != node.dumphdinfo()['mnemonic']
86-
assert chainid != node.getwalletinfo()['hdchainid']
89+
assert mnemonic != get_mnemonic(node)
90+
if not self.options.descriptors:
91+
assert chainid != node.getwalletinfo()['hdchainid']
8792
assert_equal(balance_non_HD, node.getbalance())
8893
node.keypoolrefill(5)
8994
node.rescanblockchain()
@@ -96,18 +101,20 @@ def run_test(self):
96101
self.restart_node(0, extra_args=['-keypool=10'])
97102
assert node.upgradetohd("", "", "", True)
98103
# Completely different keys, no HD coins should be recovered
99-
assert mnemonic != node.dumphdinfo()['mnemonic']
100-
assert chainid != node.getwalletinfo()['hdchainid']
104+
assert mnemonic != get_mnemonic(node)
105+
if not self.options.descriptors:
106+
assert chainid != node.getwalletinfo()['hdchainid']
101107
assert_equal(balance_non_HD, node.getbalance())
102108

103109
self.recover_non_hd()
104110

105111
self.log.info("Same mnemonic, another mnemonic passphrase, no wallet passphrase, should result in a different set of keys")
106112
new_mnemonic_passphrase = "somewords"
107113
assert node.upgradetohd(mnemonic, new_mnemonic_passphrase)
108-
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
109-
new_chainid = node.getwalletinfo()['hdchainid']
110-
assert chainid != new_chainid
114+
assert_equal(mnemonic, get_mnemonic(node))
115+
if not self.options.descriptors:
116+
new_chainid = node.getwalletinfo()['hdchainid']
117+
assert chainid != new_chainid
111118
assert_equal(balance_non_HD, node.getbalance())
112119
node.keypoolrefill(5)
113120
node.rescanblockchain()
@@ -119,8 +126,9 @@ def run_test(self):
119126

120127
self.log.info("Same mnemonic, another mnemonic passphrase, no wallet passphrase, should result in a different set of keys (again)")
121128
assert node.upgradetohd(mnemonic, new_mnemonic_passphrase)
122-
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
123-
assert_equal(new_chainid, node.getwalletinfo()['hdchainid'])
129+
assert_equal(mnemonic, get_mnemonic(node))
130+
if not self.options.descriptors:
131+
assert_equal(new_chainid, node.getwalletinfo()['hdchainid'])
124132
assert_equal(balance_non_HD, node.getbalance())
125133
node.keypoolrefill(5)
126134
node.rescanblockchain()
@@ -132,8 +140,9 @@ def run_test(self):
132140

133141
self.log.info("Same mnemonic, no mnemonic passphrase, no wallet passphrase, should recover all coins after rescan")
134142
assert node.upgradetohd(mnemonic)
135-
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
136-
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
143+
assert_equal(mnemonic, get_mnemonic(node))
144+
if not self.options.descriptors:
145+
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
137146
node.keypoolrefill(5)
138147
assert balance_after != node.getbalance()
139148
node.rescanblockchain()
@@ -144,8 +153,9 @@ def run_test(self):
144153
self.log.info("Same mnemonic, no mnemonic passphrase, no wallet passphrase, large enough keepool, should recover all coins with no extra rescan")
145154
self.restart_node(0, extra_args=['-keypool=10'])
146155
assert node.upgradetohd(mnemonic)
147-
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
148-
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
156+
assert_equal(mnemonic, get_mnemonic(node))
157+
if not self.options.descriptors:
158+
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
149159
# All coins should be recovered
150160
assert_equal(balance_after, node.getbalance())
151161

@@ -154,8 +164,9 @@ def run_test(self):
154164
self.log.info("Same mnemonic, no mnemonic passphrase, no wallet passphrase, large enough keepool, rescan is skipped initially, should recover all coins after rescanblockchain")
155165
self.restart_node(0, extra_args=['-keypool=10'])
156166
assert node.upgradetohd(mnemonic, "", "", False)
157-
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
158-
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
167+
assert_equal(mnemonic, get_mnemonic(node))
168+
if not self.options.descriptors:
169+
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
159170
assert balance_after != node.getbalance()
160171
node.rescanblockchain()
161172
# All coins should be recovered
@@ -171,8 +182,9 @@ def run_test(self):
171182
self.start_node(0, extra_args=['-rescan'])
172183
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)
173184
node.walletpassphrase(walletpass, 100)
174-
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
175-
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
185+
assert_equal(mnemonic, get_mnemonic(node))
186+
if not self.options.descriptors:
187+
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
176188
# Note: wallet encryption results in additional keypool topup,
177189
# so we can't compare new balance to balance_non_HD here,
178190
# assert_equal(balance_non_HD, node.getbalance()) # won't work
@@ -191,12 +203,25 @@ def run_test(self):
191203
node.wait_until_stopped()
192204
self.start_node(0, extra_args=['-rescan'])
193205
assert_raises_rpc_error(-13, "Error: Wallet encrypted but passphrase not supplied to RPC.", node.upgradetohd, mnemonic)
194-
assert_raises_rpc_error(-1, "Error: The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass")
206+
if not self.options.descriptors:
207+
assert_raises_rpc_error(-1, "Error: The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass")
208+
else:
209+
assert_raises_rpc_error(-1, "SetupDescriptorScriptPubKeyMans: Wallet is locked, cannot setup new descriptors", node.upgradetohd, mnemonic, "", "wrongpass")
210+
if self.options.descriptors:
211+
# TODO - implement auto-unlock descriptor wallet
212+
node.walletpassphrase(walletpass, 100)
195213
assert node.upgradetohd(mnemonic, "", walletpass)
196-
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)
214+
# TODO - drop it too!
215+
if self.options.descriptors:
216+
node.walletlock()
217+
if not self.options.descriptors:
218+
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)
219+
else:
220+
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.listdescriptors, True)
197221
node.walletpassphrase(walletpass, 100)
198-
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
199-
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
222+
assert_equal(mnemonic, get_mnemonic(node))
223+
if not self.options.descriptors:
224+
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
200225
# Note: wallet encryption results in additional keypool topup,
201226
# so we can't compare new balance to balance_non_HD here,
202227
# assert_equal(balance_non_HD, node.getbalance()) # won't work
@@ -206,6 +231,28 @@ def run_test(self):
206231
# All coins should be recovered
207232
assert_equal(balance_after, node.getbalance())
208233

234+
self.log.info("Test upgradetohd with user defined mnemonic")
235+
custom_mnemonic = "similar behave slot swim scissors throw planet view ghost laugh drift calm"
236+
# this address belongs to custom mnemonic with no passphrase
237+
custom_address_1 = "yLpq97zZUsFQ2rdMqhcPKkYT36MoPK4Hob"
238+
# this address belongs to custom mnemonic with passphrase "custom-passphrase"
239+
custom_address_2 = "yYBPeZQcqgQHu9dxA5pKBWtYbK2hwfFHxf"
240+
node.sendtoaddress(custom_address_1, 11)
241+
node.sendtoaddress(custom_address_2, 12)
242+
self.generate(node, 1)
243+
244+
node.createwallet("wallet-11", blank=True)
245+
w11 = node.get_wallet_rpc("wallet-11")
246+
w11.upgradetohd(custom_mnemonic)
247+
assert_equal(11, w11.getbalance())
248+
w11.unloadwallet()
249+
250+
node.createwallet("wallet-12", blank=True)
251+
w12 = node.get_wallet_rpc("wallet-12")
252+
w12.upgradetohd(custom_mnemonic, "custom-passphrase")
253+
assert_equal(12, w12.getbalance())
254+
w12.unloadwallet()
255+
209256

210257
if __name__ == '__main__':
211258
WalletUpgradeToHDTest().main ()

0 commit comments

Comments
 (0)