Skip to content

Commit 127719f

Browse files
committed
test: Add CheckMempoolEphemeralInvariants
Checks that transactions in mempool with dust follow expected invariants.
1 parent e2e30e8 commit 127719f

File tree

2 files changed

+60
-0
lines changed

2 files changed

+60
-0
lines changed

src/test/util/txmempool.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,54 @@ std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns,
141141
return std::nullopt;
142142
}
143143

144+
std::vector<uint32_t> GetDustIndexes(const CTransactionRef tx_ref, CFeeRate dust_relay_rate)
145+
{
146+
std::vector<uint32_t> dust_indexes;
147+
for (size_t i = 0; i < tx_ref->vout.size(); ++i) {
148+
const auto& output = tx_ref->vout[i];
149+
if (IsDust(output, dust_relay_rate)) dust_indexes.push_back(i);
150+
}
151+
152+
return dust_indexes;
153+
}
154+
155+
void CheckMempoolEphemeralInvariants(const CTxMemPool& tx_pool)
156+
{
157+
LOCK(tx_pool.cs);
158+
for (const auto& tx_info : tx_pool.infoAll()) {
159+
const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash()));
160+
161+
std::vector<uint32_t> dust_indexes = GetDustIndexes(tx_info.tx, tx_pool.m_opts.dust_relay_feerate);
162+
163+
Assert(dust_indexes.size() < 2);
164+
165+
if (dust_indexes.empty()) continue;
166+
167+
// Transaction must have no base fee
168+
Assert(entry.GetFee() == 0 && entry.GetModifiedFee() == 0);
169+
170+
// Transaction has single dust; make sure it's swept or will not be mined
171+
const auto& children = entry.GetMemPoolChildrenConst();
172+
173+
// Multiple children should never happen as non-dust-spending child
174+
// can get mined as package
175+
Assert(children.size() < 2);
176+
177+
if (children.empty()) {
178+
// No children and no fees; modified fees aside won't get mined so it's fine
179+
// Happens naturally if child spend is RBF cycled away.
180+
continue;
181+
}
182+
183+
// Only-child should be spending the dust
184+
const auto& only_child = children.begin()->get().GetTx();
185+
COutPoint dust_outpoint{tx_info.tx->GetHash(), dust_indexes[0]};
186+
Assert(std::any_of(only_child.vin.begin(), only_child.vin.end(), [&dust_outpoint](const CTxIn& txin) {
187+
return txin.prevout == dust_outpoint;
188+
}));
189+
}
190+
}
191+
144192
void CheckMempoolTRUCInvariants(const CTxMemPool& tx_pool)
145193
{
146194
LOCK(tx_pool.cs);

src/test/util/txmempool.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns,
4747
bool expect_valid,
4848
const CTxMemPool* mempool);
4949

50+
/** Check that we never get into a state where an ephemeral dust
51+
* transaction would be mined without the spend of the dust
52+
* also being mined. This assumes standardness checks are being
53+
* enforced.
54+
*/
55+
void CheckMempoolEphemeralInvariants(const CTxMemPool& tx_pool);
56+
57+
/** Return indexes of the transaction's outputs that are considered dust
58+
* at given dust_relay_rate.
59+
*/
60+
std::vector<uint32_t> GetDustIndexes(const CTransactionRef tx_ref, CFeeRate dust_relay_rate);
61+
5062
/** For every transaction in tx_pool, check TRUC invariants:
5163
* - a TRUC tx's ancestor count must be within TRUC_ANCESTOR_LIMIT
5264
* - a TRUC tx's descendant count must be within TRUC_DESCENDANT_LIMIT

0 commit comments

Comments
 (0)