2017-12-21 20:33:47 +01:00
#!/usr/bin/env python3
2017-05-29 13:51:40 +02:00
# 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.
""" Test Hierarchical Deterministic wallet function. """
2019-08-09 01:21:11 +02:00
import shutil
2017-10-14 00:23:04 +02:00
import os
2019-08-09 01:21:11 +02:00
2017-05-29 13:51:40 +02:00
from test_framework . test_framework import BitcoinTestFramework
2018-03-22 11:04:37 +01:00
from test_framework . util import (
assert_equal ,
)
2017-05-29 13:51:40 +02:00
class WalletHDTest ( BitcoinTestFramework ) :
2017-09-01 18:47:13 +02:00
def set_test_params ( self ) :
2018-04-18 13:48:59 +02:00
self . setup_clean_chain = True
self . num_nodes = 2
2017-05-02 20:02:55 +02:00
self . extra_args = [ [ ' -usehd=0 ' ] , [ ' -usehd=1 ' , ' -keypool=0 ' ] ]
2017-05-29 13:51:40 +02:00
2019-06-20 18:36:17 +02:00
def setup_network ( self ) :
2021-06-17 19:05:11 +02:00
self . add_nodes ( self . num_nodes , self . extra_args )
2019-09-24 00:54:00 +02:00
self . start_nodes ( )
2018-11-02 16:41:29 +01:00
self . import_deterministic_coinbase_privkeys ( )
2019-06-20 18:36:17 +02:00
2018-09-13 12:33:15 +02:00
def skip_test_if_missing_module ( self ) :
self . skip_if_no_wallet ( )
2018-03-22 11:04:37 +01:00
def run_test ( self ) :
2017-05-29 13:51:40 +02:00
# Make sure can't switch off usehd after wallet creation
2017-03-06 10:04:02 +01:00
self . stop_node ( 1 )
2023-02-14 09:48:36 +01:00
self . nodes [ 1 ] . 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 )
2019-09-24 00:54:00 +02:00
self . start_node ( 1 )
2022-09-24 14:36:35 +02:00
self . connect_nodes ( 0 , 1 )
2017-05-29 13:51:40 +02:00
# Make sure we use hd, keep chainid
chainid = self . nodes [ 1 ] . getwalletinfo ( ) [ ' hdchainid ' ]
assert_equal ( len ( chainid ) , 64 )
# create an internal key
change_addr = self . nodes [ 1 ] . getrawchangeaddress ( )
2020-12-17 13:46:20 +01:00
change_addrV = self . nodes [ 1 ] . getaddressinfo ( change_addr )
2017-05-29 13:51:40 +02:00
assert_equal ( change_addrV [ " hdkeypath " ] , " m/44 ' /1 ' /0 ' /1/0 " ) #first internal child key
# Import a non-HD private key in the HD wallet
non_hd_add = self . nodes [ 0 ] . getnewaddress ( )
self . nodes [ 1 ] . importprivkey ( self . nodes [ 0 ] . dumpprivkey ( non_hd_add ) )
# This should be enough to keep the master key and the non-HD key
2018-03-22 11:04:37 +01:00
self . nodes [ 1 ] . backupwallet ( os . path . join ( self . nodes [ 1 ] . datadir , " hd.bak " ) )
#self.nodes[1].dumpwallet(os.path.join(self.nodes[1].datadir, "hd.dump"))
2017-05-29 13:51:40 +02:00
# Derive some HD addresses and remember the last
# Also send funds to each add
self . nodes [ 0 ] . generate ( 101 )
hd_add = None
2018-04-11 23:11:06 +02:00
NUM_HD_ADDS = 10
for i in range ( NUM_HD_ADDS ) :
2017-05-29 13:51:40 +02:00
hd_add = self . nodes [ 1 ] . getnewaddress ( )
2020-12-17 13:46:20 +01:00
hd_info = self . nodes [ 1 ] . getaddressinfo ( hd_add )
2020-02-04 13:34:18 +01:00
assert_equal ( hd_info [ " hdkeypath " ] , " m/44 ' /1 ' /0 ' /0/ " + str ( i ) )
2017-05-29 13:51:40 +02:00
assert_equal ( hd_info [ " hdchainid " ] , chainid )
self . nodes [ 0 ] . sendtoaddress ( hd_add , 1 )
self . nodes [ 0 ] . generate ( 1 )
self . nodes [ 0 ] . sendtoaddress ( non_hd_add , 1 )
self . nodes [ 0 ] . generate ( 1 )
# create an internal key (again)
change_addr = self . nodes [ 1 ] . getrawchangeaddress ( )
2020-12-17 13:46:20 +01:00
change_addrV = self . nodes [ 1 ] . getaddressinfo ( change_addr )
2017-05-29 13:51:40 +02:00
assert_equal ( change_addrV [ " hdkeypath " ] , " m/44 ' /1 ' /0 ' /1/1 " ) #second internal child key
self . sync_all ( )
2018-04-11 23:11:06 +02:00
assert_equal ( self . nodes [ 1 ] . getbalance ( ) , NUM_HD_ADDS + 1 )
2017-05-29 13:51:40 +02:00
2017-03-09 21:16:20 +01:00
self . log . info ( " Restore backup ... " )
2019-07-04 16:48:01 +02:00
self . stop_node ( 1 )
2021-01-22 15:58:07 +01:00
# we need to delete the complete chain directory
2017-08-14 16:03:26 +02:00
# otherwise node1 would auto-recover all funds in flag the keypool keys as used
2021-01-22 15:58:07 +01:00
shutil . rmtree ( os . path . join ( self . nodes [ 1 ] . datadir , self . chain , " blocks " ) )
shutil . rmtree ( os . path . join ( self . nodes [ 1 ] . datadir , self . chain , " chainstate " ) )
shutil . rmtree ( os . path . join ( self . nodes [ 1 ] . datadir , self . chain , " evodb " ) )
shutil . rmtree ( os . path . join ( self . nodes [ 1 ] . datadir , self . chain , " llmq " ) )
2022-11-30 20:23:48 +01:00
shutil . copyfile (
os . path . join ( self . nodes [ 1 ] . datadir , " hd.bak " ) ,
os . path . join ( self . nodes [ 1 ] . datadir , self . chain , ' wallets ' , self . default_wallet_name , self . wallet_data_filename ) ,
)
2019-09-24 00:54:00 +02:00
self . start_node ( 1 )
2017-05-29 13:51:40 +02:00
# Assert that derivation is deterministic
hd_add_2 = None
2018-04-11 23:11:06 +02:00
for i in range ( NUM_HD_ADDS ) :
2017-05-29 13:51:40 +02:00
hd_add_2 = self . nodes [ 1 ] . getnewaddress ( )
2020-12-17 13:46:20 +01:00
hd_info_2 = self . nodes [ 1 ] . getaddressinfo ( hd_add_2 )
2018-04-11 23:11:06 +02:00
assert_equal ( hd_info_2 [ " hdkeypath " ] , " m/44 ' /1 ' /0 ' /0/ " + str ( i ) )
2017-05-29 13:51:40 +02:00
assert_equal ( hd_info_2 [ " hdchainid " ] , chainid )
assert_equal ( hd_add , hd_add_2 )
2022-09-24 14:36:35 +02:00
self . connect_nodes ( 0 , 1 )
2017-08-14 16:03:26 +02:00
self . sync_all ( )
2017-05-29 13:51:40 +02:00
# Needs rescan
2017-05-02 20:02:55 +02:00
self . stop_node ( 1 )
2019-09-24 00:54:00 +02:00
self . start_node ( 1 , extra_args = self . extra_args [ 1 ] + [ ' -rescan ' ] )
2018-04-11 23:11:06 +02:00
assert_equal ( self . nodes [ 1 ] . getbalance ( ) , NUM_HD_ADDS + 1 )
2017-05-29 13:51:40 +02:00
2017-10-14 00:23:04 +02:00
# Try a RPC based rescan
self . stop_node ( 1 )
2021-01-22 15:58:07 +01:00
shutil . rmtree ( os . path . join ( self . nodes [ 1 ] . datadir , self . chain , " blocks " ) )
shutil . rmtree ( os . path . join ( self . nodes [ 1 ] . datadir , self . chain , " chainstate " ) )
shutil . rmtree ( os . path . join ( self . nodes [ 1 ] . datadir , self . chain , " evodb " ) )
shutil . rmtree ( os . path . join ( self . nodes [ 1 ] . datadir , self . chain , " llmq " ) )
2022-11-30 20:23:48 +01:00
shutil . copyfile (
os . path . join ( self . nodes [ 1 ] . datadir , " hd.bak " ) ,
os . path . join ( self . nodes [ 1 ] . datadir , self . chain , " wallets " , self . default_wallet_name , self . wallet_data_filename ) ,
)
2017-10-14 00:23:04 +02:00
self . start_node ( 1 , extra_args = self . extra_args [ 1 ] )
2022-09-24 14:36:35 +02:00
self . connect_nodes ( 0 , 1 )
2017-10-14 00:23:04 +02:00
self . sync_all ( )
2018-04-11 23:11:06 +02:00
# Wallet automatically scans blocks older than key on startup
assert_equal ( self . nodes [ 1 ] . getbalance ( ) , NUM_HD_ADDS + 1 )
2017-10-14 00:23:04 +02:00
out = self . nodes [ 1 ] . rescanblockchain ( 0 , 1 )
assert_equal ( out [ ' start_height ' ] , 0 )
assert_equal ( out [ ' stop_height ' ] , 1 )
out = self . nodes [ 1 ] . rescanblockchain ( )
assert_equal ( out [ ' start_height ' ] , 0 )
assert_equal ( out [ ' stop_height ' ] , self . nodes [ 1 ] . getblockcount ( ) )
2018-04-11 23:11:06 +02:00
assert_equal ( self . nodes [ 1 ] . getbalance ( ) , NUM_HD_ADDS + 1 )
2017-10-14 00:23:04 +02:00
2017-05-29 13:51:40 +02:00
# send a tx and make sure its using the internal chain for the changeoutput
txid = self . nodes [ 1 ] . sendtoaddress ( self . nodes [ 0 ] . getnewaddress ( ) , 1 )
2017-05-02 20:50:57 +02:00
outs = self . nodes [ 1 ] . decoderawtransaction ( self . nodes [ 1 ] . gettransaction ( txid ) [ ' hex ' ] ) [ ' vout ' ]
2017-05-29 13:51:40 +02:00
keypath = " "
for out in outs :
if out [ ' value ' ] != 1 :
2020-12-17 13:46:20 +01:00
keypath = self . nodes [ 1 ] . getaddressinfo ( out [ ' scriptPubKey ' ] [ ' addresses ' ] [ 0 ] ) [ ' hdkeypath ' ]
2017-05-29 13:51:40 +02:00
assert_equal ( keypath [ 0 : 13 ] , " m/44 ' /1 ' /0 ' /1 " )
if __name__ == ' __main__ ' :
WalletHDTest ( ) . main ( )