Skip to content

Commit 9b1df49

Browse files
committed
[rpc] permit any package, not just child-with-unconfirmed-parents
We can safely allow any package since MiniGraph can linearize and group things into ancestor subsets.
1 parent dcd39e6 commit 9b1df49

File tree

5 files changed

+87
-86
lines changed

5 files changed

+87
-86
lines changed

doc/policy/packages.md

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ Graph (a directed edge exists between a transaction that spends the output of an
88
For every transaction `t` in a **topologically sorted** package, if any of its parents are present
99
in the package, they appear somewhere in the list before `t`.
1010

11-
A **child-with-parents** package is a topologically sorted package that consists of exactly one child and at least one
12-
of its unconfirmed parents. Not all unconfirmed parents need to be present but no other transactions may be present; the
13-
parent of a parent should not be in this package (unless this "grandparent" is also a direct parent of the child).
14-
1511
## Package Mempool Acceptance Rules
1612

1713
The following rules are enforced for all packages:
@@ -57,18 +53,8 @@ The following rules are enforced for all packages:
5753
The following rules are only enforced for packages to be submitted to the mempool (not
5854
enforced for test accepts):
5955

60-
* Packages must be child-with-parents packages. This also means packages must contain at
61-
least 1 transaction. (#31096)
62-
63-
- *Rationale*: This allows for fee-bumping by CPFP. Allowing multiple parents makes it possible
64-
to fee-bump a batch of transactions. Restricting packages to a defined topology is easier to
65-
reason about and simplifies the validation logic greatly.
66-
67-
- Warning: Batched fee-bumping may be unsafe for some use cases. Users and application developers
68-
should take caution if utilizing multi-parent packages.
69-
70-
* Transactions in the package that have the same txid as another transaction already in the mempool
71-
will be removed from the package prior to submission ("deduplication").
56+
* Transactions in the package that are already in the mempool or have the same txid as another
57+
transaction already in the mempool are skipped ("deduplication").
7258

7359
- *Rationale*: Node operators are free to set their mempool policies however they please, nodes
7460
may receive transactions in different orders, and malicious counterparties may try to take

src/rpc/mempool.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,10 +1144,6 @@ static RPCHelpMan submitpackage()
11441144
txns.emplace_back(MakeTransactionRef(std::move(mtx)));
11451145
}
11461146
CHECK_NONFATAL(!txns.empty());
1147-
if (txns.size() > 1 && !IsChildWithParentsTree(txns)) {
1148-
throw JSONRPCTransactionError(TransactionError::INVALID_PACKAGE, "package topology disallowed. not child-with-parents or parents depend on each other.");
1149-
}
1150-
11511147
NodeContext& node = EnsureAnyNodeContext(request.context);
11521148
CTxMemPool& mempool = EnsureMemPool(node);
11531149
Chainstate& chainstate = EnsureChainman(node).ActiveChainstate();

src/test/txpackage_tests.cpp

Lines changed: 82 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -353,74 +353,122 @@ BOOST_AUTO_TEST_CASE(noncontextual_package_tests)
353353
}
354354
}
355355

356-
BOOST_AUTO_TEST_CASE(package_submission_tests)
356+
BOOST_AUTO_TEST_CASE(package_topology)
357357
{
358358
// Mine blocks to mature coinbases.
359-
mineBlocks(60);
359+
mineBlocks(20);
360360
CFeeRate minfeerate(5000);
361361
MockMempoolMinFee(minfeerate);
362362
LOCK(cs_main);
363363
unsigned int expected_pool_size = m_node.mempool->size();
364364
CKey parent_key = GenerateRandomKey();
365365
CScript parent_locking_script = GetScriptForDestination(PKHash(parent_key.GetPubKey()));
366366
const CAmount coinbase_value{50 * COIN};
367+
const CAmount generous_fee{1000};
367368

368-
// Unrelated transactions are not allowed in package submission.
369369
Package package_unrelated;
370370
for (size_t i{0}; i < 10; ++i) {
371-
auto mtx = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[i + 25], /*input_vout=*/0,
371+
auto mtx = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[i], /*input_vout=*/0,
372372
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
373373
/*output_destination=*/parent_locking_script,
374-
/*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
374+
/*output_amount=*/coinbase_value - generous_fee, /*submit=*/false);
375375
package_unrelated.emplace_back(MakeTransactionRef(mtx));
376376
}
377377
auto result_unrelated_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
378378
package_unrelated, /*test_accept=*/false, /*client_maxfeerate=*/{});
379-
// We don't expect m_tx_results for each transaction when basic sanity checks haven't passed.
380-
BOOST_CHECK(result_unrelated_submit.m_state.IsInvalid());
381-
BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
382-
BOOST_CHECK_EQUAL(result_unrelated_submit.m_state.GetRejectReason(), "package-not-child-with-parents");
379+
BOOST_CHECK(result_unrelated_submit.m_state.IsValid());
380+
expected_pool_size += 10;
383381
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
384-
382+
// We should see a result for each transaction. They should have been validated individually.
383+
for (const auto& tx : package_unrelated) {
384+
auto it = result_unrelated_submit.m_tx_results.find(tx->GetWitnessHash());
385+
BOOST_CHECK(it != result_unrelated_submit.m_tx_results.end());
386+
BOOST_CHECK(it->second.m_state.IsValid());
387+
BOOST_CHECK_EQUAL(it->second.m_subpackage_wtxids.value().size(), 1);
388+
}
385389
// Parent and Child (and Grandchild) Package
386-
Package package_parent_child;
387390
Package package_3gen;
388-
auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
391+
auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[10], /*input_vout=*/0,
389392
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
390393
/*output_destination=*/parent_locking_script,
391-
/*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
394+
/*output_amount=*/coinbase_value - generous_fee, /*submit=*/false);
392395
CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
393-
package_parent_child.push_back(tx_parent);
394396
package_3gen.push_back(tx_parent);
395397

