2016-12-15 17:04:32 +01:00
#!/usr/bin/env python3
# Copyright (c) 2014-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.
2019-01-07 10:55:35 +01:00
""" Test wallet import RPCs.
Test rescan behavior of importaddress , importpubkey , importprivkey , and
2017-02-17 12:53:31 +01:00
importmulti RPCs with different types of keys and rescan options .
2017-03-09 21:16:20 +01:00
In the first part of the test , node 0 creates an address for each type of
import RPC call and sends BTC to it . Then other nodes import the addresses ,
and the test makes listtransactions and getbalance calls to confirm that the
importing node either did or did not execute rescans picking up the send
transactions .
2017-02-17 12:53:31 +01:00
In the second part of the test , node 0 sends more BTC to each address , and the
test makes more listtransactions and getbalance calls to confirm that the
importing nodes pick up the new transactions regardless of whether rescans
happened previously .
"""
2016-12-15 17:04:32 +01:00
from test_framework . test_framework import BitcoinTestFramework
2020-06-14 03:58:03 +02:00
from test_framework . util import ( assert_raises_rpc_error , connect_nodes , assert_equal , set_node_times )
2016-12-15 17:04:32 +01:00
import collections
import enum
import itertools
Call = enum . Enum ( " Call " , " single multi " )
Data = enum . Enum ( " Data " , " address pub priv " )
2017-02-17 12:53:31 +01:00
Rescan = enum . Enum ( " Rescan " , " no yes late_timestamp " )
class Variant ( collections . namedtuple ( " Variant " , " call data rescan prune " ) ) :
""" Helper for importing one key and verifying scanned transactions. """
2017-07-12 16:29:02 +02:00
def try_rpc ( self , func , * args , * * kwargs ) :
if self . expect_disabled :
2019-09-25 11:34:51 +02:00
assert_raises_rpc_error ( - 4 , " Rescan is disabled in pruned mode " , func , * args , * * kwargs )
2017-07-12 16:29:02 +02:00
else :
return func ( * args , * * kwargs )
2017-02-17 12:53:31 +01:00
def do_import ( self , timestamp ) :
""" Call one key import RPC. """
2018-05-11 00:08:57 +02:00
rescan = self . rescan == Rescan . yes
2017-02-17 12:53:31 +01:00
if self . call == Call . single :
if self . data == Data . address :
2018-11-10 12:45:39 +01:00
response = self . try_rpc ( self . node . importaddress , address = self . address [ " address " ] , label = self . label , rescan = rescan )
2017-02-17 12:53:31 +01:00
elif self . data == Data . pub :
2018-11-10 12:45:39 +01:00
response = self . try_rpc ( self . node . importpubkey , pubkey = self . address [ " pubkey " ] , label = self . label , rescan = rescan )
2017-02-17 12:53:31 +01:00
elif self . data == Data . priv :
2018-11-10 12:45:39 +01:00
response = self . try_rpc ( self . node . importprivkey , privkey = self . key , label = self . label , rescan = rescan )
2017-02-17 12:53:31 +01:00
assert_equal ( response , None )
2017-07-12 16:29:02 +02:00
2017-02-17 12:53:31 +01:00
elif self . call == Call . multi :
response = self . node . importmulti ( [ {
" scriptPubKey " : {
" address " : self . address [ " address " ]
} ,
2017-03-06 10:01:25 +01:00
" timestamp " : timestamp + TIMESTAMP_WINDOW + ( 1 if self . rescan == Rescan . late_timestamp else 0 ) ,
2017-02-17 12:53:31 +01:00
" pubkeys " : [ self . address [ " pubkey " ] ] if self . data == Data . pub else [ ] ,
" keys " : [ self . key ] if self . data == Data . priv else [ ] ,
2018-11-10 12:45:39 +01:00
" label " : self . label ,
2017-02-17 12:53:31 +01:00
" watchonly " : self . data != Data . priv
} ] , { " rescan " : self . rescan in ( Rescan . yes , Rescan . late_timestamp ) } )
assert_equal ( response , [ { " success " : True } ] )
def check ( self , txid = None , amount = None , confirmations = None ) :
2018-11-10 12:45:39 +01:00
""" Verify that listtransactions/listreceivedbyaddress return expected values. """
txs = self . node . listtransactions ( label = self . label , count = 10000 , skip = 0 , include_watchonly = True )
assert_equal ( len ( txs ) , self . expected_txs )
2017-02-17 12:53:31 +01:00
2018-05-11 00:08:57 +02:00
addresses = self . node . listreceivedbyaddress ( minconf = 0 , include_watchonly = True , address_filter = self . address [ ' address ' ] )
if self . expected_txs :
assert_equal ( len ( addresses [ 0 ] [ " txids " ] ) , self . expected_txs )
2017-02-17 12:53:31 +01:00
if txid is not None :
2018-11-10 12:45:39 +01:00
tx , = [ tx for tx in txs if tx [ " txid " ] == txid ]
assert_equal ( tx [ " label " ] , self . label )
assert_equal ( tx [ " address " ] , self . address [ " address " ] )
assert_equal ( tx [ " amount " ] , amount )
assert_equal ( tx [ " category " ] , " receive " )
assert_equal ( tx [ " label " ] , self . label )
assert_equal ( tx [ " txid " ] , txid )
assert_equal ( tx [ " confirmations " ] , confirmations )
assert_equal ( " trusted " not in tx , True )
2018-05-11 00:08:57 +02:00
address , = [ ad for ad in addresses if txid in ad [ " txids " ] ]
assert_equal ( address [ " address " ] , self . address [ " address " ] )
assert_equal ( address [ " amount " ] , self . expected_balance )
assert_equal ( address [ " confirmations " ] , confirmations )
2019-01-07 10:55:35 +01:00
# Verify the transaction is correctly marked watchonly depending on
# whether the transaction pays to an imported public key or
# imported private key. The test setup ensures that transaction
# inputs will not be from watchonly keys (important because
# involvesWatchonly will be true if either the transaction output
# or inputs are watchonly).
2017-02-17 12:53:31 +01:00
if self . data != Data . priv :
2018-05-11 00:08:57 +02:00
assert_equal ( address [ " involvesWatchonly " ] , True )
2017-02-17 12:53:31 +01:00
else :
2018-05-11 00:08:57 +02:00
assert_equal ( " involvesWatchonly " not in address , True )
2017-02-17 12:53:31 +01:00
# List of Variants for each way a key or address could be imported.
IMPORT_VARIANTS = [ Variant ( * variants ) for variants in itertools . product ( Call , Data , Rescan , ( False , True ) ) ]
# List of nodes to import keys to. Half the nodes will have pruning disabled,
# half will have it enabled. Different nodes will be used for imports that are
# expected to cause rescans, and imports that are not expected to cause
# rescans, in order to prevent rescans during later imports picking up
# transactions associated with earlier imports. This makes it easier to keep
# track of expected balances and transactions.
ImportNode = collections . namedtuple ( " ImportNode " , " prune rescan " )
IMPORT_NODES = [ ImportNode ( * fields ) for fields in itertools . product ( ( False , True ) , repeat = 2 ) ]
# Rescans start at the earliest block up to 2 hours before the key timestamp.
2017-03-06 10:01:25 +01:00
TIMESTAMP_WINDOW = 2 * 60 * 60
2016-12-15 17:04:32 +01:00
class ImportRescanTest ( BitcoinTestFramework ) :
2017-09-01 18:47:13 +02:00
def set_test_params ( self ) :
2019-01-07 10:55:35 +01:00
self . num_nodes = 2 + len ( IMPORT_NODES )
2016-12-15 17:04:32 +01:00
def setup_network ( self ) :
2018-05-11 00:08:57 +02:00
extra_args = [ [ ] for _ in range ( self . num_nodes ) ]
2019-01-07 10:55:35 +01:00
for i , import_node in enumerate ( IMPORT_NODES , 2 ) :
2017-02-17 12:53:31 +01:00
if import_node . prune :
2018-01-31 13:22:29 +01:00
# txindex is enabled by default in Dash and needs to be disabled for import-rescan.py
2019-09-20 15:12:07 +02:00
extra_args [ i ] + = [ " -prune=1 " , " -txindex=0 " , " -reindex " ]
2017-02-17 12:53:31 +01:00
2021-06-17 19:05:11 +02:00
self . add_nodes ( self . num_nodes , extra_args = extra_args )
2019-09-24 00:54:00 +02:00
self . start_nodes ( )
2016-12-15 17:04:32 +01:00
for i in range ( 1 , self . num_nodes ) :
connect_nodes ( self . nodes [ i ] , 0 )
def run_test ( self ) :
2018-05-11 00:08:57 +02:00
# Create one transaction on node 0 with a unique amount for
2016-12-15 17:04:32 +01:00
# each possible type of wallet import RPC.
2017-02-17 12:53:31 +01:00
for i , variant in enumerate ( IMPORT_VARIANTS ) :
2018-11-10 12:45:39 +01:00
variant . label = " label {} {} " . format ( i , variant )
variant . address = self . nodes [ 1 ] . getaddressinfo ( self . nodes [ 1 ] . getnewaddress ( variant . label ) )
2019-01-07 10:55:35 +01:00
variant . key = self . nodes [ 1 ] . dumpprivkey ( variant . address [ " address " ] )
variant . initial_amount = 10 - ( i + 1 ) / 4.0
2017-02-17 12:53:31 +01:00
variant . initial_txid = self . nodes [ 0 ] . sendtoaddress ( variant . address [ " address " ] , variant . initial_amount )
# Generate a block containing the initial transactions, then another
# block further in the future (past the rescan window).
2016-12-15 17:04:32 +01:00
self . nodes [ 0 ] . generate ( 1 )
assert_equal ( self . nodes [ 0 ] . getrawmempool ( ) , [ ] )
2017-02-17 12:53:31 +01:00
timestamp = self . nodes [ 0 ] . getblockheader ( self . nodes [ 0 ] . getbestblockhash ( ) ) [ " time " ]
2017-03-06 10:01:25 +01:00
set_node_times ( self . nodes , timestamp + TIMESTAMP_WINDOW + 1 )
2017-02-17 12:53:31 +01:00
self . nodes [ 0 ] . generate ( 1 )
2020-04-14 12:00:16 +02:00
self . sync_blocks ( )
2016-12-15 17:04:32 +01:00
2017-02-17 12:53:31 +01:00
# For each variation of wallet key import, invoke the import RPC and
# check the results from getbalance and listtransactions.
for variant in IMPORT_VARIANTS :
variant . expect_disabled = variant . rescan == Rescan . yes and variant . prune and variant . call == Call . single
expect_rescan = variant . rescan == Rescan . yes and not variant . expect_disabled
2019-01-07 10:55:35 +01:00
variant . node = self . nodes [ 2 + IMPORT_NODES . index ( ImportNode ( variant . prune , expect_rescan ) ) ]
2017-02-17 12:53:31 +01:00
variant . do_import ( timestamp )
if expect_rescan :
variant . expected_balance = variant . initial_amount
variant . expected_txs = 1
variant . check ( variant . initial_txid , variant . initial_amount , 2 )
else :
variant . expected_balance = 0
variant . expected_txs = 0
variant . check ( )
# Create new transactions sending to each address.
for i , variant in enumerate ( IMPORT_VARIANTS ) :
2019-01-07 10:55:35 +01:00
variant . sent_amount = 10 - ( 2 * i + 1 ) / 8.0
2017-02-17 12:53:31 +01:00
variant . sent_txid = self . nodes [ 0 ] . sendtoaddress ( variant . address [ " address " ] , variant . sent_amount )
2016-12-15 17:04:32 +01:00
2017-02-17 12:53:31 +01:00
# Generate a block containing the new transactions.
2016-12-15 17:04:32 +01:00
self . nodes [ 0 ] . generate ( 1 )
assert_equal ( self . nodes [ 0 ] . getrawmempool ( ) , [ ] )
2020-04-14 12:00:16 +02:00
self . sync_blocks ( )
2016-12-15 17:04:32 +01:00
2017-02-17 12:53:31 +01:00
# Check the latest results from getbalance and listtransactions.
for variant in IMPORT_VARIANTS :
if not variant . expect_disabled :
variant . expected_balance + = variant . sent_amount
variant . expected_txs + = 1
variant . check ( variant . sent_txid , variant . sent_amount , 1 )
else :
variant . check ( )
2021-06-17 20:26:27 +02:00
for i , import_node in enumerate ( IMPORT_NODES , 2 ) :
if import_node . prune :
self . stop_node ( i , expected_stderr = ' Warning: You are starting with governance validation disabled. This is expected because you are running a pruned node. ' )
2017-02-17 12:53:31 +01:00
2016-12-15 17:04:32 +01:00
if __name__ == " __main__ " :
ImportRescanTest ( ) . main ( )