mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 12:02:48 +01:00
Merge #20305: wallet: introduce fee_rate sat/vB param/option
05e82d86b09d914ebce05dbc92a7299cb026847b wallet: override minfee checks (fOverrideFeeRate) for fee_rate (Jon Atack) 9a670b4f07a6140de809d73cbd7f3e614eb6ea74 wallet: update sendtoaddress, send RPC examples with fee_rate (Jon Atack) be481b72e24fb6834bd674cd8daee67c6938b42d wallet: use MIN_RELAY_TX_FEE in bumpfee help (Jon Atack) 449b730579566459e350703611629e63e54657ed wallet: provide valid values if invalid estimate mode passed (Jon Atack) 6da3afbaee5809ebf6d88efaa3958c505c2d71c7 wallet: update remaining rpcwallet fee rate units to BTC/kvB (Jon Atack) 173b5b5fe07d45be5a1e5bc7a5df996f20ab1e85 wallet: update fee rate units, use sat/vB for fee_rate error messages (Jon Atack) 7f9835a05abf3e168ad93e7195cbaa4bf61b9b07 wallet: remove fee rates from conf_target helps (Jon Atack) b7994c01e9a3251536fe6538a22f614774eec82d wallet: add fee_rate unit warnings to bumpfee (Jon Atack) 410e471fa42d3db04e8879c71f8c824dcc151a83 wallet: remove redundant bumpfee fee_rate checks (Jon Atack) a0d495747320c79b27a83c216dcc526ac8df8f24 wallet: introduce fee_rate (sat/vB) param/option (Jon Atack) e21212f01b7c41eba13b0479b252053cf482bc1f wallet: remove unneeded WALLET_BTC_KB_TO_SAT_B constant (Jon Atack) 6112cf20d43b0be34fe0edce2ac3e6b27cae1bbe wallet: add CFeeRate ctor doxygen documentation (Jon Atack) 3f7279161347543ce4e997d78ea89a4043491145 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-ACK 05e82d8 MarcoFalke: review ACK 05e82d86b0 did not test and found a few style nits, which can be fixed later 🍯 Xekyo: tACK 05e82d86b09d914ebce05dbc92a7299cb026847b Sjors: utACK 05e82d86b09d914ebce05dbc92a7299cb026847b Tree-SHA512: a4ee5f184ada53f1840b2923d25873bda88c5a2ae48e67eeea2417a0b35154798cfdb3c147b05dd56bd6608a784e1b91623bb985ee2ab9ef2baaec22206d0a9c
This commit is contained in:
parent
0fa19226cb
commit
f436c20bc4
@ -19,8 +19,8 @@ enum class FeeEstimateMode {
|
|||||||
UNSET, //!< Use default settings based on other criteria
|
UNSET, //!< Use default settings based on other criteria
|
||||||
ECONOMICAL, //!< Force estimateSmartFee to use non-conservative estimates
|
ECONOMICAL, //!< Force estimateSmartFee to use non-conservative estimates
|
||||||
CONSERVATIVE, //!< Force estimateSmartFee to use conservative estimates
|
CONSERVATIVE, //!< Force estimateSmartFee to use conservative estimates
|
||||||
DASH_KB, //!< Use explicit DASH/kB fee given in coin control
|
DASH_KB, //!< Use DASH/kB fee rate unit
|
||||||
DUFF_B, //!< Use explicit duff/B fee given in coin control
|
DUFF_B, //!< Use duff/B fee rate unit
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,7 +39,12 @@ public:
|
|||||||
// We've previously had bugs creep in from silent double->int conversion...
|
// 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");
|
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 kB (duff/kB).
|
||||||
|
*
|
||||||
|
* Passing a num_bytes value of COIN (1e8) returns a fee rate in satoshis per B (sat/B),
|
||||||
|
* e.g. (nFeePaid * 1e8 / 1e3) == (nFeePaid / 1e5),
|
||||||
|
* where 1e5 is the ratio to convert from DASH/kB to sat/B.
|
||||||
|
*/
|
||||||
CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes);
|
CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes);
|
||||||
/**
|
/**
|
||||||
* Return the fee in satoshis for the given size in bytes.
|
* Return the fee in satoshis for the given size in bytes.
|
||||||
|
@ -44,7 +44,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||||||
{ "sendtoaddress", 6, "use_cj" },
|
{ "sendtoaddress", 6, "use_cj" },
|
||||||
{ "sendtoaddress", 7, "conf_target" },
|
{ "sendtoaddress", 7, "conf_target" },
|
||||||
{ "sendtoaddress", 9, "avoid_reuse" },
|
{ "sendtoaddress", 9, "avoid_reuse" },
|
||||||
{ "sendtoaddress", 10, "verbose"},
|
{ "sendtoaddress", 10, "fee_rate"},
|
||||||
|
{ "sendtoaddress", 11, "verbose"},
|
||||||
{ "settxfee", 0, "amount" },
|
{ "settxfee", 0, "amount" },
|
||||||
{ "sethdseed", 0, "newkeypool" },
|
{ "sethdseed", 0, "newkeypool" },
|
||||||
{ "getreceivedbyaddress", 1, "minconf" },
|
{ "getreceivedbyaddress", 1, "minconf" },
|
||||||
@ -91,7 +92,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||||||
{ "sendmany", 6, "use_is" },
|
{ "sendmany", 6, "use_is" },
|
||||||
{ "sendmany", 7, "use_cj" },
|
{ "sendmany", 7, "use_cj" },
|
||||||
{ "sendmany", 8, "conf_target" },
|
{ "sendmany", 8, "conf_target" },
|
||||||
{ "sendmany", 10, "verbose" },
|
{ "sendmany", 10, "fee_rate" },
|
||||||
|
{ "sendmany", 11, "verbose" },
|
||||||
{ "deriveaddresses", 1, "range" },
|
{ "deriveaddresses", 1, "range" },
|
||||||
{ "scantxoutset", 1, "scanobjects" },
|
{ "scantxoutset", 1, "scanobjects" },
|
||||||
{ "addmultisigaddress", 0, "nrequired" },
|
{ "addmultisigaddress", 0, "nrequired" },
|
||||||
@ -152,7 +154,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||||||
{ "lockunspent", 1, "transactions" },
|
{ "lockunspent", 1, "transactions" },
|
||||||
{ "send", 0, "outputs" },
|
{ "send", 0, "outputs" },
|
||||||
{ "send", 1, "conf_target" },
|
{ "send", 1, "conf_target" },
|
||||||
{ "send", 3, "options" },
|
{ "send", 3, "fee_rate"},
|
||||||
|
{ "send", 4, "options" },
|
||||||
{ "importprivkey", 2, "rescan" },
|
{ "importprivkey", 2, "rescan" },
|
||||||
{ "importelectrumwallet", 1, "index" },
|
{ "importelectrumwallet", 1, "index" },
|
||||||
{ "importaddress", 2, "rescan" },
|
{ "importaddress", 2, "rescan" },
|
||||||
|
@ -1160,7 +1160,7 @@ static RPCHelpMan estimatesmartfee()
|
|||||||
if (!request.params[1].isNull()) {
|
if (!request.params[1].isNull()) {
|
||||||
FeeEstimateMode fee_mode;
|
FeeEstimateMode fee_mode;
|
||||||
if (!FeeModeFromString(request.params[1].get_str(), 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;
|
if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false;
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,8 @@ BOOST_AUTO_TEST_CASE(ToStringTest)
|
|||||||
CFeeRate feeRate;
|
CFeeRate feeRate;
|
||||||
feeRate = CFeeRate(1);
|
feeRate = CFeeRate(1);
|
||||||
BOOST_CHECK_EQUAL(feeRate.ToString(), "0.00000001 DASH/kB");
|
BOOST_CHECK_EQUAL(feeRate.ToString(), "0.00000001 DASH/kB");
|
||||||
|
BOOST_CHECK_EQUAL(feeRate.ToString(FeeEstimateMode::DASH_KB), "0.00000001 DASH/kB");
|
||||||
|
BOOST_CHECK_EQUAL(feeRate.ToString(FeeEstimateMode::DUFF_B), "0.001 duff/B");
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
@ -40,8 +40,6 @@ const std::vector<std::pair<std::string, FeeEstimateMode>>& FeeModeMap()
|
|||||||
{"unset", FeeEstimateMode::UNSET},
|
{"unset", FeeEstimateMode::UNSET},
|
||||||
{"economical", FeeEstimateMode::ECONOMICAL},
|
{"economical", FeeEstimateMode::ECONOMICAL},
|
||||||
{"conservative", FeeEstimateMode::CONSERVATIVE},
|
{"conservative", FeeEstimateMode::CONSERVATIVE},
|
||||||
{(CURRENCY_UNIT + "/kB"), FeeEstimateMode::DASH_KB},
|
|
||||||
{(CURRENCY_ATOM + "/B"), FeeEstimateMode::DUFF_B},
|
|
||||||
};
|
};
|
||||||
return FEE_MODES;
|
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; });
|
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)
|
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode)
|
||||||
{
|
{
|
||||||
auto searchkey = ToUpper(mode_string);
|
auto searchkey = ToUpper(mode_string);
|
||||||
|
@ -13,5 +13,6 @@ enum class FeeReason;
|
|||||||
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode);
|
bool FeeModeFromString(const std::string& mode_string, FeeEstimateMode& fee_estimate_mode);
|
||||||
std::string StringForFeeReason(FeeReason reason);
|
std::string StringForFeeReason(FeeReason reason);
|
||||||
std::string FeeModes(const std::string& delimiter);
|
std::string FeeModes(const std::string& delimiter);
|
||||||
|
const std::string InvalidEstimateModeErrorMessage();
|
||||||
|
|
||||||
#endif // BITCOIN_UTIL_FEES_H
|
#endif // BITCOIN_UTIL_FEES_H
|
||||||
|
@ -52,8 +52,6 @@ using interfaces::FoundBlock;
|
|||||||
|
|
||||||
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
|
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
|
||||||
|
|
||||||
static const uint32_t WALLET_DASH_KB_TO_DUFF_B = COIN / 1000; // 1 duff / B = 0.00001 DASH / kB
|
|
||||||
|
|
||||||
static inline bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param) {
|
static inline bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param) {
|
||||||
bool can_avoid_reuse = wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
|
bool can_avoid_reuse = wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
|
||||||
bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
|
bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
|
||||||
@ -206,33 +204,41 @@ static std::string LabelFromValue(const UniValue& value)
|
|||||||
* Update coin control with fee estimation based on the given parameters
|
* Update coin control with fee estimation based on the given parameters
|
||||||
*
|
*
|
||||||
* @param[in] wallet Wallet reference
|
* @param[in] wallet Wallet reference
|
||||||
* @param[in,out] cc Coin control which is to be updated
|
* @param[in,out] cc Coin control to be updated
|
||||||
* @param[in] estimate_mode String value (e.g. "ECONOMICAL")
|
* @param[in] conf_target UniValue integer; confirmation target in blocks, values between 1 and 1008 are valid per policy/fees.h;
|
||||||
* @param[in] estimate_param Parameter (blocks to confirm, explicit fee rate, etc)
|
* if a fee_rate is present, 0 is allowed here as a no-op positional placeholder
|
||||||
* @throws a JSONRPCError if estimate_mode is unknown, or if estimate_param is missing when required
|
* @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/B;
|
||||||
|
* 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& wallet, CCoinControl& cc, const UniValue& estimate_mode, const UniValue& estimate_param)
|
static void SetFeeEstimateMode(const CWallet& wallet, CCoinControl& cc, const UniValue& conf_target, const UniValue& estimate_mode, const UniValue& fee_rate, bool override_min_fee)
|
||||||
{
|
{
|
||||||
if (!estimate_mode.isNull()) {
|
if (!fee_rate.isNull()) {
|
||||||
if (!FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) {
|
if (!conf_target.isNull() && conf_target.get_int() > 0) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
|
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 (!estimate_mode.isNull() && !estimate_mode.get_str().empty()) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and fee_rate");
|
||||||
}
|
}
|
||||||
|
CFeeRate fee_rate_in_sat_vb{CFeeRate(AmountFromValue(fee_rate), COIN)};
|
||||||
if (cc.m_fee_mode == FeeEstimateMode::DASH_KB || cc.m_fee_mode == FeeEstimateMode::DUFF_B) {
|
if (override_min_fee) {
|
||||||
if (estimate_param.isNull()) {
|
if (fee_rate_in_sat_vb <= CFeeRate(0)) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Selected estimate_mode %s requires a fee rate to be specified in conf_target", estimate_mode.get_str()));
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid fee_rate %s (must be greater than 0)", fee_rate_in_sat_vb.ToString(FeeEstimateMode::DUFF_B)));
|
||||||
}
|
}
|
||||||
|
cc.fOverrideFeeRate = true;
|
||||||
CAmount fee_rate = AmountFromValue(estimate_param);
|
|
||||||
if (cc.m_fee_mode == FeeEstimateMode::DUFF_B) {
|
|
||||||
fee_rate /= WALLET_DASH_KB_TO_DUFF_B;
|
|
||||||
}
|
}
|
||||||
|
cc.m_feerate = fee_rate_in_sat_vb;
|
||||||
cc.m_feerate = CFeeRate(fee_rate);
|
return;
|
||||||
|
}
|
||||||
} else if (!estimate_param.isNull()) {
|
if (!estimate_mode.isNull() && !FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) {
|
||||||
cc.m_confirm_target = ParseConfirmTarget(estimate_param, wallet.chain().estimateMaxBlocks());
|
throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
|
||||||
|
}
|
||||||
|
if (!conf_target.isNull()) {
|
||||||
|
cc.m_confirm_target = ParseConfirmTarget(conf_target, wallet.chain().estimateMaxBlocks());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,12 +432,12 @@ static RPCHelpMan sendtoaddress()
|
|||||||
"The recipient will receive less amount of Dash than you enter in the amount field."},
|
"The recipient will receive less amount of Dash than you enter in the amount field."},
|
||||||
{"use_is", RPCArg::Type::BOOL, /* default */ "false", "Deprecated and ignored"},
|
{"use_is", RPCArg::Type::BOOL, /* default */ "false", "Deprecated and ignored"},
|
||||||
{"use_cj", RPCArg::Type::BOOL, /* default */ "false", "Use CoinJoin funds only"},
|
{"use_cj", RPCArg::Type::BOOL, /* default */ "false", "Use CoinJoin funds only"},
|
||||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
|
||||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
|
||||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||||
" \"" + FeeModes("\"\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"
|
{"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."},
|
" 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 + "/B."},
|
||||||
{"verbose", RPCArg::Type::BOOL, /* default */ "false", "If true, return extra information about the transaction."},
|
{"verbose", RPCArg::Type::BOOL, /* default */ "false", "If true, return extra information about the transaction."},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -447,12 +453,17 @@ static RPCHelpMan sendtoaddress()
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
RPCExamples{
|
RPCExamples{
|
||||||
HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1")
|
"\nSend 0.1 Dash\n"
|
||||||
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\" \"seans outpost\"")
|
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1") +
|
||||||
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" true")
|
"\nSend 0.1 Dash with a confirmation target of 6 blocks in economical fee estimate mode using positional arguments\n"
|
||||||
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 0.00002 " + (CURRENCY_UNIT + "/kB"))
|
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\" \"sean's outpost\" false false false 6 economical") +
|
||||||
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 2 " + (CURRENCY_ATOM + "/B"))
|
"\nSend 0.1 Dash with a fee rate of 1 " + CURRENCY_ATOM + "/B, subtract fee from amount, use CoinJoin funds only, using positional arguments\n"
|
||||||
+ HelpExampleRpc("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\", 0.1, \"donation\", \"seans outpost\"")
|
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"drinks\" \"room77\" true false true 0 \"\" 1") +
|
||||||
|
"\nSend 0.2 Dash 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 Dash with a fee rate of 25 " + CURRENCY_ATOM + "/B 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 avoid_reuse=true comment=\"2 pizzas\" comment_to=\"jeremy\" verbose=true")
|
||||||
},
|
},
|
||||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
{
|
{
|
||||||
@ -487,7 +498,7 @@ static RPCHelpMan sendtoaddress()
|
|||||||
// We also enable partial spend avoidance if reuse avoidance is set.
|
// We also enable partial spend avoidance if reuse avoidance is set.
|
||||||
coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse;
|
coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse;
|
||||||
|
|
||||||
SetFeeEstimateMode(*pwallet, coin_control, request.params[8], request.params[7]);
|
SetFeeEstimateMode(*pwallet, coin_control, /* conf_target */ request.params[7], /* estimate_mode */ request.params[8], /* fee_rate */ request.params[10], /* override_min_fee */ false);
|
||||||
|
|
||||||
EnsureWalletIsUnlocked(*pwallet);
|
EnsureWalletIsUnlocked(*pwallet);
|
||||||
|
|
||||||
@ -501,7 +512,7 @@ static RPCHelpMan sendtoaddress()
|
|||||||
|
|
||||||
std::vector<CRecipient> recipients;
|
std::vector<CRecipient> recipients;
|
||||||
ParseRecipients(address_amounts, subtractFeeFromAmount, recipients);
|
ParseRecipients(address_amounts, subtractFeeFromAmount, recipients);
|
||||||
bool verbose = request.params[10].isNull() ? false: request.params[10].get_bool();
|
const bool verbose{request.params[11].isNull() ? false : request.params[11].get_bool()};
|
||||||
|
|
||||||
return SendMoney(*pwallet, coin_control, recipients, mapValue, verbose);
|
return SendMoney(*pwallet, coin_control, recipients, mapValue, verbose);
|
||||||
},
|
},
|
||||||
@ -922,10 +933,10 @@ static RPCHelpMan sendmany()
|
|||||||
},
|
},
|
||||||
{"use_is", RPCArg::Type::BOOL, /* default */ "false", "Deprecated and ignored"},
|
{"use_is", RPCArg::Type::BOOL, /* default */ "false", "Deprecated and ignored"},
|
||||||
{"use_cj", RPCArg::Type::BOOL, /* default */ "false", "Use CoinJoin funds only"},
|
{"use_cj", RPCArg::Type::BOOL, /* default */ "false", "Use CoinJoin funds only"},
|
||||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
|
||||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
|
||||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||||
" \"" + FeeModes("\"\n\"") + "\""},
|
" \"" + FeeModes("\"\n\"") + "\""},
|
||||||
|
{"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/B."},
|
||||||
{"verbose", RPCArg::Type::BOOL, /* default */ "false", "If true, return extra infomration about the transaction."},
|
{"verbose", RPCArg::Type::BOOL, /* default */ "false", "If true, return extra infomration about the transaction."},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -981,11 +992,11 @@ static RPCHelpMan sendmany()
|
|||||||
coin_control.UseCoinJoin(request.params[7].get_bool());
|
coin_control.UseCoinJoin(request.params[7].get_bool());
|
||||||
}
|
}
|
||||||
|
|
||||||
SetFeeEstimateMode(*pwallet, coin_control, request.params[9], request.params[8]);
|
SetFeeEstimateMode(*pwallet, coin_control, /* conf_target */ request.params[8], /* estimate_mode */ request.params[9], /* fee_rate */ request.params[10], /* override_min_fee */ false);
|
||||||
|
|
||||||
std::vector<CRecipient> recipients;
|
std::vector<CRecipient> recipients;
|
||||||
ParseRecipients(sendTo, subtractFeeFromAmount, recipients);
|
ParseRecipients(sendTo, subtractFeeFromAmount, recipients);
|
||||||
bool verbose = request.params[10].isNull() ? false : request.params[10].get_bool();
|
const bool verbose{request.params[11].isNull() ? false : request.params[11].get_bool()};
|
||||||
|
|
||||||
return SendMoney(*pwallet, coin_control, recipients, std::move(mapValue), verbose);
|
return SendMoney(*pwallet, coin_control, recipients, std::move(mapValue), verbose);
|
||||||
},
|
},
|
||||||
@ -3380,7 +3391,7 @@ static RPCHelpMan listunspent()
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, const UniValue& options, CCoinControl& coinControl)
|
void FundTransaction(CWallet& wallet, 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
|
// 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
|
// the user could have gotten from another RPC command prior to now
|
||||||
@ -3413,7 +3424,8 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
|
|||||||
{"lockUnspents", UniValueType(UniValue::VBOOL)},
|
{"lockUnspents", UniValueType(UniValue::VBOOL)},
|
||||||
{"lock_unspents", UniValueType(UniValue::VBOOL)},
|
{"lock_unspents", UniValueType(UniValue::VBOOL)},
|
||||||
{"locktime", UniValueType(UniValue::VNUM)},
|
{"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)},
|
{"psbt", UniValueType(UniValue::VBOOL)},
|
||||||
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
||||||
{"subtract_fee_from_outputs", UniValueType(UniValue::VARR)},
|
{"subtract_fee_from_outputs", UniValueType(UniValue::VARR)},
|
||||||
@ -3453,21 +3465,27 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 + "/B) and feeRate (" + CURRENCY_UNIT + "/kB)");
|
||||||
|
}
|
||||||
if (options.exists("conf_target")) {
|
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")) {
|
if (options.exists("estimate_mode")) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
|
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::DASH_KB)));
|
||||||
|
}
|
||||||
|
coinControl.m_feerate = fee_rate;
|
||||||
coinControl.fOverrideFeeRate = true;
|
coinControl.fOverrideFeeRate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.exists("subtractFeeFromOutputs") || options.exists("subtract_fee_from_outputs") )
|
if (options.exists("subtractFeeFromOutputs") || options.exists("subtract_fee_from_outputs") )
|
||||||
subtractFeeFromOutputs = (options.exists("subtract_fee_from_outputs") ? options["subtract_fee_from_outputs"] : options["subtractFeeFromOutputs"]).get_array();
|
subtractFeeFromOutputs = (options.exists("subtract_fee_from_outputs") ? options["subtract_fee_from_outputs"] : options["subtractFeeFromOutputs"]).get_array();
|
||||||
|
|
||||||
SetFeeEstimateMode(wallet, coinControl, options["estimate_mode"], options["conf_target"]);
|
SetFeeEstimateMode(wallet, coinControl, options["conf_target"], options["estimate_mode"], options["fee_rate"], override_min_fee);
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if options is null and not a bool
|
// if options is null and not a bool
|
||||||
@ -3526,7 +3544,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"
|
"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."},
|
"e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
|
||||||
{"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
|
{"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 + "/B."},
|
||||||
|
{"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_UNIT + "/kB."},
|
||||||
{"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "The integers.\n"
|
{"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "The integers.\n"
|
||||||
"The fee will be equally deducted from the amount of each specified output.\n"
|
"The fee will be equally deducted from the amount of each specified output.\n"
|
||||||
"Those recipients will receive less Dash than you enter in their corresponding amount field.\n"
|
"Those recipients will receive less Dash than you enter in their corresponding amount field.\n"
|
||||||
@ -3535,8 +3554,7 @@ static RPCHelpMan fundrawtransaction()
|
|||||||
{"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."},
|
{"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
|
||||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
|
||||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||||
" \"" + FeeModes("\"\n\"") + "\""},
|
" \"" + FeeModes("\"\n\"") + "\""},
|
||||||
},
|
},
|
||||||
@ -3578,7 +3596,7 @@ static RPCHelpMan fundrawtransaction()
|
|||||||
CCoinControl coin_control;
|
CCoinControl coin_control;
|
||||||
// Automatically select (additional) coins. Can be overridden by options.add_inputs.
|
// Automatically select (additional) coins. Can be overridden by options.add_inputs.
|
||||||
coin_control.m_add_inputs = true;
|
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);
|
UniValue result(UniValue::VOBJ);
|
||||||
result.pushKV("hex", EncodeHexTx(CTransaction(tx)));
|
result.pushKV("hex", EncodeHexTx(CTransaction(tx)));
|
||||||
@ -4180,10 +4198,10 @@ static RPCHelpMan send()
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
|
||||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
|
||||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||||
" \"" + FeeModes("\"\n\"") + "\""},
|
" \"" + FeeModes("\"\n\"") + "\""},
|
||||||
|
{"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/B."},
|
||||||
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
|
{"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."},
|
{"add_inputs", RPCArg::Type::BOOL, /* default */ "false", "If inputs are specified, automatically include more if they are not enough."},
|
||||||
@ -4193,10 +4211,10 @@ static RPCHelpMan send()
|
|||||||
{"add_to_wallet", RPCArg::Type::BOOL, /* default */ "true", "When false, returns a serialized transaction which will not be added to the wallet or broadcast"},
|
{"add_to_wallet", RPCArg::Type::BOOL, /* default */ "true", "When false, returns a serialized transaction which will not be added to the wallet or broadcast"},
|
||||||
{"change_address", RPCArg::Type::STR_HEX, /* default */ "pool address", "The Dash address to receive the change"},
|
{"change_address", RPCArg::Type::STR_HEX, /* default */ "pool address", "The Dash address to receive the change"},
|
||||||
{"change_position", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"},
|
{"change_position", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"},
|
||||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
|
||||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
|
||||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||||
" \"" + FeeModes("\"\n\"") + "\""},
|
" \"" + FeeModes("\"\n\"") + "\""},
|
||||||
|
{"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/B."},
|
||||||
{"include_watching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only.\n"
|
{"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"
|
"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."},
|
"e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
|
||||||
@ -4231,36 +4249,53 @@ static RPCHelpMan send()
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
RPCExamples{""
|
RPCExamples{""
|
||||||
"\nSend with a fee rate of 1 " + CURRENCY_ATOM + "/B\n"
|
"\nSend 0.1 Dash with a confirmation target of 6 blocks in economical fee estimate mode\n"
|
||||||
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 " + CURRENCY_ATOM + "/B\n") +
|
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 6 economical\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"
|
"Send 0.2 Dash with a fee rate of 1 " + CURRENCY_ATOM + "/B using positional arguments\n"
|
||||||
|
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.2}' 0 \"\" 1\n") +
|
||||||
|
"Send 0.2 Dash with a fee rate of 1 " + CURRENCY_ATOM + "/B using the options argument\n"
|
||||||
|
+ HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.2}' '{\"fee_rate\": 1}'\n") +
|
||||||
|
"Send 0.3 Dash with a fee rate of 25 " + CURRENCY_ATOM + "/B 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}]}'")
|
+ 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
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
{
|
{
|
||||||
RPCTypeCheck(request.params, {
|
RPCTypeCheck(request.params, {
|
||||||
UniValueType(), // ARR or OBJ, checked later
|
UniValueType(), // outputs (ARR or OBJ, checked later)
|
||||||
UniValue::VNUM,
|
UniValue::VNUM, // conf_target
|
||||||
UniValue::VSTR,
|
UniValue::VSTR, // estimate_mode
|
||||||
UniValue::VOBJ
|
UniValue::VNUM, // fee_rate
|
||||||
|
UniValue::VOBJ, // options
|
||||||
}, true
|
}, true
|
||||||
);
|
);
|
||||||
|
|
||||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||||
if (!pwallet) return NullUniValue;
|
if (!pwallet) return NullUniValue;
|
||||||
|
|
||||||
UniValue options = request.params[3];
|
UniValue options{request.params[4].isNull() ? UniValue::VOBJ : request.params[4]};
|
||||||
if (options.exists("feeRate") || options.exists("fee_rate") || options.exists("estimate_mode") || options.exists("conf_target")) {
|
if (options.exists("estimate_mode") || options.exists("conf_target")) {
|
||||||
if (!request.params[1].isNull() || !request.params[2].isNull()) {
|
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 {
|
} else {
|
||||||
options.pushKV("conf_target", request.params[1]);
|
options.pushKV("conf_target", request.params[1]);
|
||||||
options.pushKV("estimate_mode", request.params[2]);
|
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"))) {
|
if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (options["estimate_mode"].get_str() == "unset"))) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Specify estimate_mode");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Specify estimate_mode");
|
||||||
}
|
}
|
||||||
|
if (options.exists("feeRate")) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Use fee_rate (" + CURRENCY_ATOM + "/B) instead of feeRate");
|
||||||
|
}
|
||||||
if (options.exists("changeAddress")) {
|
if (options.exists("changeAddress")) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_address");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_address");
|
||||||
}
|
}
|
||||||
@ -4286,7 +4321,7 @@ static RPCHelpMan send()
|
|||||||
// Automatically select coins, unless at least one is manually selected. Can
|
// Automatically select coins, unless at least one is manually selected. Can
|
||||||
// be overridden by options.add_inputs.
|
// be overridden by options.add_inputs.
|
||||||
coin_control.m_add_inputs = rawTx.vin.size() == 0;
|
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;
|
bool add_to_wallet = true;
|
||||||
if (options.exists("add_to_wallet")) {
|
if (options.exists("add_to_wallet")) {
|
||||||
@ -4530,6 +4565,7 @@ static RPCHelpMan walletcreatefundedpsbt()
|
|||||||
{"changePosition", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"},
|
{"changePosition", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"},
|
||||||
{"includeWatching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only"},
|
{"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"},
|
{"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"},
|
||||||
|
{"fee_rate", RPCArg::Type::AMOUNT, /* default */ "not set, fall back to wallet fee estimation", "Specify a fee rate in " + CURRENCY_ATOM + "/B."},
|
||||||
{"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"},
|
{"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"},
|
||||||
{"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "The outputs to subtract the fee from.\n"
|
{"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"
|
"The fee will be equally deducted from the amount of each specified output.\n"
|
||||||
@ -4539,8 +4575,7 @@ static RPCHelpMan walletcreatefundedpsbt()
|
|||||||
{"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."},
|
{"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target (in blocks)\n"
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet -txconfirmtarget", "Confirmation target in blocks"},
|
||||||
"or fee rate (for " + CURRENCY_UNIT + "/kB and " + CURRENCY_ATOM + "/B estimate modes)"},
|
|
||||||
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
||||||
" \"" + FeeModes("\"\n\"") + "\""},
|
" \"" + FeeModes("\"\n\"") + "\""},
|
||||||
},
|
},
|
||||||
@ -4585,7 +4620,7 @@ static RPCHelpMan walletcreatefundedpsbt()
|
|||||||
// Automatically select coins, unless at least one is manually selected. Can
|
// Automatically select coins, unless at least one is manually selected. Can
|
||||||
// be overridden by options.add_inputs.
|
// be overridden by options.add_inputs.
|
||||||
coin_control.m_add_inputs = rawTx.vin.size() == 0;
|
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
|
// Make a blank psbt
|
||||||
PartiallySignedTransaction psbtx{rawTx};
|
PartiallySignedTransaction psbtx{rawTx};
|
||||||
|
@ -3555,7 +3555,7 @@ bool CWallet::CreateTransactionInternal(
|
|||||||
// Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
|
// Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
|
||||||
// provided one
|
// provided one
|
||||||
if (coin_control.m_feerate && nFeeRateNeeded > *coin_control.m_feerate) {
|
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::DUFF_B), nFeeRateNeeded.ToString(FeeEstimateMode::DUFF_B));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class EstimateFeeTest(BitcoinTestFramework):
|
|||||||
|
|
||||||
# wrong type for estimatesmartfee(estimate_mode)
|
# 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(-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)
|
# wrong type for estimaterawfee(threshold)
|
||||||
assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 1, 'foo')
|
assert_raises_rpc_error(-3, "Expected type number, got string", self.nodes[0].estimaterawfee, 1, 'foo')
|
||||||
|
@ -94,7 +94,6 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||||||
self.test_op_return()
|
self.test_op_return()
|
||||||
self.test_watchonly()
|
self.test_watchonly()
|
||||||
self.test_all_watched_funds()
|
self.test_all_watched_funds()
|
||||||
self.test_feerate_with_conf_target_and_estimate_mode()
|
|
||||||
self.test_option_feerate()
|
self.test_option_feerate()
|
||||||
self.test_address_reuse()
|
self.test_address_reuse()
|
||||||
self.test_option_subtract_fee_from_outputs()
|
self.test_option_subtract_fee_from_outputs()
|
||||||
@ -704,74 +703,86 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||||||
wwatch.unloadwallet()
|
wwatch.unloadwallet()
|
||||||
|
|
||||||
def test_option_feerate(self):
|
def test_option_feerate(self):
|
||||||
self.log.info("Test fundrawtxn feeRate option")
|
self.log.info("Test fundrawtxn with explicit fee rates (fee_rate duff/B and feeRate DASH/kB)")
|
||||||
|
|
||||||
# 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")
|
|
||||||
node = self.nodes[3]
|
node = self.nodes[3]
|
||||||
# Make sure there is exactly one input so coin selection can't skew the result.
|
# 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 = []
|
inputs = []
|
||||||
outputs = {node.getnewaddress() : 1}
|
outputs = {node.getnewaddress() : 1}
|
||||||
rawtx = node.createrawtransaction(inputs, outputs)
|
rawtx = node.createrawtransaction(inputs, outputs)
|
||||||
|
|
||||||
for unit, fee_rate in {"dash/kb": 0.1, "duff/b": 10000}.items():
|
result = node.fundrawtransaction(rawtx) # uses self.min_relay_tx_fee (set by settxfee)
|
||||||
self.log.info("Test fundrawtxn with conf_target {} estimate_mode {} produces expected fee".format(fee_rate, unit))
|
btc_kvb_to_sat_vb = 100000 # (1e5)
|
||||||
# With no arguments passed, expect fee of 225 sats/b.
|
result1 = node.fundrawtransaction(rawtx, {"fee_rate": 2 * btc_kvb_to_sat_vb * self.min_relay_tx_fee})
|
||||||
assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000225, vspan=0.00000001)
|
result2 = node.fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee})
|
||||||
# Expect fee to be 10,000x higher when explicit fee 10,000x greater is specified.
|
result3 = node.fundrawtransaction(rawtx, {"fee_rate": 10 * btc_kvb_to_sat_vb * self.min_relay_tx_fee})
|
||||||
result = node.fundrawtransaction(rawtx, {"conf_target": fee_rate, "estimate_mode": unit})
|
result4 = node.fundrawtransaction(rawtx, {"feeRate": 10 * self.min_relay_tx_fee})
|
||||||
assert_approx(result["fee"], vexp=0.0225, vspan=0.0001)
|
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": "duff/b"}.items():
|
# With no arguments passed, expect fee of 225 satoshis.
|
||||||
self.log.info("Test fundrawtxn raises RPC error if both feeRate and {} are passed".format(field))
|
assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000225, vspan=0.00000001)
|
||||||
assert_raises_rpc_error(
|
# Expect fee to be 10,000x higher when an explicit fee rate 10,000x greater is specified.
|
||||||
-8, "Cannot specify both {} and feeRate".format(field),
|
result = node.fundrawtransaction(rawtx, {"fee_rate": 10000})
|
||||||
lambda: node.fundrawtransaction(rawtx, {"feeRate": 0.1, field: fee_rate}))
|
assert_approx(result["fee"], vexp=0.0225, vspan=0.0001)
|
||||||
|
|
||||||
self.log.info("Test fundrawtxn with invalid estimate_mode settings")
|
self.log.info("Test fundrawtxn with invalid estimate_mode settings")
|
||||||
for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
|
for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
|
||||||
assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
|
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}))
|
node.fundrawtransaction, rawtx, {"estimate_mode": v, "conf_target": 0.1, "add_inputs": True})
|
||||||
for mode in ["foo", Decimal("3.141592")]:
|
for mode in ["", "foo", Decimal("3.141592")]:
|
||||||
assert_raises_rpc_error(-8, "Invalid estimate_mode parameter",
|
assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
|
||||||
lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": 0.1}))
|
node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": 0.1, "add_inputs": True})
|
||||||
|
|
||||||
self.log.info("Test fundrawtxn with invalid conf_target settings")
|
self.log.info("Test fundrawtxn with invalid conf_target settings")
|
||||||
for mode in ["unset", "economical", "conservative", "dash/kb", "duff/b"]:
|
for mode in ["unset", "economical", "conservative"]:
|
||||||
self.log.debug("{}".format(mode))
|
self.log.debug("{}".format(mode))
|
||||||
for k, v in {"string": "", "object": {"foo": "bar"}}.items():
|
for k, v in {"string": "", "object": {"foo": "bar"}}.items():
|
||||||
assert_raises_rpc_error(-3, "Expected type number for conf_target, got {}".format(k),
|
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}))
|
node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": v, "add_inputs": True})
|
||||||
if mode in ["dash/kb", "duff/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 DASH/kB) is lower than the minimum fee rate setting (0.00001000 DASH/kB)",
|
|
||||||
lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": 0}))
|
|
||||||
else:
|
|
||||||
for n in [-1, 0, 1009]:
|
for n in [-1, 0, 1009]:
|
||||||
assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008",
|
assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
|
||||||
lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": mode, "conf_target": n}))
|
node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": n, "add_inputs": True})
|
||||||
|
|
||||||
for unit, fee_rate in {"duff/B": 0.99999999, "DASH/kB": 0.00000999}.items():
|
self.log.info("Test invalid fee rate settings")
|
||||||
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(-8, "Invalid fee_rate 0.000 duff/B (must be greater than 0)",
|
||||||
assert_raises_rpc_error(-4, "Fee rate (0.00000999 DASH/kB) is lower than the minimum fee rate setting (0.00001000 DASH/kB)",
|
node.fundrawtransaction, rawtx, {"fee_rate": 0, "add_inputs": True})
|
||||||
lambda: self.nodes[1].fundrawtransaction(rawtx, {"estimate_mode": unit, "conf_target": fee_rate, "add_inputs": True}))
|
assert_raises_rpc_error(-8, "Invalid feeRate 0.00000000 DASH/kB (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 (duff/B) and feeRate (DASH/kB)",
|
||||||
|
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):
|
def test_address_reuse(self):
|
||||||
"""Test no address reuse occurs."""
|
"""Test no address reuse occurs."""
|
||||||
@ -799,12 +810,32 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||||||
outputs = {self.nodes[2].getnewaddress(): 1}
|
outputs = {self.nodes[2].getnewaddress(): 1}
|
||||||
rawtx = self.nodes[3].createrawtransaction(inputs, outputs)
|
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)
|
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": []}), # empty subtraction list
|
||||||
self.nodes[3].fundrawtransaction(rawtx, {"subtractFeeFromOutputs": [0]}), # uses self.min_relay_tx_fee (set by settxfee)
|
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}),
|
||||||
self.nodes[3].fundrawtransaction(rawtx, {"feeRate": 2 * self.min_relay_tx_fee, "subtractFeeFromOutputs": [0]}),]
|
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]
|
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)]
|
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)]
|
change = [d['vout'][r['changepos']]['value'] for d, r in zip(dec_tx, result)]
|
||||||
|
@ -108,60 +108,74 @@ class PSBTTest(BitcoinTestFramework):
|
|||||||
assert_equal(walletprocesspsbt_out['complete'], True)
|
assert_equal(walletprocesspsbt_out['complete'], True)
|
||||||
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
|
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
|
||||||
|
|
||||||
self.log.info("Test walletcreatefundedpsbt feeRate of 0.1 DASH/kB produces a total fee at or slightly below -maxtxfee (~0.06650000)")
|
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)")
|
||||||
res = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.1, "add_inputs": True})
|
res1 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"fee_rate": 10000, "add_inputs": True})
|
||||||
assert_approx(res["fee"], 0.04, 0.03)
|
assert_approx(res1["fee"], 0.04, 0.005)
|
||||||
|
res2 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.1, "add_inputs": True})
|
||||||
|
assert_approx(res2["fee"], 0.04, 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.00000224, 0.0000001)
|
||||||
|
res4 = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"feeRate": 0.00000999, "add_inputs": True})
|
||||||
|
assert_approx(res4["fee"], 0.00000224, 0.0000001)
|
||||||
|
|
||||||
self.log.info("Test walletcreatefundedpsbt explicit fee rate with conf_target and estimate_mode")
|
self.log.info("Test invalid fee rate settings")
|
||||||
for unit, fee_rate in {"dash/kb": 0.1, "duff/b": 10000}.items():
|
assert_raises_rpc_error(-8, "Invalid fee_rate 0.000 duff/B (must be greater than 0)",
|
||||||
fee = self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"conf_target": fee_rate, "estimate_mode": unit, "add_inputs": True})["fee"]
|
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": 0, "add_inputs": True})
|
||||||
self.log.info("- conf_target {}, estimate_mode {} produces fee {} at or slightly below -maxtxfee (~0.05290000)".format(fee_rate, unit, fee))
|
assert_raises_rpc_error(-8, "Invalid feeRate 0.00000000 DASH/kB (must be greater than 0)",
|
||||||
assert_approx(fee, vexp=0.04, vspan=0.03)
|
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": "duff/b"}.items():
|
self.log.info("- raises RPC error if both feeRate and fee_rate are passed")
|
||||||
self.log.info("- raises RPC error if both feeRate and {} are passed".format(field))
|
assert_raises_rpc_error(-8, "Cannot specify both fee_rate (duff/B) and feeRate (DASH/kB)",
|
||||||
assert_raises_rpc_error(-8, "Cannot specify both {} and feeRate".format(field),
|
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"fee_rate": 0.1, "feeRate": 0.1, "add_inputs": True})
|
||||||
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 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")
|
self.log.info("- raises RPC error with invalid estimate_mode settings")
|
||||||
for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
|
for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
|
||||||
assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
|
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}))
|
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": v, "conf_target": 0.1, "add_inputs": True})
|
||||||
for mode in ["foo", Decimal("3.141592")]:
|
for mode in ["", "foo", Decimal("3.141592")]:
|
||||||
assert_raises_rpc_error(-8, "Invalid estimate_mode parameter",
|
assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
|
||||||
lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": mode, "conf_target": 0.1, "add_inputs": True}))
|
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 ["DUFF/B", "DASH/KB"]:
|
|
||||||
assert_raises_rpc_error(-8, "Selected estimate_mode {} requires a fee rate to be specified in conf_target".format(unit),
|
|
||||||
lambda: self.nodes[1].walletcreatefundedpsbt(inputs, outputs, 0, {"estimate_mode": unit}))
|
|
||||||
|
|
||||||
self.log.info("- raises RPC error with invalid conf_target settings")
|
self.log.info("- raises RPC error with invalid conf_target settings")
|
||||||
for mode in ["unset", "economical", "conservative", "dash/kb", "duff/b"]:
|
for mode in ["unset", "economical", "conservative"]:
|
||||||
self.log.debug("{}".format(mode))
|
self.log.debug("{}".format(mode))
|
||||||
for k, v in {"string": "", "object": {"foo": "bar"}}.items():
|
for k, v in {"string": "", "object": {"foo": "bar"}}.items():
|
||||||
assert_raises_rpc_error(-3, "Expected type number for conf_target, got {}".format(k),
|
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}))
|
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": mode, "conf_target": v, "add_inputs": True})
|
||||||
if mode in ["dash/kb", "duff/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 DASH/kB) is lower than the minimum fee rate setting (0.00001000 DASH/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]:
|
for n in [-1, 0, 1009]:
|
||||||
assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008",
|
assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
|
||||||
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": n, "add_inputs": True})
|
||||||
|
|
||||||
for unit, fee_rate in {"DUFF/B": 0.99999999, "DASH/KB": 0.00000999}.items():
|
self.log.info("Test walletcreatefundedpsbt with too-high fee rate produces total fee well above -maxtxfee and raises RPC error")
|
||||||
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 DASH/kB) is lower than the minimum fee rate setting (0.00001000 DASH/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 DASH/kB produces total fee well above -maxtxfee and raises RPC error")
|
|
||||||
# previously this was silently capped at -maxtxfee
|
# previously this was silently capped at -maxtxfee
|
||||||
for bool_add, outputs_array in {True: outputs, False: [{self.nodes[1].getnewaddress(): 1}]}.items():
|
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)",
|
msg = "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)"
|
||||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs_array, 0, {"feeRate": 10, "add_inputs": bool_add})
|
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")
|
self.log.info("Test various PSBT operations")
|
||||||
# partially sign multisig things with node 1
|
# partially sign multisig things with node 1
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
"""Test the wallet."""
|
"""Test the wallet."""
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
from test_framework.blocktools import COINBASE_MATURITY
|
from test_framework.blocktools import COINBASE_MATURITY
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
@ -16,6 +17,8 @@ from test_framework.util import (
|
|||||||
)
|
)
|
||||||
from test_framework.wallet_util import test_address
|
from test_framework.wallet_util import test_address
|
||||||
|
|
||||||
|
OUT_OF_RANGE = "Amount out of range"
|
||||||
|
|
||||||
|
|
||||||
class WalletTest(BitcoinTestFramework):
|
class WalletTest(BitcoinTestFramework):
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
@ -50,6 +53,9 @@ class WalletTest(BitcoinTestFramework):
|
|||||||
assert_fee_amount(fee, tx_size, fee_per_byte * 1000)
|
assert_fee_amount(fee, tx_size, fee_per_byte * 1000)
|
||||||
return curr_balance
|
return curr_balance
|
||||||
|
|
||||||
|
def get_vsize(self, txn):
|
||||||
|
return self.nodes[0].decoderawtransaction(txn)['size']
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
|
|
||||||
# Check that there's no UTXO on none of the nodes
|
# Check that there's no UTXO on none of the nodes
|
||||||
@ -79,7 +85,7 @@ class WalletTest(BitcoinTestFramework):
|
|||||||
assert_equal(len(self.nodes[1].listunspent()), 1)
|
assert_equal(len(self.nodes[1].listunspent()), 1)
|
||||||
assert_equal(len(self.nodes[2].listunspent()), 0)
|
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"]
|
confirmed_txid, confirmed_index = utxos[0]["txid"], utxos[0]["vout"]
|
||||||
# First, outputs that are unspent both in the chain and in the
|
# First, outputs that are unspent both in the chain and in the
|
||||||
# mempool should appear with or without include_mempool
|
# mempool should appear with or without include_mempool
|
||||||
@ -93,7 +99,7 @@ class WalletTest(BitcoinTestFramework):
|
|||||||
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 110)
|
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 110)
|
||||||
mempool_txid = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 100)
|
mempool_txid = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 100)
|
||||||
|
|
||||||
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
|
# utxo spent in mempool should be visible if you exclude mempool
|
||||||
# but invisible if you include mempool
|
# but invisible if you include mempool
|
||||||
txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, False)
|
txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, False)
|
||||||
@ -239,67 +245,45 @@ class WalletTest(BitcoinTestFramework):
|
|||||||
assert_equal(self.nodes[2].getbalance(), node_2_bal)
|
assert_equal(self.nodes[2].getbalance(), node_2_bal)
|
||||||
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('100'), fee_per_byte, count_bytes(self.nodes[2].gettransaction(txid)['hex']))
|
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('100'), fee_per_byte, count_bytes(self.nodes[2].gettransaction(txid)['hex']))
|
||||||
|
|
||||||
self.start_node(3, self.nodes[3].extra_args)
|
self.log.info("Test sendmany with fee_rate param (explicit fee rate in duff/B)")
|
||||||
self.connect_nodes(0, 3)
|
fee_rate_sat_vb = 2
|
||||||
self.log.info("Test case-insensitive explicit fee rate (sendmany as DASH/kB)")
|
fee_rate_btc_kvb = fee_rate_sat_vb * 1e3 / 1e8
|
||||||
# Throw if no conf_target provided
|
explicit_fee_rate_btc_kvb = Decimal(fee_rate_btc_kvb) / 1000
|
||||||
assert_raises_rpc_error(-8, "Selected estimate_mode dash/kB requires a fee rate to be specified in conf_target",
|
|
||||||
self.nodes[2].sendmany,
|
|
||||||
amounts={ address: 10 },
|
|
||||||
estimate_mode='dash/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='dash/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='dash/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, count_bytes(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 case-insensitive explicit fee rate (sendmany as duff/B)")
|
# Passing conf_target 0, estimate_mode "" as placeholder arguments should allow fee_rate to apply.
|
||||||
# Throw if no conf_target provided
|
txid = self.nodes[2].sendmany(amounts={address: 10}, conf_target=0, estimate_mode="", fee_rate=fee_rate_sat_vb)
|
||||||
assert_raises_rpc_error(-8, "Selected estimate_mode duff/b requires a fee rate to be specified in conf_target",
|
|
||||||
self.nodes[2].sendmany,
|
|
||||||
amounts={ address: 10 },
|
|
||||||
estimate_mode='duff/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='duff/b')
|
|
||||||
fee_duff_per_b = 2
|
|
||||||
fee_per_kb = fee_duff_per_b / 100000.0
|
|
||||||
explicit_fee_per_byte = Decimal(fee_per_kb) / 1000
|
|
||||||
txid = self.nodes[2].sendmany(
|
|
||||||
amounts={ address: 10 },
|
|
||||||
conf_target=fee_duff_per_b,
|
|
||||||
estimate_mode='duff/b',
|
|
||||||
)
|
|
||||||
self.nodes[2].generate(1)
|
self.nodes[2].generate(1)
|
||||||
self.sync_all(self.nodes[0:3])
|
self.sync_all(self.nodes[0:3])
|
||||||
balance = self.nodes[2].getbalance()
|
balance = self.nodes[2].getbalance()
|
||||||
node_2_bal = self.check_fee_amount(balance, node_2_bal - Decimal('10'), explicit_fee_per_byte, count_bytes(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)
|
assert_equal(balance, node_2_bal)
|
||||||
node_0_bal += Decimal('10')
|
node_0_bal += Decimal('10')
|
||||||
assert_equal(self.nodes[0].getbalance(), node_0_bal)
|
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.
|
# Test setting explicit fee rate just below the minimum.
|
||||||
for unit, fee_rate in {"DASH/kB": 0.00000999, "duff/B": 0.99999999}.items():
|
self.log.info("Test sendmany raises 'fee rate too low' if fee_rate of 0.99999999 is passed")
|
||||||
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.999 duff/B) is lower than the minimum fee rate setting (1.000 duff/B)",
|
||||||
assert_raises_rpc_error(-6, "Fee rate (0.00000999 DASH/kB) is lower than the minimum fee rate setting (0.00001000 DASH/kB)",
|
self.nodes[2].sendmany, amounts={address: 10}, fee_rate=0.99999999)
|
||||||
self.nodes[2].sendmany, amounts={address: 10}, estimate_mode=unit, conf_target=fee_rate)
|
|
||||||
|
self.log.info("Test sendmany raises if fee_rate of 0 or -1 is passed")
|
||||||
|
assert_raises_rpc_error(-6, "Fee rate (0.000 duff/B) is lower than the minimum fee rate setting (1.000 duff/B)",
|
||||||
|
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)
|
||||||
|
self.sync_all()
|
||||||
|
|
||||||
# check if we can list zero value tx as available coins
|
# check if we can list zero value tx as available coins
|
||||||
# 1. create raw_tx
|
# 1. create raw_tx
|
||||||
@ -328,7 +312,7 @@ class WalletTest(BitcoinTestFramework):
|
|||||||
assert_equal(uTx['amount'], Decimal('0'))
|
assert_equal(uTx['amount'], Decimal('0'))
|
||||||
assert found
|
assert found
|
||||||
|
|
||||||
# do some -walletbroadcast tests
|
self.log.info("Test -walletbroadcast")
|
||||||
self.stop_nodes()
|
self.stop_nodes()
|
||||||
self.start_node(0, ["-walletbroadcast=0"])
|
self.start_node(0, ["-walletbroadcast=0"])
|
||||||
self.start_node(1, ["-walletbroadcast=0"])
|
self.start_node(1, ["-walletbroadcast=0"])
|
||||||
@ -388,7 +372,7 @@ class WalletTest(BitcoinTestFramework):
|
|||||||
|
|
||||||
# General checks for errors from incorrect inputs
|
# General checks for errors from incorrect inputs
|
||||||
# This will raise an exception because the amount is negative
|
# 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
|
# 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")
|
assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
|
||||||
@ -430,78 +414,43 @@ class WalletTest(BitcoinTestFramework):
|
|||||||
self.nodes[0].generate(1)
|
self.nodes[0].generate(1)
|
||||||
self.sync_all(self.nodes[0:3])
|
self.sync_all(self.nodes[0:3])
|
||||||
|
|
||||||
self.log.info("Test case-insensitive explicit fee rate (sendtoaddress as DASH/kB)")
|
self.log.info("Test sendtoaddress with fee_rate param (explicit fee rate in sat/vB)")
|
||||||
self.nodes[0].generate(1)
|
|
||||||
self.sync_all(self.nodes[0:3])
|
|
||||||
prebalance = self.nodes[2].getbalance()
|
prebalance = self.nodes[2].getbalance()
|
||||||
assert prebalance > 2
|
assert prebalance > 2
|
||||||
address = self.nodes[1].getnewaddress()
|
address = self.nodes[1].getnewaddress()
|
||||||
# Throw if no conf_target provided
|
amount = 3
|
||||||
assert_raises_rpc_error(-8, "Selected estimate_mode dash/Kb requires a fee rate to be specified in conf_target",
|
fee_rate_sat_vb = 2
|
||||||
self.nodes[2].sendtoaddress,
|
fee_rate_btc_kvb = fee_rate_sat_vb * 1e3 / 1e8
|
||||||
address=address,
|
|
||||||
amount=1.0,
|
# Passing conf_target 0, estimate_mode "" as placeholder arguments should allow fee_rate to apply.
|
||||||
estimate_mode='dash/Kb')
|
txid = self.nodes[2].sendtoaddress(address=address, amount=amount, conf_target=0, estimate_mode="", fee_rate=fee_rate_sat_vb)
|
||||||
# Throw if negative feerate
|
tx_size = self.get_vsize(self.nodes[2].gettransaction(txid)['hex'])
|
||||||
assert_raises_rpc_error(-3, "Amount out of range",
|
|
||||||
self.nodes[2].sendtoaddress,
|
|
||||||
address=address,
|
|
||||||
amount=1.0,
|
|
||||||
conf_target=-1,
|
|
||||||
estimate_mode='dash/kb')
|
|
||||||
txid = self.nodes[2].sendtoaddress(
|
|
||||||
address=address,
|
|
||||||
amount=1.0,
|
|
||||||
conf_target=0.00002500,
|
|
||||||
estimate_mode='dash/kb',
|
|
||||||
)
|
|
||||||
tx_size = count_bytes(self.nodes[2].gettransaction(txid)['hex'])
|
|
||||||
self.sync_all(self.nodes[0:3])
|
|
||||||
self.nodes[0].generate(1)
|
self.nodes[0].generate(1)
|
||||||
self.sync_all(self.nodes[0:3])
|
self.sync_all(self.nodes[0:3])
|
||||||
postbalance = self.nodes[2].getbalance()
|
postbalance = self.nodes[2].getbalance()
|
||||||
fee = prebalance - postbalance - Decimal('1')
|
fee = prebalance - postbalance - Decimal(amount)
|
||||||
assert_fee_amount(fee, tx_size, Decimal('0.00002500'))
|
assert_fee_amount(fee, tx_size, Decimal(fee_rate_btc_kvb))
|
||||||
|
|
||||||
self.sync_all(self.nodes[0:3])
|
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)
|
||||||
self.log.info("Test case-insensitive explicit fee rate (sendtoaddress as duff/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 duff/b requires a fee rate to be specified in conf_target",
|
|
||||||
self.nodes[2].sendtoaddress,
|
|
||||||
address=address,
|
|
||||||
amount=1.0,
|
|
||||||
estimate_mode='duff/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='duff/b')
|
|
||||||
txid = self.nodes[2].sendtoaddress(
|
|
||||||
address=address,
|
|
||||||
amount=1.0,
|
|
||||||
conf_target=2,
|
|
||||||
estimate_mode='duff/B',
|
|
||||||
)
|
|
||||||
tx_size = count_bytes(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'))
|
|
||||||
|
|
||||||
# Test setting explicit fee rate just below the minimum.
|
# Test setting explicit fee rate just below the minimum.
|
||||||
for unit, fee_rate in {"DASH/kB": 0.00000999, "sat/B": 0.99999999}.items():
|
self.log.info("Test sendtoaddress raises 'fee rate too low' if fee_rate of 0.99999999 is passed")
|
||||||
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.999 duff/B) is lower than the minimum fee rate setting (1.000 duff/B)",
|
||||||
assert_raises_rpc_error(-6, "Fee rate (0.00000999 DASH/kB) is lower than the minimum fee rate setting (0.00001000 DASH/kB)",
|
self.nodes[2].sendtoaddress, address=address, amount=1, fee_rate=0.999)
|
||||||
self.nodes[2].sendtoaddress, address=address, amount=1, estimate_mode=unit, conf_target=fee_rate)
|
|
||||||
|
self.log.info("Test sendtoaddress raises if fee_rate of 0 or -1 is passed")
|
||||||
|
assert_raises_rpc_error(-6, "Fee rate (0.000 duff/B) is lower than the minimum fee rate setting (1.000 duff/B)",
|
||||||
|
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], ["dash/kb", "duff/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
|
# 2. Import address from node2 to node1
|
||||||
self.nodes[1].importaddress(address_to_import)
|
self.nodes[1].importaddress(address_to_import)
|
||||||
@ -559,7 +508,7 @@ class WalletTest(BitcoinTestFramework):
|
|||||||
]
|
]
|
||||||
chainlimit = 6
|
chainlimit = 6
|
||||||
for m in maintenance:
|
for m in maintenance:
|
||||||
self.log.info("check " + m)
|
self.log.info("Test " + m)
|
||||||
self.stop_nodes()
|
self.stop_nodes()
|
||||||
# set lower ancestor limit for later
|
# set lower ancestor limit for later
|
||||||
self.start_node(0, [m, "-limitancestorcount=" + str(chainlimit)])
|
self.start_node(0, [m, "-limitancestorcount=" + str(chainlimit)])
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
"""Test the send RPC command."""
|
"""Test the send RPC command."""
|
||||||
|
|
||||||
from decimal import Decimal, getcontext
|
from decimal import Decimal, getcontext
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
from test_framework.authproxy import JSONRPCException
|
from test_framework.authproxy import JSONRPCException
|
||||||
from test_framework.descriptors import descsum_create
|
from test_framework.descriptors import descsum_create
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
@ -12,7 +14,7 @@ from test_framework.util import (
|
|||||||
assert_equal,
|
assert_equal,
|
||||||
assert_fee_amount,
|
assert_fee_amount,
|
||||||
assert_greater_than,
|
assert_greater_than,
|
||||||
assert_raises_rpc_error
|
assert_raises_rpc_error,
|
||||||
)
|
)
|
||||||
|
|
||||||
class WalletSendTest(BitcoinTestFramework):
|
class WalletSendTest(BitcoinTestFramework):
|
||||||
@ -29,8 +31,8 @@ class WalletSendTest(BitcoinTestFramework):
|
|||||||
self.skip_if_no_wallet()
|
self.skip_if_no_wallet()
|
||||||
|
|
||||||
def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,
|
def test_send(self, from_wallet, to_wallet=None, amount=None, data=None,
|
||||||
arg_conf_target=None, arg_estimate_mode=None,
|
arg_conf_target=None, arg_estimate_mode=None, arg_fee_rate=None,
|
||||||
conf_target=None, estimate_mode=None, add_to_wallet=None, psbt=None,
|
conf_target=None, estimate_mode=None, fee_rate=None, add_to_wallet=None, psbt=None,
|
||||||
inputs=None, add_inputs=None, include_unsafe=None, change_address=None, change_position=None,
|
inputs=None, add_inputs=None, include_unsafe=None, change_address=None, change_position=None,
|
||||||
include_watching=None, locktime=None, lock_unspents=None, subtract_fee_from_outputs=None,
|
include_watching=None, locktime=None, lock_unspents=None, subtract_fee_from_outputs=None,
|
||||||
expect_error=None):
|
expect_error=None):
|
||||||
@ -66,6 +68,8 @@ class WalletSendTest(BitcoinTestFramework):
|
|||||||
options["conf_target"] = conf_target
|
options["conf_target"] = conf_target
|
||||||
if estimate_mode is not None:
|
if estimate_mode is not None:
|
||||||
options["estimate_mode"] = estimate_mode
|
options["estimate_mode"] = estimate_mode
|
||||||
|
if fee_rate is not None:
|
||||||
|
options["fee_rate"] = fee_rate
|
||||||
if inputs is not None:
|
if inputs is not None:
|
||||||
options["inputs"] = inputs
|
options["inputs"] = inputs
|
||||||
if add_inputs is not None:
|
if add_inputs is not None:
|
||||||
@ -89,18 +93,19 @@ class WalletSendTest(BitcoinTestFramework):
|
|||||||
options = None
|
options = None
|
||||||
|
|
||||||
if expect_error is 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:
|
else:
|
||||||
try:
|
try:
|
||||||
assert_raises_rpc_error(expect_error[0], expect_error[1], from_wallet.send,
|
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:
|
except AssertionError:
|
||||||
# Provide debug info if the test fails
|
# Provide debug info if the test fails
|
||||||
self.log.error("Unexpected successful result:")
|
self.log.error("Unexpected successful result:")
|
||||||
self.log.error(arg_conf_target)
|
self.log.error(arg_conf_target)
|
||||||
self.log.error(arg_estimate_mode)
|
self.log.error(arg_estimate_mode)
|
||||||
|
self.log.error(arg_fee_rate)
|
||||||
self.log.error(options)
|
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)
|
self.log.error(res)
|
||||||
if "txid" in res and add_to_wallet:
|
if "txid" in res and add_to_wallet:
|
||||||
self.log.error("Transaction details:")
|
self.log.error("Transaction details:")
|
||||||
@ -273,10 +278,10 @@ class WalletSendTest(BitcoinTestFramework):
|
|||||||
assert_equal(self.nodes[1].decodepsbt(res1["psbt"])["fee"],
|
assert_equal(self.nodes[1].decodepsbt(res1["psbt"])["fee"],
|
||||||
self.nodes[1].decodepsbt(res2["psbt"])["fee"])
|
self.nodes[1].decodepsbt(res2["psbt"])["fee"])
|
||||||
# but not at the same time
|
# but not at the same time
|
||||||
for mode in ["unset", "economical", "conservative", "dash/kb", "duff/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",
|
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,
|
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...")
|
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)
|
res = self.test_send(from_wallet=w3, to_wallet=w1, amount=1)
|
||||||
@ -302,60 +307,57 @@ class WalletSendTest(BitcoinTestFramework):
|
|||||||
assert res["complete"]
|
assert res["complete"]
|
||||||
|
|
||||||
self.log.info("Test setting explicit fee rate")
|
self.log.info("Test setting explicit fee rate")
|
||||||
res1 = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=1, arg_estimate_mode="economical", add_to_wallet=False)
|
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, conf_target=1, estimate_mode="economical", 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"])
|
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="dash/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"]
|
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
|
||||||
assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00007"))
|
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="duff/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"]
|
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
|
||||||
assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00002"))
|
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="dash/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"]
|
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
|
||||||
assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00004531"))
|
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="duff/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"]
|
fee = self.nodes[1].decodepsbt(res["psbt"])["fee"]
|
||||||
assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00003"))
|
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.
|
# Test that passing fee_rate as both an argument and an option raises.
|
||||||
# for mode in ["unset", "economical", "conservative", "dash/kb", "duff/b"]:
|
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=1, fee_rate=1, add_to_wallet=False,
|
||||||
for mode in ["dash/kb", "duff/b"]:
|
expect_error=(-8, "Pass the fee_rate either as an argument, or in the options object, but not both"))
|
||||||
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 DASH/kB) is lower than the minimum fee rate setting (0.00001000 DASH/kB)"))
|
|
||||||
|
|
||||||
for mode in ["foo", Decimal("3.141592")]:
|
assert_raises_rpc_error(-8, "Use fee_rate (duff/B) instead of feeRate", w0.send, {w1.getnewaddress(): 1}, 6, "conservative", 1, {"feeRate": 0.01})
|
||||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode=mode,
|
|
||||||
expect_error=(-8, "Invalid estimate_mode parameter"))
|
|
||||||
# TODO: these 2 equivalent sends with an invalid estimate_mode arg should both fail, but they do not...why?
|
|
||||||
# self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_conf_target=0.1, arg_estimate_mode=mode,
|
|
||||||
# expect_error=(-8, "Invalid estimate_mode parameter"))
|
|
||||||
# assert_raises_rpc_error(-8, "Invalid estimate_mode parameter", lambda: w0.send({w1.getnewaddress(): 1}, 0.1, mode))
|
|
||||||
|
|
||||||
# TODO: These tests should pass for consistency with the other explicit feerate RPCs, but they do not.
|
assert_raises_rpc_error(-3, "Unexpected key totalFee", w0.send, {w1.getnewaddress(): 1}, 6, "conservative", 1, {"totalFee": 0.01})
|
||||||
# for mode in ["unset", "economical", "conservative", "dash/kb", "duff/b"]:
|
|
||||||
# self.log.debug("{}".format(mode))
|
for target, mode in product([-1, 0, 1009], ["economical", "conservative"]):
|
||||||
# for k, v in {"string": "", "object": {"foo": "bar"}}.items():
|
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=target, estimate_mode=mode,
|
||||||
# self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=v, estimate_mode=mode,
|
expect_error=(-8, "Invalid conf_target, must be between 1 and 1008")) # max value of 1008 per src/policy/fees.h
|
||||||
# expect_error=(-3, "Expected type number for conf_target, got {}".format(k)))
|
msg = 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"'
|
||||||
|
for target, mode in product([-1, 0], ["dash/kb", "dufff/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", "dash/kb", "duff/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 duff/B instead of DASH/kB if duff/B is selected.
|
|
||||||
# Test setting explicit fee rate just below the minimum.
|
# Test setting explicit fee rate just below the minimum.
|
||||||
for unit, fee_rate in {"duff/B": 0.99999999, "DASH/kB": 0.00000999}.items():
|
self.log.info("Explicit fee rate raises RPC error 'fee rate too low' if fee_rate of 0.99999999 is passed")
|
||||||
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, fee_rate=0.99999999,
|
||||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=fee_rate, estimate_mode=unit,
|
expect_error=(-4, "Fee rate (0.999 duff/B) is lower than the minimum fee rate setting (1.000 duff/B)"))
|
||||||
expect_error=(-4, "Fee rate (0.00000999 DASH/kB) is lower than the minimum fee rate setting (0.00001000 DASH/kB)"))
|
|
||||||
|
|
||||||
self.log.info("Explicit fee rate raises RPC error if estimate_mode is passed without a conf_target")
|
|
||||||
for unit, fee_rate in {"duff/B": 100, "DASH/kB": 0.001}.items():
|
|
||||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, estimate_mode=unit,
|
|
||||||
expect_error=(-8, "Selected estimate_mode {} requires a fee rate to be specified in conf_target".format(unit)))
|
|
||||||
|
|
||||||
# TODO: Return hex if fee rate is below -maxmempool
|
# 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="duff/b", add_to_wallet=False)
|
# res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=0.1, estimate_mode="duff/b", add_to_wallet=False)
|
||||||
|
Loading…
Reference in New Issue
Block a user