Skip to content

Commit a573605

Browse files
committed
RPC: Add maxfeerate and maxburnamount args to submitpackage
1 parent 5f9fd11 commit a573605

File tree

8 files changed

+105
-29
lines changed

8 files changed

+105
-29
lines changed

src/rpc/client.cpp

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

src/rpc/mempool.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ static RPCHelpMan testmempoolaccept()
183183
Chainstate& chainstate = chainman.ActiveChainstate();
184184
const PackageMempoolAcceptResult package_result = [&] {
185185
LOCK(::cs_main);
186-
if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true);
186+
if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true, /*max_sane_feerate=*/CFeeRate(0));
187187
return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
188188
chainman.ProcessTransaction(txns[0], /*test_accept=*/true));
189189
}();
@@ -828,6 +828,14 @@ static RPCHelpMan submitpackage()
828828
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
829829
},
830830
},
831+
{"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
832+
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
833+
"/kvB.\nSet to 0 to accept any fee rate."},
834+
{"maxburnamount", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(0)},
835+
"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"
836+
"If burning funds through unspendable outputs is desired, increase this value.\n"
837+
"This check is based on heuristics and does not guarantee spendability of outputs.\n"
838+
},
831839
},
832840
RPCResult{
833841
RPCResult::Type::OBJ, "", "",
@@ -865,6 +873,14 @@ static RPCHelpMan submitpackage()
865873
"Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions.");
866874
}
867875

876+
// Fee check needs to be run with chainstate and package context
877+
const CFeeRate max_raw_tx_fee_rate = request.params[1].isNull() ?
878+
DEFAULT_MAX_RAW_TX_FEE_RATE :
879+
CFeeRate(AmountFromValue(request.params[1]));
880+
881+
// Burn sanity check is run with no context
882+
const CAmount max_burn_amount = request.params[2].isNull() ? 0 : AmountFromValue(request.params[2]);
883+
868884
std::vector<CTransactionRef> txns;
869885
txns.reserve(raw_transactions.size());
870886
for (const auto& rawtx : raw_transactions.getValues()) {
@@ -873,16 +889,24 @@ static RPCHelpMan submitpackage()
873889
throw JSONRPCError(RPC_DESERIALIZATION_ERROR,
874890
"TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input.");
875891
}
892+
893+
for (const auto& out : mtx.vout) {
894+
if((out.scriptPubKey.IsUnspendable() || !out.scriptPubKey.HasValidOps()) && out.nValue > max_burn_amount) {
895+
throw JSONRPCTransactionError(TransactionError::MAX_BURN_EXCEEDED);
896+
}
897+
}
898+
876899
txns.emplace_back(MakeTransactionRef(std::move(mtx)));
877900
}
878901
if (!IsChildWithParentsTree(txns)) {
879902
throw JSONRPCTransactionError(TransactionError::INVALID_PACKAGE, "package topology disallowed. not child-with-parents or parents depend on each other.");
880903
}
881904

905+
882906
NodeContext& node = EnsureAnyNodeContext(request.context);
883907
CTxMemPool& mempool = EnsureMemPool(node);
884908
Chainstate& chainstate = EnsureChainman(node).ActiveChainstate();
885-
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_raw_tx_fee_rate));
886910

887911
// First catch any errors.
888912
switch(package_result.m_state.GetResult()) {

src/test/fuzz/package_eval.cpp

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

272272
const auto result_package = WITH_LOCK(::cs_main,
273-
return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit));
273+
return ProcessNewPackage(chainstate, tx_pool, txs, /*test_accept=*/single_submit, /*max_sane_feerate=*/CFeeRate(0)));
274274

275275
const auto res = WITH_LOCK(::cs_main, return AcceptToMemoryPool(chainstate, txs.back(), GetTime(), bypass_limits, /*test_accept=*/!single_submit));
276276
const bool accepted = res.m_result_type == MempoolAcceptResult::ResultType::VALID;

src/test/fuzz/tx_pool.cpp

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

