txgraph: avoid holes in DepGraph positions (mem optimization)
What changed, and why it matters
This is a memory-optimization cleanup inside Bitcoin Core's transaction graph code. It ensures internal data structures don't contain unused gaps ('holes'), which reduces memory usage. There is no indication this fixes a security vulnerability or enables attacks.
No security action required. Treat as routine code-quality/memory optimization.
Security signals we found
No security-relevant signals in commit message or diff
Changes are framed as memory optimization
Added assertions improve internal consistency checking but do not address an identified vulnerability
Evidence from the diff
The commit modifies Cluster::Split and Cluster::Merge in src/txgraph.cpp so that clusters are reconstructed without holes in their DepGraph positions. It adds an Assume in Merge that new positions always append at the end, and a SanityCheck assertion that non-splitting clusters have a contiguous filled position set. The stated motivation is memory optimization, not correctness or security.
Changed components
src/txgraph.cppCluster::SplitCluster::MergeCluster::SanityCheckInspect captured patch +14 / −8
diff --git a/src/txgraph.cpp b/src/txgraph.cpp
index 4dee1d02..4c4772ef 100644
--- a/src/txgraph.cpp
+++ b/src/txgraph.cpp
@@ -1001,9 +1001,12 @@ bool Cluster::Split(TxGraphImpl& graph, int level) noexcept
// Iterate over the connected components of this Cluster's m_depgraph.
while (todo.Any()) {
auto component = m_depgraph.FindConnectedComponent(todo);
- auto split_quality = component.Count() <= 2 ? QualityLevel::OPTIMAL : new_quality;
- if (first && component == todo) {
- // The existing Cluster is an entire component. Leave it be, but update its quality.
+ auto component_size = component.Count();
+ auto split_quality = component_size <= 2 ? QualityLevel::OPTIMAL : new_quality;
+ if (first && component == todo && SetType::Fill(component_size) == component) {
+ // The existing Cluster is an entire component, without holes. Leave it be, but update
+ // its quality. If there are holes, we continue, so that the Cluster is reconstructed
+ // without holes, reducing memory usage.
Assume(todo == m_depgraph.Positions());
graph.SetClusterQuality(level, m_quality, m_setindex, split_quality);
// If this made the quality ACCEPTABLE or OPTIMAL, we need to compute and cache its
@@ -1064,12 +1067,11 @@ void Cluster::Merge(TxGraphImpl& graph, int level, Cluster& other) noexcept
auto idx = other.m_mapping[pos];
// Copy the transaction into this Cluster, and remember its position.
auto new_pos = m_depgraph.AddTransaction(other.m_depgraph.FeeRate(pos));
+ // Since this cluster must have been made hole-free before being merged into, all added
+ // transactions should appear at the end.
+ Assume(new_pos == m_mapping.size());
remap[pos] = new_pos;
- if (new_pos == m_mapping.size()) {
- m_mapping.push_back(idx);
- } else {
- m_mapping[new_pos] = idx;
- }
+ m_mapping.push_back(idx);
m_linearization.push_back(new_pos);
// Copy the transaction's dependencies, translating them using remap. Note that since
// pos iterates over other.m_linearization, which is in topological order, all parents
@@ -2286,6 +2288,10 @@ void Cluster::SanityCheck(const TxGraphImpl& graph, int level) const
assert(m_depgraph.PositionRange() == m_mapping.size());
// The linearization for this Cluster must contain every transaction once.
assert(m_depgraph.TxCount() == m_linearization.size());
+ // Unless a split is to be applied, the cluster cannot have any holes.
+ if (!NeedsSplitting()) {
+ assert(m_depgraph.Positions() == SetType::Fill(m_depgraph.TxCount()));
+ }
// Compute the chunking of m_linearization.
LinearizationChunking linchunking(m_depgraph, m_linearization);
Why this scored 15/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.