Skip to content

Commit 223588b

Browse files
committed
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
1 parent 869f7ab commit 223588b

File tree

12 files changed

+358
-174
lines changed

12 files changed

+358
-174
lines changed

test/functional/rpc_createmultisig.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test multisig RPCs"""
66

7+
from test_framework.authproxy import JSONRPCException
78
from test_framework.descriptors import descsum_create, drop_origins
89
from test_framework.test_framework import BitcoinTestFramework
910
from test_framework.util import (
1011
assert_raises_rpc_error,
1112
assert_equal,
1213
)
13-
from test_framework.key import ECPubKey
14+
from test_framework.key import ECPubKey, ECKey, bytes_to_wif
1415

1516
import binascii
1617
import decimal
@@ -28,10 +29,14 @@ def skip_test_if_missing_module(self):
2829
self.skip_if_no_wallet()
2930

3031
def get_keys(self):
32+
self.pub = []
33+
self.priv = []
3134
node0, node1, node2 = self.nodes
32-
add = [node1.getnewaddress() for _ in range(self.nkeys)]
33-
self.pub = [node1.getaddressinfo(a)["pubkey"] for a in add]
34-
self.priv = [node1.dumpprivkey(a) for a in add]
35+
for _ in range(self.nkeys):
36+
k = ECKey()
37+
k.generate()
38+
self.pub.append(k.get_pubkey().get_bytes().hex())
39+
self.priv.append(bytes_to_wif(k.get_bytes(), k.is_compressed))
3540
self.final = node2.getnewaddress()
3641

3742
def run_test(self):
@@ -64,17 +69,20 @@ def run_test(self):
6469
pk_obj.compressed = False
6570
pk2 = binascii.hexlify(pk_obj.get_bytes()).decode()
6671

72+
node0.createwallet(wallet_name='wmulti0', disable_private_keys=True)
73+
wmulti0 = node0.get_wallet_rpc('wmulti0')
74+
6775
# Check all permutations of keys because order matters apparently
6876
for keys in itertools.permutations([pk0, pk1, pk2]):
6977
# Results should be the same as this legacy one
7078
legacy_addr = node0.createmultisig(2, keys, 'legacy')['address']
71-
assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'legacy')['address'])
79+
assert_equal(legacy_addr, wmulti0.addmultisigaddress(2, keys, '', 'legacy')['address'])
7280

7381
# Generate addresses with the segwit types. These should all make legacy addresses
74-
assert_equal(legacy_addr, node0.createmultisig(2, keys, 'bech32')['address'])
75-
assert_equal(legacy_addr, node0.createmultisig(2, keys, 'p2sh-segwit')['address'])
76-
assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'bech32')['address'])
77-
assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'p2sh-segwit')['address'])
82+
assert_equal(legacy_addr, wmulti0.createmultisig(2, keys, 'bech32')['address'])
83+
assert_equal(legacy_addr, wmulti0.createmultisig(2, keys, 'p2sh-segwit')['address'])
84+
assert_equal(legacy_addr, wmulti0.addmultisigaddress(2, keys, '', 'bech32')['address'])
85+
assert_equal(legacy_addr, wmulti0.addmultisigaddress(2, keys, '', 'p2sh-segwit')['address'])
7886

7987
self.log.info('Testing sortedmulti descriptors with BIP 67 test vectors')
8088
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 @@ def run_test(self):
8997
assert_equal(self.nodes[0].deriveaddresses(sorted_key_desc)[0], t['address'])
9098

9199
def check_addmultisigaddress_errors(self):
100+
if self.options.descriptors:
101+
return
92102
self.log.info('Check that addmultisigaddress fails when the private keys are missing')
93103
addresses = [self.nodes[1].getnewaddress(address_type='legacy') for _ in range(2)]
94104
assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses))
@@ -115,6 +125,15 @@ def checkbalances(self):
115125

116126
def do_multisig(self):
117127
node0, node1, node2 = self.nodes
128+
if 'wmulti' not in node1.listwallets():
129+
try:
130+
node1.loadwallet('wmulti')
131+
except JSONRPCException as e:
132+
if e.error['code'] == -18 and 'Wallet wmulti not found' in e.error['message']:
133+
node1.createwallet(wallet_name='wmulti', disable_private_keys=True)
134+
else:
135+
raise
136+
wmulti = node1.get_wallet_rpc('wmulti')
118137

119138
# Construct the expected descriptor
120139
desc = 'multi({},{})'.format(self.nsigs, ','.join(self.pub))
@@ -134,7 +153,7 @@ def do_multisig(self):
134153
assert madd[0:4] == "bcrt" # actually a bech32 address
135154

136155
# compare against addmultisigaddress
137-
msigw = node1.addmultisigaddress(self.nsigs, self.pub, None, self.output_type)
156+
msigw = wmulti.addmultisigaddress(self.nsigs, self.pub, None, self.output_type)
138157
maddw = msigw["address"]
139158
mredeemw = msigw["redeemScript"]
140159
assert_equal(desc, drop_origins(msigw['descriptor']))
@@ -194,6 +213,8 @@ def do_multisig(self):
194213
txinfo = node0.getrawtransaction(tx, True, blk)
195214
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"]))
196215

216+
wmulti.unloadwallet()
217+
197218

198219
if __name__ == '__main__':
199220
RpcCreateMultiSigTest().main()

test/functional/rpc_psbt.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,23 @@ def test_utxo_conversion(self):
4848
disconnect_nodes(offline_node, 2)
4949
disconnect_nodes(mining_node, 0)
5050

51+
# Create watchonly on online_node
52+
online_node.createwallet(wallet_name='wonline', disable_private_keys=True)
53+
wonline = online_node.get_wallet_rpc('wonline')
54+
w2 = online_node.get_wallet_rpc('')
55+
5156
# Mine a transaction that credits the offline address
5257
offline_addr = offline_node.getnewaddress(address_type="p2sh-segwit")
53-
online_addr = online_node.getnewaddress(address_type="p2sh-segwit")
54-
online_node.importaddress(offline_addr, "", False)
58+
online_addr = w2.getnewaddress(address_type="p2sh-segwit")
59+
wonline.importaddress(offline_addr, "", False)
5560
mining_node.sendtoaddress(address=offline_addr, amount=1.0)
5661
mining_node.generate(nblocks=1)
5762
self.sync_blocks([mining_node, online_node])
5863

5964
# Construct an unsigned PSBT on the online node (who doesn't know the output is Segwit, so will include a non-witness UTXO)
60-
utxos = online_node.listunspent(addresses=[offline_addr])
61-
raw = online_node.createrawtransaction([{"txid":utxos[0]["txid"], "vout":utxos[0]["vout"]}],[{online_addr:0.9999}])
62-
psbt = online_node.walletprocesspsbt(online_node.converttopsbt(raw))["psbt"]
65+
utxos = wonline.listunspent(addresses=[offline_addr])
66+
raw = wonline.createrawtransaction([{"txid":utxos[0]["txid"], "vout":utxos[0]["vout"]}],[{online_addr:0.9999}])
67+
psbt = wonline.walletprocesspsbt(online_node.converttopsbt(raw))["psbt"]
6368
assert "non_witness_utxo" in mining_node.decodepsbt(psbt)["inputs"][0]
6469

6570
# Have the offline node sign the PSBT (which will update the UTXO to segwit)
@@ -72,6 +77,8 @@ def test_utxo_conversion(self):
7277
self.sync_blocks([mining_node, online_node])
7378
assert_equal(online_node.gettxout(txid,0)["confirmations"], 1)
7479

80+
wonline.unloadwallet()
81+
7582
# Reconnect
7683
connect_nodes(self.nodes[0], 1)
7784
connect_nodes(self.nodes[0], 2)
@@ -89,13 +96,23 @@ def run_test(self):
8996
final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex']
9097
self.nodes[0].sendrawtransaction(final_tx)
9198

92-
# Create p2sh, p2wpkh, and p2wsh addresses
99+
# Get pubkeys
93100
pubkey0 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())['pubkey']
94101
pubkey1 = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey']
95102
pubkey2 = self.nodes[2].getaddressinfo(self.nodes[2].getnewaddress())['pubkey']
96-
p2sh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "legacy")['address']
97-
p2wsh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "bech32")['address']
98-
p2sh_p2wsh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "p2sh-segwit")['address']
103+
104+
# Setup watchonly wallets
105+
self.nodes[2].createwallet(wallet_name='wmulti', disable_private_keys=True)
106+
wmulti = self.nodes[2].get_wallet_rpc('wmulti')
107+
108+
# Create all the addresses
109+
p2sh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "legacy")['address']
110+
p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "bech32")['address']
111+
p2sh_p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "p2sh-segwit")['address']
112+
if not self.options.descriptors:
113+
wmulti.importaddress(p2sh)
114+
wmulti.importaddress(p2wsh)
115+
wmulti.importaddress(p2sh_p2wsh)
99116
p2wpkh = self.nodes[1].getnewaddress("", "bech32")
100117
p2pkh = self.nodes[1].getnewaddress("", "legacy")
101118
p2sh_p2wpkh = self.nodes[1].getnewaddress("", "p2sh-segwit")
@@ -146,11 +163,14 @@ def run_test(self):
146163
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})
147164

148165
# partially sign multisig things with node 1
149-
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']
166+
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']
150167
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx)
151168
psbtx = walletprocesspsbt_out['psbt']
152169
assert_equal(walletprocesspsbt_out['complete'], False)
153170

171+
# Unload wmulti, we don't need it anymore
172+
wmulti.unloadwallet()
173+
154174
# partially sign with node 2. This should be complete and sendable
155175
walletprocesspsbt_out = self.nodes[2].walletprocesspsbt(psbtx)
156176
assert_equal(walletprocesspsbt_out['complete'], True)
@@ -297,7 +317,7 @@ def run_test(self):
297317

298318
# Signer tests
299319
for i, signer in enumerate(signers):
300-
self.nodes[2].createwallet("wallet{}".format(i))
320+
self.nodes[2].createwallet(wallet_name="wallet{}".format(i))
301321
wrpc = self.nodes[2].get_wallet_rpc("wallet{}".format(i))
302322
for key in signer['privkeys']:
303323
wrpc.importprivkey(key)

test/functional/test_framework/key.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
anything but tests."""
99
import random
1010

