aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rwxr-xr-xtest/functional/feature_backwards_compatibility.py183
-rwxr-xr-xtest/functional/feature_nulldummy.py16
-rwxr-xr-xtest/functional/feature_taproot.py14
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py144
-rwxr-xr-xtest/functional/rpc_net.py9
-rwxr-xr-xtest/functional/rpc_psbt.py62
-rwxr-xr-xtest/functional/rpc_rawtransaction.py247
-rwxr-xr-xtest/functional/rpc_signrawtransaction.py34
-rw-r--r--test/functional/test_framework/blocktools.py23
-rwxr-xr-xtest/functional/test_framework/script_util.py59
-rwxr-xr-xtest/functional/test_framework/test_framework.py9
-rwxr-xr-xtest/functional/test_runner.py38
-rwxr-xr-xtest/functional/tool_wallet.py90
-rwxr-xr-xtest/functional/wallet_address_types.py31
-rwxr-xr-xtest/functional/wallet_balance.py42
-rwxr-xr-xtest/functional/wallet_basic.py33
-rwxr-xr-xtest/functional/wallet_bumpfee.py134
-rwxr-xr-xtest/functional/wallet_createwallet.py55
-rwxr-xr-xtest/functional/wallet_importdescriptors.py3
-rwxr-xr-xtest/functional/wallet_importprunedfunds.py53
-rwxr-xr-xtest/functional/wallet_labels.py4
-rwxr-xr-xtest/functional/wallet_listsinceblock.py19
-rwxr-xr-xtest/functional/wallet_listtransactions.py26
-rwxr-xr-xtest/functional/wallet_multiwallet.py72
-rwxr-xr-xtest/functional/wallet_send.py70
-rwxr-xr-xtest/functional/wallet_upgradewallet.py4
26 files changed, 993 insertions, 481 deletions
diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py
index 21776d85c9..b161c71a85 100755
--- a/test/functional/feature_backwards_compatibility.py
+++ b/test/functional/feature_backwards_compatibility.py
@@ -27,6 +27,7 @@ from test_framework.descriptors import descsum_create
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']
@@ -127,7 +128,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
# w2: wallet with private keys disabled, created on master: update this
# test when default wallets private keys disabled can no longer be
# opened by older versions.
- node_master.rpc.createwallet(wallet_name="w2", disable_private_keys=True)
+ node_master.createwallet(wallet_name="w2", disable_private_keys=True)
wallet = node_master.get_wallet_rpc("w2")
info = wallet.getwalletinfo()
assert info['private_keys_enabled'] == False
@@ -149,7 +150,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
# w3: blank wallet, created on master: update this
# test when default blank wallets can no longer be opened by older versions.
- node_master.rpc.createwallet(wallet_name="w3", blank=True)
+ node_master.createwallet(wallet_name="w3", blank=True)
wallet = node_master.get_wallet_rpc("w3")
info = wallet.getwalletinfo()
assert info['private_keys_enabled']
@@ -215,67 +216,89 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
os.path.join(node_v19_wallets_dir, wallet)
)
- # 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), 5)
- assert_equal(txs[1]["txid"], tx1_id)
- assert_equal(txs[2]["walletconflicts"], [tx1_id])
- assert_equal(txs[1]["replaced_by_txid"], tx2_id)
- assert not(txs[1]["abandoned"])
- assert_equal(txs[1]["confirmations"], -1)
- assert_equal(txs[2]["blockindex"], 1)
- assert txs[3]["abandoned"]
- assert_equal(txs[4]["walletconflicts"], [tx3_id])
- assert_equal(txs[3]["replaced_by_txid"], tx4_id)
- assert not(hasattr(txs[3], "blockindex"))
-
- 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
-
- # 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), 5)
- assert_equal(txs[1]["txid"], tx1_id)
- assert_equal(txs[2]["walletconflicts"], [tx1_id])
- assert_equal(txs[1]["replaced_by_txid"], tx2_id)
- assert not(txs[1]["abandoned"])
- assert_equal(txs[1]["confirmations"], -1)
- assert_equal(txs[2]["blockindex"], 1)
- assert txs[3]["abandoned"]
- assert_equal(txs[4]["walletconflicts"], [tx3_id])
- assert_equal(txs[3]["replaced_by_txid"], tx4_id)
- assert not(hasattr(txs[3], "blockindex"))
-
- 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
+ if not self.options.descriptors:
+ # Descriptor wallets break compatibility, only run this test for legacy wallet
+ # 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), 5)
+ assert_equal(txs[1]["txid"], tx1_id)
+ assert_equal(txs[2]["walletconflicts"], [tx1_id])
+ assert_equal(txs[1]["replaced_by_txid"], tx2_id)
+ assert not(txs[1]["abandoned"])
+ assert_equal(txs[1]["confirmations"], -1)
+ assert_equal(txs[2]["blockindex"], 1)
+ assert txs[3]["abandoned"]
+ assert_equal(txs[4]["walletconflicts"], [tx3_id])
+ assert_equal(txs[3]["replaced_by_txid"], tx4_id)
+ assert not(hasattr(txs[3], "blockindex"))
+
+ 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
+
+ # 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), 5)
+ assert_equal(txs[1]["txid"], tx1_id)
+ assert_equal(txs[2]["walletconflicts"], [tx1_id])
+ assert_equal(txs[1]["replaced_by_txid"], tx2_id)
+ assert not(txs[1]["abandoned"])
+ assert_equal(txs[1]["confirmations"], -1)
+ assert_equal(txs[2]["blockindex"], 1)
+ assert txs[3]["abandoned"]
+ assert_equal(txs[4]["walletconflicts"], [tx3_id])
+ assert_equal(txs[3]["replaced_by_txid"], tx4_id)
+ assert not(hasattr(txs[3], "blockindex"))
+
+ 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_v17.loadwallet("w1")
+ wallet = node_v17.get_wallet_rpc("w1")
+ info = wallet.getwalletinfo()
+ assert info['private_keys_enabled']
+ assert info['keypoolsize'] > 0
+
+ node_v17.loadwallet("w2")
+ wallet = node_v17.get_wallet_rpc("w2")
+ info = wallet.getwalletinfo()
+ 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 file verification failed: wallet.dat corrupt, salvage failed", node_v19.loadwallet, "w1")
+ assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v19.loadwallet, "w2")
+ assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v19.loadwallet, "w3")
+ assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v18.loadwallet, "w1")
+ assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v18.loadwallet, "w2")
+ assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v18.loadwallet, "w3")
# Open the wallets in v0.17
node_v17.loadwallet("w1_v18")
@@ -284,24 +307,12 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
assert info['private_keys_enabled']
assert info['keypoolsize'] > 0
- node_v17.loadwallet("w1")
- wallet = node_v17.get_wallet_rpc("w1")
- info = wallet.getwalletinfo()
- assert info['private_keys_enabled']
- assert info['keypoolsize'] > 0
-
node_v17.loadwallet("w2_v18")
wallet = node_v17.get_wallet_rpc("w2_v18")
info = wallet.getwalletinfo()
assert info['private_keys_enabled'] == False
assert info['keypoolsize'] == 0
- node_v17.loadwallet("w2")
- wallet = node_v17.get_wallet_rpc("w2")
- info = wallet.getwalletinfo()
- assert info['private_keys_enabled'] == False
- assert info['keypoolsize'] == 0
-
# RPC loadwallet failure causes bitcoind to exit, in addition to the RPC
# call failure, so the following test won't work:
# assert_raises_rpc_error(-4, "Wallet loading failed.", node_v17.loadwallet, 'w3_v18')
@@ -309,14 +320,22 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
# Instead, we stop node and try to launch it with the wallet:
self.stop_node(4)
node_v17.assert_start_raises_init_error(["-wallet=w3_v18"], "Error: Error loading w3_v18: Wallet requires newer version of Bitcoin Core")
- node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: Error loading w3: Wallet requires newer version of Bitcoin Core")
+ if self.options.descriptors:
+ # Descriptor wallets appear to be corrupted wallets to old software
+ node_v17.assert_start_raises_init_error(["-wallet=w1"], "Error: wallet.dat corrupt, salvage failed")
+ node_v17.assert_start_raises_init_error(["-wallet=w2"], "Error: wallet.dat corrupt, salvage failed")
+ node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: wallet.dat corrupt, salvage failed")
+ else:
+ node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: Error loading w3: Wallet requires newer version of Bitcoin Core")
self.start_node(4)
- # Open most recent wallet in v0.16 (no loadwallet RPC)
- self.restart_node(5, extra_args=["-wallet=w2"])
- wallet = node_v16.get_wallet_rpc("w2")
- info = wallet.getwalletinfo()
- assert info['keypoolsize'] == 1
+ if not self.options.descriptors:
+ # Descriptor wallets break compatibility, only run this test for legacy wallets
+ # Open most recent wallet in v0.16 (no loadwallet RPC)
+ self.restart_node(5, extra_args=["-wallet=w2"])
+ wallet = node_v16.get_wallet_rpc("w2")
+ info = wallet.getwalletinfo()
+ assert info['keypoolsize'] == 1
# Create upgrade wallet in v0.16
self.restart_node(-1, extra_args=["-wallet=u1_v16"])
diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py
index d1196a4bbd..b0eac7056b 100755
--- a/test/functional/feature_nulldummy.py
+++ b/test/functional/feature_nulldummy.py
@@ -51,10 +51,18 @@ 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.wit_address = self.nodes[0].getnewaddress(address_type='p2sh-segwit')
- self.wit_ms_address = self.nodes[0].addmultisigaddress(1, [self.address], '', 'p2sh-segwit')['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']
+ self.wit_address = w0.getnewaddress(address_type='p2sh-segwit')
+ self.wit_ms_address = wmulti.addmultisigaddress(1, [self.pubkey], '', 'p2sh-segwit')['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)
+ wmulti.importaddress(self.wit_ms_address)
self.coinbase_blocks = self.nodes[0].generate(2) # Block 2
coinbase_txid = []
diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py
index 7b534c1c2f..3e47e24a3b 100755
--- a/test/functional/feature_taproot.py
+++ b/test/functional/feature_taproot.py
@@ -1129,13 +1129,13 @@ def spenders_taproot_inactive():
]
tap = taproot_construct(pub, scripts)
- # Test that keypath spending is valid & standard if compliant, but valid and nonstandard otherwise.
- add_spender(spenders, "inactive/keypath_valid", key=sec, tap=tap)
+ # Test that keypath spending is valid & non-standard, regardless of validity.
+ add_spender(spenders, "inactive/keypath_valid", key=sec, tap=tap, standard=False)
add_spender(spenders, "inactive/keypath_invalidsig", key=sec, tap=tap, standard=False, sighash=bitflipper(default_sighash))
add_spender(spenders, "inactive/keypath_empty", key=sec, tap=tap, standard=False, witness=[])
- # Same for scriptpath spending (but using future features like annex, leaf versions, or OP_SUCCESS is nonstandard).
- add_spender(spenders, "inactive/scriptpath_valid", key=sec, tap=tap, leaf="pk", inputs=[getter("sign")])
+ # Same for scriptpath spending (and features like annex, leaf versions, or OP_SUCCESS don't change this)
+ add_spender(spenders, "inactive/scriptpath_valid", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")])
add_spender(spenders, "inactive/scriptpath_invalidsig", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")], sighash=bitflipper(default_sighash))
add_spender(spenders, "inactive/scriptpath_invalidcb", key=sec, tap=tap, leaf="pk", standard=False, inputs=[getter("sign")], controlblock=bitflipper(default_controlblock))
add_spender(spenders, "inactive/scriptpath_valid_unkleaf", key=sec, tap=tap, leaf="future_leaf", standard=False, inputs=[getter("sign")])
@@ -1451,7 +1451,11 @@ class TaprootTest(BitcoinTestFramework):
# Pre-taproot activation tests.
self.log.info("Pre-activation tests...")
- self.test_spenders(self.nodes[0], spenders_taproot_inactive(), input_counts=[1, 2, 2, 2, 2, 3])
+ # Run each test twice; once in isolation, and once combined with others. Testing in isolation
+ # means that the standardness is verified in every test (as combined transactions are only standard
+ # when all their inputs are standard).
+ self.test_spenders(self.nodes[0], spenders_taproot_inactive(), input_counts=[1])
+ self.test_spenders(self.nodes[0], spenders_taproot_inactive(), input_counts=[2, 3])
if __name__ == '__main__':
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 167c4671ef..503993162b 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -5,8 +5,10 @@
"""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_approx,
assert_equal,
assert_fee_amount,
assert_greater_than,
@@ -88,6 +90,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.test_op_return()
self.test_watchonly()
self.test_all_watched_funds()
+ self.test_feerate_with_conf_target_and_estimate_mode()
self.test_option_feerate()
self.test_address_reuse()
self.test_option_subtract_fee_from_outputs()
@@ -100,17 +103,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(200)
- 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(), 1.5)
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0)
@@ -119,6 +124,8 @@ class RawTransactionsTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all()
+ wwatch.unloadwallet()
+
def test_simple(self):
self.log.info("Test fundrawtxn")
inputs = [ ]
@@ -406,7 +413,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:1.1}
@@ -438,7 +445,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'],
@@ -464,7 +471,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()
@@ -473,13 +480,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 1.2 BTC to msig addr.
self.nodes[0].sendtoaddress(mSigObj, 1.2)
@@ -489,22 +501,39 @@ class RawTransactionsTest(BitcoinTestFramework):
oldBalance = self.nodes[1].getbalance()
inputs = []
outputs = {self.nodes[1].getnewaddress():1.1}
- 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('1.10000000'), 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('wpkh(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/0h/*h)'),
+ 'timestamp': 'now',
+ 'active': True
+ },
+ {
+ 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/1h/*h)'),
+ 'timestamp': 'now',
+ 'active': True,
+ 'internal': True
+ }])
+ self.nodes[1].walletlock()
+
# Drain the keypool.
self.nodes[1].getnewaddress()
self.nodes[1].getrawchangeaddress()
@@ -621,7 +650,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("wpkh(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)
@@ -629,6 +676,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")
@@ -636,17 +685,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"]
@@ -654,6 +705,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")
@@ -671,6 +724,59 @@ class RawTransactionsTest(BitcoinTestFramework):
assert_fee_amount(result2['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
+ def test_feerate_with_conf_target_and_estimate_mode(self):
+ self.log.info("Test fundrawtxn passing an explicit fee rate using conf_target and estimate_mode")
+ node = self.nodes[3]
+ # Make sure there is exactly one input so coin selection can't skew the result.
+ assert_equal(len(node.listunspent(1)), 1)
+ inputs = []
+ outputs = {node.getnewaddress() : 1}
+ rawtx = node.createrawtransaction(inputs, outputs)
+
+ for unit, fee_rate in {"btc/kb": 0.1, "sat/b": 10000}.items():
+ self.log.info("Test fundrawtxn with conf_target {} estimate_mode {} produces expected fee".format(fee_rate, unit))
+ # With no arguments passed, expect fee of 141 sats/b.
+ assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000141, vspan=0.00000001)
+ # Expect fee to be 10,000x higher when explicit fee 10,000x greater is specified.
+ result = node.fundrawtransaction(rawtx, {"conf_target": fee_rate, "estimate_mode": unit})
+ assert_approx(result["fee"], vexp=0.0141, vspan=0.0001)
+
+ for field, fee_rate in {"conf_target": 0.1, "estimate_mode": "sat/b"}.items():
+ self.log.info("Test fundrawtxn raises RPC error if both feeRate and {} are passed".format(field))
+ assert_raises_rpc_error(
+ -8, "Cannot specify both {} and feeRate".format(field),
+ lambda: node.fundrawtransaction(rawtx, {"feeRate": 0.1, field: fee_rate}))
+
+ self.log.info("Test fundrawtxn with invalid estimate_mode settings")
+ for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
+ assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
+ lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": v, "conf_target": 0.1}))
+ for mode in ["foo", Decimal("3.141592")]:
+ assert_raises_rpc_error(-8, "Invalid estimate_mode parameter",
+ lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": 0.1}))
+
+ self.log.info("Test fundrawtxn with invalid conf_target settings")
+ for mode in ["unset", "economical", "conservative", "btc/kb", "sat/b"]:
+ self.log.debug("{}".format(mode))
+ for k, v in {"string": "", "object": {"foo": "bar"}}.items():
+ assert_raises_rpc_error(-3, "Expected type number for conf_target, got {}".format(k),
+ lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": v}))
+ if mode in ["btc/kb", "sat/b"]:
+ assert_raises_rpc_error(-3, "Amount out of range",
+ lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": -1}))
+ assert_raises_rpc_error(-4, "Fee rate (0.00000000 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
+ lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": 0}))
+ else:
+ for n in [-1, 0, 1009]:
+ assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008",
+ lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": n}))
+
+ for unit, fee_rate in {"sat/B": 0.99999999, "BTC/kB": 0.00000999}.items():
+ self.log.info("- raises RPC error 'fee rate too low' if conf_target {} and estimate_mode {} are passed".format(fee_rate, unit))
+ assert_raises_rpc_error(-4, "Fee rate (0.00000999 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
+ lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": unit, "conf_target": fee_rate, "add_inputs": True}))
+
+
def test_address_reuse(self):
"""Test no address reuse occurs."""
self.log.info("Test fundrawtxn does not reuse addresses")
diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py
index 034827b2b8..2efd090733 100755
--- a/test/functional/rpc_net.py
+++ b/test/functional/rpc_net.py
@@ -21,7 +21,6 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_approx,
assert_equal,
- assert_greater_than_or_equal,
assert_greater_than,
assert_raises_rpc_error,
p2p_port,
@@ -114,10 +113,10 @@ class NetTest(BitcoinTestFramework):
self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytessent'] >= net_totals_before['totalbytessent'] + 32 * 2), timeout=1)
self.wait_until(lambda: (self.nodes[0].getnettotals()['totalbytesrecv'] >= net_totals_before['totalbytesrecv'] + 32 * 2), timeout=1)
- peer_info_after = self.nodes[0].getpeerinfo()
- for before, after in zip(peer_info_before, peer_info_after):
- assert_greater_than_or_equal(after['bytesrecv_per_msg'].get('pong', 0), before['bytesrecv_per_msg'].get('pong', 0) + 32)
- assert_greater_than_or_equal(after['bytessent_per_msg'].get('ping', 0), before['bytessent_per_msg'].get('ping', 0) + 32)
+ for peer_before in peer_info_before:
+ peer_after = lambda: next(p for p in self.nodes[0].getpeerinfo() if p['id'] == peer_before['id'])
+ self.wait_until(lambda: peer_after()['bytesrecv_per_msg'].get('pong', 0) >= peer_before['bytesrecv_per_msg'].get('pong', 0) + 32, timeout=1)
+ self.wait_until(lambda: peer_after()['bytessent_per_msg'].get('ping', 0) >= peer_before['bytessent_per_msg'].get('ping', 0) + 32, timeout=1)
def test_getnetworkinfo(self):
self.log.info("Test getnetworkinfo")
diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py
index 32dc2f8644..ca75bcb9bb 100755
--- a/test/functional/rpc_psbt.py
+++ b/test/functional/rpc_psbt.py
@@ -172,8 +172,11 @@ class PSBTTest(BitcoinTestFramework):
elif out['scriptPubKey']['addresses'][0] == p2pkh:
p2pkh_pos = out['n']
+ inputs = [{"txid": txid, "vout": p2wpkh_pos}, {"txid": txid, "vout": p2sh_p2wpkh_pos}, {"txid": txid, "vout": p2pkh_pos}]
+ outputs = [{self.nodes[1].getnewaddress(): 29.99}]
+
# spend single key from node 1
- created_psbt = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99})
+ created_psbt = self.nodes[1].walletcreatefundedpsbt(inputs, outputs)
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(created_psbt['psbt'])
# Make sure it has both types of UTXOs
decoded = self.nodes[1].decodepsbt(walletprocesspsbt_out['psbt'])
@@ -184,15 +187,62 @@ class PSBTTest(BitcoinTestFramework):
assert_equal(walletprocesspsbt_out['complete'], True)
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
- # feeRate of 0.1 BTC / KB produces a total fee slightly below -maxtxfee (~0.05280000):
- res = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 0.1, "add_inputs": True})
+ self.log.info("Test walletcreatefundedpsbt feeRate of 0.1 BTC/kB produces a total fee at or slightly below -maxtxfee (~0.05290000)")
+ res = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.1, "add_inputs": True})
assert_approx(res["fee"], 0.055, 0.005)
- # feeRate of 10 BTC / KB produces a total fee well above -maxtxfee
+ self.log.info("Test walletcreatefundedpsbt explicit fee rate with conf_target and estimate_mode")
+ for unit, fee_rate in {"btc/kb": 0.1, "sat/b": 10000}.items():
+ fee = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"conf_target": fee_rate, "estimate_mode": unit, "add_inputs": True})["fee"]
+ self.log.info("- conf_target {}, estimate_mode {} produces fee {} at or slightly below -maxtxfee (~0.05290000)".format(fee_rate, unit, fee))
+ assert_approx(fee, vexp=0.055, vspan=0.005)
+
+ for field, fee_rate in {"conf_target": 0.1, "estimate_mode": "sat/b"}.items():
+ self.log.info("- raises RPC error if both feeRate and {} are passed".format(field))
+ assert_raises_rpc_error(-8, "Cannot specify both {} and feeRate".format(field),
+ lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.1, field: fee_rate, "add_inputs": True}))
+
+ self.log.info("- raises RPC error with invalid estimate_mode settings")
+ for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
+ assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
+ lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": v, "conf_target": 0.1, "add_inputs": True}))
+ for mode in ["foo", Decimal("3.141592")]:
+ assert_raises_rpc_error(-8, "Invalid estimate_mode parameter",
+ lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": mode, "conf_target": 0.1, "add_inputs": True}))
+
+ self.log.info("- raises RPC error if estimate_mode is passed without a conf_target")
+ for unit in ["SAT/B", "BTC/KB"]:
+ assert_raises_rpc_error(-8, "Selected estimate_mode {} requires a fee rate to be specified in conf_target".format(unit),
+ lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": unit}))
+
+ self.log.info("- raises RPC error with invalid conf_target settings")
+ for mode in ["unset", "economical", "conservative", "btc/kb", "sat/b"]:
+ self.log.debug("{}".format(mode))
+ for k, v in {"string": "", "object": {"foo": "bar"}}.items():
+ assert_raises_rpc_error(-3, "Expected type number for conf_target, got {}".format(k),
+ lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": mode, "conf_target": v, "add_inputs": True}))
+ if mode in ["btc/kb", "sat/b"]:
+ assert_raises_rpc_error(-3, "Amount out of range",
+ lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": mode, "conf_target": -1, "add_inputs": True}))
+ assert_raises_rpc_error(-4, "Fee rate (0.00000000 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
+ lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": mode, "conf_target": 0, "add_inputs": True}))
+ else:
+ for n in [-1, 0, 1009]:
+ assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008",
+ lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": mode, "conf_target": n, "add_inputs": True}))
+
+ for unit, fee_rate in {"SAT/B": 0.99999999, "BTC/KB": 0.00000999}.items():
+ self.log.info("- raises RPC error 'fee rate too low' if conf_target {} and estimate_mode {} are passed".format(fee_rate, unit))
+ assert_raises_rpc_error(-4, "Fee rate (0.00000999 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
+ lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": unit, "conf_target": fee_rate, "add_inputs": True}))
+
+ self.log.info("Test walletcreatefundedpsbt feeRate of 10 BTC/kB produces total fee well above -maxtxfee and raises RPC error")
# previously this was silently capped at -maxtxfee
- assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 10, "add_inputs": True})
- assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():1}, 0, {"feeRate": 10, "add_inputs": False})
+ for bool_add, outputs_array in {True: outputs, False: [{self.nodes[1].getnewaddress(): 1}]}.items():
+ assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
+ self.nodes[1].walletcreatefundedpsbt, inputs, outputs_array, 0, {"feeRate": 10, "add_inputs": bool_add})
+ self.log.info("Test various PSBT operations")
# partially sign multisig things with node 1
psbtx = wmulti.walletcreatefundedpsbt(inputs=[{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], outputs={self.nodes[1].getnewaddress():29.99}, options={'changeAddress': self.nodes[1].getrawchangeaddress()})['psbt']
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx)
diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py
index 326495843f..554c30c0d2 100755
--- a/test/functional/rpc_rawtransaction.py
+++ b/test/functional/rpc_rawtransaction.py
@@ -20,6 +20,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,
)
@@ -242,121 +243,124 @@ 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()
-
- 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.
-
- mSigObj = self.nodes[2].addmultisigaddress(2, [addr1Obj['pubkey'], addr1])['address']
-
- #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()
- 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'], "amount" : vout['value']}]
- 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('50.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'], "amount" : vout['value']}]
- 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('50.00000000')+Decimal('2.19000000')) #block reward + tx
+ 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)
+
+ # 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']
+
+ #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()
+ 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'], "amount" : vout['value']}]
+ 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('50.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'], "amount" : vout['value']}]
+ 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('50.00000000')+Decimal('2.19000000')) #block reward + tx
# decoderawtransaction tests
# witness transaction
@@ -369,9 +373,20 @@ class RawTransactionsTest(BitcoinTestFramework):
decrawtx = self.nodes[0].decoderawtransaction(encrawtx, False) # decode as non-witness transaction
assert_equal(decrawtx['vout'][0]['value'], Decimal('1.00000000'))
+ # Basic signrawtransaction test
+ addr = self.nodes[1].getnewaddress()
+ txid = self.nodes[0].sendtoaddress(addr, 10)
+ self.nodes[0].generate(1)
+ 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()
+
# getrawtransaction tests
# 1. valid parameters - only supply txid
- txId = rawTx["txid"]
assert_equal(self.nodes[0].getrawtransaction(txId), rawTxSigned['hex'])
# 2. valid parameters - supply txid and 0 for non-verbose
diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/rpc_signrawtransaction.py
index 704b65c060..b962e1c3a5 100755
--- a/test/functional/rpc_signrawtransaction.py
+++ b/test/functional/rpc_signrawtransaction.py
@@ -5,10 +5,13 @@
"""Test transaction signing using the signrawtransaction* RPCs."""
from test_framework.address import check_script, script_to_p2sh
+from test_framework.key import ECKey
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
from test_framework.messages import sha256
from test_framework.script import CScript, OP_0, OP_CHECKSIG
+from test_framework.script_util import key_to_p2pkh_script, script_to_p2sh_p2wsh_script, script_to_p2wsh_script
+from test_framework.wallet_util import bytes_to_wif
from decimal import Decimal
@@ -151,21 +154,24 @@ class SignRawTransactionsTest(BitcoinTestFramework):
def witness_script_test(self):
self.log.info("Test signing transaction to P2SH-P2WSH addresses without wallet")
# Create a new P2SH-P2WSH 1-of-1 multisig address:
- embedded_address = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())
- embedded_privkey = self.nodes[1].dumpprivkey(embedded_address["address"])
- p2sh_p2wsh_address = self.nodes[1].addmultisigaddress(1, [embedded_address["pubkey"]], "", "p2sh-segwit")
+ eckey = ECKey()
+ eckey.generate()
+ embedded_privkey = bytes_to_wif(eckey.get_bytes())
+ embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
+ p2sh_p2wsh_address = self.nodes[1].createmultisig(1, [embedded_pubkey], "p2sh-segwit")
# send transaction to P2SH-P2WSH 1-of-1 multisig address
self.nodes[0].generate(101)
self.nodes[0].sendtoaddress(p2sh_p2wsh_address["address"], 49.999)
self.nodes[0].generate(1)
self.sync_all()
- # Find the UTXO for the transaction node[1] should have received, check witnessScript matches
- unspent_output = self.nodes[1].listunspent(0, 999999, [p2sh_p2wsh_address["address"]])[0]
- assert_equal(unspent_output["witnessScript"], p2sh_p2wsh_address["redeemScript"])
- p2sh_redeemScript = CScript([OP_0, sha256(hex_str_to_bytes(p2sh_p2wsh_address["redeemScript"]))])
- assert_equal(unspent_output["redeemScript"], p2sh_redeemScript.hex())
+ # Get the UTXO info from scantxoutset
+ unspent_output = self.nodes[1].scantxoutset('start', [p2sh_p2wsh_address['descriptor']])['unspents'][0]
+ spk = script_to_p2sh_p2wsh_script(p2sh_p2wsh_address['redeemScript']).hex()
+ unspent_output['witnessScript'] = p2sh_p2wsh_address['redeemScript']
+ unspent_output['redeemScript'] = script_to_p2wsh_script(unspent_output['witnessScript']).hex()
+ assert_equal(spk, unspent_output['scriptPubKey'])
# Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys
- spending_tx = self.nodes[0].createrawtransaction([unspent_output], {self.nodes[1].getnewaddress(): Decimal("49.998")})
+ spending_tx = self.nodes[0].createrawtransaction([unspent_output], {self.nodes[1].get_wallet_rpc(self.default_wallet_name).getnewaddress(): Decimal("49.998")})
spending_tx_signed = self.nodes[0].signrawtransactionwithkey(spending_tx, [embedded_privkey], [unspent_output])
# Check the signing completed successfully
assert 'complete' in spending_tx_signed
@@ -177,11 +183,13 @@ class SignRawTransactionsTest(BitcoinTestFramework):
def verify_txn_with_witness_script(self, tx_type):
self.log.info("Test with a {} script as the witnessScript".format(tx_type))
- embedded_addr_info = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress('', 'legacy'))
- embedded_privkey = self.nodes[1].dumpprivkey(embedded_addr_info['address'])
+ eckey = ECKey()
+ eckey.generate()
+ embedded_privkey = bytes_to_wif(eckey.get_bytes())
+ embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
witness_script = {
- 'P2PKH': embedded_addr_info['scriptPubKey'],
- 'P2PK': CScript([hex_str_to_bytes(embedded_addr_info['pubkey']), OP_CHECKSIG]).hex()
+ 'P2PKH': key_to_p2pkh_script(embedded_pubkey).hex(),
+ 'P2PK': CScript([hex_str_to_bytes(embedded_pubkey), OP_CHECKSIG]).hex()
}.get(tx_type, "Invalid tx_type")
redeem_script = CScript([OP_0, sha256(check_script(witness_script))]).hex()
addr = script_to_p2sh(redeem_script)
diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py
index 0859380d06..64bc7e0485 100644
--- a/test/functional/test_framework/blocktools.py
+++ b/test/functional/test_framework/blocktools.py
@@ -162,9 +162,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()
@@ -173,14 +172,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 80fbae70bf..318a438705 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, sha256, OP_0, 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
# non-witness size of at least 82 bytes (MIN_STANDARD_TX_NONWITNESS_SIZE in
@@ -24,3 +25,59 @@ from test_framework.script import CScript
# met.
DUMMY_P2WPKH_SCRIPT = CScript([b'a' * 21])
DUMMY_2_P2WPKH_SCRIPT = CScript([b'b' * 21])
+
+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 key_to_p2sh_p2wpkh_script(key, main = False):
+ key = check_key(key)
+ p2shscript = CScript([OP_0, hash160(key)])
+ return script_to_p2sh_script(p2shscript, main)
+
+def program_to_witness_script(version, program, main = False):
+ if isinstance(program, str):
+ program = hex_str_to_bytes(program)
+ assert 0 <= version <= 16
+ assert 2 <= len(program) <= 40
+ assert version > 0 or len(program) in [20, 32]
+ return CScript([version, program])
+
+def script_to_p2wsh_script(script, main = False):
+ script = check_script(script)
+ return program_to_witness_script(0, sha256(script), main)
+
+def key_to_p2wpkh_script(key, main = False):
+ key = check_key(key)
+ return program_to_witness_script(0, hash160(key), main)
+
+def script_to_p2sh_p2wsh_script(script, main = False):
+ script = check_script(script)
+ p2shscript = CScript([OP_0, sha256(script)])
+ return script_to_p2sh_script(p2shscript, 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 d3e0737f21..831599913d 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -183,9 +183,14 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
help="run nodes under the valgrind memory error detector: expect at least a ~10x slowdown, valgrind 3.14 or later required")
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('--timeout-factor', dest="timeout_factor", type=float, default=1.0, help='adjust test timeouts by a factor. Setting it to 0 disables all timeouts')
+
+ 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()
self.options.previous_releases_path = previous_releases_path
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index b25fb24064..5b3db282e1 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -95,8 +95,9 @@ BASE_SCRIPTS = [
'feature_maxuploadtarget.py',
'feature_block.py',
'rpc_fundrawtransaction.py',
+ 'rpc_fundrawtransaction.py --descriptors',
'p2p_compactblocks.py',
- 'feature_segwit.py',
+ 'feature_segwit.py --legacy-wallet',
# vv Tests less than 2m vv
'wallet_basic.py',
'wallet_basic.py --descriptors',
@@ -106,19 +107,24 @@ BASE_SCRIPTS = [
'p2p_timeouts.py',
'p2p_tx_download.py',
'mempool_updatefromblock.py',
- 'wallet_dump.py',
+ 'wallet_dump.py --legacy-wallet',
'wallet_listtransactions.py',
+ 'wallet_listtransactions.py --descriptors',
'feature_taproot.py',
# vv Tests less than 60s vv
'p2p_sendheaders.py',
- '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',
'wallet_address_types.py',
+ 'wallet_address_types.py --descriptors',
'feature_bip68_sequence.py',
'p2p_feefilter.py',
'feature_reindex.py',
@@ -132,6 +138,7 @@ BASE_SCRIPTS = [
'mempool_resurrect.py',
'wallet_txn_doublespend.py --mineblock',
'tool_wallet.py',
+ 'tool_wallet.py --descriptors',
'wallet_txn_clone.py',
'wallet_txn_clone.py --segwit',
'rpc_getchaintips.py',
@@ -147,8 +154,9 @@ BASE_SCRIPTS = [
'wallet_multiwallet.py --usecli',
'wallet_createwallet.py',
'wallet_createwallet.py --usecli',
- 'wallet_watchonly.py',
- 'wallet_watchonly.py --usecli',
+ 'wallet_createwallet.py --descriptors',
+ 'wallet_watchonly.py --legacy-wallet',
+ 'wallet_watchonly.py --usecli --legacy-wallet',
'wallet_reorgsrestore.py',
'interface_http.py',
'interface_rpc.py',
@@ -158,13 +166,16 @@ BASE_SCRIPTS = [
'rpc_whitelist.py',
'feature_proxy.py',
'rpc_signrawtransaction.py',
+ 'rpc_signrawtransaction.py --descriptors',
'wallet_groups.py',
'p2p_addrv2_relay.py',
+ 'wallet_groups.py --descriptors',
'p2p_disconnect_ban.py',
'rpc_decodescript.py',
'rpc_blockchain.py',
'rpc_deprecated.py',
'wallet_disable.py',
+ 'wallet_disable.py --descriptors',
'p2p_addr_relay.py',
'p2p_getaddr_caching.py',
'p2p_getdata.py',
@@ -184,7 +195,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',
@@ -197,17 +210,20 @@ 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',
'rpc_generate.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_importdescriptors.py --descriptors',
'wallet_upgradewallet.py',
'rpc_bind.py --ipv4',
@@ -216,9 +232,11 @@ BASE_SCRIPTS = [
'mining_basic.py',
'feature_signet.py',
'wallet_bumpfee.py',
- 'wallet_implicitsegwit.py',
+ 'wallet_bumpfee.py --descriptors',
+ 'wallet_implicitsegwit.py --legacy-wallet',
'rpc_named_arguments.py',
'wallet_listsinceblock.py',
+ 'wallet_listsinceblock.py --descriptors',
'p2p_leak.py',
'wallet_encryption.py',
'wallet_encryption.py --descriptors',
@@ -226,16 +244,20 @@ BASE_SCRIPTS = [
'feature_cltv.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',
'rpc_getblockstats.py',
'wallet_create_tx.py',
'wallet_send.py',
+ 'wallet_create_tx.py --descriptors',
'p2p_fingerprint.py',
'feature_uacomment.py',
'wallet_coinbase_category.py',
+ 'wallet_coinbase_category.py --descriptors',
'feature_filelock.py',
'feature_loadblock.py',
'p2p_dos_header_tree.py',
diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py
index c7c056a8dc..615b772dc8 100755
--- a/test/functional/tool_wallet.py
+++ b/test/functional/tool_wallet.py
@@ -71,8 +71,11 @@ 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", "regtest", "wallets")
+ 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 bitcoind?"
self.assert_raises_tool_error(
- 'Error initializing wallet database environment "{}"!'.format(locked_dir),
+ error,
'-wallet=' + self.default_wallet_name,
'info',
)
@@ -95,19 +98,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: 6
+ 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: 3
- ''')
+ Format: bdb
+ Descriptors: no
+ Encrypted: no
+ HD (hd seed available): yes
+ Keypool Size: 2
+ Transactions: 0
+ Address Book: 3
+ ''')
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')
timestamp_after = self.wallet_timestamp()
self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after))
@@ -138,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: 6
+ 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: 3
- ''')
+ Format: bdb
+ Descriptors: no
+ Encrypted: no
+ HD (hd seed available): yes
+ Keypool Size: 2
+ Transactions: 1
+ Address Book: 3
+ ''')
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')
shasum_after = self.wallet_shasum()
timestamp_after = self.wallet_timestamp()
@@ -230,9 +261,12 @@ 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()
- self.test_salvage()
+ 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()
if __name__ == '__main__':
ToolWalletTest().main()
diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py
index 6b3276e404..2db5eae33b 100755
--- a/test/functional/wallet_address_types.py
+++ b/test/functional/wallet_address_types.py
@@ -228,18 +228,25 @@ class AddressTypeTest(BitcoinTestFramework):
compressed_1 = "0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52"
compressed_2 = "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073"
- # addmultisigaddress with at least 1 uncompressed key should return a legacy address.
- for node in range(4):
- self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, uncompressed_2])['address'], True, 'legacy')
- self.test_address(node, self.nodes[node].addmultisigaddress(2, [compressed_1, uncompressed_2])['address'], True, 'legacy')
- self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, compressed_2])['address'], True, 'legacy')
- # addmultisigaddress with all compressed keys should return the appropriate address type (even when the keys are not ours).
- self.test_address(0, self.nodes[0].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'legacy')
- self.test_address(1, self.nodes[1].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'p2sh-segwit')
- self.test_address(2, self.nodes[2].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'p2sh-segwit')
- self.test_address(3, self.nodes[3].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'bech32')
-
- for explicit_type, multisig, from_node in itertools.product([False, True], [False, True], range(4)):
+ if not self.options.descriptors:
+ # Tests for addmultisigaddress's address type behavior is only for legacy wallets.
+ # Descriptor wallets do not have addmultsigaddress so these tests are not needed for those.
+ # addmultisigaddress with at least 1 uncompressed key should return a legacy address.
+ for node in range(4):
+ self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, uncompressed_2])['address'], True, 'legacy')
+ self.test_address(node, self.nodes[node].addmultisigaddress(2, [compressed_1, uncompressed_2])['address'], True, 'legacy')
+ self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, compressed_2])['address'], True, 'legacy')
+ # addmultisigaddress with all compressed keys should return the appropriate address type (even when the keys are not ours).
+ self.test_address(0, self.nodes[0].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'legacy')
+ self.test_address(1, self.nodes[1].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'p2sh-segwit')
+ self.test_address(2, self.nodes[2].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'p2sh-segwit')
+ self.test_address(3, self.nodes[3].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'bech32')
+
+ do_multisigs = [False]
+ if not self.options.descriptors:
+ do_multisigs.append(True)
+
+ for explicit_type, multisig, from_node in itertools.product([False, True], do_multisigs, range(4)):
address_type = None
if explicit_type and not multisig:
if from_node == 1:
diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py
index 589fab9992..0e786ca595 100755
--- a/test/functional/wallet_balance.py
+++ b/test/functional/wallet_balance.py
@@ -57,14 +57,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)
@@ -73,22 +75,28 @@ class WalletTest(BitcoinTestFramework):
self.nodes[1].generatetoaddress(101, ADDRESS_WATCHONLY)
self.sync_all()
- assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50)
- assert_equal(self.nodes[0].getwalletinfo()['balance'], 50)
- assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 50)
+ 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'], 50)
+ assert_equal(self.nodes[0].getwalletinfo()['balance'], 50)
+ assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 50)
- assert_equal(self.nodes[0].getbalances()['watchonly']['immature'], 5000)
- assert 'watchonly' not in self.nodes[1].getbalances()
+ assert_equal(self.nodes[0].getbalances()['watchonly']['immature'], 5000)
+ assert 'watchonly' not in self.nodes[1].getbalances()
- assert_equal(self.nodes[0].getbalance(), 50)
- assert_equal(self.nodes[1].getbalance(), 50)
+ assert_equal(self.nodes[0].getbalance(), 50)
+ assert_equal(self.nodes[1].getbalance(), 50)
self.log.info("Test getbalance with different arguments")
assert_equal(self.nodes[0].getbalance("*"), 50)
assert_equal(self.nodes[0].getbalance("*", 1), 50)
- assert_equal(self.nodes[0].getbalance("*", 1, True), 100)
assert_equal(self.nodes[0].getbalance(minconf=1), 50)
- assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 100)
+ if not self.options.descriptors:
+ assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 100)
+ assert_equal(self.nodes[0].getbalance("*", 1, True), 100)
+ else:
+ assert_equal(self.nodes[0].getbalance(minconf=0, include_watchonly=True), 50)
+ assert_equal(self.nodes[0].getbalance("*", 1, True), 50)
assert_equal(self.nodes[1].getbalance(minconf=0, include_watchonly=True), 50)
# Send 40 BTC from 0 to 1 and 60 BTC from 1 to 0.
@@ -156,6 +164,8 @@ class WalletTest(BitcoinTestFramework):
expected_balances_1 = {'mine': {'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_basic.py b/test/functional/wallet_basic.py
index d386d94e0c..40daffd2c2 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -209,6 +209,8 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), node_2_bal)
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
+ self.log.info("Test sendmany")
+
# Sendmany 10 BTC
txid = self.nodes[2].sendmany('', {address: 10}, 0, "", [])
self.nodes[2].generate(1)
@@ -225,9 +227,9 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance(), node_2_bal)
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
- # Sendmany with explicit fee (BTC/kB)
+ self.log.info("Test case-insensitive explicit fee rate (sendmany as BTC/kB)")
# Throw if no conf_target provided
- assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
+ assert_raises_rpc_error(-8, "Selected estimate_mode bTc/kB requires a fee rate to be specified in conf_target",
self.nodes[2].sendmany,
amounts={ address: 10 },
estimate_mode='bTc/kB')
@@ -251,9 +253,9 @@ class WalletTest(BitcoinTestFramework):
node_0_bal += Decimal('10')
assert_equal(self.nodes[0].getbalance(), node_0_bal)
- # Sendmany with explicit fee (SAT/B)
+ self.log.info("Test case-insensitive explicit fee rate (sendmany as sat/B)")
# Throw if no conf_target provided
- assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
+ assert_raises_rpc_error(-8, "Selected estimate_mode sat/b requires a fee rate to be specified in conf_target",
self.nodes[2].sendmany,
amounts={ address: 10 },
estimate_mode='sat/b')
@@ -279,6 +281,12 @@ class WalletTest(BitcoinTestFramework):
node_0_bal += Decimal('10')
assert_equal(self.nodes[0].getbalance(), node_0_bal)
+ # Test setting explicit fee rate just below the minimum.
+ for unit, fee_rate in {"BTC/kB": 0.00000999, "sat/B": 0.99999999}.items():
+ self.log.info("Test sendmany raises 'fee rate too low' if conf_target {} and estimate_mode {} are passed".format(fee_rate, unit))
+ assert_raises_rpc_error(-6, "Fee rate (0.00000999 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
+ self.nodes[2].sendmany, amounts={address: 10}, estimate_mode=unit, conf_target=fee_rate)
+
self.start_node(3, self.nodes[3].extra_args)
self.connect_nodes(0, 3)
self.sync_all()
@@ -412,15 +420,14 @@ class WalletTest(BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
- # send with explicit btc/kb fee
- self.log.info("test explicit fee (sendtoaddress as btc/kb)")
+ self.log.info("Test case-insensitive explicit fee rate (sendtoaddress as BTC/kB)")
self.nodes[0].generate(1)
self.sync_all(self.nodes[0:3])
prebalance = self.nodes[2].getbalance()
assert prebalance > 2
address = self.nodes[1].getnewaddress()
# Throw if no conf_target provided
- assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
+ assert_raises_rpc_error(-8, "Selected estimate_mode BTc/Kb requires a fee rate to be specified in conf_target",
self.nodes[2].sendtoaddress,
address=address,
amount=1.0,
@@ -446,15 +453,15 @@ class WalletTest(BitcoinTestFramework):
fee = prebalance - postbalance - Decimal('1')
assert_fee_amount(fee, tx_size, Decimal('0.00002500'))
- # send with explicit sat/b fee
self.sync_all(self.nodes[0:3])
- self.log.info("test explicit fee (sendtoaddress as sat/b)")
+
+ self.log.info("Test case-insensitive explicit fee rate (sendtoaddress as sat/B)")
self.nodes[0].generate(1)
prebalance = self.nodes[2].getbalance()
assert prebalance > 2
address = self.nodes[1].getnewaddress()
# Throw if no conf_target provided
- assert_raises_rpc_error(-8, "Selected estimate_mode requires a fee rate",
+ assert_raises_rpc_error(-8, "Selected estimate_mode SAT/b requires a fee rate to be specified in conf_target",
self.nodes[2].sendtoaddress,
address=address,
amount=1.0,
@@ -480,6 +487,12 @@ class WalletTest(BitcoinTestFramework):
fee = prebalance - postbalance - Decimal('1')
assert_fee_amount(fee, tx_size, Decimal('0.00002000'))
+ # Test setting explicit fee rate just below the minimum.
+ for unit, fee_rate in {"BTC/kB": 0.00000999, "sat/B": 0.99999999}.items():
+ self.log.info("Test sendtoaddress raises 'fee rate too low' if conf_target {} and estimate_mode {} are passed".format(fee_rate, unit))
+ assert_raises_rpc_error(-6, "Fee rate (0.00000999 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
+ self.nodes[2].sendtoaddress, address=address, amount=1, estimate_mode=unit, conf_target=fee_rate)
+
# 2. Import address from node2 to node1
self.nodes[1].importaddress(address_to_import)
diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py
index 4b29e65b09..88a7778e19 100755
--- a/test/functional/wallet_bumpfee.py
+++ b/test/functional/wallet_bumpfee.py
@@ -17,7 +17,7 @@ from decimal import Decimal
import io
from test_framework.blocktools import add_witness_commitment, create_block, create_coinbase, send_to_witness
-from test_framework.messages import BIP125_SEQUENCE_NUMBER, CTransaction
+from test_framework.messages import BIP125_SEQUENCE_NUMBER, COIN, CTransaction
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -36,6 +36,8 @@ NORMAL = 0.00100000
HIGH = 0.00500000
TOO_HIGH = 1.00000000
+BTC_MODE = "BTC/kB"
+SAT_MODE = "sat/B"
class BumpFeeTest(BitcoinTestFramework):
def set_test_params(self):
@@ -76,10 +78,9 @@ class BumpFeeTest(BitcoinTestFramework):
self.log.info("Running tests")
dest_address = peer_node.getnewaddress()
- self.test_invalid_parameters(rbf_node, dest_address)
- test_simple_bumpfee_succeeds(self, "default", rbf_node, peer_node, dest_address)
- test_simple_bumpfee_succeeds(self, "fee_rate", rbf_node, peer_node, dest_address)
- test_feerate_args(self, rbf_node, peer_node, dest_address)
+ for mode in ["default", "fee_rate", BTC_MODE, SAT_MODE]:
+ test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address)
+ self.test_invalid_parameters(rbf_node, peer_node, dest_address)
test_segwit_bumpfee_succeeds(self, rbf_node, dest_address)
test_nonrbf_bumpfee_fails(self, peer_node, dest_address)
test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address)
@@ -98,28 +99,56 @@ class BumpFeeTest(BitcoinTestFramework):
test_small_output_with_feerate_succeeds(self, rbf_node, dest_address)
test_no_more_inputs_fails(self, rbf_node, dest_address)
- def test_invalid_parameters(self, node, dest_address):
- txid = spend_one_input(node, dest_address)
- # invalid estimate mode
- assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", node.bumpfee, txid, {
- "estimate_mode": "moo",
- })
- assert_raises_rpc_error(-3, "Expected type string", node.bumpfee, txid, {
- "estimate_mode": 38,
- })
- assert_raises_rpc_error(-3, "Expected type string", node.bumpfee, txid, {
- "estimate_mode": {
- "foo": "bar",
- },
- })
- assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", node.bumpfee, txid, {
- "estimate_mode": Decimal("3.141592"),
- })
- # confTarget and conf_target
- assert_raises_rpc_error(-8, "confTarget and conf_target options should not both be set", node.bumpfee, txid, {
- "confTarget": 123,
- "conf_target": 456,
- })
+ def test_invalid_parameters(self, rbf_node, peer_node, dest_address):
+ self.log.info('Test invalid parameters')
+ rbfid = spend_one_input(rbf_node, dest_address)
+ self.sync_mempools((rbf_node, peer_node))
+ assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool()
+
+ assert_raises_rpc_error(-3, "Unexpected key totalFee", rbf_node.bumpfee, rbfid, {"totalFee": NORMAL})
+ assert_raises_rpc_error(-4, "is too high (cannot be higher than", rbf_node.bumpfee, rbfid, {"fee_rate": TOO_HIGH})
+
+ # For each fee mode, bumping to just above minrelay should fail to increase the total fee enough.
+ for options in [{"fee_rate": INSUFFICIENT}, {"conf_target": INSUFFICIENT, "estimate_mode": BTC_MODE}, {"conf_target": 1, "estimate_mode": SAT_MODE}]:
+ assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, rbfid, options)
+
+ self.log.info("Test explicit fee rate raises RPC error if estimate_mode is passed without a conf_target")
+ for unit, fee_rate in {"SAT/B": 100, "BTC/KB": NORMAL}.items():
+ assert_raises_rpc_error(-8, "Selected estimate_mode {} requires a fee rate to be specified in conf_target".format(unit),
+ rbf_node.bumpfee, rbfid, {"fee_rate": fee_rate, "estimate_mode": unit})
+
+ self.log.info("Test explicit fee rate raises RPC error if both fee_rate and conf_target are passed")
+ error_both = "Cannot specify both conf_target and fee_rate. Please provide either a confirmation " \
+ "target in blocks for automatic fee estimation, or an explicit fee rate."
+ assert_raises_rpc_error(-8, error_both, rbf_node.bumpfee, rbfid, {"conf_target": NORMAL, "fee_rate": NORMAL})
+
+ self.log.info("Test invalid conf_target settings")
+ assert_raises_rpc_error(-8, "confTarget and conf_target options should not both be set",
+ rbf_node.bumpfee, rbfid, {"confTarget": 123, "conf_target": 456})
+ for field in ["confTarget", "conf_target"]:
+ assert_raises_rpc_error(-4, "is too high (cannot be higher than -maxtxfee",
+ lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": BTC_MODE, "conf_target": 2009}))
+ assert_raises_rpc_error(-4, "is too high (cannot be higher than -maxtxfee",
+ lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": SAT_MODE, "conf_target": 2009 * 10000}))
+
+ self.log.info("Test invalid estimate_mode settings")
+ for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
+ assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
+ lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": v, "fee_rate": NORMAL}))
+ for mode in ["foo", Decimal("3.141592")]:
+ assert_raises_rpc_error(-8, "Invalid estimate_mode parameter",
+ lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": mode, "fee_rate": NORMAL}))
+
+ self.log.info("Test invalid fee_rate settings")
+ for mode in ["unset", "economical", "conservative", BTC_MODE, SAT_MODE]:
+ self.log.debug("{}".format(mode))
+ for k, v in {"string": "", "object": {"foo": "bar"}}.items():
+ assert_raises_rpc_error(-3, "Expected type number for fee_rate, got {}".format(k),
+ lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": mode, "fee_rate": v}))
+ assert_raises_rpc_error(-3, "Amount out of range",
+ lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": mode, "fee_rate": -1}))
+ assert_raises_rpc_error(-8, "Invalid fee_rate 0.00000000 BTC/kB (must be greater than 0)",
+ lambda: rbf_node.bumpfee(rbfid, {"estimate_mode": mode, "fee_rate": 0}))
self.clear_mempool()
@@ -132,6 +161,13 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address):
if mode == "fee_rate":
bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"fee_rate": NORMAL})
bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": NORMAL})
+ elif mode == BTC_MODE:
+ bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"conf_target": NORMAL, "estimate_mode": BTC_MODE})
+ bumped_tx = rbf_node.bumpfee(rbfid, {"conf_target": NORMAL, "estimate_mode": BTC_MODE})
+ elif mode == SAT_MODE:
+ sat_fee = NORMAL * COIN / 1000 # convert NORMAL from BTC/kB to sat/B
+ bumped_psbt = rbf_node.psbtbumpfee(rbfid, {"conf_target": sat_fee, "estimate_mode": SAT_MODE})
+ bumped_tx = rbf_node.bumpfee(rbfid, {"conf_target": sat_fee, "estimate_mode": SAT_MODE})
else:
bumped_psbt = rbf_node.psbtbumpfee(rbfid)
bumped_tx = rbf_node.bumpfee(rbfid)
@@ -158,33 +194,13 @@ def test_simple_bumpfee_succeeds(self, mode, rbf_node, peer_node, dest_address):
self.clear_mempool()
-def test_feerate_args(self, rbf_node, peer_node, dest_address):
- self.log.info('Test fee_rate args')
- rbfid = spend_one_input(rbf_node, dest_address)
- self.sync_mempools((rbf_node, peer_node))
- assert rbfid in rbf_node.getrawmempool() and rbfid in peer_node.getrawmempool()
-
- assert_raises_rpc_error(-8, "conf_target can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"fee_rate": NORMAL, "confTarget": 1})
-
- assert_raises_rpc_error(-3, "Unexpected key totalFee", rbf_node.bumpfee, rbfid, {"totalFee": NORMAL})
- assert_raises_rpc_error(-8, "conf_target can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.", rbf_node.bumpfee, rbfid, {"fee_rate":0.00001, "confTarget": 1})
-
- # Bumping to just above minrelay should fail to increase total fee enough, at least
- assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, rbfid, {"fee_rate": INSUFFICIENT})
-
- assert_raises_rpc_error(-3, "Amount out of range", rbf_node.bumpfee, rbfid, {"fee_rate": -1})
-
- assert_raises_rpc_error(-4, "is too high (cannot be higher than", rbf_node.bumpfee, rbfid, {"fee_rate": TOO_HIGH})
- self.clear_mempool()
-
-
def test_segwit_bumpfee_succeeds(self, rbf_node, dest_address):
self.log.info('Test that segwit-sourcing bumpfee works')
# Create a transaction with segwit output, then create an RBF transaction
# which spends it, and make sure bumpfee can be called on it.
segwit_in = next(u for u in rbf_node.listunspent() if u["amount"] == Decimal("0.001"))
- segwit_out = rbf_node.getaddressinfo(rbf_node.getnewaddress(address_type='p2sh-segwit'))
+ segwit_out = rbf_node.getaddressinfo(rbf_node.getnewaddress(address_type='bech32'))
segwitid = send_to_witness(
use_p2wsh=False,
node=rbf_node,
@@ -365,7 +381,7 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
rbf_node.createwallet(wallet_name="signer", disable_private_keys=False, blank=True)
signer = rbf_node.get_wallet_rpc("signer")
assert signer.getwalletinfo()['private_keys_enabled']
- result = signer.importmulti([{
+ reqs = [{
"desc": priv_rec_desc,
"timestamp": 0,
"range": [0,1],
@@ -378,7 +394,11 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
"range": [0, 0],
"internal": True,
"keypool": False
- }])
+ }]
+ if self.options.descriptors:
+ result = signer.importdescriptors(reqs)
+ else:
+ result = signer.importmulti(reqs)
assert_equal(result, [{'success': True}, {'success': True}])
# Create another wallet with just the public keys, which creates PSBTs
@@ -386,21 +406,27 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
watcher = rbf_node.get_wallet_rpc("watcher")
assert not watcher.getwalletinfo()['private_keys_enabled']
- result = watcher.importmulti([{
+ reqs = [{
"desc": pub_rec_desc,
"timestamp": 0,
"range": [0, 10],
"internal": False,
"keypool": True,
- "watchonly": True
+ "watchonly": True,
+ "active": True,
}, {
"desc": pub_change_desc,
"timestamp": 0,
"range": [0, 10],
"internal": True,
"keypool": True,
- "watchonly": True
- }])
+ "watchonly": True,
+ "active": True,
+ }]
+ if self.options.descriptors:
+ result = watcher.importdescriptors(reqs)
+ else:
+ result = watcher.importmulti(reqs)
assert_equal(result, [{'success': True}, {'success': True}])
funding_address1 = watcher.getnewaddress(address_type='bech32')
diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py
index 48c0fcb731..cf3317121f 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_p2wpkh
+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):
@@ -35,10 +39,14 @@ class CreateWalletTest(BitcoinTestFramework):
w1.importpubkey(w0.getaddressinfo(address1)['pubkey'])
self.log.info('Test that private keys cannot be imported')
- addr = w0.getnewaddress('', 'legacy')
- 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]}])
+ if self.options.descriptors:
+ result = w1.importdescriptors([{'desc': descsum_create('wpkh(' + privkey + ')'), 'timestamp': 'now'}])
+ else:
+ result = w1.importmulti([{'scriptPubKey': {'address': key_to_p2wpkh(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)
@@ -58,12 +66,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.sethdseed()
+ if self.options.descriptors:
+ w3.importdescriptors([{
+ 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/0h/*)'),
+ 'timestamp': 'now',
+ 'active': True
+ },
+ {
+ 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/1h/*)'),
+ 'timestamp': 'now',
+ 'active': True,
+ 'internal': True
+ }])
+ else:
+ w3.sethdseed()
assert_equal(w3.getwalletinfo()['keypoolsize'], 1)
w3.getnewaddress()
w3.getrawchangeaddress()
@@ -80,7 +101,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.sethdseed()
+ if self.options.descriptors:
+ w4.importdescriptors([{
+ 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/0h/*)'),
+ 'timestamp': 'now',
+ 'active': True
+ },
+ {
+ 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/1h/*)'),
+ 'timestamp': 'now',
+ 'active': True,
+ 'internal': True
+ }])
+ else:
+ w4.sethdseed()
w4.getnewaddress()
w4.getrawchangeaddress()
@@ -111,13 +145,14 @@ class CreateWalletTest(BitcoinTestFramework):
w6.walletpassphrase('thisisapassphrase', 60)
w6.signmessage(w6.getnewaddress('', 'legacy'), "test")
w6.keypoolrefill(1)
- # There should only be 1 key
+ # There should only be 1 key for legacy, 3 for descriptors
walletinfo = w6.getwalletinfo()
- assert_equal(walletinfo['keypoolsize'], 1)
- assert_equal(walletinfo['keypoolsize_hd_internal'], 1)
+ keys = 3 if self.options.descriptors else 1
+ assert_equal(walletinfo['keypoolsize'], keys)
+ 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 743a9ac9ff..949adeb703 100755
--- a/test/functional/wallet_importdescriptors.py
+++ b/test/functional/wallet_importdescriptors.py
@@ -34,6 +34,7 @@ class ImportDescriptorsTest(BitcoinTestFramework):
["-addresstype=bech32", "-keypool=5"]
]
self.setup_clean_chain = True
+ self.wallet_names = []
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -59,7 +60,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 78426018ef..d10d8ae1c8 100755
--- a/test/functional/wallet_importprunedfunds.py
+++ b/test/functional/wallet_importprunedfunds.py
@@ -5,11 +5,14 @@
"""Test the importprunedfunds and removeprunedfunds RPCs."""
from decimal import Decimal
+from test_framework.address import key_to_p2wpkh
+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):
@@ -30,8 +33,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_p2wpkh(eckey.get_pubkey().get_bytes())
+ self.nodes[0].importprivkey(address3_privkey)
# Check only one address
address_info = self.nodes[0].getaddressinfo(address1)
@@ -80,37 +86,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 cff59bd1c1..883b97561e 100755
--- a/test/functional/wallet_labels.py
+++ b/test/functional/wallet_labels.py
@@ -135,7 +135,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')
BECH32_VALID = {
'✔️_VER15_PROG40': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqn2cjv3',
@@ -156,7 +156,7 @@ class WalletLabelsTest(BitcoinTestFramework):
ad = BECH32_INVALID[l]
assert_raises_rpc_error(
-5,
- "Invalid Bitcoin address or script",
+ "Address is not valid" if self.options.descriptors else "Invalid Bitcoin 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 09a336b764..07c14db6b1 100755
--- a/test/functional/wallet_listsinceblock.py
+++ b/test/functional/wallet_listsinceblock.py
@@ -4,6 +4,8 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the listsinceblock RPC."""
+from test_framework.address import key_to_p2wpkh
+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 (
@@ -11,6 +13,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
@@ -181,15 +184,21 @@ class ListSinceBlockTest(BitcoinTestFramework):
self.sync_all()
- # Split network into two
- self.split_network()
-
# share utxo between nodes[1] and nodes[2]
+ eckey = ECKey()
+ eckey.generate()
+ privkey = bytes_to_wif(eckey.get_bytes())
+ address = key_to_p2wpkh(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 = utxos[0]
- privkey = self.nodes[2].dumpprivkey(utxo['address'])
+ utxo = [u for u in utxos if u["address"] == address][0]
self.nodes[1].importprivkey(privkey)
+ # Split network into two
+ self.split_network()
+
# 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 8ff663ccd2..3e98208431 100755
--- a/test/functional/wallet_listtransactions.py
+++ b/test/functional/wallet_listtransactions.py
@@ -91,18 +91,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"})
self.run_rbf_opt_in_test()
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index 63af9e8720..3f6a17269e 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -82,8 +82,8 @@ class MultiWalletTest(BitcoinTestFramework):
# directory paths) can be loaded
# create another dummy wallet for use in testing backups later
self.start_node(0)
- node.createwallet("empty", descriptors=False)
- node.createwallet("plain", descriptors=False)
+ node.createwallet("empty")
+ node.createwallet("plain")
node.createwallet("created")
self.stop_nodes()
empty_wallet = os.path.join(self.options.tmpdir, 'empty.dat')
@@ -101,20 +101,29 @@ class MultiWalletTest(BitcoinTestFramework):
# sub/w5 - to verify relative wallet path is created correctly
# extern/w6 - to verify absolute wallet path is created correctly
# w7_symlink - to verify symlinked wallet path is initialized correctly
- # w8 - to verify existing wallet file is loaded correctly
+ # w8 - to verify existing wallet file is loaded correctly. Not tested for SQLite wallets as this is a deprecated BDB behavior.
# '' - to verify default wallet file is created correctly
- wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5', os.path.join(self.options.tmpdir, 'extern/w6'), 'w7_symlink', 'w8', self.default_wallet_name]
+ to_create = ['w1', 'w2', 'w3', 'w', 'sub/w5', 'w7_symlink']
+ in_wallet_dir = to_create.copy() # Wallets in the wallet dir
+ in_wallet_dir.append('w7') # w7 is not loaded or created, but will be listed by listwalletdir because w7_symlink
+ to_create.append(os.path.join(self.options.tmpdir, 'extern/w6')) # External, not in the wallet dir, so we need to avoid adding it to in_wallet_dir
+ to_load = [self.default_wallet_name]
+ if not self.options.descriptors:
+ to_load.append('w8')
+ wallet_names = to_create + to_load # Wallet names loaded in the wallet
+ in_wallet_dir += to_load # The loaded wallets are also in the wallet dir
self.start_node(0)
- for wallet_name in wallet_names[:-2]:
- self.nodes[0].createwallet(wallet_name, descriptors=False)
- for wallet_name in wallet_names[-2:]:
+ for wallet_name in to_create:
+ self.nodes[0].createwallet(wallet_name)
+ for wallet_name in to_load:
self.nodes[0].loadwallet(wallet_name)
- assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [self.default_wallet_name, os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8'])
+ assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), sorted(in_wallet_dir))
assert_equal(set(node.listwallets()), set(wallet_names))
# should raise rpc error if wallet path can't be created
- assert_raises_rpc_error(-1, "boost::filesystem::create_directory:", self.nodes[0].createwallet, "w8/bad", descriptors=False)
+ err_code = -4 if self.options.descriptors else -1
+ assert_raises_rpc_error(err_code, "boost::filesystem::create_directory:", self.nodes[0].createwallet, "w8/bad")
# check that all requested wallets were created
self.stop_node(0)
@@ -125,13 +134,16 @@ class MultiWalletTest(BitcoinTestFramework):
self.nodes[0].assert_start_raises_init_error(['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir())
self.nodes[0].assert_start_raises_init_error(['-walletdir=debug.log'], 'Error: Specified -walletdir "debug.log" is not a directory', cwd=data_dir())
- # should not initialize if there are duplicate wallets
- self.nodes[0].assert_start_raises_init_error(['-wallet=w1', '-wallet=w1'], 'Error: Error loading wallet w1. Duplicate -wallet filename specified.')
+ self.start_node(0, ['-wallet=w1', '-wallet=w1'])
+ self.stop_node(0, 'Warning: Ignoring duplicate -wallet w1.')
- # should not initialize if one wallet is a copy of another
- shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy'))
- exp_stderr = r"BerkeleyDatabase: Can't open database w8_copy \(duplicates fileid \w+ from w8\)"
- self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
+ if not self.options.descriptors:
+ # Only BDB doesn't open duplicate wallet files. SQLite does not have this limitation. While this may be desired in the future, it is not necessary
+ # should not initialize if one wallet is a copy of another
+ shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy'))
+ in_wallet_dir.append('w8_copy')
+ exp_stderr = r"BerkeleyDatabase: Can't open database w8_copy \(duplicates fileid \w+ from w8\)"
+ self.nodes[0].assert_start_raises_init_error(['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
# should not initialize if wallet file is a symlink
os.symlink('w8', wallet_dir('w8_symlink'))
@@ -167,15 +179,18 @@ class MultiWalletTest(BitcoinTestFramework):
competing_wallet_dir = os.path.join(self.options.tmpdir, 'competing_walletdir')
os.mkdir(competing_wallet_dir)
self.restart_node(0, ['-nowallet', '-walletdir=' + competing_wallet_dir])
- self.nodes[0].createwallet(self.default_wallet_name, descriptors=False)
- exp_stderr = r"Error: Error initializing wallet database environment \"\S+competing_walletdir\S*\"!"
+ self.nodes[0].createwallet(self.default_wallet_name)
+ if self.options.descriptors:
+ exp_stderr = r"Error: SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another bitcoind?"
+ else:
+ exp_stderr = r"Error: Error initializing wallet database environment \"\S+competing_walletdir\S*\"!"
self.nodes[1].assert_start_raises_init_error(['-walletdir=' + competing_wallet_dir], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
self.restart_node(0)
for wallet_name in wallet_names:
self.nodes[0].loadwallet(wallet_name)
- assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [self.default_wallet_name, os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy'])
+ assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), sorted(in_wallet_dir))
wallets = [wallet(w) for w in wallet_names]
wallet_bad = wallet("bad")
@@ -266,18 +281,22 @@ class MultiWalletTest(BitcoinTestFramework):
# Fail to load duplicate wallets
path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "w1", "wallet.dat")
- assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, wallet_names[0])
+ if self.options.descriptors:
+ assert_raises_rpc_error(-4, "Wallet file verification failed. SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another bitcoind?", self.nodes[0].loadwallet, wallet_names[0])
+ else:
+ assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, wallet_names[0])
- # Fail to load duplicate wallets by different ways (directory and filepath)
- if not self.options.descriptors:
+ # This tests the default wallet that BDB makes, so SQLite wallet doesn't need to test this
+ # Fail to load duplicate wallets by different ways (directory and filepath)
path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "wallet.dat")
assert_raises_rpc_error(-4, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, 'wallet.dat')
- # Fail to load if one wallet is a copy of another
- assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
+ # Only BDB doesn't open duplicate wallet files. SQLite does not have this limitation. While this may be desired in the future, it is not necessary
+ # Fail to load if one wallet is a copy of another
+ assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
- # Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304
- assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
+ # Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304
+ assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
# Fail to load if wallet file is a symlink
@@ -296,6 +315,7 @@ class MultiWalletTest(BitcoinTestFramework):
# Successfully create a wallet with a new name
loadwallet_name = self.nodes[0].createwallet('w9')
+ in_wallet_dir.append('w9')
assert_equal(loadwallet_name['name'], 'w9')
w9 = node.get_wallet_rpc('w9')
assert_equal(w9.getwalletinfo()['walletname'], 'w9')
@@ -343,7 +363,7 @@ class MultiWalletTest(BitcoinTestFramework):
assert_equal(self.nodes[0].listwallets(), ['w1'])
assert_equal(w1.getwalletinfo()['walletname'], 'w1')
- assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [self.default_wallet_name, os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy', 'w9'])
+ assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), sorted(in_wallet_dir))
# Test backing up and restoring wallets
self.log.info("Test wallet backup")
diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py
index 60ab2d457e..921a726d4b 100755
--- a/test/functional/wallet_send.py
+++ b/test/functional/wallet_send.py
@@ -97,6 +97,8 @@ class WalletSendTest(BitcoinTestFramework):
except AssertionError:
# Provide debug info if the test fails
self.log.error("Unexpected successful result:")
+ self.log.error(arg_conf_target)
+ self.log.error(arg_estimate_mode)
self.log.error(options)
res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options)
self.log.error(res)
@@ -224,8 +226,10 @@ class WalletSendTest(BitcoinTestFramework):
assert_equal(self.nodes[1].decodepsbt(res1["psbt"])["fee"],
self.nodes[1].decodepsbt(res2["psbt"])["fee"])
# but not at the same time
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=1, arg_estimate_mode="economical",
- conf_target=1, estimate_mode="economical", add_to_wallet=False, expect_error=(-8,"Use either conf_target and estimate_mode or the options dictionary to control fee rate"))
+ for mode in ["unset", "economical", "conservative", "btc/kb", "sat/b"]:
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=1, arg_estimate_mode="economical",
+ conf_target=1, estimate_mode=mode, add_to_wallet=False,
+ expect_error=(-8, "Use either conf_target and estimate_mode or the options dictionary to control fee rate"))
self.log.info("Create PSBT from watch-only wallet w3, sign with w2...")
res = self.test_send(from_wallet=w3, to_wallet=w1, amount=1)
@@ -246,19 +250,61 @@ class WalletSendTest(BitcoinTestFramework):
res = w2.walletprocesspsbt(res["psbt"])
assert res["complete"]
- self.log.info("Set fee rate...")
+ self.log.info("Test setting explicit fee rate")
+ res1 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=1, arg_estimate_mode="economical", add_to_wallet=False)
+ res2 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=1, estimate_mode="economical", add_to_wallet=False)
+ assert_equal(self.nodes[1].decodepsbt(res1["psbt"])["fee"], self.nodes[1].decodepsbt(res2["psbt"])["fee"])
+
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.00007, estimate_mode="btc/kb", add_to_wallet=False)
+ fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
+ assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00007"))
+
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=2, estimate_mode="sat/b", add_to_wallet=False)
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00002"))
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=-1, estimate_mode="sat/b",
- expect_error=(-3, "Amount out of range"))
- # Fee rate of 0.1 satoshi per byte should throw an error
- # TODO: error should use sat/b
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode="sat/b",
- expect_error=(-4, "Fee rate (0.00000100 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)"))
-
- self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.000001, estimate_mode="BTC/KB",
- expect_error=(-4, "Fee rate (0.00000100 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)"))
+
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=0.00004531, arg_estimate_mode="btc/kb", add_to_wallet=False)
+ fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
+ assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00004531"))
+
+ res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=3, arg_estimate_mode="sat/b", add_to_wallet=False)
+ fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
+ assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00003"))
+
+ # TODO: This test should pass with all modes, e.g. with the next line uncommented, for consistency with the other explicit feerate RPCs.
+ # for mode in ["unset", "economical", "conservative", "btc/kb", "sat/b"]:
+ for mode in ["btc/kb", "sat/b"]:
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=-1, estimate_mode=mode,
+ expect_error=(-3, "Amount out of range"))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0, estimate_mode=mode,
+ expect_error=(-4, "Fee rate (0.00000000 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)"))
+
+ for mode in ["foo", Decimal("3.141592")]:
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode=mode,
+ expect_error=(-8, "Invalid estimate_mode parameter"))
+ # TODO: these 2 equivalent sends with an invalid estimate_mode arg should both fail, but they do not...why?
+ # self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=0.1, arg_estimate_mode=mode,
+ # expect_error=(-8, "Invalid estimate_mode parameter"))
+ # assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", lambda: w0.send({w1.getnewaddress(): 1}, 0.1, mode))
+
+ # TODO: These tests should pass for consistency with the other explicit feerate RPCs, but they do not.
+ # for mode in ["unset", "economical", "conservative", "btc/kb", "sat/b"]:
+ # self.log.debug("{}".format(mode))
+ # for k, v in {"string": "", "object": {"foo": "bar"}}.items():
+ # self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=v, estimate_mode=mode,
+ # expect_error=(-3, "Expected type number for conf_target, got {}".format(k)))
+
+ # TODO: error should use sat/B instead of BTC/kB if sat/B is selected.
+ # Test setting explicit fee rate just below the minimum.
+ for unit, fee_rate in {"sat/B": 0.99999999, "BTC/kB": 0.00000999}.items():
+ self.log.info("Explicit fee rate raises RPC error 'fee rate too low' if conf_target {} and estimate_mode {} are passed".format(fee_rate, unit))
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=fee_rate, estimate_mode=unit,
+ expect_error=(-4, "Fee rate (0.00000999 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)"))
+
+ self.log.info("Explicit fee rate raises RPC error if estimate_mode is passed without a conf_target")
+ for unit, fee_rate in {"sat/B": 100, "BTC/kB": 0.001}.items():
+ self.test_send(from_wallet=w0, to_wallet=w1, amount=1, estimate_mode=unit,
+ expect_error=(-8, "Selected estimate_mode {} requires a fee rate to be specified in conf_target".format(unit)))
# TODO: Return hex if fee rate is below -maxmempool
# res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode="sat/b", add_to_wallet=False)
diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py
index 446a601aee..15d9b109c5 100755
--- a/test/functional/wallet_upgradewallet.py
+++ b/test/functional/wallet_upgradewallet.py
@@ -107,7 +107,7 @@ class UpgradeWalletTest(BitcoinTestFramework):
# calling upgradewallet without version arguments
# should return nothing if successful
- assert_equal(wallet.upgradewallet(), "")
+ assert_equal(wallet.upgradewallet(), {})
new_version = wallet.getwalletinfo()["walletversion"]
# upgraded wallet version should be greater than older one
assert_greater_than(new_version, old_version)
@@ -130,7 +130,7 @@ class UpgradeWalletTest(BitcoinTestFramework):
assert_equal('hdseedid' in wallet.getwalletinfo(), False)
# calling upgradewallet with explicit version number
# should return nothing if successful
- assert_equal(wallet.upgradewallet(169900), "")
+ assert_equal(wallet.upgradewallet(169900), {})
new_version = wallet.getwalletinfo()["walletversion"]
# upgraded wallet should have version 169900
assert_equal(new_version, 169900)