aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorSamuel Dobson <dobsonsa68@gmail.com>2020-11-04 15:45:01 +1300
committerSamuel Dobson <dobsonsa68@gmail.com>2020-11-04 16:35:23 +1300
commit5d32009f1a3b091299ff4a9345195b2359125f98 (patch)
tree8f9d442963cbe2cf2cb00556dbb2f1a9f06b8b4e /test
parent17c6fb176a07d86f96f7cf41164c4b71cd1b3765 (diff)
parent0be29000c011dec0722481dbebb159873da6fa54 (diff)
downloadbitcoin-5d32009f1a3b091299ff4a9345195b2359125f98.tar.xz
Merge #20220: wallet, rpc: explicit fee rate follow-ups/fixes for 0.21
0be29000c011dec0722481dbebb159873da6fa54 rpc: update conf_target helps for correctness/consistency (Jon Atack) 778b9be40667876c705e586849ea9c9e44cf451c wallet, rpc: fix send subtract_fee_from_outputs help (Jon Atack) 603c0050837ec65765208dd54dde354145fbe098 wallet: add rpc send explicit fee rate coverage (Jon Atack) dd341e602d5160fc621c0299179b91403756b61d wallet: add sendtoaddress/sendmany explicit fee rate coverage (Jon Atack) 44e7bfa60313e4ae67da49e5ba4535038b71b453 wallet: add walletcreatefundedpsbt explicit fee rate coverage (Jon Atack) 6e1ea4273e52fdcd86c87628aa595c03a071ca8c test: refactor for walletcreatefundedpsbt fee rate coverage (Jon Atack) 3ac7b0c6f1c68e74a84d868a454f508bada6b09d wallet: fundrawtx fee rate coverage, fixup ParseConfirmTarget() (Jon Atack) 2d8eba8f8425a2515022d51f1f5b4911329fbf55 wallet: combine redundant bumpfee invalid params and args tests (Jon Atack) 1697a40b6f841a54ee0d9744ed7fd09034b0ddad wallet: improve bumpfee error/help, add explicit fee rate coverage (Jon Atack) fc5721723d34f76f9e1ffd2e31f274ea6b22f894 wallet: fix SetFeeEstimateMode() error message (Jon Atack) 052427eef1c9da84c474c5161b1910d3328ef0da wallet, bugfix: fix bumpfee with explicit fee rate modes (Jon Atack) Pull request description: Follow-up to #11413 providing a base to build on for #19543: - bugfix for `bumpfee` raising a JSON error with explicit feerates, fixes issue #20219 - adds explicit feerate test coverage for `bumpfee`, `fundrawtransaction`, `walletcreatefundedpsbt`, `send`, `sendtoaddress`, and `sendmany` - improves a few related RPC error messages and `ParseConfirmTarget()` / error message - fixes/improves the explicit fee rate information in the 6 RPC helps, of which 2 were also missing `conf_target` sat/B units This provides a spec and regression coverage for the potential next step of a universal `sat/vB` feerate argument (see #19543), as well as immediate coverage and minimum fixes for 0.21. ACKs for top commit: kallewoof: Concept/Tested ACK 0be29000c011dec0722481dbebb159873da6fa54 meshcollider: Code review + functional test run ACK 0be29000c011dec0722481dbebb159873da6fa54 Tree-SHA512: efd965003e991cba51d4504e2940f06ab3d742e34022e96a673606b44fad85596aa03a8c1809f06df7ebcf21a38e18a891e54392fe3d6fb4d120bbe4ea0cf5e0
Diffstat (limited to 'test')
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py55
-rwxr-xr-xtest/functional/rpc_psbt.py62
-rwxr-xr-xtest/functional/wallet_basic.py33
-rwxr-xr-xtest/functional/wallet_bumpfee.py110
-rwxr-xr-xtest/functional/wallet_send.py70
5 files changed, 255 insertions, 75 deletions
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 5a23b60dd8..503993162b 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -8,6 +8,7 @@ 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,
@@ -89,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()
@@ -722,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_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/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 9877a9cbd7..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,26 +194,6 @@ 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
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)