mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-07-02 11:41:18 +02:00
wallet: introduce fee_rate (sat/vB) param/option
Create a fee_rate (sat/vB) RPC param and replace overloading the conf_target and estimate_mode params in the following 6 RPCs with it: - sendtoaddress - sendmany - send - fundrawtransaction - walletcreatefundedpsbt - bumpfee In RPC bumpfee, the previously existing fee_rate remains but the unit is changed from BTC/kvB to sat/vB. This is a breaking change, but it should not be an overly risky one, as the units change by a factor of 1e5 and any fees specified in BTC/kvB after this commit will either be too low and raise an error or be 1 sat/vB and can be RBFed. Update the test coverage for each RPC. Co-authored-by: Murch <murch@murch.one>
This commit is contained in:
@ -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, COIN, CTransaction
|
||||
from test_framework.messages import BIP125_SEQUENCE_NUMBER, CTransaction
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
@ -29,15 +29,13 @@ from test_framework.util import (
|
||||
WALLET_PASSPHRASE = "test"
|
||||
WALLET_PASSPHRASE_TIMEOUT = 3600
|
||||
|
||||
# Fee rates (in BTC per 1000 vbytes)
|
||||
INSUFFICIENT = 0.00001000
|
||||
ECONOMICAL = 0.00050000
|
||||
NORMAL = 0.00100000
|
||||
HIGH = 0.00500000
|
||||
TOO_HIGH = 1.00000000
|
||||
# Fee rates (sat/vB)
|
||||
INSUFFICIENT = 1
|
||||
ECONOMICAL = 50
|
||||
NORMAL = 100
|
||||
HIGH = 500
|
||||
TOO_HIGH = 100000
|
||||
|
||||
BTC_MODE = "BTC/kB"
|
||||
SAT_MODE = "sat/B"
|
||||
|
||||
class BumpFeeTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
@ -78,7 +76,7 @@ class BumpFeeTest(BitcoinTestFramework):
|
||||
|
||||
self.log.info("Running tests")
|
||||
dest_address = peer_node.getnewaddress()
|
||||
for mode in ["default", "fee_rate", BTC_MODE, SAT_MODE]:
|
||||
for mode in ["default", "fee_rate"]:
|
||||
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)
|
||||
@ -105,50 +103,43 @@ class BumpFeeTest(BitcoinTestFramework):
|
||||
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 key in ["totalFee", "feeRate"]:
|
||||
assert_raises_rpc_error(-3, "Unexpected key {}".format(key), rbf_node.bumpfee, rbfid, {key: NORMAL})
|
||||
|
||||
# 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)
|
||||
# Bumping to just above minrelay should fail to increase the total fee enough.
|
||||
assert_raises_rpc_error(-8, "Insufficient total fee 0.00000141, must be at least 0.00001704 (oldFee 0.00000999 + incrementalFee 0.00000705)",
|
||||
rbf_node.bumpfee, rbfid, {"fee_rate": INSUFFICIENT})
|
||||
|
||||
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 invalid fee rate settings")
|
||||
assert_raises_rpc_error(-8, "Invalid fee_rate 0.00000000 BTC/kB (must be greater than 0)",
|
||||
rbf_node.bumpfee, rbfid, {"fee_rate": 0})
|
||||
assert_raises_rpc_error(-4, "Specified or calculated fee 0.141 is too high (cannot be higher than -maxtxfee 0.10",
|
||||
rbf_node.bumpfee, rbfid, {"fee_rate": TOO_HIGH})
|
||||
assert_raises_rpc_error(-3, "Amount out of range", rbf_node.bumpfee, rbfid, {"fee_rate": -1})
|
||||
for value in [{"foo": "bar"}, True]:
|
||||
assert_raises_rpc_error(-3, "Amount is not a number or string", rbf_node.bumpfee, rbfid, {"fee_rate": value})
|
||||
assert_raises_rpc_error(-3, "Invalid amount", rbf_node.bumpfee, rbfid, {"fee_rate": ""})
|
||||
|
||||
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})
|
||||
assert_raises_rpc_error(-8, "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.",
|
||||
rbf_node.bumpfee, rbfid, {"conf_target": NORMAL, "fee_rate": NORMAL})
|
||||
|
||||
self.log.info("Test explicit fee rate raises RPC error if both fee_rate and estimate_mode are passed")
|
||||
assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and fee_rate",
|
||||
rbf_node.bumpfee, rbfid, {"estimate_mode": "economical", "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}))
|
||||
rbf_node.bumpfee, rbfid, {"confTarget": 123, "conf_target": 456})
|
||||
|
||||
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}))
|
||||
rbf_node.bumpfee, rbfid, {"estimate_mode": v})
|
||||
for mode in ["foo", Decimal("3.1415"), "sat/B", "BTC/kB"]:
|
||||
assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", rbf_node.bumpfee, rbfid, {"estimate_mode": mode})
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@ -161,13 +152,6 @@ 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)
|
||||
@ -319,11 +303,11 @@ def test_dust_to_fee(self, rbf_node, dest_address):
|
||||
# boundary. Thus expected transaction size (p2wpkh, 1 input, 2 outputs) is 140-141 vbytes, usually 141.
|
||||
if not 140 <= fulltx["vsize"] <= 141:
|
||||
raise AssertionError("Invalid tx vsize of {} (140-141 expected), full tx: {}".format(fulltx["vsize"], fulltx))
|
||||
# Bump with fee_rate of 0.00350250 BTC per 1000 vbytes to create dust.
|
||||
# Bump with fee_rate of 350.25 sat/vB vbytes to create dust.
|
||||
# Expected fee is 141 vbytes * fee_rate 0.00350250 BTC / 1000 vbytes = 0.00049385 BTC.
|
||||
# or occasionally 140 vbytes * fee_rate 0.00350250 BTC / 1000 vbytes = 0.00049035 BTC.
|
||||
# Dust should be dropped to the fee, so actual bump fee is 0.00050000 BTC.
|
||||
bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": 0.00350250})
|
||||
bumped_tx = rbf_node.bumpfee(rbfid, {"fee_rate": 350.25})
|
||||
full_bumped_tx = rbf_node.getrawtransaction(bumped_tx["txid"], 1)
|
||||
assert_equal(bumped_tx["fee"], Decimal("0.00050000"))
|
||||
assert_equal(len(fulltx["vout"]), 2)
|
||||
|
Reference in New Issue
Block a user