Skip to content

Commit 5d045c3

Browse files
committed
Merge bitcoin/bitcoin#28950: RPC: Add maxfeerate and maxburnamount args to submitpackage
38f70ba RPC: Add maxfeerate and maxburnamount args to submitpackage (Greg Sanders) Pull request description: Resolves bitcoin/bitcoin#28949 I couldn't manage to do it very cleanly outside of (sub)package evaluation itself, since it would change the current interface very heavily. Instead I threaded through the max fee argument and used that directly via ATMPArgs. From that perspective, this is somewhat a reversion from bitcoin/bitcoin#19339. In a post-cluster mempool world, these checks could be consolidated to right after the given (ancestor) package is linearized/chunked, by just checking the feerate of the top chunk and rejecting the submission entirely if the top chunk is too high. The implication here is that subpackages can be submitted to the mempool prior to hitting this new fee-based error condition. ACKs for top commit: ismaelsadeeq: Re-ACK bitcoin/bitcoin@38f70ba 👍🏾 glozow: ACK 38f70ba with some non-blocking nits murchandamus: LGTM, code review ACK 38f70ba Tree-SHA512: 38212aa9de25730944cee58b0806a3d37097e42719af8dd7de91ce86bb5d9770b6f7c37354bf418bd8ba571c52947da1dcdbb968bf429dd1dbdf8715315af18f
2 parents 9a459e3 + 38f70ba commit 5d045c3

File tree

9 files changed

+124
-30
lines changed

9 files changed

+124
-30
lines changed

src/node/transaction.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ struct NodeContext;
2626
*/
2727
static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10};
2828

29+
/** Maximum burn value for sendrawtransaction, submitpackage, and testmempoolaccept RPC calls.
30+
* By default, a transaction with a burn value higher than this will be rejected
31+
* by these RPCs and the GUI. This can be overridden with the maxburnamount argument.
32+
*/
33+
static const CAmount DEFAULT_MAX_BURN_AMOUNT{0};
34+
2935
/**
3036
* Submit a transaction to the mempool and (optionally) relay it to all P2P peers.
3137
*

src/rpc/client.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
128128
{ "testmempoolaccept", 0, "rawtxs" },
129129
{ "testmempoolaccept", 1, "maxfeerate" },
130130
{ "submitpackage", 0, "package" },
131+
{ "submitpackage", 1, "maxfeerate" },
132+
{ "submitpackage", 2, "maxburnamount" },
131133
{ "combinerawtransaction", 0, "txs" },
132134
{ "fundrawtransaction", 1, "options" },
133135
{ "fundrawtransaction", 1, "add_inputs"},

src/rpc/mempool.cpp

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
using kernel::DumpMempool;
3030

31+
using node::DEFAULT_MAX_BURN_AMOUNT;
3132
using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
3233
using node::MempoolPath;
3334
using node::NodeContext;
@@ -46,7 +47,7 @@ static RPCHelpMan sendrawtransaction()
4647
{"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
4748
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
4849
"/kvB.\nFee rates larger than 1BTC/kvB are rejected.\nSet to 0 to accept any fee rate."},
49-
{"maxburnamount", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(0)},
50+
{"maxburnamount", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_BURN_AMOUNT)},
5051
"Reject transactions with provably unspendable outputs (e.g. 'datacarrier' outputs that use the OP_RETURN opcode) greater than the specified value, expressed in " + CURRENCY_UNIT + ".\n"
5152
"If burning funds through unspendable outputs is desired, increase this value.\n"
5253
"This check is based on heuristics and does not guarantee spendability of outputs.\n"},
@@ -180,7 +181,7 @@ static RPCHelpMan testmempoolaccept()
180181
Chainstate& chainstate = chainman.ActiveChainstate();
181182
const PackageMempoolAcceptResult package_result = [&] {
182183
LOCK(::cs_main);
183-
if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true);
184+
if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true, /*max_sane_feerate=*/{});
184185
return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
185186
chainman.ProcessTransaction(txns[0], /*test_accept=*/true));
186187
}();
@@ -823,6 +824,14 @@ static RPCHelpMan submitpackage()
823824
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
824825
},
825826
},
827+
{"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
828+
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
829+
"/kvB.\nFee rates larger than 1BTC/kvB are rejected.\nSet to 0 to accept any fee rate."},
830+
{"maxburnamount", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_BURN_AMOUNT)},
831+
"Reject transactions with provably unspendable outputs (e.g. 'datacarrier' outputs that use the OP_RETURN opcode) greater than the specified value, expressed in " + CURRENCY_UNIT + ".\n"
832+
"If burning funds through unspendable outputs is desired, increase this value.\n"
833+
"This check is based on heuristics and does not guarantee spendability of outputs.\n"
834+
},
826835
},
827836
RPCResult{
828837
RPCResult::Type::OBJ, "", "",
@@ -862,6 +871,17 @@ static RPCHelpMan submitpackage()
862871
"Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
863872
}
864873