396398
CKey child_key = GenerateRandomKey();
397399
CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey()));
398400
auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0,
399401
/*input_height=*/101, /*input_signing_key=*/parent_key,
400402
/*output_destination=*/child_locking_script,
401-
/*output_amount=*/CAmount(48 * COIN), /*submit=*/false);
403+
/*output_amount=*/coinbase_value - 2 * generous_fee, /*submit=*/false);
402404
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
403-
package_parent_child.push_back(tx_child);
404405
package_3gen.push_back(tx_child);
405406

406407
CKey grandchild_key = GenerateRandomKey();
407408
CScript grandchild_locking_script = GetScriptForDestination(PKHash(grandchild_key.GetPubKey()));
408409
auto mtx_grandchild = CreateValidMempoolTransaction(/*input_transaction=*/tx_child, /*input_vout=*/0,
409410
/*input_height=*/101, /*input_signing_key=*/child_key,
410411
/*output_destination=*/grandchild_locking_script,
411-
/*output_amount=*/CAmount(47 * COIN), /*submit=*/false);
412+
/*output_amount=*/coinbase_value - 3 * generous_fee, /*submit=*/false);
412413
CTransactionRef tx_grandchild = MakeTransactionRef(mtx_grandchild);
413414
package_3gen.push_back(tx_grandchild);
414415

