Skip to content

Commit 24246c3

Browse files
committed
Merge bitcoin/bitcoin#31385: package validation: relax the package-not-child-with-unconfirmed-parents rule
ea17a94 [doc] release note for relaxing requirement of all unconfirmed parents present (glozow) 12f48d5 test: add chained 1p1c propagation test (Greg Sanders) 525be56 [unit test] package submission 2p1c with 1 parent missing (glozow) f24771a relax child-with-unconfirmed-parents rule (glozow) Pull request description: Broadens the package validation interface, see #27463 for wider context. On master, package rules include that (1) the package topology must be child-wth-parents (2) all of the child's unconfirmed parents must be present. This PR relaxes the second rule, leaving the first rule untouched (there are plans to change that as well, but not here). Original motivation for this rule was based on the idea that we would have a child-with-unconfirmed-parents package relay protocol, and this would verify that the peer provided the "correct" package. For various reasons, we're not planning on doing this. We could potentially do this for ancestor packages (with a similar definition that all UTXOs to make the tx valid are available in this package), but it's also questionable whether it's useful to enforce this. This rule gets in the way of certain usage of 1p1c package relay currently. If a transaction has multiple parents, of which only 1 requires a package CPFP, this rule blocks the package from relaying. Even if all the non-low-feerate parents are already in mempool, when the p2p logic submits the 1p1c package, it gets rejected for not meeting this rule. ACKs for top commit: ishaanam: re-utACK ea17a94 instagibbs: ACK ea17a94 Tree-SHA512: c2231761ae7b2acea10a96735e7a36c646f517964d0acb59bacbae1c5a1950e0223458b84c6d5ce008f0c1d53c1605df0fb3cd0064ee535ead006eb7c0fa625b
2 parents b8025b3 + ea17a94 commit 24246c3

File tree

8 files changed

+180
-81
lines changed

8 files changed

+180
-81
lines changed

doc/policy/packages.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@ 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-unconfirmed-parents** package is a topologically sorted package that consists of
12-
exactly one child and all of its unconfirmed parents (no other transactions may be present).
13-
The last transaction in the package is the child, and its package can be canonically defined based
14-
on the current state: each of its inputs must be available in the UTXO set as of the current chain
15-
tip or some preceding transaction in the package.
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).
1614

1715
## Package Mempool Acceptance Rules
1816

@@ -73,7 +71,7 @@ The following rules are enforced for all packages:
7371
The following rules are only enforced for packages to be submitted to the mempool (not
7472
enforced for test accepts):
7573

