Merge pull request #1887 from codablock/pr_backport_hd_wallet

Backport wallet-dump.py and key timestamp PRs
This commit is contained in:
UdjinM6 2018-02-01 15:40:23 +03:00 committed by GitHub
commit 5c00bfe679
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 424 additions and 189 deletions

View File

@ -118,6 +118,7 @@ testScripts = [
# vv Tests less than 2m vv # vv Tests less than 2m vv
'wallet.py', 'wallet.py',
'wallet-accounts.py', 'wallet-accounts.py',
'wallet-dump.py',
'listtransactions.py', 'listtransactions.py',
# vv Tests less than 60s vv # vv Tests less than 60s vv
'sendheaders.py', # NOTE: needs dash_hash to pass 'sendheaders.py', # NOTE: needs dash_hash to pass

View File

@ -2,55 +2,105 @@
# 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.
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.
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.authproxy import JSONRPCException
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, set_node_times)
from decimal import Decimal 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 late_timestamp")
def call_import_rpc(call, data, address, scriptPubKey, pubkey, key, label, node, rescan): class Variant(collections.namedtuple("Variant", "call data rescan prune")):
"""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, timestamp):
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, error = try_rpc(self.node.importaddress, self.address["address"], self.label,
response = node.importprivkey(key, label, rescan) self.rescan == Rescan.yes)
assert_equal(response, None) elif self.data == Data.pub:
elif call == Call.multi: response, error = try_rpc(self.node.importpubkey, self.address["pubkey"], self.label,
response = node.importmulti([{ self.rescan == Rescan.yes)
"scriptPubKey": { elif self.data == Data.priv:
"address": address response, error = try_rpc(self.node.importprivkey, self.key, self.label, self.rescan == Rescan.yes)
}, assert_equal(response, None)
"timestamp": "now", assert_equal(error, {'message': 'Rescan is disabled in pruned mode',
"pubkeys": [pubkey] if data == Data.pub else [], 'code': -4} if self.expect_disabled else None)
"keys": [key] if data == Data.priv else [], elif self.call == Call.multi:
"label": label, response = self.node.importmulti([{
"watchonly": watchonly "scriptPubKey": {
}], {"rescan": rescan}) "address": self.address["address"]
assert_equal(response, [{"success": True}]) },
return watchonly "timestamp": timestamp + RESCAN_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0),
"pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [],
"keys": [self.key] if self.data == Data.priv else [],
"label": self.label,
"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):
"""Verify that getbalance/listtransactions return expected values."""
balance = self.node.getbalance(self.label, 0, False, 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"], confirmations)
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, (False, True))]
# List of bitcoind nodes that will import keys. # List of nodes to import keys to. Half the nodes will have pruning disabled,
IMPORT_NODES = [ # half will have it enabled. Different nodes will be used for imports that are
ImportNode(rescan=True), # expected to cause rescans, and imports that are not expected to cause
ImportNode(rescan=False), # 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.
RESCAN_WINDOW = 2 * 60 * 60
class ImportRescanTest(BitcoinTestFramework): class ImportRescanTest(BitcoinTestFramework):
@ -60,6 +110,11 @@ class ImportRescanTest(BitcoinTestFramework):
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)]
for i, import_node in enumerate(IMPORT_NODES, 1):
if import_node.prune:
# txindex is enabled by default in Dash and needs to be disabled for import-rescan.py
extra_args[i] += ["-prune=1", "-txindex=0", "-reindex-chainstate"]
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args) self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, extra_args)
for i in range(1, self.num_nodes): for i in range(1, self.num_nodes):
connect_nodes(self.nodes[i], 0) connect_nodes(self.nodes[i], 0)
@ -67,89 +122,64 @@ 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, then another
# block further in the future (past the rescan window).
self.nodes[0].generate(1) self.nodes[0].generate(1)
assert_equal(self.nodes[0].getrawmempool(), []) assert_equal(self.nodes[0].getrawmempool(), [])
timestamp = self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"]
set_node_times(self.nodes, timestamp + RESCAN_WINDOW + 1)
self.nodes[0].generate(1)
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.
for node, import_node in zip(self.nodes[1:], IMPORT_NODES): for variant in IMPORT_VARIANTS:
for import_rpc, label, amount, txid, addr in import_rpc_variants: variant.expect_disabled = variant.rescan == Rescan.yes and variant.prune and variant.call == Call.single
watchonly = import_rpc(node, import_node.rescan) expect_rescan = variant.rescan == Rescan.yes and not variant.expect_disabled
variant.node = self.nodes[1 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))]
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()
balance = node.getbalance(label, 0, False, 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) if not variant.expect_disabled:
for (import_rpc, label, amount, txid, addr), spend_txid in zip(import_rpc_variants, spend_txids): variant.expected_balance += variant.sent_amount
balance = node.getbalance(label, 0, False, True) variant.expected_txs += 1
spend_tx = [tx for tx in txs if tx["txid"] == spend_txid] variant.check(variant.sent_txid, variant.sent_amount, 1)
if import_node.rescan: else:
assert_equal(balance, amount) variant.check()
assert_equal(len(spend_tx), 1)
assert_equal(spend_tx[0]["account"], "")
assert_equal(spend_tx[0]["amount"] + spend_tx[0]["fee"], -amount) def try_rpc(func, *args, **kwargs):
assert_equal(spend_tx[0]["category"], "send") try:
assert_equal("label" not in spend_tx[0], True) return func(*args, **kwargs), None
assert_equal(spend_tx[0]["confirmations"], 1) except JSONRPCException as e:
assert_equal("trusted" not in spend_tx[0], True) return None, e.error
assert_equal("involvesWatchonly" not in txs[0], True)
else:
assert_equal(balance, 0)
assert_equal(spend_tx, [])
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -20,6 +20,7 @@ class ImportMultiTest (BitcoinTestFramework):
print ("Mining blocks...") print ("Mining blocks...")
self.nodes[0].generate(1) self.nodes[0].generate(1)
self.nodes[1].generate(1) self.nodes[1].generate(1)
timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime']
# keyword definition # keyword definition
PRIV_KEY = 'privkey' PRIV_KEY = 'privkey'
@ -59,6 +60,9 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], True) assert_equal(address_assert['iswatchonly'], True)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal(address_assert['timestamp'], timestamp)
watchonly_address = address['address']
watchonly_timestamp = timestamp
print("Should not import an invalid address") print("Should not import an invalid address")
result = self.nodes[1].importmulti([{ result = self.nodes[1].importmulti([{
@ -83,6 +87,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], True) assert_equal(address_assert['iswatchonly'], True)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal(address_assert['timestamp'], timestamp)
# ScriptPubKey + !internal # ScriptPubKey + !internal
print("Should not import a scriptPubKey without internal flag") print("Should not import a scriptPubKey without internal flag")
@ -97,6 +102,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal('timestamp' in address_assert, False)
# Address + Public key + !Internal # Address + Public key + !Internal
@ -113,6 +119,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], True) assert_equal(address_assert['iswatchonly'], True)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal(address_assert['timestamp'], timestamp)
# ScriptPubKey + Public key + internal # ScriptPubKey + Public key + internal
@ -129,6 +136,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], True) assert_equal(address_assert['iswatchonly'], True)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal(address_assert['timestamp'], timestamp)
# ScriptPubKey + Public key + !internal # ScriptPubKey + Public key + !internal
print("Should not import a scriptPubKey without internal and with public key") print("Should not import a scriptPubKey without internal and with public key")
@ -145,6 +153,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal('timestamp' in address_assert, False)
# Address + Private key + !watchonly # Address + Private key + !watchonly
print("Should import an address with private key") print("Should import an address with private key")
@ -160,6 +169,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], True) assert_equal(address_assert['ismine'], True)
assert_equal(address_assert['timestamp'], timestamp)
# Address + Private key + watchonly # Address + Private key + watchonly
print("Should not import an address with private key and with watchonly") print("Should not import an address with private key and with watchonly")
@ -178,6 +188,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal('timestamp' in address_assert, False)
# ScriptPubKey + Private key + internal # ScriptPubKey + Private key + internal
print("Should import a scriptPubKey with internal and with private key") print("Should import a scriptPubKey with internal and with private key")
@ -192,6 +203,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], True) assert_equal(address_assert['ismine'], True)
assert_equal(address_assert['timestamp'], timestamp)
# ScriptPubKey + Private key + !internal # ScriptPubKey + Private key + !internal
print("Should not import a scriptPubKey without internal and with private key") print("Should not import a scriptPubKey without internal and with private key")
@ -207,6 +219,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal('timestamp' in address_assert, False)
# P2SH address # P2SH address
@ -217,6 +230,7 @@ class ImportMultiTest (BitcoinTestFramework):
self.nodes[1].generate(100) self.nodes[1].generate(100)
transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00)
self.nodes[1].generate(1) self.nodes[1].generate(1)
timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime']
transaction = self.nodes[1].gettransaction(transactionid) transaction = self.nodes[1].gettransaction(transactionid)
print("Should import a p2sh") print("Should import a p2sh")
@ -230,6 +244,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(multi_sig_script['address']) address_assert = self.nodes[1].validateaddress(multi_sig_script['address'])
assert_equal(address_assert['isscript'], True) assert_equal(address_assert['isscript'], True)
assert_equal(address_assert['iswatchonly'], True) assert_equal(address_assert['iswatchonly'], True)
assert_equal(address_assert['timestamp'], timestamp)
p2shunspent = self.nodes[1].listunspent(0,999999, [multi_sig_script['address']])[0] p2shunspent = self.nodes[1].listunspent(0,999999, [multi_sig_script['address']])[0]
assert_equal(p2shunspent['spendable'], False) assert_equal(p2shunspent['spendable'], False)
assert_equal(p2shunspent['solvable'], False) assert_equal(p2shunspent['solvable'], False)
@ -243,6 +258,7 @@ class ImportMultiTest (BitcoinTestFramework):
self.nodes[1].generate(100) self.nodes[1].generate(100)
transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00)
self.nodes[1].generate(1) self.nodes[1].generate(1)
timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime']
transaction = self.nodes[1].gettransaction(transactionid) transaction = self.nodes[1].gettransaction(transactionid)
print("Should import a p2sh with respective redeem script") print("Should import a p2sh with respective redeem script")
@ -254,6 +270,8 @@ class ImportMultiTest (BitcoinTestFramework):
"redeemscript": multi_sig_script['redeemScript'] "redeemscript": multi_sig_script['redeemScript']
}]) }])
assert_equal(result[0]['success'], True) assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].validateaddress(multi_sig_script['address'])
assert_equal(address_assert['timestamp'], timestamp)
p2shunspent = self.nodes[1].listunspent(0,999999, [multi_sig_script['address']])[0] p2shunspent = self.nodes[1].listunspent(0,999999, [multi_sig_script['address']])[0]
assert_equal(p2shunspent['spendable'], False) assert_equal(p2shunspent['spendable'], False)
@ -268,6 +286,7 @@ class ImportMultiTest (BitcoinTestFramework):
self.nodes[1].generate(100) self.nodes[1].generate(100)
transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00) transactionid = self.nodes[1].sendtoaddress(multi_sig_script['address'], 10.00)
self.nodes[1].generate(1) self.nodes[1].generate(1)
timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())['mediantime']
transaction = self.nodes[1].gettransaction(transactionid) transaction = self.nodes[1].gettransaction(transactionid)
print("Should import a p2sh with respective redeem script and private keys") print("Should import a p2sh with respective redeem script and private keys")
@ -280,6 +299,8 @@ class ImportMultiTest (BitcoinTestFramework):
"keys": [ self.nodes[0].dumpprivkey(sig_address_1['address']), self.nodes[0].dumpprivkey(sig_address_2['address'])] "keys": [ self.nodes[0].dumpprivkey(sig_address_1['address']), self.nodes[0].dumpprivkey(sig_address_2['address'])]
}]) }])
assert_equal(result[0]['success'], True) assert_equal(result[0]['success'], True)
address_assert = self.nodes[1].validateaddress(multi_sig_script['address'])
assert_equal(address_assert['timestamp'], timestamp)
p2shunspent = self.nodes[1].listunspent(0,999999, [multi_sig_script['address']])[0] p2shunspent = self.nodes[1].listunspent(0,999999, [multi_sig_script['address']])[0]
assert_equal(p2shunspent['spendable'], False) assert_equal(p2shunspent['spendable'], False)
@ -327,6 +348,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal('timestamp' in address_assert, False)
# ScriptPubKey + Public key + internal + Wrong pubkey # ScriptPubKey + Public key + internal + Wrong pubkey
@ -346,6 +368,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal('timestamp' in address_assert, False)
# Address + Private key + !watchonly + Wrong private key # Address + Private key + !watchonly + Wrong private key
@ -365,6 +388,7 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal('timestamp' in address_assert, False)
# ScriptPubKey + Private key + internal + Wrong private key # ScriptPubKey + Private key + internal + Wrong private key
@ -383,6 +407,15 @@ class ImportMultiTest (BitcoinTestFramework):
address_assert = self.nodes[1].validateaddress(address['address']) address_assert = self.nodes[1].validateaddress(address['address'])
assert_equal(address_assert['iswatchonly'], False) assert_equal(address_assert['iswatchonly'], False)
assert_equal(address_assert['ismine'], False) assert_equal(address_assert['ismine'], False)
assert_equal('timestamp' in address_assert, False)
# restart nodes to check for proper serialization/deserialization of watch only address
stop_nodes(self.nodes)
self.nodes = start_nodes(2, self.options.tmpdir)
address_assert = self.nodes[1].validateaddress(watchonly_address)
assert_equal(address_assert['iswatchonly'], True)
assert_equal(address_assert['ismine'], False)
assert_equal(address_assert['timestamp'], watchonly_timestamp);
# Bad or missing timestamps # Bad or missing timestamps
print("Should throw on invalid or missing timestamp values") print("Should throw on invalid or missing timestamp values")

View File

@ -240,7 +240,7 @@ def wait_for_bitcoind_start(process, url, i):
raise # unknown JSON RPC exception raise # unknown JSON RPC exception
time.sleep(0.25) time.sleep(0.25)
def initialize_chain(test_dir, num_nodes, cachedir): def initialize_chain(test_dir, num_nodes, cachedir, extra_args=None, redirect_stderr=False):
""" """
Create a cache of a 200-block-long chain (with wallet) for MAX_NODES Create a cache of a 200-block-long chain (with wallet) for MAX_NODES
Afterward, create num_nodes copies from the cache Afterward, create num_nodes copies from the cache
@ -266,7 +266,12 @@ def initialize_chain(test_dir, num_nodes, cachedir):
args = [ os.getenv("DASHD", "dashd"), "-server", "-keypool=1", "-datadir="+datadir, "-discover=0" ] args = [ os.getenv("DASHD", "dashd"), "-server", "-keypool=1", "-datadir="+datadir, "-discover=0" ]
if i > 0: if i > 0:
args.append("-connect=127.0.0.1:"+str(p2p_port(0))) args.append("-connect=127.0.0.1:"+str(p2p_port(0)))
bitcoind_processes[i] = subprocess.Popen(args) if extra_args is not None:
args += extra_args
stderr = None
if redirect_stderr:
stderr = sys.stdout
bitcoind_processes[i] = subprocess.Popen(args, stderr=stderr)
if os.getenv("PYTHON_DEBUG", ""): if os.getenv("PYTHON_DEBUG", ""):
print("initialize_chain: dashd started, waiting for RPC to come up") print("initialize_chain: dashd started, waiting for RPC to come up")
wait_for_bitcoind_start(bitcoind_processes[i], rpc_url(i), i) wait_for_bitcoind_start(bitcoind_processes[i], rpc_url(i), i)

114
qa/rpc-tests/wallet-dump.py Executable file
View File

@ -0,0 +1,114 @@
#!/usr/bin/env python3
# Copyright (c) 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 *
def read_dump(file_name, addrs, hd_master_addr_old):
"""
Read the given dump, count the addrs that match, count change and reserve.
Also check that the old hd_master is inactive
"""
with open(file_name, encoding='utf8') as inputfile:
found_addr = 0
found_addr_chg = 0
found_addr_rsv = 0
hd_master_addr_ret = None
for line in inputfile:
# only read non comment lines
if line[0] != "#" and len(line) > 10:
# split out some data
key_label, comment = line.split("#")
# key = key_label.split(" ")[0]
keytype = key_label.split(" ")[2]
if len(comment) > 1:
addr_keypath = comment.split(" addr=")[1]
addr = addr_keypath.split(" ")[0]
keypath = None
if keytype == "inactivehdmaster=1":
# ensure the old master is still available
assert(hd_master_addr_old == addr)
elif keytype == "hdmaster=1":
# ensure we have generated a new hd master key
assert(hd_master_addr_old != addr)
hd_master_addr_ret = addr
else:
keypath = addr_keypath.rstrip().split("hdkeypath=")[1]
# count key types
for addrObj in addrs:
if addrObj['address'] == addr and addrObj['hdkeypath'] == keypath and keytype == "label=":
found_addr += 1
break
elif keytype == "change=1":
found_addr_chg += 1
break
elif keytype == "reserve=1":
found_addr_rsv += 1
break
return found_addr, found_addr_chg, found_addr_rsv, hd_master_addr_ret
class WalletDumpTest(BitcoinTestFramework):
def __init__(self):
super().__init__()
self.setup_clean_chain = False
self.num_nodes = 1
self.extra_args = [["-keypool=90", "-usehd=1"]]
def setup_chain(self):
# TODO remove this when usehd=1 becomes the default
# use our own cache and -usehd=1 as extra arg as the default cache is run with -usehd=0
initialize_chain(self.options.tmpdir, self.num_nodes, self.options.cachedir + "/hd", ["-usehd=1"], redirect_stderr=True)
def setup_network(self, split=False):
# Use 1 minute timeout because the initial getnewaddress RPC can take
# longer than the default 30 seconds due to an expensive
# CWallet::TopUpKeyPool call, and the encryptwallet RPC made later in
# the test often takes even longer.
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.extra_args, timewait=60, redirect_stderr=True)
def run_test (self):
tmpdir = self.options.tmpdir
# generate 20 addresses to compare against the dump
test_addr_count = 20
addrs = []
for i in range(0,test_addr_count):
addr = self.nodes[0].getnewaddress()
vaddr= self.nodes[0].validateaddress(addr) #required to get hd keypath
addrs.append(vaddr)
# Should be a no-op:
self.nodes[0].keypoolrefill()
# dump unencrypted wallet
self.nodes[0].dumpwallet(tmpdir + "/node0/wallet.unencrypted.dump")
found_addr, found_addr_chg, found_addr_rsv, hd_master_addr_unenc = \
read_dump(tmpdir + "/node0/wallet.unencrypted.dump", addrs, None)
assert_equal(found_addr, test_addr_count) # all keys must be in the dump
assert_equal(found_addr_chg, 50) # 50 blocks where mined
assert_equal(found_addr_rsv, 180) # keypool size (external+internal)
#encrypt wallet, restart, unlock and dump
self.nodes[0].encryptwallet('test')
bitcoind_processes[0].wait()
self.nodes[0] = start_node(0, self.options.tmpdir, self.extra_args[0])
self.nodes[0].walletpassphrase('test', 10)
# Should be a no-op:
self.nodes[0].keypoolrefill()
self.nodes[0].dumpwallet(tmpdir + "/node0/wallet.encrypted.dump")
found_addr, found_addr_chg, found_addr_rsv, hd_master_addr_enc = \
read_dump(tmpdir + "/node0/wallet.encrypted.dump", addrs, hd_master_addr_unenc)
assert_equal(found_addr, test_addr_count)
# TODO clarify if we want the behavior that is tested below in Dash (only when HD seed was generated and not user-provided)
# assert_equal(found_addr_chg, 180 + 50) # old reserve keys are marked as change now
assert_equal(found_addr_rsv, 180) # keypool size
if __name__ == '__main__':
WalletDumpTest().main ()

View File

@ -1745,7 +1745,8 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
fMasternodeMode = GetBoolArg("-masternode", false); fMasternodeMode = GetBoolArg("-masternode", false);
// TODO: masternode should have no wallet // TODO: masternode should have no wallet
if((fMasternodeMode || masternodeConfig.getCount() > -1) && fTxIndex == false) { if((fMasternodeMode || masternodeConfig.getCount() > -1) && fTxIndex == false
&& chainparams.NetworkIDString() != CBaseChainParams::REGTEST) { // TODO remove this when pruning is fixed. See https://github.com/dashpay/dash/pull/1817 and https://github.com/dashpay/dash/pull/1743
return InitError("Enabling Masternode support requires turning on transaction indexing." return InitError("Enabling Masternode support requires turning on transaction indexing."
"Please add txindex=1 to your configuration and start with -reindex"); "Please add txindex=1 to your configuration and start with -reindex");
} }

View File

@ -296,6 +296,7 @@ UniValue validateaddress(const JSONRPCRequest& request)
" \"pubkey\" : \"publickeyhex\", (string) The hex value of the raw public key\n" " \"pubkey\" : \"publickeyhex\", (string) The hex value of the raw public key\n"
" \"iscompressed\" : true|false, (boolean) If the address is compressed\n" " \"iscompressed\" : true|false, (boolean) If the address is compressed\n"
" \"account\" : \"account\" (string) DEPRECATED. The account associated with the address, \"\" is the default account\n" " \"account\" : \"account\" (string) DEPRECATED. The account associated with the address, \"\" is the default account\n"
" \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n"
" \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n" " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n"
" \"hdchainid\" : \"<hash>\" (string, optional) The ID of the HD chain\n" " \"hdchainid\" : \"<hash>\" (string, optional) The ID of the HD chain\n"
"}\n" "}\n"
@ -333,11 +334,21 @@ UniValue validateaddress(const JSONRPCRequest& request)
if (pwalletMain && pwalletMain->mapAddressBook.count(dest)) if (pwalletMain && pwalletMain->mapAddressBook.count(dest))
ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest].name)); ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest].name));
CKeyID keyID; CKeyID keyID;
CHDChain hdChainCurrent; if (pwalletMain) {
if (pwalletMain && address.GetKeyID(keyID) && pwalletMain->mapHdPubKeys.count(keyID) && pwalletMain->GetHDChain(hdChainCurrent)) const auto& meta = pwalletMain->mapKeyMetadata;
{ auto it = address.GetKeyID(keyID) ? meta.find(keyID) : meta.end();
ret.push_back(Pair("hdkeypath", pwalletMain->mapHdPubKeys[keyID].GetKeyPath())); if (it == meta.end()) {
ret.push_back(Pair("hdchainid", hdChainCurrent.GetID().GetHex())); it = meta.find(CScriptID(scriptPubKey));
}
if (it != meta.end()) {
ret.push_back(Pair("timestamp", it->second.nCreateTime));
}
CHDChain hdChainCurrent;
if (!keyID.IsNull() && pwalletMain->mapHdPubKeys.count(keyID) && pwalletMain->GetHDChain(hdChainCurrent)) {
ret.push_back(Pair("hdkeypath", pwalletMain->mapHdPubKeys[keyID].GetKeyPath()));
ret.push_back(Pair("hdchainid", hdChainCurrent.GetID().GetHex()));
}
} }
#endif #endif
} }

View File

@ -146,7 +146,7 @@ UniValue importprivkey(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
// whenever a key is imported, we need to scan the whole chain // whenever a key is imported, we need to scan the whole chain
pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value' pwalletMain->UpdateTimeFirstKey(1);
if (fRescan) { if (fRescan) {
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
@ -164,7 +164,7 @@ void ImportScript(const CScript& script, const string& strLabel, bool isRedeemSc
pwalletMain->MarkDirty(); pwalletMain->MarkDirty();
if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script)) if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script, 0 /* nCreateTime */))
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
if (isRedeemScript) { if (isRedeemScript) {
@ -498,8 +498,8 @@ UniValue importwallet(const JSONRPCRequest& request)
} }
file.close(); file.close();
pwalletMain->ShowProgress("", 100); // hide progress dialog in GUI pwalletMain->ShowProgress("", 100); // hide progress dialog in GUI
if (!pwalletMain->nTimeFirstKey || nTimeBegin < pwalletMain->nTimeFirstKey)
pwalletMain->nTimeFirstKey = nTimeBegin; pwalletMain->UpdateTimeFirstKey(nTimeBegin);
CBlockIndex *pindex = chainActive.FindEarliestAtLeast(nTimeBegin - 7200); CBlockIndex *pindex = chainActive.FindEarliestAtLeast(nTimeBegin - 7200);
@ -635,8 +635,7 @@ UniValue importelectrumwallet(const JSONRPCRequest& request)
// Assume that electrum wallet was created at that block // Assume that electrum wallet was created at that block
int nTimeBegin = chainActive[nStartHeight]->GetBlockTime(); int nTimeBegin = chainActive[nStartHeight]->GetBlockTime();
if (!pwalletMain->nTimeFirstKey || nTimeBegin < pwalletMain->nTimeFirstKey) pwalletMain->UpdateTimeFirstKey(nTimeBegin);
pwalletMain->nTimeFirstKey = nTimeBegin;
LogPrintf("Rescanning %i blocks\n", chainActive.Height() - nStartHeight + 1); LogPrintf("Rescanning %i blocks\n", chainActive.Height() - nStartHeight + 1);
pwalletMain->ScanForWalletTransactions(chainActive[nStartHeight], true); pwalletMain->ScanForWalletTransactions(chainActive[nStartHeight], true);
@ -755,15 +754,17 @@ UniValue dumpwallet(const JSONRPCRequest& request)
if (!file.is_open()) if (!file.is_open())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
std::map<CKeyID, int64_t> mapKeyBirth; std::map<CTxDestination, int64_t> mapKeyBirth;
std::set<CKeyID> setKeyPool; std::set<CKeyID> setKeyPool;
pwalletMain->GetKeyBirthTimes(mapKeyBirth); pwalletMain->GetKeyBirthTimes(mapKeyBirth);
pwalletMain->GetAllReserveKeys(setKeyPool); pwalletMain->GetAllReserveKeys(setKeyPool);
// sort time/key pairs // sort time/key pairs
std::vector<std::pair<int64_t, CKeyID> > vKeyBirth; std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
for (std::map<CKeyID, int64_t>::const_iterator it = mapKeyBirth.begin(); it != mapKeyBirth.end(); it++) { for (const auto& entry : mapKeyBirth) {
vKeyBirth.push_back(std::make_pair(it->second, it->first)); if (const CKeyID* keyID = boost::get<CKeyID>(&entry.first)) { // set and test
vKeyBirth.push_back(std::make_pair(entry.second, *keyID));
}
} }
mapKeyBirth.clear(); mapKeyBirth.clear();
std::sort(vKeyBirth.begin(), vKeyBirth.end()); std::sort(vKeyBirth.begin(), vKeyBirth.end());
@ -927,7 +928,7 @@ UniValue ProcessImport(const UniValue& data, const int64_t timestamp)
pwalletMain->MarkDirty(); pwalletMain->MarkDirty();
if (!pwalletMain->HaveWatchOnly(redeemScript) && !pwalletMain->AddWatchOnly(redeemScript)) { if (!pwalletMain->HaveWatchOnly(redeemScript) && !pwalletMain->AddWatchOnly(redeemScript, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
} }
@ -944,7 +945,7 @@ UniValue ProcessImport(const UniValue& data, const int64_t timestamp)
pwalletMain->MarkDirty(); pwalletMain->MarkDirty();
if (!pwalletMain->HaveWatchOnly(redeemDestination) && !pwalletMain->AddWatchOnly(redeemDestination)) { if (!pwalletMain->HaveWatchOnly(redeemDestination) && !pwalletMain->AddWatchOnly(redeemDestination, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
} }
@ -988,9 +989,7 @@ UniValue ProcessImport(const UniValue& data, const int64_t timestamp)
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
} }
if (timestamp < pwalletMain->nTimeFirstKey) { pwalletMain->UpdateTimeFirstKey(timestamp);
pwalletMain->nTimeFirstKey = timestamp;
}
} }
} }
@ -1039,7 +1038,7 @@ UniValue ProcessImport(const UniValue& data, const int64_t timestamp)
pwalletMain->MarkDirty(); pwalletMain->MarkDirty();
if (!pwalletMain->HaveWatchOnly(pubKeyScript) && !pwalletMain->AddWatchOnly(pubKeyScript)) { if (!pwalletMain->HaveWatchOnly(pubKeyScript) && !pwalletMain->AddWatchOnly(pubKeyScript, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
} }
@ -1057,7 +1056,7 @@ UniValue ProcessImport(const UniValue& data, const int64_t timestamp)
pwalletMain->MarkDirty(); pwalletMain->MarkDirty();
if (!pwalletMain->HaveWatchOnly(scriptRawPubKey) && !pwalletMain->AddWatchOnly(scriptRawPubKey)) { if (!pwalletMain->HaveWatchOnly(scriptRawPubKey) && !pwalletMain->AddWatchOnly(scriptRawPubKey, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
} }
@ -1118,9 +1117,7 @@ UniValue ProcessImport(const UniValue& data, const int64_t timestamp)
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
} }
if (timestamp < pwalletMain->nTimeFirstKey) { pwalletMain->UpdateTimeFirstKey(timestamp);
pwalletMain->nTimeFirstKey = timestamp;
}
success = true; success = true;
} }
@ -1133,7 +1130,7 @@ UniValue ProcessImport(const UniValue& data, const int64_t timestamp)
pwalletMain->MarkDirty(); pwalletMain->MarkDirty();
if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script)) { if (!pwalletMain->HaveWatchOnly(script) && !pwalletMain->AddWatchOnly(script, timestamp)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
} }
@ -1194,7 +1191,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
" or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n" " or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n"
" key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n" " key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n"
" \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n" " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
" 0 can be specified to scan the entire blockchain.\n" " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n"
" creation time of all keys being imported by the importmulti call will be scanned.\n"
" \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey\n" " \"redeemscript\": \"<script>\" , (string, optional) Allowed only if the scriptPubKey is a P2SH address or a P2SH scriptPubKey\n"
" \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n" " \"pubkeys\": [\"<pubKey>\", ... ] , (array, optional) Array of strings giving pubkeys that must occur in the output or redeemscript\n"
" \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n" " \"keys\": [\"<key>\", ... ] , (array, optional) Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript\n"
@ -1240,7 +1238,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
EnsureWalletIsUnlocked(); EnsureWalletIsUnlocked();
// Verify all timestamps are present before importing any keys. // Verify all timestamps are present before importing any keys.
const int64_t now = chainActive.Tip() ? chainActive.Tip()->GetBlockTime() : 0; const int64_t now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0;
for (const UniValue& data : requests.getValues()) { for (const UniValue& data : requests.getValues()) {
GetImportTimestamp(data, now); GetImportTimestamp(data, now);
} }
@ -1278,7 +1276,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
} }
if (fRescan && fRunScan && requests.size()) { if (fRescan && fRunScan && requests.size()) {
CBlockIndex* pindex = nLowestTimestamp > minimumTimestamp ? chainActive.FindEarliestAtLeast(nLowestTimestamp) : chainActive.Genesis(); CBlockIndex* pindex = nLowestTimestamp > minimumTimestamp ? chainActive.FindEarliestAtLeast(std::max<int64_t>(nLowestTimestamp - 7200, 0)) : chainActive.Genesis();
CBlockIndex* scannedRange = nullptr; CBlockIndex* scannedRange = nullptr;
if (pindex) { if (pindex) {
scannedRange = pwalletMain->ScanForWalletTransactions(pindex, true); scannedRange = pwalletMain->ScanForWalletTransactions(pindex, true);

View File

@ -130,8 +130,7 @@ CPubKey CWallet::GenerateNewKey(uint32_t nAccountIndex, bool fInternal)
// Create new metadata // Create new metadata
mapKeyMetadata[pubkey.GetID()] = metadata; mapKeyMetadata[pubkey.GetID()] = metadata;
if (!nTimeFirstKey || nCreationTime < nTimeFirstKey) UpdateTimeFirstKey(nCreationTime);
nTimeFirstKey = nCreationTime;
if (!AddKeyPubKey(secret, pubkey)) if (!AddKeyPubKey(secret, pubkey))
throw std::runtime_error(std::string(__func__) + ": AddKey failed"); throw std::runtime_error(std::string(__func__) + ": AddKey failed");
@ -171,8 +170,7 @@ void CWallet::DeriveNewChildKey(const CKeyMetadata& metadata, CKey& secretRet, u
// store metadata // store metadata
mapKeyMetadata[pubkey.GetID()] = metadata; mapKeyMetadata[pubkey.GetID()] = metadata;
if (!nTimeFirstKey || metadata.nCreateTime < nTimeFirstKey) UpdateTimeFirstKey(metadata.nCreateTime);
nTimeFirstKey = metadata.nCreateTime;
// update the chain model in the database // update the chain model in the database
CHDChain hdChainCurrent; CHDChain hdChainCurrent;
@ -333,13 +331,11 @@ bool CWallet::AddCryptedKey(const CPubKey &vchPubKey,
return false; return false;
} }
bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta) bool CWallet::LoadKeyMetadata(const CTxDestination& keyID, const CKeyMetadata &meta)
{ {
AssertLockHeld(cs_wallet); // mapKeyMetadata AssertLockHeld(cs_wallet); // mapKeyMetadata
if (meta.nCreateTime && (!nTimeFirstKey || meta.nCreateTime < nTimeFirstKey)) UpdateTimeFirstKey(meta.nCreateTime);
nTimeFirstKey = meta.nCreateTime; mapKeyMetadata[keyID] = meta;
mapKeyMetadata[pubkey.GetID()] = meta;
return true; return true;
} }
@ -348,6 +344,18 @@ bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigne
return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret);
} }
void CWallet::UpdateTimeFirstKey(int64_t nCreateTime)
{
AssertLockHeld(cs_wallet);
if (nCreateTime <= 1) {
// Cannot determine birthday information, so set the wallet birthday to
// the beginning of time.
nTimeFirstKey = 1;
} else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) {
nTimeFirstKey = nCreateTime;
}
}
bool CWallet::AddCScript(const CScript& redeemScript) bool CWallet::AddCScript(const CScript& redeemScript)
{ {
if (!CCryptoKeyStore::AddCScript(redeemScript)) if (!CCryptoKeyStore::AddCScript(redeemScript))
@ -373,15 +381,22 @@ bool CWallet::LoadCScript(const CScript& redeemScript)
return CCryptoKeyStore::AddCScript(redeemScript); return CCryptoKeyStore::AddCScript(redeemScript);
} }
bool CWallet::AddWatchOnly(const CScript &dest) bool CWallet::AddWatchOnly(const CScript& dest)
{ {
if (!CCryptoKeyStore::AddWatchOnly(dest)) if (!CCryptoKeyStore::AddWatchOnly(dest))
return false; return false;
nTimeFirstKey = 1; // No birthday information for watch-only keys. const CKeyMetadata& meta = mapKeyMetadata[CScriptID(dest)];
UpdateTimeFirstKey(meta.nCreateTime);
NotifyWatchonlyChanged(true); NotifyWatchonlyChanged(true);
if (!fFileBacked) if (!fFileBacked)
return true; return true;
return CWalletDB(strWalletFile).WriteWatchOnly(dest); return CWalletDB(strWalletFile).WriteWatchOnly(dest, meta);
}
bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime)
{
mapKeyMetadata[CScriptID(dest)].nCreateTime = nCreateTime;
return AddWatchOnly(dest);
} }
bool CWallet::RemoveWatchOnly(const CScript &dest) bool CWallet::RemoveWatchOnly(const CScript &dest)
@ -4589,14 +4604,16 @@ public:
void operator()(const CNoDestination &none) {} void operator()(const CNoDestination &none) {}
}; };
void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const { void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const {
AssertLockHeld(cs_wallet); // mapKeyMetadata AssertLockHeld(cs_wallet); // mapKeyMetadata
mapKeyBirth.clear(); mapKeyBirth.clear();
// get birth times for keys with metadata // get birth times for keys with metadata
for (std::map<CKeyID, CKeyMetadata>::const_iterator it = mapKeyMetadata.begin(); it != mapKeyMetadata.end(); it++) for (const auto& entry : mapKeyMetadata) {
if (it->second.nCreateTime) if (entry.second.nCreateTime) {
mapKeyBirth[it->first] = it->second.nCreateTime; mapKeyBirth[entry.first] = entry.second.nCreateTime;
}
}
// map in which we'll infer heights of other keys // map in which we'll infer heights of other keys
CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganized; use a 144-block safety margin CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganized; use a 144-block safety margin

View File

@ -679,6 +679,20 @@ private:
std::set<int64_t> setInternalKeyPool; std::set<int64_t> setInternalKeyPool;
std::set<int64_t> setExternalKeyPool; std::set<int64_t> setExternalKeyPool;
int64_t nTimeFirstKey;
/**
* Private version of AddWatchOnly method which does not accept a
* timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if
* the watch key did not previously have a timestamp associated with it.
* Because this is an inherited virtual method, it is accessible despite
* being marked private, but it is marked private anyway to encourage use
* of the other AddWatchOnly which accepts a timestamp and sets
* nTimeFirstKey more intelligently for more efficient rescans.
*/
bool AddWatchOnly(const CScript& dest) override;
public: public:
/* /*
* Main wallet lock. * Main wallet lock.
@ -707,7 +721,9 @@ public:
mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
} }
std::map<CKeyID, CKeyMetadata> mapKeyMetadata; // Map from Key ID (for regular keys) or Script ID (for watch-only keys) to
// key metadata.
std::map<CTxDestination, CKeyMetadata> mapKeyMetadata;
typedef std::map<unsigned int, CMasterKey> MasterKeyMap; typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys; MasterKeyMap mapMasterKeys;
@ -766,7 +782,6 @@ public:
std::set<COutPoint> setLockedCoins; std::set<COutPoint> setLockedCoins;
int64_t nTimeFirstKey;
int64_t nKeysLeftSinceAutoBackup; int64_t nKeysLeftSinceAutoBackup;
std::map<CKeyID, CHDPubKey> mapHdPubKeys; //<! memory map of HD extended pubkeys std::map<CKeyID, CHDPubKey> mapHdPubKeys; //<! memory map of HD extended pubkeys
@ -825,29 +840,30 @@ public:
*/ */
CPubKey GenerateNewKey(uint32_t nAccountIndex, bool fInternal /*= false*/); CPubKey GenerateNewKey(uint32_t nAccountIndex, bool fInternal /*= false*/);
//! HaveKey implementation that also checks the mapHdPubKeys //! HaveKey implementation that also checks the mapHdPubKeys
bool HaveKey(const CKeyID &address) const; bool HaveKey(const CKeyID &address) const override;
//! GetPubKey implementation that also checks the mapHdPubKeys //! GetPubKey implementation that also checks the mapHdPubKeys
bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override;
//! GetKey implementation that can derive a HD private key on the fly //! GetKey implementation that can derive a HD private key on the fly
bool GetKey(const CKeyID &address, CKey& keyOut) const; bool GetKey(const CKeyID &address, CKey& keyOut) const override;
//! Adds a HDPubKey into the wallet(database) //! Adds a HDPubKey into the wallet(database)
bool AddHDPubKey(const CExtPubKey &extPubKey, bool fInternal); bool AddHDPubKey(const CExtPubKey &extPubKey, bool fInternal);
//! loads a HDPubKey into the wallets memory //! loads a HDPubKey into the wallets memory
bool LoadHDPubKey(const CHDPubKey &hdPubKey); bool LoadHDPubKey(const CHDPubKey &hdPubKey);
//! Adds a key to the store, and saves it to disk. //! Adds a key to the store, and saves it to disk.
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override;
//! Adds a key to the store, without saving it to disk (used by LoadWallet) //! Adds a key to the store, without saving it to disk (used by LoadWallet)
bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); } bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); }
//! Load metadata (used by LoadWallet) //! Load metadata (used by LoadWallet)
bool LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &metadata); bool LoadKeyMetadata(const CTxDestination& pubKey, const CKeyMetadata &metadata);
bool LoadMinVersion(int nVersion) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; } bool LoadMinVersion(int nVersion) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; }
void UpdateTimeFirstKey(int64_t nCreateTime);
//! Adds an encrypted key to the store, and saves it to disk. //! Adds an encrypted key to the store, and saves it to disk.
bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) override;
//! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet)
bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
bool AddCScript(const CScript& redeemScript); bool AddCScript(const CScript& redeemScript) override;
bool LoadCScript(const CScript& redeemScript); bool LoadCScript(const CScript& redeemScript);
//! Adds a destination data tuple to the store, and saves it to disk //! Adds a destination data tuple to the store, and saves it to disk
@ -860,8 +876,8 @@ public:
bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const; bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const;
//! Adds a watch-only address to the store, and saves it to disk. //! Adds a watch-only address to the store, and saves it to disk.
bool AddWatchOnly(const CScript &dest); bool AddWatchOnly(const CScript& dest, int64_t nCreateTime);
bool RemoveWatchOnly(const CScript &dest); bool RemoveWatchOnly(const CScript &dest) override;
//! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)
bool LoadWatchOnly(const CScript &dest); bool LoadWatchOnly(const CScript &dest);
@ -869,7 +885,7 @@ public:
bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase);
bool EncryptWallet(const SecureString& strWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase);
void GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const; void GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const;
/** /**
* Increment the next transaction order id * Increment the next transaction order id
@ -883,11 +899,11 @@ public:
void MarkDirty(); void MarkDirty();
bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true); bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true);
bool LoadToWallet(const CWalletTx& wtxIn); bool LoadToWallet(const CWalletTx& wtxIn);
void SyncTransaction(const CTransaction& tx, const CBlockIndex *pindex, int posInBlock); void SyncTransaction(const CTransaction& tx, const CBlockIndex *pindex, int posInBlock) override;
bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate); bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false); CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
void ReacceptWalletTransactions(); void ReacceptWalletTransactions();
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman); void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman); std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman);
CAmount GetBalance() const; CAmount GetBalance() const;
CAmount GetUnconfirmedBalance() const; CAmount GetUnconfirmedBalance() const;
@ -976,7 +992,7 @@ public:
CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const; CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const; CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetChange(const CTransaction& tx) const; CAmount GetChange(const CTransaction& tx) const;
void SetBestChain(const CBlockLocator& loc); void SetBestChain(const CBlockLocator& loc) override;
DBErrors LoadWallet(bool& fFirstRunRet); DBErrors LoadWallet(bool& fFirstRunRet);
DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx); DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx);
@ -986,9 +1002,9 @@ public:
bool DelAddressBook(const CTxDestination& address); bool DelAddressBook(const CTxDestination& address);
bool UpdatedTransaction(const uint256 &hashTx); bool UpdatedTransaction(const uint256 &hashTx) override;
void Inventory(const uint256 &hash) void Inventory(const uint256 &hash) override
{ {
{ {
LOCK(cs_wallet); LOCK(cs_wallet);
@ -998,8 +1014,8 @@ public:
} }
} }
void GetScriptForMining(boost::shared_ptr<CReserveScript> &script); void GetScriptForMining(boost::shared_ptr<CReserveScript> &script) override;
void ResetRequestCount(const uint256 &hash) void ResetRequestCount(const uint256 &hash) override
{ {
LOCK(cs_wallet); LOCK(cs_wallet);
mapRequestCount[hash] = 0; mapRequestCount[hash] = 0;

View File

@ -120,15 +120,19 @@ bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
return Write(std::make_pair(std::string("cscript"), hash), *(const CScriptBase*)(&redeemScript), false); return Write(std::make_pair(std::string("cscript"), hash), *(const CScriptBase*)(&redeemScript), false);
} }
bool CWalletDB::WriteWatchOnly(const CScript &dest) bool CWalletDB::WriteWatchOnly(const CScript &dest, const CKeyMetadata& keyMeta)
{ {
nWalletDBUpdateCounter++; nWalletDBUpdateCounter++;
if (!Write(std::make_pair(std::string("watchmeta"), *(const CScriptBase*)(&dest)), keyMeta))
return false;
return Write(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)), '1'); return Write(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)), '1');
} }
bool CWalletDB::EraseWatchOnly(const CScript &dest) bool CWalletDB::EraseWatchOnly(const CScript &dest)
{ {
nWalletDBUpdateCounter++; nWalletDBUpdateCounter++;
if (!Erase(std::make_pair(std::string("watchmeta"), *(const CScriptBase*)(&dest))))
return false;
return Erase(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest))); return Erase(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)));
} }
@ -259,6 +263,7 @@ class CWalletScanState {
public: public:
unsigned int nKeys; unsigned int nKeys;
unsigned int nCKeys; unsigned int nCKeys;
unsigned int nWatchKeys;
unsigned int nKeyMeta; unsigned int nKeyMeta;
bool fIsEncrypted; bool fIsEncrypted;
bool fAnyUnordered; bool fAnyUnordered;
@ -266,7 +271,7 @@ public:
vector<uint256> vWalletUpgrade; vector<uint256> vWalletUpgrade;
CWalletScanState() { CWalletScanState() {
nKeys = nCKeys = nKeyMeta = 0; nKeys = nCKeys = nWatchKeys = nKeyMeta = 0;
fIsEncrypted = false; fIsEncrypted = false;
fAnyUnordered = false; fAnyUnordered = false;
nFileVersion = 0; nFileVersion = 0;
@ -348,16 +353,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
} }
else if (strType == "watchs") else if (strType == "watchs")
{ {
wss.nWatchKeys++;
CScript script; CScript script;
ssKey >> *(CScriptBase*)(&script); ssKey >> *(CScriptBase*)(&script);
char fYes; char fYes;
ssValue >> fYes; ssValue >> fYes;
if (fYes == '1') if (fYes == '1')
pwallet->LoadWatchOnly(script); pwallet->LoadWatchOnly(script);
// Watch-only addresses have no birthday information for now,
// so set the wallet birthday to the beginning of time.
pwallet->nTimeFirstKey = 1;
} }
else if (strType == "key" || strType == "wkey") else if (strType == "key" || strType == "wkey")
{ {
@ -458,20 +460,27 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
} }
wss.fIsEncrypted = true; wss.fIsEncrypted = true;
} }
else if (strType == "keymeta") else if (strType == "keymeta" || strType == "watchmeta")
{ {
CPubKey vchPubKey; CTxDestination keyID;
ssKey >> vchPubKey; if (strType == "keymeta")
{
CPubKey vchPubKey;
ssKey >> vchPubKey;
keyID = vchPubKey.GetID();
}
else if (strType == "watchmeta")
{
CScript script;
ssKey >> *(CScriptBase*)(&script);
keyID = CScriptID(script);
}
CKeyMetadata keyMeta; CKeyMetadata keyMeta;
ssValue >> keyMeta; ssValue >> keyMeta;
wss.nKeyMeta++; wss.nKeyMeta++;
pwallet->LoadKeyMetadata(vchPubKey, keyMeta); pwallet->LoadKeyMetadata(keyID, keyMeta);
// find earliest key creation time, as wallet birthday
if (!pwallet->nTimeFirstKey ||
(keyMeta.nCreateTime < pwallet->nTimeFirstKey))
pwallet->nTimeFirstKey = keyMeta.nCreateTime;
} }
else if (strType == "defaultkey") else if (strType == "defaultkey")
{ {
@ -658,8 +667,8 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys); wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys);
// nTimeFirstKey is only reliable if all keys have metadata // nTimeFirstKey is only reliable if all keys have metadata
if ((wss.nKeys + wss.nCKeys) != wss.nKeyMeta) if ((wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta)
pwallet->nTimeFirstKey = 1; // 0 would be considered 'no value' pwallet->UpdateTimeFirstKey(1);
BOOST_FOREACH(uint256 hash, wss.vWalletUpgrade) BOOST_FOREACH(uint256 hash, wss.vWalletUpgrade)
WriteTx(pwallet->mapWallet[hash]); WriteTx(pwallet->mapWallet[hash]);

View File

@ -96,7 +96,7 @@ public:
bool WriteCScript(const uint160& hash, const CScript& redeemScript); bool WriteCScript(const uint160& hash, const CScript& redeemScript);
bool WriteWatchOnly(const CScript &script); bool WriteWatchOnly(const CScript &script, const CKeyMetadata &keymeta);
bool EraseWatchOnly(const CScript &script); bool EraseWatchOnly(const CScript &script);
bool WriteBestBlock(const CBlockLocator& locator); bool WriteBestBlock(const CBlockLocator& locator);