Add a --descriptors option to various tests

Adds a --descriptors option globally to the test framework. This will
make the test create and use descriptor wallets. However some tests may
not work with this.

Some tests are modified to work with --descriptors and run with that
option in test_runer:
* wallet_basic.py
* wallet_encryption.py
* wallet_keypool.py
* wallet_keypool_topup.py
* wallet_labels.py
* wallet_avoidreuse.py
This commit is contained in:
Andrew Chow 2019-07-16 15:33:35 -04:00
parent 869f7ab30a
commit 223588b1bb
12 changed files with 347 additions and 163 deletions

View file

@ -4,13 +4,14 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test multisig RPCs""" """Test multisig RPCs"""
from test_framework.authproxy import JSONRPCException
from test_framework.descriptors import descsum_create, drop_origins from test_framework.descriptors import descsum_create, drop_origins
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import ( from test_framework.util import (
assert_raises_rpc_error, assert_raises_rpc_error,
assert_equal, assert_equal,
) )
from test_framework.key import ECPubKey from test_framework.key import ECPubKey, ECKey, bytes_to_wif
import binascii import binascii
import decimal import decimal
@ -28,10 +29,14 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
self.skip_if_no_wallet() self.skip_if_no_wallet()
def get_keys(self): def get_keys(self):
self.pub = []
self.priv = []
node0, node1, node2 = self.nodes node0, node1, node2 = self.nodes
add = [node1.getnewaddress() for _ in range(self.nkeys)] for _ in range(self.nkeys):
self.pub = [node1.getaddressinfo(a)["pubkey"] for a in add] k = ECKey()
self.priv = [node1.dumpprivkey(a) for a in add] k.generate()
self.pub.append(k.get_pubkey().get_bytes().hex())
self.priv.append(bytes_to_wif(k.get_bytes(), k.is_compressed))
self.final = node2.getnewaddress() self.final = node2.getnewaddress()
def run_test(self): def run_test(self):
@ -64,17 +69,20 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
pk_obj.compressed = False pk_obj.compressed = False
pk2 = binascii.hexlify(pk_obj.get_bytes()).decode() pk2 = binascii.hexlify(pk_obj.get_bytes()).decode()
node0.createwallet(wallet_name='wmulti0', disable_private_keys=True)
wmulti0 = node0.get_wallet_rpc('wmulti0')
# Check all permutations of keys because order matters apparently # Check all permutations of keys because order matters apparently
for keys in itertools.permutations([pk0, pk1, pk2]): for keys in itertools.permutations([pk0, pk1, pk2]):
# Results should be the same as this legacy one # Results should be the same as this legacy one
legacy_addr = node0.createmultisig(2, keys, 'legacy')['address'] legacy_addr = node0.createmultisig(2, keys, 'legacy')['address']
assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'legacy')['address']) assert_equal(legacy_addr, wmulti0.addmultisigaddress(2, keys, '', 'legacy')['address'])
# Generate addresses with the segwit types. These should all make legacy addresses # Generate addresses with the segwit types. These should all make legacy addresses
assert_equal(legacy_addr, node0.createmultisig(2, keys, 'bech32')['address']) assert_equal(legacy_addr, wmulti0.createmultisig(2, keys, 'bech32')['address'])
assert_equal(legacy_addr, node0.createmultisig(2, keys, 'p2sh-segwit')['address']) assert_equal(legacy_addr, wmulti0.createmultisig(2, keys, 'p2sh-segwit')['address'])
assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'bech32')['address']) assert_equal(legacy_addr, wmulti0.addmultisigaddress(2, keys, '', 'bech32')['address'])
assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'p2sh-segwit')['address']) assert_equal(legacy_addr, wmulti0.addmultisigaddress(2, keys, '', 'p2sh-segwit')['address'])
self.log.info('Testing sortedmulti descriptors with BIP 67 test vectors') self.log.info('Testing sortedmulti descriptors with BIP 67 test vectors')
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/rpc_bip67.json'), encoding='utf-8') as f: with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/rpc_bip67.json'), encoding='utf-8') as f:
@ -89,6 +97,8 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
assert_equal(self.nodes[0].deriveaddresses(sorted_key_desc)[0], t['address']) assert_equal(self.nodes[0].deriveaddresses(sorted_key_desc)[0], t['address'])
def check_addmultisigaddress_errors(self): def check_addmultisigaddress_errors(self):
if self.options.descriptors:
return
self.log.info('Check that addmultisigaddress fails when the private keys are missing') self.log.info('Check that addmultisigaddress fails when the private keys are missing')
addresses = [self.nodes[1].getnewaddress(address_type='legacy') for _ in range(2)] addresses = [self.nodes[1].getnewaddress(address_type='legacy') for _ in range(2)]
assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses)) assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses))
@ -115,6 +125,15 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
def do_multisig(self): def do_multisig(self):
node0, node1, node2 = self.nodes node0, node1, node2 = self.nodes
if 'wmulti' not in node1.listwallets():
try:
node1.loadwallet('wmulti')
except JSONRPCException as e:
if e.error['code'] == -18 and 'Wallet wmulti not found' in e.error['message']:
node1.createwallet(wallet_name='wmulti', disable_private_keys=True)
else:
raise
wmulti = node1.get_wallet_rpc('wmulti')
# Construct the expected descriptor # Construct the expected descriptor
desc = 'multi({},{})'.format(self.nsigs, ','.join(self.pub)) desc = 'multi({},{})'.format(self.nsigs, ','.join(self.pub))
@ -134,7 +153,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
assert madd[0:4] == "bcrt" # actually a bech32 address assert madd[0:4] == "bcrt" # actually a bech32 address
# compare against addmultisigaddress # compare against addmultisigaddress
msigw = node1.addmultisigaddress(self.nsigs, self.pub, None, self.output_type) msigw = wmulti.addmultisigaddress(self.nsigs, self.pub, None, self.output_type)
maddw = msigw["address"] maddw = msigw["address"]
mredeemw = msigw["redeemScript"] mredeemw = msigw["redeemScript"]
assert_equal(desc, drop_origins(msigw['descriptor'])) assert_equal(desc, drop_origins(msigw['descriptor']))
@ -194,6 +213,8 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
txinfo = node0.getrawtransaction(tx, True, blk) txinfo = node0.getrawtransaction(tx, True, blk)
self.log.info("n/m=%d/%d %s size=%d vsize=%d weight=%d" % (self.nsigs, self.nkeys, self.output_type, txinfo["size"], txinfo["vsize"], txinfo["weight"])) self.log.info("n/m=%d/%d %s size=%d vsize=%d weight=%d" % (self.nsigs, self.nkeys, self.output_type, txinfo["size"], txinfo["vsize"], txinfo["weight"]))
wmulti.unloadwallet()
if __name__ == '__main__': if __name__ == '__main__':
RpcCreateMultiSigTest().main() RpcCreateMultiSigTest().main()

View file

@ -48,18 +48,23 @@ class PSBTTest(BitcoinTestFramework):
disconnect_nodes(offline_node, 2) disconnect_nodes(offline_node, 2)
disconnect_nodes(mining_node, 0) disconnect_nodes(mining_node, 0)
# Create watchonly on online_node
online_node.createwallet(wallet_name='wonline', disable_private_keys=True)
wonline = online_node.get_wallet_rpc('wonline')
w2 = online_node.get_wallet_rpc('')
# Mine a transaction that credits the offline address # Mine a transaction that credits the offline address
offline_addr = offline_node.getnewaddress(address_type="p2sh-segwit") offline_addr = offline_node.getnewaddress(address_type="p2sh-segwit")
online_addr = online_node.getnewaddress(address_type="p2sh-segwit") online_addr = w2.getnewaddress(address_type="p2sh-segwit")
online_node.importaddress(offline_addr, "", False) wonline.importaddress(offline_addr, "", False)
mining_node.sendtoaddress(address=offline_addr, amount=1.0) mining_node.sendtoaddress(address=offline_addr, amount=1.0)
mining_node.generate(nblocks=1) mining_node.generate(nblocks=1)
self.sync_blocks([mining_node, online_node]) self.sync_blocks([mining_node, online_node])
# Construct an unsigned PSBT on the online node (who doesn't know the output is Segwit, so will include a non-witness UTXO) # Construct an unsigned PSBT on the online node (who doesn't know the output is Segwit, so will include a non-witness UTXO)
utxos = online_node.listunspent(addresses=[offline_addr]) utxos = wonline.listunspent(addresses=[offline_addr])
raw = online_node.createrawtransaction([{"txid":utxos[0]["txid"], "vout":utxos[0]["vout"]}],[{online_addr:0.9999}]) raw = wonline.createrawtransaction([{"txid":utxos[0]["txid"], "vout":utxos[0]["vout"]}],[{online_addr:0.9999}])
psbt = online_node.walletprocesspsbt(online_node.converttopsbt(raw))["psbt"] psbt = wonline.walletprocesspsbt(online_node.converttopsbt(raw))["psbt"]
assert "non_witness_utxo" in mining_node.decodepsbt(psbt)["inputs"][0] assert "non_witness_utxo" in mining_node.decodepsbt(psbt)["inputs"][0]
# Have the offline node sign the PSBT (which will update the UTXO to segwit) # Have the offline node sign the PSBT (which will update the UTXO to segwit)
@ -72,6 +77,8 @@ class PSBTTest(BitcoinTestFramework):
self.sync_blocks([mining_node, online_node]) self.sync_blocks([mining_node, online_node])
assert_equal(online_node.gettxout(txid,0)["confirmations"], 1) assert_equal(online_node.gettxout(txid,0)["confirmations"], 1)
wonline.unloadwallet()
# Reconnect # Reconnect
connect_nodes(self.nodes[0], 1) connect_nodes(self.nodes[0], 1)
connect_nodes(self.nodes[0], 2) connect_nodes(self.nodes[0], 2)
@ -89,13 +96,23 @@ class PSBTTest(BitcoinTestFramework):
final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex'] final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex']
self.nodes[0].sendrawtransaction(final_tx) self.nodes[0].sendrawtransaction(final_tx)
# Create p2sh, p2wpkh, and p2wsh addresses # Get pubkeys
pubkey0 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())['pubkey'] pubkey0 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())['pubkey']
pubkey1 = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey'] pubkey1 = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey']
pubkey2 = self.nodes[2].getaddressinfo(self.nodes[2].getnewaddress())['pubkey'] pubkey2 = self.nodes[2].getaddressinfo(self.nodes[2].getnewaddress())['pubkey']
p2sh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "legacy")['address']
p2wsh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "bech32")['address'] # Setup watchonly wallets
p2sh_p2wsh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "p2sh-segwit")['address'] self.nodes[2].createwallet(wallet_name='wmulti', disable_private_keys=True)
wmulti = self.nodes[2].get_wallet_rpc('wmulti')
# Create all the addresses
p2sh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "legacy")['address']
p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "bech32")['address']
p2sh_p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "p2sh-segwit")['address']
if not self.options.descriptors:
wmulti.importaddress(p2sh)
wmulti.importaddress(p2wsh)
wmulti.importaddress(p2sh_p2wsh)
p2wpkh = self.nodes[1].getnewaddress("", "bech32") p2wpkh = self.nodes[1].getnewaddress("", "bech32")
p2pkh = self.nodes[1].getnewaddress("", "legacy") p2pkh = self.nodes[1].getnewaddress("", "legacy")
p2sh_p2wpkh = self.nodes[1].getnewaddress("", "p2sh-segwit") p2sh_p2wpkh = self.nodes[1].getnewaddress("", "p2sh-segwit")
@ -146,11 +163,14 @@ class PSBTTest(BitcoinTestFramework):
assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 10}) assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 10})
# partially sign multisig things with node 1 # partially sign multisig things with node 1
psbtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], {self.nodes[1].getnewaddress():29.99})['psbt'] psbtx = wmulti.walletcreatefundedpsbt(inputs=[{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], outputs={self.nodes[1].getnewaddress():29.99}, options={'changeAddress': self.nodes[1].getrawchangeaddress()})['psbt']
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx) walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx)
psbtx = walletprocesspsbt_out['psbt'] psbtx = walletprocesspsbt_out['psbt']
assert_equal(walletprocesspsbt_out['complete'], False) assert_equal(walletprocesspsbt_out['complete'], False)
# Unload wmulti, we don't need it anymore
wmulti.unloadwallet()
# partially sign with node 2. This should be complete and sendable # partially sign with node 2. This should be complete and sendable
walletprocesspsbt_out = self.nodes[2].walletprocesspsbt(psbtx) walletprocesspsbt_out = self.nodes[2].walletprocesspsbt(psbtx)
assert_equal(walletprocesspsbt_out['complete'], True) assert_equal(walletprocesspsbt_out['complete'], True)
@ -297,7 +317,7 @@ class PSBTTest(BitcoinTestFramework):
# Signer tests # Signer tests
for i, signer in enumerate(signers): for i, signer in enumerate(signers):
self.nodes[2].createwallet("wallet{}".format(i)) self.nodes[2].createwallet(wallet_name="wallet{}".format(i))
wrpc = self.nodes[2].get_wallet_rpc("wallet{}".format(i)) wrpc = self.nodes[2].get_wallet_rpc("wallet{}".format(i))
for key in signer['privkeys']: for key in signer['privkeys']:
wrpc.importprivkey(key) wrpc.importprivkey(key)

View file

@ -8,6 +8,8 @@ keys, and is trivially vulnerable to side channel attacks. Do not use for
anything but tests.""" anything but tests."""
import random import random
from .address import byte_to_base58
def modinv(a, n): def modinv(a, n):
"""Compute the modular inverse of a modulo n """Compute the modular inverse of a modulo n
@ -384,3 +386,14 @@ class ECKey():
rb = r.to_bytes((r.bit_length() + 8) // 8, 'big') rb = r.to_bytes((r.bit_length() + 8) // 8, 'big')
sb = s.to_bytes((s.bit_length() + 8) // 8, 'big') sb = s.to_bytes((s.bit_length() + 8) // 8, 'big')
return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb
def bytes_to_wif(b, compressed=True):
if compressed:
b += b'\x01'
return byte_to_base58(b, 239)
def generate_wif_key():
# Makes a WIF privkey for imports
k = ECKey()
k.generate()
return bytes_to_wif(k.get_bytes(), k.is_compressed)

View file

@ -165,6 +165,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
help="run nodes under the valgrind memory error detector: expect at least a ~10x slowdown, valgrind 3.14 or later required") help="run nodes under the valgrind memory error detector: expect at least a ~10x slowdown, valgrind 3.14 or later required")
parser.add_argument("--randomseed", type=int, parser.add_argument("--randomseed", type=int,
help="set a random seed for deterministically reproducing a previous test run") help="set a random seed for deterministically reproducing a previous test run")
parser.add_argument("--descriptors", default=False, action="store_true",
help="Run test using a descriptor wallet")
self.add_options(parser) self.add_options(parser)
self.options = parser.parse_args() self.options = parser.parse_args()
@ -333,11 +335,23 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
def setup_nodes(self): def setup_nodes(self):
"""Override this method to customize test node setup""" """Override this method to customize test node setup"""
extra_args = None extra_args = [[]] * self.num_nodes
wallets = [[]] * self.num_nodes
if hasattr(self, "extra_args"): if hasattr(self, "extra_args"):
extra_args = self.extra_args extra_args = self.extra_args
wallets = [[x for x in eargs if x.startswith('-wallet=')] for eargs in extra_args]
extra_args = [x + ['-nowallet'] for x in extra_args]
self.add_nodes(self.num_nodes, extra_args) self.add_nodes(self.num_nodes, extra_args)
self.start_nodes() self.start_nodes()
for i, n in enumerate(self.nodes):
n.extra_args.pop()
if '-wallet=0' in n.extra_args or '-nowallet' in n.extra_args or '-disablewallet' in n.extra_args or not self.is_wallet_compiled():
continue
if '-wallet=' not in wallets[i] and not any([x.startswith('-wallet=') for x in wallets[i]]):
wallets[i].append('-wallet=')
for w in wallets[i]:
wallet_name = w.split('=', 1)[1]
n.createwallet(wallet_name=wallet_name, descriptors=self.options.descriptors)
self.import_deterministic_coinbase_privkeys() self.import_deterministic_coinbase_privkeys()
if not self.setup_clean_chain: if not self.setup_clean_chain:
for n in self.nodes: for n in self.nodes:
@ -408,6 +422,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
use_cli=self.options.usecli, use_cli=self.options.usecli,
start_perf=self.options.perf, start_perf=self.options.perf,
use_valgrind=self.options.valgrind, use_valgrind=self.options.valgrind,
descriptors=self.options.descriptors,
)) ))
def start_node(self, i, *args, **kwargs): def start_node(self, i, *args, **kwargs):
@ -547,6 +562,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
bitcoin_cli=self.options.bitcoincli, bitcoin_cli=self.options.bitcoincli,
coverage_dir=None, coverage_dir=None,
cwd=self.options.tmpdir, cwd=self.options.tmpdir,
descriptors=self.options.descriptors,
)) ))
self.start_node(CACHE_NODE_ID) self.start_node(CACHE_NODE_ID)
cache_node = self.nodes[CACHE_NODE_ID] cache_node = self.nodes[CACHE_NODE_ID]

View file

@ -62,7 +62,7 @@ class TestNode():
To make things easier for the test writer, any unrecognised messages will To make things easier for the test writer, any unrecognised messages will
be dispatched to the RPC connection.""" be dispatched to the RPC connection."""
def __init__(self, i, datadir, *, chain, rpchost, timewait, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None): def __init__(self, i, datadir, *, chain, rpchost, timewait, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False):
""" """
Kwargs: Kwargs:
start_perf (bool): If True, begin profiling the node with `perf` as soon as start_perf (bool): If True, begin profiling the node with `perf` as soon as
@ -80,6 +80,7 @@ class TestNode():
self.binary = bitcoind self.binary = bitcoind
self.coverage_dir = coverage_dir self.coverage_dir = coverage_dir
self.cwd = cwd self.cwd = cwd
self.descriptors = descriptors
if extra_conf is not None: if extra_conf is not None:
append_config(datadir, extra_conf) append_config(datadir, extra_conf)
# Most callers will just need to add extra args to the standard list below. # Most callers will just need to add extra args to the standard list below.
@ -171,10 +172,10 @@ class TestNode():
def __getattr__(self, name): def __getattr__(self, name):
"""Dispatches any unrecognised messages to the RPC connection or a CLI instance.""" """Dispatches any unrecognised messages to the RPC connection or a CLI instance."""
if self.use_cli: if self.use_cli:
return getattr(RPCOverloadWrapper(self.cli, True), name) return getattr(RPCOverloadWrapper(self.cli, True, self.descriptors), name)
else: else:
assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection") assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection")
return getattr(RPCOverloadWrapper(self.rpc), name) return getattr(RPCOverloadWrapper(self.rpc, descriptors=self.descriptors), name)
def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, **kwargs): def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, **kwargs):
"""Start the node.""" """Start the node."""
@ -266,11 +267,11 @@ class TestNode():
def get_wallet_rpc(self, wallet_name): def get_wallet_rpc(self, wallet_name):
if self.use_cli: if self.use_cli:
return RPCOverloadWrapper(self.cli("-rpcwallet={}".format(wallet_name)), True) return RPCOverloadWrapper(self.cli("-rpcwallet={}".format(wallet_name)), True, self.descriptors)
else: else:
assert self.rpc_connected and self.rpc, self._node_msg("RPC not connected") assert self.rpc_connected and self.rpc, self._node_msg("RPC not connected")
wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name)) wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name))
return RPCOverloadWrapper(self.rpc / wallet_path) return RPCOverloadWrapper(self.rpc / wallet_path, descriptors=self.descriptors)
def stop_node(self, expected_stderr='', wait=0): def stop_node(self, expected_stderr='', wait=0):
"""Stop the node.""" """Stop the node."""
@ -598,13 +599,28 @@ class TestNodeCLI():
return cli_stdout.rstrip("\n") return cli_stdout.rstrip("\n")
class RPCOverloadWrapper(): class RPCOverloadWrapper():
def __init__(self, rpc, cli=False): def __init__(self, rpc, cli=False, descriptors=False):
self.rpc = rpc self.rpc = rpc
self.is_cli = cli self.is_cli = cli
self.descriptors = descriptors
def __getattr__(self, name): def __getattr__(self, name):
return getattr(self.rpc, name) return getattr(self.rpc, name)
def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase=None, avoid_reuse=None, descriptors=None):
if self.is_cli:
if disable_private_keys is None:
disable_private_keys = 'null'
if blank is None:
blank = 'null'
if passphrase is None:
passphrase = ''
if avoid_reuse is None:
avoid_reuse = 'null'
if descriptors is None:
descriptors = self.descriptors
return self.__getattr__('createwallet')(wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors)
def importprivkey(self, privkey, label=None, rescan=None): def importprivkey(self, privkey, label=None, rescan=None):
wallet_info = self.getwalletinfo() wallet_info = self.getwalletinfo()
if self.is_cli: if self.is_cli:

View file

@ -76,6 +76,7 @@ BASE_SCRIPTS = [
# Scripts that are run by default. # Scripts that are run by default.
# Longest test should go first, to favor running tests in parallel # Longest test should go first, to favor running tests in parallel
'wallet_hd.py', 'wallet_hd.py',
'wallet_hd.py --descriptors',
'wallet_backup.py', 'wallet_backup.py',
# vv Tests less than 5m vv # vv Tests less than 5m vv
'mining_getblocktemplate_longpoll.py', 'mining_getblocktemplate_longpoll.py',
@ -86,7 +87,9 @@ BASE_SCRIPTS = [
'feature_segwit.py', 'feature_segwit.py',
# vv Tests less than 2m vv # vv Tests less than 2m vv
'wallet_basic.py', 'wallet_basic.py',
'wallet_basic.py --descriptors',
'wallet_labels.py', 'wallet_labels.py',
'wallet_labels.py --descriptors',
'p2p_segwit.py', 'p2p_segwit.py',
'p2p_timeouts.py', 'p2p_timeouts.py',
'p2p_tx_download.py', 'p2p_tx_download.py',
@ -109,6 +112,7 @@ BASE_SCRIPTS = [
'feature_abortnode.py', 'feature_abortnode.py',
# vv Tests less than 30s vv # vv Tests less than 30s vv
'wallet_keypool_topup.py', 'wallet_keypool_topup.py',
'wallet_keypool_topup.py --descriptors',
'feature_fee_estimation.py', 'feature_fee_estimation.py',
'interface_zmq.py', 'interface_zmq.py',
'interface_bitcoin_cli.py', 'interface_bitcoin_cli.py',
@ -122,6 +126,7 @@ BASE_SCRIPTS = [
'interface_rest.py', 'interface_rest.py',
'mempool_spend_coinbase.py', 'mempool_spend_coinbase.py',
'wallet_avoidreuse.py', 'wallet_avoidreuse.py',
'wallet_avoidreuse.py --descriptors',
'mempool_reorg.py', 'mempool_reorg.py',
'mempool_persist.py', 'mempool_persist.py',
'wallet_multiwallet.py', 'wallet_multiwallet.py',
@ -134,6 +139,7 @@ BASE_SCRIPTS = [
'interface_http.py', 'interface_http.py',
'interface_rpc.py', 'interface_rpc.py',
'rpc_psbt.py', 'rpc_psbt.py',
'rpc_psbt.py --descriptors',
'rpc_users.py', 'rpc_users.py',
'rpc_whitelist.py', 'rpc_whitelist.py',
'feature_proxy.py', 'feature_proxy.py',
@ -147,6 +153,7 @@ BASE_SCRIPTS = [
'p2p_addr_relay.py', 'p2p_addr_relay.py',
'rpc_net.py', 'rpc_net.py',
'wallet_keypool.py', 'wallet_keypool.py',
'wallet_keypool.py --descriptors',
'wallet_descriptor.py', 'wallet_descriptor.py',
'p2p_mempool.py', 'p2p_mempool.py',
'p2p_filter.py', 'p2p_filter.py',
@ -169,6 +176,7 @@ BASE_SCRIPTS = [
'mempool_packages.py', 'mempool_packages.py',
'mempool_package_onemore.py', 'mempool_package_onemore.py',
'rpc_createmultisig.py', 'rpc_createmultisig.py',
'rpc_createmultisig.py --descriptors',
'feature_versionbits_warning.py', 'feature_versionbits_warning.py',
'rpc_preciousblock.py', 'rpc_preciousblock.py',
'wallet_importprunedfunds.py', 'wallet_importprunedfunds.py',
@ -192,6 +200,7 @@ BASE_SCRIPTS = [
'wallet_listsinceblock.py', 'wallet_listsinceblock.py',
'p2p_leak.py', 'p2p_leak.py',
'wallet_encryption.py', 'wallet_encryption.py',
'wallet_encryption.py --descriptors',
'feature_dersig.py', 'feature_dersig.py',
'feature_cltv.py', 'feature_cltv.py',
'rpc_uptime.py', 'rpc_uptime.py',

View file

@ -133,7 +133,7 @@ class AvoidReuseTest(BitcoinTestFramework):
tempwallet = ".wallet_avoidreuse.py_test_immutable_wallet.dat" tempwallet = ".wallet_avoidreuse.py_test_immutable_wallet.dat"
# Create a wallet with disable_private_keys set; this should work # Create a wallet with disable_private_keys set; this should work
self.nodes[1].createwallet(tempwallet, True) self.nodes[1].createwallet(wallet_name=tempwallet, disable_private_keys=True)
w = self.nodes[1].get_wallet_rpc(tempwallet) w = self.nodes[1].get_wallet_rpc(tempwallet)
# Attempt to unset the disable_private_keys flag; this should not work # Attempt to unset the disable_private_keys flag; this should not work
@ -249,43 +249,44 @@ class AvoidReuseTest(BitcoinTestFramework):
# getbalances should show no used, 5 btc trusted # getbalances should show no used, 5 btc trusted
assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5}) assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5})
# For the second send, we transmute it to a related single-key address if not self.options.descriptors:
# to make sure it's also detected as re-use # For the second send, we transmute it to a related single-key address
fund_spk = self.nodes[0].getaddressinfo(fundaddr)["scriptPubKey"] # to make sure it's also detected as re-use
fund_decoded = self.nodes[0].decodescript(fund_spk) fund_spk = self.nodes[0].getaddressinfo(fundaddr)["scriptPubKey"]
if second_addr_type == "p2sh-segwit": fund_decoded = self.nodes[0].decodescript(fund_spk)
new_fundaddr = fund_decoded["segwit"]["p2sh-segwit"] if second_addr_type == "p2sh-segwit":
elif second_addr_type == "bech32": new_fundaddr = fund_decoded["segwit"]["p2sh-segwit"]
new_fundaddr = fund_decoded["segwit"]["addresses"][0] elif second_addr_type == "bech32":
else: new_fundaddr = fund_decoded["segwit"]["addresses"][0]
new_fundaddr = fundaddr else:
assert_equal(second_addr_type, "legacy") new_fundaddr = fundaddr
assert_equal(second_addr_type, "legacy")
self.nodes[0].sendtoaddress(new_fundaddr, 10) self.nodes[0].sendtoaddress(new_fundaddr, 10)
self.nodes[0].generate(1) self.nodes[0].generate(1)
self.sync_all() self.sync_all()
# listunspent should show 2 total outputs (5, 10 btc), one unused (5), one reused (10) # listunspent should show 2 total outputs (5, 10 btc), one unused (5), one reused (10)
assert_unspent(self.nodes[1], total_count=2, total_sum=15, reused_count=1, reused_sum=10) assert_unspent(self.nodes[1], total_count=2, total_sum=15, reused_count=1, reused_sum=10)
# getbalances should show 10 used, 5 btc trusted # getbalances should show 10 used, 5 btc trusted
assert_balances(self.nodes[1], mine={"used": 10, "trusted": 5}) assert_balances(self.nodes[1], mine={"used": 10, "trusted": 5})
# node 1 should now have a balance of 5 (no dirty) or 15 (including dirty) # node 1 should now have a balance of 5 (no dirty) or 15 (including dirty)
assert_approx(self.nodes[1].getbalance(), 5, 0.001) assert_approx(self.nodes[1].getbalance(), 5, 0.001)
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 15, 0.001) assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 15, 0.001)
assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[1].sendtoaddress, retaddr, 10) assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[1].sendtoaddress, retaddr, 10)
self.nodes[1].sendtoaddress(retaddr, 4) self.nodes[1].sendtoaddress(retaddr, 4)
# listunspent should show 2 total outputs (1, 10 btc), one unused (1), one reused (10) # listunspent should show 2 total outputs (1, 10 btc), one unused (1), one reused (10)
assert_unspent(self.nodes[1], total_count=2, total_sum=11, reused_count=1, reused_sum=10) assert_unspent(self.nodes[1], total_count=2, total_sum=11, reused_count=1, reused_sum=10)
# getbalances should show 10 used, 1 btc trusted # getbalances should show 10 used, 1 btc trusted
assert_balances(self.nodes[1], mine={"used": 10, "trusted": 1}) assert_balances(self.nodes[1], mine={"used": 10, "trusted": 1})
# node 1 should now have about 1 btc left (no dirty) and 11 (including dirty) # node 1 should now have about 1 btc left (no dirty) and 11 (including dirty)
assert_approx(self.nodes[1].getbalance(), 1, 0.001) assert_approx(self.nodes[1].getbalance(), 1, 0.001)
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 11, 0.001) assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 11, 0.001)
def test_getbalances_used(self): def test_getbalances_used(self):
''' '''

View file

@ -49,6 +49,7 @@ class WalletTest(BitcoinTestFramework):
return self.nodes[0].decoderawtransaction(txn)['vsize'] return self.nodes[0].decoderawtransaction(txn)['vsize']
def run_test(self): def run_test(self):
# Check that there's no UTXO on none of the nodes # Check that there's no UTXO on none of the nodes
assert_equal(len(self.nodes[0].listunspent()), 0) assert_equal(len(self.nodes[0].listunspent()), 0)
assert_equal(len(self.nodes[1].listunspent()), 0) assert_equal(len(self.nodes[1].listunspent()), 0)
@ -219,7 +220,7 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), node_2_bal) assert_equal(self.nodes[2].getbalance(), node_2_bal)
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])) node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
self.start_node(3) self.start_node(3, self.nodes[3].extra_args)
connect_nodes(self.nodes[0], 3) connect_nodes(self.nodes[0], 3)
self.sync_all() self.sync_all()
@ -315,57 +316,59 @@ class WalletTest(BitcoinTestFramework):
# This will raise an exception since generate does not accept a string # This will raise an exception since generate does not accept a string
assert_raises_rpc_error(-1, "not an integer", self.nodes[0].generate, "2") assert_raises_rpc_error(-1, "not an integer", self.nodes[0].generate, "2")
# This will raise an exception for the invalid private key format if not self.options.descriptors:
assert_raises_rpc_error(-5, "Invalid private key encoding", self.nodes[0].importprivkey, "invalid")
# This will raise an exception for importing an address with the PS2H flag # This will raise an exception for the invalid private key format
temp_address = self.nodes[1].getnewaddress("", "p2sh-segwit") assert_raises_rpc_error(-5, "Invalid private key encoding", self.nodes[0].importprivkey, "invalid")
assert_raises_rpc_error(-5, "Cannot use the p2sh flag with an address - use a script instead", self.nodes[0].importaddress, temp_address, "label", False, True)
# This will raise an exception for attempting to dump the private key of an address you do not own # This will raise an exception for importing an address with the PS2H flag
assert_raises_rpc_error(-3, "Address does not refer to a key", self.nodes[0].dumpprivkey, temp_address) temp_address = self.nodes[1].getnewaddress("", "p2sh-segwit")
assert_raises_rpc_error(-5, "Cannot use the p2sh flag with an address - use a script instead", self.nodes[0].importaddress, temp_address, "label", False, True)
# This will raise an exception for attempting to get the private key of an invalid Bitcoin address # This will raise an exception for attempting to dump the private key of an address you do not own
assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].dumpprivkey, "invalid") assert_raises_rpc_error(-3, "Address does not refer to a key", self.nodes[0].dumpprivkey, temp_address)
# This will raise an exception for attempting to set a label for an invalid Bitcoin address # This will raise an exception for attempting to get the private key of an invalid Bitcoin address
assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].setlabel, "invalid address", "label") assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].dumpprivkey, "invalid")
# This will raise an exception for importing an invalid address # This will raise an exception for attempting to set a label for an invalid Bitcoin address
assert_raises_rpc_error(-5, "Invalid Bitcoin address or script", self.nodes[0].importaddress, "invalid") assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].setlabel, "invalid address", "label")
# This will raise an exception for attempting to import a pubkey that isn't in hex # This will raise an exception for importing an invalid address
assert_raises_rpc_error(-5, "Pubkey must be a hex string", self.nodes[0].importpubkey, "not hex") assert_raises_rpc_error(-5, "Invalid Bitcoin address or script", self.nodes[0].importaddress, "invalid")
# This will raise an exception for importing an invalid pubkey # This will raise an exception for attempting to import a pubkey that isn't in hex
assert_raises_rpc_error(-5, "Pubkey is not a valid public key", self.nodes[0].importpubkey, "5361746f736869204e616b616d6f746f") assert_raises_rpc_error(-5, "Pubkey must be a hex string", self.nodes[0].importpubkey, "not hex")
# Import address and private key to check correct behavior of spendable unspents # This will raise an exception for importing an invalid pubkey
# 1. Send some coins to generate new UTXO assert_raises_rpc_error(-5, "Pubkey is not a valid public key", self.nodes[0].importpubkey, "5361746f736869204e616b616d6f746f")
address_to_import = self.nodes[2].getnewaddress()
txid = self.nodes[0].sendtoaddress(address_to_import, 1)
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
# 2. Import address from node2 to node1 # Import address and private key to check correct behavior of spendable unspents
self.nodes[1].importaddress(address_to_import) # 1. Send some coins to generate new UTXO
address_to_import = self.nodes[2].getnewaddress()
txid = self.nodes[0].sendtoaddress(address_to_import, 1)
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
# 3. Validate that the imported address is watch-only on node1 # 2. Import address from node2 to node1
assert self.nodes[1].getaddressinfo(address_to_import)["iswatchonly"] self.nodes[1].importaddress(address_to_import)
# 4. Check that the unspents after import are not spendable # 3. Validate that the imported address is watch-only on node1
assert_array_result(self.nodes[1].listunspent(), assert self.nodes[1].getaddressinfo(address_to_import)["iswatchonly"]
{"address": address_to_import},
{"spendable": False})
# 5. Import private key of the previously imported address on node1 # 4. Check that the unspents after import are not spendable
priv_key = self.nodes[2].dumpprivkey(address_to_import) assert_array_result(self.nodes[1].listunspent(),
self.nodes[1].importprivkey(priv_key) {"address": address_to_import},
{"spendable": False})
# 6. Check that the unspents are now spendable on node1 # 5. Import private key of the previously imported address on node1
assert_array_result(self.nodes[1].listunspent(), priv_key = self.nodes[2].dumpprivkey(address_to_import)
{"address": address_to_import}, self.nodes[1].importprivkey(priv_key)
{"spendable": True})
# 6. Check that the unspents are now spendable on node1
assert_array_result(self.nodes[1].listunspent(),
{"address": address_to_import},
{"spendable": True})
# Mine a block from node0 to an address from node1 # Mine a block from node0 to an address from node1
coinbase_addr = self.nodes[1].getnewaddress() coinbase_addr = self.nodes[1].getnewaddress()
@ -460,7 +463,8 @@ class WalletTest(BitcoinTestFramework):
# Try with walletrejectlongchains # Try with walletrejectlongchains
# Double chain limit but require combining inputs, so we pass SelectCoinsMinConf # Double chain limit but require combining inputs, so we pass SelectCoinsMinConf
self.stop_node(0) self.stop_node(0)
self.start_node(0, extra_args=["-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)]) extra_args = ["-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)]
self.start_node(0, extra_args=extra_args)
# wait for loadmempool # wait for loadmempool
timeout = 10 timeout = 10

View file

@ -27,17 +27,21 @@ class WalletHDTest(BitcoinTestFramework):
def run_test(self): def run_test(self):
# Make sure we use hd, keep masterkeyid # Make sure we use hd, keep masterkeyid
masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] hd_fingerprint = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['hdmasterfingerprint']
assert_equal(len(masterkeyid), 40) assert_equal(len(hd_fingerprint), 8)
# create an internal key # create an internal key
change_addr = self.nodes[1].getrawchangeaddress() change_addr = self.nodes[1].getrawchangeaddress()
change_addrV= self.nodes[1].getaddressinfo(change_addr) change_addrV= self.nodes[1].getaddressinfo(change_addr)
assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") #first internal child key if self.options.descriptors:
assert_equal(change_addrV["hdkeypath"], "m/84'/1'/0'/1/0")
else:
assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") #first internal child key
# Import a non-HD private key in the HD wallet # Import a non-HD private key in the HD wallet
non_hd_add = self.nodes[0].getnewaddress() non_hd_add = 'bcrt1qmevj8zfx0wdvp05cqwkmr6mxkfx60yezwjksmt'
self.nodes[1].importprivkey(self.nodes[0].dumpprivkey(non_hd_add)) non_hd_key = 'cS9umN9w6cDMuRVYdbkfE4c7YUFLJRoXMfhQ569uY4odiQbVN8Rt'
self.nodes[1].importprivkey(non_hd_key)
# This should be enough to keep the master key and the non-HD key # This should be enough to keep the master key and the non-HD key
self.nodes[1].backupwallet(os.path.join(self.nodes[1].datadir, "hd.bak")) self.nodes[1].backupwallet(os.path.join(self.nodes[1].datadir, "hd.bak"))
@ -48,11 +52,14 @@ class WalletHDTest(BitcoinTestFramework):
self.nodes[0].generate(101) self.nodes[0].generate(101)
hd_add = None hd_add = None
NUM_HD_ADDS = 10 NUM_HD_ADDS = 10
for i in range(NUM_HD_ADDS): for i in range(1, NUM_HD_ADDS + 1):
hd_add = self.nodes[1].getnewaddress() hd_add = self.nodes[1].getnewaddress()
hd_info = self.nodes[1].getaddressinfo(hd_add) hd_info = self.nodes[1].getaddressinfo(hd_add)
assert_equal(hd_info["hdkeypath"], "m/0'/0'/"+str(i)+"'") if self.options.descriptors:
assert_equal(hd_info["hdseedid"], masterkeyid) assert_equal(hd_info["hdkeypath"], "m/84'/1'/0'/0/" + str(i))
else:
assert_equal(hd_info["hdkeypath"], "m/0'/0'/"+str(i)+"'")
assert_equal(hd_info["hdmasterfingerprint"], hd_fingerprint)
self.nodes[0].sendtoaddress(hd_add, 1) self.nodes[0].sendtoaddress(hd_add, 1)
self.nodes[0].generate(1) self.nodes[0].generate(1)
self.nodes[0].sendtoaddress(non_hd_add, 1) self.nodes[0].sendtoaddress(non_hd_add, 1)
@ -61,7 +68,10 @@ class WalletHDTest(BitcoinTestFramework):
# create an internal key (again) # create an internal key (again)
change_addr = self.nodes[1].getrawchangeaddress() change_addr = self.nodes[1].getrawchangeaddress()
change_addrV= self.nodes[1].getaddressinfo(change_addr) change_addrV= self.nodes[1].getaddressinfo(change_addr)
assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") #second internal child key if self.options.descriptors:
assert_equal(change_addrV["hdkeypath"], "m/84'/1'/0'/1/1")
else:
assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") #second internal child key
self.sync_all() self.sync_all()
assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1) assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1)
@ -72,16 +82,19 @@ class WalletHDTest(BitcoinTestFramework):
# otherwise node1 would auto-recover all funds in flag the keypool keys as used # otherwise node1 would auto-recover all funds in flag the keypool keys as used
shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "blocks")) shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "blocks"))
shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "chainstate")) shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "chainstate"))
shutil.copyfile(os.path.join(self.nodes[1].datadir, "hd.bak"), os.path.join(self.nodes[1].datadir, self.chain, "wallets", "wallet.dat")) shutil.copyfile(os.path.join(self.nodes[1].datadir, "hd.bak"), os.path.join(self.nodes[1].datadir, self.chain, 'wallets', "wallet.dat"))
self.start_node(1) self.start_node(1)
# Assert that derivation is deterministic # Assert that derivation is deterministic
hd_add_2 = None hd_add_2 = None
for i in range(NUM_HD_ADDS): for i in range(1, NUM_HD_ADDS + 1):
hd_add_2 = self.nodes[1].getnewaddress() hd_add_2 = self.nodes[1].getnewaddress()
hd_info_2 = self.nodes[1].getaddressinfo(hd_add_2) hd_info_2 = self.nodes[1].getaddressinfo(hd_add_2)
assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/"+str(i)+"'") if self.options.descriptors:
assert_equal(hd_info_2["hdseedid"], masterkeyid) assert_equal(hd_info_2["hdkeypath"], "m/84'/1'/0'/0/" + str(i))
else:
assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/"+str(i)+"'")
assert_equal(hd_info_2["hdmasterfingerprint"], hd_fingerprint)
assert_equal(hd_add, hd_add_2) assert_equal(hd_add, hd_add_2)
connect_nodes(self.nodes[0], 1) connect_nodes(self.nodes[0], 1)
self.sync_all() self.sync_all()
@ -117,41 +130,45 @@ class WalletHDTest(BitcoinTestFramework):
if out['value'] != 1: if out['value'] != 1:
keypath = self.nodes[1].getaddressinfo(out['scriptPubKey']['addresses'][0])['hdkeypath'] keypath = self.nodes[1].getaddressinfo(out['scriptPubKey']['addresses'][0])['hdkeypath']
assert_equal(keypath[0:7], "m/0'/1'") if self.options.descriptors:
assert_equal(keypath[0:14], "m/84'/1'/0'/1/")
else:
assert_equal(keypath[0:7], "m/0'/1'")
# Generate a new HD seed on node 1 and make sure it is set if not self.options.descriptors:
orig_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] # Generate a new HD seed on node 1 and make sure it is set
self.nodes[1].sethdseed() orig_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid']
new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] self.nodes[1].sethdseed()
assert orig_masterkeyid != new_masterkeyid new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid']
addr = self.nodes[1].getnewaddress() assert orig_masterkeyid != new_masterkeyid
assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Make sure the new address is the first from the keypool addr = self.nodes[1].getnewaddress()
self.nodes[1].keypoolrefill(1) # Fill keypool with 1 key assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Make sure the new address is the first from the keypool
self.nodes[1].keypoolrefill(1) # Fill keypool with 1 key
# Set a new HD seed on node 1 without flushing the keypool # Set a new HD seed on node 1 without flushing the keypool
new_seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress()) new_seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress())
orig_masterkeyid = new_masterkeyid orig_masterkeyid = new_masterkeyid
self.nodes[1].sethdseed(False, new_seed) self.nodes[1].sethdseed(False, new_seed)
new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid']
assert orig_masterkeyid != new_masterkeyid assert orig_masterkeyid != new_masterkeyid
addr = self.nodes[1].getnewaddress() addr = self.nodes[1].getnewaddress()
assert_equal(orig_masterkeyid, self.nodes[1].getaddressinfo(addr)['hdseedid']) assert_equal(orig_masterkeyid, self.nodes[1].getaddressinfo(addr)['hdseedid'])
assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/1\'') # Make sure the new address continues previous keypool assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/1\'') # Make sure the new address continues previous keypool
# Check that the next address is from the new seed # Check that the next address is from the new seed
self.nodes[1].keypoolrefill(1) self.nodes[1].keypoolrefill(1)
next_addr = self.nodes[1].getnewaddress() next_addr = self.nodes[1].getnewaddress()
assert_equal(new_masterkeyid, self.nodes[1].getaddressinfo(next_addr)['hdseedid']) assert_equal(new_masterkeyid, self.nodes[1].getaddressinfo(next_addr)['hdseedid'])
assert_equal(self.nodes[1].getaddressinfo(next_addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Make sure the new address is not from previous keypool assert_equal(self.nodes[1].getaddressinfo(next_addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Make sure the new address is not from previous keypool
assert next_addr != addr assert next_addr != addr
# Sethdseed parameter validity # Sethdseed parameter validity
assert_raises_rpc_error(-1, 'sethdseed', self.nodes[0].sethdseed, False, new_seed, 0) assert_raises_rpc_error(-1, 'sethdseed', self.nodes[0].sethdseed, False, new_seed, 0)
assert_raises_rpc_error(-5, "Invalid private key", self.nodes[1].sethdseed, False, "not_wif") assert_raises_rpc_error(-5, "Invalid private key", self.nodes[1].sethdseed, False, "not_wif")
assert_raises_rpc_error(-1, "JSON value is not a boolean as expected", self.nodes[1].sethdseed, "Not_bool") assert_raises_rpc_error(-1, "JSON value is not a boolean as expected", self.nodes[1].sethdseed, "Not_bool")
assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[1].sethdseed, False, True) assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[1].sethdseed, False, True)
assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, new_seed) assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, new_seed)
assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress())) assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress()))
if __name__ == '__main__': if __name__ == '__main__':
WalletHDTest().main () WalletHDTest().main ()

View file

@ -22,16 +22,63 @@ class KeyPoolTest(BitcoinTestFramework):
addr_before_encrypting = nodes[0].getnewaddress() addr_before_encrypting = nodes[0].getnewaddress()
addr_before_encrypting_data = nodes[0].getaddressinfo(addr_before_encrypting) addr_before_encrypting_data = nodes[0].getaddressinfo(addr_before_encrypting)
wallet_info_old = nodes[0].getwalletinfo() wallet_info_old = nodes[0].getwalletinfo()
assert addr_before_encrypting_data['hdseedid'] == wallet_info_old['hdseedid'] if not self.options.descriptors:
assert addr_before_encrypting_data['hdseedid'] == wallet_info_old['hdseedid']
# Encrypt wallet and wait to terminate # Encrypt wallet and wait to terminate
nodes[0].encryptwallet('test') nodes[0].encryptwallet('test')
if self.options.descriptors:
# Import hardened derivation only descriptors
nodes[0].walletpassphrase('test', 10)
nodes[0].importdescriptors([
{
"desc": "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/*h)#y4dfsj7n",
"timestamp": "now",
"range": [0,0],
"active": True
},
{
"desc": "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1h/*h)#a0nyvl0k",
"timestamp": "now",
"range": [0,0],
"active": True
},
{
"desc": "sh(wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/2h/*h))#lmeu2axg",
"timestamp": "now",
"range": [0,0],
"active": True
},
{
"desc": "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/3h/*h)#jkl636gm",
"timestamp": "now",
"range": [0,0],
"active": True,
"internal": True
},
{
"desc": "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/4h/*h)#l3crwaus",
"timestamp": "now",
"range": [0,0],
"active": True,
"internal": True
},
{
"desc": "sh(wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/5h/*h))#qg8wa75f",
"timestamp": "now",
"range": [0,0],
"active": True,
"internal": True
}
])
nodes[0].walletlock()
# Keep creating keys # Keep creating keys
addr = nodes[0].getnewaddress() addr = nodes[0].getnewaddress()
addr_data = nodes[0].getaddressinfo(addr) addr_data = nodes[0].getaddressinfo(addr)
wallet_info = nodes[0].getwalletinfo() wallet_info = nodes[0].getwalletinfo()
assert addr_before_encrypting_data['hdseedid'] != wallet_info['hdseedid'] assert addr_before_encrypting_data['hdmasterfingerprint'] != addr_data['hdmasterfingerprint']
assert addr_data['hdseedid'] == wallet_info['hdseedid'] if not self.options.descriptors:
assert addr_data['hdseedid'] == wallet_info['hdseedid']
assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)
# put six (plus 2) new keys in the keypool (100% external-, +100% internal-keys, 1 in min) # put six (plus 2) new keys in the keypool (100% external-, +100% internal-keys, 1 in min)
@ -39,8 +86,12 @@ class KeyPoolTest(BitcoinTestFramework):
nodes[0].keypoolrefill(6) nodes[0].keypoolrefill(6)
nodes[0].walletlock() nodes[0].walletlock()
wi = nodes[0].getwalletinfo() wi = nodes[0].getwalletinfo()
assert_equal(wi['keypoolsize_hd_internal'], 6) if self.options.descriptors:
assert_equal(wi['keypoolsize'], 6) assert_equal(wi['keypoolsize_hd_internal'], 18)
assert_equal(wi['keypoolsize'], 18)
else:
assert_equal(wi['keypoolsize_hd_internal'], 6)
assert_equal(wi['keypoolsize'], 6)
# drain the internal keys # drain the internal keys
nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress()
@ -80,11 +131,15 @@ class KeyPoolTest(BitcoinTestFramework):
nodes[0].walletpassphrase('test', 100) nodes[0].walletpassphrase('test', 100)
nodes[0].keypoolrefill(100) nodes[0].keypoolrefill(100)
wi = nodes[0].getwalletinfo() wi = nodes[0].getwalletinfo()
assert_equal(wi['keypoolsize_hd_internal'], 100) if self.options.descriptors:
assert_equal(wi['keypoolsize'], 100) assert_equal(wi['keypoolsize_hd_internal'], 300)
assert_equal(wi['keypoolsize'], 300)
else:
assert_equal(wi['keypoolsize_hd_internal'], 100)
assert_equal(wi['keypoolsize'], 100)
# create a blank wallet # create a blank wallet
nodes[0].createwallet(wallet_name='w2', blank=True) nodes[0].createwallet(wallet_name='w2', blank=True, disable_private_keys=True)
w2 = nodes[0].get_wallet_rpc('w2') w2 = nodes[0].get_wallet_rpc('w2')
# refer to initial wallet as w1 # refer to initial wallet as w1
@ -92,8 +147,11 @@ class KeyPoolTest(BitcoinTestFramework):
# import private key and fund it # import private key and fund it
address = addr.pop() address = addr.pop()
privkey = w1.dumpprivkey(address) desc = w1.getaddressinfo(address)['desc']
res = w2.importmulti([{'scriptPubKey': {'address': address}, 'keys': [privkey], 'timestamp': 'now'}]) if self.options.descriptors:
res = w2.importdescriptors([{'desc': desc, 'timestamp': 'now'}])
else:
res = w2.importmulti([{'desc': desc, 'timestamp': 'now'}])
assert_equal(res[0]['success'], True) assert_equal(res[0]['success'], True)
w1.walletpassphrase('test', 100) w1.walletpassphrase('test', 100)

View file

@ -79,7 +79,15 @@ class KeypoolRestoreTest(BitcoinTestFramework):
assert_equal(self.nodes[idx].getbalance(), 15) assert_equal(self.nodes[idx].getbalance(), 15)
assert_equal(self.nodes[idx].listtransactions()[0]['category'], "receive") assert_equal(self.nodes[idx].listtransactions()[0]['category'], "receive")
# Check that we have marked all keys up to the used keypool key as used # Check that we have marked all keys up to the used keypool key as used
assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress())['hdkeypath'], "m/0'/0'/110'") if self.options.descriptors:
if output_type == 'legacy':
assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress(address_type=output_type))['hdkeypath'], "m/44'/1'/0'/0/110")
elif output_type == 'p2sh-segwit':
assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress(address_type=output_type))['hdkeypath'], "m/49'/1'/0'/0/110")
elif output_type == 'bech32':
assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress(address_type=output_type))['hdkeypath'], "m/84'/1'/0'/0/110")
else:
assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress(address_type=output_type))['hdkeypath'], "m/0'/0'/110'")
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -115,15 +115,16 @@ class WalletLabelsTest(BitcoinTestFramework):
assert_raises_rpc_error(-11, "No addresses with label", node.getaddressesbylabel, "") assert_raises_rpc_error(-11, "No addresses with label", node.getaddressesbylabel, "")
# Check that addmultisigaddress can assign labels. # Check that addmultisigaddress can assign labels.
for label in labels: if not self.options.descriptors:
addresses = [] for label in labels:
for x in range(10): addresses = []
addresses.append(node.getnewaddress()) for x in range(10):
multisig_address = node.addmultisigaddress(5, addresses, label.name)['address'] addresses.append(node.getnewaddress())
label.add_address(multisig_address) multisig_address = node.addmultisigaddress(5, addresses, label.name)['address']
label.purpose[multisig_address] = "send" label.add_address(multisig_address)
label.verify(node) label.purpose[multisig_address] = "send"
node.generate(101) label.verify(node)
node.generate(101)
# Check that setlabel can change the label of an address from a # Check that setlabel can change the label of an address from a
# different label. # different label.