Skip to content

Commit 2a67584

Browse files
achow101vijaydasmp
authored andcommitted
Merge bitcoin#29434: rpc: Fixed signed integer overflow for large feerates
dddd7be doc: Clarify maxfeerate help (MarcoFalke) fa2a4fd rpc: Fixed signed integer overflow for large feerates (MarcoFalke) fade94d rpc: Add ParseFeeRate helper (MarcoFalke) fa0ff66 rpc: Implement RPCHelpMan::ArgValue<> for UniValue (MarcoFalke) Pull request description: Passing large BTC/kvB feerates to RPCs is problematic, because: * They are likely a typo. 1BTC/kvB (or larger) seems absurd. * They may cause signed integer overflow. * Anyone really wanting to pick such a large value can set `0` to disable the check. Fix all issues by rejecting anything more than 1BTC/kvB during parsing. ACKs for top commit: brunoerg: crACK dddd7be achow101: ACK dddd7be vasild: ACK dddd7be tdb3: Code review ACK and basic test ACK for dddd7be. fjahr: utACK dddd7be Tree-SHA512: 5dcce1f0abe059dc6b2ff56787e11081d73a45b4ddd6dcc2c1ea13709ebc13af5e7265e84fffb97ef32027b56b81955672a67ed7702e8fa30c2e849d67727bac
1 parent 4af395d commit 2a67584

4 files changed

Lines changed: 28 additions & 8 deletions

File tree

src/rpc/mempool.cpp

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ RPCHelpMan sendrawtransaction()
4141
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
4242
{"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
4343
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
44-
"/kB.\nSet to 0 to accept any fee rate.\n"},
44+
"/kB.\nFee rates larger than 1BTC/kvB are rejected.\nSet to 0 to accept any fee rate.\n"},
4545
{"instantsend", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Deprecated and ignored"},
4646
{"bypasslimits", RPCArg::Type::BOOL, RPCArg::Default{false}, "Bypass transaction policy limits"},
4747
},
@@ -73,9 +73,7 @@ RPCHelpMan sendrawtransaction()
7373
}
7474
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
7575

76-
const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
77-
DEFAULT_MAX_RAW_TX_FEE_RATE :
78-
CFeeRate(AmountFromValue(request.params[1]));
76+
const CFeeRate max_raw_tx_fee_rate{ParseFeeRate(self.Arg<UniValue>(1))};
7977

8078
int64_t virtual_size = GetVirtualTransactionSize(*tx);
8179
CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
@@ -111,7 +109,8 @@ static RPCHelpMan testmempoolaccept()
111109
},
112110
},
113111
{"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
114-
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
112+
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
113+
"/kB\nFee rates larger than 1BTC/kvB are rejected.\nSet to 0 to accept any fee rate."},
115114
},
116115
RPCResult{
117116
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()
155154
"Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
156155
}
157156

158-
const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
159-
DEFAULT_MAX_RAW_TX_FEE_RATE :
160-
CFeeRate(AmountFromValue(request.params[1]));
157+
const CFeeRate max_raw_tx_fee_rate{ParseFeeRate(self.Arg<UniValue>(1))};
161158

162159
std::vector<CTransactionRef> txns;
163160
txns.reserve(raw_transactions.size());

src/rpc/util.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ CAmount AmountFromValue(const UniValue& value, int decimals)
9595
return amount;
9696
}
9797

98+
CFeeRate ParseFeeRate(const UniValue& json)
99+
{
100+
CAmount val{AmountFromValue(json)};
101+
if (val >= COIN) throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee rates larger than or equal to 1BTC/kvB are not accepted");
102+
return CFeeRate{val};
103+
}
104+
98105
uint256 ParseHashV(const UniValue& v, std::string strName)
99106
{
100107
const std::string& strHex(v.get_str());
@@ -569,11 +576,14 @@ static void CheckRequiredOrDefault(const RPCArg& param)
569576
void force_semicolon(ret_type)
570577

571578
// Optional arg (without default). Can also be called on required args, if needed.
579+
TMPL_INST(nullptr, const UniValue*, maybe_arg;);
580+
TMPL_INST(nullptr, const UniValue*, maybe_arg;);
572581
TMPL_INST(nullptr, std::optional<double>, maybe_arg ? std::optional{maybe_arg->get_real()} : std::nullopt;);
573582
TMPL_INST(nullptr, std::optional<bool>, maybe_arg ? std::optional{maybe_arg->get_bool()} : std::nullopt;);
574583
TMPL_INST(nullptr, const std::string*, maybe_arg ? &maybe_arg->get_str() : nullptr;);
575584

576585
// Required arg or optional arg with default value.
586+
TMPL_INST(CheckRequiredOrDefault, const UniValue&, *CHECK_NONFATAL(maybe_arg););
577587
TMPL_INST(CheckRequiredOrDefault, int, CHECK_NONFATAL(maybe_arg)->getInt<int>(););
578588
TMPL_INST(CheckRequiredOrDefault, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt<uint64_t>(););
579589
TMPL_INST(CheckRequiredOrDefault, const std::string&, CHECK_NONFATAL(maybe_arg)->get_str(););

src/rpc/util.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ bool ParseBoolV(const UniValue& v, const std::string &strName);
118118
* @returns a CAmount if the various checks pass.
119119
*/
120120
CAmount AmountFromValue(const UniValue& value, int decimals = 8);
121+
/**
122+
* Parse a json number or string, denoting BTC/kvB, into a CFeeRate (sat/kvB).
123+
* Reject negative values or rates larger than 1BTC/kvB.
124+
*/
125+
CFeeRate ParseFeeRate(const UniValue& json);
121126

122127
using RPCArgList = std::vector<std::pair<std::string, UniValue>>;
123128
std::string HelpExampleCli(const std::string& methodname, const std::string& args);

test/functional/mempool_accept.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,17 @@ def run_test(self):
7777
txid_in_block = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_in_block)
7878
self.generate(node, 1)
7979
self.mempool_size = 0
80+
# Also check feerate. 1BTC/kvB fails
81+
assert_raises_rpc_error(-8, "Fee rates larger than or equal to 1BTC/kvB are not accepted", lambda: self.check_mempool_result(
82+
result_expected=None,
83+
rawtxs=[raw_tx_in_block],
84+
maxfeerate=1,
85+
))
86+
# ... 0.99 passes
8087
self.check_mempool_result(
8188
result_expected=[{'txid': txid_in_block, 'allowed': False, 'reject-reason': 'txn-already-known'}],
8289
rawtxs=[raw_tx_in_block],
90+
maxfeerate=0.99,
8391
)
8492

8593
self.log.info('A transaction not in the mempool')

0 commit comments

Comments
 (0)