txgraph: add SingletonClusterImpl (mem optimization)
What changed, and why it matters
This commit is a memory optimization for Bitcoin Core's transaction graph code. It adds a special lightweight implementation for clusters that contain exactly one transaction, avoiding the overhead of full dependency-graph data structures. There is no security-relevant change here—only a performance and memory-usage improvement.
No security action needed. Review as normal code-quality/memory-optimization change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces SingletonClusterImpl, a Cluster subclass optimized for single-transaction clusters. It mirrors the GenericClusterImpl interface but stores only a GraphIndex and FeePerWeight, eliminating m_depgraph, m_mapping, and m_linearization. GenericClusterImpl’s minimum intended transaction count is raised from 1 to 2, and CreateEmptyCluster now dispatches to SingletonClusterImpl for tx_count == 1. All methods are straightforward singleton-specific implementations; several assert/Assume that operations impossible for singletons (dependencies, merge, relinearize) are not invoked.
Changed components
src/txgraph.cppCluster abstractionTxGraphImpl cluster creationInspect captured patch +341 / −3
diff --git a/src/txgraph.cpp b/src/txgraph.cpp
index f66b4707..015bdd34 100644
--- a/src/txgraph.cpp
+++ b/src/txgraph.cpp
@@ -183,7 +183,7 @@ public:
/** Mark all the Entry objects belonging to this staging Cluster as missing. The Cluster must be
* deleted immediately after. */
virtual void MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept = 0;
- /** Remove all transactions from a Cluster. */
+ /** Remove all transactions from a (non-empty) Cluster. */
virtual void Clear(TxGraphImpl& graph, int level) noexcept = 0;
/** Change a Cluster's level from 1 (staging) to 0 (main). */
virtual void MoveToMain(TxGraphImpl& graph) noexcept = 0;
@@ -251,7 +251,7 @@ class GenericClusterImpl final : public Cluster
public:
/** The smallest number of transactions this Cluster implementation is intended for. */
- static constexpr DepGraphIndex MIN_INTENDED_TX_COUNT{1};
+ static constexpr DepGraphIndex MIN_INTENDED_TX_COUNT{2};
/** The largest number of transactions this Cluster implementation supports. */
static constexpr DepGraphIndex MAX_TX_COUNT{SetType::Size()};
@@ -293,6 +293,61 @@ public:
void SanityCheck(const TxGraphImpl& graph, int level) const final;
};
+/** An implementation of Cluster that only supports 1 transaction. */
+class SingletonClusterImpl final : public Cluster
+{
+ friend class TxGraphImpl;
+
+ /** The feerate of the (singular) transaction in this Cluster. */
+ FeePerWeight m_feerate;
+ /** Constant to indicate that this Cluster is empty. */
+ static constexpr auto NO_GRAPH_INDEX = GraphIndex(-1);
+ /** The GraphIndex of the transaction. NO_GRAPH_INDEX if this Cluster is empty. */
+ GraphIndex m_graph_index = NO_GRAPH_INDEX;
+
+public:
+ /** The smallest number of transactions this Cluster implementation is intended for. */
+ static constexpr DepGraphIndex MIN_INTENDED_TX_COUNT{1};
+ /** The largest number of transactions this Cluster implementation supports. */
+ static constexpr DepGraphIndex MAX_TX_COUNT{1};
+
+ SingletonClusterImpl() noexcept = delete;
+ /** Construct an empty SingletonClusterImpl. */
+ explicit SingletonClusterImpl(uint64_t sequence) noexcept : Cluster(sequence) {}
+
+ size_t TotalMemoryUsage() const noexcept final;
+ constexpr DepGraphIndex GetMinIntendedTxCount() const noexcept final { return MIN_INTENDED_TX_COUNT; }
+ constexpr DepGraphIndex GetMaxTxCount() const noexcept final { return MAX_TX_COUNT; }
+ LinearizationIndex GetTxCount() const noexcept final { return m_graph_index != NO_GRAPH_INDEX; }
+ DepGraphIndex GetDepGraphIndexRange() const noexcept final { return GetTxCount(); }
+ uint64_t GetTotalTxSize() const noexcept final { return GetTxCount() ? m_feerate.size : 0; }
+ GraphIndex GetClusterEntry(DepGraphIndex index) const noexcept final { Assume(index == 0); Assume(GetTxCount()); return m_graph_index; }
+ DepGraphIndex AppendTransaction(GraphIndex graph_idx, FeePerWeight feerate) noexcept final;
+ void AddDependencies(SetType parents, DepGraphIndex child) noexcept final;
+ void ExtractTransactions(const std::function<void (DepGraphIndex, GraphIndex, FeePerWeight, SetType)>& visit_fn) noexcept final;
+ int GetLevel(const TxGraphImpl& graph) const noexcept final;
+ void UpdateMapping(DepGraphIndex cluster_idx, GraphIndex graph_idx) noexcept final { Assume(cluster_idx == 0); m_graph_index = graph_idx; }
+ void Updated(TxGraphImpl& graph, int level) noexcept final;
+ Cluster* CopyToStaging(TxGraphImpl& graph) const noexcept final;
+ void GetConflicts(const TxGraphImpl& graph, std::vector<Cluster*>& out) const noexcept final;
+ void MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept final;
+ void Clear(TxGraphImpl& graph, int level) noexcept final;
+ void MoveToMain(TxGraphImpl& graph) noexcept final;
+ void Compact() noexcept final;
+ void ApplyRemovals(TxGraphImpl& graph, int level, std::span<GraphIndex>& to_remove) noexcept final;
+ [[nodiscard]] bool Split(TxGraphImpl& graph, int level) noexcept final;
+ void Merge(TxGraphImpl& graph, int level, Cluster& cluster) noexcept final;
+ void ApplyDependencies(TxGraphImpl& graph, int level, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept final;
+ std::pair<uint64_t, bool> Relinearize(TxGraphImpl& graph, int level, uint64_t max_iters) noexcept final;
+ void AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept final;
+ uint64_t AppendTrimData(std::vector<TrimTxData>& ret, std::vector<std::pair<GraphIndex, GraphIndex>>& deps) const noexcept final;
+ void GetAncestorRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept final;
+ void GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept final;
+ bool GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::Ref*> range, LinearizationIndex start_pos) noexcept final;
+ FeePerWeight GetIndividualFeerate(DepGraphIndex idx) noexcept final;
+ void SetFee(TxGraphImpl& graph, int level, DepGraphIndex idx, int64_t fee) noexcept final;
+ void SanityCheck(const TxGraphImpl& graph, int level) const final;
+};
/** The transaction graph, including staged changes.
*
@@ -320,6 +375,7 @@ public:
class TxGraphImpl final : public TxGraph
{
friend class Cluster;
+ friend class SingletonClusterImpl;
friend class GenericClusterImpl;
friend class BlockBuilderImpl;
private:
@@ -594,10 +650,18 @@ public:
{
return std::make_unique<GenericClusterImpl>(m_next_sequence_counter++);
}
+ /** Create an empty SingletonClusterImpl object. */
+ std::unique_ptr<SingletonClusterImpl> CreateEmptySingletonCluster() noexcept
+ {
+ return std::make_unique<SingletonClusterImpl>(m_next_sequence_counter++);
+ }
/** Create an empty Cluster of the appropriate implementation for the specified (maximum) tx
* count. */
std::unique_ptr<Cluster> CreateEmptyCluster(DepGraphIndex tx_count) noexcept
{
+ if (tx_count >= SingletonClusterImpl::MIN_INTENDED_TX_COUNT && tx_count <= SingletonClusterImpl::MAX_TX_COUNT) {
+ return CreateEmptySingletonCluster();
+ }
if (tx_count >= GenericClusterImpl::MIN_INTENDED_TX_COUNT && tx_count <= GenericClusterImpl::MAX_TX_COUNT) {
return CreateEmptyGenericCluster();
}
@@ -805,6 +869,14 @@ size_t GenericClusterImpl::TotalMemoryUsage() const noexcept
sizeof(std::unique_ptr<Cluster>);
}
+size_t SingletonClusterImpl::TotalMemoryUsage() const noexcept
+{
+ return // Memory usage of the allocated SingletonClusterImpl itself.
+ memusage::MallocUsage(sizeof(SingletonClusterImpl)) +
+ // Memory usage of the ClusterSet::m_clusters entry.
+ sizeof(std::unique_ptr<Cluster>);
+}
+
uint64_t GenericClusterImpl::GetTotalTxSize() const noexcept
{
uint64_t ret{0};
@@ -823,11 +895,26 @@ DepGraphIndex GenericClusterImpl::AppendTransaction(GraphIndex graph_idx, FeePer
return ret;
}
+DepGraphIndex SingletonClusterImpl::AppendTransaction(GraphIndex graph_idx, FeePerWeight feerate) noexcept
+{
+ Assume(!GetTxCount());
+ m_graph_index = graph_idx;
+ m_feerate = feerate;
+ return 0;
+}
+
void GenericClusterImpl::AddDependencies(SetType parents, DepGraphIndex child) noexcept
{
m_depgraph.AddDependencies(parents, child);
}
+void SingletonClusterImpl::AddDependencies(SetType parents, DepGraphIndex child) noexcept
+{
+ // Singletons cannot have any dependencies.
+ Assume(child == 0);
+ Assume(parents == SetType{} || parents == SetType::Fill(0));
+}
+
void GenericClusterImpl::ExtractTransactions(const std::function<void (DepGraphIndex, GraphIndex, FeePerWeight, SetType)>& visit_fn) noexcept
{
for (auto pos : m_linearization) {
@@ -839,6 +926,14 @@ void GenericClusterImpl::ExtractTransactions(const std::function<void (DepGraphI
m_mapping.clear();
}
+void SingletonClusterImpl::ExtractTransactions(const std::function<void (DepGraphIndex, GraphIndex, FeePerWeight, SetType)>& visit_fn) noexcept
+{
+ if (GetTxCount()) {
+ visit_fn(0, m_graph_index, m_feerate, SetType{});
+ m_graph_index = NO_GRAPH_INDEX;
+ }
+}
+
int GenericClusterImpl::GetLevel(const TxGraphImpl& graph) const noexcept
{
// GetLevel() does not work for empty Clusters.
@@ -856,6 +951,23 @@ int GenericClusterImpl::GetLevel(const TxGraphImpl& graph) const noexcept
return -1;
}
+int SingletonClusterImpl::GetLevel(const TxGraphImpl& graph) const noexcept
+{
+ // GetLevel() does not work for empty Clusters.
+ if (!Assume(GetTxCount())) return -1;
+
+ // Get the Entry in this Cluster.
+ const auto& entry = graph.m_entries[m_graph_index];
+ // See if there is a level whose Locator matches this Cluster, if so return that level.
+ for (int level = 0; level < MAX_LEVELS; ++level) {
+ if (entry.m_locator[level].cluster == this) return level;
+ }
+ // Given that we started with an Entry that occurs in this Cluster, one of its Locators must
+ // point back to it.
+ assert(false);
+ return -1;
+}
+
void TxGraphImpl::ClearLocator(int level, GraphIndex idx, bool oversized_tx) noexcept
{
auto& entry = m_entries[idx];
@@ -931,6 +1043,27 @@ void GenericClusterImpl::Updated(TxGraphImpl& graph, int level) noexcept
}
}
+void SingletonClusterImpl::Updated(TxGraphImpl& graph, int level) noexcept
+{
+ // Don't do anything if this is empty.
+ if (GetTxCount() == 0) return;
+
+ auto& entry = graph.m_entries[m_graph_index];
+ // Discard any potential ChunkData prior to modifying the Cluster (as that could
+ // invalidate its ordering).
+ if (level == 0) graph.ClearChunkData(entry);
+ entry.m_locator[level].SetPresent(this, 0);
+ // If this is for the main graph (level = 0), compute its chunking and store its information in
+ // the Entry's m_main_lin_index and m_main_chunk_feerate.
+ if (level == 0 && IsAcceptable()) {
+ entry.m_main_lin_index = 0;
+ entry.m_main_chunk_feerate = m_feerate;
+ // Always use the special LinearizationIndex(-1), indicating singleton chunk at end of
+ // Cluster, here.
+ graph.CreateChunkData(m_graph_index, LinearizationIndex(-1));
+ }
+}
+
void GenericClusterImpl::GetConflicts(const TxGraphImpl& graph, std::vector<Cluster*>& out) const noexcept
{
for (auto i : m_linearization) {
@@ -943,6 +1076,19 @@ void GenericClusterImpl::GetConflicts(const TxGraphImpl& graph, std::vector<Clus
}
}
+void SingletonClusterImpl::GetConflicts(const TxGraphImpl& graph, std::vector<Cluster*>& out) const noexcept
+{
+ // Empty clusters have no conflicts.
+ if (GetTxCount() == 0) return;
+
+ auto& entry = graph.m_entries[m_graph_index];
+ // If the transaction in this Cluster also exists in a lower-level Cluster, then that Cluster
+ // conflicts.
+ if (entry.m_locator[0].IsPresent()) {
+ out.push_back(entry.m_locator[0].cluster);
+ }
+}
+
std::vector<Cluster*> TxGraphImpl::GetConflicts() const noexcept
{
Assume(GetTopLevel() == 1);
@@ -973,7 +1119,7 @@ Cluster* GenericClusterImpl::CopyToStaging(TxGraphImpl& graph) const noexcept
// Construct an empty Cluster.
auto ret = graph.CreateEmptyGenericCluster();
auto ptr = ret.get();
- // Copy depgraph, mapping, and linearization/
+ // Copy depgraph, mapping, and linearization.
ptr->m_depgraph = m_depgraph;
ptr->m_mapping = m_mapping;
ptr->m_linearization = m_linearization;
@@ -986,6 +1132,23 @@ Cluster* GenericClusterImpl::CopyToStaging(TxGraphImpl& graph) const noexcept
return ptr;
}
+Cluster* SingletonClusterImpl::CopyToStaging(TxGraphImpl& graph) const noexcept
+{
+ // Construct an empty Cluster.
+ auto ret = graph.CreateEmptySingletonCluster();
+ auto ptr = ret.get();
+ // Copy data.
+ ptr->m_graph_index = m_graph_index;
+ ptr->m_feerate = m_feerate;
+ // Insert the new Cluster into the graph.
+ graph.InsertCluster(/*level=*/1, std::move(ret), m_quality);
+ // Update its Locators.
+ ptr->Updated(graph, /*level=*/1);
+ // Update memory usage.
+ graph.GetClusterSet(/*level=*/1).m_cluster_usage += ptr->TotalMemoryUsage();
+ return ptr;
+}
+
void GenericClusterImpl::ApplyRemovals(TxGraphImpl& graph, int level, std::span<GraphIndex>& to_remove) noexcept
{
// Iterate over the prefix of to_remove that applies to this cluster.
@@ -1048,8 +1211,28 @@ void GenericClusterImpl::ApplyRemovals(TxGraphImpl& graph, int level, std::span<
Updated(graph, level);
}
+void SingletonClusterImpl::ApplyRemovals(TxGraphImpl& graph, int level, std::span<GraphIndex>& to_remove) noexcept
+{
+ // We can only remove the one transaction this Cluster has.
+ Assume(!to_remove.empty());
+ Assume(GetTxCount());
+ Assume(to_remove.front() == m_graph_index);
+ // Pop all copies of m_graph_index from the front of to_remove (at least one, but there may be
+ // multiple).
+ do {
+ to_remove = to_remove.subspan(1);
+ } while (!to_remove.empty() && to_remove.front() == m_graph_index);
+ // Clear this cluster.
+ graph.ClearLocator(level, m_graph_index, m_quality == QualityLevel::OVERSIZED_SINGLETON);
+ m_graph_index = NO_GRAPH_INDEX;
+ graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_SPLIT);
+ // No need to account for m_cluster_usage changes here, as SingletonClusterImpl has constant
+ // memory usage.
+}
+
void GenericClusterImpl::Clear(TxGraphImpl& graph, int level) noexcept
{
+ Assume(GetTxCount());
graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
for (auto i : m_linearization) {
graph.ClearLocator(level, m_mapping[i], m_quality == QualityLevel::OVERSIZED_SINGLETON);
@@ -1059,6 +1242,14 @@ void GenericClusterImpl::Clear(TxGraphImpl& graph, int level) noexcept
m_mapping.clear();
}
+void SingletonClusterImpl::Clear(TxGraphImpl& graph, int level) noexcept
+{
+ Assume(GetTxCount());
+ graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
+ graph.ClearLocator(level, m_graph_index, m_quality == QualityLevel::OVERSIZED_SINGLETON);
+ m_graph_index = NO_GRAPH_INDEX;
+}
+
void GenericClusterImpl::MoveToMain(TxGraphImpl& graph) noexcept
{
for (auto i : m_linearization) {
@@ -1076,6 +1267,20 @@ void GenericClusterImpl::MoveToMain(TxGraphImpl& graph) noexcept
Updated(graph, /*level=*/0);
}
+void SingletonClusterImpl::MoveToMain(TxGraphImpl& graph) noexcept
+{
+ if (GetTxCount()) {
+ auto& entry = graph.m_entries[m_graph_index];
+ entry.m_locator[1].SetMissing();
+ }
+ auto quality = m_quality;
+ graph.GetClusterSet(/*level=*/1).m_cluster_usage -= TotalMemoryUsage();
+ auto cluster = graph.ExtractCluster(/*level=*/1, quality, m_setindex);
+ graph.InsertCluster(/*level=*/0, std::move(cluster), quality);
+ graph.GetClusterSet(/*level=*/0).m_cluster_usage += TotalMemoryUsage();
+ Updated(graph, /*level=*/0);
+}
+
void GenericClusterImpl::Compact() noexcept
{
m_linearization.shrink_to_fit();
@@ -1083,6 +1288,11 @@ void GenericClusterImpl::Compact() noexcept
m_depgraph.Compact();
}
+void SingletonClusterImpl::Compact() noexcept
+{
+ // Nothing to compact; SingletonClusterImpl is constant size.
+}
+
void GenericClusterImpl::AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept
{
auto chunk_feerates = ChunkLinearization(m_depgraph, m_linearization);
@@ -1090,6 +1300,13 @@ void GenericClusterImpl::AppendChunkFeerates(std::vector<FeeFrac>& ret) const no
ret.insert(ret.end(), chunk_feerates.begin(), chunk_feerates.end());
}
+void SingletonClusterImpl::AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept
+{
+ if (GetTxCount()) {
+ ret.push_back(m_feerate);
+ }
+}
+
uint64_t GenericClusterImpl::AppendTrimData(std::vector<TrimTxData>& ret, std::vector<std::pair<GraphIndex, GraphIndex>>& deps) const noexcept
{
const LinearizationChunking linchunking(m_depgraph, m_linearization);
@@ -1121,6 +1338,16 @@ uint64_t GenericClusterImpl::AppendTrimData(std::vector<TrimTxData>& ret, std::v
return size;
}
+uint64_t SingletonClusterImpl::AppendTrimData(std::vector<TrimTxData>& ret, std::vector<std::pair<GraphIndex, GraphIndex>>& deps) const noexcept
+{
+ if (!GetTxCount()) return 0;
+ auto& entry = ret.emplace_back();
+ entry.m_chunk_feerate = m_feerate;
+ entry.m_index = m_graph_index;
+ entry.m_tx_size = m_feerate.size;
+ return m_feerate.size;
+}
+
bool GenericClusterImpl::Split(TxGraphImpl& graph, int level) noexcept
{
// This function can only be called when the Cluster needs splitting.
@@ -1205,6 +1432,21 @@ bool GenericClusterImpl::Split(TxGraphImpl& graph, int level) noexcept
return true;
}
+bool SingletonClusterImpl::Split(TxGraphImpl& graph, int level) noexcept
+{
+ Assume(NeedsSplitting());
+ if (GetTxCount() == 0) {
+ // The cluster is now empty.
+ graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
+ return true;
+ } else {
+ // Nothing changed.
+ graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::OPTIMAL);
+ Updated(graph, level);
+ return false;
+ }
+}
+
void GenericClusterImpl::Merge(TxGraphImpl& graph, int level, Cluster& other) noexcept
{
/** Vector to store the positions in this Cluster for each position in other. */
@@ -1238,6 +1480,13 @@ void GenericClusterImpl::Merge(TxGraphImpl& graph, int level, Cluster& other) no
});
}
+void SingletonClusterImpl::Merge(TxGraphImpl& graph, int level, Cluster& other_abstract) noexcept
+{
+ // Nothing can be merged into a singleton; it should have been converted to GenericClusterImpl
+ // first.
+ Assume(false);
+}
+
void GenericClusterImpl::ApplyDependencies(TxGraphImpl& graph, int level, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept
{
// This function is invoked by TxGraphImpl::ApplyDependencies after merging groups of Clusters
@@ -1286,6 +1535,15 @@ void GenericClusterImpl::ApplyDependencies(TxGraphImpl& graph, int level, std::s
Updated(graph, level);
}
+void SingletonClusterImpl::ApplyDependencies(TxGraphImpl& graph, int level, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept
+{
+ // Nothing can actually be applied.
+ for (auto& [par, chl] : to_apply) {
+ Assume(par == m_graph_index);
+ Assume(chl == m_graph_index);
+ }
+}
+
TxGraphImpl::~TxGraphImpl() noexcept
{
// If Refs outlive the TxGraphImpl they refer to, unlink them, so that their destructor does not
@@ -1861,6 +2119,14 @@ std::pair<uint64_t, bool> GenericClusterImpl::Relinearize(TxGraphImpl& graph, in
return {cost, improved};
}
+std::pair<uint64_t, bool> SingletonClusterImpl::Relinearize(TxGraphImpl& graph, int level, uint64_t max_iters) noexcept
+{
+ // All singletons are optimal, oversized, or need splitting. Each of these precludes
+ // Relinearize from being called.
+ assert(false);
+ return {0, false};
+}
+
void TxGraphImpl::MakeAcceptable(Cluster& cluster, int level) noexcept
{
// Relinearize the Cluster if needed.
@@ -1990,6 +2256,18 @@ void GenericClusterImpl::GetAncestorRefs(const TxGraphImpl& graph, std::span<std
}
}
+void SingletonClusterImpl::GetAncestorRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept
+{
+ Assume(GetTxCount());
+ while (!args.empty()) {
+ if (args.front().first != this) break;
+ args = args.subspan(1);
+ }
+ const auto& entry = graph.m_entries[m_graph_index];
+ Assume(entry.m_ref != nullptr);
+ output.push_back(entry.m_ref);
+}
+
void GenericClusterImpl::GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept
{
/** The union of all descendants to be returned. */
@@ -2009,6 +2287,12 @@ void GenericClusterImpl::GetDescendantRefs(const TxGraphImpl& graph, std::span<s
}
}
+void SingletonClusterImpl::GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept
+{
+ // In a singleton cluster, the ancestors or descendants are always just the entire cluster.
+ GetAncestorRefs(graph, args, output);
+}
+
bool GenericClusterImpl::GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::Ref*> range, LinearizationIndex start_pos) noexcept
{
// Translate the transactions in the Cluster (in linearization order, starting at start_pos in
@@ -2023,11 +2307,29 @@ bool GenericClusterImpl::GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::R
return start_pos == m_linearization.size();
}
+bool SingletonClusterImpl::GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::Ref*> range, LinearizationIndex start_pos) noexcept
+{
+ Assume(!range.empty());
+ Assume(GetTxCount());
+ Assume(start_pos == 0);
+ const auto& entry = graph.m_entries[m_graph_index];
+ Assume(entry.m_ref != nullptr);
+ range[0] = entry.m_ref;
+ return true;
+}
+
FeePerWeight GenericClusterImpl::GetIndividualFeerate(DepGraphIndex idx) noexcept
{
return FeePerWeight::FromFeeFrac(m_depgraph.FeeRate(idx));
}
+FeePerWeight SingletonClusterImpl::GetIndividualFeerate(DepGraphIndex idx) noexcept
+{
+ Assume(GetTxCount());
+ Assume(idx == 0);
+ return m_feerate;
+}
+
void GenericClusterImpl::MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept
{
// Mark all transactions of a Cluster missing, needed when aborting staging, so that the
@@ -2039,6 +2341,14 @@ void GenericClusterImpl::MakeStagingTransactionsMissing(TxGraphImpl& graph) noex
}
}
+void SingletonClusterImpl::MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept
+{
+ if (GetTxCount()) {
+ auto& entry = graph.m_entries[m_graph_index];
+ entry.m_locator[1].SetMissing();
+ }
+}
+
std::vector<TxGraph::Ref*> TxGraphImpl::GetAncestors(const Ref& arg, Level level_select) noexcept
{
// Return the empty vector if the Ref is empty.
@@ -2347,6 +2657,14 @@ void GenericClusterImpl::SetFee(TxGraphImpl& graph, int level, DepGraphIndex idx
Updated(graph, level);
}
+void SingletonClusterImpl::SetFee(TxGraphImpl& graph, int level, DepGraphIndex idx, int64_t fee) noexcept
+{
+ Assume(GetTxCount());
+ Assume(idx == 0);
+ m_feerate.fee = fee;
+ Updated(graph, level);
+}
+
void TxGraphImpl::SetTransactionFee(const Ref& ref, int64_t fee) noexcept
{
// Don't do anything if the passed Ref is empty.
@@ -2494,6 +2812,26 @@ void GenericClusterImpl::SanityCheck(const TxGraphImpl& graph, int level) const
assert(m_done == m_depgraph.Positions());
}
+void SingletonClusterImpl::SanityCheck(const TxGraphImpl& graph, int level) const
+{
+ // All singletons are optimal, oversized, or need splitting.
+ Assume(IsOptimal() || IsOversized() || NeedsSplitting());
+ if (GetTxCount()) {
+ const auto& entry = graph.m_entries[m_graph_index];
+ // Check that the Entry has a locator pointing back to this Cluster & position within it.
+ assert(entry.m_locator[level].cluster == this);
+ assert(entry.m_locator[level].index == 0);
+ // For main-level entries, check linearization position and chunk feerate.
+ if (level == 0 && IsAcceptable()) {
+ assert(entry.m_main_lin_index == 0);
+ assert(entry.m_main_chunk_feerate == m_feerate);
+ assert(entry.m_main_chunkindex_iterator != graph.m_main_chunkindex.end());
+ auto& chunk_data = *entry.m_main_chunkindex_iterator;
+ assert(chunk_data.m_chunk_count == LinearizationIndex(-1));
+ }
+ }
+}
+
void TxGraphImpl::SanityCheck() const
{
/** Which GraphIndexes ought to occur in m_unlinked, based on m_entries. */
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.