[qa] Simplify import-rescan.py
Get rid of partial functions so the test can be more easily extended to add more variants of imports with options that affect rescanning (e.g. different key timestamps). Also change the second half of the test to send /to/ the imported addresses, instead of /from/ the imported addresses. The goal of this part of the test was to confirm that the wallet would pick up new transactions after an import regardless of whether or not a rescan happened during the import. But because the wallet can only do this reliably for incoming transactions and not outgoing transactions (which require the wallet to look up transaction inputs) the test previously was less meaningful than it should have been.
This commit is contained in:
parent
afae75fd3d
commit
8be0866883
1 changed files with 106 additions and 106 deletions
|
@ -2,6 +2,22 @@
|
||||||
# Copyright (c) 2014-2016 The Bitcoin Core developers
|
# Copyright (c) 2014-2016 The Bitcoin Core developers
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""Test rescan behavior of importaddress, importpubkey, importprivkey, and
|
||||||
|
importmulti RPCs with different types of keys and rescan options.
|
||||||
|
|
||||||
|
Test uses three connected nodes.
|
||||||
|
|
||||||
|
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 nodes 1 and 2 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import (start_nodes, connect_nodes, sync_blocks, assert_equal)
|
from test_framework.util import (start_nodes, connect_nodes, sync_blocks, assert_equal)
|
||||||
|
@ -10,53 +26,72 @@ from decimal import Decimal
|
||||||
import collections
|
import collections
|
||||||
import enum
|
import enum
|
||||||
import itertools
|
import itertools
|
||||||
import functools
|
|
||||||
|
|
||||||
Call = enum.Enum("Call", "single multi")
|
Call = enum.Enum("Call", "single multi")
|
||||||
Data = enum.Enum("Data", "address pub priv")
|
Data = enum.Enum("Data", "address pub priv")
|
||||||
ImportNode = collections.namedtuple("ImportNode", "rescan")
|
Rescan = enum.Enum("Rescan", "no yes")
|
||||||
|
|
||||||
|
|
||||||
def call_import_rpc(call, data, address, scriptPubKey, pubkey, key, label, node, rescan):
|
class Variant(collections.namedtuple("Variant", "call data rescan")):
|
||||||
"""Helper that calls a wallet import RPC on a bitcoin node."""
|
"""Helper for importing one key and verifying scanned transactions."""
|
||||||
watchonly = data != Data.priv
|
|
||||||
if call == Call.single:
|
def do_import(self):
|
||||||
if data == Data.address:
|
"""Call one key import RPC."""
|
||||||
response = node.importaddress(address, label, rescan)
|
|
||||||
elif data == Data.pub:
|
if self.call == Call.single:
|
||||||
response = node.importpubkey(pubkey, label, rescan)
|
if self.data == Data.address:
|
||||||
elif data == Data.priv:
|
response = self.node.importaddress(self.address["address"], self.label, self.rescan == Rescan.yes)
|
||||||
response = node.importprivkey(key, label, rescan)
|
elif self.data == Data.pub:
|
||||||
assert_equal(response, None)
|
response = self.node.importpubkey(self.address["pubkey"], self.label, self.rescan == Rescan.yes)
|
||||||
elif call == Call.multi:
|
elif self.data == Data.priv:
|
||||||
response = node.importmulti([{
|
response = self.node.importprivkey(self.key, self.label, self.rescan == Rescan.yes)
|
||||||
"scriptPubKey": {
|
assert_equal(response, None)
|
||||||
"address": address
|
elif self.call == Call.multi:
|
||||||
},
|
response = self.node.importmulti([{
|
||||||
"timestamp": "now",
|
"scriptPubKey": {
|
||||||
"pubkeys": [pubkey] if data == Data.pub else [],
|
"address": self.address["address"]
|
||||||
"keys": [key] if data == Data.priv else [],
|
},
|
||||||
"label": label,
|
"timestamp": "now",
|
||||||
"watchonly": watchonly
|
"pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [],
|
||||||
}], {"rescan": rescan})
|
"keys": [self.key] if self.data == Data.priv else [],
|
||||||
assert_equal(response, [{"success": True}])
|
"label": self.label,
|
||||||
return watchonly
|
"watchonly": self.data != Data.priv
|
||||||
|
}], {"rescan": self.rescan == Rescan.yes})
|
||||||
|
assert_equal(response, [{"success": True}])
|
||||||
|
|
||||||
|
def check(self, txid=None, amount=None):
|
||||||
|
"""Verify that getbalance/listtransactions return expected values."""
|
||||||
|
|
||||||
|
balance = self.node.getbalance(self.label, 0, True)
|
||||||
|
assert_equal(balance, self.expected_balance)
|
||||||
|
|
||||||
|
txs = self.node.listtransactions(self.label, 10000, 0, True)
|
||||||
|
assert_equal(len(txs), self.expected_txs)
|
||||||
|
|
||||||
|
if txid is not None:
|
||||||
|
tx, = [tx for tx in txs if tx["txid"] == txid]
|
||||||
|
assert_equal(tx["account"], 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"], 1)
|
||||||
|
assert_equal("trusted" not in tx, True)
|
||||||
|
if self.data != Data.priv:
|
||||||
|
assert_equal(tx["involvesWatchonly"], True)
|
||||||
|
else:
|
||||||
|
assert_equal("involvesWatchonly" not in tx, True)
|
||||||
|
|
||||||
|
|
||||||
# List of RPCs that import a wallet key or address in various ways.
|
# List of Variants for each way a key or address could be imported.
|
||||||
IMPORT_RPCS = [functools.partial(call_import_rpc, call, data) for call, data in itertools.product(Call, Data)]
|
IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, Rescan)]
|
||||||
|
|
||||||
# List of bitcoind nodes that will import keys.
|
|
||||||
IMPORT_NODES = [
|
|
||||||
ImportNode(rescan=True),
|
|
||||||
ImportNode(rescan=False),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class ImportRescanTest(BitcoinTestFramework):
|
class ImportRescanTest(BitcoinTestFramework):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.num_nodes = 1 + len(IMPORT_NODES)
|
self.num_nodes = 3
|
||||||
|
|
||||||
def setup_network(self):
|
def setup_network(self):
|
||||||
extra_args = [["-debug=1"] for _ in range(self.num_nodes)]
|
extra_args = [["-debug=1"] for _ in range(self.num_nodes)]
|
||||||
|
@ -67,89 +102,54 @@ class ImportRescanTest(BitcoinTestFramework):
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
# Create one transaction on node 0 with a unique amount and label for
|
# Create one transaction on node 0 with a unique amount and label for
|
||||||
# each possible type of wallet import RPC.
|
# each possible type of wallet import RPC.
|
||||||
import_rpc_variants = []
|
for i, variant in enumerate(IMPORT_VARIANTS):
|
||||||
for i, import_rpc in enumerate(IMPORT_RPCS):
|
variant.label = "label {} {}".format(i, variant)
|
||||||
label = "label{}".format(i)
|
variant.address = self.nodes[0].validateaddress(self.nodes[0].getnewaddress(variant.label))
|
||||||
addr = self.nodes[0].validateaddress(self.nodes[0].getnewaddress(label))
|
variant.key = self.nodes[0].dumpprivkey(variant.address["address"])
|
||||||
key = self.nodes[0].dumpprivkey(addr["address"])
|
variant.initial_amount = 25 - (i + 1) / 4.0
|
||||||
amount = 24.9375 - i * .0625
|
variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
|
||||||
txid = self.nodes[0].sendtoaddress(addr["address"], amount)
|
|
||||||
import_rpc = functools.partial(import_rpc, addr["address"], addr["scriptPubKey"], addr["pubkey"], key,
|
|
||||||
label)
|
|
||||||
import_rpc_variants.append((import_rpc, label, amount, txid, addr))
|
|
||||||
|
|
||||||
|
# Generate a block containing the initial transactions.
|
||||||
self.nodes[0].generate(1)
|
self.nodes[0].generate(1)
|
||||||
assert_equal(self.nodes[0].getrawmempool(), [])
|
assert_equal(self.nodes[0].getrawmempool(), [])
|
||||||
sync_blocks(self.nodes)
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
# For each importing node and variation of wallet import RPC, invoke
|
# For each variation of wallet key import, invoke the import RPC and
|
||||||
# the RPC and check the results from getbalance and listtransactions.
|
# check the results from getbalance and listtransactions. Import to
|
||||||
for node, import_node in zip(self.nodes[1:], IMPORT_NODES):
|
# node 1 if rescanning is expected, and to node 2 if rescanning is not
|
||||||
for import_rpc, label, amount, txid, addr in import_rpc_variants:
|
# expected. Node 2 is reserved for imports that do not cause rescans,
|
||||||
watchonly = import_rpc(node, import_node.rescan)
|
# so later import calls don't inadvertently cause the wallet to pick up
|
||||||
|
# transactions from earlier import calls where a rescan was not
|
||||||
|
# expected (this would make it complicated to figure out expected
|
||||||
|
# balances in the second part of the test.)
|
||||||
|
for variant in IMPORT_VARIANTS:
|
||||||
|
variant.node = self.nodes[1 if variant.rescan == Rescan.yes else 2]
|
||||||
|
variant.do_import()
|
||||||
|
if variant.rescan == Rescan.yes:
|
||||||
|
variant.expected_balance = variant.initial_amount
|
||||||
|
variant.expected_txs = 1
|
||||||
|
variant.check(variant.initial_txid, variant.initial_amount)
|
||||||
|
else:
|
||||||
|
variant.expected_balance = 0
|
||||||
|
variant.expected_txs = 0
|
||||||
|
variant.check()
|
||||||
|
|
||||||
balance = node.getbalance(label, 0, True)
|
# Create new transactions sending to each address.
|
||||||
if import_node.rescan:
|
|
||||||
assert_equal(balance, amount)
|
|
||||||
else:
|
|
||||||
assert_equal(balance, 0)
|
|
||||||
|
|
||||||
txs = node.listtransactions(label, 10000, 0, True)
|
|
||||||
if import_node.rescan:
|
|
||||||
assert_equal(len(txs), 1)
|
|
||||||
assert_equal(txs[0]["account"], label)
|
|
||||||
assert_equal(txs[0]["address"], addr["address"])
|
|
||||||
assert_equal(txs[0]["amount"], amount)
|
|
||||||
assert_equal(txs[0]["category"], "receive")
|
|
||||||
assert_equal(txs[0]["label"], label)
|
|
||||||
assert_equal(txs[0]["txid"], txid)
|
|
||||||
assert_equal(txs[0]["confirmations"], 1)
|
|
||||||
assert_equal("trusted" not in txs[0], True)
|
|
||||||
if watchonly:
|
|
||||||
assert_equal(txs[0]["involvesWatchonly"], True)
|
|
||||||
else:
|
|
||||||
assert_equal("involvesWatchonly" not in txs[0], True)
|
|
||||||
else:
|
|
||||||
assert_equal(len(txs), 0)
|
|
||||||
|
|
||||||
# Create spends for all the imported addresses.
|
|
||||||
spend_txids = []
|
|
||||||
fee = self.nodes[0].getnetworkinfo()["relayfee"]
|
fee = self.nodes[0].getnetworkinfo()["relayfee"]
|
||||||
for import_rpc, label, amount, txid, addr in import_rpc_variants:
|
for i, variant in enumerate(IMPORT_VARIANTS):
|
||||||
raw_tx = self.nodes[0].getrawtransaction(txid)
|
variant.sent_amount = 25 - (2 * i + 1) / 8.0
|
||||||
decoded_tx = self.nodes[0].decoderawtransaction(raw_tx)
|
variant.sent_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.sent_amount)
|
||||||
input_vout = next(out["n"] for out in decoded_tx["vout"]
|
|
||||||
if out["scriptPubKey"]["addresses"] == [addr["address"]])
|
|
||||||
inputs = [{"txid": txid, "vout": input_vout}]
|
|
||||||
outputs = {self.nodes[0].getnewaddress(): Decimal(amount) - fee}
|
|
||||||
raw_spend_tx = self.nodes[0].createrawtransaction(inputs, outputs)
|
|
||||||
signed_spend_tx = self.nodes[0].signrawtransaction(raw_spend_tx)
|
|
||||||
spend_txid = self.nodes[0].sendrawtransaction(signed_spend_tx["hex"])
|
|
||||||
spend_txids.append(spend_txid)
|
|
||||||
|
|
||||||
|
# Generate a block containing the new transactions.
|
||||||
self.nodes[0].generate(1)
|
self.nodes[0].generate(1)
|
||||||
assert_equal(self.nodes[0].getrawmempool(), [])
|
assert_equal(self.nodes[0].getrawmempool(), [])
|
||||||
sync_blocks(self.nodes)
|
sync_blocks(self.nodes)
|
||||||
|
|
||||||
# Check the results from getbalance and listtransactions after the spends.
|
# Check the latest results from getbalance and listtransactions.
|
||||||
for node, import_node in zip(self.nodes[1:], IMPORT_NODES):
|
for variant in IMPORT_VARIANTS:
|
||||||
txs = node.listtransactions("*", 10000, 0, True)
|
variant.expected_balance += variant.sent_amount
|
||||||
for (import_rpc, label, amount, txid, addr), spend_txid in zip(import_rpc_variants, spend_txids):
|
variant.expected_txs += 1
|
||||||
balance = node.getbalance(label, 0, True)
|
variant.check(variant.sent_txid, variant.sent_amount)
|
||||||
spend_tx = [tx for tx in txs if tx["txid"] == spend_txid]
|
|
||||||
if import_node.rescan:
|
|
||||||
assert_equal(balance, amount)
|
|
||||||
assert_equal(len(spend_tx), 1)
|
|
||||||
assert_equal(spend_tx[0]["account"], "")
|
|
||||||
assert_equal(spend_tx[0]["amount"] + spend_tx[0]["fee"], -amount)
|
|
||||||
assert_equal(spend_tx[0]["category"], "send")
|
|
||||||
assert_equal("label" not in spend_tx[0], True)
|
|
||||||
assert_equal(spend_tx[0]["confirmations"], 1)
|
|
||||||
assert_equal("trusted" not in spend_tx[0], True)
|
|
||||||
assert_equal("involvesWatchonly" not in txs[0], True)
|
|
||||||
else:
|
|
||||||
assert_equal(balance, 0)
|
|
||||||
assert_equal(spend_tx, [])
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
Loading…
Reference in a new issue