2
2
# Copyright (c) 2014-2016 The Bitcoin Core developers
3
3
# Distributed under the MIT software license, see the accompanying
4
4
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
+ """Test rescan behavior of importaddress, importpubkey, importprivkey, and
6
+ importmulti RPCs with different types of keys and rescan options.
7
+
8
+ Test uses three connected nodes.
9
+
10
+ In the first part of the test, node 0 creates an address for each type of
11
+ import RPC call and sends BTC to it. Then nodes 1 and 2 import the addresses,
12
+ and the test makes listtransactions and getbalance calls to confirm that the
13
+ importing node either did or did not execute rescans picking up the send
14
+ transactions.
15
+
16
+ In the second part of the test, node 0 sends more BTC to each address, and the
17
+ test makes more listtransactions and getbalance calls to confirm that the
18
+ importing nodes pick up the new transactions regardless of whether rescans
19
+ happened previously.
20
+ """
5
21
6
22
from test_framework .test_framework import BitcoinTestFramework
7
23
from test_framework .util import (start_nodes , connect_nodes , sync_blocks , assert_equal )
10
26
import collections
11
27
import enum
12
28
import itertools
13
- import functools
14
29
15
30
Call = enum .Enum ("Call" , "single multi" )
16
31
Data = enum .Enum ("Data" , "address pub priv" )
17
- ImportNode = collections .namedtuple ("ImportNode" , "rescan" )
18
-
19
-
20
- def call_import_rpc (call , data , address , scriptPubKey , pubkey , key , label , node , rescan ):
21
- """Helper that calls a wallet import RPC on a bitcoin node."""
22
- watchonly = data != Data .priv
23
- if call == Call .single :
24
- if data == Data .address :
25
- response = node .importaddress (address , label , rescan )
26
- elif data == Data .pub :
27
- response = node .importpubkey (pubkey , label , rescan )
28
- elif data == Data .priv :
29
- response = node .importprivkey (key , label , rescan )
30
- assert_equal (response , None )
31
- elif call == Call .multi :
32
- response = node .importmulti ([{
33
- "scriptPubKey" : {
34
- "address" : address
35
- },
36
- "timestamp" : "now" ,
37
- "pubkeys" : [pubkey ] if data == Data .pub else [],
38
- "keys" : [key ] if data == Data .priv else [],
39
- "label" : label ,
40
- "watchonly" : watchonly
41
- }], {"rescan" : rescan })
42
- assert_equal (response , [{"success" : True }])
43
- return watchonly
44
-
45
-
46
- # List of RPCs that import a wallet key or address in various ways.
47
- IMPORT_RPCS = [functools .partial (call_import_rpc , call , data ) for call , data in itertools .product (Call , Data )]
48
-
49
- # List of bitcoind nodes that will import keys.
50
- IMPORT_NODES = [
51
- ImportNode (rescan = True ),
52
- ImportNode (rescan = False ),
53
- ]
32
+ Rescan = enum .Enum ("Rescan" , "no yes" )
33
+
34
+
35
+ class Variant (collections .namedtuple ("Variant" , "call data rescan" )):
36
+ """Helper for importing one key and verifying scanned transactions."""
37
+
38
+ def do_import (self ):
39
+ """Call one key import RPC."""
40
+
41
+ if self .call == Call .single :
42
+ if self .data == Data .address :
43
+ response = self .node .importaddress (self .address ["address" ], self .label , self .rescan == Rescan .yes )
44
+ elif self .data == Data .pub :
45
+ response = self .node .importpubkey (self .address ["pubkey" ], self .label , self .rescan == Rescan .yes )
46
+ elif self .data == Data .priv :
47
+ response = self .node .importprivkey (self .key , self .label , self .rescan == Rescan .yes )
48
+ assert_equal (response , None )
49
+ elif self .call == Call .multi :
50
+ response = self .node .importmulti ([{
51
+ "scriptPubKey" : {
52
+ "address" : self .address ["address" ]
53
+ },
54
+ "timestamp" : "now" ,
55
+ "pubkeys" : [self .address ["pubkey" ]] if self .data == Data .pub else [],
56
+ "keys" : [self .key ] if self .data == Data .priv else [],
57
+ "label" : self .label ,
58
+ "watchonly" : self .data != Data .priv
59
+ }], {"rescan" : self .rescan == Rescan .yes })
60
+ assert_equal (response , [{"success" : True }])
61
+
62
+ def check (self , txid = None , amount = None ):
63
+ """Verify that getbalance/listtransactions return expected values."""
64
+
65
+ balance = self .node .getbalance (self .label , 0 , True )
66
+ assert_equal (balance , self .expected_balance )
67
+
68
+ txs = self .node .listtransactions (self .label , 10000 , 0 , True )
69
+ assert_equal (len (txs ), self .expected_txs )
70
+
71
+ if txid is not None :
72
+ tx , = [tx for tx in txs if tx ["txid" ] == txid ]
73
+ assert_equal (tx ["account" ], self .label )
74
+ assert_equal (tx ["address" ], self .address ["address" ])
75
+ assert_equal (tx ["amount" ], amount )
76
+ assert_equal (tx ["category" ], "receive" )
77
+ assert_equal (tx ["label" ], self .label )
78
+ assert_equal (tx ["txid" ], txid )
79
+ assert_equal (tx ["confirmations" ], 1 )
80
+ assert_equal ("trusted" not in tx , True )
81
+ if self .data != Data .priv :
82
+ assert_equal (tx ["involvesWatchonly" ], True )
83
+ else :
84
+ assert_equal ("involvesWatchonly" not in tx , True )
85
+
86
+
87
+ # List of Variants for each way a key or address could be imported.
88
+ IMPORT_VARIANTS = [Variant (* variants ) for variants in itertools .product (Call , Data , Rescan )]
54
89
55
90
56
91
class ImportRescanTest (BitcoinTestFramework ):
57
92
def __init__ (self ):
58
93
super ().__init__ ()
59
- self .num_nodes = 1 + len ( IMPORT_NODES )
94
+ self .num_nodes = 3
60
95
61
96
def setup_network (self ):
62
97
extra_args = [["-debug=1" ] for _ in range (self .num_nodes )]
@@ -67,89 +102,54 @@ def setup_network(self):
67
102
def run_test (self ):
68
103
# Create one transaction on node 0 with a unique amount and label for
69
104
# each possible type of wallet import RPC.
70
- import_rpc_variants = []
71
- for i , import_rpc in enumerate (IMPORT_RPCS ):
72
- label = "label{}" .format (i )
73
- addr = self .nodes [0 ].validateaddress (self .nodes [0 ].getnewaddress (label ))
74
- key = self .nodes [0 ].dumpprivkey (addr ["address" ])
75
- amount = 24.9375 - i * .0625
76
- txid = self .nodes [0 ].sendtoaddress (addr ["address" ], amount )
77
- import_rpc = functools .partial (import_rpc , addr ["address" ], addr ["scriptPubKey" ], addr ["pubkey" ], key ,
78
- label )
79
- import_rpc_variants .append ((import_rpc , label , amount , txid , addr ))
80
-
105
+ for i , variant in enumerate (IMPORT_VARIANTS ):
106
+ variant .label = "label {} {}" .format (i , variant )
107
+ variant .address = self .nodes [0 ].validateaddress (self .nodes [0 ].getnewaddress (variant .label ))
108
+ variant .key = self .nodes [0 ].dumpprivkey (variant .address ["address" ])
109
+ variant .initial_amount = 25 - (i + 1 ) / 4.0
110
+ variant .initial_txid = self .nodes [0 ].sendtoaddress (variant .address ["address" ], variant .initial_amount )
111
+
112
+ # Generate a block containing the initial transactions.
81
113
self .nodes [0 ].generate (1 )
82
114
assert_equal (self .nodes [0 ].getrawmempool (), [])
83
115
sync_blocks (self .nodes )
84
116
85
- # For each importing node and variation of wallet import RPC, invoke
86
- # the RPC and check the results from getbalance and listtransactions.
87
- for node , import_node in zip (self .nodes [1 :], IMPORT_NODES ):
88
- for import_rpc , label , amount , txid , addr in import_rpc_variants :
89
- watchonly = import_rpc (node , import_node .rescan )
90
-
91
- balance = node .getbalance (label , 0 , True )
92
- if import_node .rescan :
93
- assert_equal (balance , amount )
94
- else :
95
- assert_equal (balance , 0 )
96
-
97
- txs = node .listtransactions (label , 10000 , 0 , True )
98
- if import_node .rescan :
99
- assert_equal (len (txs ), 1 )
100
- assert_equal (txs [0 ]["account" ], label )
101
- assert_equal (txs [0 ]["address" ], addr ["address" ])
102
- assert_equal (txs [0 ]["amount" ], amount )
103
- assert_equal (txs [0 ]["category" ], "receive" )
104
- assert_equal (txs [0 ]["label" ], label )
105
- assert_equal (txs [0 ]["txid" ], txid )
106
- assert_equal (txs [0 ]["confirmations" ], 1 )
107
- assert_equal ("trusted" not in txs [0 ], True )
108
- if watchonly :
109
- assert_equal (txs [0 ]["involvesWatchonly" ], True )
110
- else :
111
- assert_equal ("involvesWatchonly" not in txs [0 ], True )
112
- else :
113
- assert_equal (len (txs ), 0 )
114
-
115
- # Create spends for all the imported addresses.
116
- spend_txids = []
117
+ # For each variation of wallet key import, invoke the import RPC and
118
+ # check the results from getbalance and listtransactions. Import to
119
+ # node 1 if rescanning is expected, and to node 2 if rescanning is not
120
+ # expected. Node 2 is reserved for imports that do not cause rescans,
121
+ # so later import calls don't inadvertently cause the wallet to pick up
122
+ # transactions from earlier import calls where a rescan was not
123
+ # expected (this would make it complicated to figure out expected
124
+ # balances in the second part of the test.)
125
+ for variant in IMPORT_VARIANTS :
126
+ variant .node = self .nodes [1 if variant .rescan == Rescan .yes else 2 ]
127
+ variant .do_import ()
128
+ if variant .rescan == Rescan .yes :
129
+ variant .expected_balance = variant .initial_amount
130
+ variant .expected_txs = 1
131
+ variant .check (variant .initial_txid , variant .initial_amount )
132
+ else :
133
+ variant .expected_balance = 0
134
+ variant .expected_txs = 0
135
+ variant .check ()
136
+
137
+ # Create new transactions sending to each address.
117
138
fee = self .nodes [0 ].getnetworkinfo ()["relayfee" ]
118
- for import_rpc , label , amount , txid , addr in import_rpc_variants :
119
- raw_tx = self .nodes [0 ].getrawtransaction (txid )
120
- decoded_tx = self .nodes [0 ].decoderawtransaction (raw_tx )
121
- input_vout = next (out ["n" ] for out in decoded_tx ["vout" ]
122
- if out ["scriptPubKey" ]["addresses" ] == [addr ["address" ]])
123
- inputs = [{"txid" : txid , "vout" : input_vout }]
124
- outputs = {self .nodes [0 ].getnewaddress (): Decimal (amount ) - fee }
125
- raw_spend_tx = self .nodes [0 ].createrawtransaction (inputs , outputs )
126
- signed_spend_tx = self .nodes [0 ].signrawtransaction (raw_spend_tx )
127
- spend_txid = self .nodes [0 ].sendrawtransaction (signed_spend_tx ["hex" ])
128
- spend_txids .append (spend_txid )
139
+ for i , variant in enumerate (IMPORT_VARIANTS ):
140
+ variant .sent_amount = 25 - (2 * i + 1 ) / 8.0
141
+ variant .sent_txid = self .nodes [0 ].sendtoaddress (variant .address ["address" ], variant .sent_amount )
129
142
143
+ # Generate a block containing the new transactions.
130
144
self .nodes [0 ].generate (1 )
131
145
assert_equal (self .nodes [0 ].getrawmempool (), [])
132
146
sync_blocks (self .nodes )
133
147
134
- # Check the results from getbalance and listtransactions after the spends.
135
- for node , import_node in zip (self .nodes [1 :], IMPORT_NODES ):
136
- txs = node .listtransactions ("*" , 10000 , 0 , True )
137
- for (import_rpc , label , amount , txid , addr ), spend_txid in zip (import_rpc_variants , spend_txids ):
138
- balance = node .getbalance (label , 0 , True )
139
- spend_tx = [tx for tx in txs if tx ["txid" ] == spend_txid ]
140
- if import_node .rescan :
141
- assert_equal (balance , amount )
142
- assert_equal (len (spend_tx ), 1 )
143
- assert_equal (spend_tx [0 ]["account" ], "" )
144
- assert_equal (spend_tx [0 ]["amount" ] + spend_tx [0 ]["fee" ], - amount )
145
- assert_equal (spend_tx [0 ]["category" ], "send" )
146
- assert_equal ("label" not in spend_tx [0 ], True )
147
- assert_equal (spend_tx [0 ]["confirmations" ], 1 )
148
- assert_equal ("trusted" not in spend_tx [0 ], True )
149
- assert_equal ("involvesWatchonly" not in txs [0 ], True )
150
- else :
151
- assert_equal (balance , 0 )
152
- assert_equal (spend_tx , [])
148
+ # Check the latest results from getbalance and listtransactions.
149
+ for variant in IMPORT_VARIANTS :
150
+ variant .expected_balance += variant .sent_amount
151
+ variant .expected_txs += 1
152
+ variant .check (variant .sent_txid , variant .sent_amount )
153
153
154
154
155
155
if __name__ == "__main__" :
0 commit comments