Skip to content

Commit cbbfe71

Browse files
glozowinstagibbs
authored andcommitted
cpfp carveout is excluded in packages
The behavior is not new, but this rule exits earlier than before. Previously, a carve out could have been granted in PreChecks() but then nullified in PackageMempoolChecks() when CheckPackageLimits() is called with the default limits.
1 parent 69f7ab0 commit cbbfe71

File tree

3 files changed

+41
-14
lines changed

3 files changed

+41
-14
lines changed

doc/policy/packages.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,13 @@ The following rules are enforced for all packages:
4848
heavily connected, i.e. some transaction in the package is the ancestor or descendant of all
4949
the other transactions.
5050

51-
The following rules are only enforced for packages to be submitted to the mempool (not enforced for
52-
test accepts):
51+
* [CPFP Carve Out](./mempool-limits.md#CPFP-Carve-Out) is disabled in packaged contexts. (#21800)
52+
53+
- *Rationale*: This carve out cannot be accurately applied when there are multiple transactions'
54+
ancestors and descendants being considered at the same time.
55+
56+
The following rules are only enforced for packages to be submitted to the mempool (not
57+
enforced for test accepts):
5358

5459
* Packages must be child-with-unconfirmed-parents packages. This also means packages must contain at
5560
least 2 transactions. (#22674)

src/validation.cpp

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,9 @@ class MemPoolAccept
478478
*/
479479
const std::optional<CFeeRate> m_client_maxfeerate;
480480

481+
/** Whether CPFP carveout and RBF carveout are granted. */
482+
const bool m_allow_carveouts;
483+
481484
/** Parameters for single transaction mempool validation. */
482485
static ATMPArgs SingleAccept(const CChainParams& chainparams, int64_t accept_time,
483486
bool bypass_limits, std::vector<COutPoint>& coins_to_uncache,
@@ -492,6 +495,7 @@ class MemPoolAccept
492495
/* m_package_submission */ false,
493496
/* m_package_feerates */ false,
494497
/* m_client_maxfeerate */ {}, // checked by caller
498+
/* m_allow_carveouts */ true,
495499
};
496500
}
497501

@@ -508,6 +512,7 @@ class MemPoolAccept
508512
/* m_package_submission */ false, // not submitting to mempool
509513
/* m_package_feerates */ false,
510514
/* m_client_maxfeerate */ {}, // checked by caller
515+
/* m_allow_carveouts */ false,
511516
};
512517
}
513518

@@ -524,6 +529,7 @@ class MemPoolAccept
524529
/* m_package_submission */ true,
525530
/* m_package_feerates */ true,
526531
/* m_client_maxfeerate */ client_maxfeerate,
532+
/* m_allow_carveouts */ false,
527533
};
528534
}
529535

@@ -539,6 +545,7 @@ class MemPoolAccept
539545
/* m_package_submission */ true, // do not LimitMempoolSize in Finalize()
540546
/* m_package_feerates */ false, // only 1 transaction
541547
/* m_client_maxfeerate */ package_args.m_client_maxfeerate,
548+
/* m_allow_carveouts */ false,
542549
};
543550
}
544551

@@ -554,7 +561,8 @@ class MemPoolAccept
554561
bool allow_sibling_eviction,
555562
bool package_submission,
556563
bool package_feerates,
557-
std::optional<CFeeRate> client_maxfeerate)
564+
std::optional<CFeeRate> client_maxfeerate,
565+
bool allow_carveouts)
558566
: m_chainparams{chainparams},
559567
m_accept_time{accept_time},
560568
m_bypass_limits{bypass_limits},
@@ -564,7 +572,8 @@ class MemPoolAccept
564572
m_allow_sibling_eviction{allow_sibling_eviction},
565573
m_package_submission{package_submission},
566574
m_package_feerates{package_feerates},
567-
m_client_maxfeerate{client_maxfeerate}
575+
m_client_maxfeerate{client_maxfeerate},
576+
m_allow_carveouts{allow_carveouts}
568577
{
569578
}
570579
};
@@ -917,7 +926,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
917926
CTxMemPool::Limits maybe_rbf_limits = m_pool.m_opts.limits;
918927

