diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -9,13 +9,15 @@ import json import os +from test_framework.authproxy import JSONRPCException from test_framework.descriptors import descsum_create, drop_origins -from test_framework.key import ECPubKey +from test_framework.key import ECPubKey, ECKey from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_raises_rpc_error, assert_equal, ) +from test_framework.wallet_util import bytes_to_wif class RpcCreateMultiSigTest(BitcoinTestFramework): @@ -28,10 +30,14 @@ self.skip_if_no_wallet() def get_keys(self): + self.pub = [] + self.priv = [] node0, node1, node2 = self.nodes - add = [node1.getnewaddress() for _ in range(self.nkeys)] - self.pub = [node1.getaddressinfo(a)["pubkey"] for a in add] - self.priv = [node1.dumpprivkey(a) for a in add] + for _ in range(self.nkeys): + k = ECKey() + 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() def run_test(self): @@ -64,17 +70,22 @@ pk_obj.compressed = False 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 for keys in itertools.permutations([pk0, pk1, pk2]): # Results should be the same as this legacy one - legacy_addr = node0.createmultisig(2, keys)['address'] + legacy_addr = wmulti0.createmultisig(2, keys)['address'] assert_equal( - legacy_addr, node0.addmultisigaddress( + legacy_addr, wmulti0.addmultisigaddress( 2, keys, '')['address']) # Generate addresses with the segwit types. These should all make # legacy addresses - assert_equal(legacy_addr, node0.createmultisig(2, keys)['address']) + assert_equal( + legacy_addr, wmulti0.createmultisig( + 2, keys)['address']) self.log.info( 'Testing sortedmulti descriptors with BIP 67 test vectors') @@ -92,6 +103,8 @@ sorted_key_desc)[0], t['address']) def check_addmultisigaddress_errors(self): + if self.options.descriptors: + return self.log.info( 'Check that addmultisigaddress fails when the private keys are missing') addresses = [self.nodes[1].getnewaddress( @@ -126,6 +139,18 @@ def do_multisig(self): 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 desc = 'multi({},{})'.format(self.nsigs, ','.join(self.pub)) @@ -138,7 +163,7 @@ assert_equal(desc, msig['descriptor']) # compare against addmultisigaddress - msigw = node1.addmultisigaddress(self.nsigs, self.pub, None) + msigw = wmulti.addmultisigaddress(self.nsigs, self.pub, None) maddw = msigw["address"] mredeemw = msigw["redeemScript"] assert_equal(desc, drop_origins(msigw['descriptor'])) @@ -178,6 +203,8 @@ self.log.info("n/m={}/{} size={}".format(self.nsigs, self.nkeys, txinfo["size"])) + wmulti.unloadwallet() + if __name__ == '__main__': RpcCreateMultiSigTest().main() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -58,15 +58,25 @@ final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex'] self.nodes[0].sendrawtransaction(final_tx) - # Create p2sh, p2pkh addresses + # Get pubkeys pubkey0 = self.nodes[0].getaddressinfo( self.nodes[0].getnewaddress())['pubkey'] pubkey1 = self.nodes[1].getaddressinfo( self.nodes[1].getnewaddress())['pubkey'] pubkey2 = self.nodes[2].getaddressinfo( self.nodes[2].getnewaddress())['pubkey'] - p2sh = self.nodes[1].addmultisigaddress( + + # Setup watchonly wallets + 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], "")['address'] + if not self.options.descriptors: + wmulti.importaddress(p2sh) p2pkh = self.nodes[1].getnewaddress("") # fund those addresses @@ -103,46 +113,46 @@ # feeRate of 100,000 XEC / KB produces a total fee slightly below # -maxtxfee res = self.nodes[1].walletcreatefundedpsbt( - [ - { - "txid": txid, "vout": p2sh_pos}, { - "txid": txid, "vout": p2pkh_pos}], { - self.nodes[1].getnewaddress(): 29990000}, 0, { - "feeRate": 100000, "add_inputs": True}) + inputs=[{"txid": txid, "vout": p2sh_pos}], + outputs={self.nodes[1].getnewaddress(): 9990000}, + locktime=0, + options={"feeRate": 100000, "add_inputs": True}) assert_approx(res["fee"], 65000, 5000) # feeRate of 10,000,000 XEC / KB produces a total fee well above -maxtxfee # previously this was silently capped at -maxtxfee - assert_raises_rpc_error(-4, - "Fee exceeds maximum configured by -maxtxfee", - self.nodes[1].walletcreatefundedpsbt, - [{"txid": txid, - "vout": p2sh_pos}, - {"txid": txid, - "vout": p2pkh_pos}], - {self.nodes[1].getnewaddress(): 29990000}, - 0, - {"feeRate": 10000000, - "add_inputs": True}) - assert_raises_rpc_error(-4, - "Fee exceeds maximum configured by -maxtxfee", - self.nodes[1].walletcreatefundedpsbt, - [{"txid": txid, - "vout": p2sh_pos}, - {"txid": txid, - "vout": p2pkh_pos}], - {self.nodes[1].getnewaddress(): 1000000}, - 0, - {"feeRate": 10000000, - "add_inputs": False}) + assert_raises_rpc_error( + -4, + "Fee exceeds maximum configured by -maxtxfee", + self.nodes[1].walletcreatefundedpsbt, + [{"txid": txid, "vout": p2sh_pos}, + {"txid": txid, "vout": p2pkh_pos}], + {self.nodes[1].getnewaddress(): 29990000}, + 0, + {"feeRate": 10000000, "add_inputs": True}) + assert_raises_rpc_error( + -4, + "Fee exceeds maximum configured by -maxtxfee", + self.nodes[1].walletcreatefundedpsbt, + [{"txid": txid, "vout": p2sh_pos}, + {"txid": txid, "vout": p2pkh_pos}], + {self.nodes[1].getnewaddress(): 1000000}, + 0, + {"feeRate": 10000000, "add_inputs": False}) # partially sign multisig things with node 1 - psbtx = self.nodes[1].walletcreatefundedpsbt([{"txid": txid, "vout": p2sh_pos}], { - self.nodes[1].getnewaddress(): 9990000})['psbt'] + psbtx = wmulti.walletcreatefundedpsbt( + inputs=[{"txid": txid, "vout": p2sh_pos}], + outputs={self.nodes[1].getnewaddress(): 9990000}, + options={'changeAddress': self.nodes[1].getrawchangeaddress() + })['psbt'] walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx) psbtx = walletprocesspsbt_out['psbt'] 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 walletprocesspsbt_out = self.nodes[2].walletprocesspsbt(psbtx) assert_equal(walletprocesspsbt_out['complete'], True) @@ -286,7 +296,7 @@ # Signer tests 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)) for key in signer['privkeys']: wrpc.importprivkey(key) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -178,6 +178,8 @@ 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, 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") parser.add_argument("--with-axionactivation", dest="axionactivation", default=False, action="store_true", help="Activate axion update on timestamp {}".format(TIMESTAMP_IN_THE_PAST)) parser.add_argument( @@ -378,11 +380,27 @@ def setup_nodes(self): """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"): 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.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() if not self.setup_clean_chain: for n in self.nodes: @@ -454,6 +472,7 @@ emulator=self.options.emulator, start_perf=self.options.perf, use_valgrind=self.options.valgrind, + descriptors=self.options.descriptors, )) if self.options.axionactivation: self.nodes[i].extend_default_args( @@ -686,6 +705,7 @@ coverage_dir=None, cwd=self.options.tmpdir, emulator=self.options.emulator, + descriptors=self.options.descriptors, )) if self.options.axionactivation: diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -63,7 +63,7 @@ be dispatched to the RPC connection.""" def __init__(self, i, datadir, *, chain, host, rpc_port, p2p_port, timewait, timeout_factor, bitcoind, bitcoin_cli, - coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, emulator=None, start_perf=False, use_valgrind=False): + coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, emulator=None, start_perf=False, use_valgrind=False, descriptors=False): """ Kwargs: start_perf (bool): If True, begin profiling the node with `perf` as soon as @@ -87,6 +87,7 @@ "Binary '{}' could not be found.\nTry setting it manually:\n\tBITCOIND= {}".format(self.binary, sys.argv[0])) self.coverage_dir = coverage_dir self.cwd = cwd + self.descriptors = descriptors if extra_conf is not None: append_config(datadir, extra_conf) # Most callers will just need to add extra args to the default list @@ -217,13 +218,15 @@ def __getattr__(self, name): """Dispatches any unrecognised messages to the RPC connection or a CLI instance.""" if self.use_cli: - return getattr(RPCOverloadWrapper(self.cli, True), name) + return getattr(RPCOverloadWrapper( + self.cli, True, self.descriptors), name) else: assert self.rpc is not None, self._node_msg( "Error: RPC not initialized") - assert self.rpc_connected, self._node_msg( - "Error: No RPC connection") - return getattr(RPCOverloadWrapper(self.rpc), name) + assert self.rpc_connected and self.rpc is not None, self._node_msg( + "Error: no RPC connection") + return getattr(RPCOverloadWrapper( + self.rpc, descriptors=self.descriptors), name) def clear_default_args(self): self.default_args.clear() @@ -395,14 +398,15 @@ def get_wallet_rpc(self, wallet_name): if self.use_cli: return RPCOverloadWrapper( - self.cli("-rpcwallet={}".format(wallet_name)), True) + self.cli("-rpcwallet={}".format(wallet_name)), True, self.descriptors) else: assert self.rpc is not None, self._node_msg( "Error: RPC not initialized") assert self.rpc_connected, self._node_msg( "Error: RPC not connected") 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, wait_until_stopped=True): @@ -809,13 +813,30 @@ class RPCOverloadWrapper(): - def __init__(self, rpc, cli=False): + def __init__(self, rpc, cli=False, descriptors=False): self.rpc = rpc self.is_cli = cli + self.descriptors = descriptors def __getattr__(self, 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): wallet_info = self.getwalletinfo() if self.is_cli: diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -100,11 +100,20 @@ # testName --param1 --param2 # testname --param3 "rpc_bind.py": [["--ipv4"], ["--ipv6"], ["--nonloopback"]], + "rpc_createmultisig.py": [["--descriptors"]], "rpc_deriveaddresses.py": [["--usecli"]], - "wallet_txn_doublespend.py": [["--mineblock"]], - "wallet_txn_clone.py": [["--mineblock"]], + "rpc_psbt.py": [["--descriptors"]], + "wallet_avoidreuse.py": [["--descriptors"]], + "wallet_basic.py": [["--descriptors"]], "wallet_createwallet.py": [["--usecli"]], + "wallet_encryption.py": [["--descriptors"]], + "wallet_hd.py": [["--descriptors"]], + "wallet_keypool.py": [["--descriptors"]], + "wallet_keypool_topup.py": [["--descriptors"]], + "wallet_labels.py": [["--descriptors"]], "wallet_multiwallet.py": [["--usecli"]], + "wallet_txn_doublespend.py": [["--mineblock"]], + "wallet_txn_clone.py": [["--mineblock"]], "wallet_watchonly.py": [["--usecli"]], } diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py --- a/test/functional/wallet_avoidreuse.py +++ b/test/functional/wallet_avoidreuse.py @@ -149,7 +149,9 @@ tempwallet = ".wallet_avoidreuse.py_test_immutable_wallet.dat" # 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) # Attempt to unset the disable_private_keys flag; this should not work @@ -314,68 +316,63 @@ # getbalances should show no used, 5MM XEC trusted assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5000000}) - # For the second send, we transmute it to a related single-key address - # to make sure it's also detected as re-use - # NB: this is not very useful for ABC, but we keep the new variable - # name for consistency. - new_fundaddr = fundaddr - - self.nodes[0].sendtoaddress(new_fundaddr, 10000000) - self.nodes[0].generate(1) - self.sync_all() - - # listunspent should show 2 total outputs (5MM, 10MM XEC), one unused - # (5MM), one reused (10MM) - assert_unspent( - self.nodes[1], - total_count=2, - total_sum=15000000, - reused_count=1, - reused_sum=10000000) - # getbalances should show 10MM used, 5MM XEC trusted - assert_balances( - self.nodes[1], - mine={ - "used": 10000000, - "trusted": 5000000}) - - # node 1 should now have a balance of 5MM (no dirty) or 15MM (including - # dirty) - assert_approx(self.nodes[1].getbalance(), 5000000, 1000) - assert_approx( - self.nodes[1].getbalance( - avoid_reuse=False), - 15000000, - 1000) - - assert_raises_rpc_error(-6, "Insufficient funds", - self.nodes[1].sendtoaddress, retaddr, 10000000) - - self.nodes[1].sendtoaddress(retaddr, 4000000) - - # listunspent should show 2 total outputs (1MM, 10MM XEC), one unused - # (1MM), one reused (10MM) - assert_unspent( - self.nodes[1], - total_count=2, - total_sum=11000000, - reused_count=1, - reused_sum=10000000) - # getbalances should show 10MM used, 1MM XEC trusted - assert_balances( - self.nodes[1], - mine={ - "used": 10000000, - "trusted": 1000000}) - - # node 1 should now have about 1MM XEC left (no dirty) and 11MM - # (including dirty) - assert_approx(self.nodes[1].getbalance(), 1000000, 1000) - assert_approx( - self.nodes[1].getbalance( - avoid_reuse=False), - 11000000, - 1000) + if not self.options.descriptors: + # For the second send, we transmute it to a related single-key address + # to make sure it's also detected as re-use + # NB: this is not very useful for ABC, but we keep the new variable + # name for consistency. + new_fundaddr = fundaddr + + self.nodes[0].sendtoaddress(new_fundaddr, 10000000) + self.nodes[0].generate(1) + self.sync_all() + + # listunspent should show 2 total outputs (5MM, 10MM XEC), one unused + # (5MM), one reused (10MM) + assert_unspent( + self.nodes[1], + total_count=2, + total_sum=15000000, + reused_count=1, + reused_sum=10000000) + # getbalances should show 10MM used, 5MM XEC trusted + assert_balances( + self.nodes[1], + mine={ + "used": 10000000, + "trusted": 5000000}) + + # node 1 should now have a balance of 5MM (no dirty) or 15MM (including + # dirty) + assert_approx(self.nodes[1].getbalance(), 5000000, 1000) + assert_approx(self.nodes[1].getbalance(avoid_reuse=False), + 15000000, 1000) + + assert_raises_rpc_error(-6, "Insufficient funds", + self.nodes[1].sendtoaddress, retaddr, 10000000) + + self.nodes[1].sendtoaddress(retaddr, 4000000) + + # listunspent should show 2 total outputs (1MM, 10MM XEC), one unused + # (1MM), one reused (10MM) + assert_unspent( + self.nodes[1], + total_count=2, + total_sum=11000000, + reused_count=1, + reused_sum=10000000) + # getbalances should show 10MM used, 1MM XEC trusted + assert_balances( + self.nodes[1], + mine={ + "used": 10000000, + "trusted": 1000000}) + + # node 1 should now have about 1MM XEC left (no dirty) and 11MM + # (including dirty) + assert_approx(self.nodes[1].getbalance(), 1000000, 1000) + assert_approx(self.nodes[1].getbalance(avoid_reuse=False), + 11000000, 1000) def test_getbalances_used(self): ''' diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -48,6 +48,7 @@ return curr_balance 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) @@ -292,7 +293,8 @@ txid_not_broadcast = self.nodes[0].sendtoaddress( self.nodes[2].getnewaddress(), 2000000) tx_obj_not_broadcast = self.nodes[0].gettransaction(txid_not_broadcast) - self.nodes[1].generate(1) # mine a block, tx should not be in there + # mine a block, tx should not be in there + self.nodes[1].generate(1) self.sync_all(self.nodes[0:3]) # should not be changed because tx was not broadcasted assert_equal(self.nodes[2].getbalance(), node_2_bal) @@ -361,90 +363,92 @@ 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") - - # 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 dump the private key - # of an address you do not own - assert_raises_rpc_error(-4, - "Private key for address", - self.nodes[0].dumpprivkey, - temp_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].dumpprivkey, - "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 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 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 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, 1000000) - 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) - - # 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}) - - # 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}) + if not self.options.descriptors: + # 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 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 dump the private + # key of an address you do not own + assert_raises_rpc_error(-4, + "Private key for address", + self.nodes[0].dumpprivkey, + temp_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].dumpprivkey, + "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 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 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 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, 1000000) + 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) + + # 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}) + + # 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}) # Mine a block from node0 to an address from node1 coinbase_addr = self.nodes[1].getnewaddress() @@ -513,8 +517,9 @@ # ==Check that wallet prefers to use coins that don't exceed mempool li # Get all non-zero utxos together - chain_addrs = [self.nodes[0].getnewaddress( - ), self.nodes[0].getnewaddress()] + chain_addrs = [ + self.nodes[0].getnewaddress(), + self.nodes[0].getnewaddress()] singletxid = self.nodes[0].sendtoaddress( chain_addrs[0], self.nodes[0].getbalance(), "", "", True) self.nodes[0].generate(1) @@ -554,9 +559,9 @@ # Double chain limit but require combining inputs, so we pass # SelectCoinsMinConf self.stop_node(0) - self.start_node(0, - self.extra_args[0] + ["-walletrejectlongchains", - "-limitancestorcount=" + str(2 * chainlimit)]) + extra_args = ["-acceptnonstdtxn=1", "-walletrejectlongchains", + "-limitancestorcount=" + str(2 * chainlimit)] + self.start_node(0, extra_args=extra_args) # wait until the wallet has submitted all transactions to the mempool wait_until( diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -27,18 +27,23 @@ def run_test(self): # Make sure we use hd, keep masterkeyid - masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] - assert_equal(len(masterkeyid), 40) + hd_fingerprint = self.nodes[1].getaddressinfo( + self.nodes[1].getnewaddress())['hdmasterfingerprint'] + assert_equal(len(hd_fingerprint), 8) # create an internal key change_addr = self.nodes[1].getrawchangeaddress() change_addrV = self.nodes[1].getaddressinfo(change_addr) - # first internal child key - assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") + if self.options.descriptors: + assert_equal(change_addrV["hdkeypath"], "m/44'/1'/0'/1/0") + else: + # first internal child key + assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") # Import a non-HD private key in the HD wallet - non_hd_add = self.nodes[0].getnewaddress() - self.nodes[1].importprivkey(self.nodes[0].dumpprivkey(non_hd_add)) + non_hd_add = 'ecregtest:qr09jgufyeae4s97nqp6mv0tv6eymfunygeyv0llfe' + 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 self.nodes[1].backupwallet( @@ -50,11 +55,14 @@ self.nodes[0].generate(101) hd_add = None 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_info = self.nodes[1].getaddressinfo(hd_add) - assert_equal(hd_info["hdkeypath"], "m/0'/0'/" + str(i) + "'") - assert_equal(hd_info["hdseedid"], masterkeyid) + if self.options.descriptors: + assert_equal(hd_info["hdkeypath"], "m/44'/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, 1000000) self.nodes[0].generate(1) self.nodes[0].sendtoaddress(non_hd_add, 1000000) @@ -63,8 +71,11 @@ # create an internal key (again) change_addr = self.nodes[1].getrawchangeaddress() change_addrV = self.nodes[1].getaddressinfo(change_addr) - # second internal child key - assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") + if self.options.descriptors: + assert_equal(change_addrV["hdkeypath"], "m/44'/1'/0'/1/1") + else: + # second internal child key + assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") self.sync_all() assert_equal( @@ -73,7 +84,7 @@ self.log.info("Restore backup ...") self.stop_node(1) - # we need to delete the complete regtest directory + # we need to delete the complete chain directory # otherwise node1 would auto-recover all funds in flag the keypool keys # as used shutil.rmtree( @@ -81,19 +92,32 @@ self.nodes[1].datadir, self.chain, "blocks")) - 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.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")) self.start_node(1) # Assert that derivation is deterministic 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_info_2 = self.nodes[1].getaddressinfo(hd_add_2) - assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/" + str(i) + "'") - assert_equal(hd_info_2["hdseedid"], masterkeyid) + if self.options.descriptors: + assert_equal(hd_info_2["hdkeypath"], "m/44'/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) connect_nodes(self.nodes[0], self.nodes[1]) self.sync_all() @@ -150,178 +174,185 @@ keypath = self.nodes[1].getaddressinfo( out['scriptPubKey']['addresses'][0])['hdkeypath'] - assert_equal(keypath[0:7], "m/0'/1'") - - # Generate a new HD seed on node 1 and make sure it is set - orig_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] - self.nodes[1].sethdseed() - new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] - assert orig_masterkeyid != new_masterkeyid - addr = self.nodes[1].getnewaddress() - # Make sure the new address is the first from the keypool - assert_equal(self.nodes[1].getaddressinfo( - addr)['hdkeypath'], 'm/0\'/0\'/0\'') - # Fill keypool with 1 key - self.nodes[1].keypoolrefill(1) - - # Set a new HD seed on node 1 without flushing the keypool - new_seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress()) - orig_masterkeyid = new_masterkeyid - self.nodes[1].sethdseed(False, new_seed) - new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] - assert orig_masterkeyid != new_masterkeyid - addr = self.nodes[1].getnewaddress() - assert_equal(orig_masterkeyid, self.nodes[1].getaddressinfo( - addr)['hdseedid']) - # Make sure the new address continues previous keypool - assert_equal(self.nodes[1].getaddressinfo( - addr)['hdkeypath'], 'm/0\'/0\'/1\'') - - # Check that the next address is from the new seed - self.nodes[1].keypoolrefill(1) - next_addr = self.nodes[1].getnewaddress() - assert_equal(new_masterkeyid, self.nodes[1].getaddressinfo( - next_addr)['hdseedid']) - # Make sure the new address is not from previous keypool - assert_equal(self.nodes[1].getaddressinfo( - next_addr)['hdkeypath'], 'm/0\'/0\'/0\'') - assert next_addr != addr - - # Sethdseed parameter validity - 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(-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(-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())) - - self.log.info( - 'Test sethdseed restoring with keys outside of the initial keypool') - self.nodes[0].generate(10) - # Restart node 1 with keypool of 3 and a different wallet - self.nodes[1].createwallet(wallet_name='origin', blank=True) - self.restart_node(1, extra_args=['-keypool=3', '-wallet=origin']) - connect_nodes(self.nodes[0], self.nodes[1]) - - # sethdseed restoring and seeing txs to addresses out of the - # keypool - origin_rpc = self.nodes[1].get_wallet_rpc('origin') - seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress()) - origin_rpc.sethdseed(True, seed) - - self.nodes[1].createwallet(wallet_name='restore', blank=True) - restore_rpc = self.nodes[1].get_wallet_rpc('restore') - # Set to be the same seed as origin_rpc - restore_rpc.sethdseed(True, seed) - # Rotate to a new seed, making original `seed` inactive - restore_rpc.sethdseed(True) - - self.nodes[1].createwallet(wallet_name='restore2', blank=True) - restore2_rpc = self.nodes[1].get_wallet_rpc('restore2') - # Set to be the same seed as origin_rpc - restore2_rpc.sethdseed(True, seed) - # Rotate to a new seed, making original `seed` inactive - restore2_rpc.sethdseed(True) - - # Check persistence of inactive seed by reloading restore. restore2 - # is still loaded to test the case where the wallet is not reloaded - restore_rpc.unloadwallet() - self.nodes[1].loadwallet('restore') - restore_rpc = self.nodes[1].get_wallet_rpc('restore') - - # Empty origin keypool and get an address that is beyond the - # initial keypool - origin_rpc.getnewaddress() - origin_rpc.getnewaddress() - # Last address of initial keypool - last_addr = origin_rpc.getnewaddress() - # First address beyond initial keypool - addr = origin_rpc.getnewaddress() - - # Check that the restored seed has last_addr but does not have addr - info = restore_rpc.getaddressinfo(last_addr) - assert_equal(info['ismine'], True) - info = restore_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], False) - info = restore2_rpc.getaddressinfo(last_addr) - assert_equal(info['ismine'], True) - info = restore2_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], False) - # Check that the origin seed has addr - info = origin_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], True) - - # Send a transaction to addr, which is out of the initial keypool. - # The wallet that has set a new seed (restore_rpc) should not - # detect this transaction. - txid = self.nodes[0].sendtoaddress(addr, 1000000) - origin_rpc.sendrawtransaction( - self.nodes[0].gettransaction(txid)['hex']) - self.nodes[0].generate(1) - self.sync_blocks() - origin_rpc.gettransaction(txid) - assert_raises_rpc_error(-5, - 'Invalid or non-wallet transaction id', - restore_rpc.gettransaction, - txid) - out_of_kp_txid = txid - - # Send a transaction to last_addr, which is in the initial keypool. - # The wallet that has set a new seed (restore_rpc) should detect this - # transaction and generate 3 new keys from the initial seed. - # The previous transaction (out_of_kp_txid) should still not be - # detected as a rescan is required. - txid = self.nodes[0].sendtoaddress(last_addr, 1000000) - origin_rpc.sendrawtransaction( - self.nodes[0].gettransaction(txid)['hex']) - self.nodes[0].generate(1) - self.sync_blocks() - origin_rpc.gettransaction(txid) - restore_rpc.gettransaction(txid) - assert_raises_rpc_error(-5, - 'Invalid or non-wallet transaction id', - restore_rpc.gettransaction, - out_of_kp_txid) - restore2_rpc.gettransaction(txid) - assert_raises_rpc_error(-5, - 'Invalid or non-wallet transaction id', - restore2_rpc.gettransaction, - out_of_kp_txid) - - # After rescanning, restore_rpc should now see out_of_kp_txid and generate an additional key. - # addr should now be part of restore_rpc and be ismine - restore_rpc.rescanblockchain() - restore_rpc.gettransaction(out_of_kp_txid) - info = restore_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], True) - restore2_rpc.rescanblockchain() - restore2_rpc.gettransaction(out_of_kp_txid) - info = restore2_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], True) - - # Check again that 3 keys were derived. - # Empty keypool and get an address that is beyond the initial - # keypool - origin_rpc.getnewaddress() - origin_rpc.getnewaddress() - last_addr = origin_rpc.getnewaddress() - addr = origin_rpc.getnewaddress() - - # Check that the restored seed has last_addr but does not have addr - info = restore_rpc.getaddressinfo(last_addr) - assert_equal(info['ismine'], True) - info = restore_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], False) - info = restore2_rpc.getaddressinfo(last_addr) - assert_equal(info['ismine'], True) - info = restore2_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], False) + if self.options.descriptors: + assert_equal(keypath[0:14], "m/44'/1'/0'/1/") + else: + assert_equal(keypath[0:7], "m/0'/1'") + + if not self.options.descriptors: + # Generate a new HD seed on node 1 and make sure it is set + orig_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] + self.nodes[1].sethdseed() + new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] + assert orig_masterkeyid != new_masterkeyid + addr = self.nodes[1].getnewaddress() + # Make sure the new address is the first from the keypool + assert_equal(self.nodes[1].getaddressinfo( + addr)['hdkeypath'], 'm/0\'/0\'/0\'') + # Fill keypool with 1 key + self.nodes[1].keypoolrefill(1) + + # Set a new HD seed on node 1 without flushing the keypool + new_seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress()) + orig_masterkeyid = new_masterkeyid + self.nodes[1].sethdseed(False, new_seed) + new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] + assert orig_masterkeyid != new_masterkeyid + addr = self.nodes[1].getnewaddress() + assert_equal(orig_masterkeyid, + self.nodes[1].getaddressinfo(addr)['hdseedid']) + # Make sure the new address continues previous keypool + assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], + 'm/0\'/0\'/1\'') + + # Check that the next address is from the new seed + self.nodes[1].keypoolrefill(1) + next_addr = self.nodes[1].getnewaddress() + assert_equal(new_masterkeyid, + self.nodes[1].getaddressinfo(next_addr)['hdseedid']) + # Make sure the new address is not from previous keypool + assert_equal(self.nodes[1].getaddressinfo(next_addr)['hdkeypath'], + 'm/0\'/0\'/0\'') + assert next_addr != addr + + # Sethdseed parameter validity + 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( + -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(-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())) + + self.log.info( + 'Test sethdseed restoring with keys outside of the initial keypool') + self.nodes[0].generate(10) + # Restart node 1 with keypool of 3 and a different wallet + self.nodes[1].createwallet(wallet_name='origin', blank=True) + self.restart_node(1, extra_args=['-keypool=3', '-wallet=origin']) + connect_nodes(self.nodes[0], self.nodes[1]) + + # sethdseed restoring and seeing txs to addresses out of the + # keypool + origin_rpc = self.nodes[1].get_wallet_rpc('origin') + seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress()) + origin_rpc.sethdseed(True, seed) + + self.nodes[1].createwallet(wallet_name='restore', blank=True) + restore_rpc = self.nodes[1].get_wallet_rpc('restore') + # Set to be the same seed as origin_rpc + restore_rpc.sethdseed(True, seed) + # Rotate to a new seed, making original `seed` inactive + restore_rpc.sethdseed(True) + + self.nodes[1].createwallet(wallet_name='restore2', blank=True) + restore2_rpc = self.nodes[1].get_wallet_rpc('restore2') + # Set to be the same seed as origin_rpc + restore2_rpc.sethdseed(True, seed) + # Rotate to a new seed, making original `seed` inactive + restore2_rpc.sethdseed(True) + + # Check persistence of inactive seed by reloading restore. restore2 + # is still loaded to test the case where the wallet is not reloaded + restore_rpc.unloadwallet() + self.nodes[1].loadwallet('restore') + restore_rpc = self.nodes[1].get_wallet_rpc('restore') + + # Empty origin keypool and get an address that is beyond the + # initial keypool + origin_rpc.getnewaddress() + origin_rpc.getnewaddress() + # Last address of initial keypool + last_addr = origin_rpc.getnewaddress() + # First address beyond initial keypool + addr = origin_rpc.getnewaddress() + + # Check that the restored seed has last_addr but does not have addr + info = restore_rpc.getaddressinfo(last_addr) + assert_equal(info['ismine'], True) + info = restore_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], False) + info = restore2_rpc.getaddressinfo(last_addr) + assert_equal(info['ismine'], True) + info = restore2_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], False) + # Check that the origin seed has addr + info = origin_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], True) + + # Send a transaction to addr, which is out of the initial keypool. + # The wallet that has set a new seed (restore_rpc) should not + # detect this transaction. + txid = self.nodes[0].sendtoaddress(addr, 1000000) + origin_rpc.sendrawtransaction( + self.nodes[0].gettransaction(txid)['hex']) + self.nodes[0].generate(1) + self.sync_blocks() + origin_rpc.gettransaction(txid) + assert_raises_rpc_error(-5, + 'Invalid or non-wallet transaction id', + restore_rpc.gettransaction, + txid) + out_of_kp_txid = txid + + # Send a transaction to last_addr, which is in the initial keypool. + # The wallet that has set a new seed (restore_rpc) should detect this + # transaction and generate 3 new keys from the initial seed. + # The previous transaction (out_of_kp_txid) should still not be + # detected as a rescan is required. + txid = self.nodes[0].sendtoaddress(last_addr, 1000000) + origin_rpc.sendrawtransaction( + self.nodes[0].gettransaction(txid)['hex']) + self.nodes[0].generate(1) + self.sync_blocks() + origin_rpc.gettransaction(txid) + restore_rpc.gettransaction(txid) + assert_raises_rpc_error(-5, + 'Invalid or non-wallet transaction id', + restore_rpc.gettransaction, + out_of_kp_txid) + restore2_rpc.gettransaction(txid) + assert_raises_rpc_error(-5, + 'Invalid or non-wallet transaction id', + restore2_rpc.gettransaction, + out_of_kp_txid) + + # After rescanning, restore_rpc should now see out_of_kp_txid and generate an additional key. + # addr should now be part of restore_rpc and be ismine + restore_rpc.rescanblockchain() + restore_rpc.gettransaction(out_of_kp_txid) + info = restore_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], True) + restore2_rpc.rescanblockchain() + restore2_rpc.gettransaction(out_of_kp_txid) + info = restore2_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], True) + + # Check again that 3 keys were derived. + # Empty keypool and get an address that is beyond the initial + # keypool + origin_rpc.getnewaddress() + origin_rpc.getnewaddress() + last_addr = origin_rpc.getnewaddress() + addr = origin_rpc.getnewaddress() + + # Check that the restored seed has last_addr but does not have addr + info = restore_rpc.getaddressinfo(last_addr) + assert_equal(info['ismine'], True) + info = restore_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], False) + info = restore2_rpc.getaddressinfo(last_addr) + assert_equal(info['ismine'], True) + info = restore2_rpc.getaddressinfo(addr) + assert_equal(info['ismine'], False) if __name__ == '__main__': diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py --- a/test/functional/wallet_keypool.py +++ b/test/functional/wallet_keypool.py @@ -23,20 +23,62 @@ 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') + if self.options.descriptors: + # Import hardened derivation only descriptors + nodes[0].walletpassphrase('test', 10) + nodes[0].importdescriptors([ + { + "desc": "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/*h)#dfe4u060", + "timestamp": "now", + "range": [0, 0], + "active": True + }, + { + "desc": "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1h/*h)#a0nyvl0k", + "timestamp": "now", + "range": [0, 0], + "active": True + }, + { + "desc": "sh(pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/2h/*h))#ptcuwey3", + "timestamp": "now", + "range": [0, 0], + "active": True + }, + { + "desc": "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/4h/*h)#l3crwaus", + "timestamp": "now", + "range": [0, 0], + "active": True, + "internal": True + }, + { + "desc": "sh(pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/5h/*h))#7cxwe6ks", + "timestamp": "now", + "range": [0, 0], + "active": True, + "internal": True + } + ]) + nodes[0].walletlock() # 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_raises_rpc_error( - -12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) + if not self.options.descriptors: + # For Core, getaddressinfo also returns 'hdmasterfingerprint' + # with --descriptors. Our codebase diverged after D3122, + # GetKeyForDestination works only for P2PKH. + assert addr_before_encrypting_data['hdmasterfingerprint'] != addr_data['hdmasterfingerprint'] + 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) @@ -44,6 +86,10 @@ nodes[0].keypoolrefill(6) nodes[0].walletlock() wi = nodes[0].getwalletinfo() + + # This would be 18, for Core is using --descriptors. Our version + # of KeypoolCountExternalKeys has only LegacyScriptPubKeyMan + # when Core's version has 2 additional managers (BECH32, P2SH_SEGWIT). assert_equal(wi['keypoolsize_hd_internal'], 6) assert_equal(wi['keypoolsize'], 6) @@ -91,7 +137,10 @@ assert_equal(wi['keypoolsize'], 100) # 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') # refer to initial wallet as w1 @@ -99,9 +148,12 @@ # import private key and fund it address = addr.pop() - privkey = w1.dumpprivkey(address) - res = w2.importmulti( - [{'scriptPubKey': {'address': address}, 'keys': [privkey], 'timestamp': 'now'}]) + desc = w1.getaddressinfo(address)['desc'] + self.log.info(desc) + 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) w1.walletpassphrase('test', 100) @@ -134,27 +186,34 @@ outputs=[{destination: 100.00}], options={"subtractFeeFromOutputs": [0], "feeRate": 100}) assert_equal("psbt" in res, True) - # should work without subtractFeeFromOutputs if the exact fee is - # subtracted from the amount - res = w2.walletcreatefundedpsbt(inputs=[], - outputs=[{destination: 80.00}], - options={"feeRate": 100}) - assert_equal("psbt" in res, True) + if not self.options.descriptors: + # FIXME: make the following work with --descriptors, find out why + # it affects the way the fees are calculated + # should work without subtractFeeFromOutputs if the exact fee is + # subtracted from the amount + res = w2.walletcreatefundedpsbt(inputs=[], + outputs=[{destination: 80.00}], + options={"feeRate": 100}) + assert_equal("psbt" in res, True) # dust change should be removed res = w2.walletcreatefundedpsbt(inputs=[], - outputs=[{destination: 79.00}], + # Note: without --descriptors, we could + # raise this amount the 80.00 + outputs=[{destination: 78.40}], options={"feeRate": 100}) assert_equal("psbt" in res, True) - # create a transaction without change at the maximum fee rate, such - # that the output is still spendable: - res = w2.walletcreatefundedpsbt( - inputs=[], - outputs=[{destination: 100.00}], - options={"subtractFeeFromOutputs": [0], "feeRate": 494.90}) - assert_equal("psbt" in res, True) - assert_equal(res["fee"], Decimal("94.53")) + if not self.options.descriptors: + # FIXME: this should theoretically work with --descriptors + # create a transaction without change at the maximum fee rate, such + # that the output is still spendable: + res = w2.walletcreatefundedpsbt( + inputs=[], + outputs=[{destination: 100.00}], + options={"subtractFeeFromOutputs": [0], "feeRate": 492.30}) + assert_equal("psbt" in res, True) + assert_equal(res["fee"], Decimal("94.53")) # creating a 10,000 sat transaction with a manual change address should # be possible diff --git a/test/functional/wallet_keypool_topup.py b/test/functional/wallet_keypool_topup.py --- a/test/functional/wallet_keypool_topup.py +++ b/test/functional/wallet_keypool_topup.py @@ -66,8 +66,16 @@ assert_equal(self.nodes[1].listtransactions() [0]['category'], "receive") # Check that we have marked all keys up to the used keypool key as used - assert_equal(self.nodes[1].getaddressinfo( - self.nodes[1].getnewaddress())['hdkeypath'], "m/0'/0'/110'") + if self.options.descriptors: + assert_equal( + self.nodes[1].getaddressinfo( + self.nodes[1].getnewaddress())['hdkeypath'], + "m/44'/1'/0'/0/110") + else: + assert_equal( + self.nodes[1].getaddressinfo( + self.nodes[1].getnewaddress())['hdkeypath'], + "m/0'/0'/110'") if __name__ == '__main__': diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py --- a/test/functional/wallet_labels.py +++ b/test/functional/wallet_labels.py @@ -125,16 +125,17 @@ "") # Check that addmultisigaddress can assign labels. - for label in labels: - addresses = [] - for _ in range(10): - addresses.append(node.getnewaddress()) - multisig_address = node.addmultisigaddress( - 5, addresses, label.name)['address'] - label.add_address(multisig_address) - label.purpose[multisig_address] = "send" - label.verify(node) - node.generate(101) + if not self.options.descriptors: + for label in labels: + addresses = [] + for _ in range(10): + addresses.append(node.getnewaddress()) + multisig_address = node.addmultisigaddress( + 5, addresses, label.name)['address'] + label.add_address(multisig_address) + label.purpose[multisig_address] = "send" + label.verify(node) + node.generate(101) # Check that setlabel can change the label of an address from a # different label.