Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions test/functional/test_framework/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,29 @@ def force_finish_mnsync(node):
while not node.mnsync("status")['IsSynced']:
node.mnsync("next")


def get_mnemonic(node):
"""
Return mnemonic if known from legacy HD wallets and Descriptor Wallets
Raises exception if there is none.
"""
if not node.getwalletinfo()['descriptors']:
return node.dumphdinfo()["mnemonic"]

mnemonic = None
descriptors = node.listdescriptors(True)['descriptors']
for desc in descriptors:
if desc['desc'][:4] == 'pkh(':
if mnemonic is None:
mnemonic = desc['mnemonic']
else:
assert_equal(mnemonic, desc['mnemonic'])
elif desc['desc'][:6] == 'combo(':
assert 'mnemonic' not in desc
else:
raise AssertionError(f"Unknown descriptor type: {desc['desc']}")
return mnemonic

# Transaction/Block functions
#############################

Expand Down
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@
'wallet_encryption.py --legacy-wallet',
'wallet_encryption.py --descriptors',
'wallet_upgradetohd.py --legacy-wallet',
'wallet_upgradetohd.py --descriptors',
'feature_dersig.py',
'feature_cltv.py',
'feature_new_quorum_type_activation.py',
Expand Down
45 changes: 10 additions & 35 deletions test/functional/wallet_mnemonicbits.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
get_mnemonic,
)

class WalletMnemonicbitsTest(BitcoinTestFramework):
Expand All @@ -17,24 +18,6 @@ def set_test_params(self):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

def get_mnemonic(self, node):
if not self.options.descriptors:
return node.dumphdinfo()["mnemonic"]

mnemonic = None
descriptors = node.listdescriptors(True)['descriptors']
for desc in descriptors:
if desc['desc'][:4] == 'pkh(':
if mnemonic is None:
mnemonic = desc['mnemonic']
else:
assert_equal(mnemonic, desc['mnemonic'])
elif desc['desc'][:6] == 'combo(':
assert 'mnemonic' not in desc
else:
raise AssertionError(f"Unknown descriptor type: {desc['desc']}")
return mnemonic

def run_test(self):
self.log.info("Test -mnemonicbits")

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

mnemonic_pre = self.get_mnemonic(self.nodes[0])
mnemonic_pre = get_mnemonic(self.nodes[0])


self.nodes[0].encryptwallet('pass')
Expand Down Expand Up @@ -88,22 +71,14 @@ def run_test(self):
self.nodes[0].loadwallet("wallet_160")
self.nodes[0].loadwallet("wallet_192")
self.nodes[0].loadwallet("wallet_224")
if self.options.descriptors:
self.nodes[0].createwallet("wallet_256", False, True, "", False, True) # blank Descriptors
self.nodes[0].get_wallet_rpc("wallet_256").upgradetohd()
assert_equal(len(self.get_mnemonic(self.nodes[0].get_wallet_rpc(self.default_wallet_name)).split()), 12) # 12 words by default
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_160").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 15) # 15 words
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_192").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 18) # 18 words
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_224").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 21) # 21 words
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_256").listdescriptors(True)["descriptors"][0]["mnemonic"].split()), 24) # 24 words
else:
self.nodes[0].createwallet("wallet_256", False, True) # blank HD legacy
self.nodes[0].get_wallet_rpc("wallet_256").upgradetohd()
assert_equal(len(self.nodes[0].get_wallet_rpc(self.default_wallet_name).dumphdinfo()["mnemonic"].split()), 12) # 12 words by default
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_160").dumphdinfo()["mnemonic"].split()), 15) # 15 words
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_192").dumphdinfo()["mnemonic"].split()), 18) # 18 words
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_224").dumphdinfo()["mnemonic"].split()), 21) # 21 words
assert_equal(len(self.nodes[0].get_wallet_rpc("wallet_256").dumphdinfo()["mnemonic"].split()), 24) # 24 words
self.nodes[0].createwallet("wallet_256", blank=True, descriptors=self.options.descriptors) # blank wallet
self.nodes[0].get_wallet_rpc("wallet_256").upgradetohd()

assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc(self.default_wallet_name)).split()), 12) # 12 words by default
assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_160")).split()), 15) # 15 words
assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_192")).split()), 18) # 18 words
assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_224")).split()), 21) # 21 words
assert_equal(len(get_mnemonic(self.nodes[0].get_wallet_rpc("wallet_256")).split()), 24) # 24 words


if __name__ == '__main__':
Expand Down
101 changes: 74 additions & 27 deletions test/functional/wallet_upgradetohd.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# Copyright (c) 2016 The Bitcoin Core developers
# Copyright (c) 2016-2025 The Dash Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
Expand All @@ -15,6 +15,7 @@
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
get_mnemonic,
)


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

def recover_non_hd(self):
self.log.info("Recover non-HD wallet to check different upgrade paths")
node = self.nodes[0]
self.stop_node(0)
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))
self.start_node(0)
assert 'hdchainid' not in node.getwalletinfo()
if not self.options.descriptors:
assert 'hdchainid' not in node.getwalletinfo()

def run_test(self):
node = self.nodes[0]
Expand All @@ -47,9 +50,10 @@ def run_test(self):
assert 'hdchainid' not in node.getwalletinfo()
balance_before = node.getbalance()
assert node.upgradetohd()
mnemonic = node.dumphdinfo()['mnemonic']
chainid = node.getwalletinfo()['hdchainid']
assert_equal(len(chainid), 64)
mnemonic = get_mnemonic(node)
if not self.options.descriptors:
chainid = node.getwalletinfo()['hdchainid']
assert_equal(len(chainid), 64)
assert_equal(balance_before, node.getbalance())

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

self.log.info("No mnemonic, no mnemonic passphrase, no wallet passphrase, should result in completely different keys")
assert node.upgradetohd()
assert mnemonic != node.dumphdinfo()['mnemonic']
assert chainid != node.getwalletinfo()['hdchainid']
assert mnemonic != get_mnemonic(node)
if not self.options.descriptors:
assert chainid != node.getwalletinfo()['hdchainid']
assert_equal(balance_non_HD, node.getbalance())
node.keypoolrefill(5)
node.rescanblockchain()
Expand All @@ -96,18 +101,20 @@ def run_test(self):
self.restart_node(0, extra_args=['-keypool=10'])
assert node.upgradetohd("", "", "", True)
# Completely different keys, no HD coins should be recovered
assert mnemonic != node.dumphdinfo()['mnemonic']
assert chainid != node.getwalletinfo()['hdchainid']
assert mnemonic != get_mnemonic(node)
if not self.options.descriptors:
assert chainid != node.getwalletinfo()['hdchainid']
assert_equal(balance_non_HD, node.getbalance())

self.recover_non_hd()

self.log.info("Same mnemonic, another mnemonic passphrase, no wallet passphrase, should result in a different set of keys")
new_mnemonic_passphrase = "somewords"
assert node.upgradetohd(mnemonic, new_mnemonic_passphrase)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
new_chainid = node.getwalletinfo()['hdchainid']
assert chainid != new_chainid
assert_equal(mnemonic, get_mnemonic(node))
if not self.options.descriptors:
new_chainid = node.getwalletinfo()['hdchainid']
assert chainid != new_chainid
assert_equal(balance_non_HD, node.getbalance())
node.keypoolrefill(5)
node.rescanblockchain()
Expand All @@ -119,8 +126,9 @@ def run_test(self):

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

self.log.info("Same mnemonic, no mnemonic passphrase, no wallet passphrase, should recover all coins after rescan")
assert node.upgradetohd(mnemonic)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
assert_equal(mnemonic, get_mnemonic(node))
if not self.options.descriptors:
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
node.keypoolrefill(5)
assert balance_after != node.getbalance()
node.rescanblockchain()
Expand All @@ -144,8 +153,9 @@ def run_test(self):
self.log.info("Same mnemonic, no mnemonic passphrase, no wallet passphrase, large enough keepool, should recover all coins with no extra rescan")
self.restart_node(0, extra_args=['-keypool=10'])
assert node.upgradetohd(mnemonic)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
assert_equal(mnemonic, get_mnemonic(node))
if not self.options.descriptors:
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
# All coins should be recovered
assert_equal(balance_after, node.getbalance())

