Skip to content

Commit 816132e

Browse files
author
MarcoFalke
committed
Merge #20426: wallet: allow zero-fee fundrawtransaction/walletcreatefundedpsbt and other fixes
9f08780 Use the correct incremental fee constant in bumpfee help (Jon Atack) 3f1e10b Update feeRate (BTC/kvB) to fee_rate (sat/vB) in wallet_bumpfee (Jon Atack) 1b3d700 Allow zero-fee fundrawtxn and walletcreatefundedpsbt calls (Jon Atack) Pull request description: - Fixes https://github.com/bitcoin/bitcoin/pull/20305/files#r525406176. A check to raise an error on zero-fee txns was mistakenly extended in a0d4957 from the bumpfee and send{toaddress, many} RPCs to also include fundrawtransaction and walletcreatefundedpsbt. This commit re-overrides zero fee rate checking for these two RPCs, not only for the feeRate (BTC/kvB) arg to return to previous behavior, but also for the new fee_rate (sat/vB) arg. Negative fee rates will still raise "amount out of range" by the MoneyRange check in src/bitcoin-tx.cpp::AmountFromValue. - Updates a wallet bumpfee test from feeRate (BTC/kvB) to fee_rate (sat/vB) - Fixes https://github.com/bitcoin/bitcoin/pull/20305/files#r525405363 to use the correct incremental fee rate constant in the bumpfee help (thanks Marco Falke for the catch) and rectifies "1.000 sat/vB sat/vB" in the help to "1.000 sat/vB" ACKs for top commit: MarcoFalke: review ACK 9f08780 🐾 promag: Code review ACK 9f08780. Xekyo: Code review reACK 9f08780. Tree-SHA512: 413dfb4f23ebaf3d2ef210dd04610a843272e64eabba428699f5de4d646a86ac4911dab66b5e2f5ebea53b76e4be8347ef40824c1592c750d5eaa12579d3cdf6
2 parents d415998 + 9f08780 commit 816132e

File tree

5 files changed

+32
-36
lines changed

5 files changed

+32
-36
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
#include <util/translation.h>
3030
#include <util/url.h>
3131
#include <util/vector.h>
32-
#include <validation.h>
3332
#include <wallet/coincontrol.h>
3433
#include <wallet/context.h>
3534
#include <wallet/feebumper.h>
@@ -219,14 +218,8 @@ static void SetFeeEstimateMode(const CWallet* pwallet, CCoinControl& cc, const U
219218
if (!estimate_mode.isNull() && !estimate_mode.get_str().empty()) {
220219
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and fee_rate");
221220
}
222-
CFeeRate fee_rate_in_sat_vb{CFeeRate(AmountFromValue(fee_rate), COIN)};
223-
if (override_min_fee) {
224-
if (fee_rate_in_sat_vb <= CFeeRate(0)) {
225-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid fee_rate %s (must be greater than 0)", fee_rate_in_sat_vb.ToString(FeeEstimateMode::SAT_VB)));
226-
}
227-
cc.fOverrideFeeRate = true;
228-
}
229-
cc.m_feerate = fee_rate_in_sat_vb;
221+
cc.m_feerate = CFeeRate(AmountFromValue(fee_rate), COIN);
222+
if (override_min_fee) cc.fOverrideFeeRate = true;
230223
// Default RBF to true for explicit fee_rate, if unset.
231224
if (cc.m_signal_bip125_rbf == nullopt) cc.m_signal_bip125_rbf = true;
232225
return;
@@ -3150,11 +3143,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
31503143
if (options.exists("estimate_mode")) {
31513144
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
31523145
}
3153-
CFeeRate fee_rate(AmountFromValue(options["feeRate"]));
3154-
if (fee_rate <= CFeeRate(0)) {
3155-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid feeRate %s (must be greater than 0)", fee_rate.ToString(FeeEstimateMode::BTC_KVB)));
3156-
}
3157-
coinControl.m_feerate = fee_rate;
3146+
coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"]));
31583147
coinControl.fOverrideFeeRate = true;
31593148
}
31603149