415-
// 3 Generations is not allowed.
416+
// Submit package parent + child + grandchild.
416417
{
417418
auto result_3gen_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
418419
package_3gen, /*test_accept=*/false, /*client_maxfeerate=*/{});
419-
BOOST_CHECK(result_3gen_submit.m_state.IsInvalid());
420-
BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
421-
BOOST_CHECK_EQUAL(result_3gen_submit.m_state.GetRejectReason(), "package-not-child-with-parents");
420+
expected_pool_size += 3;
421+
BOOST_CHECK_MESSAGE(result_3gen_submit.m_state.IsValid(),
422+
"Package validation unexpectedly failed: " << result_3gen_submit.m_state.GetRejectReason());
423+
BOOST_CHECK_EQUAL(result_3gen_submit.m_tx_results.size(), package_3gen.size());
424+
auto it_parent = result_3gen_submit.m_tx_results.find(tx_parent->GetWitnessHash());
425+
auto it_child = result_3gen_submit.m_tx_results.find(tx_child->GetWitnessHash());
426+
auto it_grandchild = result_3gen_submit.m_tx_results.find(tx_grandchild->GetWitnessHash());
427+
428+
BOOST_CHECK(it_parent->second.m_effective_feerate == CFeeRate(generous_fee, GetVirtualTransactionSize(*tx_parent)));
429+
BOOST_CHECK_EQUAL(it_parent->second.m_subpackage_wtxids.value().size(), 1);
430+
BOOST_CHECK_EQUAL(it_parent->second.m_subpackage_wtxids.value().front(), tx_parent->GetWitnessHash());
431+
BOOST_CHECK(it_child->second.m_effective_feerate == CFeeRate(generous_fee, GetVirtualTransactionSize(*tx_child)));
432+
BOOST_CHECK_EQUAL(it_child->second.m_subpackage_wtxids.value().size(), 1);
433+
BOOST_CHECK_EQUAL(it_child->second.m_subpackage_wtxids.value().front(), tx_child->GetWitnessHash());
434+
435+
BOOST_CHECK(it_grandchild->second.m_effective_feerate == CFeeRate(generous_fee, GetVirtualTransactionSize(*tx_grandchild)));
436+
BOOST_CHECK_EQUAL(it_grandchild->second.m_subpackage_wtxids.value().size(), 1);
437+
BOOST_CHECK_EQUAL(it_grandchild->second.m_subpackage_wtxids.value().front(), tx_grandchild->GetWitnessHash());
438+
422439
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
423440
}
441+
}
442+
443+
BOOST_AUTO_TEST_CASE(package_submission_tests)
444+
{
445+
// Mine blocks to mature coinbases.
446+
mineBlocks(60);
447+
CFeeRate minfeerate(5000);
448+
MockMempoolMinFee(minfeerate);
449+
LOCK(cs_main);
450+
unsigned int expected_pool_size = m_node.mempool->size();
451+
CKey parent_key = GenerateRandomKey();
452+
CScript parent_locking_script = GetScriptForDestination(PKHash(parent_key.GetPubKey()));
453+
const CAmount coinbase_value{50 * COIN};
454+
455+
// Parent and Child
456+
Package package_parent_child;
457+
auto mtx_parent = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[0], /*input_vout=*/0,
458+
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
459+
/*output_destination=*/parent_locking_script,
460+
/*output_amount=*/CAmount(49 * COIN), /*submit=*/false);
461+
CTransactionRef tx_parent = MakeTransactionRef(mtx_parent);
462+
package_parent_child.push_back(tx_parent);
463+
464+
CKey child_key = GenerateRandomKey();
465+
CScript child_locking_script = GetScriptForDestination(PKHash(child_key.GetPubKey()));
466+
auto mtx_child = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent, /*input_vout=*/0,
467+
/*input_height=*/101, /*input_signing_key=*/parent_key,
468+
/*output_destination=*/child_locking_script,
469+
/*output_amount=*/CAmount(48 * COIN), /*submit=*/false);
470+
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
471+
package_parent_child.push_back(tx_child);
424472

425473
// Parent and child package where transactions are invalid for reasons other than fee and
426474
// missing inputs, so the package validation isn't expected to happen.
@@ -448,22 +496,21 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
448496
BOOST_CHECK_EQUAL(result_quit_early.m_state.GetResult(), PackageValidationResult::PCKG_TX);
449497
}
450498

451-
// Submit package with parent + child.
499+
// Submit package parent + child
452500
{
453-
const auto submit_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
454-
package_parent_child, /*test_accept=*/false, /*client_maxfeerate=*/{});
501+
auto result_parent_child_submit = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
502+
package_parent_child, /*test_accept=*/false, /*client_maxfeerate=*/{});
455503
expected_pool_size += 2;
456-
BOOST_CHECK_MESSAGE(submit_parent_child.m_state.IsValid(),
457-
"Package validation unexpectedly failed: " << submit_parent_child.m_state.GetRejectReason());
458-
BOOST_CHECK_EQUAL(submit_parent_child.m_tx_results.size(), package_parent_child.size());
459-
auto it_parent = submit_parent_child.m_tx_results.find(tx_parent->GetWitnessHash());
460-
auto it_child = submit_parent_child.m_tx_results.find(tx_child->GetWitnessHash());
461-
BOOST_CHECK(it_parent != submit_parent_child.m_tx_results.end());
462-
BOOST_CHECK(it_parent->second.m_state.IsValid());
463-
BOOST_CHECK(it_parent->second.m_effective_feerate == CFeeRate(1 * COIN, GetVirtualTransactionSize(*tx_parent)));
504+
BOOST_CHECK_MESSAGE(result_parent_child_submit.m_state.IsValid(),
505+
"Package validation unexpectedly failed: " << result_parent_child_submit.m_state.GetRejectReason());
506+
BOOST_CHECK_EQUAL(result_parent_child_submit.m_tx_results.size(), package_parent_child.size());
507+
auto it_parent = result_parent_child_submit.m_tx_results.find(tx_parent->GetWitnessHash());
508+
auto it_child = result_parent_child_submit.m_tx_results.find(tx_child->GetWitnessHash());
509+
510+
BOOST_CHECK(it_parent->second.m_effective_feerate == CFeeRate(COIN, GetVirtualTransactionSize(*tx_parent)));
464511
BOOST_CHECK_EQUAL(it_parent->second.m_subpackage_wtxids.value().size(), 1);
465512
BOOST_CHECK_EQUAL(it_parent->second.m_subpackage_wtxids.value().front(), tx_parent->GetWitnessHash());
466-
BOOST_CHECK(it_child->second.m_effective_feerate == CFeeRate(1 * COIN, GetVirtualTransactionSize(*tx_child)));
513+
BOOST_CHECK(it_child->second.m_effective_feerate == CFeeRate(COIN, GetVirtualTransactionSize(*tx_child)));
467514
BOOST_CHECK_EQUAL(it_child->second.m_subpackage_wtxids.value().size(), 1);
468515
BOOST_CHECK_EQUAL(it_child->second.m_subpackage_wtxids.value().front(), tx_child->GetWitnessHash());
469516

