Calculate descendant information for mempool RPC output on-the-fly
What changed, and why it matters
This commit changes how Bitcoin Core calculates and reports descendant transaction information in mempool RPC output. Previously, descendant counts, sizes, and fees were read from cached values stored on each mempool entry. Now they are computed on-the-fly by walking the descendant graph. The change is described by the project as preparation for removing cached descendant state. There is no direct evidence in the commit or supplied references that this fixes a security vulnerability; it appears to be a refactoring and consistency change.
Treat as a routine refactoring commit. Reviewers may want to confirm that CalculateDescendantData() is called only when pool.cs is held, that the graph traversal does not introduce meaningful RPC latency or lock contention, and that fee/size arithmetic cannot overflow. No immediate security response is indicated by the available evidence.
Security signals we found
Change moves from cached to on-the-fly computation of mempool descendant metadata
New graph traversal introduced in RPC path (entryToJSON)
No input validation changes, no memory safety fixes, no authentication changes observed
Commit message frames change as preparatory refactoring, not as a security fix
Evidence from the diff
The patch adds CTxMemPool::CalculateDescendantData(), which uses m_txgraph->GetDescendants() to collect all descendants of a mempool entry, then sums their sizes and modified fees. The RPC helper entryToJSON() is updated to call this new method instead of using CTxMemPoolEntry::GetCountWithDescendants(), GetSizeWithDescendants(), and GetModFeesWithDescendants(). A corresponding header declaration is added with the same EXCLUSIVE_LOCKS_REQUIRED(cs) annotation as CalculateAncestorData().
Changed components
src/rpc/mempool.cpp (entryToJSON)src/txmempool.cpp (CTxMemPool::CalculateDescendantData)src/txmempool.h (declaration)Inspect captured patch +20 / −3
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
index fa70758a..b5373697 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -291,13 +291,14 @@ static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPool
AssertLockHeld(pool.cs);
auto [ancestor_count, ancestor_size, ancestor_fees] = pool.CalculateAncestorData(e);
+ auto [descendant_count, descendant_size, descendant_fees] = pool.CalculateDescendantData(e);
info.pushKV("vsize", (int)e.GetTxSize());
info.pushKV("weight", (int)e.GetTxWeight());
info.pushKV("time", count_seconds(e.GetTime()));
info.pushKV("height", (int)e.GetHeight());
- info.pushKV("descendantcount", e.GetCountWithDescendants());
- info.pushKV("descendantsize", e.GetSizeWithDescendants());
+ info.pushKV("descendantcount", descendant_count);
+ info.pushKV("descendantsize", descendant_size);
info.pushKV("ancestorcount", ancestor_count);
info.pushKV("ancestorsize", ancestor_size);
info.pushKV("wtxid", e.GetTx().GetWitnessHash().ToString());
@@ -306,7 +307,7 @@ static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPool
fees.pushKV("base", ValueFromAmount(e.GetFee()));
fees.pushKV("modified", ValueFromAmount(e.GetModifiedFee()));
fees.pushKV("ancestor", ValueFromAmount(ancestor_fees));
- fees.pushKV("descendant", ValueFromAmount(e.GetModFeesWithDescendants()));
+ fees.pushKV("descendant", ValueFromAmount(descendant_fees));
info.pushKV("fees", std::move(fees));
const CTransaction& tx = e.GetTx();
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index f7bdbf94..ce9218f9 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -1217,6 +1217,21 @@ std::tuple<size_t, size_t, CAmount> CTxMemPool::CalculateAncestorData(const CTxM
return {ancestor_count, ancestor_size, ancestor_fees};
}
+std::tuple<size_t, size_t, CAmount> CTxMemPool::CalculateDescendantData(const CTxMemPoolEntry& entry) const
+{
+ auto descendants = m_txgraph->GetDescendants(entry, TxGraph::Level::MAIN);
+ size_t descendant_count = descendants.size();
+ size_t descendant_size = 0;
+ CAmount descendant_fees = 0;
+
+ for (auto tx: descendants) {
+ const CTxMemPoolEntry &desc = static_cast<const CTxMemPoolEntry&>(*tx);
+ descendant_size += desc.GetTxSize();
+ descendant_fees += desc.GetModifiedFee();
+ }
+ return {descendant_count, descendant_size, descendant_fees};
+}
+
void CTxMemPool::GetTransactionAncestry(const Txid& txid, size_t& ancestors, size_t& descendants, size_t* const ancestorsize, CAmount* const ancestorfees) const {
LOCK(cs);
auto it = mapTx.find(txid);
diff --git a/src/txmempool.h b/src/txmempool.h
index b65333cc..49ee510a 100644
--- a/src/txmempool.h
+++ b/src/txmempool.h
@@ -287,6 +287,7 @@ public:
uint64_t CalculateDescendantMaximum(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs);
std::tuple<size_t, size_t, CAmount> CalculateAncestorData(const CTxMemPoolEntry& entry) const EXCLUSIVE_LOCKS_REQUIRED(cs);
+ std::tuple<size_t, size_t, CAmount> CalculateDescendantData(const CTxMemPoolEntry& entry) const EXCLUSIVE_LOCKS_REQUIRED(cs);
private:
typedef std::map<txiter, setEntries, CompareIteratorByHash> cacheMap;
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.