919928
// Calculate in-mempool ancestors, up to a limit.
920-
if (ws.m_conflicts.size() == 1) {
929+
if (ws.m_conflicts.size() == 1 && args.m_allow_carveouts) {
921930
// In general, when we receive an RBF transaction with mempool conflicts, we want to know whether we
922931
// would meet the chain limits after the conflicts have been removed. However, there isn't a practical
923932
// way to do this short of calculating the ancestor and descendant sets with an overlay cache of
@@ -956,6 +965,13 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
956965
ws.m_ancestors = std::move(*ancestors);
957966
} else {
958967
// If CalculateMemPoolAncestors fails second time, we want the original error string.
968+
const auto error_message{util::ErrorString(ancestors).original};
969+
970+
// Carve-out is not allowed in this context; fail
971+
if (!args.m_allow_carveouts) {
972+
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
973+
}
974+
959975
// Contracting/payment channels CPFP carve-out:
960976
// If the new transaction is relatively small (up to 40k weight)
961977
// and has at most one ancestor (ie ancestor limit of 2, including
@@ -974,7 +990,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
974990
.descendant_count = maybe_rbf_limits.descendant_count + 1,
975991
.descendant_size_vbytes = maybe_rbf_limits.descendant_size_vbytes + EXTRA_DESCENDANT_TX_SIZE_LIMIT,
976992
};
977-
const auto error_message{util::ErrorString(ancestors).original};
978993
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || ws.m_ptx->nVersion == 3) {
979994
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
980995
}
@@ -1431,9 +1446,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
14311446
}
14321447

14331448
// Apply package mempool ancestor/descendant limits. Skip if there is only one transaction,
1434-
// because it's unnecessary. Also, CPFP carve out can increase the limit for individual
1435-
// transactions, but this exemption is not extended to packages in CheckPackageLimits().
1436-
std::string err_string;
1449+
// because it's unnecessary.
14371450
if (txns.size() > 1 && !PackageMempoolChecks(txns, m_total_vsize, package_state)) {
14381451
return PackageMempoolAcceptResult(package_state, std::move(results));
14391452
}

test/functional/mempool_package_onemore.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,30 @@ def run_test(self):
4747

4848
# Adding one more transaction on to the chain should fail.
4949
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_tx, [utxo])
50-
# ...even if it chains on from some point in the middle of the chain.
50+
# ... or if it chains on from some point in the middle of the chain.
5151
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[2]])
5252
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[1]])
5353
# ...even if it chains on to two parent transactions with one in the chain.
5454
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[0], second_chain])
5555
# ...especially if its > 40k weight
5656
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[0]], num_outputs=350)
57+
# ...even if it's submitted with other transactions
58+
replaceable_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=[chain[0]])
59+
txns = [replaceable_tx["tx"], self.wallet.create_self_transfer_multi(utxos_to_spend=replaceable_tx["new_utxos"])["tx"]]
60+
txns_hex = [tx.serialize().hex() for tx in txns]
61+
assert_equal(self.nodes[0].testmempoolaccept(txns_hex)[0]["reject-reason"], "too-long-mempool-chain")
62+
pkg_result = self.nodes[0].submitpackage(txns_hex)
63+
assert "too-long-mempool-chain" in pkg_result["tx-results"][txns[0].getwtxid()]["error"]
64+
assert_equal(pkg_result["tx-results"][txns[1].getwtxid()]["error"], "bad-txns-inputs-missingorspent")
5765
# But not if it chains directly off the first transaction
58-
replacable_tx = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], utxos_to_spend=[chain[0]])['tx']
66+
self.nodes[0].sendrawtransaction(replaceable_tx["hex"])
5967
# and the second chain should work just fine
6068
self.chain_tx([second_chain])
6169

62-
# Make sure we can RBF the chain which used our carve-out rule
63-
replacable_tx.vout[0].nValue -= 1000000
64-
self.nodes[0].sendrawtransaction(replacable_tx.serialize().hex())
70+
# Ensure an individual transaction with single direct conflict can RBF the chain which used our carve-out rule
71+
replacement_tx = replaceable_tx["tx"]
72+
replacement_tx.vout[0].nValue -= 1000000
73+
self.nodes[0].sendrawtransaction(replacement_tx.serialize().hex())
6574

6675
# Finally, check that we added two transactions
6776
assert_equal(len(self.nodes[0].getrawmempool()), DEFAULT_ANCESTOR_LIMIT + 3)

0 commit comments

Comments
 (0)