Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add a --descriptors option to various tests
Adds the option to use a descriptor wallet to:
* wallet_basic.py
* wallet_encryption.py
* wallet_keypool.py
* wallet_keypool_topup.py
* wallet_labels.py

Also runs these tests with --descriptors in test_runner
  • Loading branch information
achow101 committed Aug 12, 2019
1 parent 0060e36 commit d777554
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 70 deletions.
12 changes: 10 additions & 2 deletions test/functional/test_framework/test_framework.py
Expand Up @@ -17,6 +17,7 @@
import time

from .authproxy import JSONRPCException
from .descriptors import descsum_create
from . import coverage
from .test_node import TestNode
from .mininode import NetworkThread
Expand Down Expand Up @@ -306,15 +307,22 @@ def setup_nodes(self):
assert_equal(chain_info["blocks"], 200)
assert_equal(chain_info["initialblockdownload"], False)

def import_deterministic_coinbase_privkeys(self):
def import_deterministic_coinbase_privkeys(self, descriptors=False):
for n in self.nodes:
try:
n.getwalletinfo()
except JSONRPCException as e:
assert str(e).startswith('Method not found')
continue

n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase')
if descriptors:
n.importdescriptors([{
'desc': descsum_create('pkh(' + n.get_deterministic_priv_key().key + ')'),
'label': 'coinbase',
'timestamp': 'now'
}])
else:
n.importprivkey(privkey=n.get_deterministic_priv_key().key, label='coinbase')

def run_test(self):
"""Tests must override this method to define test logic"""
Expand Down
5 changes: 5 additions & 0 deletions test/functional/test_runner.py
Expand Up @@ -88,7 +88,9 @@
'feature_segwit.py',
# vv Tests less than 2m vv
'wallet_basic.py',
'wallet_basic.py --descriptors',
'wallet_labels.py',
'wallet_labels.py --descriptors',
'p2p_segwit.py',
'p2p_timeouts.py',
'wallet_dump.py',
Expand All @@ -110,6 +112,7 @@
'feature_abortnode.py',
# vv Tests less than 30s vv
'wallet_keypool_topup.py',
'wallet_keypool_topup.py --descriptors',
'interface_zmq.py',
'interface_bitcoin_cli.py',
'mempool_resurrect.py',
Expand Down Expand Up @@ -142,6 +145,7 @@
'wallet_disable.py',
'rpc_net.py',
'wallet_keypool.py',
'wallet_keypool.py --descriptors',
'wallet_descriptor.py',
'p2p_mempool.py',
'p2p_blocksonly.py',
Expand Down Expand Up @@ -182,6 +186,7 @@
'wallet_listsinceblock.py',
'p2p_leak.py',
'wallet_encryption.py',
'wallet_encryption.py --descriptors',
'feature_dersig.py',
'feature_cltv.py',
'rpc_uptime.py',
Expand Down
111 changes: 62 additions & 49 deletions test/functional/wallet_basic.py
Expand Up @@ -30,6 +30,12 @@ def skip_test_if_missing_module(self):

def setup_network(self):
self.setup_nodes()
# Setup descriptor wallets
if self.options.descriptors:
for n in self.nodes:
n.createwallet(wallet_name='desc', descriptors=True)
n.get_wallet_rpc('').unloadwallet()
self.import_deterministic_coinbase_privkeys(True)
# Only need nodes 0-2 running at start of test
self.stop_node(3)
connect_nodes_bi(self.nodes, 0, 1)
Expand All @@ -46,7 +52,11 @@ def check_fee_amount(self, curr_balance, balance_with_fee, fee_per_byte, tx_size
def get_vsize(self, txn):
return self.nodes[0].decoderawtransaction(txn)['vsize']

def add_options(self, parser):
parser.add_argument('--descriptors', action='store_true', dest="descriptors", help="Run test using a descriptor wallet", default=False)

def run_test(self):

# Check that there's no UTXO on none of the nodes
assert_equal(len(self.nodes[0].listunspent()), 0)
assert_equal(len(self.nodes[1].listunspent()), 0)
Expand Down Expand Up @@ -217,7 +227,7 @@ def run_test(self):
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']))

self.start_node(3)
self.start_node(3, self.nodes[3].extra_args + (['-wallet=desc'] if self.options.descriptors else []))
connect_nodes_bi(self.nodes, 0, 3)
self.sync_all()

Expand Down Expand Up @@ -250,9 +260,9 @@ def run_test(self):

# do some -walletbroadcast tests
self.stop_nodes()
self.start_node(0, ["-walletbroadcast=0"])
self.start_node(1, ["-walletbroadcast=0"])
self.start_node(2, ["-walletbroadcast=0"])
self.start_node(0, ["-walletbroadcast=0"] + (['-wallet=desc'] if self.options.descriptors else []))
self.start_node(1, ["-walletbroadcast=0"] + (['-wallet=desc'] if self.options.descriptors else []))
self.start_node(2, ["-walletbroadcast=0"] + (['-wallet=desc'] if self.options.descriptors else []))
connect_nodes_bi(self.nodes, 0, 1)
connect_nodes_bi(self.nodes, 1, 2)
connect_nodes_bi(self.nodes, 0, 2)
Expand All @@ -277,9 +287,9 @@ def run_test(self):

# restart the nodes with -walletbroadcast=1
self.stop_nodes()
self.start_node(0)
self.start_node(1)
self.start_node(2)
self.start_node(0, ['-wallet=desc'] if self.options.descriptors else [])
self.start_node(1, ['-wallet=desc'] if self.options.descriptors else [])
self.start_node(2, ['-wallet=desc'] if self.options.descriptors else [])
connect_nodes_bi(self.nodes, 0, 1)
connect_nodes_bi(self.nodes, 1, 2)
connect_nodes_bi(self.nodes, 0, 2)
Expand Down Expand Up @@ -313,57 +323,59 @@ def run_test(self):
# 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")

# This will raise an exception for the invalid private key format
assert_raises_rpc_error(-5, "Invalid private key encoding", self.nodes[0].importprivkey, "invalid")
if not self.options.descriptors:

# This will raise an exception for importing an address with the PS2H flag
temp_address = self.nodes[1].getnewaddress()
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 the invalid private key format
assert_raises_rpc_error(-5, "Invalid private key encoding", self.nodes[0].importprivkey, "invalid")

# This will raise an exception for attempting to dump the private key of an address you do not own
assert_raises_rpc_error(-3, "Address does not refer to a key", self.nodes[0].dumpprivkey, temp_address)
# This will raise an exception for importing an address with the PS2H flag
temp_address = self.nodes[1].getnewaddress()
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
assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].dumpprivkey, "invalid")
# This will raise an exception for attempting to dump the private key of an address you do not own
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
assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].setlabel, "invalid address", "label")
# 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].dumpprivkey, "invalid")