874+
// Fee check needs to be run with chainstate and package context
875+
const CFeeRate max_raw_tx_fee_rate = ParseFeeRate(self.Arg<UniValue>(1));
876+
std::optional<CFeeRate> max_sane_feerate{max_raw_tx_fee_rate};
877+
// 0-value is special; it's mapped to no sanity check
878+
if (max_raw_tx_fee_rate == CFeeRate(0)) {
879+
max_sane_feerate = std::nullopt;
880+
}
881+
882+
// Burn sanity check is run with no context
883+
const CAmount max_burn_amount = request.params[2].isNull() ? 0 : AmountFromValue(request.params[2]);
884+
865885
std::vector<CTransactionRef> txns;
866886
txns.reserve(raw_transactions.size());
867887
for (const auto& rawtx : raw_transactions.getValues()) {
@@ -870,6 +890,13 @@ static RPCHelpMan submitpackage()
870890
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
871891
"TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input.");
872892
}
893+
894+
for (const auto& out : mtx.vout) {
895+
if((out.scriptPubKey.IsUnspendable() || !out.scriptPubKey.HasValidOps()) && out.nValue > max_burn_amount) {
896+
throw JSONRPCTransactionError(TransactionError::MAX_BURN_EXCEEDED);
897+
}
898+
}
899+
873900
txns.emplace_back(MakeTransactionRef(std::move(mtx)));
874901
}
875902
if (!IsChildWithParentsTree(txns)) {
@@ -879,7 +906,7 @@ static RPCHelpMan submitpackage()
879906
NodeContext& node = EnsureAnyNodeContext(request.context);
880907
CTxMemPool& mempool = EnsureMemPool(node);
881908
Chainstate& chainstate = EnsureChainman(node).ActiveChainstate();
882-
const auto package_result = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false));
909+
const auto package_result = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false, max_sane_feerate));
883910

884911
std::string package_msg = "success";
885912

src/test/fuzz/package_eval.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ FUZZ_TARGET(tx_package_eval, .init = initialize_tx_pool)
277277
auto single_submit = txs.size() == 1 && fuzzed_data_provider.ConsumeBool();
278278

279279
const auto result_package = WITH_LOCK(::cs_main,
280-
return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit));
280+
return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, /*max_sane_feerate=*/{}));
281281

282282
// Always set bypass_limits to false because it is not supported in ProcessNewPackage and
283283
// can be a source of divergence.

src/test/fuzz/tx_pool.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ FUZZ_TARGET(tx_pool_standard, .init = initialize_tx_pool)
291291
// Make sure ProcessNewPackage on one transaction works.
292292
// The result is not guaranteed to be the same as what is returned by ATMP.
293293
const auto result_package = WITH_LOCK(::cs_main,
294-
return ProcessNewPackage(chainstate, tx_pool, {tx}, true));
294+
return ProcessNewPackage(chainstate, tx_pool, {tx}, true, /*max_sane_feerate=*/{}));
295295
// If something went wrong due to a package-specific policy, it might not return a
296296
// validation result for the transaction.
297297
if (result_package.m_state.GetResult() != PackageValidationResult::PCKG_POLICY) {

0 commit comments

Comments
 (0)