diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py index f786ed02e2..cfcb70e2aa 100755 --- a/test/functional/feature_backwards_compatibility.py +++ b/test/functional/feature_backwards_compatibility.py @@ -24,6 +24,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, + assert_raises_rpc_error, ) @@ -82,7 +83,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): # w1: regular wallet, created on master: update this test when default # wallets can no longer be opened by older versions. - node_master.rpc.createwallet(wallet_name="w1") + node_master.createwallet(wallet_name="w1") wallet = node_master.get_wallet_rpc("w1") info = wallet.getwalletinfo() assert info['private_keys_enabled'] @@ -101,7 +102,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): assert info['private_keys_enabled'] assert info['keypoolsize'] > 0 # Use addmultisigaddress (see #18075) - address_18075 = wallet.rpc.addmultisigaddress(1, ["0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52", "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073"], "")["address"] + address_18075 = wallet.addmultisigaddress(1, ["0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52", "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073"], "")["address"] assert wallet.getaddressinfo(address_18075)["solvable"] # w1_v18: regular wallet, created with v0.18 @@ -217,82 +218,100 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): os.path.join(node_v20_wallets_dir, wallet) ) - # Open the wallets in v0.20 - node_v20.loadwallet("w1") - wallet = node_v20.get_wallet_rpc("w1") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] > 0 - txs = wallet.listtransactions() - assert_equal(len(txs), 1) + if not self.options.descriptors: + # Descriptor wallets break compatibility, only run this test for legacy wallet + # Open the wallets in v0.20 + node_v20.loadwallet("w1") + wallet = node_v20.get_wallet_rpc("w1") + info = wallet.getwalletinfo() + assert info['private_keys_enabled'] + assert info['keypoolsize'] > 0 + txs = wallet.listtransactions() + assert_equal(len(txs), 1) - node_v20.loadwallet("w2") - wallet = node_v20.get_wallet_rpc("w2") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] == False - assert info['keypoolsize'] == 0 + node_v20.loadwallet("w2") + wallet = node_v20.get_wallet_rpc("w2") + info = wallet.getwalletinfo() + assert info['private_keys_enabled'] == False + assert info['keypoolsize'] == 0 - node_v20.loadwallet("w3") - wallet = node_v20.get_wallet_rpc("w3") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] == 0 + node_v20.loadwallet("w3") + wallet = node_v20.get_wallet_rpc("w3") + info = wallet.getwalletinfo() + assert info['private_keys_enabled'] + assert info['keypoolsize'] == 0 - # Open the wallets in v0.19 - node_v19.loadwallet("w1") - wallet = node_v19.get_wallet_rpc("w1") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] > 0 - txs = wallet.listtransactions() - assert_equal(len(txs), 1) + # Open the wallets in v0.19 + node_v19.loadwallet("w1") + wallet = node_v19.get_wallet_rpc("w1") + info = wallet.getwalletinfo() + assert info['private_keys_enabled'] + assert info['keypoolsize'] > 0 + txs = wallet.listtransactions() + assert_equal(len(txs), 1) - node_v19.loadwallet("w2") - wallet = node_v19.get_wallet_rpc("w2") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] == False - assert info['keypoolsize'] == 0 + node_v19.loadwallet("w2") + wallet = node_v19.get_wallet_rpc("w2") + info = wallet.getwalletinfo() + assert info['private_keys_enabled'] == False + assert info['keypoolsize'] == 0 - node_v19.loadwallet("w3") - wallet = node_v19.get_wallet_rpc("w3") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] == 0 + node_v19.loadwallet("w3") + wallet = node_v19.get_wallet_rpc("w3") + info = wallet.getwalletinfo() + assert info['private_keys_enabled'] + assert info['keypoolsize'] == 0 - # Open the wallets in v0.18 - node_v18.loadwallet("w1") - wallet = node_v18.get_wallet_rpc("w1") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] > 0 - txs = wallet.listtransactions() - assert_equal(len(txs), 1) + # Open the wallets in v0.18 + node_v18.loadwallet("w1") + wallet = node_v18.get_wallet_rpc("w1") + info = wallet.getwalletinfo() + assert info['private_keys_enabled'] + assert info['keypoolsize'] > 0 + txs = wallet.listtransactions() + assert_equal(len(txs), 1) - node_v18.loadwallet("w2") - wallet = node_v18.get_wallet_rpc("w2") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] == False - assert info['keypoolsize'] == 0 + node_v18.loadwallet("w2") + wallet = node_v18.get_wallet_rpc("w2") + info = wallet.getwalletinfo() + assert info['private_keys_enabled'] == False + assert info['keypoolsize'] == 0 - node_v18.loadwallet("w3") - wallet = node_v18.get_wallet_rpc("w3") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] == 0 + node_v18.loadwallet("w3") + wallet = node_v18.get_wallet_rpc("w3") + info = wallet.getwalletinfo() + assert info['private_keys_enabled'] + assert info['keypoolsize'] == 0 + + node_v17.loadwallet("w1") + wallet = node_v17.get_wallet_rpc("w1") + info = wallet.getwalletinfo() + # doesn't have private_keys_enabled in v17 + #assert info['private_keys_enabled'] + assert info['keypoolsize'] > 0 + + node_v17.loadwallet("w2") + wallet = node_v17.get_wallet_rpc("w2") + info = wallet.getwalletinfo() + # doesn't have private_keys_enabled in v17 + # TODO enable back when HD wallets are created by default + # assert info['private_keys_enabled'] == False + # assert info['keypoolsize'] == 0 + else: + # Descriptor wallets appear to be corrupted wallets to old software + assert_raises_rpc_error(-4, "Wallet requires newer version of Dash Core", node_v19.loadwallet, "w1") + node_v19.loadwallet("w2") + node_v19.loadwallet("w3") + assert_raises_rpc_error(-18, "Data is not in recognized format", node_v18.loadwallet, "w1") + node_v18.loadwallet("w2") + node_v18.loadwallet("w3") # Open the wallets in v0.17 node_v17.loadwallet("w1_v18") wallet = node_v17.get_wallet_rpc("w1_v18") info = wallet.getwalletinfo() # doesn't have private_keys_enabled in v17 - #assert info['private_keys_enabled'] - assert info['keypoolsize'] > 0 - - node_v17.loadwallet("w1") - wallet = node_v17.get_wallet_rpc("w1") - info = wallet.getwalletinfo() - # doesn't have private_keys_enabled in v17 - #assert info['private_keys_enabled'] + # assert info['private_keys_enabled'] assert info['keypoolsize'] > 0 node_v17.loadwallet("w2_v18") @@ -300,14 +319,6 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): info = wallet.getwalletinfo() # doesn't have private_keys_enabled in v17 # TODO enable back when HD wallets are created by default - # assert info['private_keys_enabled'] == False - # assert info['keypoolsize'] == 0 - - node_v17.loadwallet("w2") - wallet = node_v17.get_wallet_rpc("w2") - info = wallet.getwalletinfo() - # doesn't have private_keys_enabled in v17 - # TODO enable back when HD wallets are created by default #assert info['private_keys_enabled'] == False #assert info['keypoolsize'] == 0 diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py index acf3a1a87d..47c2515c70 100755 --- a/test/functional/feature_nulldummy.py +++ b/test/functional/feature_nulldummy.py @@ -46,8 +46,15 @@ class NULLDUMMYTest(BitcoinTestFramework): self.skip_if_no_wallet() def run_test(self): - self.address = self.nodes[0].getnewaddress() - self.ms_address = self.nodes[0].addmultisigaddress(1, [self.address])['address'] + self.nodes[0].createwallet(wallet_name='wmulti', disable_private_keys=True) + wmulti = self.nodes[0].get_wallet_rpc('wmulti') + w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + self.address = w0.getnewaddress() + self.pubkey = w0.getaddressinfo(self.address)['pubkey'] + self.ms_address = wmulti.addmultisigaddress(1, [self.pubkey])['address'] + if not self.options.descriptors: + # Legacy wallets need to import these so that they are watched by the wallet. This is unnecssary (and does not need to be tested) for descriptor wallets + wmulti.importaddress(self.ms_address) self.coinbase_blocks = self.nodes[0].generate(2) # Block 2 coinbase_txid = [] diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index c4977efab0..e0982ce816 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -5,6 +5,7 @@ """Test the fundrawtransaction RPC.""" from decimal import Decimal +from test_framework.descriptors import descsum_create from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -104,17 +105,19 @@ class RawTransactionsTest(BitcoinTestFramework): rawmatch = self.nodes[2].fundrawtransaction(rawmatch, {"changePosition":1, "subtractFeeFromOutputs":[0]}) assert_equal(rawmatch["changepos"], -1) + self.nodes[3].createwallet(wallet_name="wwatch", disable_private_keys=True) + wwatch = self.nodes[3].get_wallet_rpc('wwatch') watchonly_address = self.nodes[0].getnewaddress() watchonly_pubkey = self.nodes[0].getaddressinfo(watchonly_address)["pubkey"] self.watchonly_amount = Decimal(2000) - self.nodes[3].importpubkey(watchonly_pubkey, "", True) + wwatch.importpubkey(watchonly_pubkey, "", True) self.watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, self.watchonly_amount) # Lock UTXO so nodes[0] doesn't accidentally spend it self.watchonly_vout = find_vout_for_address(self.nodes[0], self.watchonly_txid, watchonly_address) self.nodes[0].lockunspent(False, [{"txid": self.watchonly_txid, "vout": self.watchonly_vout}]) - self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), self.watchonly_amount / 10) + self.nodes[0].sendtoaddress(self.nodes[3].get_wallet_rpc(self.default_wallet_name).getnewaddress(), self.watchonly_amount / 10) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 15) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10) @@ -123,6 +126,8 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_all() + wwatch.unloadwallet() + def test_simple(self): self.log.info("Test fundrawtxn") inputs = [ ] @@ -400,7 +405,7 @@ class RawTransactionsTest(BitcoinTestFramework): addr1Obj = self.nodes[1].getaddressinfo(addr1) addr2Obj = self.nodes[1].getaddressinfo(addr2) - mSigObj = self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address'] + mSigObj = self.nodes[3].createmultisig(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address'] inputs = [] outputs = {mSigObj:11} @@ -432,7 +437,7 @@ class RawTransactionsTest(BitcoinTestFramework): addr4Obj = self.nodes[1].getaddressinfo(addr4) addr5Obj = self.nodes[1].getaddressinfo(addr5) - mSigObj = self.nodes[1].addmultisigaddress( + mSigObj = self.nodes[1].createmultisig( 4, [ addr1Obj['pubkey'], @@ -458,7 +463,7 @@ class RawTransactionsTest(BitcoinTestFramework): def test_spend_2of2(self): """Spend a 2-of-2 multisig transaction over fundraw.""" - self.log.info("Test fundrawtxn spending 2-of-2 multisig") + self.log.info("Test fundpsbt spending 2-of-2 multisig") # Create 2-of-2 addr. addr1 = self.nodes[2].getnewaddress() @@ -467,13 +472,18 @@ class RawTransactionsTest(BitcoinTestFramework): addr1Obj = self.nodes[2].getaddressinfo(addr1) addr2Obj = self.nodes[2].getaddressinfo(addr2) - mSigObj = self.nodes[2].addmultisigaddress( + self.nodes[2].createwallet(wallet_name='wmulti', disable_private_keys=True) + wmulti = self.nodes[2].get_wallet_rpc('wmulti') + w2 = self.nodes[2].get_wallet_rpc(self.default_wallet_name) + mSigObj = wmulti.addmultisigaddress( 2, [ addr1Obj['pubkey'], addr2Obj['pubkey'], ] )['address'] + if not self.options.descriptors: + wmulti.importaddress(mSigObj) # send 12 DASH to msig addr self.nodes[0].sendtoaddress(mSigObj, 12) @@ -483,22 +493,39 @@ class RawTransactionsTest(BitcoinTestFramework): oldBalance = self.nodes[1].getbalance() inputs = [] outputs = {self.nodes[1].getnewaddress():11} - rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - fundedTx = self.nodes[2].fundrawtransaction(rawtx) + funded_psbt = wmulti.walletcreatefundedpsbt(inputs=inputs, outputs=outputs, options={'changeAddress': w2.getrawchangeaddress()})['psbt'] - signedTx = self.nodes[2].signrawtransactionwithwallet(fundedTx['hex']) - self.nodes[2].sendrawtransaction(signedTx['hex']) + signed_psbt = w2.walletprocesspsbt(funded_psbt) + final_psbt = w2.finalizepsbt(signed_psbt['psbt']) + self.nodes[2].sendrawtransaction(final_psbt['hex']) self.nodes[2].generate(1) self.sync_all() # Make sure funds are received at node1. assert_equal(oldBalance+Decimal('11.0000000'), self.nodes[1].getbalance()) + wmulti.unloadwallet() + def test_locked_wallet(self): - self.log.info("Test fundrawtxn with locked wallet") + self.log.info("Test fundrawtxn with locked wallet and hardened derivation") self.nodes[1].encryptwallet("test") + if self.options.descriptors: + self.nodes[1].walletpassphrase('test', 10) + self.nodes[1].importdescriptors([{ + 'desc': descsum_create('pkh(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/0h/*h)'), + 'timestamp': 'now', + 'active': True + }, + { + 'desc': descsum_create('pkh(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/1h/*h)'), + 'timestamp': 'now', + 'active': True, + 'internal': True + }]) + self.nodes[1].walletlock() + # Drain the keypool. self.nodes[1].getnewaddress() inputs = self.nodes[1].listunspent() @@ -616,7 +643,25 @@ class RawTransactionsTest(BitcoinTestFramework): outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount / 2} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) - result = self.nodes[3].fundrawtransaction(rawtx, {'includeWatching': True }) + self.nodes[3].loadwallet('wwatch') + wwatch = self.nodes[3].get_wallet_rpc('wwatch') + # Setup change addresses for the watchonly wallet + desc_import = [{ + "desc": descsum_create("pkh(tpubD6NzVbkrYhZ4YNXVQbNhMK1WqguFsUXceaVJKbmno2aZ3B6QfbMeraaYvnBSGpV3vxLyTTK9DYT1yoEck4XUScMzXoQ2U2oSmE2JyMedq3H/1/*)"), + "timestamp": "now", + "internal": True, + "active": True, + "keypool": True, + "range": [0, 100], + "watchonly": True, + }] + if self.options.descriptors: + wwatch.importdescriptors(desc_import) + else: + wwatch.importmulti(desc_import) + + # Backward compatibility test (2nd params is includeWatching) + result = wwatch.fundrawtransaction(rawtx, True) res_dec = self.nodes[0].decoderawtransaction(result["hex"]) assert_equal(len(res_dec["vin"]), 1) assert_equal(res_dec["vin"][0]["txid"], self.watchonly_txid) @@ -624,6 +669,8 @@ class RawTransactionsTest(BitcoinTestFramework): assert "fee" in result.keys() assert_greater_than(result["changepos"], -1) + wwatch.unloadwallet() + def test_all_watched_funds(self): self.log.info("Test fundrawtxn using entirety of watched funds") @@ -631,17 +678,19 @@ class RawTransactionsTest(BitcoinTestFramework): outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount} rawtx = self.nodes[3].createrawtransaction(inputs, outputs) - # Backward compatibility test (2nd param is includeWatching). - result = self.nodes[3].fundrawtransaction(rawtx, True) + self.nodes[3].loadwallet('wwatch') + wwatch = self.nodes[3].get_wallet_rpc('wwatch') + w3 = self.nodes[3].get_wallet_rpc(self.default_wallet_name) + result = wwatch.fundrawtransaction(rawtx, {'includeWatching': True, 'changeAddress': w3.getrawchangeaddress(), 'subtractFeeFromOutputs': [0]}) res_dec = self.nodes[0].decoderawtransaction(result["hex"]) - assert_equal(len(res_dec["vin"]), 2) - assert res_dec["vin"][0]["txid"] == self.watchonly_txid or res_dec["vin"][1]["txid"] == self.watchonly_txid + assert_equal(len(res_dec["vin"]), 1) + assert res_dec["vin"][0]["txid"] == self.watchonly_txid assert_greater_than(result["fee"], 0) - assert_greater_than(result["changepos"], -1) - assert_equal(result["fee"] + res_dec["vout"][result["changepos"]]["value"], self.watchonly_amount / 10) + assert_equal(result["changepos"], -1) + assert_equal(result["fee"] + res_dec["vout"][0]["value"], self.watchonly_amount) - signedtx = self.nodes[3].signrawtransactionwithwallet(result["hex"]) + signedtx = wwatch.signrawtransactionwithwallet(result["hex"]) assert not signedtx["complete"] signedtx = self.nodes[0].signrawtransactionwithwallet(signedtx["hex"]) assert signedtx["complete"] @@ -649,6 +698,8 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].generate(1) self.sync_all() + wwatch.unloadwallet() + def test_option_feerate(self): self.log.info("Test fundrawtxn feeRate option") diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 11aa9a7782..c32a0c2e01 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -22,6 +22,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, + find_vout_for_address, hex_str_to_bytes, ) @@ -186,125 +187,140 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[0].reconsiderblock(block1) assert_equal(self.nodes[0].getbestblockhash(), block2) - ######################### - # RAW TX MULTISIG TESTS # - ######################### - # 2of2 test - addr1 = self.nodes[2].getnewaddress() - addr2 = self.nodes[2].getnewaddress() + if not self.options.descriptors: + # The traditional multisig workflow does not work with descriptor wallets so these are legacy only. + # The multisig workflow with descriptor wallets uses PSBTs and is tested elsewhere, no need to do them here. + ######################### + # RAW TX MULTISIG TESTS # + ######################### + # 2of2 test + addr1 = self.nodes[2].getnewaddress() + addr2 = self.nodes[2].getnewaddress() - addr1Obj = self.nodes[2].getaddressinfo(addr1) - addr2Obj = self.nodes[2].getaddressinfo(addr2) + addr1Obj = self.nodes[2].getaddressinfo(addr1) + addr2Obj = self.nodes[2].getaddressinfo(addr2) - # Tests for createmultisig and addmultisigaddress - assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, ["01020304"]) - self.nodes[0].createmultisig(2, [addr1Obj['pubkey'], addr2Obj['pubkey']]) # createmultisig can only take public keys - assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 2, [addr1Obj['pubkey'], addr1]) # addmultisigaddress can take both pubkeys and addresses so long as they are in the wallet, which is tested here. + # Tests for createmultisig and addmultisigaddress + assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, ["01020304"]) + self.nodes[0].createmultisig(2, [addr1Obj['pubkey'], addr2Obj['pubkey']]) # createmultisig can only take public keys + assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 2, [addr1Obj['pubkey'], addr1]) # addmultisigaddress can take both pubkeys and addresses so long as they are in the wallet, which is tested here. - mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr1])['address'] + mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr1])['address'] - #use balance deltas instead of absolute values - bal = self.nodes[2].getbalance() + #use balance deltas instead of absolute values + bal = self.nodes[2].getbalance() - # send 1.2 BTC to msig adr - txId = self.nodes[0].sendtoaddress(mSigObj, 1.2) - self.sync_all() + # send 1.2 BTC to msig adr + txId = self.nodes[0].sendtoaddress(mSigObj, 1.2) + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + assert_equal(self.nodes[2].getbalance(), bal+Decimal('1.20000000')) #node2 has both keys of the 2of2 ms addr., tx should affect the balance + + + # 2of3 test from different nodes + bal = self.nodes[2].getbalance() + addr1 = self.nodes[1].getnewaddress() + addr2 = self.nodes[2].getnewaddress() + addr3 = self.nodes[2].getnewaddress() + + addr1Obj = self.nodes[1].getaddressinfo(addr1) + addr2Obj = self.nodes[2].getaddressinfo(addr2) + addr3Obj = self.nodes[2].getaddressinfo(addr3) + + mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey']])['address'] + + txId = self.nodes[0].sendtoaddress(mSigObj, 2.2) + decTx = self.nodes[0].gettransaction(txId) + rawTx = self.nodes[0].decoderawtransaction(decTx['hex']) + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + + #THIS IS AN INCOMPLETE FEATURE + #NODE2 HAS TWO OF THREE KEY AND THE FUNDS SHOULD BE SPENDABLE AND COUNT AT BALANCE CALCULATION + assert_equal(self.nodes[2].getbalance(), bal) #for now, assume the funds of a 2of3 multisig tx are not marked as spendable + + txDetails = self.nodes[0].gettransaction(txId, True) + rawTx = self.nodes[0].decoderawtransaction(txDetails['hex']) + vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('2.20000000')) + + bal = self.nodes[0].getbalance() + inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex']}] + outputs = { self.nodes[0].getnewaddress() : 2.19 } + rawTx = self.nodes[2].createrawtransaction(inputs, outputs) + rawTxPartialSigned = self.nodes[1].signrawtransactionwithwallet(rawTx, inputs) + assert_equal(rawTxPartialSigned['complete'], False) #node1 only has one key, can't comp. sign the tx + + rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx, inputs) + assert_equal(rawTxSigned['complete'], True) #node2 can sign the tx compl., own two of three keys + self.nodes[2].sendrawtransaction(rawTxSigned['hex']) + rawTx = self.nodes[0].decoderawtransaction(rawTxSigned['hex']) + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + assert_equal(self.nodes[0].getbalance(), bal+Decimal('500.00000000')+Decimal('2.19000000')) #block reward + tx + + # 2of2 test for combining transactions + bal = self.nodes[2].getbalance() + addr1 = self.nodes[1].getnewaddress() + addr2 = self.nodes[2].getnewaddress() + + addr1Obj = self.nodes[1].getaddressinfo(addr1) + addr2Obj = self.nodes[2].getaddressinfo(addr2) + + self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address'] + mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address'] + mSigObjValid = self.nodes[2].getaddressinfo(mSigObj) + + txId = self.nodes[0].sendtoaddress(mSigObj, 2.2) + decTx = self.nodes[0].gettransaction(txId) + rawTx2 = self.nodes[0].decoderawtransaction(decTx['hex']) + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + + assert_equal(self.nodes[2].getbalance(), bal) # the funds of a 2of2 multisig tx should not be marked as spendable + + txDetails = self.nodes[0].gettransaction(txId, True) + rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex']) + vout = next(o for o in rawTx2['vout'] if o['value'] == Decimal('2.20000000')) + + bal = self.nodes[0].getbalance() + inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex']}] + outputs = { self.nodes[0].getnewaddress() : 2.19 } + rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs) + rawTxPartialSigned1 = self.nodes[1].signrawtransactionwithwallet(rawTx2, inputs) + self.log.debug(rawTxPartialSigned1) + assert_equal(rawTxPartialSigned1['complete'], False) #node1 only has one key, can't comp. sign the tx + + rawTxPartialSigned2 = self.nodes[2].signrawtransactionwithwallet(rawTx2, inputs) + self.log.debug(rawTxPartialSigned2) + assert_equal(rawTxPartialSigned2['complete'], False) #node2 only has one key, can't comp. sign the tx + rawTxComb = self.nodes[2].combinerawtransaction([rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']]) + self.log.debug(rawTxComb) + self.nodes[2].sendrawtransaction(rawTxComb) + rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb) + self.sync_all() + self.nodes[0].generate(1) + self.sync_all() + assert_equal(self.nodes[0].getbalance(), bal+Decimal('500.00000000')+Decimal('2.19000000')) #block reward + tx + + + # Basic signrawtransaction test + addr = self.nodes[1].getnewaddress() + txid = self.nodes[0].sendtoaddress(addr, 10) self.nodes[0].generate(1) self.sync_all() - assert_equal(self.nodes[2].getbalance(), bal+Decimal('1.20000000')) #node2 has both keys of the 2of2 ms addr., tx should affect the balance - - - # 2of3 test from different nodes - bal = self.nodes[2].getbalance() - addr1 = self.nodes[1].getnewaddress() - addr2 = self.nodes[2].getnewaddress() - addr3 = self.nodes[2].getnewaddress() - - addr1Obj = self.nodes[1].getaddressinfo(addr1) - addr2Obj = self.nodes[2].getaddressinfo(addr2) - addr3Obj = self.nodes[2].getaddressinfo(addr3) - - mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey']])['address'] - - txId = self.nodes[0].sendtoaddress(mSigObj, 2.2) - decTx = self.nodes[0].gettransaction(txId) - rawTx = self.nodes[0].decoderawtransaction(decTx['hex']) - self.sync_all() + vout = find_vout_for_address(self.nodes[1], txid, addr) + rawTx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): 9.999}) + rawTxSigned = self.nodes[1].signrawtransactionwithwallet(rawTx) + txId = self.nodes[1].sendrawtransaction(rawTxSigned['hex']) self.nodes[0].generate(1) self.sync_all() - #THIS IS AN INCOMPLETE FEATURE - #NODE2 HAS TWO OF THREE KEY AND THE FUNDS SHOULD BE SPENDABLE AND COUNT AT BALANCE CALCULATION - assert_equal(self.nodes[2].getbalance(), bal) #for now, assume the funds of a 2of3 multisig tx are not marked as spendable - - txDetails = self.nodes[0].gettransaction(txId, True) - rawTx = self.nodes[0].decoderawtransaction(txDetails['hex']) - vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('2.20000000')) - - bal = self.nodes[0].getbalance() - inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex']}] - outputs = { self.nodes[0].getnewaddress() : 2.19 } - rawTx = self.nodes[2].createrawtransaction(inputs, outputs) - rawTxPartialSigned = self.nodes[1].signrawtransactionwithwallet(rawTx, inputs) - assert_equal(rawTxPartialSigned['complete'], False) #node1 only has one key, can't comp. sign the tx - - rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx, inputs) - assert_equal(rawTxSigned['complete'], True) #node2 can sign the tx compl., own two of three keys - self.nodes[2].sendrawtransaction(rawTxSigned['hex']) - rawTx = self.nodes[0].decoderawtransaction(rawTxSigned['hex']) - self.sync_all() - self.nodes[0].generate(1) - self.sync_all() - assert_equal(self.nodes[0].getbalance(), bal+Decimal('500.00000000')+Decimal('2.19000000')) #block reward + tx - - # 2of2 test for combining transactions - bal = self.nodes[2].getbalance() - addr1 = self.nodes[1].getnewaddress() - addr2 = self.nodes[2].getnewaddress() - - addr1Obj = self.nodes[1].getaddressinfo(addr1) - addr2Obj = self.nodes[2].getaddressinfo(addr2) - - self.nodes[1].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address'] - mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address'] - mSigObjValid = self.nodes[2].getaddressinfo(mSigObj) - - txId = self.nodes[0].sendtoaddress(mSigObj, 2.2) - decTx = self.nodes[0].gettransaction(txId) - rawTx2 = self.nodes[0].decoderawtransaction(decTx['hex']) - self.sync_all() - self.nodes[0].generate(1) - self.sync_all() - - assert_equal(self.nodes[2].getbalance(), bal) # the funds of a 2of2 multisig tx should not be marked as spendable - - txDetails = self.nodes[0].gettransaction(txId, True) - rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex']) - vout = next(o for o in rawTx2['vout'] if o['value'] == Decimal('2.20000000')) - - bal = self.nodes[0].getbalance() - inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex']}] - outputs = { self.nodes[0].getnewaddress() : 2.19 } - rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs) - rawTxPartialSigned1 = self.nodes[1].signrawtransactionwithwallet(rawTx2, inputs) - self.log.debug(rawTxPartialSigned1) - assert_equal(rawTxPartialSigned1['complete'], False) #node1 only has one key, can't comp. sign the tx - - rawTxPartialSigned2 = self.nodes[2].signrawtransactionwithwallet(rawTx2, inputs) - self.log.debug(rawTxPartialSigned2) - assert_equal(rawTxPartialSigned2['complete'], False) #node2 only has one key, can't comp. sign the tx - rawTxComb = self.nodes[2].combinerawtransaction([rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']]) - self.log.debug(rawTxComb) - self.nodes[2].sendrawtransaction(rawTxComb) - rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb) - self.sync_all() - self.nodes[0].generate(1) - self.sync_all() - assert_equal(self.nodes[0].getbalance(), bal+Decimal('500.00000000')+Decimal('2.19000000')) #block reward + tx - # getrawtransaction tests # 1. valid parameters - only supply txid - txId = rawTx["txid"] assert_equal(self.nodes[0].getrawtransaction(txId), rawTxSigned['hex']) assert_equal(self.nodes[0].getrawtransactionmulti({"0":[txId]})[txId], rawTxSigned['hex']) diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 7e68fe9a63..e8202f5e7d 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -202,9 +202,8 @@ def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, script_pub_key=C def create_transaction(node, txid, to_address, *, amount): """ Return signed transaction spending the first output of the - input txid. Note that the node must be able to sign for the - output that is being spent, and the node must not be running - multiple wallets. + input txid. Note that the node must have a wallet that can + sign for the output that is being spent. """ raw_tx = create_raw_transaction(node, txid, to_address, amount=amount) tx = CTransaction() @@ -213,14 +212,18 @@ def create_transaction(node, txid, to_address, *, amount): def create_raw_transaction(node, txid, to_address, *, amount): """ Return raw signed transaction spending the first output of the - input txid. Note that the node must be able to sign for the - output that is being spent, and the node must not be running - multiple wallets. + input txid. Note that the node must have a wallet that can sign + for the output that is being spent. """ - rawtx = node.createrawtransaction(inputs=[{"txid": txid, "vout": 0}], outputs={to_address: amount}) - signresult = node.signrawtransactionwithwallet(rawtx) - assert_equal(signresult["complete"], True) - return signresult['hex'] + psbt = node.createpsbt(inputs=[{"txid": txid, "vout": 0}], outputs={to_address: amount}) + for _ in range(2): + for w in node.listwallets(): + wrpc = node.get_wallet_rpc(w) + signed_psbt = wrpc.walletprocesspsbt(psbt) + psbt = signed_psbt['psbt'] + final_psbt = node.finalizepsbt(psbt) + assert_equal(final_psbt["complete"], True) + return final_psbt['hex'] def get_legacy_sigopcount_block(block, accurate=True): count = 0 diff --git a/test/functional/test_framework/script_util.py b/test/functional/test_framework/script_util.py index 18340b2bbb..40a4486755 100755 --- a/test/functional/test_framework/script_util.py +++ b/test/functional/test_framework/script_util.py @@ -3,7 +3,8 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Useful Script constants and utils.""" -from test_framework.script import CScript +from test_framework.script import CScript, hash160, OP_DUP, OP_HASH160, OP_CHECKSIG, OP_EQUAL, OP_EQUALVERIFY +from test_framework.util import hex_str_to_bytes # To prevent a "tx-size-small" policy rule error, a transaction has to have a # size of at least 83 bytes (MIN_STANDARD_TX_SIZE in @@ -24,3 +25,33 @@ from test_framework.script import CScript # met. DUMMY_P2SH_SCRIPT = CScript([b'a' * 22]) DUMMY_2_P2SH_SCRIPT = CScript([b'b' * 22]) + +def keyhash_to_p2pkh_script(hash, main = False): + assert len(hash) == 20 + return CScript([OP_DUP, OP_HASH160, hash, OP_EQUALVERIFY, OP_CHECKSIG]) + +def scripthash_to_p2sh_script(hash, main = False): + assert len(hash) == 20 + return CScript([OP_HASH160, hash, OP_EQUAL]) + +def key_to_p2pkh_script(key, main = False): + key = check_key(key) + return keyhash_to_p2pkh_script(hash160(key), main) + +def script_to_p2sh_script(script, main = False): + script = check_script(script) + return scripthash_to_p2sh_script(hash160(script), main) + +def check_key(key): + if isinstance(key, str): + key = hex_str_to_bytes(key) # Assuming this is hex string + if isinstance(key, bytes) and (len(key) == 33 or len(key) == 65): + return key + assert False + +def check_script(script): + if isinstance(script, str): + script = hex_str_to_bytes(script) # Assuming this is hex string + if isinstance(script, bytes) or isinstance(script, CScript): + return script + assert False diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 24d2e8a0de..8f5034e866 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -219,8 +219,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): parser.add_argument("--randomseed", type=int, help="set a random seed for deterministically reproducing a previous test run") parser.add_argument('--timeout-factor', dest="timeout_factor", type=float, default=1.0, help='adjust test timeouts by a factor. Setting it to 0 disables all timeouts') - parser.add_argument("--descriptors", default=False, action="store_true", - help="Run test using a descriptor wallet") + + group = parser.add_mutually_exclusive_group() + group.add_argument("--descriptors", default=False, action="store_true", + help="Run test using a descriptor wallet", dest='descriptors') + group.add_argument("--legacy-wallet", default=False, action="store_false", + help="Run test using legacy wallets", dest='descriptors') self.add_options(parser) self.options = parser.parse_args() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 09778e7aad..90cbb60045 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -104,6 +104,7 @@ BASE_SCRIPTS = [ 'feature_block.py', # NOTE: needs dash_hash to pass 'rpc_fundrawtransaction.py', 'rpc_fundrawtransaction.py --nohd', + 'rpc_fundrawtransaction.py --descriptors', 'wallet_multiwallet.py --usecli', 'p2p_quorum_data.py', # vv Tests less than 2m vv @@ -116,8 +117,9 @@ BASE_SCRIPTS = [ 'feature_bip68_sequence.py', 'mempool_updatefromblock.py', 'p2p_tx_download.py', - 'wallet_dump.py', + 'wallet_dump.py --legacy-wallet', 'wallet_listtransactions.py', + 'wallet_listtransactions.py --descriptors', 'feature_multikeysporks.py', 'feature_dip3_v19.py', 'feature_llmq_signing.py', # NOTE: needs dash_hash to pass @@ -136,13 +138,16 @@ BASE_SCRIPTS = [ # vv Tests less than 60s vv 'p2p_sendheaders.py', # NOTE: needs dash_hash to pass 'p2p_sendheaders_compressed.py', # NOTE: needs dash_hash to pass - 'wallet_importmulti.py', + 'wallet_importmulti.py --legacy-wallet', 'mempool_limit.py', 'rpc_txoutproof.py', 'wallet_listreceivedby.py', + 'wallet_listreceivedby.py --descriptors', 'wallet_abandonconflict.py', + 'wallet_abandonconflict.py --descriptors', 'feature_csv_activation.py', 'rpc_rawtransaction.py', + 'rpc_rawtransaction.py --descriptors', 'feature_reindex.py', 'feature_abortnode.py', # vv Tests less than 30s vv @@ -157,6 +162,7 @@ BASE_SCRIPTS = [ 'mempool_resurrect.py', 'wallet_txn_doublespend.py --mineblock', 'tool_wallet.py', + 'tool_wallet.py --descriptors', 'wallet_txn_clone.py', 'rpc_getchaintips.py', 'rpc_misc.py', @@ -170,9 +176,10 @@ BASE_SCRIPTS = [ 'wallet_multiwallet.py --descriptors', 'wallet_createwallet.py', 'wallet_createwallet.py --usecli', + 'wallet_createwallet.py --descriptors', 'wallet_reorgsrestore.py', - 'wallet_watchonly.py', - 'wallet_watchonly.py --usecli', + 'wallet_watchonly.py --legacy-wallet', + 'wallet_watchonly.py --usecli --legacy-wallet', 'interface_http.py', 'interface_rpc.py', 'rpc_psbt.py', @@ -181,8 +188,10 @@ BASE_SCRIPTS = [ 'rpc_whitelist.py', 'feature_proxy.py', 'rpc_signrawtransaction.py', + 'rpc_signrawtransaction.py --descriptors', 'p2p_addrv2_relay.py', 'wallet_groups.py', + 'wallet_groups.py --descriptors', 'p2p_disconnect_ban.py', 'feature_addressindex.py', 'feature_timestampindex.py', @@ -191,6 +200,7 @@ BASE_SCRIPTS = [ 'rpc_blockchain.py', 'rpc_deprecated.py', 'wallet_disable.py', + 'wallet_disable.py --descriptors', 'p2p_addr_relay.py', 'p2p_getaddr_caching.py', 'p2p_getdata.py', @@ -211,7 +221,9 @@ BASE_SCRIPTS = [ 'feature_assumevalid.py', 'example_test.py', 'wallet_txn_doublespend.py', + 'wallet_txn_doublespend.py --descriptors', 'feature_backwards_compatibility.py', + 'feature_backwards_compatibility.py --descriptors', 'wallet_txn_clone.py --mineblock', 'feature_notifications.py', 'rpc_getblockfilter.py', @@ -226,16 +238,19 @@ BASE_SCRIPTS = [ 'feature_versionbits_warning.py', 'rpc_preciousblock.py', 'wallet_importprunedfunds.py', + 'wallet_importprunedfunds.py --descriptors', 'p2p_leak_tx.py', 'p2p_eviction.py', 'rpc_signmessage.py', 'rpc_generateblock.py', 'wallet_balance.py', + 'wallet_balance.py --descriptors', 'feature_nulldummy.py', + 'feature_nulldummy.py --descriptors', 'mempool_accept.py', 'mempool_expiry.py', - 'wallet_import_rescan.py', - 'wallet_import_with_label.py', + 'wallet_import_rescan.py --legacy-wallet', + 'wallet_import_with_label.py --legacy-wallet', 'wallet_upgradewallet.py', 'wallet_importdescriptors.py --descriptors', 'wallet_mnemonicbits.py', @@ -245,6 +260,7 @@ BASE_SCRIPTS = [ 'mining_basic.py', 'rpc_named_arguments.py', 'wallet_listsinceblock.py', + 'wallet_listsinceblock.py --descriptors', 'wallet_listdescriptors.py --descriptors', 'p2p_leak.py', 'p2p_compactblocks.py', @@ -261,7 +277,9 @@ BASE_SCRIPTS = [ 'feature_governance.py', 'rpc_uptime.py', 'wallet_resendwallettransactions.py', + 'wallet_resendwallettransactions.py --descriptors', 'wallet_fallbackfee.py', + 'wallet_fallbackfee.py --descriptors', 'rpc_dumptxoutset.py', 'feature_minchainwork.py', 'rpc_estimatefee.py', @@ -274,12 +292,14 @@ BASE_SCRIPTS = [ 'rpc_verifychainlock.py', 'wallet_create_tx.py', 'wallet_send.py', + 'wallet_create_tx.py --descriptors', 'p2p_fingerprint.py', 'rpc_platform_filter.py', 'rpc_wipewallettxes.py', 'feature_dip0020_activation.py', 'feature_uacomment.py', 'wallet_coinbase_category.py', + 'wallet_coinbase_category.py --descriptors', 'feature_filelock.py', 'feature_loadblock.py', 'p2p_blockfilters.py', diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index f804a6c4b4..6fee23ea4d 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -70,9 +70,9 @@ class ToolWalletTest(BitcoinTestFramework): self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create') self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo') locked_dir = os.path.join(self.options.tmpdir, "node0", self.chain, "wallets") - error = "SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another dashd?" - if self.is_bdb_compiled(): - error = 'Error initializing wallet database environment "{}"!'.format(locked_dir) + error = 'Error initializing wallet database environment "{}"!'.format(locked_dir) + if self.options.descriptors: + error = "SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another dashd?" self.assert_raises_tool_error( error, '-wallet=' + self.default_wallet_name, @@ -97,19 +97,33 @@ class ToolWalletTest(BitcoinTestFramework): # shasum_before = self.wallet_shasum() timestamp_before = self.wallet_timestamp() self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before)) - out = textwrap.dedent('''\ - Wallet info - =========== - Name: \ + if self.options.descriptors: + out = textwrap.dedent('''\ + Wallet info + =========== + Name: default_wallet + Format: sqlite + Descriptors: yes + Encrypted: no + HD (hd seed available): yes + Keypool Size: 2 + Transactions: 0 + Address Book: 1 + ''') + else: + out = textwrap.dedent('''\ + Wallet info + =========== + Name: \ - Format: bdb - Descriptors: no - Encrypted: no - HD (hd seed available): yes - Keypool Size: 2 - Transactions: 0 - Address Book: 1 - ''') + Format: bdb + Descriptors: no + Encrypted: no + HD (hd seed available): yes + Keypool Size: 2 + Transactions: 0 + Address Book: 1 + ''') self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info') timestamp_after = self.wallet_timestamp() @@ -141,19 +155,33 @@ class ToolWalletTest(BitcoinTestFramework): shasum_before = self.wallet_shasum() timestamp_before = self.wallet_timestamp() self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before)) - out = textwrap.dedent('''\ - Wallet info - =========== - Name: \ + if self.options.descriptors: + out = textwrap.dedent('''\ + Wallet info + =========== + Name: default_wallet + Format: sqlite + Descriptors: yes + Encrypted: no + HD (hd seed available): yes + Keypool Size: 2 + Transactions: 1 + Address Book: 1 + ''') + else: + out = textwrap.dedent('''\ + Wallet info + =========== + Name: \ - Format: bdb - Descriptors: no - Encrypted: no - HD (hd seed available): yes - Keypool Size: 2 - Transactions: 1 - Address Book: 1 - ''') + Format: bdb + Descriptors: no + Encrypted: no + HD (hd seed available): yes + Keypool Size: 2 + Transactions: 1 + Address Book: 1 + ''') self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info') shasum_after = self.wallet_shasum() timestamp_after = self.wallet_timestamp() @@ -264,9 +292,11 @@ class ToolWalletTest(BitcoinTestFramework): # Warning: The following tests are order-dependent. self.test_tool_wallet_info() self.test_tool_wallet_info_after_transaction() - self.test_tool_wallet_create_on_existing_wallet() - self.test_getwalletinfo_on_different_wallet() - if self.is_bdb_compiled(): + if not self.options.descriptors: + # TODO: Wallet tool needs more create options at which point these can be enabled. + self.test_tool_wallet_create_on_existing_wallet() + self.test_getwalletinfo_on_different_wallet() + # Salvage is a legacy wallet only thing self.test_salvage() self.test_wipe() diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index ad5f3d11e9..e1976568fb 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -58,14 +58,16 @@ class WalletTest(BitcoinTestFramework): self.skip_if_no_wallet() def run_test(self): - self.nodes[0].importaddress(ADDRESS_WATCHONLY) - # Check that nodes don't own any UTXOs - assert_equal(len(self.nodes[0].listunspent()), 0) - assert_equal(len(self.nodes[1].listunspent()), 0) + if not self.options.descriptors: + # Tests legacy watchonly behavior which is not present (and does not need to be tested) in descriptor wallets + self.nodes[0].importaddress(ADDRESS_WATCHONLY) + # Check that nodes don't own any UTXOs + assert_equal(len(self.nodes[0].listunspent()), 0) + assert_equal(len(self.nodes[1].listunspent()), 0) - self.log.info("Check that only node 0 is watching an address") - assert 'watchonly' in self.nodes[0].getbalances() - assert 'watchonly' not in self.nodes[1].getbalances() + self.log.info("Check that only node 0 is watching an address") + assert 'watchonly' in self.nodes[0].getbalances() + assert 'watchonly' not in self.nodes[1].getbalances() self.log.info("Mining blocks ...") self.nodes[0].generate(1) @@ -74,25 +76,30 @@ class WalletTest(BitcoinTestFramework): self.nodes[1].generatetoaddress(COINBASE_MATURITY + 1, ADDRESS_WATCHONLY) self.sync_all() - assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 500) - assert_equal(self.nodes[0].getwalletinfo()['balance'], 500) - assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 500) + if not self.options.descriptors: + # Tests legacy watchonly behavior which is not present (and does not need to be tested) in descriptor wallets + assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 500) + assert_equal(self.nodes[0].getwalletinfo()['balance'], 500) + assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 500) - assert_equal(self.nodes[0].getbalances()['watchonly']['immature'], 50000) - assert 'watchonly' not in self.nodes[1].getbalances() + assert_equal(self.nodes[0].getbalances()['watchonly']['immature'], 50000) + assert 'watchonly' not in self.nodes[1].getbalances() - assert_equal(self.nodes[0].getbalance(), 500) - assert_equal(self.nodes[1].getbalance(), 500) + assert_equal(self.nodes[0].getbalance(), 500) + assert_equal(self.nodes[1].getbalance(), 500) self.log.info("Test getbalance with different arguments") assert_equal(self.nodes[0].getbalance("*"), 500) assert_equal(self.nodes[0].getbalance("*", 1), 500) - assert_equal(self.nodes[0].getbalance("*", 1, True), 500) - assert_equal(self.nodes[0].getbalance("*", 1, True, False), 500) assert_equal(self.nodes[0].getbalance(minconf=1, addlocked=True), 500) assert_equal(self.nodes[0].getbalance(minconf=1, avoid_reuse=False), 500) assert_equal(self.nodes[0].getbalance(minconf=1), 500) - assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 1000) + if not self.options.descriptors: + assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 1000) + assert_equal(self.nodes[0].getbalance("*", 1, True, True), 1000) + else: + assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 500) + assert_equal(self.nodes[0].getbalance("*", 1, True), 500) assert_equal(self.nodes[1].getbalance(minconf=0, include_watchonly=True), 500) # Send 490 BTC from 0 to 1 and 960 BTC from 1 to 0. @@ -162,6 +169,8 @@ class WalletTest(BitcoinTestFramework): 'immature': Decimal('0E-8'), 'trusted': Decimal('0E-8'), # node 1's send had an unsafe input 'untrusted_pending': Decimal('30.0') - fee_node_1}} # Doesn't include output of node 0's send since it was spent + if self.options.descriptors: + del expected_balances_0["watchonly"] assert_equal(self.nodes[0].getbalances(), expected_balances_0) assert_equal(self.nodes[1].getbalances(), expected_balances_1) # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py index 61af66ee7f..d310b2d8a7 100755 --- a/test/functional/wallet_createwallet.py +++ b/test/functional/wallet_createwallet.py @@ -5,11 +5,15 @@ """Test createwallet arguments. """ +from test_framework.address import key_to_p2pkh +from test_framework.descriptors import descsum_create +from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) +from test_framework.wallet_util import bytes_to_wif, generate_wif_key class CreateWalletTest(BitcoinTestFramework): def set_test_params(self): @@ -34,12 +38,16 @@ class CreateWalletTest(BitcoinTestFramework): w1.importpubkey(w0.getaddressinfo(address1)['pubkey']) self.log.info('Test that private keys cannot be imported') - addr = w0.getnewaddress() - privkey = w0.dumpprivkey(addr) + eckey = ECKey() + eckey.generate() + privkey = bytes_to_wif(eckey.get_bytes()) assert_raises_rpc_error(-4, 'Cannot import private keys to a wallet with private keys disabled', w1.importprivkey, privkey) - result = w1.importmulti([{'scriptPubKey': {'address': addr}, 'timestamp': 'now', 'keys': [privkey]}]) - assert(not result[0]['success']) - assert('warning' not in result[0]) + if self.options.descriptors: + result = w1.importdescriptors([{'desc': descsum_create('pkh(' + privkey + ')'), 'timestamp': 'now'}]) + else: + result = w1.importmulti([{'scriptPubKey': {'address': key_to_p2pkh(eckey.get_pubkey().get_bytes())}, 'timestamp': 'now', 'keys': [privkey]}]) + assert not result[0]['success'] + assert 'warning' not in result[0] assert_equal(result[0]['error']['code'], -4) assert_equal(result[0]['error']['message'], 'Cannot import private keys to a wallet with private keys disabled') @@ -57,12 +65,25 @@ class CreateWalletTest(BitcoinTestFramework): assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getrawchangeaddress) # Import private key - w3.importprivkey(w0.dumpprivkey(address1)) + w3.importprivkey(generate_wif_key()) # Imported private keys are currently ignored by the keypool assert_equal(w3.getwalletinfo()['keypoolsize'], 0) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress) # Set the seed - w3.upgradetohd() + if self.options.descriptors: + w3.importdescriptors([{ + 'desc': descsum_create('pkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/0h/*)'), + 'timestamp': 'now', + 'active': True + }, + { + 'desc': descsum_create('pkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/1h/*)'), + 'timestamp': 'now', + 'active': True, + 'internal': True + }]) + else: + w3.upgradetohd() assert_equal(w3.getwalletinfo()['keypoolsize'], 1) w3.getnewaddress() w3.getrawchangeaddress() @@ -79,7 +100,20 @@ class CreateWalletTest(BitcoinTestFramework): assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress) # Now set a seed and it should work. Wallet should also be encrypted w4.walletpassphrase('pass', 60) - w4.upgradetohd(walletpassphrase='pass') + if self.options.descriptors: + w4.importdescriptors([{ + 'desc': descsum_create('pkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/0h/*)'), + 'timestamp': 'now', + 'active': True + }, + { + 'desc': descsum_create('pkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/1h/*)'), + 'timestamp': 'now', + 'active': True, + 'internal': True + }]) + else: + w4.upgradetohd(walletpassphrase='pass') w4.getnewaddress() w4.getrawchangeaddress() @@ -110,13 +144,15 @@ class CreateWalletTest(BitcoinTestFramework): w6.walletpassphrase('thisisapassphrase', 60) w6.signmessage(w6.getnewaddress(), "test") w6.keypoolrefill(1) - # There should only be 1 key + # There should only be 1 key for legacy, 1 for descriptors (dash has only one type of addresses) walletinfo = w6.getwalletinfo() - assert_equal(walletinfo['keypoolsize'], 1) - assert_equal(walletinfo['keypoolsize_hd_internal'], 1) + keys = 1 if self.options.descriptors else 1 + assert_equal(walletinfo['keypoolsize'], keys) + # hd_internals are not refilled by default for descriptor wallets atm + assert_equal(walletinfo['keypoolsize_hd_internal'], keys) # Allow empty passphrase, but there should be a warning resp = self.nodes[0].createwallet(wallet_name='w7', disable_private_keys=False, blank=False, passphrase='') - assert_equal(resp['warning'], 'Empty string given as passphrase, wallet will not be encrypted.') + assert 'Empty string given as passphrase, wallet will not be encrypted.' in resp['warning'] w7 = node.get_wallet_rpc('w7') assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 60) diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index f3cc96db82..35ab4e85ab 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -35,6 +35,7 @@ class ImportDescriptorsTest(BitcoinTestFramework): ["-keypool=5"] ] self.setup_clean_chain = True + self.wallet_names = [] def skip_test_if_missing_module(self): self.skip_if_no_wallet() @@ -60,7 +61,7 @@ class ImportDescriptorsTest(BitcoinTestFramework): def run_test(self): self.log.info('Setting up wallets') - self.nodes[0].createwallet(wallet_name='w0', disable_private_keys=False) + self.nodes[0].createwallet(wallet_name='w0', disable_private_keys=False, descriptors=True) w0 = self.nodes[0].get_wallet_rpc('w0') self.nodes[1].createwallet(wallet_name='w1', disable_private_keys=True, blank=True, descriptors=True) diff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py index 50da2d13c6..08f52cecba 100755 --- a/test/functional/wallet_importprunedfunds.py +++ b/test/functional/wallet_importprunedfunds.py @@ -5,12 +5,15 @@ """Test the importprunedfunds and removeprunedfunds RPCs.""" from decimal import Decimal +from test_framework.address import key_to_p2pkh from test_framework.blocktools import COINBASE_MATURITY +from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) +from test_framework.wallet_util import bytes_to_wif class ImportPrunedFundsTest(BitcoinTestFramework): def set_test_params(self): @@ -31,8 +34,11 @@ class ImportPrunedFundsTest(BitcoinTestFramework): # pubkey address2 = self.nodes[0].getnewaddress() # privkey - address3 = self.nodes[0].getnewaddress() - address3_privkey = self.nodes[0].dumpprivkey(address3) # Using privkey + eckey = ECKey() + eckey.generate() + address3_privkey = bytes_to_wif(eckey.get_bytes()) + address3 = key_to_p2pkh(eckey.get_pubkey().get_bytes()) + self.nodes[0].importprivkey(address3_privkey) # Check only one address address_info = self.nodes[0].getaddressinfo(address1) @@ -81,37 +87,44 @@ class ImportPrunedFundsTest(BitcoinTestFramework): assert_equal(balance1, Decimal(0)) # Import with affiliated address with no rescan - self.nodes[1].importaddress(address=address2, rescan=False) - self.nodes[1].importprunedfunds(rawtransaction=rawtxn2, txoutproof=proof2) - assert [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid2] + self.nodes[1].createwallet('wwatch', disable_private_keys=True) + wwatch = self.nodes[1].get_wallet_rpc('wwatch') + wwatch.importaddress(address=address2, rescan=False) + wwatch.importprunedfunds(rawtransaction=rawtxn2, txoutproof=proof2) + assert [tx for tx in wwatch.listtransactions(include_watchonly=True) if tx['txid'] == txnid2] # Import with private key with no rescan - self.nodes[1].importprivkey(privkey=address3_privkey, rescan=False) - self.nodes[1].importprunedfunds(rawtxn3, proof3) - assert [tx for tx in self.nodes[1].listtransactions() if tx['txid'] == txnid3] - balance3 = self.nodes[1].getbalance() + w1 = self.nodes[1].get_wallet_rpc(self.default_wallet_name) + w1.importprivkey(privkey=address3_privkey, rescan=False) + w1.importprunedfunds(rawtxn3, proof3) + assert [tx for tx in w1.listtransactions() if tx['txid'] == txnid3] + balance3 = w1.getbalance() assert_equal(balance3, Decimal('0.025')) # Addresses Test - after import - address_info = self.nodes[1].getaddressinfo(address1) + address_info = w1.getaddressinfo(address1) assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], False) - address_info = self.nodes[1].getaddressinfo(address2) - assert_equal(address_info['iswatchonly'], True) - assert_equal(address_info['ismine'], False) - address_info = self.nodes[1].getaddressinfo(address3) + address_info = wwatch.getaddressinfo(address2) + if self.options.descriptors: + assert_equal(address_info['iswatchonly'], False) + assert_equal(address_info['ismine'], True) + else: + assert_equal(address_info['iswatchonly'], True) + assert_equal(address_info['ismine'], False) + address_info = w1.getaddressinfo(address3) assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], True) # Remove transactions - assert_raises_rpc_error(-8, "Transaction does not exist in wallet.", self.nodes[1].removeprunedfunds, txnid1) - assert not [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid1] + assert_raises_rpc_error(-8, "Transaction does not exist in wallet.", w1.removeprunedfunds, txnid1) + assert not [tx for tx in w1.listtransactions(include_watchonly=True) if tx['txid'] == txnid1] - self.nodes[1].removeprunedfunds(txnid2) - assert not [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid2] + wwatch.removeprunedfunds(txnid2) + assert not [tx for tx in wwatch.listtransactions(include_watchonly=True) if tx['txid'] == txnid2] - self.nodes[1].removeprunedfunds(txnid3) - assert not [tx for tx in self.nodes[1].listtransactions(include_watchonly=True) if tx['txid'] == txnid3] + w1.removeprunedfunds(txnid3) + assert not [tx for tx in w1.listtransactions(include_watchonly=True) if tx['txid'] == txnid3] if __name__ == '__main__': ImportPrunedFundsTest().main() diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py index 7891c86e6e..5275abe80b 100755 --- a/test/functional/wallet_labels.py +++ b/test/functional/wallet_labels.py @@ -138,7 +138,7 @@ class WalletLabelsTest(BitcoinTestFramework): change_label(node, labels[2].addresses[0], labels[2], labels[2]) self.log.info('Check watchonly labels') - node.createwallet(wallet_name='watch_only', disable_private_keys=True, descriptors=False) + node.createwallet(wallet_name='watch_only', disable_private_keys=True) wallet_watch_only = node.get_wallet_rpc('watch_only') VALID = { '✔️_a1': 'yMNJePdcKvXtWWQnFYHNeJ5u8TF2v1dfK4', @@ -159,7 +159,7 @@ class WalletLabelsTest(BitcoinTestFramework): ad = INVALID[l] assert_raises_rpc_error( -5, - "Invalid Dash address or script", + "Address is not valid" if self.options.descriptors else "Invalid Dash address or script", lambda: wallet_watch_only.importaddress(label=l, rescan=False, address=ad), ) diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py index 8fceee3366..eb68ab5815 100755 --- a/test/functional/wallet_listsinceblock.py +++ b/test/functional/wallet_listsinceblock.py @@ -4,7 +4,9 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the listsinceblock RPC.""" +from test_framework.address import key_to_p2pkh from test_framework.blocktools import COINBASE_MATURITY +from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import BIP125_SEQUENCE_NUMBER from test_framework.util import ( @@ -12,6 +14,7 @@ from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) +from test_framework.wallet_util import bytes_to_wif from decimal import Decimal @@ -182,15 +185,21 @@ class ListSinceBlockTest(BitcoinTestFramework): self.sync_all() + # share utxo between nodes[1] and nodes[2] + eckey = ECKey() + eckey.generate() + privkey = bytes_to_wif(eckey.get_bytes()) + address = key_to_p2pkh(eckey.get_pubkey().get_bytes()) + self.nodes[2].sendtoaddress(address, 10) + self.nodes[2].generate(6) + self.nodes[2].importprivkey(privkey) + utxos = self.nodes[2].listunspent() + utxo = [u for u in utxos if u["address"] == address][0] + self.nodes[1].importprivkey(privkey) + # Split network into two self.split_network() - # share utxo between nodes[1] and nodes[2] - utxos = self.nodes[2].listunspent() - utxo = utxos[0] - privkey = self.nodes[2].dumpprivkey(utxo['address']) - self.nodes[1].importprivkey(privkey) - # send from nodes[1] using utxo to nodes[0] change = '%.8f' % (float(utxo['amount']) - 1.0003) recipient_dict = { diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index ccdf18606f..2e25d61df9 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -77,18 +77,20 @@ class ListTransactionsTest(BitcoinTestFramework): {"category": "receive", "amount": Decimal("0.44")}, {"txid": txid} ) - pubkey = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey'] - multisig = self.nodes[1].createmultisig(1, [pubkey]) - self.nodes[0].importaddress(multisig["redeemScript"], "watchonly", False, True) - txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1) - self.nodes[1].generate(1) - self.sync_all() - assert_equal(len(self.nodes[0].listtransactions(label="watchonly", include_watchonly=True)), 1) - assert_equal(len(self.nodes[0].listtransactions(dummy="watchonly", include_watchonly=True)), 1) - assert len(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=False)) == 0 - assert_array_result(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=True), - {"category": "receive", "amount": Decimal("0.1")}, - {"txid": txid, "label": "watchonly"}) + if not self.options.descriptors: + # include_watchonly is a legacy wallet feature, so don't test it for descriptor wallets + pubkey = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey'] + multisig = self.nodes[1].createmultisig(1, [pubkey]) + self.nodes[0].importaddress(multisig["redeemScript"], "watchonly", False, True) + txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1) + self.nodes[1].generate(1) + self.sync_all() + assert_equal(len(self.nodes[0].listtransactions(label="watchonly", include_watchonly=True)), 1) + assert_equal(len(self.nodes[0].listtransactions(dummy="watchonly", include_watchonly=True)), 1) + assert len(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=False)) == 0 + assert_array_result(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=True), + {"category": "receive", "amount": Decimal("0.1")}, + {"txid": txid, "label": "watchonly"}) if __name__ == '__main__': ListTransactionsTest().main()