Skip to content

Commit 80fc1af

Browse files
committed
Merge bitcoin/bitcoin#26289: Use util::Result in for calculating mempool ancestors
47c4b1f mempool: log/halt when CalculateMemPoolAncestors fails unexpectedly (stickies-v) 5481f65 mempool: add AssumeCalculateMemPoolAncestors helper function (stickies-v) f911bdf mempool: use util::Result for CalculateMemPoolAncestors (stickies-v) 66e028f mempool: use util::Result for CalculateAncestorsAndCheckLimits (stickies-v) Pull request description: Upon reviewing the documentation for `CTxMemPool::CalculateMemPoolAncestors`, I noticed `setAncestors` was meant to be an `out` parameter but actually is an `in,out` parameter, as can be observed by adding `assert(setAncestors.empty());` as the first line in the function and running `make check`. This PR fixes this unexpected behaviour and introduces refactoring improvements to make intents and effects of the code more clear. ## Unexpected behaviour This behaviour occurs only in the package acceptance path, currently only triggered by `testmempoolaccept` and `submitpackage` RPCs. In `MemPoolAccept::AcceptMultipleTransactions()`, we first call `PreChecks()` and then `SubmitPackage()` with the same `Workspace ws` reference. `PreChecks` leaves `ws.m_ancestors` in a potentially non-empty state, before it is passed on to `MemPoolAccept::SubmitPackage`. `SubmitPackage` is the only place where `setAncestors` isn't guaranteed to be empty before calling `CalculateMemPoolAncestors`. The most straightforward fix is to just forcefully clear `setAncestors` at the beginning of CalculateMemPoolAncestors, which is done in the first bugfix commit. ## Improvements ### Return value instead of out-parameters This PR updates the function signatures for `CTxMemPool::CalculateMemPoolAncestors` and `CTxMemPool::CalculateAncestorsAndCheckLimits` to use a `util::Result` return type and eliminate both the `setAncestors` `in,out`-parameter as well as the error string. It simplifies the code and makes the intent and effects more explicit. ### Observability There are 7 instances where we currently call `CalculateMemPoolAncestors` without actually checking if the function succeeded because we assume that it can't fail, such as in [miner.cpp](https://github.com/bitcoin/bitcoin/blob/69b10212ea5370606c7a5aa500a70c36b4cbb58f/src/node/miner.cpp#L399). This PR adds a new wrapper `AssumeCalculateMemPoolAncestors` function that logs such unexpected failures, or in case of debug builds even halts the program. It's not crucial to the objective, more of an observability improvement that seems sensible to add on here. ACKs for top commit: achow101: ACK 47c4b1f w0xlt: ACK bitcoin/bitcoin@47c4b1f glozow: ACK 47c4b1f furszy: light code review ACK 47c4b1f aureleoules: ACK 47c4b1f Tree-SHA512: d908dad00d1a5645eb865c4877cc0bae74b9cd3332a3641eb4a285431aef119f9fc78172d38b55c592168a73dae83242e6af3348815f7b37cbe2d448a3a58648
2 parents f301bf5 + 47c4b1f commit 80fc1af

File tree

8 files changed

+121
-111
lines changed

8 files changed

+121
-111
lines changed

src/node/interfaces.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -672,12 +672,9 @@ class ChainImpl : public Chain
672672
if (!m_node.mempool) return true;
673673
LockPoints lp;
674674
CTxMemPoolEntry entry(tx, 0, 0, 0, false, 0, lp);
675-
CTxMemPool::setEntries ancestors;
676675
const CTxMemPool::Limits& limits{m_node.mempool->m_limits};
677-
std::string unused_error_string;
678676
LOCK(m_node.mempool->cs);
679-
return m_node.mempool->CalculateMemPoolAncestors(
680-
entry, ancestors, limits, unused_error_string);
677+
return m_node.mempool->CalculateMemPoolAncestors(entry, limits).has_value();
681678
}
682679
CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override
683680
{

src/node/miner.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,9 +394,7 @@ void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSele
394394
continue;
395395
}
396396

397-
CTxMemPool::setEntries ancestors;
398-
std::string dummy;
399-
mempool.CalculateMemPoolAncestors(*iter, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false);
397+
auto ancestors{mempool.AssumeCalculateMemPoolAncestors(__func__, *iter, CTxMemPool::Limits::NoLimits(), /*fSearchForParents=*/false)};
400398

