mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-20 10:38:42 +01:00
Merge #20220: wallet, rpc: explicit fee rate follow-ups/fixes for 0.21
0be29000c0
rpc: update conf_target helps for correctness/consistency (Jon Atack)778b9be406
wallet, rpc: fix send subtract_fee_from_outputs help (Jon Atack)603c005083
wallet: add rpc send explicit fee rate coverage (Jon Atack)dd341e602d
wallet: add sendtoaddress/sendmany explicit fee rate coverage (Jon Atack)44e7bfa603
wallet: add walletcreatefundedpsbt explicit fee rate coverage (Jon Atack)6e1ea4273e
test: refactor for walletcreatefundedpsbt fee rate coverage (Jon Atack)3ac7b0c6f1
wallet: fundrawtx fee rate coverage, fixup ParseConfirmTarget() (Jon Atack)2d8eba8f84
wallet: combine redundant bumpfee invalid params and args tests (Jon Atack)1697a40b6f
wallet: improve bumpfee error/help, add explicit fee rate coverage (Jon Atack)fc5721723d
wallet: fix SetFeeEstimateMode() error message (Jon Atack)052427eef1
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 ACK0be29000c0
meshcollider: Code review + functional test run ACK0be29000c0
Tree-SHA512: efd965003e991cba51d4504e2940f06ab3d742e34022e96a673606b44fad85596aa03a8c1809f06df7ebcf21a38e18a891e54392fe3d6fb4d120bbe4ea0cf5e0
This commit is contained in:
commit
5d32009f1a
@ -272,11 +272,12 @@ UniValue DescribeAddress(const CTxDestination& dest)
|
||||
|
||||
unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target)
|
||||
{
|
||||
int target = value.get_int();
|
||||
if (target < 1 || (unsigned int)target > max_target) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid conf_target, must be between %u - %u", 1, max_target));
|
||||
const int target{value.get_int()};
|
||||
const unsigned int unsigned_target{static_cast<unsigned int>(target)};
|
||||
if (target < 1 || unsigned_target > max_target) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid conf_target, must be between %u and %u", 1, max_target));
|
||||
}
|
||||
return (unsigned int)target;
|
||||
return unsigned_target;
|
||||
}
|
||||
|
||||
RPCErrorCode RPCErrorFromTransactionError(TransactionError terr)
|
||||
|
@ -214,7 +214,7 @@ static void SetFeeEstimateMode(const CWallet* pwallet, CCoinControl& cc, const U
|
||||
|
||||
if (cc.m_fee_mode == FeeEstimateMode::BTC_KB || cc.m_fee_mode == FeeEstimateMode::SAT_B) {
|
||||
if (estimate_param.isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Selected estimate_mode requires a fee rate");
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Selected estimate_mode %s requires a fee rate to be specified in conf_target", estimate_mode.get_str()));
|
||||
}
|
||||
|
||||
CAmount fee_rate = AmountFromValue(estimate_param);
|
||||
@ -440,7 +440,8 @@ static RPCHelpMan sendtoaddress()
|
||||
{"subtractfeefromamount", RPCArg::Type::BOOL, /* default */ "false", "The fee will be deducted from the amount being sent.\n"
|
||||
"The recipient will receive less bitcoins than you enter in the amount field."},
|
||||
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||
" \"" + FeeModes("\"\n\"") + "\""},
|
||||
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "true", "(only available if avoid_reuse wallet flag is set) Avoid spending from dirty addresses; addresses are considered\n"
|
||||
@ -868,7 +869,8 @@ static RPCHelpMan sendmany()
|
||||
},
|
||||
},
|
||||
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||
" \"" + FeeModes("\"\n\"") + "\""},
|
||||
{"verbose", RPCArg::Type::BOOL, /* default */ "false", "If true, return extra infomration about the transaction."},
|
||||
@ -3044,7 +3046,7 @@ static RPCHelpMan listunspent()
|
||||
};
|
||||
}
|
||||
|
||||
void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, UniValue options, CCoinControl& coinControl)
|
||||
void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, const UniValue& options, CCoinControl& coinControl)
|
||||
{
|
||||
// Make sure the results are valid at least up to the most recent block
|
||||
// the user could have gotten from another RPC command prior to now
|
||||
@ -3210,7 +3212,8 @@ static RPCHelpMan fundrawtransaction()
|
||||
},
|
||||
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n"
|
||||
"Allows this transaction to be replaced by a transaction with higher fees"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||
" \"" + FeeModes("\"\n\"") + "\""},
|
||||
},
|
||||
@ -3378,7 +3381,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
|
||||
"The command will pay the additional fee by reducing change outputs or adding inputs when necessary. It may add a new change output if one does not already exist.\n"
|
||||
"All inputs in the original transaction will be included in the replacement transaction.\n"
|
||||
"The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n"
|
||||
"By default, the new fee will be calculated automatically using estimatesmartfee.\n"
|
||||
"By default, the new fee will be calculated automatically using the estimatesmartfee RPC.\n"
|
||||
"The user can specify a confirmation target for estimatesmartfee.\n"
|
||||
"Alternatively, the user can specify a fee_rate (" + CURRENCY_UNIT + " per kB) for the new transaction.\n"
|
||||
"At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n"
|
||||
@ -3387,10 +3390,11 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
|
||||
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The txid to be bumped"},
|
||||
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
|
||||
{
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
|
||||
{"fee_rate", RPCArg::Type::NUM, /* default */ "fall back to 'conf_target'", "fee rate (NOT total fee) to pay, in " + CURRENCY_UNIT + " per kB\n"
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"fee_rate", RPCArg::Type::NUM, /* default */ "fall back to 'conf_target'", "fee rate (NOT total fee) to pay, in " + CURRENCY_UNIT + "/kB.\n"
|
||||
"Specify a fee rate instead of relying on the built-in fee estimator.\n"
|
||||
"Must be at least 0.0001 " + CURRENCY_UNIT + " per kB higher than the current transaction fee rate.\n"},
|
||||
"Must be at least 0.0001 " + CURRENCY_UNIT + "/kB higher than the current transaction fee rate.\n"},
|
||||
{"replaceable", RPCArg::Type::BOOL, /* default */ "true", "Whether the new transaction should still be\n"
|
||||
"marked bip-125 replaceable. If true, the sequence numbers in the transaction will\n"
|
||||
"be left unchanged from the original. If false, any input sequence numbers in the\n"
|
||||
@ -3464,9 +3468,8 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
|
||||
|
||||
if (!conf_target.isNull()) {
|
||||
if (options.exists("fee_rate")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "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.");
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "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.");
|
||||
}
|
||||
coin_control.m_confirm_target = ParseConfirmTarget(conf_target, pwallet->chain().estimateMaxBlocks());
|
||||
} else if (options.exists("fee_rate")) {
|
||||
CFeeRate fee_rate(AmountFromValue(options["fee_rate"]));
|
||||
if (fee_rate <= CFeeRate(0)) {
|
||||
@ -4014,7 +4017,8 @@ static RPCHelpMan send()
|
||||
},
|
||||
},
|
||||
},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||
" \"" + FeeModes("\"\n\"") + "\""},
|
||||
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
|
||||
@ -4024,7 +4028,8 @@ static RPCHelpMan send()
|
||||
{"change_address", RPCArg::Type::STR_HEX, /* default */ "pool address", "The bitcoin address to receive the change"},
|
||||
{"change_position", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"},
|
||||
{"change_type", RPCArg::Type::STR, /* default */ "set by -changetype", "The output type to use. Only valid if change_address is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||
" \"" + FeeModes("\"\n\"") + "\""},
|
||||
{"include_watching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only.\n"
|
||||
@ -4040,7 +4045,7 @@ static RPCHelpMan send()
|
||||
{"locktime", RPCArg::Type::NUM, /* default */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"},
|
||||
{"lock_unspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
|
||||
{"psbt", RPCArg::Type::BOOL, /* default */ "automatic", "Always return a PSBT, implies add_to_wallet=false."},
|
||||
{"subtract_fee_from_outputs", RPCArg::Type::ARR, /* default */ "empty array", "A JSON array of integers.\n"
|
||||
{"subtract_fee_from_outputs", RPCArg::Type::ARR, /* default */ "empty array", "Outputs to subtract the fee from, specified as integer indices.\n"
|
||||
"The fee will be equally deducted from the amount of each specified output.\n"
|
||||
"Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
|
||||
"If no outputs are specified here, the sender pays the fee.",
|
||||
@ -4121,7 +4126,7 @@ static RPCHelpMan send()
|
||||
CMutableTransaction rawTx = ConstructTransaction(options["inputs"], request.params[0], options["locktime"], rbf);
|
||||
CCoinControl coin_control;
|
||||
// Automatically select coins, unless at least one is manually selected. Can
|
||||
// be overriden by options.add_inputs.
|
||||
// be overridden by options.add_inputs.
|
||||
coin_control.m_add_inputs = rawTx.vin.size() == 0;
|
||||
FundTransaction(pwallet, rawTx, fee, change_position, options, coin_control);
|
||||
|
||||
@ -4362,7 +4367,8 @@ static RPCHelpMan walletcreatefundedpsbt()
|
||||
},
|
||||
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n"
|
||||
"Allows this transaction to be replaced by a transaction with higher fees"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "fall back to wallet's confirmation target (txconfirmtarget)", "Confirmation target (in blocks)"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||
" \"" + FeeModes("\"\n\"") + "\""},
|
||||
},
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
# 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})
|
||||
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
|
||||
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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
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="economical", add_to_wallet=False, expect_error=(-8,"Use either conf_target and estimate_mode or the options dictionary to control fee rate"))
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user