src/test/txpackage_tests.cpp

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup)
134134
/*output_amount=*/CAmount(48 * COIN), /*submit=*/false);
135135
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
136136
Package package_parent_child{tx_parent, tx_child};
137-
const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_parent_child, /*test_accept=*/true);
137+
const auto result_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_parent_child, /*test_accept=*/true, /*max_sane_feerate=*/CFeeRate(0));
138138
if (auto err_parent_child{CheckPackageMempoolAcceptResult(package_parent_child, result_parent_child, /*expect_valid=*/true, nullptr)}) {
139139
BOOST_ERROR(err_parent_child.value());
140140
} else {
@@ -153,7 +153,7 @@ BOOST_FIXTURE_TEST_CASE(package_validation_tests, TestChain100Setup)
153153
CTransactionRef giant_ptx = create_placeholder_tx(999, 999);
154154
BOOST_CHECK(GetVirtualTransactionSize(*giant_ptx) > DEFAULT_ANCESTOR_SIZE_LIMIT_KVB * 1000);
155155
Package package_single_giant{giant_ptx};
156-
auto result_single_large = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_single_giant, /*test_accept=*/true);
156+
auto result_single_large = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_single_giant, /*test_accept=*/true, /*max_sane_feerate=*/CFeeRate(0));
157157
if (auto err_single_large{CheckPackageMempoolAcceptResult(package_single_giant, result_single_large, /*expect_valid=*/false, nullptr)}) {
158158
BOOST_ERROR(err_single_large.value());
159159
} else {
@@ -280,7 +280,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
280280
package_unrelated.emplace_back(MakeTransactionRef(mtx));
281281
}
282282
auto result_unrelated_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
283-
package_unrelated, /*test_accept=*/false);
283+
package_unrelated, /*test_accept=*/false, /*max_sane_feerate=*/CFeeRate(0));
284284
// We don't expect m_tx_results for each transaction when basic sanity checks haven't passed.
285285
BOOST_CHECK(result_unrelated_submit.m_state.IsInvalid());
286286
BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
@@ -322,7 +322,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
322322
// 3 Generations is not allowed.
323323
{
324324
auto result_3gen_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
325-
package_3gen, /*test_accept=*/false);
325+
package_3gen, /*test_accept=*/false, /*max_sane_feerate=*/CFeeRate(0));
326326
BOOST_CHECK(result_3gen_submit.m_state.IsInvalid());
327327
BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
328328
BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetRejectReason(), "package-not-child-with-parents");
@@ -339,7 +339,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
339339
CTransactionRef tx_parent_invalid = MakeTransactionRef(mtx_parent_invalid);
340340
Package package_invalid_parent{tx_parent_invalid, tx_child};
341341
auto result_quit_early = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
342-
package_invalid_parent, /*test_accept=*/ false);
342+
package_invalid_parent, /*test_accept=*/ false, /*max_sane_feerate=*/CFeeRate(0));
343343
if (auto err_parent_invalid{CheckPackageMempoolAcceptResult(package_invalid_parent, result_quit_early, /*expect_valid=*/false, m_node.mempool.get())}) {
344344
BOOST_ERROR(err_parent_invalid.value());
345345
} else {
@@ -360,7 +360,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
360360
package_missing_parent.push_back(MakeTransactionRef(mtx_child));
361361
{
362362
const auto result_missing_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
363-
package_missing_parent, /*test_accept=*/false);
363+
package_missing_parent, /*test_accept=*/false, /*max_sane_feerate=*/CFeeRate(0));
364364
BOOST_CHECK(result_missing_parent.m_state.IsInvalid());
365365
BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
366366
BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetRejectReason(), "package-not-child-with-unconfirmed-parents");
@@ -370,7 +370,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
370370
// Submit package with parent + child.
371371
{
372372
const auto submit_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
373-
package_parent_child, /*test_accept=*/false);
373+
package_parent_child, /*test_accept=*/false, /*max_sane_feerate=*/CFeeRate(0));
374374
expected_pool_size += 2;
375375
BOOST_CHECK_MESSAGE(submit_parent_child.m_state.IsValid(),
376376
"Package validation unexpectedly failed: " << submit_parent_child.m_state.GetRejectReason());
@@ -392,7 +392,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
392392
// Already-in-mempool transactions should be detected and de-duplicated.
393393
{
394394
const auto submit_deduped = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
395-
package_parent_child, /*test_accept=*/false);
395+
package_parent_child, /*test_accept=*/false, /*max_sane_feerate=*/CFeeRate(0));
396396
if (auto err_deduped{CheckPackageMempoolAcceptResult(package_parent_child, submit_deduped, /*expect_valid=*/true, m_node.mempool.get())}) {
397397
BOOST_ERROR(err_deduped.value());
398398
} else {
@@ -464,15 +464,15 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
464464
{
465465
Package package_parent_child1{ptx_parent, ptx_child1};
466466
const auto submit_witness1 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
467-
package_parent_child1, /*test_accept=*/false);
467+
package_parent_child1, /*test_accept=*/false, /*max_sane_feerate=*/CFeeRate(0));
468468
if (auto err_witness1{CheckPackageMempoolAcceptResult(package_parent_child1, submit_witness1, /*expect_valid=*/true, m_node.mempool.get())}) {
469469
BOOST_ERROR(err_witness1.value());
470470
}
471471