76-
* Packages must be child-with-unconfirmed-parents packages. This also means packages must contain at
74+
* Packages must be child-with-parents packages. This also means packages must contain at
7775
least 1 transaction. (#31096)
7876

7977
- *Rationale*: This allows for fee-bumping by CPFP. Allowing multiple parents makes it possible

doc/release-notes-31385.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
RPC
2+
3+
The `submitpackage` RPC, which allows submissions of child-with-parents
4+
packages, no longer requires that all unconfirmed parents be present. The
5+
package may contain other in-mempool ancestors as well. (#31385)
6+
7+
P2P
8+
9+
Opportunistic 1-parent-1-child package relay has been improved to handle
10+
situations when the child already has unconfirmed parent(s) in the mempool.
11+
This means that 1p1c packages can be accepted and propagate, even if they are
12+
connected to broader topologies: multi-parent-1-child (where only 1 parent
13+
requires fee-bumping), grandparent-parent-child (where only parent requires
14+
fee-bumping) etc. (#31385)

src/policy/packages.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ static_assert(MAX_PACKAGE_WEIGHT >= MAX_STANDARD_TX_WEIGHT);
2626

2727
// If a package is to be evaluated, it must be at least as large as the mempool's ancestor/descendant limits,
2828
// otherwise transactions that would be individually accepted may be rejected in a package erroneously.
29-
// Since a submitted package must be child-with-unconfirmed-parents (all of the transactions are an ancestor
29+
// Since a submitted package must be child-with-parents (all of the transactions are a parent
3030
// of the child), package limits are ultimately bounded by mempool package limits. Ensure that the
3131
// defaults reflect this constraint.
3232
static_assert(DEFAULT_DESCENDANT_LIMIT >= MAX_PACKAGE_COUNT);

src/test/txpackage_tests.cpp

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,9 @@ BOOST_AUTO_TEST_CASE(noncontextual_package_tests)
355355

356356
BOOST_AUTO_TEST_CASE(package_submission_tests)
357357
{
358+
// Mine blocks to mature coinbases.
359+
mineBlocks(3);
360+
MockMempoolMinFee(CFeeRate(5000));
358361
LOCK(cs_main);
359362
unsigned int expected_pool_size = m_node.mempool->size();
360363
CKey parent_key = GenerateRandomKey();
@@ -441,20 +444,6 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
441444
BOOST_CHECK_EQUAL(result_quit_early.m_state.GetResult(), PackageValidationResult::PCKG_TX);
442445
}
443446

444-
// Child with missing parent.
445-
mtx_child.vin.emplace_back(COutPoint(package_unrelated[0]->GetHash(), 0));
446-
Package package_missing_parent;
447-
package_missing_parent.push_back(tx_parent);
448-
package_missing_parent.push_back(MakeTransactionRef(mtx_child));
449-
{
450-
const auto result_missing_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
451-
package_missing_parent, /*test_accept=*/false, /*client_maxfeerate=*/{});
452-
BOOST_CHECK(result_missing_parent.m_state.IsInvalid());
453-
BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
454-
BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetRejectReason(), "package-not-child-with-unconfirmed-parents");
455-
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
456-
}
457-
458447
// Submit package with parent + child.
459448
{
460449
const auto submit_parent_child = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
@@ -492,6 +481,60 @@ BOOST_AUTO_TEST_CASE(package_submission_tests)
492481

493482
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
494483
}
484+
485+
// In-mempool parent and child with missing parent.
486+
{
487+
auto tx_parent_1 = MakeTransactionRef(CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[1], /*input_vout=*/0,
488+
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
489+
/*output_destination=*/parent_locking_script,
490+
/*output_amount=*/CAmount(50 * COIN - low_fee_amt), /*submit=*/false));
491+
auto tx_parent_2 = MakeTransactionRef(CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[2], /*input_vout=*/0,
492+
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
493+
/*output_destination=*/parent_locking_script,
494+
/*output_amount=*/CAmount(50 * COIN - 800), /*submit=*/false));
495+
496+
auto tx_child_missing_parent = MakeTransactionRef(CreateValidMempoolTransaction({tx_parent_1, tx_parent_2},
497+
{{tx_parent_1->GetHash(), 0}, {tx_parent_2->GetHash(), 0}},
498+
/*input_height=*/0, {parent_key},
499+
{{49 * COIN, child_locking_script}}, /*submit=*/false));
500+
501+
Package package_missing_parent{tx_parent_1, tx_child_missing_parent};
502+
503+
const auto result_missing_parent = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
504+
package_missing_parent, /*test_accept=*/false, /*client_maxfeerate=*/{});
505+
if (auto err_missing_parent{CheckPackageMempoolAcceptResult(package_missing_parent, result_missing_parent, /*expect_valid=*/false, m_node.mempool.get())}) {
506+
BOOST_ERROR(err_missing_parent.value());
507+
} else {
508+
auto it_parent = result_missing_parent.m_tx_results.find(tx_parent_1->GetWitnessHash());
509+
auto it_child = result_missing_parent.m_tx_results.find(tx_child_missing_parent->GetWitnessHash());
510+
511+
BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetResult(), PackageValidationResult::PCKG_TX);
512+
BOOST_CHECK_EQUAL(result_missing_parent.m_state.GetRejectReason(), "transaction failed");
513+
514+
BOOST_CHECK_EQUAL(it_parent->second.m_state.GetResult(), TxValidationResult::TX_RECONSIDERABLE);
515+
BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MISSING_INPUTS);
516+
BOOST_CHECK_EQUAL(it_child->second.m_state.GetRejectReason(), "bad-txns-inputs-missingorspent");
517+
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
518+
}
519+
520+
// Submit parent2 ahead of time, should become ok.
521+
Package package_just_parent2{tx_parent_2};
522+
expected_pool_size += 1;
523+
const auto result_just_parent2 = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
524+
package_just_parent2, /*test_accept=*/false, /*client_maxfeerate=*/{});
525+
if (auto err_parent2{CheckPackageMempoolAcceptResult(package_just_parent2, result_just_parent2, /*expect_valid=*/true, m_node.mempool.get())}) {
526+
BOOST_ERROR(err_parent2.value());
527+
}
528+
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
529+
530+
const auto result_parent_already_in = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
531+
package_missing_parent, /*test_accept=*/false, /*client_maxfeerate=*/{});
532+
expected_pool_size += 2;
533+
if (auto err_parent_already_in{CheckPackageMempoolAcceptResult(package_missing_parent, result_parent_already_in, /*expect_valid=*/true, m_node.mempool.get())}) {
534+
BOOST_ERROR(err_parent_already_in.value());
535+
}
536+
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
537+
}
495538
}
496539

497540
// Tests for packages containing a single transaction

src/validation.cpp

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ class MemPoolAccept
520520
};
521521
}
522522