@@ -1530,15 +1577,15 @@ BOOST_FIXTURE_TEST_CASE(linearization_tests, TestChain100Setup)
15301577
// 3sat/vB 3sat/vB 20sat/vB
15311578
// ^ ^ ^ ^
15321579
// parent1 parent2
1533-
// 7sat/vB 7sat/vB
1580+
// 8sat/vB 8sat/vB
15341581
// ^ ^ ^ ^
15351582
// child
15361583
// 1sat/vB
15371584
//
15381585
// child is also spending all the grandparents so that this is a child-with-parents package.
15391586
const CFeeRate feerate_grandparents_low(3000);
15401587
const CFeeRate feerate_grandparent_high(20000);
1541-
const CFeeRate feerate_parents(8000);
1588+
const CFeeRate feerate_parents(8200);
15421589
const CFeeRate feerate_child(1000);
15431590
const CFeeRate mempool_min_feerate{m_node.mempool->GetMinFee()};
15441591

src/validation.cpp

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -512,8 +512,8 @@ class MemPoolAccept
512512
};
513513
}
514514

515-
/** Parameters for child-with-parents package validation. */
516-
static ATMPArgs PackageChildWithParents(const CChainParams& chainparams, int64_t accept_time,
515+
/** Parameters for package validation of a tx with ancestors. */
516+
static ATMPArgs AnyPackageAccept(const CChainParams& chainparams, int64_t accept_time,
517517
std::vector<COutPoint>& coins_to_uncache, const std::optional<CFeeRate>& client_maxfeerate) {
518518
return ATMPArgs{/* m_chainparams */ chainparams,
519519
/* m_accept_time */ accept_time,
@@ -1616,26 +1616,14 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
16161616
// Used if returning a PackageMempoolAcceptResult directly from this function.
16171617
PackageValidationState package_state_quit_early;
16181618

1619-
// There are two topologies we are able to handle through this function:
1620-
// (1) A single transaction
1621-
// (2) A child-with-parents package.
16221619
// 1. Check that the package is well-formed. If it isn't, we won't try to validate any of the
16231620
// transactions and thus won't return any MempoolAcceptResults, just a package-wide error.
1624-
16251621
// Context-free package checks.
16261622
if (!IsWellFormedPackage(package, package_state_quit_early, /*require_sorted=*/true)) {
16271623
return PackageMempoolAcceptResult(package_state_quit_early, {});
16281624
}
16291625

1630-
if (package.size() > 1 && !IsChildWithParents(package)) {
1631-
// All transactions in the package must be a parent of the last transaction. This is just an
1632-
// opportunity for us to fail fast on a context-free check without taking the mempool lock.
1633-
package_state_quit_early.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-parents");
1634-
return PackageMempoolAcceptResult(package_state_quit_early, {});
1635-
}
1636-
16371626
PackageValidationState package_state_final;
1638-
16391627
LOCK(m_pool.cs);
16401628
MiniGraph package_sorter(package, std::max(m_pool.GetMinFee(), m_pool.m_opts.min_relay_feerate));
16411629

@@ -1950,7 +1938,7 @@ PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxM
19501938
auto args = MemPoolAccept::ATMPArgs::PackageTestAccept(chainparams, GetTime(), coins_to_uncache);
19511939
return MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args);
19521940
} else {
1953-
auto args = MemPoolAccept::ATMPArgs::PackageChildWithParents(chainparams, GetTime(), coins_to_uncache, client_maxfeerate);
1941+
auto args = MemPoolAccept::ATMPArgs::AnyPackageAccept(chainparams, GetTime(), coins_to_uncache, client_maxfeerate);
19541942
return MemPoolAccept(pool, active_chainstate).AcceptPackage(package, args);
19551943
}
19561944
}();

0 commit comments

Comments
 (0)