472472
// Child2 would have been validated individually.
473473
Package package_parent_child2{ptx_parent, ptx_child2};
474474
const auto submit_witness2 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
475-
package_parent_child2, /*test_accept=*/false);
475+
package_parent_child2, /*test_accept=*/false, /*max_sane_feerate=*/CFeeRate(0));
476476
if (auto err_witness2{CheckPackageMempoolAcceptResult(package_parent_child2, submit_witness2, /*expect_valid=*/true, m_node.mempool.get())}) {
477477
BOOST_ERROR(err_witness2.value());
478478
} else {
@@ -486,7 +486,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
486486
// Deduplication should work when wtxid != txid. Submit package with the already-in-mempool
487487
// transactions again, which should not fail.
488488
const auto submit_segwit_dedup = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
489-
package_parent_child1, /*test_accept=*/false);
489+
package_parent_child1, /*test_accept=*/false, /*max_sane_feerate=*/CFeeRate(0));
490490
if (auto err_segwit_dedup{CheckPackageMempoolAcceptResult(package_parent_child1, submit_segwit_dedup, /*expect_valid=*/true, m_node.mempool.get())}) {
491491
BOOST_ERROR(err_segwit_dedup.value());
492492
} else {
@@ -517,7 +517,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
517517
{
518518
Package package_child2_grandchild{ptx_child2, ptx_grandchild};
519519
const auto submit_spend_ignored = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
520-
package_child2_grandchild, /*test_accept=*/false);
520+
package_child2_grandchild, /*test_accept=*/false, /*max_sane_feerate=*/CFeeRate(0));
521521
if (auto err_spend_ignored{CheckPackageMempoolAcceptResult(package_child2_grandchild, submit_spend_ignored, /*expect_valid=*/true, m_node.mempool.get())}) {
522522
BOOST_ERROR(err_spend_ignored.value());
523523
} else {
@@ -616,7 +616,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
616616
// parent3 should be accepted
617617
// child should be accepted
618618
{
619-
const auto mixed_result = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_mixed, false);
619+
const auto mixed_result = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_mixed, false, /*max_sane_feerate=*/CFeeRate(0));
620620
if (auto err_mixed{CheckPackageMempoolAcceptResult(package_mixed, mixed_result, /*expect_valid=*/true, m_node.mempool.get())}) {
621621
BOOST_ERROR(err_mixed.value());
622622
} else {
@@ -682,7 +682,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
682682
{
683683
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
684684
const auto submit_cpfp_deprio = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
685-
package_cpfp, /*test_accept=*/ false);
685+
package_cpfp, /*test_accept=*/ false, /*max_sane_feerate=*/CFeeRate(0));
686686
if (auto err_cpfp_deprio{CheckPackageMempoolAcceptResult(package_cpfp, submit_cpfp_deprio, /*expect_valid=*/false, m_node.mempool.get())}) {
687687
BOOST_ERROR(err_cpfp_deprio.value());
688688
} else {
@@ -704,7 +704,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
704704
{
705705
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
706706
const auto submit_cpfp = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
707-
package_cpfp, /*test_accept=*/ false);
707+
package_cpfp, /*test_accept=*/ false, /*max_sane_feerate=*/CFeeRate(0));
708708
if (auto err_cpfp{CheckPackageMempoolAcceptResult(package_cpfp, submit_cpfp, /*expect_valid=*/true, m_node.mempool.get())}) {
709709
BOOST_ERROR(err_cpfp.value());
710710
} else {
@@ -756,7 +756,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
756756
// Cheap package should fail for being too low fee.
757757
{
758758
const auto submit_package_too_low = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
759-
package_still_too_low, /*test_accept=*/false);
759+
package_still_too_low, /*test_accept=*/false, /*max_sane_feerate=*/CFeeRate(0));
760760
if (auto err_package_too_low{CheckPackageMempoolAcceptResult(package_still_too_low, submit_package_too_low, /*expect_valid=*/false, m_node.mempool.get())}) {
761761
BOOST_ERROR(err_package_too_low.value());
762762
} else {
@@ -782,7 +782,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
782782
// Now that the child's fees have "increased" by 1 BTC, the cheap package should succeed.
783783
{
784784
const auto submit_prioritised_package = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
785-
package_still_too_low, /*test_accept=*/false);
785+
package_still_too_low, /*test_accept=*/false, /*max_sane_feerate=*/CFeeRate(0));
786786
if (auto err_prioritised{CheckPackageMempoolAcceptResult(package_still_too_low, submit_prioritised_package, /*expect_valid=*/true, m_node.mempool.get())}) {
787787
BOOST_ERROR(err_prioritised.value());
788788
} else {
@@ -830,7 +830,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
830830
{
831831
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
832832
const auto submit_rich_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
833-
package_rich_parent, /*test_accept=*/false);
833+
package_rich_parent, /*test_accept=*/false, /*max_sane_feerate=*/CFeeRate(0));
834834
if (auto err_rich_parent{CheckPackageMempoolAcceptResult(package_rich_parent, submit_rich_parent, /*expect_valid=*/false, m_node.mempool.get())}) {
835835
BOOST_ERROR(err_rich_parent.value());
836836
} else {

0 commit comments

Comments
 (0)