401399
onlyUnconfirmed(ancestors);
402400
ancestors.insert(iter);

src/policy/rbf.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool)
2222
{
2323
AssertLockHeld(pool.cs);
2424

25-
CTxMemPool::setEntries ancestors;
26-
2725
// First check the transaction itself.
2826
if (SignalsOptInRBF(tx)) {
2927
return RBFTransactionState::REPLACEABLE_BIP125;
@@ -37,9 +35,9 @@ RBFTransactionState IsRBFOptIn(const CTransaction& tx, const CTxMemPool& pool)
3735

3836
// If all the inputs have nSequence >= maxint-1, it still might be
3937
// signaled for RBF if any unconfirmed parents have signaled.
40-
std::string dummy;
41-
CTxMemPoolEntry entry = *pool.mapTx.find(tx.GetHash());
42-
pool.CalculateMemPoolAncestors(entry, ancestors, CTxMemPool::Limits::NoLimits(), dummy, false);
38+
const CTxMemPoolEntry entry{*pool.mapTx.find(tx.GetHash())};
39+
auto ancestors{pool.AssumeCalculateMemPoolAncestors(__func__, entry, CTxMemPool::Limits::NoLimits(),
40+
/*fSearchForParents=*/false)};
4341

4442
for (CTxMemPool::txiter it : ancestors) {
4543
if (SignalsOptInRBF(it->GetTx())) {

src/rpc/mempool.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include <util/moneystr.h>
2424
#include <util/time.h>
2525

26+
#include <utility>
27+
2628
using kernel::DumpMempool;
2729

2830
using node::DEFAULT_MAX_RAW_TX_FEE_RATE;
@@ -449,19 +451,17 @@ static RPCHelpMan getmempoolancestors()
449451
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not in mempool");
450452
}
451453

452-
CTxMemPool::setEntries setAncestors;
453-
std::string dummy;
454-
mempool.CalculateMemPoolAncestors(*it, setAncestors, CTxMemPool::Limits::NoLimits(), dummy, false);
454+
auto ancestors{mempool.AssumeCalculateMemPoolAncestors(__func__, *it, CTxMemPool::Limits::NoLimits(), /*fSearchForParents=*/false)};
455455

456456
if (!fVerbose) {
457457
UniValue o(UniValue::VARR);
458-
for (CTxMemPool::txiter ancestorIt : setAncestors) {
458+
for (CTxMemPool::txiter ancestorIt : ancestors) {
459459
o.push_back(ancestorIt->GetTx().GetHash().ToString());
460460
}
461461
return o;
462462
} else {
463463
UniValue o(UniValue::VOBJ);
464-
for (CTxMemPool::txiter ancestorIt : setAncestors) {
464+
for (CTxMemPool::txiter ancestorIt : ancestors) {
465465
const CTxMemPoolEntry &e = *ancestorIt;
466466
const uint256& _hash = e.GetTx().GetHash();
467467
UniValue info(UniValue::VOBJ);

src/test/mempool_tests.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,10 +202,9 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
202202
tx7.vout[1].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
203203
tx7.vout[1].nValue = 1 * COIN;
204204

205-
CTxMemPool::setEntries setAncestorsCalculated;
206-
std::string dummy;
207-
BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(2'000'000LL).FromTx(tx7), setAncestorsCalculated, CTxMemPool::Limits::NoLimits(), dummy), true);
208-
BOOST_CHECK(setAncestorsCalculated == setAncestors);
205+
auto ancestors_calculated{pool.CalculateMemPoolAncestors(entry.Fee(2000000LL).FromTx(tx7), CTxMemPool::Limits::NoLimits())};
206+
BOOST_REQUIRE(ancestors_calculated.has_value());
207+
BOOST_CHECK(*ancestors_calculated == setAncestors);
209208

210209
pool.addUnchecked(entry.FromTx(tx7), setAncestors);
211210
BOOST_CHECK_EQUAL(pool.size(), 7U);
@@ -261,9 +260,9 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
261260
tx10.vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
262261
tx10.vout[0].nValue = 10 * COIN;
263262

264-
setAncestorsCalculated.clear();
265-
BOOST_CHECK_EQUAL(pool.CalculateMemPoolAncestors(entry.Fee(200'000LL).Time(NodeSeconds{4s}).FromTx(tx10), setAncestorsCalculated, CTxMemPool::Limits::NoLimits(), dummy), true);
266-
BOOST_CHECK(setAncestorsCalculated == setAncestors);
263+
ancestors_calculated = pool.CalculateMemPoolAncestors(entry.Fee(200000LL).Time(NodeSeconds{4s}).FromTx(tx10), CTxMemPool::Limits::NoLimits());
264+
BOOST_REQUIRE(ancestors_calculated);
265+
BOOST_CHECK(*ancestors_calculated == setAncestors);
267266

268267
pool.addUnchecked(entry.FromTx(tx10), setAncestors);
269268

src/txmempool.cpp

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@
1717
#include <util/check.h>
1818
#include <util/moneystr.h>
1919
#include <util/overflow.h>
20+
#include <util/result.h>
2021
#include <util/system.h>
2122
#include <util/time.h>
23+
#include <util/translation.h>
2224
#include <validationinterface.h>
2325

2426
#include <cmath>
2527
#include <optional>
28+
#include <string_view>
29+
#include <utility>
2630

2731
bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp)
2832
{
@@ -147,50 +151,46 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256>& vHashes
147151
}
148152
}
149153

150-
bool CTxMemPool::CalculateAncestorsAndCheckLimits(size_t entry_size,
151-
size_t entry_count,
152-
setEntries& setAncestors,
153-
CTxMemPoolEntry::Parents& staged_ancestors,
154-
const Limits& limits,
155-
std::string &errString) const
154+
util::Result<CTxMemPool::setEntries> CTxMemPool::CalculateAncestorsAndCheckLimits(
155+
size_t entry_size,
156+
size_t entry_count,
157+
CTxMemPoolEntry::Parents& staged_ancestors,
158+
const Limits& limits) const
156159
{
157160
size_t totalSizeWithAncestors = entry_size;
161+
setEntries ancestors;
158162

159163
while (!staged_ancestors.empty()) {
160164
const CTxMemPoolEntry& stage = staged_ancestors.begin()->get();
161165
txiter stageit = mapTx.iterator_to(stage);
162166

163-
setAncestors.insert(stageit);
167+
ancestors.insert(stageit);
164168
staged_ancestors.erase(stage);
165169
totalSizeWithAncestors += stageit->GetTxSize();
166170

167171
if (stageit->GetSizeWithDescendants() + entry_size > static_cast<uint64_t>(limits.descendant_size_vbytes)) {
168-
errString = strprintf("exceeds descendant size limit for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_size_vbytes);
169-
return false;
172+
return util::Error{Untranslated(strprintf("exceeds descendant size limit for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_size_vbytes))};
170173
} else if (stageit->GetCountWithDescendants() + entry_count > static_cast<uint64_t>(limits.descendant_count)) {
171-
errString = strprintf("too many descendants for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_count);
172-
return false;
174+
return util::Error{Untranslated(strprintf("too many descendants for tx %s [limit: %u]", stageit->GetTx().GetHash().ToString(), limits.descendant_count))};
173175
} else if (totalSizeWithAncestors > static_cast<uint64_t>(limits.ancestor_size_vbytes)) {
174-
errString = strprintf("exceeds ancestor size limit [limit: %u]", limits.ancestor_size_vbytes);
175-
return false;
176+
return util::Error{Untranslated(strprintf("exceeds ancestor size limit [limit: %u]", limits.ancestor_size_vbytes))};
176177
}
177178

178179
const CTxMemPoolEntry::Parents& parents = stageit->GetMemPoolParentsConst();
179180
for (const CTxMemPoolEntry& parent : parents) {
180181
txiter parent_it = mapTx.iterator_to(parent);
181182

182183
// If this is a new ancestor, add it.
183-
if (setAncestors.count(parent_it) == 0) {
184+
if (ancestors.count(parent_it) == 0) {
184185
staged_ancestors.insert(parent);
185186
}
186-
if (staged_ancestors.size() + setAncestors.size() + entry_count > static_cast<uint64_t>(limits.ancestor_count)) {
187-
errString = strprintf("too many unconfirmed ancestors [limit: %u]", limits.ancestor_count);
188-
return false;
187+
if (staged_ancestors.size() + ancestors.size() + entry_count > static_cast<uint64_t>(limits.ancestor_count)) {
188+
return util::Error{Untranslated(strprintf("too many unconfirmed ancestors [limit: %u]", limits.ancestor_count))};
189189
}
190190
}
191191
}
192192

193-
return true;
193+
return ancestors;
194194
}
195195

196196
bool CTxMemPool::CheckPackageLimits(const Package& package,
@@ -215,20 +215,17 @@ bool CTxMemPool::CheckPackageLimits(const Package& package,
215215
// When multiple transactions are passed in, the ancestors and descendants of all transactions
216216
// considered together must be within limits even if they are not interdependent. This may be
217217
// stricter than the limits for each individual transaction.
218-
setEntries setAncestors;
219-
const auto ret = CalculateAncestorsAndCheckLimits(total_size, package.size(),
220-
setAncestors, staged_ancestors,
221-
limits, errString);
218+
const auto ancestors{CalculateAncestorsAndCheckLimits(total_size, package.size(),
219+
staged_ancestors, limits)};
222220
// It's possible to overestimate the ancestor/descendant totals.
223-
if (!ret) errString.insert(0, "possibly ");
224-
return ret;
221+
if (!ancestors.has_value()) errString = "possibly " + util::ErrorString(ancestors).original;
222+
return ancestors.has_value();
225223
}
226224

227-
bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry,
228-
setEntries &setAncestors,
229-
const Limits& limits,
230-
std::string &errString,
231-
bool fSearchForParents /* = true */) const
225+
util::Result<CTxMemPool::setEntries> CTxMemPool::CalculateMemPoolAncestors(
226+
const CTxMemPoolEntry &entry,
227+
const Limits& limits,
228+
bool fSearchForParents /* = true */) const
232229
{
233230
CTxMemPoolEntry::Parents staged_ancestors;
234231
const CTransaction &tx = entry.GetTx();
@@ -242,8 +239,7 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry,
242239
if (piter) {
243240
staged_ancestors.insert(**piter);
244241
if (staged_ancestors.size() + 1 > static_cast<uint64_t>(limits.ancestor_count)) {
245-
errString = strprintf("too many unconfirmed parents [limit: %u]", limits.ancestor_count);
246-
return false;
242+
return util::Error{Untranslated(strprintf("too many unconfirmed parents [limit: %u]", limits.ancestor_count))};
247243
}
248244
}
249245
}
@@ -254,9 +250,22 @@ bool CTxMemPool::CalculateMemPoolAncestors(const CTxMemPoolEntry &entry,
254250
staged_ancestors = it->GetMemPoolParentsConst();
255251
}
256252

257-
return CalculateAncestorsAndCheckLimits(entry.GetTxSize(), /*entry_count=*/1,
258-
setAncestors, staged_ancestors,
259-
limits, errString);
253+
return CalculateAncestorsAndCheckLimits(entry.GetTxSize(), /*entry_count=*/1, staged_ancestors,
254+
limits);
255+
}
256+
257+
CTxMemPool::setEntries CTxMemPool::AssumeCalculateMemPoolAncestors(
258+
std::string_view calling_fn_name,
259+
const CTxMemPoolEntry &entry,
260+
const Limits& limits,
261+
bool fSearchForParents /* = true */) const
262+
{
263+
auto result{Assume(CalculateMemPoolAncestors(entry, limits, fSearchForParents))};
264+
if (!result) {
265+
LogPrintLevel(BCLog::MEMPOOL, BCLog::Level::Error, "%s: CalculateMemPoolAncestors failed unexpectedly, continuing with empty ancestor set (%s)\n",
266+
calling_fn_name, util::ErrorString(result).original);
267+
}
268+
return std::move(result).value_or(CTxMemPool::setEntries{});
260269
}
261270

262271
void CTxMemPool::UpdateAncestorsOf(bool add, txiter it, setEntries &setAncestors)
@@ -320,9 +329,7 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b
320329
}
321330
}
322331
for (txiter removeIt : entriesToRemove) {
323-
setEntries setAncestors;
324332
const CTxMemPoolEntry &entry = *removeIt;
325-
std::string dummy;
326333
// Since this is a tx that is already in the mempool, we can call CMPA
327334
// with fSearchForParents = false. If the mempool is in a consistent
328335
// state, then using true or false should both be correct, though false
@@ -342,10 +349,10 @@ void CTxMemPool::UpdateForRemoveFromMempool(const setEntries &entriesToRemove, b
342349
// mempool parents we'd calculate by searching, and it's important that
343350
// we use the cached notion of ancestor transactions as the set of
344351
// things to update for removal.
345-
CalculateMemPoolAncestors(entry, setAncestors, Limits::NoLimits(), dummy, false);
352+
auto ancestors{AssumeCalculateMemPoolAncestors(__func__, entry, Limits::NoLimits(), /*fSearchForParents=*/false)};
346353
// Note that UpdateAncestorsOf severs the child links that point to
347354
// removeIt in the entries for the parents of removeIt.
348-
UpdateAncestorsOf(false, removeIt, setAncestors);
355+
UpdateAncestorsOf(false, removeIt, ancestors);
349356
}
350357
// After updating all the ancestor sizes, we can now sever the link between each
351358
// transaction being removed and any mempool children (ie, update CTxMemPoolEntry::m_parents
@@ -695,15 +702,13 @@ void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendhei
695702
assert(setParentCheck.size() == it->GetMemPoolParentsConst().size());
696703
assert(std::equal(setParentCheck.begin(), setParentCheck.end(), it->GetMemPoolParentsConst().begin(), comp));
697704
// Verify ancestor state is correct.
698-
setEntries setAncestors;
699-
std::string dummy;
700-
CalculateMemPoolAncestors(*it, setAncestors, Limits::NoLimits(), dummy);
701-
uint64_t nCountCheck = setAncestors.size() + 1;
705+
auto ancestors{AssumeCalculateMemPoolAncestors(__func__, *it, Limits::NoLimits())};
706+
uint64_t nCountCheck = ancestors.size() + 1;
702707
uint64_t nSizeCheck = it->GetTxSize();
703708
CAmount nFeesCheck = it->GetModifiedFee();
704709
int64_t nSigOpCheck = it->GetSigOpCost();
705710

706-
for (txiter ancestorIt : setAncestors) {
711+
for (txiter ancestorIt : ancestors) {
707712
nSizeCheck += ancestorIt->GetTxSize();
708713
nFeesCheck += ancestorIt->GetModifiedFee();
709714
nSigOpCheck += ancestorIt->GetSigOpCost();
@@ -858,10 +863,8 @@ void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeD
858863
if (it != mapTx.end()) {
859864
mapTx.modify(it, [&nFeeDelta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(nFeeDelta); });
860865
// Now update all ancestors' modified fees with descendants
861-
setEntries setAncestors;
862-
std::string dummy;
863-
CalculateMemPoolAncestors(*it, setAncestors, Limits::NoLimits(), dummy, false);
864-
for (txiter ancestorIt : setAncestors) {
866+
auto ancestors{AssumeCalculateMemPoolAncestors(__func__, *it, Limits::NoLimits(), /*fSearchForParents=*/false)};
867+
for (txiter ancestorIt : ancestors) {
865868
mapTx.modify(ancestorIt, [=](CTxMemPoolEntry& e){ e.UpdateDescendantState(0, nFeeDelta, 0);});
866869
}
867870
// Now update all descendants' modified fees with ancestors
@@ -998,10 +1001,8 @@ int CTxMemPool::Expire(std::chrono::seconds time)
9981001

9991002
void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, bool validFeeEstimate)
10001003
{
1001-
setEntries setAncestors;
1002-
std::string dummy;
1003-
CalculateMemPoolAncestors(entry, setAncestors, Limits::NoLimits(), dummy);
1004-
return addUnchecked(entry, setAncestors, validFeeEstimate);
1004+
auto ancestors{AssumeCalculateMemPoolAncestors(__func__, entry, Limits::NoLimits())};
1005+
return addUnchecked(entry, ancestors, validFeeEstimate);
10051006
}
10061007

10071008
void CTxMemPool::UpdateChild(txiter entry, txiter child, bool add)

0 commit comments

Comments
 (0)