11+
from .address import byte_to_base58
12+
1113
def modinv(a, n):
1214
"""Compute the modular inverse of a modulo n
1315
@@ -384,3 +386,14 @@ def sign_ecdsa(self, msg, low_s=True):
384386
rb = r.to_bytes((r.bit_length() + 8) // 8, 'big')
385387
sb = s.to_bytes((s.bit_length() + 8) // 8, 'big')
386388
return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb
389+
390+
def bytes_to_wif(b, compressed=True):
391+
if compressed:
392+
b += b'\x01'
393+
return byte_to_base58(b, 239)
394+
395+
def generate_wif_key():
396+
# Makes a WIF privkey for imports
397+
k = ECKey()
398+
k.generate()
399+
return bytes_to_wif(k.get_bytes(), k.is_compressed)

test/functional/test_framework/test_framework.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ def parse_args(self):
165165
help="run nodes under the valgrind memory error detector: expect at least a ~10x slowdown, valgrind 3.14 or later required")
166166
parser.add_argument("--randomseed", type=int,
167167
help="set a random seed for deterministically reproducing a previous test run")
168+
parser.add_argument("--descriptors", default=False, action="store_true",
169+
help="Run test using a descriptor wallet")
168170
self.add_options(parser)
169171
self.options = parser.parse_args()
170172

@@ -333,11 +335,23 @@ def setup_network(self):
333335

334336
def setup_nodes(self):
335337
"""Override this method to customize test node setup"""
336-
extra_args = None
338+
extra_args = [[]] * self.num_nodes
339+
wallets = [[]] * self.num_nodes
337340
if hasattr(self, "extra_args"):
338341
extra_args = self.extra_args
342+
wallets = [[x for x in eargs if x.startswith('-wallet=')] for eargs in extra_args]
343+
extra_args = [x + ['-nowallet'] for x in extra_args]
339344
self.add_nodes(self.num_nodes, extra_args)
340345
self.start_nodes()
346+
for i, n in enumerate(self.nodes):
347+
n.extra_args.pop()
348+
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():
349+
continue
350+
if '-wallet=' not in wallets[i] and not any([x.startswith('-wallet=') for x in wallets[i]]):
351+
wallets[i].append('-wallet=')
352+
for w in wallets[i]:
353+
wallet_name = w.split('=', 1)[1]
354+
n.createwallet(wallet_name=wallet_name, descriptors=self.options.descriptors)
341355
self.import_deterministic_coinbase_privkeys()
342356
if not self.setup_clean_chain:
343357
for n in self.nodes:
@@ -408,6 +422,7 @@ def add_nodes(self, num_nodes, extra_args=None, *, rpchost=None, binary=None, bi
408422
use_cli=self.options.usecli,
409423
start_perf=self.options.perf,
410424
use_valgrind=self.options.valgrind,
425+
descriptors=self.options.descriptors,
411426
))
412427

413428
def start_node(self, i, *args, **kwargs):
@@ -547,6 +562,7 @@ def _initialize_chain(self):
547562
bitcoin_cli=self.options.bitcoincli,
548563
coverage_dir=None,
549564
cwd=self.options.tmpdir,
565+
descriptors=self.options.descriptors,
550566
))
551567
self.start_node(CACHE_NODE_ID)
552568
cache_node = self.nodes[CACHE_NODE_ID]

test/functional/test_framework/test_node.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class TestNode():
6262
To make things easier for the test writer, any unrecognised messages will
6363
be dispatched to the RPC connection."""
6464