# This will raise an exception for importing an invalid address
assert_raises_rpc_error(-5, "Invalid Bitcoin address or script", self.nodes[0].importaddress, "invalid")
# This will raise an exception for attempting to set a label for an invalid Bitcoin address
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
assert_raises_rpc_error(-5, "Pubkey must be a hex string", self.nodes[0].importpubkey, "not hex")
# This will raise an exception for importing an invalid address
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
assert_raises_rpc_error(-5, "Pubkey is not a valid public key", self.nodes[0].importpubkey, "5361746f736869204e616b616d6f746f")
# This will raise an exception for attempting to import a pubkey that isn't in hex
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
# 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])
# This will raise an exception for importing an invalid pubkey
assert_raises_rpc_error(-5, "Pubkey is not a valid public key", self.nodes[0].importpubkey, "5361746f736869204e616b616d6f746f")

# Import address and private key to check correct behavior of spendable unspents
# 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])

# 2. Import address from node2 to node1
self.nodes[1].importaddress(address_to_import)
# 2. Import address from node2 to node1
self.nodes[1].importaddress(address_to_import)

# 3. Validate that the imported address is watch-only on node1
assert self.nodes[1].getaddressinfo(address_to_import)["iswatchonly"]
# 3. Validate that the imported address is watch-only on node1
assert self.nodes[1].getaddressinfo(address_to_import)["iswatchonly"]

# 4. Check that the unspents after import are not spendable
assert_array_result(self.nodes[1].listunspent(),
{"address": address_to_import},
{"spendable": False})
# 4. Check that the unspents after import are not spendable
assert_array_result(self.nodes[1].listunspent(),
{"address": address_to_import},
{"spendable": False})

# 5. Import private key of the previously imported address on node1
priv_key = self.nodes[2].dumpprivkey(address_to_import)
self.nodes[1].importprivkey(priv_key)
# 5. Import private key of the previously imported address on node1
priv_key = self.nodes[2].dumpprivkey(address_to_import)
self.nodes[1].importprivkey(priv_key)

