|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# Copyright (c) 2014-2016 The Bitcoin Core developers |
| 3 | +# Distributed under the MIT software license, see the accompanying |
| 4 | +# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 5 | + |
| 6 | +from test_framework.test_framework import BitcoinTestFramework |
| 7 | +from test_framework.util import (start_nodes, connect_nodes, sync_blocks, assert_equal) |
| 8 | +from decimal import Decimal |
| 9 | + |
| 10 | +import collections |
| 11 | +import enum |
| 12 | +import itertools |
| 13 | +import functools |
| 14 | + |
| 15 | +Call = enum.Enum("Call", "single multi") |
| 16 | +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 | + "pubkeys": [pubkey] if data == Data.pub else [], |
| 37 | + "keys": [key] if data == Data.priv else [], |
| 38 | + "label": label, |
| 39 | + "watchonly": watchonly |
| 40 | + }], {"rescan": rescan}) |
| 41 | + assert_equal(response, [{"success": True}]) |
| 42 | + return watchonly |
| 43 | + |
| 44 | + |
| 45 | +# List of RPCs that import a wallet key or address in various ways. |
| 46 | +IMPORT_RPCS = [functools.partial(call_import_rpc, call, data) for call, data in itertools.product(Call, Data)] |
| 47 | + |
| 48 | +# List of bitcoind nodes that will import keys. |
| 49 | +IMPORT_NODES = [ |
| 50 | + ImportNode(rescan=True), |
| 51 | + ImportNode(rescan=False), |
| 52 | +] |
| 53 | + |
| 54 | + |
| 55 | +class ImportRescanTest(BitcoinTestFramework): |
| 56 | + def __init__(self): |
| 57 | + super().__init__() |
| 58 | + self.num_nodes = 1 + len(IMPORT_NODES) |
| 59 | + |
| 60 | + def setup_network(self): |
| 61 | + extra_args = [["-debug=1"] for _ in range(self.num_nodes)] |
| 62 | + self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args) |
| 63 | + for i in range(1, self.num_nodes): |
| 64 | + connect_nodes(self.nodes[i], 0) |
| 65 | + |
| 66 | + def run_test(self): |
| 67 | + # Create one transaction on node 0 with a unique amount and label for |
| 68 | + # each possible type of wallet import RPC. |
| 69 | + import_rpc_variants = [] |
| 70 | + for i, import_rpc in enumerate(IMPORT_RPCS): |
| 71 | + label = "label{}".format(i) |
| 72 | + addr = self.nodes[0].validateaddress(self.nodes[0].getnewaddress(label)) |
| 73 | + key = self.nodes[0].dumpprivkey(addr["address"]) |
| 74 | + amount = 24.9375 - i * .0625 |
| 75 | + txid = self.nodes[0].sendtoaddress(addr["address"], amount) |
| 76 | + import_rpc = functools.partial(import_rpc, addr["address"], addr["scriptPubKey"], addr["pubkey"], key, |
| 77 | + label) |
| 78 | + import_rpc_variants.append((import_rpc, label, amount, txid, addr)) |
| 79 | + |
| 80 | + self.nodes[0].generate(1) |
| 81 | + assert_equal(self.nodes[0].getrawmempool(), []) |
| 82 | + sync_blocks(self.nodes) |
| 83 | + |
| 84 | + # For each importing node and variation of wallet import RPC, invoke |
| 85 | + # the RPC and check the results from getbalance and listtransactions. |
| 86 | + for node, import_node in zip(self.nodes[1:], IMPORT_NODES): |
| 87 | + for import_rpc, label, amount, txid, addr in import_rpc_variants: |
| 88 | + watchonly = import_rpc(node, import_node.rescan) |
| 89 | + |
| 90 | + balance = node.getbalance(label, 0, True) |
| 91 | + if import_node.rescan: |
| 92 | + assert_equal(balance, amount) |
| 93 | + else: |
| 94 | + assert_equal(balance, 0) |
| 95 | + |
| 96 | + txs = node.listtransactions(label, 10000, 0, True) |
| 97 | + if import_node.rescan: |
| 98 | + assert_equal(len(txs), 1) |
| 99 | + assert_equal(txs[0]["account"], label) |
| 100 | + assert_equal(txs[0]["address"], addr["address"]) |
| 101 | + assert_equal(txs[0]["amount"], amount) |
| 102 | + assert_equal(txs[0]["category"], "receive") |
| 103 | + assert_equal(txs[0]["label"], label) |
| 104 | + assert_equal(txs[0]["txid"], txid) |
| 105 | + assert_equal(txs[0]["confirmations"], 1) |
| 106 | + assert_equal("trusted" not in txs[0], True) |
| 107 | + if watchonly: |
| 108 | + assert_equal(txs[0]["involvesWatchonly"], True) |
| 109 | + else: |
| 110 | + assert_equal("involvesWatchonly" not in txs[0], True) |
| 111 | + else: |
| 112 | + assert_equal(len(txs), 0) |
| 113 | + |
| 114 | + # Create spends for all the imported addresses. |
| 115 | + spend_txids = [] |
| 116 | + fee = self.nodes[0].getnetworkinfo()["relayfee"] |
| 117 | + for import_rpc, label, amount, txid, addr in import_rpc_variants: |
| 118 | + raw_tx = self.nodes[0].getrawtransaction(txid) |
| 119 | + decoded_tx = self.nodes[0].decoderawtransaction(raw_tx) |
| 120 | + input_vout = next(out["n"] for out in decoded_tx["vout"] |
| 121 | + if out["scriptPubKey"]["addresses"] == [addr["address"]]) |
| 122 | + inputs = [{"txid": txid, "vout": input_vout}] |
| 123 | + outputs = {self.nodes[0].getnewaddress(): Decimal(amount) - fee} |
| 124 | + raw_spend_tx = self.nodes[0].createrawtransaction(inputs, outputs) |
| 125 | + signed_spend_tx = self.nodes[0].signrawtransaction(raw_spend_tx) |
| 126 | + spend_txid = self.nodes[0].sendrawtransaction(signed_spend_tx["hex"]) |
| 127 | + spend_txids.append(spend_txid) |
| 128 | + |
| 129 | + self.nodes[0].generate(1) |
| 130 | + assert_equal(self.nodes[0].getrawmempool(), []) |
| 131 | + sync_blocks(self.nodes) |
| 132 | + |
| 133 | + # Check the results from getbalance and listtransactions after the spends. |
| 134 | + for node, import_node in zip(self.nodes[1:], IMPORT_NODES): |
| 135 | + txs = node.listtransactions("*", 10000, 0, True) |
| 136 | + for (import_rpc, label, amount, txid, addr), spend_txid in zip(import_rpc_variants, spend_txids): |
| 137 | + balance = node.getbalance(label, 0, True) |
| 138 | + spend_tx = [tx for tx in txs if tx["txid"] == spend_txid] |
| 139 | + if import_node.rescan: |
| 140 | + assert_equal(balance, amount) |
| 141 | + assert_equal(len(spend_tx), 1) |
| 142 | + assert_equal(spend_tx[0]["account"], "") |
| 143 | + assert_equal(spend_tx[0]["amount"] + spend_tx[0]["fee"], -amount) |
| 144 | + assert_equal(spend_tx[0]["category"], "send") |
| 145 | + assert_equal("label" not in spend_tx[0], True) |
| 146 | + assert_equal(spend_tx[0]["confirmations"], 1) |
| 147 | + assert_equal("trusted" not in spend_tx[0], True) |
| 148 | + assert_equal("involvesWatchonly" not in txs[0], True) |
| 149 | + else: |
| 150 | + assert_equal(balance, 0) |
| 151 | + assert_equal(spend_tx, []) |
| 152 | + |
| 153 | + |
| 154 | +if __name__ == "__main__": |
| 155 | + ImportRescanTest().main() |
0 commit comments