65-
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):
65+
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):
6666
"""
6767
Kwargs:
6868
start_perf (bool): If True, begin profiling the node with `perf` as soon as
@@ -80,6 +80,7 @@ def __init__(self, i, datadir, *, chain, rpchost, timewait, bitcoind, bitcoin_cl
8080
self.binary = bitcoind
8181
self.coverage_dir = coverage_dir
8282
self.cwd = cwd
83+
self.descriptors = descriptors
8384
if extra_conf is not None:
8485
append_config(datadir, extra_conf)
8586
# Most callers will just need to add extra args to the standard list below.
@@ -171,10 +172,10 @@ def __del__(self):
171172
def __getattr__(self, name):
172173
"""Dispatches any unrecognised messages to the RPC connection or a CLI instance."""
173174
if self.use_cli:
174-
return getattr(RPCOverloadWrapper(self.cli, True), name)
175+
return getattr(RPCOverloadWrapper(self.cli, True, self.descriptors), name)
175176
else:
176177
assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection")
177-
return getattr(RPCOverloadWrapper(self.rpc), name)
178+
return getattr(RPCOverloadWrapper(self.rpc, descriptors=self.descriptors), name)
178179

179180
def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, **kwargs):
180181
"""Start the node."""
@@ -266,11 +267,11 @@ def generate(self, nblocks, maxtries=1000000):
266267

267268
def get_wallet_rpc(self, wallet_name):
268269
if self.use_cli:
269-
return RPCOverloadWrapper(self.cli("-rpcwallet={}".format(wallet_name)), True)
270+
return RPCOverloadWrapper(self.cli("-rpcwallet={}".format(wallet_name)), True, self.descriptors)
270271
else:
271272
assert self.rpc_connected and self.rpc, self._node_msg("RPC not connected")
272273
wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name))
273-
return RPCOverloadWrapper(self.rpc / wallet_path)
274+
return RPCOverloadWrapper(self.rpc / wallet_path, descriptors=self.descriptors)
274275

275276
def stop_node(self, expected_stderr='', wait=0):
276277
"""Stop the node."""
@@ -598,13 +599,28 @@ def send_cli(self, command=None, *args, **kwargs):
598599
return cli_stdout.rstrip("\n")
599600

600601
class RPCOverloadWrapper():
601-
def __init__(self, rpc, cli=False):
602+
def __init__(self, rpc, cli=False, descriptors=False):
602603
self.rpc = rpc
603604
self.is_cli = cli
605+
self.descriptors = descriptors
604606

605607
def __getattr__(self, name):
606608
return getattr(self.rpc, name)
607609

610+
def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase=None, avoid_reuse=None, descriptors=None):
611+
if self.is_cli:
612+
if disable_private_keys is None:
613+
disable_private_keys = 'null'
614+
if blank is None:
615+
blank = 'null'
616+
if passphrase is None:
617+
passphrase = ''
618+
if avoid_reuse is None:
619+
avoid_reuse = 'null'
620+
if descriptors is None:
621+
descriptors = self.descriptors
622+
return self.__getattr__('createwallet')(wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors)
623+
608624
def importprivkey(self, privkey, label=None, rescan=None):
609625
wallet_info = self.getwalletinfo()
610626
if self.is_cli:

0 commit comments

Comments
 (0)