mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-04 13:55:23 -05:00
Merge #20305: wallet: introduce fee_rate sat/vB param/option
05e82d86b0
wallet: override minfee checks (fOverrideFeeRate) for fee_rate (Jon Atack)9a670b4f07
wallet: update sendtoaddress, send RPC examples with fee_rate (Jon Atack)be481b72e2
wallet: use MIN_RELAY_TX_FEE in bumpfee help (Jon Atack)449b730579
wallet: provide valid values if invalid estimate mode passed (Jon Atack)6da3afbaee
wallet: update remaining rpcwallet fee rate units to BTC/kvB (Jon Atack)173b5b5fe0
wallet: update fee rate units, use sat/vB for fee_rate error messages (Jon Atack)7f9835a05a
wallet: remove fee rates from conf_target helps (Jon Atack)b7994c01e9
wallet: add fee_rate unit warnings to bumpfee (Jon Atack)410e471fa4
wallet: remove redundant bumpfee fee_rate checks (Jon Atack)a0d4957473
wallet: introduce fee_rate (sat/vB) param/option (Jon Atack)e21212f01b
wallet: remove unneeded WALLET_BTC_KB_TO_SAT_B constant (Jon Atack)6112cf20d4
wallet: add CFeeRate ctor doxygen documentation (Jon Atack)3f72791613
wallet: fix bug in RPC send options (Jon Atack) Pull request description: This PR builds on #11413 and #20220 to address #19543. - replace overloading the conf_target and estimate_mode params with `fee_rate` in sat/vB in the sendtoaddress, sendmany, send, fundrawtransaction, walletcreatefundedpsbt, and bumpfee RPCs - allow non-actionable conf_target value of `0` and estimate_mode value of `""` to be passed to use `fee_rate` as a positional argument, in addition to as a named argument - fix a bug in the experimental send RPC described in https://github.com/bitcoin/bitcoin/pull/20220#discussion_r513789526 where args were not being passed correctly into the options values - update the feerate error message units for these RPCs from BTC/kB to sat/vB - update the test coverage, help docs, doxygen docs, and some of the RPC examples - other changes to address the excellent review feedback See this wallet meeting log for more context: http://www.erisian.com.au/bitcoin-core-dev/log-2020-11-06.html#l-309 ACKs for top commit: achow101: re-ACK05e82d8
MarcoFalke: review ACK05e82d86b0
did not test and found a few style nits, which can be fixed later 🍯 Xekyo: tACK05e82d86b0
Sjors: utACK05e82d86b0
Tree-SHA512: a4ee5f184ada53f1840b2923d25873bda88c5a2ae48e67eeea2417a0b35154798cfdb3c147b05dd56bd6608a784e1b91623bb985ee2ab9ef2baaec22206d0a9c
This commit is contained in:
commit
80e32e120e
15 changed files with 438 additions and 417 deletions
|
@ -38,7 +38,7 @@ CAmount CFeeRate::GetFee(size_t nBytes_) const
|
|||
std::string CFeeRate::ToString(const FeeEstimateMode& fee_estimate_mode) const
|
||||
{
|
||||
switch (fee_estimate_mode) {
|
||||
case FeeEstimateMode::SAT_B: return strprintf("%d.%03d %s/B", nSatoshisPerK / 1000, nSatoshisPerK % 1000, CURRENCY_ATOM);
|
||||
default: return strprintf("%d.%08d %s/kB", nSatoshisPerK / COIN, nSatoshisPerK % COIN, CURRENCY_UNIT);
|
||||
case FeeEstimateMode::SAT_VB: return strprintf("%d.%03d %s/vB", nSatoshisPerK / 1000, nSatoshisPerK % 1000, CURRENCY_ATOM);
|
||||
default: return strprintf("%d.%08d %s/kvB", nSatoshisPerK / COIN, nSatoshisPerK % COIN, CURRENCY_UNIT);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,8 @@ enum class FeeEstimateMode {
|
|||
UNSET, //!< Use default settings based on other criteria
|
||||
ECONOMICAL, //!< Force estimateSmartFee to use non-conservative estimates
|
||||
CONSERVATIVE, //!< Force estimateSmartFee to use conservative estimates
|
||||
BTC_KB, //!< Use explicit BTC/kB fee given in coin control
|
||||
SAT_B, //!< Use explicit sat/B fee given in coin control
|
||||
BTC_KVB, //!< Use BTC/kvB fee rate unit
|
||||
SAT_VB, //!< Use sat/vB fee rate unit
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -39,7 +39,16 @@ public:
|
|||
// We've previously had bugs creep in from silent double->int conversion...
|
||||
static_assert(std::is_integral<I>::value, "CFeeRate should be used without floats");
|
||||
}
|
||||
/** Constructor for a fee rate in satoshis per kB. The size in bytes must not exceed (2^63 - 1)*/
|
||||
/** Constructor for a fee rate in satoshis per kvB (sat/kvB). The size in bytes must not exceed (2^63 - 1).
|
||||
*
|
||||
* Passing an nBytes value of COIN (1e8) returns a fee rate in satoshis per vB (sat/vB),
|
||||
* e.g. (nFeePaid * 1e8 / 1e3) == (nFeePaid / 1e5),
|
||||
* where 1e5 is the ratio to convert from BTC/kvB to sat/vB.
|
||||
*
|
||||
* @param[in] nFeePaid CAmount fee rate to construct with
|
||||
* @param[in] nBytes size_t bytes (units) to construct with
|
||||
* @returns fee rate
|
||||
*/
|
||||
CFeeRate(const CAmount& nFeePaid, size_t nBytes);
|
||||
/**
|
||||
* Return the fee in satoshis for the given size in bytes.
|
||||
|
@ -56,7 +65,7 @@ public:
|
|||
friend bool operator>=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK >= b.nSatoshisPerK; }
|
||||
friend bool operator!=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK != b.nSatoshisPerK; }
|
||||
CFeeRate& operator+=(const CFeeRate& a) { nSatoshisPerK += a.nSatoshisPerK; return *this; }
|
||||
std::string ToString(const FeeEstimateMode& fee_estimate_mode = FeeEstimateMode::BTC_KB) const;
|
||||
std::string ToString(const FeeEstimateMode& fee_estimate_mode = FeeEstimateMode::BTC_KVB) const;
|
||||
|
||||
SERIALIZE_METHODS(CFeeRate, obj) { READWRITE(obj.nSatoshisPerK); }
|
||||
};
|
||||
|
|
|
@ -41,7 +41,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "sendtoaddress", 5 , "replaceable" },
|
||||
{ "sendtoaddress", 6 , "conf_target" },
|
||||
{ "sendtoaddress", 8, "avoid_reuse" },
|
||||
{ "sendtoaddress", 9, "verbose"},
|
||||
{ "sendtoaddress", 9, "fee_rate"},
|
||||
{ "sendtoaddress", 10, "verbose"},
|
||||
{ "settxfee", 0, "amount" },
|
||||
{ "sethdseed", 0, "newkeypool" },
|
||||
{ "getreceivedbyaddress", 1, "minconf" },
|
||||
|
@ -73,7 +74,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "sendmany", 4, "subtractfeefrom" },
|
||||
{ "sendmany", 5 , "replaceable" },
|
||||
{ "sendmany", 6 , "conf_target" },
|
||||
{ "sendmany", 8, "verbose" },
|
||||
{ "sendmany", 8, "fee_rate"},
|
||||
{ "sendmany", 9, "verbose" },
|
||||
{ "deriveaddresses", 1, "range" },
|
||||
{ "scantxoutset", 1, "scanobjects" },
|
||||
{ "addmultisigaddress", 0, "nrequired" },
|
||||
|
@ -129,7 +131,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "lockunspent", 1, "transactions" },
|
||||
{ "send", 0, "outputs" },
|
||||
{ "send", 1, "conf_target" },
|
||||
{ "send", 3, "options" },
|
||||
{ "send", 3, "fee_rate"},
|
||||
{ "send", 4, "options" },
|
||||
{ "importprivkey", 2, "rescan" },
|
||||
{ "importaddress", 2, "rescan" },
|
||||
{ "importaddress", 3, "p2sh" },
|
||||
|
|
|
@ -1070,7 +1070,7 @@ static RPCHelpMan estimatesmartfee()
|
|||
if (!request.params[1].isNull()) {
|
||||
FeeEstimateMode fee_mode;
|
||||
if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
|
||||
}
|
||||
if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false;
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ BOOST_AUTO_TEST_CASE(BinaryOperatorTest)
|
|||
BOOST_CHECK(a <= a);
|
||||
BOOST_CHECK(b >= a);
|
||||
BOOST_CHECK(b >= b);
|
||||
// a should be 0.00000002 BTC/kB now
|
||||
// a should be 0.00000002 BTC/kvB now
|
||||
a += a;
|
||||
BOOST_CHECK(a == b);
|
||||
}
|
||||
|
@ -107,7 +107,9 @@ BOOST_AUTO_TEST_CASE(ToStringTest)
|
|||
{
|
||||
CFeeRate feeRate;
|
||||
feeRate = CFeeRate(1);
|
||||
BOOST_CHECK_EQUAL(feeRate.ToString(), "0.00000001 BTC/kB");
|
||||
BOOST_CHECK_EQUAL(feeRate.ToString(), "0.00000001 BTC/kvB");
|
||||
BOOST_CHECK_EQUAL(feeRate.ToString(FeeEstimateMode::BTC_KVB), "0.00000001 BTC/kvB");
|
||||
BOOST_CHECK_EQUAL(feeRate.ToString(FeeEstimateMode::SAT_VB), "0.001 sat/vB");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
@ -40,8 +40,6 @@ const std::vector<std::pair<std::string, FeeEstimateMode>>& FeeModeMap()
|
|||
{"unset", FeeEstimateMode::UNSET},
|
||||
{"economical", FeeEstimateMode::ECONOMICAL},
|
||||
{"conservative", FeeEstimateMode::CONSERVATIVE},
|
||||
{(CURRENCY_UNIT + "/kB"), FeeEstimateMode::BTC_KB},
|
||||
{(CURRENCY_ATOM + "/B"), FeeEstimateMode::SAT_B},
|
||||
};
|
||||
return FEE_MODES;
|
||||
}
|
||||
|
@ -51,6 +49,11 @@ std::string FeeModes(const std::string& delimiter)
|
|||
return Join(FeeModeMap(), delimiter, [&](const std::pair<std::string, FeeEstimateMode>& i) { return i.first; });
|
||||
}
|
||||
|
||||
const std::string InvalidEstimateModeErrorMessage()
|
||||
{
|
||||
return "Invalid estimate_mode parameter, must be one of: \"" + FeeModes("\", \"") + "\"";
|
||||
}
|
||||
|
||||
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode)
|
||||
{
|
||||
auto searchkey = ToUpper(mode_string);
|
||||
|
|
|
@ -13,5 +13,6 @@ enum class FeeReason;
|
|||
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode);
|
||||
std::string StringForFeeReason(FeeReason reason);
|
||||
std::string FeeModes(const std::string& delimiter);
|
||||
const std::string InvalidEstimateModeErrorMessage();
|
||||
|
||||
#endif // BITCOIN_UTIL_FEES_H
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <util/translation.h>
|
||||
#include <util/url.h>
|
||||
#include <util/vector.h>
|
||||
#include <validation.h>
|
||||
#include <wallet/coincontrol.h>
|
||||
#include <wallet/context.h>
|
||||
#include <wallet/feebumper.h>
|
||||
|
@ -48,8 +49,6 @@ using interfaces::FoundBlock;
|
|||
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
|
||||
static const std::string HELP_REQUIRING_PASSPHRASE{"\nRequires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.\n"};
|
||||
|
||||
static const uint32_t WALLET_BTC_KB_TO_SAT_B = COIN / 1000; // 1 sat / B = 0.00001 BTC / kB
|
||||
|
||||
static inline bool GetAvoidReuseFlag(const CWallet* const pwallet, const UniValue& param) {
|
||||
bool can_avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
|
||||
bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
|
||||
|
@ -199,36 +198,44 @@ static std::string LabelFromValue(const UniValue& value)
|
|||
/**
|
||||
* Update coin control with fee estimation based on the given parameters
|
||||
*
|
||||
* @param[in] pwallet Wallet pointer
|
||||
* @param[in,out] cc Coin control which is to be updated
|
||||
* @param[in] estimate_mode String value (e.g. "ECONOMICAL")
|
||||
* @param[in] estimate_param Parameter (blocks to confirm, explicit fee rate, etc)
|
||||
* @throws a JSONRPCError if estimate_mode is unknown, or if estimate_param is missing when required
|
||||
* @param[in] pwallet Wallet pointer
|
||||
* @param[in,out] cc Coin control to be updated
|
||||
* @param[in] conf_target UniValue integer; confirmation target in blocks, values between 1 and 1008 are valid per policy/fees.h;
|
||||
* if a fee_rate is present, 0 is allowed here as a no-op positional placeholder
|
||||
* @param[in] estimate_mode UniValue string; fee estimation mode, valid values are "unset", "economical" or "conservative";
|
||||
* if a fee_rate is present, "" is allowed here as a no-op positional placeholder
|
||||
* @param[in] fee_rate UniValue real; fee rate in sat/vB;
|
||||
* if a fee_rate is present, both conf_target and estimate_mode must either be null, or no-op
|
||||
* @param[in] override_min_fee bool; whether to set fOverrideFeeRate to true to disable minimum fee rate checks and instead
|
||||
* verify only that fee_rate is greater than 0
|
||||
* @throws a JSONRPCError if conf_target, estimate_mode, or fee_rate contain invalid values or are in conflict
|
||||
*/
|
||||
static void SetFeeEstimateMode(const CWallet* pwallet, CCoinControl& cc, const UniValue& estimate_mode, const UniValue& estimate_param)
|
||||
static void SetFeeEstimateMode(const CWallet* pwallet, CCoinControl& cc, const UniValue& conf_target, const UniValue& estimate_mode, const UniValue& fee_rate, bool override_min_fee)
|
||||
{
|
||||
if (!estimate_mode.isNull()) {
|
||||
if (!FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
|
||||
if (!fee_rate.isNull()) {
|
||||
if (!conf_target.isNull() && conf_target.get_int() > 0) {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
||||
if (cc.m_fee_mode == FeeEstimateMode::BTC_KB || cc.m_fee_mode == FeeEstimateMode::SAT_B) {
|
||||
if (estimate_param.isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Selected estimate_mode %s requires a fee rate to be specified in conf_target", estimate_mode.get_str()));
|
||||
if (!estimate_mode.isNull() && !estimate_mode.get_str().empty()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and fee_rate");
|
||||
}
|
||||
|
||||
CAmount fee_rate = AmountFromValue(estimate_param);
|
||||
if (cc.m_fee_mode == FeeEstimateMode::SAT_B) {
|
||||
fee_rate /= WALLET_BTC_KB_TO_SAT_B;
|
||||
CFeeRate fee_rate_in_sat_vb{CFeeRate(AmountFromValue(fee_rate), COIN)};
|
||||
if (override_min_fee) {
|
||||
if (fee_rate_in_sat_vb <= CFeeRate(0)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid fee_rate %s (must be greater than 0)", fee_rate_in_sat_vb.ToString(FeeEstimateMode::SAT_VB)));
|
||||
}
|
||||
cc.fOverrideFeeRate = true;
|
||||
}
|
||||
|
||||
cc.m_feerate = CFeeRate(fee_rate);
|
||||
|
||||
// default RBF to true for explicit fee rate modes
|
||||
cc.m_feerate = fee_rate_in_sat_vb;
|
||||
// Default RBF to true for explicit fee_rate, if unset.
|
||||
if (cc.m_signal_bip125_rbf == nullopt) cc.m_signal_bip125_rbf = true;
|
||||
} else if (!estimate_param.isNull()) {
|
||||
cc.m_confirm_target = ParseConfirmTarget(estimate_param, pwallet->chain().estimateMaxBlocks());
|
||||
return;
|
||||
}
|
||||
if (!estimate_mode.isNull() && !FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
|
||||
}
|
||||
if (!conf_target.isNull()) {
|
||||
cc.m_confirm_target = ParseConfirmTarget(conf_target, pwallet->chain().estimateMaxBlocks());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,12 +448,12 @@ 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 -txconfirmtarget", "Confirmation target (in blocks)\n"
|
||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
|
||||
{"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"
|
||||
"dirty if they have previously been used in a transaction."},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
|
||||
{"verbose", RPCArg::Type::BOOL, /* default */ "false", "If true, return extra information about the transaction."},
|
||||
},
|
||||
{
|
||||
|
@ -462,12 +469,17 @@ static RPCHelpMan sendtoaddress()
|
|||
},
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1")
|
||||
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\" \"seans outpost\"")
|
||||
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" true")
|
||||
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 0.00002 " + (CURRENCY_UNIT + "/kB"))
|
||||
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 2 " + (CURRENCY_ATOM + "/B"))
|
||||
+ HelpExampleRpc("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\", 0.1, \"donation\", \"seans outpost\"")
|
||||
"\nSend 0.1 BTC\n"
|
||||
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1") +
|
||||
"\nSend 0.1 BTC with a confirmation target of 6 blocks in economical fee estimate mode using positional arguments\n"
|
||||
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\" \"sean's outpost\" false true 6 economical") +
|
||||
"\nSend 0.1 BTC with a fee rate of 1 " + CURRENCY_ATOM + "/vB, subtract fee from amount, BIP125-replaceable, using positional arguments\n"
|
||||
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"drinks\" \"room77\" true true 0 \"\" 1") +
|
||||
"\nSend 0.2 BTC with a confirmation target of 6 blocks in economical fee estimate mode using named arguments\n"
|
||||
+ HelpExampleCli("-named sendtoaddress", "address=\"" + EXAMPLE_ADDRESS[0] + "\" amount=0.2 conf_target=6 estimate_mode=\"economical\"") +
|
||||
"\nSend 0.5 BTC with a fee rate of 25 " + CURRENCY_ATOM + "/vB using named arguments\n"
|
||||
+ HelpExampleCli("-named sendtoaddress", "address=\"" + EXAMPLE_ADDRESS[0] + "\" amount=0.5 fee_rate=25")
|
||||
+ HelpExampleCli("-named sendtoaddress", "address=\"" + EXAMPLE_ADDRESS[0] + "\" amount=0.5 fee_rate=25 subtractfeefromamount=false replaceable=true avoid_reuse=true comment=\"2 pizzas\" comment_to=\"jeremy\" verbose=true")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
|
@ -502,7 +514,7 @@ static RPCHelpMan sendtoaddress()
|
|||
// We also enable partial spend avoidance if reuse avoidance is set.
|
||||
coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse;
|
||||
|
||||
SetFeeEstimateMode(pwallet, coin_control, request.params[7], request.params[6]);
|
||||
SetFeeEstimateMode(pwallet, coin_control, /* conf_target */ request.params[6], /* estimate_mode */ request.params[7], /* fee_rate */ request.params[9], /* override_min_fee */ false);
|
||||
|
||||
EnsureWalletIsUnlocked(pwallet);
|
||||
|
||||
|
@ -516,7 +528,7 @@ static RPCHelpMan sendtoaddress()
|
|||
|
||||
std::vector<CRecipient> recipients;
|
||||
ParseRecipients(address_amounts, subtractFeeFromAmount, recipients);
|
||||
bool verbose = request.params[9].isNull() ? false: request.params[9].get_bool();
|
||||
const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()};
|
||||
|
||||
return SendMoney(pwallet, coin_control, recipients, mapValue, verbose);
|
||||
},
|
||||
|
@ -870,10 +882,10 @@ 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 -txconfirmtarget", "Confirmation target (in blocks)\n"
|
||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
|
||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||
" \"" + FeeModes("\"\n\"") + "\""},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
|
||||
{"verbose", RPCArg::Type::BOOL, /* default */ "false", "If true, return extra infomration about the transaction."},
|
||||
},
|
||||
{
|
||||
|
@ -930,11 +942,11 @@ static RPCHelpMan sendmany()
|
|||
coin_control.m_signal_bip125_rbf = request.params[5].get_bool();
|
||||
}
|
||||
|
||||
SetFeeEstimateMode(pwallet, coin_control, request.params[7], request.params[6]);
|
||||
SetFeeEstimateMode(pwallet, coin_control, /* conf_target */ request.params[6], /* estimate_mode */ request.params[7], /* fee_rate */ request.params[8], /* override_min_fee */ false);
|
||||
|
||||
std::vector<CRecipient> recipients;
|
||||
ParseRecipients(sendTo, subtractFeeFromAmount, recipients);
|
||||
bool verbose = request.params[8].isNull() ? false : request.params[8].get_bool();
|
||||
const bool verbose{request.params[9].isNull() ? false : request.params[9].get_bool()};
|
||||
|
||||
return SendMoney(pwallet, coin_control, recipients, std::move(mapValue), verbose);
|
||||
},
|
||||
|
@ -2311,7 +2323,7 @@ static RPCHelpMan settxfee()
|
|||
"\nSet the transaction fee per kB for this wallet. Overrides the global -paytxfee command line parameter.\n"
|
||||
"Can be deactivated by passing 0 as the fee. In that case automatic fee selection will be used by default.\n",
|
||||
{
|
||||
{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The transaction fee in " + CURRENCY_UNIT + "/kB"},
|
||||
{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The transaction fee in " + CURRENCY_UNIT + "/kvB"},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::BOOL, "", "Returns true if successful"
|
||||
|
@ -2434,7 +2446,7 @@ static RPCHelpMan getwalletinfo()
|
|||
{RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
|
||||
{RPCResult::Type::NUM, "keypoolsize_hd_internal", "how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)"},
|
||||
{RPCResult::Type::NUM_TIME, "unlocked_until", /* optional */ true, "the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked (only present for passphrase-encrypted wallets)"},
|
||||
{RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB"},
|
||||
{RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kvB"},
|
||||
{RPCResult::Type::STR_HEX, "hdseedid", /* optional */ true, "the Hash160 of the HD seed (only present when HD is enabled)"},
|
||||
{RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
|
||||
{RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
|
||||
|
@ -3047,7 +3059,7 @@ static RPCHelpMan listunspent()
|
|||
};
|
||||
}
|
||||
|
||||
void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, const UniValue& options, CCoinControl& coinControl)
|
||||
void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, const UniValue& options, CCoinControl& coinControl, bool override_min_fee)
|
||||
{
|
||||
// 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
|
||||
|
@ -3080,7 +3092,8 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
|
|||
{"lockUnspents", UniValueType(UniValue::VBOOL)},
|
||||
{"lock_unspents", UniValueType(UniValue::VBOOL)},
|
||||
{"locktime", UniValueType(UniValue::VNUM)},
|
||||
{"feeRate", UniValueType()}, // will be checked below
|
||||
{"fee_rate", UniValueType()}, // will be checked by AmountFromValue() in SetFeeEstimateMode()
|
||||
{"feeRate", UniValueType()}, // will be checked by AmountFromValue() below
|
||||
{"psbt", UniValueType(UniValue::VBOOL)},
|
||||
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
||||
{"subtract_fee_from_outputs", UniValueType(UniValue::VARR)},
|
||||
|
@ -3127,15 +3140,21 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
|
|||
lockUnspents = (options.exists("lock_unspents") ? options["lock_unspents"] : options["lockUnspents"]).get_bool();
|
||||
}
|
||||
|
||||
if (options.exists("feeRate"))
|
||||
{
|
||||
if (options.exists("feeRate")) {
|
||||
if (options.exists("fee_rate")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both fee_rate (" + CURRENCY_ATOM + "/vB) and feeRate (" + CURRENCY_UNIT + "/kvB)");
|
||||
}
|
||||
if (options.exists("conf_target")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.");
|
||||
}
|
||||
if (options.exists("estimate_mode")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
|
||||
}
|
||||
coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"]));
|
||||
CFeeRate fee_rate(AmountFromValue(options["feeRate"]));
|
||||
if (fee_rate <= CFeeRate(0)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid feeRate %s (must be greater than 0)", fee_rate.ToString(FeeEstimateMode::BTC_KVB)));
|
||||
}
|
||||
coinControl.m_feerate = fee_rate;
|
||||
coinControl.fOverrideFeeRate = true;
|
||||
}
|
||||
|
||||
|
@ -3145,7 +3164,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
|
|||
if (options.exists("replaceable")) {
|
||||
coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool();
|
||||
}
|
||||
SetFeeEstimateMode(pwallet, coinControl, options["estimate_mode"], options["conf_target"]);
|
||||
SetFeeEstimateMode(pwallet, coinControl, options["conf_target"], options["estimate_mode"], options["fee_rate"], override_min_fee);
|
||||
}
|
||||
} else {
|
||||
// if options is null and not a bool
|
||||
|
@ -3202,7 +3221,8 @@ static RPCHelpMan fundrawtransaction()
|
|||
"Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
|
||||
"e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
|
||||
{"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
|
||||
{"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
|
||||
{"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_UNIT + "/kvB."},
|
||||
{"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "The integers.\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"
|
||||
|
@ -3213,8 +3233,7 @@ 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 -txconfirmtarget", "Confirmation target (in blocks)\n"
|
||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
|
||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||
" \"" + FeeModes("\"\n\"") + "\""},
|
||||
},
|
||||
|
@ -3266,7 +3285,7 @@ static RPCHelpMan fundrawtransaction()
|
|||
CCoinControl coin_control;
|
||||
// Automatically select (additional) coins. Can be overridden by options.add_inputs.
|
||||
coin_control.m_add_inputs = true;
|
||||
FundTransaction(pwallet, tx, fee, change_position, request.params[1], coin_control);
|
||||
FundTransaction(pwallet, tx, fee, change_position, request.params[1], coin_control, /* override_min_fee */ true);
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("hex", EncodeHexTx(CTransaction(tx)));
|
||||
|
@ -3374,35 +3393,38 @@ RPCHelpMan signrawtransactionwithwallet()
|
|||
static RPCHelpMan bumpfee_helper(std::string method_name)
|
||||
{
|
||||
bool want_psbt = method_name == "psbtbumpfee";
|
||||
const std::string incremental_fee{CFeeRate(DEFAULT_MIN_RELAY_TX_FEE).ToString(FeeEstimateMode::SAT_VB)};
|
||||
|
||||
return RPCHelpMan{method_name,
|
||||
"\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n"
|
||||
+ std::string(want_psbt ? "Returns a PSBT instead of creating and signing a new transaction.\n" : "") +
|
||||
"An opt-in RBF transaction with the given txid must be in the wallet.\n"
|
||||
"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"
|
||||
"The command will pay the additional fee by reducing change outputs or adding inputs when necessary.\n"
|
||||
"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 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"
|
||||
"Alternatively, the user can specify a fee rate in " + CURRENCY_ATOM + "/vB 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"
|
||||
"returned by getnetworkinfo) to enter the node's mempool.\n",
|
||||
"returned by getnetworkinfo) to enter the node's mempool.\n"
|
||||
"* WARNING: before version 0.21, fee_rate was in " + CURRENCY_UNIT + "/kvB. As of 0.21, fee_rate is in " + CURRENCY_ATOM + "/vB. *\n",
|
||||
{
|
||||
{"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 -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 + "/kB higher than the current transaction fee rate.\n"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks\n"},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation",
|
||||
"\nSpecify a fee rate in " + CURRENCY_ATOM + "/vB instead of relying on the built-in fee estimator.\n"
|
||||
"Must be at least " + incremental_fee + " " + CURRENCY_ATOM + "/vB higher than the current transaction fee rate.\n"
|
||||
"WARNING: before version 0.21, fee_rate was in " + CURRENCY_UNIT + "/kvB. As of 0.21, fee_rate is in " + CURRENCY_ATOM + "/vB.\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"
|
||||
"original transaction that were less than 0xfffffffe will be increased to 0xfffffffe\n"
|
||||
"so the new transaction will not be explicitly bip-125 replaceable (though it may\n"
|
||||
"still be replaceable in practice, for example if it has unconfirmed ancestors which\n"
|
||||
"are replaceable)."},
|
||||
"are replaceable).\n"},
|
||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||
" \"" + FeeModes("\"\n\"") + "\""},
|
||||
},
|
||||
|
@ -3455,7 +3477,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
|
|||
{
|
||||
{"confTarget", UniValueType(UniValue::VNUM)},
|
||||
{"conf_target", UniValueType(UniValue::VNUM)},
|
||||
{"fee_rate", UniValueType(UniValue::VNUM)},
|
||||
{"fee_rate", UniValueType()}, // will be checked by AmountFromValue() in SetFeeEstimateMode()
|
||||
{"replaceable", UniValueType(UniValue::VBOOL)},
|
||||
{"estimate_mode", UniValueType(UniValue::VSTR)},
|
||||
},
|
||||
|
@ -3467,22 +3489,10 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
|
|||
|
||||
auto conf_target = options.exists("confTarget") ? options["confTarget"] : options["conf_target"];
|
||||
|
||||
if (!conf_target.isNull()) {
|
||||
if (options.exists("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.");
|
||||
}
|
||||
} else if (options.exists("fee_rate")) {
|
||||
CFeeRate fee_rate(AmountFromValue(options["fee_rate"]));
|
||||
if (fee_rate <= CFeeRate(0)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid fee_rate %s (must be greater than 0)", fee_rate.ToString()));
|
||||
}
|
||||
coin_control.m_feerate = fee_rate;
|
||||
}
|
||||
|
||||
if (options.exists("replaceable")) {
|
||||
coin_control.m_signal_bip125_rbf = options["replaceable"].get_bool();
|
||||
}
|
||||
SetFeeEstimateMode(pwallet, coin_control, options["estimate_mode"], conf_target);
|
||||
SetFeeEstimateMode(pwallet, coin_control, conf_target, options["estimate_mode"], options["fee_rate"], /* override_min_fee */ false);
|
||||
}
|
||||
|
||||
// Make sure the results are valid at least up to the most recent block
|
||||
|
@ -4018,10 +4028,10 @@ static RPCHelpMan send()
|
|||
},
|
||||
},
|
||||
},
|
||||
{"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)"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
|
||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||
" \"" + FeeModes("\"\n\"") + "\""},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
|
||||
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
|
||||
{
|
||||
{"add_inputs", RPCArg::Type::BOOL, /* default */ "false", "If inputs are specified, automatically include more if they are not enough."},
|
||||
|
@ -4029,10 +4039,10 @@ 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 -txconfirmtarget", "Confirmation target (in blocks)\n"
|
||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
|
||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||
" \"" + FeeModes("\"\n\"") + "\""},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
|
||||
{"include_watching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only.\n"
|
||||
"Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
|
||||
"e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
|
||||
|
@ -4069,18 +4079,25 @@ static RPCHelpMan send()
|
|||
}
|
||||
},
|
||||
RPCExamples{""
|
||||
"\nSend with a fee rate of 1 satoshi per byte\n"
|
||||
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 sat/b\n") +
|
||||
"\nCreate a transaction that should confirm the next block, with a specific input, and return result without adding to wallet or broadcasting to the network\n"
|
||||
"\nSend 0.1 BTC with a confirmation target of 6 blocks in economical fee estimate mode\n"
|
||||
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 6 economical\n") +
|
||||
"Send 0.2 BTC with a fee rate of 1 " + CURRENCY_ATOM + "/vB using positional arguments\n"
|
||||
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.2}' 0 \"\" 1\n") +
|
||||
"Send 0.2 BTC with a fee rate of 1 " + CURRENCY_ATOM + "/vB using the options argument\n"
|
||||
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.2}' '{\"fee_rate\": 1}'\n") +
|
||||
"Send 0.3 BTC with a fee rate of 25 " + CURRENCY_ATOM + "/vB using named arguments\n"
|
||||
+ HelpExampleCli("-named send", "outputs='{\"" + EXAMPLE_ADDRESS[0] + "\": 0.3}' fee_rate=25\n") +
|
||||
"Create a transaction that should confirm the next block, with a specific input, and return result without adding to wallet or broadcasting to the network\n"
|
||||
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 economical '{\"add_to_wallet\": false, \"inputs\": [{\"txid\":\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\", \"vout\":1}]}'")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
RPCTypeCheck(request.params, {
|
||||
UniValueType(), // ARR or OBJ, checked later
|
||||
UniValue::VNUM,
|
||||
UniValue::VSTR,
|
||||
UniValue::VOBJ
|
||||
UniValueType(), // outputs (ARR or OBJ, checked later)
|
||||
UniValue::VNUM, // conf_target
|
||||
UniValue::VSTR, // estimate_mode
|
||||
UniValue::VNUM, // fee_rate
|
||||
UniValue::VOBJ, // options
|
||||
}, true
|
||||
);
|
||||
|
||||
|
@ -4088,18 +4105,28 @@ static RPCHelpMan send()
|
|||
if (!wallet) return NullUniValue;
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
UniValue options = request.params[3];
|
||||
if (options.exists("feeRate") || options.exists("fee_rate") || options.exists("estimate_mode") || options.exists("conf_target")) {
|
||||
UniValue options{request.params[4].isNull() ? UniValue::VOBJ : request.params[4]};
|
||||
if (options.exists("conf_target") || options.exists("estimate_mode")) {
|
||||
if (!request.params[1].isNull() || !request.params[2].isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Use either conf_target and estimate_mode or the options dictionary to control fee rate");
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass conf_target and estimate_mode either as arguments or in the options object, but not both");
|
||||
}
|
||||
} else {
|
||||
options.pushKV("conf_target", request.params[1]);
|
||||
options.pushKV("estimate_mode", request.params[2]);
|
||||
}
|
||||
if (options.exists("fee_rate")) {
|
||||
if (!request.params[3].isNull()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass the fee_rate either as an argument, or in the options object, but not both");
|
||||
}
|
||||
} else {
|
||||
options.pushKV("fee_rate", request.params[3]);
|
||||
}
|
||||
if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (options["estimate_mode"].get_str() == "unset"))) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Specify estimate_mode");
|
||||
}
|
||||
if (options.exists("feeRate")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Use fee_rate (" + CURRENCY_ATOM + "/vB) instead of feeRate");
|
||||
}
|
||||
if (options.exists("changeAddress")) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_address");
|
||||
}
|
||||
|
@ -4129,7 +4156,7 @@ static RPCHelpMan send()
|
|||
// Automatically select coins, unless at least one is manually selected. Can
|
||||
// be overridden by options.add_inputs.
|
||||
coin_control.m_add_inputs = rawTx.vin.size() == 0;
|
||||
FundTransaction(pwallet, rawTx, fee, change_position, options, coin_control);
|
||||
FundTransaction(pwallet, rawTx, fee, change_position, options, coin_control, /* override_min_fee */ false);
|
||||
|
||||
bool add_to_wallet = true;
|
||||
if (options.exists("add_to_wallet")) {
|
||||
|
@ -4357,7 +4384,8 @@ static RPCHelpMan walletcreatefundedpsbt()
|
|||
{"change_type", RPCArg::Type::STR, /* default */ "set by -changetype", "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
|
||||
{"includeWatching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only"},
|
||||
{"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
|
||||
{"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
|
||||
{"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_UNIT + "/kvB."},
|
||||
{"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "The outputs to subtract the fee from.\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"
|
||||
|
@ -4368,8 +4396,7 @@ 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 */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
|
||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||
" \"" + FeeModes("\"\n\"") + "\""},
|
||||
},
|
||||
|
@ -4416,7 +4443,7 @@ static RPCHelpMan walletcreatefundedpsbt()
|
|||
// Automatically select coins, unless at least one is manually selected. Can
|
||||
// be overridden by options.add_inputs.
|
||||
coin_control.m_add_inputs = rawTx.vin.size() == 0;
|
||||
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3], coin_control);
|
||||
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3], coin_control, /* override_min_fee */ true);
|
||||
|
||||
// Make a blank psbt
|
||||
PartiallySignedTransaction psbtx(rawTx);
|
||||
|
@ -4549,9 +4576,9 @@ static const CRPCCommand commands[] =
|
|||
{ "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} },
|
||||
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
|
||||
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
|
||||
{ "wallet", "send", &send, {"outputs","conf_target","estimate_mode","options"} },
|
||||
{ "wallet", "sendmany", &sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode","verbose"} },
|
||||
{ "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode","avoid_reuse","verbose"} },
|
||||
{ "wallet", "send", &send, {"outputs","conf_target","estimate_mode","fee_rate","options"} },
|
||||
{ "wallet", "sendmany", &sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode","fee_rate","verbose"} },
|
||||
{ "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode","avoid_reuse","fee_rate","verbose"} },
|
||||
{ "wallet", "sethdseed", &sethdseed, {"newkeypool","seed"} },
|
||||
{ "wallet", "setlabel", &setlabel, {"address","label"} },
|
||||
{ "wallet", "settxfee", &settxfee, {"amount"} },
|
||||
|
|
|
@ -2818,7 +2818,7 @@ bool CWallet::CreateTransactionInternal(
|
|||
// Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
|
||||
// provided one
|
||||
if (coin_control.m_feerate && nFeeRateNeeded > *coin_control.m_feerate) {
|
||||
error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(), nFeeRateNeeded.ToString());
|
||||
error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), nFeeRateNeeded.ToString(FeeEstimateMode::SAT_VB));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ class EstimateFeeTest(BitcoinTestFramework):
|
|||
|
||||
# wrong type for estimatesmartfee(estimate_mode)
|
||||
assert_raises_rpc_error(-3, "Expected type string, got number", self.nodes[0].estimatesmartfee, 1, 1)
|
||||
assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", self.nodes[0].estimatesmartfee, 1, 'foo')
|
||||
assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"', self.nodes[0].estimatesmartfee, 1, 'foo')
|
||||
|
||||
# wrong type for estimaterawfee(threshold)
|
||||
assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 1, 'foo')
|
||||
|
|
|
@ -90,7 +90,6 @@ 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()
|
||||
|
@ -708,74 +707,86 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||
wwatch.unloadwallet()
|
||||
|
||||
def test_option_feerate(self):
|
||||
self.log.info("Test fundrawtxn feeRate option")
|
||||
|
||||
# Make sure there is exactly one input so coin selection can't skew the result.
|
||||
assert_equal(len(self.nodes[3].listunspent(1)), 1)
|
||||
|
||||
inputs = []
|
||||
outputs = {self.nodes[3].getnewaddress() : 1}
|
||||
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
|
||||
result = self.nodes[3].fundrawtransaction(rawtx) # uses self.min_relay_tx_fee (set by settxfee)
|
||||
result2 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee})
|
||||
result3 = self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 10 * self.min_relay_tx_fee})
|
||||
assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)", self.nodes[3].fundrawtransaction, rawtx, {"feeRate": 1})
|
||||
result_fee_rate = result['fee'] * 1000 / count_bytes(result['hex'])
|
||||
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")
|
||||
self.log.info("Test fundrawtxn with explicit fee rates (fee_rate sat/vB and feeRate BTC/kvB)")
|
||||
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)
|
||||
assert_equal(len(self.nodes[3].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)
|
||||
result = node.fundrawtransaction(rawtx) # uses self.min_relay_tx_fee (set by settxfee)
|
||||
btc_kvb_to_sat_vb = 100000 # (1e5)
|
||||
result1 = node.fundrawtransaction(rawtx, {"fee_rate": 2 * btc_kvb_to_sat_vb * self.min_relay_tx_fee})
|
||||
result2 = node.fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee})
|
||||
result3 = node.fundrawtransaction(rawtx, {"fee_rate": 10 * btc_kvb_to_sat_vb * self.min_relay_tx_fee})
|
||||
result4 = node.fundrawtransaction(rawtx, {"feeRate": 10 * self.min_relay_tx_fee})
|
||||
result_fee_rate = result['fee'] * 1000 / count_bytes(result['hex'])
|
||||
assert_fee_amount(result1['fee'], count_bytes(result2['hex']), 2 * result_fee_rate)
|
||||
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)
|
||||
assert_fee_amount(result4['fee'], count_bytes(result3['hex']), 10 * result_fee_rate)
|
||||
|
||||
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}))
|
||||
# With no arguments passed, expect fee of 141 satoshis.
|
||||
assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000141, vspan=0.00000001)
|
||||
# Expect fee to be 10,000x higher when an explicit fee rate 10,000x greater is specified.
|
||||
result = node.fundrawtransaction(rawtx, {"fee_rate": 10000})
|
||||
assert_approx(result["fee"], vexp=0.0141, vspan=0.0001)
|
||||
|
||||
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}))
|
||||
node.fundrawtransaction, rawtx, {"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, must be one of: "unset", "economical", "conservative"',
|
||||
node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": 0.1, "add_inputs": True})
|
||||
|
||||
self.log.info("Test fundrawtxn with invalid conf_target settings")
|
||||
for mode in ["unset", "economical", "conservative", "btc/kb", "sat/b"]:
|
||||
for mode in ["unset", "economical", "conservative"]:
|
||||
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}))
|
||||
node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": v, "add_inputs": True})
|
||||
for n in [-1, 0, 1009]:
|
||||
assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
|
||||
node.fundrawtransaction, rawtx, {"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].fundrawtransaction(rawtx, {"estimate_mode": unit, "conf_target": fee_rate, "add_inputs": True}))
|
||||
self.log.info("Test invalid fee rate settings")
|
||||
assert_raises_rpc_error(-8, "Invalid fee_rate 0.000 sat/vB (must be greater than 0)",
|
||||
node.fundrawtransaction, rawtx, {"fee_rate": 0, "add_inputs": True})
|
||||
assert_raises_rpc_error(-8, "Invalid feeRate 0.00000000 BTC/kvB (must be greater than 0)",
|
||||
node.fundrawtransaction, rawtx, {"feeRate": 0, "add_inputs": True})
|
||||
for param, value in {("fee_rate", 100000), ("feeRate", 1.000)}:
|
||||
assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
|
||||
node.fundrawtransaction, rawtx, {param: value, "add_inputs": True})
|
||||
assert_raises_rpc_error(-3, "Amount out of range",
|
||||
node.fundrawtransaction, rawtx, {"fee_rate": -1, "add_inputs": True})
|
||||
assert_raises_rpc_error(-3, "Amount is not a number or string",
|
||||
node.fundrawtransaction, rawtx, {"fee_rate": {"foo": "bar"}, "add_inputs": True})
|
||||
assert_raises_rpc_error(-3, "Invalid amount",
|
||||
node.fundrawtransaction, rawtx, {"fee_rate": "", "add_inputs": True})
|
||||
|
||||
self.log.info("Test min fee rate checks are bypassed with fundrawtxn, e.g. a fee_rate under 1 sat/vB is allowed")
|
||||
node.fundrawtransaction(rawtx, {"fee_rate": 0.99999999, "add_inputs": True})
|
||||
node.fundrawtransaction(rawtx, {"feeRate": 0.00000999, "add_inputs": True})
|
||||
|
||||
self.log.info("- raises RPC error if both feeRate and fee_rate are passed")
|
||||
assert_raises_rpc_error(-8, "Cannot specify both fee_rate (sat/vB) and feeRate (BTC/kvB)",
|
||||
node.fundrawtransaction, rawtx, {"fee_rate": 0.1, "feeRate": 0.1, "add_inputs": True})
|
||||
|
||||
self.log.info("- raises RPC error if both feeRate and estimate_mode passed")
|
||||
assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and feeRate",
|
||||
node.fundrawtransaction, rawtx, {"estimate_mode": "economical", "feeRate": 0.1, "add_inputs": True})
|
||||
|
||||
for param in ["feeRate", "fee_rate"]:
|
||||
self.log.info("- raises RPC error if both {} and conf_target are passed".format(param))
|
||||
assert_raises_rpc_error(-8, "Cannot specify both conf_target and {}. Please provide either a confirmation "
|
||||
"target in blocks for automatic fee estimation, or an explicit fee rate.".format(param),
|
||||
node.fundrawtransaction, rawtx, {param: 1, "conf_target": 1, "add_inputs": True})
|
||||
|
||||
self.log.info("- 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",
|
||||
node.fundrawtransaction, rawtx, {"fee_rate": 1, "estimate_mode": "economical", "add_inputs": True})
|
||||
|
||||
def test_address_reuse(self):
|
||||
"""Test no address reuse occurs."""
|
||||
|
@ -803,12 +814,32 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||
outputs = {self.nodes[2].getnewaddress(): 1}
|
||||
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
|
||||
|
||||
# Test subtract fee from outputs with feeRate (BTC/kvB)
|
||||
result = [self.nodes[3].fundrawtransaction(rawtx), # uses self.min_relay_tx_fee (set by settxfee)
|
||||
self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": []}), # empty subtraction list
|
||||
self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0]}), # uses self.min_relay_tx_fee (set by settxfee)
|
||||
self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee}),
|
||||
self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee, "subtractFeeFromOutputs": [0]}),]
|
||||
dec_tx = [self.nodes[3].decoderawtransaction(tx_['hex']) for tx_ in result]
|
||||
output = [d['vout'][1 - r['changepos']]['value'] for d, r in zip(dec_tx, result)]
|
||||
change = [d['vout'][r['changepos']]['value'] for d, r in zip(dec_tx, result)]
|
||||
|
||||
assert_equal(result[0]['fee'], result[1]['fee'], result[2]['fee'])
|
||||
assert_equal(result[3]['fee'], result[4]['fee'])
|
||||
assert_equal(change[0], change[1])
|
||||
assert_equal(output[0], output[1])
|
||||
assert_equal(output[0], output[2] + result[2]['fee'])
|
||||
assert_equal(change[0] + result[0]['fee'], change[2])
|
||||
assert_equal(output[3], output[4] + result[4]['fee'])
|
||||
assert_equal(change[3] + result[3]['fee'], change[4])
|
||||
|
||||
# Test subtract fee from outputs with fee_rate (sat/vB)
|
||||
btc_kvb_to_sat_vb = 100000 # (1e5)
|
||||
result = [self.nodes[3].fundrawtransaction(rawtx), # uses self.min_relay_tx_fee (set by settxfee)
|
||||
self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": []}), # empty subtraction list
|
||||
self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0]}), # uses self.min_relay_tx_fee (set by settxfee)
|
||||
self.nodes[3].fundrawtransaction(rawtx, {"fee_rate": 2 * btc_kvb_to_sat_vb * self.min_relay_tx_fee}),
|
||||
self.nodes[3].fundrawtransaction(rawtx, {"fee_rate": 2 * btc_kvb_to_sat_vb * self.min_relay_tx_fee, "subtractFeeFromOutputs": [0]}),]
|
||||
dec_tx = [self.nodes[3].decoderawtransaction(tx_['hex']) for tx_ in result]
|
||||
output = [d['vout'][1 - r['changepos']]['value'] for d, r in zip(dec_tx, result)]
|
||||
change = [d['vout'][r['changepos']]['value'] for d, r in zip(dec_tx, result)]
|
||||
|
|
|
@ -187,60 +187,74 @@ class PSBTTest(BitcoinTestFramework):
|
|||
assert_equal(walletprocesspsbt_out['complete'], True)
|
||||
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
|
||||
|
||||
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)
|
||||
self.log.info("Test walletcreatefundedpsbt fee rate of 10000 sat/vB and 0.1 BTC/kvB produces a total fee at or slightly below -maxtxfee (~0.05290000)")
|
||||
res1 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"fee_rate": 10000, "add_inputs": True})
|
||||
assert_approx(res1["fee"], 0.055, 0.005)
|
||||
res2 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.1, "add_inputs": True})
|
||||
assert_approx(res2["fee"], 0.055, 0.005)
|
||||
self.log.info("Test min fee rate checks with walletcreatefundedpsbt are bypassed, e.g. a fee_rate under 1 sat/vB is allowed")
|
||||
res3 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"fee_rate": 0.99999999, "add_inputs": True})
|
||||
assert_approx(res3["fee"], 0.00000381, 0.0000001)
|
||||
res4 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.00000999, "add_inputs": True})
|
||||
assert_approx(res4["fee"], 0.00000381, 0.0000001)
|
||||
|
||||
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)
|
||||
self.log.info("Test invalid fee rate settings")
|
||||
assert_raises_rpc_error(-8, "Invalid fee_rate 0.000 sat/vB (must be greater than 0)",
|
||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": 0, "add_inputs": True})
|
||||
assert_raises_rpc_error(-8, "Invalid feeRate 0.00000000 BTC/kvB (must be greater than 0)",
|
||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"feeRate": 0, "add_inputs": True})
|
||||
for param, value in {("fee_rate", 100000), ("feeRate", 1)}:
|
||||
assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)",
|
||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {param: value, "add_inputs": True})
|
||||
assert_raises_rpc_error(-3, "Amount out of range",
|
||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": -1, "add_inputs": True})
|
||||
assert_raises_rpc_error(-3, "Amount is not a number or string",
|
||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": {"foo": "bar"}, "add_inputs": True})
|
||||
assert_raises_rpc_error(-3, "Invalid amount",
|
||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": "", "add_inputs": True})
|
||||
|
||||
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 if both feeRate and fee_rate are passed")
|
||||
assert_raises_rpc_error(-8, "Cannot specify both fee_rate (sat/vB) and feeRate (BTC/kvB)",
|
||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": 0.1, "feeRate": 0.1, "add_inputs": True})
|
||||
|
||||
self.log.info("- raises RPC error if both feeRate and estimate_mode passed")
|
||||
assert_raises_rpc_error(-8, "Cannot specify both estimate_mode and feeRate",
|
||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": "economical", "feeRate": 0.1, "add_inputs": True})
|
||||
|
||||
for param in ["feeRate", "fee_rate"]:
|
||||
self.log.info("- raises RPC error if both {} and conf_target are passed".format(param))
|
||||
assert_raises_rpc_error(-8, "Cannot specify both conf_target and {}. Please provide either a confirmation "
|
||||
"target in blocks for automatic fee estimation, or an explicit fee rate.".format(param),
|
||||
self.nodes[1].walletcreatefundedpsbt ,inputs, outputs, 0, {param: 1, "conf_target": 1, "add_inputs": True})
|
||||
|
||||
self.log.info("- 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",
|
||||
self.nodes[1].walletcreatefundedpsbt ,inputs, outputs, 0, {"fee_rate": 1, "estimate_mode": "economical", "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.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, must be one of: "unset", "economical", "conservative"',
|
||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": mode, "conf_target": 0.1, "add_inputs": True})
|
||||
|
||||
self.log.info("- raises RPC error with invalid conf_target settings")
|
||||
for mode in ["unset", "economical", "conservative", "btc/kb", "sat/b"]:
|
||||
for mode in ["unset", "economical", "conservative"]:
|
||||
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}))
|
||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": mode, "conf_target": v, "add_inputs": True})
|
||||
for n in [-1, 0, 1009]:
|
||||
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[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")
|
||||
self.log.info("Test walletcreatefundedpsbt with too-high fee rate 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})
|
||||
msg = "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)"
|
||||
assert_raises_rpc_error(-4, msg, self.nodes[1].walletcreatefundedpsbt, inputs, outputs_array, 0, {"fee_rate": 1000000, "add_inputs": bool_add})
|
||||
assert_raises_rpc_error(-4, msg, self.nodes[1].walletcreatefundedpsbt, inputs, outputs_array, 0, {"feeRate": 1, "add_inputs": bool_add})
|
||||
|
||||
self.log.info("Test various PSBT operations")
|
||||
# partially sign multisig things with node 1
|
||||
|
|
|
@ -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.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)",
|
||||
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.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)",
|
||||
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, must be one of: "unset", "economical", "conservative"',
|
||||
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.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)",
|
||||
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.000 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)",
|
||||
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, must be one of: "unset", "economical", "conservative"',
|
||||
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)])
|
||||
|
|
|
@ -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,44 @@ 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, "Insufficient total fee 0.00, must be at least 0.00001704 (oldFee 0.00000999 + incrementalFee 0.00000705)",
|
||||
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, must be one of: "unset", "economical", "conservative"',
|
||||
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 +153,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 +304,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)
|
||||
|
|
|
@ -5,13 +5,15 @@
|
|||
"""Test the send RPC command."""
|
||||
|
||||
from decimal import Decimal, getcontext
|
||||
from itertools import product
|
||||
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_fee_amount,
|
||||
assert_greater_than,
|
||||
assert_raises_rpc_error
|
||||
assert_raises_rpc_error,
|
||||
)
|
||||
|
||||
class WalletSendTest(BitcoinTestFramework):
|
||||
|
@ -28,8 +30,8 @@ class WalletSendTest(BitcoinTestFramework):
|
|||
self.skip_if_no_wallet()
|
||||
|
||||
def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,
|
||||
arg_conf_target=None, arg_estimate_mode=None,
|
||||
conf_target=None, estimate_mode=None, add_to_wallet=None, psbt=None,
|
||||
arg_conf_target=None, arg_estimate_mode=None, arg_fee_rate=None,
|
||||
conf_target=None, estimate_mode=None, fee_rate=None, add_to_wallet=None, psbt=None,
|
||||
inputs=None, add_inputs=None, change_address=None, change_position=None, change_type=None,
|
||||
include_watching=None, locktime=None, lock_unspents=None, replaceable=None, subtract_fee_from_outputs=None,
|
||||
expect_error=None):
|
||||
|
@ -62,6 +64,8 @@ class WalletSendTest(BitcoinTestFramework):
|
|||
options["conf_target"] = conf_target
|
||||
if estimate_mode is not None:
|
||||
options["estimate_mode"] = estimate_mode
|
||||
if fee_rate is not None:
|
||||
options["fee_rate"] = fee_rate
|
||||
if inputs is not None:
|
||||
options["inputs"] = inputs
|
||||
if add_inputs is not None:
|
||||
|
@ -89,18 +93,19 @@ class WalletSendTest(BitcoinTestFramework):
|
|||
options = None
|
||||
|
||||
if expect_error is None:
|
||||
res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options)
|
||||
res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)
|
||||
else:
|
||||
try:
|
||||
assert_raises_rpc_error(expect_error[0], expect_error[1], from_wallet.send,
|
||||
outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options)
|
||||
outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)
|
||||
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(arg_fee_rate)
|
||||
self.log.error(options)
|
||||
res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, options=options)
|
||||
res = from_wallet.send(outputs=outputs, conf_target=arg_conf_target, estimate_mode=arg_estimate_mode, fee_rate=arg_fee_rate, options=options)
|
||||
self.log.error(res)
|
||||
if "txid" in res and add_to_wallet:
|
||||
self.log.error("Transaction details:")
|
||||
|
@ -226,10 +231,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"]:
|
||||
for mode in ["unset", "economical", "conservative"]:
|
||||
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"))
|
||||
expect_error=(-8, "Pass conf_target and estimate_mode either as arguments or in the options object, but not both"))
|
||||
|
||||
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)
|
||||
|
@ -251,60 +256,57 @@ class WalletSendTest(BitcoinTestFramework):
|
|||
assert res["complete"]
|
||||
|
||||
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)
|
||||
res1 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=1, add_to_wallet=False)
|
||||
res2 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=1, 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)
|
||||
# Passing conf_target 0, estimate_mode "" as placeholder arguments should allow fee_rate to apply.
|
||||
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0, estimate_mode="", fee_rate=7, 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)
|
||||
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=2, add_to_wallet=False)
|
||||
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
|
||||
assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00002"))
|
||||
|
||||
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)
|
||||
# Passing conf_target 0, estimate_mode "" as placeholder arguments should allow fee_rate to apply.
|
||||
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=0, arg_estimate_mode="", arg_fee_rate=4.531, 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)
|
||||
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=3, 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)"))
|
||||
# Test that passing fee_rate as both an argument and an option raises.
|
||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=1, fee_rate=1, add_to_wallet=False,
|
||||
expect_error=(-8, "Pass the fee_rate either as an argument, or in the options object, but not both"))
|
||||
|
||||
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))
|
||||
assert_raises_rpc_error(-8, "Use fee_rate (sat/vB) instead of feeRate", w0.send, {w1.getnewaddress(): 1}, 6, "conservative", 1, {"feeRate": 0.01})
|
||||
|
||||
# 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)))
|
||||
assert_raises_rpc_error(-3, "Unexpected key totalFee", w0.send, {w1.getnewaddress(): 1}, 6, "conservative", 1, {"totalFee": 0.01})
|
||||
|
||||
for target, mode in product([-1, 0, 1009], ["economical", "conservative"]):
|
||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=target, estimate_mode=mode,
|
||||
expect_error=(-8, "Invalid conf_target, must be between 1 and 1008")) # max value of 1008 per src/policy/fees.h
|
||||
msg = 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"'
|
||||
for target, mode in product([-1, 0], ["btc/kb", "sat/b"]):
|
||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=target, estimate_mode=mode, expect_error=(-8, msg))
|
||||
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, msg))
|
||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=0.1, arg_estimate_mode=mode, expect_error=(-8, msg))
|
||||
assert_raises_rpc_error(-8, msg, w0.send, {w1.getnewaddress(): 1}, 0.1, mode)
|
||||
|
||||
for mode in ["economical", "conservative", "btc/kb", "sat/b"]:
|
||||
self.log.debug("{}".format(mode))
|
||||
for k, v in {"string": "true", "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)))
|
||||
self.log.info("Explicit fee rate raises RPC error 'fee rate too low' if fee_rate of 0.99999999 is passed")
|
||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=0.99999999,
|
||||
expect_error=(-4, "Fee rate (0.999 sat/vB) is lower than the minimum fee rate setting (1.000 sat/vB)"))
|
||||
|
||||
# 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…
Add table
Reference in a new issue