@@ -3393,7 +3382,7 @@ RPCHelpMan signrawtransactionwithwallet()
33933382
static RPCHelpMan bumpfee_helper(std::string method_name)
33943383
{
33953384
bool want_psbt = method_name == "psbtbumpfee";
3396-
const std::string incremental_fee{CFeeRate(DEFAULT_MIN_RELAY_TX_FEE).ToString(FeeEstimateMode::SAT_VB)};
3385+
const std::string incremental_fee{CFeeRate(DEFAULT_INCREMENTAL_RELAY_FEE).ToString(FeeEstimateMode::SAT_VB)};
33973386

33983387
return RPCHelpMan{method_name,
33993388
"\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n"
@@ -3416,7 +3405,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
34163405
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks\n"},
34173406
{"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation",
34183407
"\nSpecify a fee rate in " + CURRENCY_ATOM + "/vB instead of relying on the built-in fee estimator.\n"
3419-
"Must be at least " + incremental_fee + " " + CURRENCY_ATOM + "/vB higher than the current transaction fee rate.\n"
3408+
"Must be at least " + incremental_fee + " higher than the current transaction fee rate.\n"
34203409
"WARNING: before version 0.21, fee_rate was in " + CURRENCY_UNIT + "/kvB. As of 0.21, fee_rate is in " + CURRENCY_ATOM + "/vB.\n"},
34213410
{"replaceable", RPCArg::Type::BOOL, /* default */ "true", "Whether the new transaction should still be\n"
34223411
"marked bip-125 replaceable. If true, the sequence numbers in the transaction will\n"

test/functional/rpc_fundrawtransaction.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -721,11 +721,17 @@ def test_option_feerate(self):
721721
result2 = node.fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee})
722722
result3 = node.fundrawtransaction(rawtx, {"fee_rate": 10 * btc_kvb_to_sat_vb * self.min_relay_tx_fee})
723723
result4 = node.fundrawtransaction(rawtx, {"feeRate": 10 * self.min_relay_tx_fee})
724+
# Test that funding non-standard "zero-fee" transactions is valid.
725+
result5 = self.nodes[3].fundrawtransaction(rawtx, {"fee_rate": 0})
726+
result6 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 0})
727+
724728
result_fee_rate = result['fee'] * 1000 / count_bytes(result['hex'])
725729
assert_fee_amount(result1['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
726730
assert_fee_amount(result2['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
727731
assert_fee_amount(result3['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
728732
assert_fee_amount(result4['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
733+
assert_fee_amount(result5['fee'], count_bytes(result5['hex']), 0)
734+
assert_fee_amount(result6['fee'], count_bytes(result6['hex']), 0)
729735

730736
# With no arguments passed, expect fee of 141 satoshis.
731737
assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000141, vspan=0.00000001)
@@ -752,19 +758,15 @@ def test_option_feerate(self):
752758
node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": n, "add_inputs": True})
753759

754760
self.log.info("Test invalid fee rate settings")
755-
assert_raises_rpc_error(-8, "Invalid fee_rate 0.000 sat/vB (must be greater than 0)",
756-
node.fundrawtransaction, rawtx, {"fee_rate": 0, "add_inputs": True})
757-
assert_raises_rpc_error(-8, "Invalid feeRate 0.00000000 BTC/kvB (must be greater than 0)",
758-
node.fundrawtransaction, rawtx, {"feeRate": 0, "add_inputs": True})
759761
for param, value in {("fee_rate", 100000), ("feeRate", 1.000)}:
760762
assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
761763
node.fundrawtransaction, rawtx, {param: value, "add_inputs": True})
762764
assert_raises_rpc_error(-3, "Amount out of range",
763-
node.fundrawtransaction, rawtx, {"fee_rate": -1, "add_inputs": True})
765+
node.fundrawtransaction, rawtx, {param: -1, "add_inputs": True})
764766
assert_raises_rpc_error(-3, "Amount is not a number or string",
765-
node.fundrawtransaction, rawtx, {"fee_rate": {"foo": "bar"}, "add_inputs": True})
767+
node.fundrawtransaction, rawtx, {param: {"foo": "bar"}, "add_inputs": True})
766768
assert_raises_rpc_error(-3, "Invalid amount",
767-
node.fundrawtransaction, rawtx, {"fee_rate": "", "add_inputs": True})
769+
node.fundrawtransaction, rawtx, {param: "", "add_inputs": True})
768770

769771
self.log.info("Test min fee rate checks are bypassed with fundrawtxn, e.g. a fee_rate under 1 sat/vB is allowed")
770772
node.fundrawtransaction(rawtx, {"fee_rate": 0.99999999, "add_inputs": True})

test/functional/rpc_psbt.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -192,26 +192,27 @@ def run_test(self):
192192
assert_approx(res1["fee"], 0.055, 0.005)
193193
res2 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.1, "add_inputs": True})
194194
assert_approx(res2["fee"], 0.055, 0.005)
195+
195196
self.log.info("Test min fee rate checks with walletcreatefundedpsbt are bypassed, e.g. a fee_rate under 1 sat/vB is allowed")
196197
res3 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"fee_rate": 0.99999999, "add_inputs": True})
197198
assert_approx(res3["fee"], 0.00000381, 0.0000001)
198199
res4 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.00000999, "add_inputs": True})
199200
assert_approx(res4["fee"], 0.00000381, 0.0000001)
200201