523-
/** Parameters for child-with-unconfirmed-parents package validation. */
523+
/** Parameters for child-with-parents package validation. */
524524
static ATMPArgs PackageChildWithParents(const CChainParams& chainparams, int64_t accept_time,
525525
std::vector<COutPoint>& coins_to_uncache, const std::optional<CFeeRate>& client_maxfeerate) {
526526
return ATMPArgs{/* m_chainparams */ chainparams,
@@ -1716,7 +1716,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
17161716

17171717
// There are two topologies we are able to handle through this function:
17181718
// (1) A single transaction
1719-
// (2) A child-with-unconfirmed-parents package.
1719+
// (2) A child-with-parents package.
17201720
// Check that the package is well-formed. If it isn't, we won't try to validate any of the
17211721
// transactions and thus won't return any MempoolAcceptResults, just a package-wide error.
17221722

@@ -1725,49 +1725,11 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
17251725
return PackageMempoolAcceptResult(package_state_quit_early, {});
17261726
}
17271727

1728-
if (package.size() > 1) {
1728+
if (package.size() > 1 && !IsChildWithParents(package)) {
17291729
// All transactions in the package must be a parent of the last transaction. This is just an
17301730
// opportunity for us to fail fast on a context-free check without taking the mempool lock.
1731-
if (!IsChildWithParents(package)) {
1732-
package_state_quit_early.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-parents");
1733-
return PackageMempoolAcceptResult(package_state_quit_early, {});
1734-
}
1735-
1736-
// IsChildWithParents() guarantees the package is > 1 transactions.
1737-
assert(package.size() > 1);
1738-
// The package must be 1 child with all of its unconfirmed parents. The package is expected to
1739-
// be sorted, so the last transaction is the child.
1740-
const auto& child = package.back();
1741-
std::unordered_set<uint256, SaltedTxidHasher> unconfirmed_parent_txids;
1742-
std::transform(package.cbegin(), package.cend() - 1,
1743-
std::inserter(unconfirmed_parent_txids, unconfirmed_parent_txids.end()),
1744-
[](const auto& tx) { return tx->GetHash(); });
1745-
1746-
// All child inputs must refer to a preceding package transaction or a confirmed UTXO. The only
1747-
// way to verify this is to look up the child's inputs in our current coins view (not including
1748-
// mempool), and enforce that all parents not present in the package be available at chain tip.
1749-
// Since this check can bring new coins into the coins cache, keep track of these coins and
1750-
// uncache them if we don't end up submitting this package to the mempool.
1751-
const CCoinsViewCache& coins_tip_cache = m_active_chainstate.CoinsTip();
1752-
for (const auto& input : child->vin) {
1753-
if (!coins_tip_cache.HaveCoinInCache(input.prevout)) {
1754-
args.m_coins_to_uncache.push_back(input.prevout);
1755-
}
1756-
}
1757-
// Using the MemPoolAccept m_view cache allows us to look up these same coins faster later.
1758-
// This should be connecting directly to CoinsTip, not to m_viewmempool, because we specifically
1759-
// require inputs to be confirmed if they aren't in the package.
1760-
m_view.SetBackend(m_active_chainstate.CoinsTip());
1761-
const auto package_or_confirmed = [this, &unconfirmed_parent_txids](const auto& input) {
1762-
return unconfirmed_parent_txids.count(input.prevout.hash) > 0 || m_view.HaveCoin(input.prevout);
1763-
};
1764-
if (!std::all_of(child->vin.cbegin(), child->vin.cend(), package_or_confirmed)) {
1765-
package_state_quit_early.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-unconfirmed-parents");
1766-
return PackageMempoolAcceptResult(package_state_quit_early, {});
1767-
}
1768-
// Protect against bugs where we pull more inputs from disk that miss being added to
1769-
// coins_to_uncache. The backend will be connected again when needed in PreChecks.
1770-
m_view.SetBackend(m_dummy);
1731+
package_state_quit_early.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-parents");
1732+
return PackageMempoolAcceptResult(package_state_quit_early, {});
17711733
}
17721734

17731735
LOCK(m_pool.cs);

test/functional/p2p_1p1c_network.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def create_packages(self):
101101
package_hex_2, parent_2, child_2 = self.create_basic_1p1c(self.wallet_nonsegwit)
102102

103103
# 3: 2-parent-1-child package. Both parents are above mempool min feerate. No package submission happens.
104-
# We require packages to be child-with-unconfirmed-parents and only allow 1-parent-1-child packages.
104+
# We require packages to be child-with-parents and only allow 1-parent-1-child packages.
105105
package_hex_3, parent_31, _parent_32, child_3 = self.create_package_2p1c(self.wallet)
106106

107107
# 4: parent + child package where the child spends 2 different outputs from the parent.

0 commit comments

Comments
 (0)