Skip to content

Commit bc49650

Browse files
committed
Merge bitcoin/bitcoin#24310: docs / fixups from RBF and packages
77202f0 [doc] package deduplication (glozow) d35a3cb [doc] clarify inaccurate comment about replacements paying higher feerate (glozow) 5ae187f [validation] look up transaction by txid (glozow) Pull request description: - Use txid, not wtxid, for `mempool.GetIter()`: bitcoin/bitcoin#22674 (comment) - Fix a historically inaccurate comment about RBF during the refactors: bitcoin/bitcoin#22855 (comment) - Add a section about package deduplication to policy/packages.md: bitcoin/bitcoin#24152 (comment) and bitcoin/bitcoin#24152 (comment) (I'm intending for this to be in v23 since it's fixups for things that are already merged, which is why I split it from #24152) ACKs for top commit: t-bast: LGTM, ACK bitcoin/bitcoin@77202f0 darosior: ACK 77202f0 LarryRuane: ACK 77202f0 Tree-SHA512: a428e791dfa59c359d3ccc67e8d3a4c1239815d2f6b29898e129700079271c00b3a45f091f70b65a6e54aa00a3d5b678b6da29d2a76b6cd6f946eaa7082ea696
2 parents 00f8492 + 77202f0 commit bc49650

File tree

3 files changed

+36
-7
lines changed

3 files changed

+36
-7
lines changed

doc/policy/packages.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,18 @@ test accepts):
5757

5858
- Warning: Batched fee-bumping may be unsafe for some use cases. Users and application developers
5959
should take caution if utilizing multi-parent packages.
60+
61+
* Transactions in the package that have the same txid as another transaction already in the mempool
62+
will be removed from the package prior to submission ("deduplication").
63+
64+
- *Rationale*: Node operators are free to set their mempool policies however they please, nodes
65+
may receive transactions in different orders, and malicious counterparties may try to take
66+
advantage of policy differences to pin or delay propagation of transactions. As such, it's
67+
possible for some package transaction(s) to already be in the mempool, and there is no need to
68+
repeat validation for those transactions or double-count them in fees.
69+
70+
- *Rationale*: We want to prevent potential censorship vectors. We should not reject entire
71+
packages because we already have one of the transactions. Also, if an attacker first broadcasts
72+
a competing package or transaction with a mutated witness, even though the two
73+
same-txid-different-witness transactions are conflicting and cannot replace each other, the
74+
honest package should still be considered for acceptance.

src/test/txpackage_tests.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,17 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
413413

414414
BOOST_CHECK(m_node.mempool->exists(GenTxid::Txid(ptx_child2->GetHash())));
415415
BOOST_CHECK(!m_node.mempool->exists(GenTxid::Wtxid(ptx_child2->GetWitnessHash())));
416+
417+
// Deduplication should work when wtxid != txid. Submit package with the already-in-mempool
418+
// transactions again, which should not fail.
419+
const auto submit_segwit_dedup = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
420+
{ptx_parent, ptx_child1}, /*test_accept=*/ false);
421+
BOOST_CHECK_MESSAGE(submit_segwit_dedup.m_state.IsValid(),
422+
"Package validation unexpectedly failed: " << submit_segwit_dedup.m_state.GetRejectReason());
423+
auto it_parent_dup = submit_segwit_dedup.m_tx_results.find(ptx_parent->GetWitnessHash());
424+
auto it_child_dup = submit_segwit_dedup.m_tx_results.find(ptx_child1->GetWitnessHash());
425+
BOOST_CHECK(it_parent_dup->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
426+
BOOST_CHECK(it_child_dup->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY);
416427
}
417428

418429
// Try submitting Package1{child2, grandchild} where child2 is same-txid-different-witness as

src/validation.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -915,12 +915,15 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
915915
TxValidationState& state = ws.m_state;
916916

917917
CFeeRate newFeeRate(ws.m_modified_fees, ws.m_vsize);
918-
// It's possible that the replacement pays more fees than its direct conflicts but not more
919-
// than all conflicts (i.e. the direct conflicts have high-fee descendants). However, if the
920-
// replacement doesn't pay more fees than its direct conflicts, then we can be sure it's not
921-
// more economically rational to mine. Before we go digging through the mempool for all
922-
// transactions that would need to be removed (direct conflicts and all descendants), check
923-
// that the replacement transaction pays more than its direct conflicts.
918+
// The replacement transaction must have a higher feerate than its direct conflicts.
919+
// - The motivation for this check is to ensure that the replacement transaction is preferable for
920+
// block-inclusion, compared to what would be removed from the mempool.
921+
// - This logic predates ancestor feerate-based transaction selection, which is why it doesn't
922+
// consider feerates of descendants.
923+
// - Note: Ancestor feerate-based transaction selection has made this comparison insufficient to
924+
// guarantee that this is incentive-compatible for miners, because it is possible for a
925+
// descendant transaction of a direct conflict to pay a higher feerate than the transaction that
926+
// might replace them, under these rules.
924927
if (const auto err_string{PaysMoreThanConflicts(ws.m_iters_conflicting, newFeeRate, hash)}) {
925928
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", *err_string);
926929
}
@@ -1318,7 +1321,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
13181321
// we know is that the inputs aren't available.
13191322
if (m_pool.exists(GenTxid::Wtxid(wtxid))) {
13201323
// Exact transaction already exists in the mempool.
1321-
auto iter = m_pool.GetIter(wtxid);
1324+
auto iter = m_pool.GetIter(txid);
13221325
assert(iter != std::nullopt);
13231326
results.emplace(wtxid, MempoolAcceptResult::MempoolTx(iter.value()->GetTxSize(), iter.value()->GetFee()));
13241327
} else if (m_pool.exists(GenTxid::Txid(txid))) {

0 commit comments

Comments
 (0)