202+
self.log.info("Test min fee rate checks with walletcreatefundedpsbt are bypassed and that funding non-standard 'zero-fee' transactions is valid")
203+
for param in ["fee_rate", "feeRate"]:
204+
assert_equal(self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {param: 0, "add_inputs": True})["fee"], 0)
205+
201206
self.log.info("Test invalid fee rate settings")
202-
assert_raises_rpc_error(-8, "Invalid fee_rate 0.000 sat/vB (must be greater than 0)",
203-
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": 0, "add_inputs": True})
204-
assert_raises_rpc_error(-8, "Invalid feeRate 0.00000000 BTC/kvB (must be greater than 0)",
205-
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"feeRate": 0, "add_inputs": True})
206207
for param, value in {("fee_rate", 100000), ("feeRate", 1)}:
207208
assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
208209
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: value, "add_inputs": True})
209210
assert_raises_rpc_error(-3, "Amount out of range",
210-
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": -1, "add_inputs": True})
211+
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: -1, "add_inputs": True})
211212
assert_raises_rpc_error(-3, "Amount is not a number or string",
212-
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": {"foo": "bar"}, "add_inputs": True})
213+
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: {"foo": "bar"}, "add_inputs": True})
213214
assert_raises_rpc_error(-3, "Invalid amount",
214-
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": "", "add_inputs": True})
215+
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: "", "add_inputs": True})
215216

216217
self.log.info("- raises RPC error if both feeRate and fee_rate are passed")
217218
assert_raises_rpc_error(-8, "Cannot specify both fee_rate (sat/vB) and feeRate (BTC/kvB)",

test/functional/wallet_bumpfee.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,10 @@ def test_invalid_parameters(self, rbf_node, peer_node, dest_address):
107107
assert_raises_rpc_error(-3, "Unexpected key {}".format(key), rbf_node.bumpfee, rbfid, {key: NORMAL})
108108

109109
# Bumping to just above minrelay should fail to increase the total fee enough.
110-
assert_raises_rpc_error(-8, "Insufficient total fee 0.00000141, must be at least 0.00001704 (oldFee 0.00000999 + incrementalFee 0.00000705)",
111-
rbf_node.bumpfee, rbfid, {"fee_rate": INSUFFICIENT})
110+
assert_raises_rpc_error(-8, "Insufficient total fee 0.00000141", rbf_node.bumpfee, rbfid, {"fee_rate": INSUFFICIENT})
112111

113112
self.log.info("Test invalid fee rate settings")
114-
assert_raises_rpc_error(-8, "Insufficient total fee 0.00, must be at least 0.00001704 (oldFee 0.00000999 + incrementalFee 0.00000705)",
115-
rbf_node.bumpfee, rbfid, {"fee_rate": 0})
113+
assert_raises_rpc_error(-8, "Insufficient total fee 0.00", rbf_node.bumpfee, rbfid, {"fee_rate": 0})
116114
assert_raises_rpc_error(-4, "Specified or calculated fee 0.141 is too high (cannot be higher than -maxtxfee 0.10",
117115
rbf_node.bumpfee, rbfid, {"fee_rate": TOO_HIGH})
118116
assert_raises_rpc_error(-3, "Amount out of range", rbf_node.bumpfee, rbfid, {"fee_rate": -1})
@@ -421,7 +419,7 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
421419
self.sync_all()
422420

423421
# Create single-input PSBT for transaction to be bumped
424-
psbt = watcher.walletcreatefundedpsbt([], {dest_address: 0.0005}, 0, {"feeRate": 0.00001}, True)['psbt']
422+
psbt = watcher.walletcreatefundedpsbt([], {dest_address: 0.0005}, 0, {"fee_rate": 1}, True)['psbt']
425423
psbt_signed = signer.walletprocesspsbt(psbt=psbt, sign=True, sighashtype="ALL", bip32derivs=True)
426424
psbt_final = watcher.finalizepsbt(psbt_signed["psbt"])
427425
original_txid = watcher.sendrawtransaction(psbt_final["hex"])

test/functional/wallet_send.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,16 @@ def run_test(self):
303303
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=v, estimate_mode=mode,
304304
expect_error=(-3, "Expected type number for conf_target, got {}".format(k)))
305305

306-
# Test setting explicit fee rate just below the minimum.
306+
# Test setting explicit fee rate just below the minimum and at zero.
307307
self.log.info("Explicit fee rate raises RPC error 'fee rate too low' if fee_rate of 0.99999999 is passed")
308308
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=0.99999999,
309309
expect_error=(-4, "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"))
310+
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=0.99999999,
311+
expect_error=(-4, "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"))
312+
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=0,
313+
expect_error=(-4, "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"))
314+
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=0,
315+
expect_error=(-4, "Fee rate (0.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"))
310316

311317
# TODO: Return hex if fee rate is below -maxmempool
312318
# res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode="sat/b", add_to_wallet=False)

0 commit comments

Comments
 (0)