Skip to content

Commit d3466e4

Browse files
committed
CheckPackageMempoolAcceptResult: Check package rbf invariants
1 parent 316d7b6 commit d3466e4

File tree

1 file changed

+28
-0
lines changed

1 file changed

+28
-0
lines changed

src/test/util/txmempool.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <chainparams.h>
88
#include <node/context.h>
99
#include <node/mempool_args.h>
10+
#include <policy/rbf.h>
1011
#include <policy/v3_policy.h>
1112
#include <txmempool.h>
1213
#include <util/check.h>
@@ -68,6 +69,28 @@ std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns,
6869
return strprintf("tx %s unexpectedly failed: %s", wtxid.ToString(), atmp_result.m_state.ToString());
6970
}
7071

72+
// Each subpackage is allowed MAX_REPLACEMENT_CANDIDATES replacements (only checking individually here)
73+
if (atmp_result.m_replaced_transactions.size() > MAX_REPLACEMENT_CANDIDATES) {
74+
return strprintf("tx %s result replaced too many transactions",
75+
wtxid.ToString());
76+
}
77+
78+
// Replacements can't happen for subpackages larger than 2
79+
if (!atmp_result.m_replaced_transactions.empty() &&
80+
atmp_result.m_wtxids_fee_calculations.has_value() && atmp_result.m_wtxids_fee_calculations.value().size() > 2) {
81+
return strprintf("tx %s was part of a too-large package RBF subpackage",
82+
wtxid.ToString());
83+
}
84+
85+
if (!atmp_result.m_replaced_transactions.empty() && mempool) {
86+
LOCK(mempool->cs);
87+
// If replacements occurred and it used 2 transactions, this is a package RBF and should result in a cluster of size 2
88+
if (atmp_result.m_wtxids_fee_calculations.has_value() && atmp_result.m_wtxids_fee_calculations.value().size() == 2) {
89+
const auto cluster = mempool->GatherClusters({tx->GetHash()});
90+
if (cluster.size() != 2) return strprintf("tx %s has too many ancestors or descendants for a package rbf", wtxid.ToString());
91+
}
92+
}
93+
7194
// m_vsize and m_base_fees should exist iff the result was VALID or MEMPOOL_ENTRY
7295
const bool mempool_entry{atmp_result.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY};
7396
if (atmp_result.m_base_fees.has_value() != (valid || mempool_entry)) {
@@ -108,6 +131,11 @@ std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns,
108131
return strprintf("wtxid %s should not be in mempool", wtxid.ToString());
109132
}
110133
}
134+
for (const auto& tx_ref : atmp_result.m_replaced_transactions) {
135+
if (mempool->exists(GenTxid::Txid(tx_ref->GetHash()))) {
136+
return strprintf("tx %s should not be in mempool as it was replaced", tx_ref->GetWitnessHash().ToString());
137+
}
138+
}
111139
}
112140
}
113141
return std::nullopt;

0 commit comments

Comments
 (0)