aboutsummaryrefslogtreecommitdiff
path: root/test/functional/rpc_fundrawtransaction.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/rpc_fundrawtransaction.py')
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py144
1 files changed, 125 insertions, 19 deletions
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")