mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-26 05:48:20 +01: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:
@@ -4,6 +4,7 @@
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test the wallet."""
|
||||
from decimal import Decimal
|
||||
from itertools import product
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
@@ -14,6 +15,8 @@ from test_framework.util import (
|
||||
)
|
||||
from test_framework.wallet_util import test_address
|
||||
|
||||
OUT_OF_RANGE = "Amount out of range"
|
||||
|
||||
|
||||
class WalletTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
@@ -74,7 +77,7 @@ class WalletTest(BitcoinTestFramework):
|
||||
assert_equal(len(self.nodes[1].listunspent()), 1)
|
||||
assert_equal(len(self.nodes[2].listunspent()), 0)
|
||||
|
||||
self.log.info("test gettxout")
|
||||
self.log.info("Test gettxout")
|
||||
confirmed_txid, confirmed_index = utxos[0]["txid"], utxos[0]["vout"]
|
||||
# First, outputs that are unspent both in the chain and in the
|
||||
# mempool should appear with or without include_mempool
|
||||
@@ -87,7 +90,7 @@ class WalletTest(BitcoinTestFramework):
|
||||
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)
|
||||
mempool_txid = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 10)
|
||||
|
||||
self.log.info("test gettxout (second part)")
|
||||
self.log.info("Test gettxout (second part)")
|
||||
# utxo spent in mempool should be visible if you exclude mempool
|
||||
# but invisible if you include mempool
|
||||
txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, False)
|
||||
@@ -227,65 +230,41 @@ 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']))
|
||||
|
||||
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 bTc/kB requires a fee rate to be specified in conf_target",
|
||||
self.nodes[2].sendmany,
|
||||
amounts={ address: 10 },
|
||||
estimate_mode='bTc/kB')
|
||||
# Throw if negative feerate
|
||||
assert_raises_rpc_error(-3, "Amount out of range",
|
||||
self.nodes[2].sendmany,
|
||||
amounts={ address: 10 },
|
||||
conf_target=-1,
|
||||
estimate_mode='bTc/kB')
|
||||
fee_per_kb = 0.0002500
|
||||
explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
|
||||
txid = self.nodes[2].sendmany(
|
||||
amounts={ address: 10 },
|
||||
conf_target=fee_per_kb,
|
||||
estimate_mode='bTc/kB',
|
||||
)
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all(self.nodes[0:3])
|
||||
node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), explicit_fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
|
||||
assert_equal(self.nodes[2].getbalance(), node_2_bal)
|
||||
node_0_bal += Decimal('10')
|
||||
assert_equal(self.nodes[0].getbalance(), node_0_bal)
|
||||
self.log.info("Test sendmany with fee_rate param (explicit fee rate in sat/vB)")
|
||||
fee_rate_sat_vb = 2
|
||||
fee_rate_btc_kvb = fee_rate_sat_vb * 1e3 / 1e8
|
||||
explicit_fee_rate_btc_kvb = Decimal(fee_rate_btc_kvb) / 1000
|
||||
|
||||
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 sat/b requires a fee rate to be specified in conf_target",
|
||||
self.nodes[2].sendmany,
|
||||
amounts={ address: 10 },
|
||||
estimate_mode='sat/b')
|
||||
# Throw if negative feerate
|
||||
assert_raises_rpc_error(-3, "Amount out of range",
|
||||
self.nodes[2].sendmany,
|
||||
amounts={ address: 10 },
|
||||
conf_target=-1,
|
||||
estimate_mode='sat/b')
|
||||
fee_sat_per_b = 2
|
||||
fee_per_kb = fee_sat_per_b / 100000.0
|
||||
explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
|
||||
txid = self.nodes[2].sendmany(
|
||||
amounts={ address: 10 },
|
||||
conf_target=fee_sat_per_b,
|
||||
estimate_mode='sAT/b',
|
||||
)
|
||||
# Passing conf_target 0, estimate_mode "" as placeholder arguments should allow fee_rate to apply.
|
||||
txid = self.nodes[2].sendmany(amounts={address: 10}, conf_target=0, estimate_mode="", fee_rate=fee_rate_sat_vb)
|
||||
self.nodes[2].generate(1)
|
||||
self.sync_all(self.nodes[0:3])
|
||||
balance = self.nodes[2].getbalance()
|
||||
node_2_bal = self.check_fee_amount(balance, node_2_bal - Decimal('10'), explicit_fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
|
||||
node_2_bal = self.check_fee_amount(balance, node_2_bal - Decimal('10'), explicit_fee_rate_btc_kvb, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
|
||||
assert_equal(balance, node_2_bal)
|
||||
node_0_bal += Decimal('10')
|
||||
assert_equal(self.nodes[0].getbalance(), node_0_bal)
|
||||
|
||||
for key in ["totalFee", "feeRate"]:
|
||||
assert_raises_rpc_error(-8, "Unknown named parameter key", self.nodes[2].sendtoaddress, address=address, amount=1, fee_rate=1, key=1)
|
||||
|
||||
# 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.log.info("Test sendmany raises 'fee rate too low' if fee_rate of 0.99999999 is passed")
|
||||
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}, fee_rate=0.99999999)
|
||||
|
||||
self.log.info("Test sendmany raises if fee_rate of 0 or -1 is passed")
|
||||
assert_raises_rpc_error(-6, "Fee rate (0.00000000 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
|
||||
self.nodes[2].sendmany, amounts={address: 10}, fee_rate=0)
|
||||
assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[2].sendmany, amounts={address: 10}, fee_rate=-1)
|
||||
|
||||
self.log.info("Test sendmany raises if an invalid conf_target or estimate_mode is passed")
|
||||
for target, mode in product([-1, 0, 1009], ["economical", "conservative"]):
|
||||
assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
|
||||
self.nodes[2].sendmany, amounts={address: 1}, conf_target=target, estimate_mode=mode)
|
||||
for target, mode in product([-1, 0], ["btc/kb", "sat/b"]):
|
||||
assert_raises_rpc_error(-8, "Invalid estimate_mode parameter",
|
||||
self.nodes[2].sendmany, amounts={address: 1}, conf_target=target, estimate_mode=mode)
|
||||
|
||||
self.start_node(3, self.nodes[3].extra_args)
|
||||
self.connect_nodes(0, 3)
|
||||
@@ -318,7 +297,7 @@ class WalletTest(BitcoinTestFramework):
|
||||
assert_equal(uTx['amount'], Decimal('0'))
|
||||
assert found
|
||||
|
||||
# do some -walletbroadcast tests
|
||||
self.log.info("Test -walletbroadcast")
|
||||
self.stop_nodes()
|
||||
self.start_node(0, ["-walletbroadcast=0"])
|
||||
self.start_node(1, ["-walletbroadcast=0"])
|
||||
@@ -378,7 +357,7 @@ class WalletTest(BitcoinTestFramework):
|
||||
|
||||
# General checks for errors from incorrect inputs
|
||||
# This will raise an exception because the amount is negative
|
||||
assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "-1")
|
||||
assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "-1")
|
||||
|
||||
# This will raise an exception because the amount type is wrong
|
||||
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
|
||||
@@ -420,78 +399,43 @@ class WalletTest(BitcoinTestFramework):
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all(self.nodes[0:3])
|
||||
|
||||
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])
|
||||
self.log.info("Test sendtoaddress with fee_rate param (explicit fee rate in sat/vB)")
|
||||
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 BTc/Kb requires a fee rate to be specified in conf_target",
|
||||
self.nodes[2].sendtoaddress,
|
||||
address=address,
|
||||
amount=1.0,
|
||||
estimate_mode='BTc/Kb')
|
||||
# Throw if negative feerate
|
||||
assert_raises_rpc_error(-3, "Amount out of range",
|
||||
self.nodes[2].sendtoaddress,
|
||||
address=address,
|
||||
amount=1.0,
|
||||
conf_target=-1,
|
||||
estimate_mode='btc/kb')
|
||||
txid = self.nodes[2].sendtoaddress(
|
||||
address=address,
|
||||
amount=1.0,
|
||||
conf_target=0.00002500,
|
||||
estimate_mode='btc/kb',
|
||||
)
|
||||
amount = 3
|
||||
fee_rate_sat_vb = 2
|
||||
fee_rate_btc_kvb = fee_rate_sat_vb * 1e3 / 1e8
|
||||
|
||||
# Passing conf_target 0, estimate_mode "" as placeholder arguments should allow fee_rate to apply.
|
||||
txid = self.nodes[2].sendtoaddress(address=address, amount=amount, conf_target=0, estimate_mode="", fee_rate=fee_rate_sat_vb)
|
||||
tx_size = self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])
|
||||
self.sync_all(self.nodes[0:3])
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all(self.nodes[0:3])
|
||||
postbalance = self.nodes[2].getbalance()
|
||||
fee = prebalance - postbalance - Decimal('1')
|
||||
assert_fee_amount(fee, tx_size, Decimal('0.00002500'))
|
||||
fee = prebalance - postbalance - Decimal(amount)
|
||||
assert_fee_amount(fee, tx_size, Decimal(fee_rate_btc_kvb))
|
||||
|
||||
self.sync_all(self.nodes[0:3])
|
||||
|
||||
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 SAT/b requires a fee rate to be specified in conf_target",
|
||||
self.nodes[2].sendtoaddress,
|
||||
address=address,
|
||||
amount=1.0,
|
||||
estimate_mode='SAT/b')
|
||||
# Throw if negative feerate
|
||||
assert_raises_rpc_error(-3, "Amount out of range",
|
||||
self.nodes[2].sendtoaddress,
|
||||
address=address,
|
||||
amount=1.0,
|
||||
conf_target=-1,
|
||||
estimate_mode='SAT/b')
|
||||
txid = self.nodes[2].sendtoaddress(
|
||||
address=address,
|
||||
amount=1.0,
|
||||
conf_target=2,
|
||||
estimate_mode='SAT/B',
|
||||
)
|
||||
tx_size = self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])
|
||||
self.sync_all(self.nodes[0:3])
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all(self.nodes[0:3])
|
||||
postbalance = self.nodes[2].getbalance()
|
||||
fee = prebalance - postbalance - Decimal('1')
|
||||
assert_fee_amount(fee, tx_size, Decimal('0.00002000'))
|
||||
for key in ["totalFee", "feeRate"]:
|
||||
assert_raises_rpc_error(-8, "Unknown named parameter key", self.nodes[2].sendtoaddress, address=address, amount=1, fee_rate=1, key=1)
|
||||
|
||||
# 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)
|
||||
self.log.info("Test sendtoaddress raises 'fee rate too low' if fee_rate of 0.99999999 is passed")
|
||||
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, fee_rate=0.99999999)
|
||||
|
||||
self.log.info("Test sendtoaddress raises if fee_rate of 0 or -1 is passed")
|
||||
assert_raises_rpc_error(-6, "Fee rate (0.00000000 BTC/kB) is lower than the minimum fee rate setting (0.00001000 BTC/kB)",
|
||||
self.nodes[2].sendtoaddress, address=address, amount=10, fee_rate=0)
|
||||
assert_raises_rpc_error(-3, OUT_OF_RANGE, self.nodes[2].sendtoaddress, address=address, amount=1.0, fee_rate=-1)
|
||||
|
||||
self.log.info("Test sendtoaddress raises if an invalid conf_target or estimate_mode is passed")
|
||||
for target, mode in product([-1, 0, 1009], ["economical", "conservative"]):
|
||||
assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
|
||||
self.nodes[2].sendtoaddress, address=address, amount=1, conf_target=target, estimate_mode=mode)
|
||||
for target, mode in product([-1, 0], ["btc/kb", "sat/b"]):
|
||||
assert_raises_rpc_error(-8, "Invalid estimate_mode parameter",
|
||||
self.nodes[2].sendtoaddress, address=address, amount=1, conf_target=target, estimate_mode=mode)
|
||||
|
||||
# 2. Import address from node2 to node1
|
||||
self.nodes[1].importaddress(address_to_import)
|
||||
@@ -549,7 +493,7 @@ class WalletTest(BitcoinTestFramework):
|
||||
]
|
||||
chainlimit = 6
|
||||
for m in maintenance:
|
||||
self.log.info("check " + m)
|
||||
self.log.info("Test " + m)
|
||||
self.stop_nodes()
|
||||
# set lower ancestor limit for later
|
||||
self.start_node(0, [m, "-limitancestorcount=" + str(chainlimit)])
|
||||
|
||||
Reference in New Issue
Block a user