Expand All @@ -154,8 +164,9 @@ def run_test(self):
self.log.info("Same mnemonic, no mnemonic passphrase, no wallet passphrase, large enough keepool, rescan is skipped initially, should recover all coins after rescanblockchain")
self.restart_node(0, extra_args=['-keypool=10'])
assert node.upgradetohd(mnemonic, "", "", False)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
assert_equal(mnemonic, get_mnemonic(node))
if not self.options.descriptors:
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
assert balance_after != node.getbalance()
node.rescanblockchain()
# All coins should be recovered
Expand All @@ -171,8 +182,9 @@ def run_test(self):
self.start_node(0, extra_args=['-rescan'])
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)
node.walletpassphrase(walletpass, 100)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
assert_equal(mnemonic, get_mnemonic(node))
if not self.options.descriptors:
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
# Note: wallet encryption results in additional keypool topup,
# so we can't compare new balance to balance_non_HD here,
# assert_equal(balance_non_HD, node.getbalance()) # won't work
Expand All @@ -191,12 +203,25 @@ def run_test(self):
node.wait_until_stopped()
self.start_node(0, extra_args=['-rescan'])
assert_raises_rpc_error(-13, "Error: Wallet encrypted but passphrase not supplied to RPC.", node.upgradetohd, mnemonic)
assert_raises_rpc_error(-1, "Error: The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass")
if not self.options.descriptors:
assert_raises_rpc_error(-1, "Error: The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass")
else:
assert_raises_rpc_error(-1, "SetupDescriptorScriptPubKeyMans: Wallet is locked, cannot setup new descriptors", node.upgradetohd, mnemonic, "", "wrongpass")
if self.options.descriptors:
# TODO - implement auto-unlock descriptor wallet
node.walletpassphrase(walletpass, 100)
assert node.upgradetohd(mnemonic, "", walletpass)
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)
# TODO - drop it too!
if self.options.descriptors:
node.walletlock()
if not self.options.descriptors:
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)
else:
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.listdescriptors, True)
node.walletpassphrase(walletpass, 100)
assert_equal(mnemonic, node.dumphdinfo()['mnemonic'])
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
assert_equal(mnemonic, get_mnemonic(node))
if not self.options.descriptors:
assert_equal(chainid, node.getwalletinfo()['hdchainid'])
# Note: wallet encryption results in additional keypool topup,
# so we can't compare new balance to balance_non_HD here,
# assert_equal(balance_non_HD, node.getbalance()) # won't work
Expand All @@ -206,6 +231,28 @@ def run_test(self):
# All coins should be recovered
assert_equal(balance_after, node.getbalance())

self.log.info("Test upgradetohd with user defined mnemonic")
custom_mnemonic = "similar behave slot swim scissors throw planet view ghost laugh drift calm"
# this address belongs to custom mnemonic with no passphrase
custom_address_1 = "yLpq97zZUsFQ2rdMqhcPKkYT36MoPK4Hob"
# this address belongs to custom mnemonic with passphrase "custom-passphrase"
custom_address_2 = "yYBPeZQcqgQHu9dxA5pKBWtYbK2hwfFHxf"
node.sendtoaddress(custom_address_1, 11)
node.sendtoaddress(custom_address_2, 12)
self.generate(node, 1)

node.createwallet("wallet-11", blank=True)
w11 = node.get_wallet_rpc("wallet-11")
w11.upgradetohd(custom_mnemonic)
assert_equal(11, w11.getbalance())
w11.unloadwallet()

node.createwallet("wallet-12", blank=True)
w12 = node.get_wallet_rpc("wallet-12")
w12.upgradetohd(custom_mnemonic, "custom-passphrase")
assert_equal(12, w12.getbalance())
w12.unloadwallet()


if __name__ == '__main__':
WalletUpgradeToHDTest().main ()
Loading