diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 8315c6a96db3..d3182d94774a 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -41,7 +41,7 @@ RPCHelpMan sendrawtransaction() {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())}, "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + - "/kB.\nSet to 0 to accept any fee rate.\n"}, + "/kB.\nFee rates larger than 1BTC/kvB are rejected.\nSet to 0 to accept any fee rate.\n"}, {"instantsend", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Deprecated and ignored"}, {"bypasslimits", RPCArg::Type::BOOL, RPCArg::Default{false}, "Bypass transaction policy limits"}, }, @@ -73,9 +73,7 @@ RPCHelpMan sendrawtransaction() } CTransactionRef tx(MakeTransactionRef(std::move(mtx))); - const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ? - DEFAULT_MAX_RAW_TX_FEE_RATE : - CFeeRate(AmountFromValue(request.params[1])); + const CFeeRate max_raw_tx_fee_rate{ParseFeeRate(self.Arg(1))}; int64_t virtual_size = GetVirtualTransactionSize(*tx); CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); @@ -111,7 +109,8 @@ static RPCHelpMan testmempoolaccept() }, }, {"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())}, - "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"}, + "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + + "/kB\nFee rates larger than 1BTC/kvB are rejected.\nSet to 0 to accept any fee rate."}, }, RPCResult{ RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n" @@ -155,9 +154,7 @@ static RPCHelpMan testmempoolaccept() "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions."); } - const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ? - DEFAULT_MAX_RAW_TX_FEE_RATE : - CFeeRate(AmountFromValue(request.params[1])); + const CFeeRate max_raw_tx_fee_rate{ParseFeeRate(self.Arg(1))}; std::vector txns; txns.reserve(raw_transactions.size()); diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 02be3a9b80a4..a6ff77e28e2a 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -95,6 +95,13 @@ CAmount AmountFromValue(const UniValue& value, int decimals) return amount; } +CFeeRate ParseFeeRate(const UniValue& json) +{ + CAmount val{AmountFromValue(json)}; + if (val >= COIN) throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee rates larger than or equal to 1BTC/kvB are not accepted"); + return CFeeRate{val}; +} + uint256 ParseHashV(const UniValue& v, std::string strName) { const std::string& strHex(v.get_str()); @@ -569,11 +576,13 @@ static void CheckRequiredOrDefault(const RPCArg& param) void force_semicolon(ret_type) // Optional arg (without default). Can also be called on required args, if needed. +TMPL_INST(nullptr, const UniValue*, maybe_arg;); TMPL_INST(nullptr, std::optional, maybe_arg ? std::optional{maybe_arg->get_real()} : std::nullopt;); TMPL_INST(nullptr, std::optional, maybe_arg ? std::optional{maybe_arg->get_bool()} : std::nullopt;); TMPL_INST(nullptr, const std::string*, maybe_arg ? &maybe_arg->get_str() : nullptr;); // Required arg or optional arg with default value. +TMPL_INST(CheckRequiredOrDefault, const UniValue&, *CHECK_NONFATAL(maybe_arg);); TMPL_INST(CheckRequiredOrDefault, int, CHECK_NONFATAL(maybe_arg)->getInt();); TMPL_INST(CheckRequiredOrDefault, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt();); TMPL_INST(CheckRequiredOrDefault, const std::string&, CHECK_NONFATAL(maybe_arg)->get_str();); diff --git a/src/rpc/util.h b/src/rpc/util.h index 5e113e992c13..baf1825d9af0 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -118,6 +118,11 @@ bool ParseBoolV(const UniValue& v, const std::string &strName); * @returns a CAmount if the various checks pass. */ CAmount AmountFromValue(const UniValue& value, int decimals = 8); +/** + * Parse a json number or string, denoting BTC/kvB, into a CFeeRate (sat/kvB). + * Reject negative values or rates larger than 1BTC/kvB. + */ +CFeeRate ParseFeeRate(const UniValue& json); using RPCArgList = std::vector>; std::string HelpExampleCli(const std::string& methodname, const std::string& args); diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index f424f19edead..f6678c4c6f63 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -77,9 +77,23 @@ def run_test(self): txid_in_block = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_in_block) self.generate(node, 1) self.mempool_size = 0 + # Also check feerate. 1BTC/kvB fails + assert_raises_rpc_error(-8, "Fee rates larger than or equal to 1BTC/kvB are not accepted", lambda: self.check_mempool_result( + result_expected=None, + rawtxs=[raw_tx_in_block], + maxfeerate=1, + )) + # Check negative feerate + assert_raises_rpc_error(-3, "Amount out of range", lambda: self.check_mempool_result( + result_expected=None, + rawtxs=[raw_tx_in_block], + maxfeerate=-0.01, + )) + # ... 0.99 passes self.check_mempool_result( result_expected=[{'txid': txid_in_block, 'allowed': False, 'reject-reason': 'txn-already-known'}], rawtxs=[raw_tx_in_block], + maxfeerate=0.99, ) self.log.info('A transaction not in the mempool')