lbrycrd/qa/rpc-tests/import-rescan.py
Russell Yanofsky 442887f27f Require timestamps for importmulti keys
Additionally, accept a "now" timestamp, to allow avoiding rescans for keys
which are known never to have been used.

Note that the behavior when "now" is specified is slightly different than the
previous behavior when no timestamp was specified at all. Previously, when no
timestamp was specified, it would avoid rescanning during the importmulti call,
but set the key's nCreateTime value to 1, which would not prevent future block
reads in later ScanForWalletTransactions calls. With this change, passing a
"now" timestamp will set the key's nCreateTime to the current block time
instead of 1.

Fixes #9491
2017-02-10 15:40:28 -05:00

157 lines
6.9 KiB
Python
Executable file

#!/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.
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (start_nodes, connect_nodes, sync_blocks, assert_equal)
from decimal import Decimal
import collections
import enum
import itertools
import functools
Call = enum.Enum("Call", "single multi")
Data = enum.Enum("Data", "address pub priv")
ImportNode = collections.namedtuple("ImportNode", "rescan")
def call_import_rpc(call, data, address, scriptPubKey, pubkey, key, label, node, rescan):
"""Helper that calls a wallet import RPC on a bitcoin node."""
watchonly = data != Data.priv
if call == Call.single:
if data == Data.address:
response = node.importaddress(address, label, rescan)
elif data == Data.pub:
response = node.importpubkey(pubkey, label, rescan)
elif data == Data.priv:
response = node.importprivkey(key, label, rescan)
assert_equal(response, None)
elif call == Call.multi:
response = node.importmulti([{
"scriptPubKey": {
"address": address
},
"timestamp": "now",
"pubkeys": [pubkey] if data == Data.pub else [],
"keys": [key] if data == Data.priv else [],
"label": label,
"watchonly": watchonly
}], {"rescan": rescan})
assert_equal(response, [{"success": True}])
return watchonly
# List of RPCs that import a wallet key or address in various ways.
IMPORT_RPCS = [functools.partial(call_import_rpc, call, data) for call, data in itertools.product(Call, Data)]
# List of bitcoind nodes that will import keys.
IMPORT_NODES = [
ImportNode(rescan=True),
ImportNode(rescan=False),
]
class ImportRescanTest(BitcoinTestFramework):
def __init__(self):
super().__init__()
self.num_nodes = 1 + len(IMPORT_NODES)
def setup_network(self):
extra_args = [["-debug=1"] for _ in range(self.num_nodes)]
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args)
for i in range(1, self.num_nodes):
connect_nodes(self.nodes[i], 0)
def run_test(self):
# Create one transaction on node 0 with a unique amount and label for
# each possible type of wallet import RPC.
import_rpc_variants = []
for i, import_rpc in enumerate(IMPORT_RPCS):
label = "label{}".format(i)
addr = self.nodes[0].validateaddress(self.nodes[0].getnewaddress(label))
key = self.nodes[0].dumpprivkey(addr["address"])
amount = 24.9375 - i * .0625
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))
self.nodes[0].generate(1)
assert_equal(self.nodes[0].getrawmempool(), [])
sync_blocks(self.nodes)
# For each importing node and variation of wallet import RPC, invoke
# the RPC and check the results from getbalance and listtransactions.
for node, import_node in zip(self.nodes[1:], IMPORT_NODES):
for import_rpc, label, amount, txid, addr in import_rpc_variants:
watchonly = import_rpc(node, import_node.rescan)
balance = node.getbalance(label, 0, True)
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"]
for import_rpc, label, amount, txid, addr in import_rpc_variants:
raw_tx = self.nodes[0].getrawtransaction(txid)
decoded_tx = self.nodes[0].decoderawtransaction(raw_tx)
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)
self.nodes[0].generate(1)
assert_equal(self.nodes[0].getrawmempool(), [])
sync_blocks(self.nodes)
# Check the results from getbalance and listtransactions after the spends.
for node, import_node in zip(self.nodes[1:], IMPORT_NODES):
txs = node.listtransactions("*", 10000, 0, True)
for (import_rpc, label, amount, txid, addr), spend_txid in zip(import_rpc_variants, spend_txids):
balance = node.getbalance(label, 0, True)
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__":
ImportRescanTest().main()