# 6. Check that the unspents are now spendable on node1
assert_array_result(self.nodes[1].listunspent(),
{"address": address_to_import},
{"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
coinbase_addr = self.nodes[1].getnewaddress()
Expand Down Expand Up @@ -408,9 +420,9 @@ def run_test(self):
self.log.info("check " + m)
self.stop_nodes()
# set lower ancestor limit for later
self.start_node(0, [m, "-limitancestorcount=" + str(chainlimit)])
self.start_node(1, [m, "-limitancestorcount=" + str(chainlimit)])
self.start_node(2, [m, "-limitancestorcount=" + str(chainlimit)])
self.start_node(0, [m, "-limitancestorcount=" + str(chainlimit)] + (['-wallet=desc'] if self.options.descriptors else []))
self.start_node(1, [m, "-limitancestorcount=" + str(chainlimit)] + (['-wallet=desc'] if self.options.descriptors else []))
self.start_node(2, [m, "-limitancestorcount=" + str(chainlimit)] + (['-wallet=desc'] if self.options.descriptors else []))
if m == '-reindex':
# reindex will leave rpc warm up "early"; Wait for it to finish
wait_until(lambda: [block_count] * 3 == [self.nodes[i].getblockcount() for i in range(3)])
Expand Down Expand Up @@ -458,7 +470,8 @@ def run_test(self):
# Try with walletrejectlongchains
# Double chain limit but require combining inputs, so we pass SelectCoinsMinConf
self.stop_node(0)
self.start_node(0, extra_args=["-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)])
extra_args = ["-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)] + (['-wallet=desc'] if self.options.descriptors else [])
self.start_node(0, extra_args=extra_args)

# wait for loadmempool
timeout = 10
Expand Down
10 changes: 10 additions & 0 deletions test/functional/wallet_encryption.py
Expand Up @@ -21,7 +21,17 @@ def set_test_params(self):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

def add_options(self, parser):
parser.add_argument('--descriptors', action='store_true', dest="descriptors", help="Run test using a descriptor wallet", default=False)

def run_test(self):
# Setup descriptor wallets
if self.options.descriptors:
for n in self.nodes:
n.createwallet(wallet_name='desc', descriptors=True)
n.get_wallet_rpc('').unloadwallet()
self.import_deterministic_coinbase_privkeys(True)

passphrase = "WalletPassphrase"
passphrase2 = "SecondWalletPassphrase"

Expand Down
34 changes: 27 additions & 7 deletions test/functional/wallet_keypool.py
Expand Up @@ -16,30 +16,46 @@ def set_test_params(self):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

def add_options(self, parser):
parser.add_argument('--descriptors', action='store_true', dest="descriptors", help="Run test using a descriptor wallet", default=False)

def run_test(self):
# Setup descriptor wallets
if self.options.descriptors:
for n in self.nodes:
n.createwallet(wallet_name='desc', descriptors=True)
n.get_wallet_rpc('').unloadwallet()
self.import_deterministic_coinbase_privkeys(True)

nodes = self.nodes
addr_before_encrypting = nodes[0].getnewaddress()
addr_before_encrypting_data = nodes[0].getaddressinfo(addr_before_encrypting)
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
nodes[0].encryptwallet('test')
# Keep creating keys
addr = nodes[0].getnewaddress()
addr_data = nodes[0].getaddressinfo(addr)
wallet_info = nodes[0].getwalletinfo()
assert addr_before_encrypting_data['hdseedid'] != wallet_info['hdseedid']
assert addr_data['hdseedid'] == wallet_info['hdseedid']
assert addr_before_encrypting_data['hdmasterfingerprint'] != addr_data['hdmasterfingerprint']
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)

# put six (plus 2) new keys in the keypool (100% external-, +100% internal-keys, 1 in min)
nodes[0].walletpassphrase('test', 12000)
nodes[0].keypoolrefill(6)
nodes[0].walletlock()
wi = nodes[0].getwalletinfo()
assert_equal(wi['keypoolsize_hd_internal'], 6)
assert_equal(wi['keypoolsize'], 6)
if self.options.descriptors:
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
nodes[0].getrawchangeaddress()
Expand Down Expand Up @@ -79,8 +95,12 @@ def run_test(self):
nodes[0].walletpassphrase('test', 100)
nodes[0].keypoolrefill(100)
wi = nodes[0].getwalletinfo()
assert_equal(wi['keypoolsize_hd_internal'], 100)
assert_equal(wi['keypoolsize'], 100)
if self.options.descriptors:
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)

if __name__ == '__main__':
KeyPoolTest().main()
26 changes: 23 additions & 3 deletions test/functional/wallet_keypool_topup.py
Expand Up @@ -29,9 +29,21 @@ def set_test_params(self):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

def add_options(self, parser):
parser.add_argument('--descriptors', action='store_true', dest="descriptors", help="Run test using a descriptor wallet", default=False)

def run_test(self):
wallet_path = os.path.join(self.nodes[1].datadir, "regtest", "wallets", "wallet.dat")
wallet_backup_path = os.path.join(self.nodes[1].datadir, "wallet.bak")
if self.options.descriptors:
for i in range(self.num_nodes):
self.nodes[i].createwallet(wallet_name='desc', descriptors=True)
self.nodes[i].unloadwallet('')
self.extra_args[i].append('-wallet=desc')
self.import_deterministic_coinbase_privkeys(True)
wallet_path = os.path.join(self.nodes[1].datadir, "regtest", "wallets", "desc", "wallet.dat")
wallet_backup_path = os.path.join(self.nodes[1].datadir, "wallet.bak")
else:
wallet_path = os.path.join(self.nodes[1].datadir, "regtest", "wallets", "wallet.dat")
wallet_backup_path = os.path.join(self.nodes[1].datadir, "wallet.bak")
self.nodes[0].generate(101)

self.log.info("Make backup of wallet")
Expand Down Expand Up @@ -79,7 +91,15 @@ def run_test(self):
assert_equal(self.nodes[idx].getbalance(), 15)
assert_equal(self.nodes[idx].listtransactions()[0]['category'], "receive")
# 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'/0'/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'/0'/0'/0/110")
elif output_type == 'bech32':
assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress(address_type=output_type))['hdkeypath'], "m/84'/0'/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__':
Expand Down

0 comments on commit d777554

Please sign in to comment.