2021-01-25 04:03:33 +01:00
#!/usr/bin/env python3
# Copyright (c) 2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""
wallet_upgradetohd . py
Test upgrade to a Hierarchical Deterministic wallet via upgradetohd rpc
"""
import shutil
import os
from test_framework . test_framework import BitcoinTestFramework
from test_framework . util import (
assert_equal ,
assert_raises_rpc_error ,
)
class WalletUpgradeToHDTest ( BitcoinTestFramework ) :
def set_test_params ( self ) :
self . num_nodes = 1
2024-02-09 18:36:14 +01:00
self . extra_args = [ [ ' -usehd=0 ' ] ]
2021-01-25 04:03:33 +01:00
2018-09-13 12:33:15 +02:00
def skip_test_if_missing_module ( self ) :
self . skip_if_no_wallet ( )
2021-01-25 04:03:33 +01:00
def setup_network ( self ) :
2024-02-09 18:36:14 +01:00
self . add_nodes ( self . num_nodes , self . extra_args )
2021-01-25 04:03:33 +01:00
self . start_nodes ( )
2018-11-02 16:41:29 +01:00
self . import_deterministic_coinbase_privkeys ( )
2021-01-25 04:03:33 +01:00
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 )
2023-02-14 09:48:36 +01:00
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 ) )
2021-01-25 04:03:33 +01:00
self . start_node ( 0 )
2021-08-27 21:03:02 +02:00
assert ' hdchainid ' not in node . getwalletinfo ( )
2021-01-25 04:03:33 +01:00
def run_test ( self ) :
node = self . nodes [ 0 ]
node . backupwallet ( os . path . join ( node . datadir , " non_hd.bak " ) )
self . log . info ( " No mnemonic, no mnemonic passphrase, no wallet passphrase " )
2021-08-27 21:03:02 +02:00
assert ' hdchainid ' not in node . getwalletinfo ( )
2021-01-25 04:03:33 +01:00
balance_before = node . getbalance ( )
2021-08-27 21:03:02 +02:00
assert node . upgradetohd ( )
2021-01-25 04:03:33 +01:00
mnemonic = node . dumphdinfo ( ) [ ' mnemonic ' ]
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 " )
for i in range ( 5 ) :
txid = node . sendtoaddress ( node . getnewaddress ( ) , 1 )
outs = node . decoderawtransaction ( node . gettransaction ( txid ) [ ' hex ' ] ) [ ' vout ' ]
for out in outs :
if out [ ' value ' ] == 1 :
2024-06-23 17:51:21 +02:00
keypath = node . getaddressinfo ( out [ ' scriptPubKey ' ] [ ' address ' ] ) [ ' hdkeypath ' ]
2021-01-25 04:03:33 +01:00
assert_equal ( keypath , " m/44 ' /1 ' /0 ' /0/ %d " % i )
else :
2024-06-23 17:51:21 +02:00
keypath = node . getaddressinfo ( out [ ' scriptPubKey ' ] [ ' address ' ] ) [ ' hdkeypath ' ]
2021-01-25 04:03:33 +01:00
assert_equal ( keypath , " m/44 ' /1 ' /0 ' /1/ %d " % i )
self . bump_mocktime ( 1 )
2024-09-26 21:17:04 +02:00
self . generate ( node , 1 , sync_fun = self . no_op )
2021-01-25 04:03:33 +01:00
self . log . info ( " Should no longer be able to start it with HD disabled " )
self . stop_node ( 0 )
2023-02-14 09:48:36 +01:00
node . assert_start_raises_init_error ( [ ' -usehd=0 ' ] , " Error: Error loading %s : You can ' t disable HD on an already existing HD wallet " % self . default_wallet_name )
2024-02-09 18:36:14 +01:00
self . extra_args = [ ]
self . start_node ( 0 , [ ] )
2021-01-25 04:03:33 +01:00
balance_after = node . getbalance ( )
self . recover_non_hd ( )
# We spent some coins from non-HD keys to HD ones earlier
balance_non_HD = node . getbalance ( )
2021-08-27 21:03:02 +02:00
assert balance_before != balance_non_HD
2021-01-25 04:03:33 +01:00
self . log . info ( " No mnemonic, no mnemonic passphrase, no wallet passphrase, should result in completely different keys " )
2021-08-27 21:03:02 +02:00
assert node . upgradetohd ( )
assert mnemonic != node . dumphdinfo ( ) [ ' mnemonic ' ]
assert chainid != node . getwalletinfo ( ) [ ' hdchainid ' ]
2021-01-25 04:03:33 +01:00
assert_equal ( balance_non_HD , node . getbalance ( ) )
node . keypoolrefill ( 5 )
node . rescanblockchain ( )
# Completely different keys, no HD coins should be recovered
assert_equal ( balance_non_HD , node . getbalance ( ) )
self . recover_non_hd ( )
2022-05-17 00:51:54 +02:00
self . log . info ( " No mnemonic, no mnemonic passphrase, no wallet passphrase, should result in completely different keys " )
2020-06-22 18:24:41 +02:00
self . restart_node ( 0 , extra_args = [ ' -keypool=10 ' ] )
2022-05-17 00:51:54 +02:00
assert node . upgradetohd ( " " , " " , " " , True )
# Completely different keys, no HD coins should be recovered
assert mnemonic != node . dumphdinfo ( ) [ ' mnemonic ' ]
assert chainid != node . getwalletinfo ( ) [ ' hdchainid ' ]
assert_equal ( balance_non_HD , node . getbalance ( ) )
self . recover_non_hd ( )
2021-01-25 04:03:33 +01:00
self . log . info ( " Same mnemonic, another mnemonic passphrase, no wallet passphrase, should result in a different set of keys " )
new_mnemonic_passphrase = " somewords "
2021-08-27 21:03:02 +02:00
assert node . upgradetohd ( mnemonic , new_mnemonic_passphrase )
2021-01-25 04:03:33 +01:00
assert_equal ( mnemonic , node . dumphdinfo ( ) [ ' mnemonic ' ] )
new_chainid = node . getwalletinfo ( ) [ ' hdchainid ' ]
2021-08-27 21:03:02 +02:00
assert chainid != new_chainid
2021-01-25 04:03:33 +01:00
assert_equal ( balance_non_HD , node . getbalance ( ) )
node . keypoolrefill ( 5 )
node . rescanblockchain ( )
# A different set of keys, no HD coins should be recovered
new_addresses = ( node . getnewaddress ( ) , node . getrawchangeaddress ( ) )
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 (again) " )
2021-08-27 21:03:02 +02:00
assert node . upgradetohd ( mnemonic , new_mnemonic_passphrase )
2021-01-25 04:03:33 +01:00
assert_equal ( mnemonic , node . dumphdinfo ( ) [ ' mnemonic ' ] )
assert_equal ( new_chainid , node . getwalletinfo ( ) [ ' hdchainid ' ] )
assert_equal ( balance_non_HD , node . getbalance ( ) )
node . keypoolrefill ( 5 )
node . rescanblockchain ( )
# A different set of keys, no HD coins should be recovered, keys should be the same as they were the previous time
assert_equal ( new_addresses , ( node . getnewaddress ( ) , node . getrawchangeaddress ( ) ) )
assert_equal ( balance_non_HD , node . getbalance ( ) )
self . recover_non_hd ( )
self . log . info ( " Same mnemonic, no mnemonic passphrase, no wallet passphrase, should recover all coins after rescan " )
2021-08-27 21:03:02 +02:00
assert node . upgradetohd ( mnemonic )
2021-01-25 04:03:33 +01:00
assert_equal ( mnemonic , node . dumphdinfo ( ) [ ' mnemonic ' ] )
assert_equal ( chainid , node . getwalletinfo ( ) [ ' hdchainid ' ] )
node . keypoolrefill ( 5 )
2022-05-17 00:51:54 +02:00
assert balance_after != node . getbalance ( )
2021-01-25 04:03:33 +01:00
node . rescanblockchain ( )
assert_equal ( balance_after , node . getbalance ( ) )
self . recover_non_hd ( )
self . log . info ( " Same mnemonic, no mnemonic passphrase, no wallet passphrase, large enough keepool, should recover all coins with no extra rescan " )
2020-06-22 18:24:41 +02:00
self . restart_node ( 0 , extra_args = [ ' -keypool=10 ' ] )
2021-08-27 21:03:02 +02:00
assert node . upgradetohd ( mnemonic )
2021-01-25 04:03:33 +01:00
assert_equal ( mnemonic , node . dumphdinfo ( ) [ ' mnemonic ' ] )
assert_equal ( chainid , node . getwalletinfo ( ) [ ' hdchainid ' ] )
# All coins should be recovered
assert_equal ( balance_after , node . getbalance ( ) )
self . recover_non_hd ( )
2022-05-17 00:51:54 +02:00
self . log . info ( " Same mnemonic, no mnemonic passphrase, no wallet passphrase, large enough keepool, rescan is skipped initially, should recover all coins after rescanblockchain " )
2020-06-22 18:24:41 +02:00
self . restart_node ( 0 , extra_args = [ ' -keypool=10 ' ] )
2022-05-17 00:51:54 +02:00
assert node . upgradetohd ( mnemonic , " " , " " , False )
assert_equal ( mnemonic , node . dumphdinfo ( ) [ ' mnemonic ' ] )
assert_equal ( chainid , node . getwalletinfo ( ) [ ' hdchainid ' ] )
assert balance_after != node . getbalance ( )
node . rescanblockchain ( )
# All coins should be recovered
assert_equal ( balance_after , node . getbalance ( ) )
self . recover_non_hd ( )
2021-01-25 04:03:33 +01:00
self . log . info ( " Same mnemonic, same mnemonic passphrase, encrypt wallet on upgrade, should recover all coins after rescan " )
walletpass = " 111pass222 "
2018-09-14 10:28:27 +02:00
assert node . upgradetohd ( mnemonic , " " , walletpass )
node . stop ( )
2021-01-25 04:03:33 +01:00
node . wait_until_stopped ( )
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 ' ] )
2021-07-17 21:15:21 +02:00
# Note: wallet encryption results in additional keypool topup,
2021-01-25 04:03:33 +01:00
# so we can't compare new balance to balance_non_HD here,
# assert_equal(balance_non_HD, node.getbalance()) # won't work
2021-08-27 21:03:02 +02:00
assert balance_non_HD != node . getbalance ( )
2021-01-25 04:03:33 +01:00
node . keypoolrefill ( 4 )
node . rescanblockchain ( )
# All coins should be recovered
assert_equal ( balance_after , node . getbalance ( ) )
self . recover_non_hd ( )
self . log . info ( " Same mnemonic, same mnemonic passphrase, encrypt wallet first, should recover all coins on upgrade after rescan " )
walletpass = " 111pass222 "
node . encryptwallet ( walletpass )
2018-09-14 10:28:27 +02:00
node . stop ( )
2021-01-25 04:03:33 +01:00
node . wait_until_stopped ( )
self . start_node ( 0 , extra_args = [ ' -rescan ' ] )
2024-07-17 15:20:22 +02:00
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 " )
2021-08-27 21:03:02 +02:00
assert node . upgradetohd ( mnemonic , " " , walletpass )
2021-01-25 04:03:33 +01:00
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 ' ] )
2021-07-17 21:15:21 +02:00
# Note: wallet encryption results in additional keypool topup,
2021-01-25 04:03:33 +01:00
# so we can't compare new balance to balance_non_HD here,
# assert_equal(balance_non_HD, node.getbalance()) # won't work
2021-08-27 21:03:02 +02:00
assert balance_non_HD != node . getbalance ( )
2021-01-25 04:03:33 +01:00
node . keypoolrefill ( 4 )
node . rescanblockchain ( )
# All coins should be recovered
assert_equal ( balance_after , node . getbalance ( ) )
if __name__ == ' __main__ ' :
WalletUpgradeToHDTest ( ) . main ( )