Bitcoin Core 32.99.0
P2P Digital Currency
Loading...
Searching...
No Matches
txgraph.cpp
Go to the documentation of this file.
1// Copyright (c) The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <txgraph.h>
6
7#include <cluster_linearize.h>
8#include <random.h>
9#include <util/bitset.h>
10#include <util/check.h>
11#include <util/feefrac.h>
12#include <util/vector.h>
13
14#include <compare>
15#include <functional>
16#include <memory>
17#include <ranges>
18#include <set>
19#include <span>
20#include <unordered_set>
21#include <utility>
22#include <vector>
23
24namespace {
25
26using namespace cluster_linearize;
27
29static constexpr int MAX_LEVELS{2};
30
31// Forward declare the TxGraph implementation class.
32class TxGraphImpl;
33
38
40enum class QualityLevel
41{
44 OVERSIZED_SINGLETON,
46 NEEDS_SPLIT_FIX,
48 NEEDS_SPLIT,
50 NEEDS_FIX,
52 NEEDS_RELINEARIZE,
54 ACCEPTABLE,
56 OPTIMAL,
59 NONE,
60};
61
63struct TrimTxData
64{
65 // Fields populated by Cluster::AppendTrimData(). These are immutable after TrimTxData
66 // construction.
68 FeePerWeight m_chunk_feerate;
70 TxGraph::GraphIndex m_index;
72 uint32_t m_tx_size;
73
74 // Fields only used internally by TxGraphImpl::Trim():
76 uint32_t m_deps_left;
78 uint32_t m_parent_count;
80 uint32_t m_parent_offset;
82 uint32_t m_children_count;
84 uint32_t m_children_offset;
85
86 // Fields only used internally by TxGraphImpl::Trim()'s union-find implementation, and only for
87 // transactions that are definitely included or definitely rejected.
88 //
89 // As transactions get processed, they get organized into trees which form partitions
90 // representing the would-be clusters up to that point. The root of each tree is a
91 // representative for that partition. See
92 // https://en.wikipedia.org/wiki/Disjoint-set_data_structure.
93 //
96 TrimTxData* m_uf_parent;
98 uint32_t m_uf_count;
100 uint64_t m_uf_size;
101};
102
104class Cluster
105{
106 friend class TxGraphImpl;
107 friend class BlockBuilderImpl;
108
109protected:
110 using GraphIndex = TxGraph::GraphIndex;
111 using SetType = BitSet<MAX_CLUSTER_COUNT_LIMIT>;
113 QualityLevel m_quality{QualityLevel::NONE};
115 ClusterSetIndex m_setindex{ClusterSetIndex(-1)};
118 uint64_t m_sequence;
119
120 explicit Cluster(uint64_t sequence) noexcept : m_sequence(sequence) {}
121
122public:
123 // Provide virtual destructor, for safe polymorphic usage inside std::unique_ptr.
124 virtual ~Cluster() = default;
125
126 // Cannot move or copy (would invalidate Cluster* in Locator and ClusterSet). */
127 Cluster(const Cluster&) = delete;
128 Cluster& operator=(const Cluster&) = delete;
129 Cluster(Cluster&&) = delete;
130 Cluster& operator=(Cluster&&) = delete;
131
132 // Generic helper functions.
133
135 bool IsAcceptable() const noexcept
136 {
137 return m_quality == QualityLevel::ACCEPTABLE || m_quality == QualityLevel::OPTIMAL;
138 }
140 bool IsTopological() const noexcept
141 {
142 return m_quality != QualityLevel::NEEDS_FIX && m_quality != QualityLevel::NEEDS_SPLIT_FIX;
143 }
145 bool IsOptimal() const noexcept
146 {
147 return m_quality == QualityLevel::OPTIMAL;
148 }
152 bool IsOversized() const noexcept { return m_quality == QualityLevel::OVERSIZED_SINGLETON; }
154 bool NeedsSplitting() const noexcept
155 {
156 return m_quality == QualityLevel::NEEDS_SPLIT || m_quality == QualityLevel::NEEDS_SPLIT_FIX;
157 }
158
160 virtual DepGraphIndex GetMinIntendedTxCount() const noexcept = 0;
162 virtual DepGraphIndex GetMaxTxCount() const noexcept = 0;
165 virtual size_t TotalMemoryUsage() const noexcept = 0;
167 virtual DepGraphIndex GetDepGraphIndexRange() const noexcept = 0;
169 virtual LinearizationIndex GetTxCount() const noexcept = 0;
171 virtual uint64_t GetTotalTxSize() const noexcept = 0;
173 virtual GraphIndex GetClusterEntry(DepGraphIndex index) const noexcept = 0;
176 virtual DepGraphIndex AppendTransaction(GraphIndex graph_idx, FeePerWeight feerate) noexcept = 0;
178 virtual void AddDependencies(SetType parents, DepGraphIndex child) noexcept = 0;
181 virtual void ExtractTransactions(const std::function<void (DepGraphIndex, GraphIndex, FeePerWeight)>& visit1_fn, const std::function<void (DepGraphIndex, GraphIndex, SetType)>& visit2_fn) noexcept = 0;
184 virtual int GetLevel(const TxGraphImpl& graph) const noexcept = 0;
186 virtual void UpdateMapping(DepGraphIndex cluster_idx, GraphIndex graph_idx) noexcept = 0;
191 virtual void Updated(TxGraphImpl& graph, int level, bool rename) noexcept = 0;
193 virtual void RemoveChunkData(TxGraphImpl& graph) noexcept = 0;
195 virtual Cluster* CopyToStaging(TxGraphImpl& graph) const noexcept = 0;
197 virtual void GetConflicts(const TxGraphImpl& graph, std::vector<Cluster*>& out) const noexcept = 0;
200 virtual void MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept = 0;
202 virtual void Clear(TxGraphImpl& graph, int level) noexcept = 0;
204 virtual void MoveToMain(TxGraphImpl& graph) noexcept = 0;
206 virtual void Compact() noexcept = 0;
207
208 // Functions that implement the Cluster-specific side of internal TxGraphImpl mutations.
209
212 virtual void ApplyRemovals(TxGraphImpl& graph, int level, std::span<GraphIndex>& to_remove) noexcept = 0;
215 [[nodiscard]] virtual bool Split(TxGraphImpl& graph, int level) noexcept = 0;
217 virtual void Merge(TxGraphImpl& graph, int level, Cluster& cluster) noexcept = 0;
219 virtual void ApplyDependencies(TxGraphImpl& graph, int level, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept = 0;
222 virtual std::pair<uint64_t, bool> Relinearize(TxGraphImpl& graph, int level, uint64_t max_cost) noexcept = 0;
224 virtual void AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept = 0;
228 virtual uint64_t AppendTrimData(std::vector<TrimTxData>& ret, std::vector<std::pair<GraphIndex, GraphIndex>>& deps) const noexcept = 0;
229
230 // Functions that implement the Cluster-specific side of public TxGraph functions.
231
234 virtual void GetAncestorRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept = 0;
237 virtual void GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept = 0;
241 virtual bool GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::Ref*> range, LinearizationIndex start_pos) noexcept = 0;
243 virtual FeePerWeight GetIndividualFeerate(DepGraphIndex idx) noexcept = 0;
245 virtual void SetFee(TxGraphImpl& graph, int level, DepGraphIndex idx, int64_t fee) noexcept = 0;
246
247 // Debugging functions.
248
249 virtual void SanityCheck(const TxGraphImpl& graph, int level) const = 0;
250};
251
254class GenericClusterImpl final : public Cluster
255{
256 friend class TxGraphImpl;
258 DepGraph<SetType> m_depgraph;
262 std::vector<GraphIndex> m_mapping;
265 std::vector<DepGraphIndex> m_linearization;
266
267public:
269 static constexpr DepGraphIndex MIN_INTENDED_TX_COUNT{2};
271 static constexpr DepGraphIndex MAX_TX_COUNT{SetType::Size()};
272
273 GenericClusterImpl() noexcept = delete;
275 explicit GenericClusterImpl(uint64_t sequence) noexcept;
276
277 size_t TotalMemoryUsage() const noexcept final;
278 constexpr DepGraphIndex GetMinIntendedTxCount() const noexcept final { return MIN_INTENDED_TX_COUNT; }
279 constexpr DepGraphIndex GetMaxTxCount() const noexcept final { return MAX_TX_COUNT; }
280 DepGraphIndex GetDepGraphIndexRange() const noexcept final { return m_depgraph.PositionRange(); }
281 LinearizationIndex GetTxCount() const noexcept final { return m_linearization.size(); }
282 uint64_t GetTotalTxSize() const noexcept final;
283 GraphIndex GetClusterEntry(DepGraphIndex index) const noexcept final { return m_mapping[index]; }
284 DepGraphIndex AppendTransaction(GraphIndex graph_idx, FeePerWeight feerate) noexcept final;
285 void AddDependencies(SetType parents, DepGraphIndex child) noexcept final;
286 void ExtractTransactions(const std::function<void (DepGraphIndex, GraphIndex, FeePerWeight)>& visit1_fn, const std::function<void (DepGraphIndex, GraphIndex, SetType)>& visit2_fn) noexcept final;
287 int GetLevel(const TxGraphImpl& graph) const noexcept final;
288 void UpdateMapping(DepGraphIndex cluster_idx, GraphIndex graph_idx) noexcept final { m_mapping[cluster_idx] = graph_idx; }
289 void Updated(TxGraphImpl& graph, int level, bool rename) noexcept final;
290 void RemoveChunkData(TxGraphImpl& graph) noexcept final;
291 Cluster* CopyToStaging(TxGraphImpl& graph) const noexcept final;
292 void GetConflicts(const TxGraphImpl& graph, std::vector<Cluster*>& out) const noexcept final;
293 void MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept final;
294 void Clear(TxGraphImpl& graph, int level) noexcept final;
295 void MoveToMain(TxGraphImpl& graph) noexcept final;
296 void Compact() noexcept final;
297 void ApplyRemovals(TxGraphImpl& graph, int level, std::span<GraphIndex>& to_remove) noexcept final;
298 [[nodiscard]] bool Split(TxGraphImpl& graph, int level) noexcept final;
299 void Merge(TxGraphImpl& graph, int level, Cluster& cluster) noexcept final;
300 void ApplyDependencies(TxGraphImpl& graph, int level, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept final;
301 std::pair<uint64_t, bool> Relinearize(TxGraphImpl& graph, int level, uint64_t max_cost) noexcept final;
302 void AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept final;
303 uint64_t AppendTrimData(std::vector<TrimTxData>& ret, std::vector<std::pair<GraphIndex, GraphIndex>>& deps) const noexcept final;
304 void GetAncestorRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept final;
305 void GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept final;
306 bool GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::Ref*> range, LinearizationIndex start_pos) noexcept final;
307 FeePerWeight GetIndividualFeerate(DepGraphIndex idx) noexcept final;
308 void SetFee(TxGraphImpl& graph, int level, DepGraphIndex idx, int64_t fee) noexcept final;
309 void SanityCheck(const TxGraphImpl& graph, int level) const final;
310};
311
313class SingletonClusterImpl final : public Cluster
314{
315 friend class TxGraphImpl;
316
318 FeePerWeight m_feerate;
320 static constexpr auto NO_GRAPH_INDEX = GraphIndex(-1);
322 GraphIndex m_graph_index = NO_GRAPH_INDEX;
323
324public:
326 static constexpr DepGraphIndex MIN_INTENDED_TX_COUNT{1};
328 static constexpr DepGraphIndex MAX_TX_COUNT{1};
329
330 SingletonClusterImpl() noexcept = delete;
332 explicit SingletonClusterImpl(uint64_t sequence) noexcept : Cluster(sequence) {}
333
334 size_t TotalMemoryUsage() const noexcept final;
335 constexpr DepGraphIndex GetMinIntendedTxCount() const noexcept final { return MIN_INTENDED_TX_COUNT; }
336 constexpr DepGraphIndex GetMaxTxCount() const noexcept final { return MAX_TX_COUNT; }
337 LinearizationIndex GetTxCount() const noexcept final { return m_graph_index != NO_GRAPH_INDEX; }
338 DepGraphIndex GetDepGraphIndexRange() const noexcept final { return GetTxCount(); }
339 uint64_t GetTotalTxSize() const noexcept final { return GetTxCount() ? m_feerate.size : 0; }
340 GraphIndex GetClusterEntry(DepGraphIndex index) const noexcept final { Assume(index == 0); Assume(GetTxCount()); return m_graph_index; }
341 DepGraphIndex AppendTransaction(GraphIndex graph_idx, FeePerWeight feerate) noexcept final;
342 void AddDependencies(SetType parents, DepGraphIndex child) noexcept final;
343 void ExtractTransactions(const std::function<void (DepGraphIndex, GraphIndex, FeePerWeight)>& visit1_fn, const std::function<void (DepGraphIndex, GraphIndex, SetType)>& visit2_fn) noexcept final;
344 int GetLevel(const TxGraphImpl& graph) const noexcept final;
345 void UpdateMapping(DepGraphIndex cluster_idx, GraphIndex graph_idx) noexcept final { Assume(cluster_idx == 0); m_graph_index = graph_idx; }
346 void Updated(TxGraphImpl& graph, int level, bool rename) noexcept final;
347 void RemoveChunkData(TxGraphImpl& graph) noexcept final;
348 Cluster* CopyToStaging(TxGraphImpl& graph) const noexcept final;
349 void GetConflicts(const TxGraphImpl& graph, std::vector<Cluster*>& out) const noexcept final;
350 void MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept final;
351 void Clear(TxGraphImpl& graph, int level) noexcept final;
352 void MoveToMain(TxGraphImpl& graph) noexcept final;
353 void Compact() noexcept final;
354 void ApplyRemovals(TxGraphImpl& graph, int level, std::span<GraphIndex>& to_remove) noexcept final;
355 [[nodiscard]] bool Split(TxGraphImpl& graph, int level) noexcept final;
356 void Merge(TxGraphImpl& graph, int level, Cluster& cluster) noexcept final;
357 void ApplyDependencies(TxGraphImpl& graph, int level, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept final;
358 std::pair<uint64_t, bool> Relinearize(TxGraphImpl& graph, int level, uint64_t max_cost) noexcept final;
359 void AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept final;
360 uint64_t AppendTrimData(std::vector<TrimTxData>& ret, std::vector<std::pair<GraphIndex, GraphIndex>>& deps) const noexcept final;
361 void GetAncestorRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept final;
362 void GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept final;
363 bool GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::Ref*> range, LinearizationIndex start_pos) noexcept final;
364 FeePerWeight GetIndividualFeerate(DepGraphIndex idx) noexcept final;
365 void SetFee(TxGraphImpl& graph, int level, DepGraphIndex idx, int64_t fee) noexcept final;
366 void SanityCheck(const TxGraphImpl& graph, int level) const final;
367};
368
392class TxGraphImpl final : public TxGraph
393{
394 friend class Cluster;
395 friend class SingletonClusterImpl;
396 friend class GenericClusterImpl;
397 friend class BlockBuilderImpl;
398private:
400 FastRandomContext m_rng;
402 const DepGraphIndex m_max_cluster_count;
404 const uint64_t m_max_cluster_size;
406 const uint64_t m_acceptable_cost;
408 const std::function<std::strong_ordering(const TxGraph::Ref&, const TxGraph::Ref&)> m_fallback_order;
409
411 struct GroupEntry
412 {
414 uint32_t m_cluster_offset;
416 uint32_t m_cluster_count;
418 uint32_t m_deps_offset;
420 uint32_t m_deps_count;
421 };
422
424 struct GroupData
425 {
427 std::vector<GroupEntry> m_groups;
429 std::vector<Cluster*> m_group_clusters;
430 };
431
433 struct ClusterSet
434 {
436 std::array<std::vector<std::unique_ptr<Cluster>>, int(QualityLevel::NONE)> m_clusters;
438 std::vector<GraphIndex> m_to_remove;
441 std::vector<std::pair<GraphIndex, GraphIndex>> m_deps_to_add;
443 std::optional<GroupData> m_group_data = GroupData{};
447 std::vector<GraphIndex> m_removed;
450 GraphIndex m_txcount{0};
452 GraphIndex m_txcount_oversized{0};
454 std::optional<bool> m_oversized{false};
457 size_t m_cluster_usage{0};
458
459 ClusterSet() noexcept = default;
460 };
461
463 ClusterSet m_main_clusterset;
465 std::optional<ClusterSet> m_staging_clusterset;
467 uint64_t m_next_sequence_counter{0};
468
470 struct ChunkData
471 {
473 mutable GraphIndex m_graph_index;
475 LinearizationIndex m_chunk_count;
476
477 ChunkData(GraphIndex graph_index, LinearizationIndex chunk_count) noexcept :
478 m_graph_index{graph_index}, m_chunk_count{chunk_count} {}
479 };
480
482 static std::strong_ordering CompareClusters(Cluster* a, Cluster* b) noexcept
483 {
484 // The nullptr pointer compares before everything else.
485 if (a == nullptr || b == nullptr) {
486 return (a != nullptr) <=> (b != nullptr);
487 }
488 // If neither pointer is nullptr, compare the Clusters' sequence numbers.
489 Assume(a == b || a->m_sequence != b->m_sequence);
490 return a->m_sequence <=> b->m_sequence;
491 }
492
494 std::strong_ordering CompareMainTransactions(GraphIndex a, GraphIndex b) const noexcept
495 {
496 if (a == b) return std::strong_ordering::equal;
497 Assume(a < m_entries.size() && b < m_entries.size());
498 const auto& entry_a = m_entries[a];
499 const auto& entry_b = m_entries[b];
500 // Compare chunk feerates, and return result if it differs.
501 auto feerate_cmp = ByRatio{entry_b.m_main_chunk_feerate} <=> ByRatio{entry_a.m_main_chunk_feerate};
502 if (feerate_cmp != 0) return feerate_cmp;
503 // Compare equal-feerate chunk prefix size for comparing equal chunk feerates. This does two
504 // things: it distinguishes equal-feerate chunks within the same cluster (because later
505 // ones will always have a higher prefix size), and it may distinguish equal-feerate chunks
506 // from distinct clusters.
507 if (entry_a.m_main_equal_feerate_chunk_prefix_size != entry_b.m_main_equal_feerate_chunk_prefix_size) {
508 return entry_a.m_main_equal_feerate_chunk_prefix_size <=> entry_b.m_main_equal_feerate_chunk_prefix_size;
509 }
510 // Compare by maximum m_fallback_order element to order equal-feerate chunks in distinct
511 // clusters, when the equal-feerate-prefix size is also the same.
512 const auto& locator_a = entry_a.m_locator[0];
513 const auto& locator_b = entry_b.m_locator[0];
514 Assume(locator_a.IsPresent() && locator_b.IsPresent());
515 if (locator_a.cluster != locator_b.cluster) {
516 auto fallback_cmp = m_fallback_order(*m_entries[entry_a.m_main_max_chunk_fallback].m_ref,
517 *m_entries[entry_b.m_main_max_chunk_fallback].m_ref);
518 if (fallback_cmp != 0) return fallback_cmp;
519 // This shouldn't be reachable as m_fallback_order defines a strong ordering.
520 Assume(false);
521 return CompareClusters(locator_a.cluster, locator_b.cluster);
522 }
523 // Within a single chunk, sort by position within cluster linearization.
524 return entry_a.m_main_lin_index <=> entry_b.m_main_lin_index;
525 }
526
528 class ChunkOrder
529 {
530 const TxGraphImpl* const m_graph;
531 public:
532 explicit ChunkOrder(const TxGraphImpl* graph) : m_graph(graph) {}
533
534 bool operator()(const ChunkData& a, const ChunkData& b) const noexcept
535 {
536 return m_graph->CompareMainTransactions(a.m_graph_index, b.m_graph_index) < 0;
537 }
538 };
539
541 using ChunkIndex = std::set<ChunkData, ChunkOrder>;
542
545 ChunkIndex m_main_chunkindex;
547 size_t m_main_chunkindex_observers{0};
549 std::vector<ChunkIndex::node_type> m_main_chunkindex_discarded;
550
581 struct Locator
582 {
584 Cluster* cluster{nullptr};
586 DepGraphIndex index{0};
587
589 void SetMissing() noexcept { cluster = nullptr; index = 0; }
591 void SetRemoved() noexcept { cluster = nullptr; index = DepGraphIndex(-1); }
593 void SetPresent(Cluster* c, DepGraphIndex i) noexcept { cluster = c; index = i; }
595 bool IsMissing() const noexcept { return cluster == nullptr && index == 0; }
597 bool IsRemoved() const noexcept { return cluster == nullptr && index == DepGraphIndex(-1); }
599 bool IsPresent() const noexcept { return cluster != nullptr; }
600 };
601
603 struct Entry
604 {
606 Ref* m_ref{nullptr};
609 ChunkIndex::iterator m_main_chunkindex_iterator;
611 Locator m_locator[MAX_LEVELS];
613 FeePerWeight m_main_chunk_feerate;
620 int32_t m_main_equal_feerate_chunk_prefix_size;
622 LinearizationIndex m_main_lin_index;
625 GraphIndex m_main_max_chunk_fallback = GraphIndex(-1);
626 };
627
629 std::vector<Entry> m_entries;
630
632 std::vector<GraphIndex> m_unlinked;
633
634public:
636 explicit TxGraphImpl(
637 DepGraphIndex max_cluster_count,
638 uint64_t max_cluster_size,
640 const std::function<std::strong_ordering(const TxGraph::Ref&, const TxGraph::Ref&)>& fallback_order
641 ) noexcept :
642 m_max_cluster_count(max_cluster_count),
643 m_max_cluster_size(max_cluster_size),
644 m_acceptable_cost(acceptable_cost),
645 m_fallback_order(fallback_order),
646 m_main_chunkindex(ChunkOrder(this))
647 {
648 Assume(max_cluster_count >= 1);
649 Assume(max_cluster_count <= MAX_CLUSTER_COUNT_LIMIT);
650 }
651
653 ~TxGraphImpl() noexcept;
654
655 // Cannot move or copy (would invalidate TxGraphImpl* in Ref, MiningOrder, EvictionOrder).
656 TxGraphImpl(const TxGraphImpl&) = delete;
657 TxGraphImpl& operator=(const TxGraphImpl&) = delete;
658 TxGraphImpl(TxGraphImpl&&) = delete;
659 TxGraphImpl& operator=(TxGraphImpl&&) = delete;
660
661 // Simple helper functions.
662
665 void SwapIndexes(GraphIndex a, GraphIndex b, std::vector<Cluster*>& affected_main) noexcept;
668 Cluster* FindCluster(GraphIndex idx, int level) const noexcept { return FindClusterAndLevel(idx, level).first; }
670 std::pair<Cluster*, int> FindClusterAndLevel(GraphIndex idx, int level) const noexcept;
672 std::unique_ptr<Cluster> ExtractCluster(int level, QualityLevel quality, ClusterSetIndex setindex) noexcept;
674 void DeleteCluster(Cluster& cluster, int level) noexcept;
676 ClusterSetIndex InsertCluster(int level, std::unique_ptr<Cluster>&& cluster, QualityLevel quality) noexcept;
678 void SetClusterQuality(int level, QualityLevel old_quality, ClusterSetIndex old_index, QualityLevel new_quality) noexcept;
680 int GetTopLevel() const noexcept { return m_staging_clusterset.has_value(); }
682 int GetSpecifiedLevel(Level level) const noexcept { return level == Level::TOP && m_staging_clusterset.has_value(); }
684 ClusterSet& GetClusterSet(int level) noexcept;
685 const ClusterSet& GetClusterSet(int level) const noexcept;
689 void ClearLocator(int level, GraphIndex index, bool oversized_tx) noexcept;
691 std::vector<Cluster*> GetConflicts() const noexcept;
693 void ClearChunkData(Entry& entry) noexcept;
695 void CreateChunkData(GraphIndex idx, LinearizationIndex chunk_count) noexcept;
697 std::unique_ptr<GenericClusterImpl> CreateEmptyGenericCluster() noexcept
698 {
699 return std::make_unique<GenericClusterImpl>(m_next_sequence_counter++);
700 }
702 std::unique_ptr<SingletonClusterImpl> CreateEmptySingletonCluster() noexcept
703 {
704 return std::make_unique<SingletonClusterImpl>(m_next_sequence_counter++);
705 }
708 std::unique_ptr<Cluster> CreateEmptyCluster(DepGraphIndex tx_count) noexcept
709 {
710 if (tx_count >= SingletonClusterImpl::MIN_INTENDED_TX_COUNT && tx_count <= SingletonClusterImpl::MAX_TX_COUNT) {
712 }
713 if (tx_count >= GenericClusterImpl::MIN_INTENDED_TX_COUNT && tx_count <= GenericClusterImpl::MAX_TX_COUNT) {
715 }
716 assert(false);
717 return {};
718 }
719
720 // Functions for handling Refs.
721
723 void UpdateRef(GraphIndex idx, Ref& new_location) noexcept final
724 {
725 auto& entry = m_entries[idx];
726 Assume(entry.m_ref != nullptr);
727 entry.m_ref = &new_location;
728 }
729
731 void UnlinkRef(GraphIndex idx) noexcept final
732 {
733 auto& entry = m_entries[idx];
734 Assume(entry.m_ref != nullptr);
735 Assume(m_main_chunkindex_observers == 0 || !entry.m_locator[0].IsPresent());
736 // Remove all chunk index entries for the affected cluster, to avoid any chunk indexes
737 // referencing unlinked/destroyed Refs.
738 if (entry.m_locator[0].IsPresent()) {
739 entry.m_locator[0].cluster->RemoveChunkData(*this);
740 }
741 entry.m_ref = nullptr;
742 // Mark the transaction as to be removed in all levels where it explicitly or implicitly
743 // exists.
744 bool exists_anywhere{false};
745 bool exists{false};
746 for (int level = 0; level <= GetTopLevel(); ++level) {
747 if (entry.m_locator[level].IsPresent()) {
748 exists_anywhere = true;
749 exists = true;
750 } else if (entry.m_locator[level].IsRemoved()) {
751 exists = false;
752 }
753 if (exists) {
754 auto& clusterset = GetClusterSet(level);
755 clusterset.m_to_remove.push_back(idx);
756 // Force recomputation of grouping data.
757 clusterset.m_group_data = std::nullopt;
758 // Do not wipe the oversized state of main if staging exists. The reason for this
759 // is that the alternative would mean that cluster merges may need to be applied to
760 // a formerly-oversized main graph while staging exists (to satisfy chunk feerate
761 // queries into main, for example), and such merges could conflict with pulls of
762 // some of their constituents into staging.
763 if (level == GetTopLevel() && clusterset.m_oversized == true) {
764 clusterset.m_oversized = std::nullopt;
765 }
766 }
767 }
768 m_unlinked.push_back(idx);
769 if (!exists_anywhere) Compact();
770 }
771
772 // Functions related to various normalization/application steps.
776 void Compact() noexcept;
780 Cluster* PullIn(Cluster* cluster, int level) noexcept;
783 void ApplyRemovals(int up_to_level) noexcept;
785 void Split(Cluster& cluster, int level) noexcept;
787 void SplitAll(int up_to_level) noexcept;
789 void GroupClusters(int level) noexcept;
791 void Merge(std::span<Cluster*> to_merge, int level) noexcept;
793 void ApplyDependencies(int level) noexcept;
795 void MakeAcceptable(Cluster& cluster, int level) noexcept;
797 void MakeAllAcceptable(int level) noexcept;
798
799 // Implementations for the public TxGraph interface.
800
801 void AddTransaction(Ref& arg, const FeePerWeight& feerate) noexcept final;
802 void RemoveTransaction(const Ref& arg) noexcept final;
803 void AddDependency(const Ref& parent, const Ref& child) noexcept final;
804 void SetTransactionFee(const Ref&, int64_t fee) noexcept final;
805
806 bool DoWork(uint64_t max_cost) noexcept final;
807
808 void StartStaging() noexcept final;
809 void CommitStaging() noexcept final;
810 void AbortStaging() noexcept final;
811 bool HaveStaging() const noexcept final { return m_staging_clusterset.has_value(); }
812
813 bool Exists(const Ref& arg, Level level) noexcept final;
814 FeePerWeight GetMainChunkFeerate(const Ref& arg) noexcept final;
815 FeePerWeight GetIndividualFeerate(const Ref& arg) noexcept final;
816 std::vector<Ref*> GetCluster(const Ref& arg, Level level) noexcept final;
817 std::vector<Ref*> GetAncestors(const Ref& arg, Level level) noexcept final;
818 std::vector<Ref*> GetDescendants(const Ref& arg, Level level) noexcept final;
819 std::vector<Ref*> GetAncestorsUnion(std::span<const Ref* const> args, Level level) noexcept final;
820 std::vector<Ref*> GetDescendantsUnion(std::span<const Ref* const> args, Level level) noexcept final;
821 GraphIndex GetTransactionCount(Level level) noexcept final;
822 bool IsOversized(Level level) noexcept final;
823 std::strong_ordering CompareMainOrder(const Ref& a, const Ref& b) noexcept final;
824 GraphIndex CountDistinctClusters(std::span<const Ref* const> refs, Level level) noexcept final;
825 std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>> GetMainStagingDiagrams() noexcept final;
826 std::vector<Ref*> Trim() noexcept final;
827
828 std::unique_ptr<BlockBuilder> GetBlockBuilder() noexcept final;
829 std::pair<std::vector<Ref*>, FeePerWeight> GetWorstMainChunk() noexcept final;
830
831 size_t GetMainMemoryUsage() noexcept final;
832
833 void SanityCheck() const final;
834};
835
836TxGraphImpl::ClusterSet& TxGraphImpl::GetClusterSet(int level) noexcept
837{
838 if (level == 0) return m_main_clusterset;
839 Assume(level == 1);
840 Assume(m_staging_clusterset.has_value());
841 return *m_staging_clusterset;
842}
843
844const TxGraphImpl::ClusterSet& TxGraphImpl::GetClusterSet(int level) const noexcept
845{
846 if (level == 0) return m_main_clusterset;
847 Assume(level == 1);
848 Assume(m_staging_clusterset.has_value());
849 return *m_staging_clusterset;
850}
851
853class BlockBuilderImpl final : public TxGraph::BlockBuilder
854{
857 TxGraphImpl* const m_graph;
859 std::unordered_set<uint64_t> m_excluded_clusters;
861 TxGraphImpl::ChunkIndex::const_iterator m_cur_iter;
864 Cluster* m_cur_cluster;
866 bool m_known_end_of_cluster;
867
868 // Move m_cur_iter / m_cur_cluster to the next acceptable chunk.
869 void Next() noexcept;
870
871public:
873 BlockBuilderImpl(TxGraphImpl& graph) noexcept;
874
875 // Implement the public interface.
876 ~BlockBuilderImpl() final;
877 std::optional<std::pair<std::vector<TxGraph::Ref*>, FeePerWeight>> GetCurrentChunk() noexcept final;
878 void Include() noexcept final;
879 void Skip() noexcept final;
880};
881
882void TxGraphImpl::ClearChunkData(Entry& entry) noexcept
883{
884 if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end()) {
885 Assume(m_main_chunkindex_observers == 0);
886 // If the Entry has a non-empty m_main_chunkindex_iterator, extract it, and move the handle
887 // to the cache of discarded chunkindex entries.
888 m_main_chunkindex_discarded.emplace_back(m_main_chunkindex.extract(entry.m_main_chunkindex_iterator));
889 entry.m_main_chunkindex_iterator = m_main_chunkindex.end();
890 }
891}
892
893void TxGraphImpl::CreateChunkData(GraphIndex idx, LinearizationIndex chunk_count) noexcept
894{
895 auto& entry = m_entries[idx];
896 // Make sure to not create chunk data for unlinked entries, which would make invoking
897 // m_fallback_order on them impossible.
898 Assume(entry.m_ref != nullptr);
899 if (!m_main_chunkindex_discarded.empty()) {
900 // Reuse an discarded node handle.
901 auto& node = m_main_chunkindex_discarded.back().value();
902 node.m_graph_index = idx;
903 node.m_chunk_count = chunk_count;
904 auto insert_result = m_main_chunkindex.insert(std::move(m_main_chunkindex_discarded.back()));
905 Assume(insert_result.inserted);
906 entry.m_main_chunkindex_iterator = insert_result.position;
907 m_main_chunkindex_discarded.pop_back();
908 } else {
909 // Construct a new entry.
910 auto emplace_result = m_main_chunkindex.emplace(idx, chunk_count);
911 Assume(emplace_result.second);
912 entry.m_main_chunkindex_iterator = emplace_result.first;
913 }
914}
915
916size_t GenericClusterImpl::TotalMemoryUsage() const noexcept
917{
918 return // Dynamic memory allocated in this Cluster.
919 memusage::DynamicUsage(m_mapping) + memusage::DynamicUsage(m_linearization) +
920 // Dynamic memory usage inside m_depgraph.
921 m_depgraph.DynamicMemoryUsage() +
922 // Memory usage of the allocated Cluster itself.
923 memusage::MallocUsage(sizeof(GenericClusterImpl)) +
924 // Memory usage of the ClusterSet::m_clusters entry.
925 sizeof(std::unique_ptr<Cluster>);
926}
927
928size_t SingletonClusterImpl::TotalMemoryUsage() const noexcept
929{
930 return // Memory usage of the allocated SingletonClusterImpl itself.
931 memusage::MallocUsage(sizeof(SingletonClusterImpl)) +
932 // Memory usage of the ClusterSet::m_clusters entry.
933 sizeof(std::unique_ptr<Cluster>);
934}
935
936uint64_t GenericClusterImpl::GetTotalTxSize() const noexcept
937{
938 uint64_t ret{0};
939 for (auto i : m_linearization) {
940 ret += m_depgraph.FeeRate(i).size;
941 }
942 return ret;
943}
944
945DepGraphIndex GenericClusterImpl::AppendTransaction(GraphIndex graph_idx, FeePerWeight feerate) noexcept
946{
947 Assume(graph_idx != GraphIndex(-1));
948 auto ret = m_depgraph.AddTransaction(feerate);
949 m_mapping.push_back(graph_idx);
950 m_linearization.push_back(ret);
951 return ret;
952}
953
954DepGraphIndex SingletonClusterImpl::AppendTransaction(GraphIndex graph_idx, FeePerWeight feerate) noexcept
955{
956 Assume(!GetTxCount());
957 m_graph_index = graph_idx;
958 m_feerate = feerate;
959 return 0;
960}
961
962void GenericClusterImpl::AddDependencies(SetType parents, DepGraphIndex child) noexcept
963{
964 m_depgraph.AddDependencies(parents, child);
965}
966
967void SingletonClusterImpl::AddDependencies(SetType parents, DepGraphIndex child) noexcept
968{
969 // Singletons cannot have any dependencies.
970 Assume(child == 0);
971 Assume(parents == SetType{} || parents == SetType::Fill(0));
972}
973
974void GenericClusterImpl::ExtractTransactions(const std::function<void (DepGraphIndex, GraphIndex, FeePerWeight)>& visit1_fn, const std::function<void (DepGraphIndex, GraphIndex, SetType)>& visit2_fn) noexcept
975{
976 for (auto pos : m_linearization) {
977 visit1_fn(pos, m_mapping[pos], FeePerWeight::FromFeeFrac(m_depgraph.FeeRate(pos)));
978 }
979 for (auto pos : m_linearization) {
980 visit2_fn(pos, m_mapping[pos], m_depgraph.GetReducedParents(pos));
981 }
982 // Purge this Cluster, now that everything has been moved.
983 m_depgraph = DepGraph<SetType>{};
984 m_linearization.clear();
985 m_mapping.clear();
986}
987
988void SingletonClusterImpl::ExtractTransactions(const std::function<void (DepGraphIndex, GraphIndex, FeePerWeight)>& visit1_fn, const std::function<void (DepGraphIndex, GraphIndex, SetType)>& visit2_fn) noexcept
989{
990 if (GetTxCount()) {
991 visit1_fn(0, m_graph_index, m_feerate);
992 visit2_fn(0, m_graph_index, SetType{});
993 m_graph_index = NO_GRAPH_INDEX;
994 }
995}
996
997int GenericClusterImpl::GetLevel(const TxGraphImpl& graph) const noexcept
998{
999 // GetLevel() does not work for empty Clusters.
1000 if (!Assume(!m_linearization.empty())) return -1;
1001
1002 // Pick an arbitrary Entry that occurs in this Cluster.
1003 const auto& entry = graph.m_entries[m_mapping[m_linearization.front()]];
1004 // See if there is a level whose Locator matches this Cluster, if so return that level.
1005 for (int level = 0; level < MAX_LEVELS; ++level) {
1006 if (entry.m_locator[level].cluster == this) return level;
1007 }
1008 // Given that we started with an Entry that occurs in this Cluster, one of its Locators must
1009 // point back to it.
1010 assert(false);
1011 return -1;
1012}
1013
1014int SingletonClusterImpl::GetLevel(const TxGraphImpl& graph) const noexcept
1015{
1016 // GetLevel() does not work for empty Clusters.
1017 if (!Assume(GetTxCount())) return -1;
1018
1019 // Get the Entry in this Cluster.
1020 const auto& entry = graph.m_entries[m_graph_index];
1021 // See if there is a level whose Locator matches this Cluster, if so return that level.
1022 for (int level = 0; level < MAX_LEVELS; ++level) {
1023 if (entry.m_locator[level].cluster == this) return level;
1024 }
1025 // Given that we started with an Entry that occurs in this Cluster, one of its Locators must
1026 // point back to it.
1027 assert(false);
1028 return -1;
1029}
1030
1031void TxGraphImpl::ClearLocator(int level, GraphIndex idx, bool oversized_tx) noexcept
1032{
1033 auto& entry = m_entries[idx];
1034 auto& clusterset = GetClusterSet(level);
1035 Assume(entry.m_locator[level].IsPresent());
1036 // Change the locator from Present to Missing or Removed.
1037 if (level == 0 || !entry.m_locator[level - 1].IsPresent()) {
1038 entry.m_locator[level].SetMissing();
1039 } else {
1040 entry.m_locator[level].SetRemoved();
1041 clusterset.m_removed.push_back(idx);
1042 }
1043 // Update the transaction count.
1044 --clusterset.m_txcount;
1045 clusterset.m_txcount_oversized -= oversized_tx;
1046 // If clearing main, adjust the status of Locators of this transaction in staging, if it exists.
1047 if (level == 0 && GetTopLevel() == 1) {
1048 if (entry.m_locator[1].IsRemoved()) {
1049 entry.m_locator[1].SetMissing();
1050 } else if (!entry.m_locator[1].IsPresent()) {
1051 --m_staging_clusterset->m_txcount;
1052 m_staging_clusterset->m_txcount_oversized -= oversized_tx;
1053 }
1054 }
1055 if (level == 0) ClearChunkData(entry);
1056}
1057
1058void GenericClusterImpl::RemoveChunkData(TxGraphImpl& graph) noexcept
1059{
1060 for (DepGraphIndex idx : m_linearization) {
1061 auto& entry = graph.m_entries[m_mapping[idx]];
1062 graph.ClearChunkData(entry);
1063 }
1064}
1065
1066void SingletonClusterImpl::RemoveChunkData(TxGraphImpl& graph) noexcept
1067{
1068 if (GetTxCount() == 0) return;
1069 auto& entry = graph.m_entries[m_graph_index];
1070 graph.ClearChunkData(entry);
1071}
1072
1073void GenericClusterImpl::Updated(TxGraphImpl& graph, int level, bool rename) noexcept
1074{
1075 // Update all the Locators for this Cluster's Entry objects.
1076 for (DepGraphIndex idx : m_linearization) {
1077 auto& entry = graph.m_entries[m_mapping[idx]];
1078 // Discard any potential ChunkData prior to modifying the Cluster (as that could
1079 // invalidate its ordering).
1080 if (level == 0 && !rename) graph.ClearChunkData(entry);
1081 entry.m_locator[level].SetPresent(this, idx);
1082 }
1083 // If this is for the main graph (level = 0), and the Cluster's quality is ACCEPTABLE or
1084 // OPTIMAL, compute its chunking and store its information in the Entry's m_main_lin_index
1085 // and m_main_chunk_feerate. These fields are only accessed after making the entire graph
1086 // ACCEPTABLE, so it is pointless to compute these if we haven't reached that quality level
1087 // yet.
1088 // When rename=true, this is always performed for level 0, to make sure the values inside the
1089 // entries remain consistent with the chunk index (otherwise unrelated chunk index operations
1090 // could cause the index to become corrupted, by inserting elements in the wrong place).
1091 if (level == 0 && (rename || IsAcceptable())) {
1092 auto chunking = ChunkLinearizationInfo(m_depgraph, m_linearization);
1097 // Iterate over the chunks.
1098 for (unsigned chunk_idx = 0; chunk_idx < chunking.size(); ++chunk_idx) {
1099 auto& chunk = chunking[chunk_idx];
1100 auto chunk_count = chunk.transactions.Count();
1101 Assume(chunk_count > 0);
1102 // Update equal_feerate_chunk_feerate to include this chunk, starting over when the
1103 // feerate changed.
1106 } else {
1107 // Note that this is adding fees to fees, and sizes to sizes, so the overall
1108 // ratio remains the same; it's just accounting for the size of the added chunk.
1110 }
1111 // Determine the m_fallback_order maximum transaction in the chunk.
1112 auto it = chunk.transactions.begin();
1113 GraphIndex max_element = m_mapping[*it];
1114 ++it;
1115 while (it != chunk.transactions.end()) {
1116 GraphIndex this_element = m_mapping[*it];
1117 if (graph.m_fallback_order(*graph.m_entries[this_element].m_ref, *graph.m_entries[max_element].m_ref) > 0) {
1119 }
1120 ++it;
1121 }
1122 // Iterate over the transactions in the linearization, which must match those in chunk.
1123 while (true) {
1124 DepGraphIndex idx = m_linearization[lin_idx];
1125 GraphIndex graph_idx = m_mapping[idx];
1126 auto& entry = graph.m_entries[graph_idx];
1127 entry.m_main_lin_index = lin_idx++;
1128 entry.m_main_chunk_feerate = FeePerWeight::FromFeeFrac(chunk.feerate);
1129 entry.m_main_equal_feerate_chunk_prefix_size = equal_feerate_chunk_feerate.size;
1130 entry.m_main_max_chunk_fallback = max_element;
1131 Assume(chunk.transactions[idx]);
1132 chunk.transactions.Reset(idx);
1133 if (chunk.transactions.None()) {
1134 // Last transaction in the chunk.
1135 if (chunk_count == 1 && chunk_idx + 1 == chunking.size()) {
1136 // If this is the final chunk of the cluster, and it contains just a single
1137 // transaction (which will always be true for the very common singleton
1138 // clusters), store the special value -1 as chunk count.
1140 }
1141 if (!rename) graph.CreateChunkData(graph_idx, chunk_count);
1142 break;
1143 }
1144 }
1145 }
1146 }
1147}
1148
1149void SingletonClusterImpl::Updated(TxGraphImpl& graph, int level, bool rename) noexcept
1150{
1151 // Don't do anything if this is empty.
1152 if (GetTxCount() == 0) return;
1153
1154 auto& entry = graph.m_entries[m_graph_index];
1155 // Discard any potential ChunkData prior to modifying the Cluster (as that could
1156 // invalidate its ordering).
1157 if (level == 0 && !rename) graph.ClearChunkData(entry);
1158 entry.m_locator[level].SetPresent(this, 0);
1159 // If this is for the main graph (level = 0), compute its chunking and store its information in
1160 // the Entry's m_main_lin_index and m_main_chunk_feerate.
1161 if (level == 0 && (rename || IsAcceptable())) {
1162 entry.m_main_lin_index = 0;
1163 entry.m_main_chunk_feerate = m_feerate;
1164 entry.m_main_equal_feerate_chunk_prefix_size = m_feerate.size;
1165 entry.m_main_max_chunk_fallback = m_graph_index;
1166 // Always use the special LinearizationIndex(-1), indicating singleton chunk at end of
1167 // Cluster, here.
1168 if (!rename) graph.CreateChunkData(m_graph_index, LinearizationIndex(-1));
1169 }
1170}
1171
1172void GenericClusterImpl::GetConflicts(const TxGraphImpl& graph, std::vector<Cluster*>& out) const noexcept
1173{
1174 for (auto i : m_linearization) {
1175 auto& entry = graph.m_entries[m_mapping[i]];
1176 // For every transaction Entry in this Cluster, if it also exists in a lower-level Cluster,
1177 // then that Cluster conflicts.
1178 if (entry.m_locator[0].IsPresent()) {
1179 out.push_back(entry.m_locator[0].cluster);
1180 }
1181 }
1182}
1183
1184void SingletonClusterImpl::GetConflicts(const TxGraphImpl& graph, std::vector<Cluster*>& out) const noexcept
1185{
1186 // Empty clusters have no conflicts.
1187 if (GetTxCount() == 0) return;
1188
1189 auto& entry = graph.m_entries[m_graph_index];
1190 // If the transaction in this Cluster also exists in a lower-level Cluster, then that Cluster
1191 // conflicts.
1192 if (entry.m_locator[0].IsPresent()) {
1193 out.push_back(entry.m_locator[0].cluster);
1194 }
1195}
1196
1197std::vector<Cluster*> TxGraphImpl::GetConflicts() const noexcept
1198{
1199 Assume(GetTopLevel() == 1);
1200 auto& clusterset = GetClusterSet(1);
1201 std::vector<Cluster*> ret;
1202 // All main Clusters containing transactions in m_removed (so (P,R) ones) are conflicts.
1203 for (auto i : clusterset.m_removed) {
1204 auto& entry = m_entries[i];
1205 if (entry.m_locator[0].IsPresent()) {
1206 ret.push_back(entry.m_locator[0].cluster);
1207 }
1208 }
1209 // Then go over all Clusters at this level, and find their conflicts (the (P,P) ones).
1210 for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
1211 auto& clusters = clusterset.m_clusters[quality];
1212 for (const auto& cluster : clusters) {
1213 cluster->GetConflicts(*this, ret);
1214 }
1215 }
1216 // Deduplicate the result (the same Cluster may appear multiple times).
1217 std::ranges::sort(ret, [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
1218 ret.erase(std::ranges::unique(ret).begin(), ret.end());
1219 return ret;
1220}
1221
1222Cluster* GenericClusterImpl::CopyToStaging(TxGraphImpl& graph) const noexcept
1223{
1224 // Construct an empty Cluster.
1225 auto ret = graph.CreateEmptyGenericCluster();
1226 auto ptr = ret.get();
1227 // Copy depgraph, mapping, and linearization.
1228 ptr->m_depgraph = m_depgraph;
1229 ptr->m_mapping = m_mapping;
1230 ptr->m_linearization = m_linearization;
1231 // Insert the new Cluster into the graph.
1232 graph.InsertCluster(/*level=*/1, std::move(ret), m_quality);
1233 // Update its Locators.
1234 ptr->Updated(graph, /*level=*/1, /*rename=*/false);
1235 // Update memory usage.
1236 graph.GetClusterSet(/*level=*/1).m_cluster_usage += ptr->TotalMemoryUsage();
1237 return ptr;
1238}
1239
1240Cluster* SingletonClusterImpl::CopyToStaging(TxGraphImpl& graph) const noexcept
1241{
1242 // Construct an empty Cluster.
1243 auto ret = graph.CreateEmptySingletonCluster();
1244 auto ptr = ret.get();
1245 // Copy data.
1246 ptr->m_graph_index = m_graph_index;
1247 ptr->m_feerate = m_feerate;
1248 // Insert the new Cluster into the graph.
1249 graph.InsertCluster(/*level=*/1, std::move(ret), m_quality);
1250 // Update its Locators.
1251 ptr->Updated(graph, /*level=*/1, /*rename=*/false);
1252 // Update memory usage.
1253 graph.GetClusterSet(/*level=*/1).m_cluster_usage += ptr->TotalMemoryUsage();
1254 return ptr;
1255}
1256
1257void GenericClusterImpl::ApplyRemovals(TxGraphImpl& graph, int level, std::span<GraphIndex>& to_remove) noexcept
1258{
1259 // Iterate over the prefix of to_remove that applies to this cluster.
1260 Assume(!to_remove.empty());
1261 SetType todo;
1262 graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
1263 do {
1264 GraphIndex idx = to_remove.front();
1265 Assume(idx < graph.m_entries.size());
1266 auto& entry = graph.m_entries[idx];
1267 auto& locator = entry.m_locator[level];
1268 // Stop once we hit an entry that applies to another Cluster.
1269 if (locator.cluster != this) break;
1270 // - Remember it in a set of to-remove DepGraphIndexes.
1271 todo.Set(locator.index);
1272 // - Remove from m_mapping. This isn't strictly necessary as unused positions in m_mapping
1273 // are just never accessed, but set it to -1 here to increase the ability to detect a bug
1274 // that causes it to be accessed regardless.
1275 m_mapping[locator.index] = GraphIndex(-1);
1276 // - Remove its linearization index from the Entry (if in main).
1277 if (level == 0) {
1278 entry.m_main_lin_index = LinearizationIndex(-1);
1279 }
1280 // - Mark it as missing/removed in the Entry's locator.
1281 graph.ClearLocator(level, idx, m_quality == QualityLevel::OVERSIZED_SINGLETON);
1282 to_remove = to_remove.subspan(1);
1283 } while(!to_remove.empty());
1284
1285 Assume(todo.Any());
1286 // Wipe from the Cluster's DepGraph (this is O(n) regardless of the number of entries
1287 // removed, so we benefit from batching all the removals).
1288 m_depgraph.RemoveTransactions(todo);
1289 m_mapping.resize(m_depgraph.PositionRange());
1290
1291 // Filter removed transactions out of m_linearization.
1292 std::erase_if(m_linearization, [&](auto pos) { return todo[pos]; });
1293
1294 Compact();
1295 graph.GetClusterSet(level).m_cluster_usage += TotalMemoryUsage();
1296 auto new_quality = IsTopological() ? QualityLevel::NEEDS_SPLIT : QualityLevel::NEEDS_SPLIT_FIX;
1297 graph.SetClusterQuality(level, m_quality, m_setindex, new_quality);
1298 Updated(graph, /*level=*/level, /*rename=*/false);
1299}
1300
1301void SingletonClusterImpl::ApplyRemovals(TxGraphImpl& graph, int level, std::span<GraphIndex>& to_remove) noexcept
1302{
1303 // We can only remove the one transaction this Cluster has.
1304 Assume(!to_remove.empty());
1305 Assume(GetTxCount());
1306 Assume(to_remove.front() == m_graph_index);
1307 // Pop all copies of m_graph_index from the front of to_remove (at least one, but there may be
1308 // multiple).
1309 do {
1310 to_remove = to_remove.subspan(1);
1311 } while (!to_remove.empty() && to_remove.front() == m_graph_index);
1312 // Clear this cluster.
1313 graph.ClearLocator(level, m_graph_index, m_quality == QualityLevel::OVERSIZED_SINGLETON);
1314 m_graph_index = NO_GRAPH_INDEX;
1315 graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_SPLIT);
1316 // No need to account for m_cluster_usage changes here, as SingletonClusterImpl has constant
1317 // memory usage.
1318}
1319
1320void GenericClusterImpl::Clear(TxGraphImpl& graph, int level) noexcept
1321{
1322 Assume(GetTxCount());
1323 graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
1324 for (auto i : m_linearization) {
1325 graph.ClearLocator(level, m_mapping[i], m_quality == QualityLevel::OVERSIZED_SINGLETON);
1326 }
1327 m_depgraph = {};
1328 m_linearization.clear();
1329 m_mapping.clear();
1330}
1331
1332void SingletonClusterImpl::Clear(TxGraphImpl& graph, int level) noexcept
1333{
1334 Assume(GetTxCount());
1335 graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
1336 graph.ClearLocator(level, m_graph_index, m_quality == QualityLevel::OVERSIZED_SINGLETON);
1337 m_graph_index = NO_GRAPH_INDEX;
1338}
1339
1340void GenericClusterImpl::MoveToMain(TxGraphImpl& graph) noexcept
1341{
1342 for (auto i : m_linearization) {
1343 GraphIndex idx = m_mapping[i];
1344 auto& entry = graph.m_entries[idx];
1345 entry.m_locator[1].SetMissing();
1346 }
1347 auto quality = m_quality;
1348 // Subtract memory usage from staging and add it to main.
1349 graph.GetClusterSet(/*level=*/1).m_cluster_usage -= TotalMemoryUsage();
1350 graph.GetClusterSet(/*level=*/0).m_cluster_usage += TotalMemoryUsage();
1351 // Remove cluster itself from staging and add it to main.
1352 auto cluster = graph.ExtractCluster(1, quality, m_setindex);
1353 graph.InsertCluster(/*level=*/0, std::move(cluster), quality);
1354 Updated(graph, /*level=*/0, /*rename=*/false);
1355}
1356
1357void SingletonClusterImpl::MoveToMain(TxGraphImpl& graph) noexcept
1358{
1359 if (GetTxCount()) {
1360 auto& entry = graph.m_entries[m_graph_index];
1361 entry.m_locator[1].SetMissing();
1362 }
1363 auto quality = m_quality;
1364 graph.GetClusterSet(/*level=*/1).m_cluster_usage -= TotalMemoryUsage();
1365 auto cluster = graph.ExtractCluster(/*level=*/1, quality, m_setindex);
1366 graph.InsertCluster(/*level=*/0, std::move(cluster), quality);
1367 graph.GetClusterSet(/*level=*/0).m_cluster_usage += TotalMemoryUsage();
1368 Updated(graph, /*level=*/0, /*rename=*/false);
1369}
1370
1371void GenericClusterImpl::Compact() noexcept
1372{
1373 m_linearization.shrink_to_fit();
1374 m_mapping.shrink_to_fit();
1375 m_depgraph.Compact();
1376}
1377
1378void SingletonClusterImpl::Compact() noexcept
1379{
1380 // Nothing to compact; SingletonClusterImpl is constant size.
1381}
1382
1383void GenericClusterImpl::AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept
1384{
1385 auto chunk_feerates = ChunkLinearization(m_depgraph, m_linearization);
1386 ret.reserve(ret.size() + chunk_feerates.size());
1387 ret.insert(ret.end(), chunk_feerates.begin(), chunk_feerates.end());
1388}
1389
1390void SingletonClusterImpl::AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept
1391{
1392 if (GetTxCount()) {
1393 ret.push_back(m_feerate);
1394 }
1395}
1396
1397uint64_t GenericClusterImpl::AppendTrimData(std::vector<TrimTxData>& ret, std::vector<std::pair<GraphIndex, GraphIndex>>& deps) const noexcept
1398{
1400 auto linchunking = ChunkLinearizationInfo(m_depgraph, m_linearization);
1401 LinearizationIndex pos{0};
1402 uint64_t size{0};
1403 auto prev_index = GraphIndex(-1);
1404 // Iterate over the chunks of this cluster's linearization.
1405 for (const auto& [chunk, chunk_feerate] : linchunking) {
1406 // Iterate over the transactions of that chunk, in linearization order.
1407 auto chunk_tx_count = chunk.Count();
1408 for (unsigned j = 0; j < chunk_tx_count; ++j) {
1409 auto cluster_idx = m_linearization[pos];
1410 // The transaction must appear in the chunk.
1412 // Construct a new element in ret.
1413 auto& entry = ret.emplace_back();
1414 entry.m_chunk_feerate = FeePerWeight::FromFeeFrac(chunk_feerate);
1415 entry.m_index = m_mapping[cluster_idx];
1416 // If this is not the first transaction of the cluster linearization, it has an
1417 // implicit dependency on its predecessor.
1418 if (pos != 0) deps.emplace_back(prev_index, entry.m_index);
1419 prev_index = entry.m_index;
1420 entry.m_tx_size = m_depgraph.FeeRate(cluster_idx).size;
1421 size += entry.m_tx_size;
1422 ++pos;
1423 }
1424 }
1425 return size;
1426}
1427
1428uint64_t SingletonClusterImpl::AppendTrimData(std::vector<TrimTxData>& ret, std::vector<std::pair<GraphIndex, GraphIndex>>& deps) const noexcept
1429{
1430 if (!GetTxCount()) return 0;
1431 auto& entry = ret.emplace_back();
1432 entry.m_chunk_feerate = m_feerate;
1433 entry.m_index = m_graph_index;
1434 entry.m_tx_size = m_feerate.size;
1435 return m_feerate.size;
1436}
1437
1438bool GenericClusterImpl::Split(TxGraphImpl& graph, int level) noexcept
1439{
1440 // This function can only be called when the Cluster needs splitting.
1442 // Determine the new quality the split-off Clusters will have.
1443 QualityLevel new_quality = IsTopological() ? QualityLevel::NEEDS_RELINEARIZE : QualityLevel::NEEDS_FIX;
1445 auto todo = m_depgraph.Positions();
1448 std::vector<std::pair<Cluster*, DepGraphIndex>> remap(m_depgraph.PositionRange());
1449 std::vector<Cluster*> new_clusters;
1450 bool first{true};
1451 // Iterate over the connected components of this Cluster's m_depgraph.
1452 while (todo.Any()) {
1453 auto component = m_depgraph.FindConnectedComponent(todo);
1454 auto component_size = component.Count();
1455 auto split_quality = component_size <= 1 ? QualityLevel::OPTIMAL : new_quality;
1456 if (first && component == todo && SetType::Fill(component_size) == component && component_size >= MIN_INTENDED_TX_COUNT) {
1457 // The existing Cluster is an entire component, without holes. Leave it be, but update
1458 // its quality. If there are holes, we continue, so that the Cluster is reconstructed
1459 // without holes, reducing memory usage. If the component's size is below the intended
1460 // transaction count for this Cluster implementation, continue so that it can get
1461 // converted.
1462 Assume(todo == m_depgraph.Positions());
1463 graph.SetClusterQuality(level, m_quality, m_setindex, split_quality);
1464 // If this made the quality ACCEPTABLE or OPTIMAL, we need to compute and cache its
1465 // chunking.
1466 Updated(graph, /*level=*/level, /*rename=*/false);
1467 return false;
1468 }
1469 first = false;
1470 // Construct a new Cluster to hold the found component.
1471 auto new_cluster = graph.CreateEmptyCluster(component_size);
1472 new_clusters.push_back(new_cluster.get());
1473 // Remember that all the component's transactions go to this new Cluster. The positions
1474 // will be determined below, so use -1 for now.
1475 for (auto i : component) {
1476 remap[i] = {new_cluster.get(), DepGraphIndex(-1)};
1477 }
1478 graph.InsertCluster(level, std::move(new_cluster), split_quality);
1479 todo -= component;
1480 }
1481 // We have to split the Cluster up. Remove accounting for the existing one first.
1482 graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
1483 // Redistribute the transactions.
1484 for (auto i : m_linearization) {
1486 Cluster* new_cluster = remap[i].first;
1487 // Copy the transaction to the new cluster's depgraph, and remember the position.
1488 remap[i].second = new_cluster->AppendTransaction(m_mapping[i], FeePerWeight::FromFeeFrac(m_depgraph.FeeRate(i)));
1489 }
1490 // Redistribute the dependencies.
1491 for (auto i : m_linearization) {
1493 Cluster* new_cluster = remap[i].first;
1494 // Copy its parents, translating positions.
1495 SetType new_parents;
1496 for (auto par : m_depgraph.GetReducedParents(i)) new_parents.Set(remap[par].second);
1497 new_cluster->AddDependencies(new_parents, remap[i].second);
1498 }
1499 // Update all the Locators of moved transactions, and memory usage.
1500 for (Cluster* new_cluster : new_clusters) {
1501 new_cluster->Updated(graph, /*level=*/level, /*rename=*/false);
1502 new_cluster->Compact();
1503 graph.GetClusterSet(level).m_cluster_usage += new_cluster->TotalMemoryUsage();
1504 }
1505 // Wipe this Cluster, and return that it needs to be deleted.
1506 m_depgraph = DepGraph<SetType>{};
1507 m_mapping.clear();
1508 m_linearization.clear();
1509 return true;
1510}
1511
1512bool SingletonClusterImpl::Split(TxGraphImpl& graph, int level) noexcept
1513{
1515 Assume(!GetTxCount());
1516 graph.GetClusterSet(level).m_cluster_usage -= TotalMemoryUsage();
1517 return true;
1518}
1519
1520void GenericClusterImpl::Merge(TxGraphImpl& graph, int level, Cluster& other) noexcept
1521{
1523 std::vector<DepGraphIndex> remap(other.GetDepGraphIndexRange());
1524 // Iterate over all transactions in the other Cluster (the one being absorbed).
1525 other.ExtractTransactions([&](DepGraphIndex pos, GraphIndex idx, FeePerWeight feerate) noexcept {
1526 // Copy the transaction into this Cluster, and remember its position.
1527 auto new_pos = m_depgraph.AddTransaction(feerate);
1528 // Since this cluster must have been made hole-free before being merged into, all added
1529 // transactions should appear at the end.
1530 Assume(new_pos == m_mapping.size());
1531 remap[pos] = new_pos;
1532 m_mapping.push_back(idx);
1533 m_linearization.push_back(new_pos);
1534 }, [&](DepGraphIndex pos, GraphIndex idx, SetType other_parents) noexcept {
1535 // Copy the transaction's dependencies, translating them using remap.
1536 SetType parents;
1537 for (auto par : other_parents) {
1538 parents.Set(remap[par]);
1539 }
1540 m_depgraph.AddDependencies(parents, remap[pos]);
1541 // Update the transaction's Locator. There is no need to call Updated() to update chunk
1542 // feerates, as Updated() will be invoked by Cluster::ApplyDependencies on the resulting
1543 // merged Cluster later anyway.
1544 auto& entry = graph.m_entries[idx];
1545 // Discard any potential ChunkData prior to modifying the Cluster (as that could
1546 // invalidate its ordering).
1547 if (level == 0) graph.ClearChunkData(entry);
1548 entry.m_locator[level].SetPresent(this, remap[pos]);
1549 });
1550}
1551
1552void SingletonClusterImpl::Merge(TxGraphImpl&, int, Cluster&) noexcept
1553{
1554 // Nothing can be merged into a singleton; it should have been converted to GenericClusterImpl first.
1555 Assume(false);
1556}
1557
1558void GenericClusterImpl::ApplyDependencies(TxGraphImpl& graph, int level, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept
1559{
1560 // Sort the list of dependencies to apply by child, so those can be applied in batch.
1561 std::ranges::sort(to_apply, [](auto& a, auto& b) { return a.second < b.second; });
1562 // Iterate over groups of to-be-added dependencies with the same child.
1563 auto it = to_apply.begin();
1564 while (it != to_apply.end()) {
1565 auto& first_child = graph.m_entries[it->second].m_locator[level];
1566 const auto child_idx = first_child.index;
1567 // Iterate over all to-be-added dependencies within that same child, gather the relevant
1568 // parents.
1569 SetType parents;
1570 while (it != to_apply.end()) {
1571 auto& child = graph.m_entries[it->second].m_locator[level];
1572 auto& parent = graph.m_entries[it->first].m_locator[level];
1573 Assume(child.cluster == this && parent.cluster == this);
1574 if (child.index != child_idx) break;
1575 parents.Set(parent.index);
1576 ++it;
1577 }
1578 // Push all dependencies to the underlying DepGraph. Note that this is O(N) in the size of
1579 // the cluster, regardless of the number of parents being added, so batching them together
1580 // has a performance benefit.
1581 m_depgraph.AddDependencies(parents, child_idx);
1582 }
1583
1584 // Finally set the cluster to NEEDS_FIX, so its linearization is fixed the next time it is
1585 // attempted to be made ACCEPTABLE.
1587 Assume(!IsOversized());
1588 graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_FIX);
1589
1590 // Finally push the changes to graph.m_entries.
1591 Updated(graph, /*level=*/level, /*rename=*/false);
1592}
1593
1594void SingletonClusterImpl::ApplyDependencies(TxGraphImpl&, int, std::span<std::pair<GraphIndex, GraphIndex>>) noexcept
1595{
1596 // Nothing can actually be applied.
1597 Assume(false);
1598}
1599
1600TxGraphImpl::~TxGraphImpl() noexcept
1601{
1602 // If Refs outlive the TxGraphImpl they refer to, unlink them, so that their destructor does not
1603 // try to reach into a non-existing TxGraphImpl anymore.
1604 for (auto& entry : m_entries) {
1605 if (entry.m_ref != nullptr) {
1606 GetRefGraph(*entry.m_ref) = nullptr;
1607 }
1608 }
1609}
1610
1611std::unique_ptr<Cluster> TxGraphImpl::ExtractCluster(int level, QualityLevel quality, ClusterSetIndex setindex) noexcept
1612{
1613 Assume(quality != QualityLevel::NONE);
1614
1615 auto& clusterset = GetClusterSet(level);
1616 auto& quality_clusters = clusterset.m_clusters[int(quality)];
1618
1619 // Extract the Cluster-owning unique_ptr.
1620 std::unique_ptr<Cluster> ret = std::move(quality_clusters[setindex]);
1621 ret->m_quality = QualityLevel::NONE;
1622 ret->m_setindex = ClusterSetIndex(-1);
1623
1624 // Clean up space in quality_cluster.
1625 auto max_setindex = quality_clusters.size() - 1;
1626 if (setindex != max_setindex) {
1627 // If the cluster was not the last element of quality_clusters, move that to take its place.
1628 quality_clusters.back()->m_setindex = setindex;
1629 quality_clusters[setindex] = std::move(quality_clusters.back());
1630 }
1631 // The last element of quality_clusters is now unused; drop it.
1632 quality_clusters.pop_back();
1633
1634 return ret;
1635}
1636
1637ClusterSetIndex TxGraphImpl::InsertCluster(int level, std::unique_ptr<Cluster>&& cluster, QualityLevel quality) noexcept
1638{
1639 // Cannot insert with quality level NONE (as that would mean not inserted).
1640 Assume(quality != QualityLevel::NONE);
1641 // The passed-in Cluster must not currently be in the TxGraphImpl.
1642 Assume(cluster->m_quality == QualityLevel::NONE);
1643
1644 // Append it at the end of the relevant TxGraphImpl::m_cluster.
1645 auto& clusterset = GetClusterSet(level);
1646 auto& quality_clusters = clusterset.m_clusters[int(quality)];
1648 cluster->m_quality = quality;
1649 cluster->m_setindex = ret;
1650 quality_clusters.push_back(std::move(cluster));
1651 return ret;
1652}
1653
1654void TxGraphImpl::SetClusterQuality(int level, QualityLevel old_quality, ClusterSetIndex old_index, QualityLevel new_quality) noexcept
1655{
1656 Assume(new_quality != QualityLevel::NONE);
1657
1658 // Don't do anything if the quality did not change.
1659 if (old_quality == new_quality) return;
1660 // Extract the cluster from where it currently resides.
1662 // And re-insert it where it belongs.
1663 InsertCluster(level, std::move(cluster_ptr), new_quality);
1664}
1665
1666void TxGraphImpl::DeleteCluster(Cluster& cluster, int level) noexcept
1667{
1668 // Extract the cluster from where it currently resides.
1669 auto cluster_ptr = ExtractCluster(level, cluster.m_quality, cluster.m_setindex);
1670 // And throw it away.
1671 cluster_ptr.reset();
1672}
1673
1674std::pair<Cluster*, int> TxGraphImpl::FindClusterAndLevel(GraphIndex idx, int level) const noexcept
1675{
1676 Assume(level >= 0 && level <= GetTopLevel());
1677 auto& entry = m_entries[idx];
1678 // Search the entry's locators from top to bottom.
1679 for (int l = level; l >= 0; --l) {
1680 // If the locator is missing, dig deeper; it may exist at a lower level and therefore be
1681 // implicitly existing at this level too.
1682 if (entry.m_locator[l].IsMissing()) continue;
1683 // If the locator has the entry marked as explicitly removed, stop.
1684 if (entry.m_locator[l].IsRemoved()) break;
1685 // Otherwise, we have found the topmost ClusterSet that contains this entry.
1686 return {entry.m_locator[l].cluster, l};
1687 }
1688 // If no non-empty locator was found, or an explicitly removed was hit, return nothing.
1689 return {nullptr, -1};
1690}
1691
1692Cluster* TxGraphImpl::PullIn(Cluster* cluster, int level) noexcept
1693{
1694 int to_level = GetTopLevel();
1695 Assume(to_level == 1);
1696 Assume(level <= to_level);
1697 // Copy the Cluster from main to staging, if it's not already there.
1698 if (level == 0) {
1699 // Make the Cluster Acceptable before copying. This isn't strictly necessary, but doing it
1700 // now avoids doing double work later.
1701 MakeAcceptable(*cluster, level);
1702 cluster = cluster->CopyToStaging(*this);
1703 }
1704 return cluster;
1705}
1706
1707void TxGraphImpl::ApplyRemovals(int up_to_level) noexcept
1708{
1710 for (int level = 0; level <= up_to_level; ++level) {
1711 auto& clusterset = GetClusterSet(level);
1712 auto& to_remove = clusterset.m_to_remove;
1713 // Skip if there is nothing to remove in this level.
1714 if (to_remove.empty()) continue;
1715 // Pull in all Clusters that are not in staging.
1716 if (level == 1) {
1717 for (GraphIndex index : to_remove) {
1718 auto [cluster, cluster_level] = FindClusterAndLevel(index, level);
1719 if (cluster != nullptr) PullIn(cluster, cluster_level);
1720 }
1721 }
1722 // Group the set of to-be-removed entries by Cluster::m_sequence.
1723 std::ranges::sort(to_remove, [&](GraphIndex a, GraphIndex b) noexcept {
1724 Cluster* cluster_a = m_entries[a].m_locator[level].cluster;
1725 Cluster* cluster_b = m_entries[b].m_locator[level].cluster;
1726 return CompareClusters(cluster_a, cluster_b) < 0;
1727 });
1728 // Process per Cluster.
1729 std::span to_remove_span{to_remove};
1730 while (!to_remove_span.empty()) {
1731 Cluster* cluster = m_entries[to_remove_span.front()].m_locator[level].cluster;
1732 if (cluster != nullptr) {
1733 // If the first to_remove_span entry's Cluster exists, hand to_remove_span to it, so it
1734 // can pop off whatever applies to it.
1735 cluster->ApplyRemovals(*this, level, to_remove_span);
1736 } else {
1737 // Otherwise, skip this already-removed entry. This may happen when
1738 // RemoveTransaction was called twice on the same Ref, for example.
1739 to_remove_span = to_remove_span.subspan(1);
1740 }
1741 }
1742 to_remove.clear();
1743 }
1744 Compact();
1745}
1746
1747void TxGraphImpl::SwapIndexes(GraphIndex a, GraphIndex b, std::vector<Cluster*>& affected_main) noexcept
1748{
1749 Assume(a < m_entries.size());
1750 Assume(b < m_entries.size());
1751 // Swap the Entry objects.
1752 std::swap(m_entries[a], m_entries[b]);
1753 // Iterate over both objects.
1754 for (int i = 0; i < 2; ++i) {
1755 GraphIndex idx = i ? b : a;
1756 Entry& entry = m_entries[idx];
1757 // Update linked Ref, if any exists.
1758 if (entry.m_ref) GetRefIndex(*entry.m_ref) = idx;
1759 // Update linked chunk index entries, if any exist.
1760 if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end()) {
1761 entry.m_main_chunkindex_iterator->m_graph_index = idx;
1762 }
1763 // Update the locators for both levels.
1764 for (int level = 0; level < MAX_LEVELS; ++level) {
1765 Locator& locator = entry.m_locator[level];
1766 if (locator.IsPresent()) {
1767 locator.cluster->UpdateMapping(locator.index, idx);
1768 if (level == 0) affected_main.push_back(locator.cluster);
1769 }
1770 }
1771 }
1772}
1773
1774void TxGraphImpl::Compact() noexcept
1775{
1776 // We cannot compact while any to-be-applied operations or staged removals remain as we'd need
1777 // to rewrite them. It is easier to delay the compaction until they have been applied.
1778 if (!m_main_clusterset.m_deps_to_add.empty()) return;
1779 if (!m_main_clusterset.m_to_remove.empty()) return;
1780 Assume(m_main_clusterset.m_removed.empty()); // non-staging m_removed is always empty
1781 if (m_staging_clusterset.has_value()) {
1782 if (!m_staging_clusterset->m_deps_to_add.empty()) return;
1783 if (!m_staging_clusterset->m_to_remove.empty()) return;
1784 if (!m_staging_clusterset->m_removed.empty()) return;
1785 }
1786
1787 // Release memory used by discarded ChunkData index entries.
1788 ClearShrink(m_main_chunkindex_discarded);
1789
1790 // Sort the GraphIndexes that need to be cleaned up. They are sorted in reverse, so the last
1791 // ones get processed first. This means earlier-processed GraphIndexes will not cause moving of
1792 // later-processed ones during the "swap with end of m_entries" step below (which might
1793 // invalidate them).
1794 std::ranges::sort(m_unlinked, std::greater{});
1795
1796 std::vector<Cluster*> affected_main;
1797 auto last = GraphIndex(-1);
1798 for (GraphIndex idx : m_unlinked) {
1799 // m_unlinked should never contain the same GraphIndex twice (the code below would fail
1800 // if so, because GraphIndexes get invalidated by removing them).
1801 Assume(idx != last);
1802 last = idx;
1803
1804 // Make sure the entry is unlinked.
1805 Entry& entry = m_entries[idx];
1806 Assume(entry.m_ref == nullptr);
1807 // Make sure the entry does not occur in the graph.
1808 for (int level = 0; level < MAX_LEVELS; ++level) {
1809 Assume(!entry.m_locator[level].IsPresent());
1810 }
1811
1812 // Move the entry to the end.
1813 if (idx != m_entries.size() - 1) SwapIndexes(idx, m_entries.size() - 1, affected_main);
1814 // Drop the entry for idx, now that it is at the end.
1815 m_entries.pop_back();
1816 }
1817
1818 // Update the affected clusters, to fixup Entry::m_main_max_chunk_fallback values which may
1819 // have become outdated due to the compaction above.
1820 std::ranges::sort(affected_main);
1821 affected_main.erase(std::unique(affected_main.begin(), affected_main.end()), affected_main.end());
1822 for (Cluster* cluster : affected_main) {
1823 cluster->Updated(*this, /*level=*/0, /*rename=*/true);
1824 }
1825 m_unlinked.clear();
1826}
1827
1828void TxGraphImpl::Split(Cluster& cluster, int level) noexcept
1829{
1830 // To split a Cluster, first make sure all removals are applied (as we might need to split
1831 // again afterwards otherwise).
1832 ApplyRemovals(level);
1833 bool del = cluster.Split(*this, level);
1834 if (del) {
1835 // Cluster::Split reports whether the Cluster is to be deleted.
1836 DeleteCluster(cluster, level);
1837 }
1838}
1839
1840void TxGraphImpl::SplitAll(int up_to_level) noexcept
1841{
1843 // Before splitting all Cluster, first make sure all removals are applied.
1845 for (int level = 0; level <= up_to_level; ++level) {
1846 for (auto quality : {QualityLevel::NEEDS_SPLIT_FIX, QualityLevel::NEEDS_SPLIT}) {
1847 auto& queue = GetClusterSet(level).m_clusters[int(quality)];
1848 while (!queue.empty()) {
1849 Split(*queue.back().get(), level);
1850 }
1851 }
1852 }
1853}
1854
1855void TxGraphImpl::GroupClusters(int level) noexcept
1856{
1857 auto& clusterset = GetClusterSet(level);
1858 // If the groupings have been computed already, nothing is left to be done.
1859 if (clusterset.m_group_data.has_value()) return;
1860
1861 // Before computing which Clusters need to be merged together, first apply all removals and
1862 // split the Clusters into connected components. If we would group first, we might end up
1863 // with inefficient and/or oversized Clusters which just end up being split again anyway.
1864 SplitAll(level);
1865
1869 std::vector<std::pair<Cluster*, uint64_t>> an_clusters;
1874 std::vector<std::pair<std::pair<GraphIndex, GraphIndex>, uint64_t>> an_deps;
1875
1876 // Construct an an_clusters entry for every oversized cluster, including ones from levels below,
1877 // as they may be inherited in this one.
1878 for (int level_iter = 0; level_iter <= level; ++level_iter) {
1879 for (auto& cluster : GetClusterSet(level_iter).m_clusters[int(QualityLevel::OVERSIZED_SINGLETON)]) {
1880 auto graph_idx = cluster->GetClusterEntry(0);
1881 auto cur_cluster = FindCluster(graph_idx, level);
1882 if (cur_cluster == nullptr) continue;
1883 an_clusters.emplace_back(cur_cluster, cur_cluster->m_sequence);
1884 }
1885 }
1886
1887 // Construct a an_clusters entry for every parent and child in the to-be-applied dependencies,
1888 // and an an_deps entry for each dependency to be applied.
1889 an_deps.reserve(clusterset.m_deps_to_add.size());
1890 for (const auto& [par, chl] : clusterset.m_deps_to_add) {
1891 auto par_cluster = FindCluster(par, level);
1892 auto chl_cluster = FindCluster(chl, level);
1893 // Skip dependencies for which the parent or child transaction is removed.
1894 if (par_cluster == nullptr || chl_cluster == nullptr) continue;
1895 an_clusters.emplace_back(par_cluster, par_cluster->m_sequence);
1896 // Do not include a duplicate when parent and child are identical, as it'll be removed
1897 // below anyway.
1898 if (chl_cluster != par_cluster) an_clusters.emplace_back(chl_cluster, chl_cluster->m_sequence);
1899 // Add entry to an_deps, using the child sequence number.
1900 an_deps.emplace_back(std::pair{par, chl}, chl_cluster->m_sequence);
1901 }
1902 // Sort and deduplicate an_clusters, so we end up with a sorted list of all involved Clusters
1903 // to which dependencies apply, or which are oversized.
1904 std::ranges::sort(an_clusters, [](auto& a, auto& b) noexcept { return a.second < b.second; });
1905 an_clusters.erase(std::ranges::unique(an_clusters).begin(), an_clusters.end());
1906 // Sort an_deps by applying the same order to the involved child cluster.
1907 std::ranges::sort(an_deps, [&](auto& a, auto& b) noexcept { return a.second < b.second; });
1908
1909 // Run the union-find algorithm to find partitions of the input Clusters which need to be
1910 // grouped together. See https://en.wikipedia.org/wiki/Disjoint-set_data_structure.
1911 {
1913 struct PartitionData
1914 {
1921 PartitionData* parent;
1924 unsigned rank;
1925 };
1927 std::vector<PartitionData> partition_data;
1928
1930 auto locate_fn = [&](uint64_t sequence) noexcept -> PartitionData* {
1931 auto it = std::lower_bound(partition_data.begin(), partition_data.end(), sequence,
1932 [](auto& a, uint64_t seq) noexcept { return a.sequence < seq; });
1933 Assume(it != partition_data.end());
1934 Assume(it->sequence == sequence);
1935 return &*it;
1936 };
1937
1939 static constexpr auto find_root_fn = [](PartitionData* data) noexcept -> PartitionData* {
1940 while (data->parent != data) {
1941 // Replace pointers to parents with pointers to grandparents.
1942 // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Finding_set_representatives.
1943 auto par = data->parent;
1944 data->parent = par->parent;
1945 data = par;
1946 }
1947 return data;
1948 };
1949
1952 static constexpr auto union_fn = [](PartitionData* arg1, PartitionData* arg2) noexcept {
1953 // Find the roots of the trees, and bail out if they are already equal (which would
1954 // mean they are in the same partition already).
1955 auto rep1 = find_root_fn(arg1);
1956 auto rep2 = find_root_fn(arg2);
1957 if (rep1 == rep2) return rep1;
1958 // Pick the lower-rank root to become a child of the higher-rank one.
1959 // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Union_by_rank.
1960 if (rep1->rank < rep2->rank) std::swap(rep1, rep2);
1961 rep2->parent = rep1;
1962 rep1->rank += (rep1->rank == rep2->rank);
1963 return rep1;
1964 };
1965
1966 // Start by initializing every Cluster as its own singleton partition.
1967 partition_data.resize(an_clusters.size());
1968 for (size_t i = 0; i < an_clusters.size(); ++i) {
1969 partition_data[i].sequence = an_clusters[i].first->m_sequence;
1970 partition_data[i].parent = &partition_data[i];
1971 partition_data[i].rank = 0;
1972 }
1973
1974 // Run through all parent/child pairs in an_deps, and union the partitions their Clusters
1975 // are in.
1976 Cluster* last_chl_cluster{nullptr};
1977 PartitionData* last_partition{nullptr};
1978 for (const auto& [dep, _] : an_deps) {
1979 auto [par, chl] = dep;
1980 auto par_cluster = FindCluster(par, level);
1981 auto chl_cluster = FindCluster(chl, level);
1982 Assume(chl_cluster != nullptr && par_cluster != nullptr);
1983 // Nothing to do if parent and child are in the same Cluster.
1984 if (par_cluster == chl_cluster) continue;
1985 Assume(par != chl);
1987 // If the child Clusters is the same as the previous iteration, union with the
1988 // tree they were in, avoiding the need for another lookup. Note that an_deps
1989 // is sorted by child Cluster, so batches with the same child are expected.
1991 } else {
1993 last_partition = union_fn(locate_fn(par_cluster->m_sequence), locate_fn(chl_cluster->m_sequence));
1994 }
1995 }
1996
1997 // Update the sequence numbers in an_clusters and an_deps to be those of the partition
1998 // representative.
1999 auto deps_it = an_deps.begin();
2000 for (size_t i = 0; i < partition_data.size(); ++i) {
2001 auto& data = partition_data[i];
2002 // Find the sequence of the representative of the partition Cluster i is in, and store
2003 // it with the Cluster.
2004 auto rep_seq = find_root_fn(&data)->sequence;
2005 an_clusters[i].second = rep_seq;
2006 // Find all dependencies whose child Cluster is Cluster i, and annotate them with rep.
2007 while (deps_it != an_deps.end()) {
2008 auto [par, chl] = deps_it->first;
2009 auto chl_cluster = FindCluster(chl, level);
2010 Assume(chl_cluster != nullptr);
2011 if (chl_cluster->m_sequence > data.sequence) break;
2012 deps_it->second = rep_seq;
2013 ++deps_it;
2014 }
2015 }
2016 }
2017
2018 // Sort both an_clusters and an_deps by sequence number of the representative of the
2019 // partition they are in, grouping all those applying to the same partition together.
2020 std::ranges::sort(an_deps, [](auto& a, auto& b) noexcept { return a.second < b.second; });
2021 std::ranges::sort(an_clusters, [](auto& a, auto& b) noexcept { return a.second < b.second; });
2022
2023 // Translate the resulting cluster groups to the m_group_data structure, and the dependencies
2024 // back to m_deps_to_add.
2025 clusterset.m_group_data = GroupData{};
2026 clusterset.m_group_data->m_group_clusters.reserve(an_clusters.size());
2027 clusterset.m_deps_to_add.clear();
2028 clusterset.m_deps_to_add.reserve(an_deps.size());
2029 clusterset.m_oversized = false;
2030 auto an_deps_it = an_deps.begin();
2031 auto an_clusters_it = an_clusters.begin();
2032 while (an_clusters_it != an_clusters.end()) {
2033 // Process all clusters/dependencies belonging to the partition with representative rep.
2034 auto rep = an_clusters_it->second;
2035 // Create and initialize a new GroupData entry for the partition.
2036 auto& new_entry = clusterset.m_group_data->m_groups.emplace_back();
2037 new_entry.m_cluster_offset = clusterset.m_group_data->m_group_clusters.size();
2038 new_entry.m_cluster_count = 0;
2039 new_entry.m_deps_offset = clusterset.m_deps_to_add.size();
2040 new_entry.m_deps_count = 0;
2043 // Add all its clusters to it (copying those from an_clusters to m_group_clusters).
2044 while (an_clusters_it != an_clusters.end() && an_clusters_it->second == rep) {
2045 clusterset.m_group_data->m_group_clusters.push_back(an_clusters_it->first);
2046 total_count += an_clusters_it->first->GetTxCount();
2047 total_size += an_clusters_it->first->GetTotalTxSize();
2049 ++new_entry.m_cluster_count;
2050 }
2051 // Add all its dependencies to it (copying those back from an_deps to m_deps_to_add).
2052 while (an_deps_it != an_deps.end() && an_deps_it->second == rep) {
2053 clusterset.m_deps_to_add.push_back(an_deps_it->first);
2054 ++an_deps_it;
2055 ++new_entry.m_deps_count;
2056 }
2057 // Detect oversizedness.
2058 if (total_count > m_max_cluster_count || total_size > m_max_cluster_size) {
2059 clusterset.m_oversized = true;
2060 }
2061 }
2062 Assume(an_deps_it == an_deps.end());
2064 Compact();
2065}
2066
2067void TxGraphImpl::Merge(std::span<Cluster*> to_merge, int level) noexcept
2068{
2069 Assume(!to_merge.empty());
2070 // Nothing to do if a group consists of just a single Cluster.
2071 if (to_merge.size() == 1) return;
2072
2073 // Move the largest Cluster to the front of to_merge. As all transactions in other to-be-merged
2074 // Clusters will be moved to that one, putting the largest one first minimizes the number of
2075 // moves.
2076 size_t max_size_pos{0};
2077 DepGraphIndex max_size = to_merge[max_size_pos]->GetTxCount();
2078 GetClusterSet(level).m_cluster_usage -= to_merge[max_size_pos]->TotalMemoryUsage();
2079 DepGraphIndex total_size = max_size;
2080 for (size_t i = 1; i < to_merge.size(); ++i) {
2081 GetClusterSet(level).m_cluster_usage -= to_merge[i]->TotalMemoryUsage();
2082 DepGraphIndex size = to_merge[i]->GetTxCount();
2083 total_size += size;
2084 if (size > max_size) {
2085 max_size_pos = i;
2086 max_size = size;
2087 }
2088 }
2089 if (max_size_pos != 0) std::swap(to_merge[0], to_merge[max_size_pos]);
2090
2091 size_t start_idx = 1;
2092 Cluster* into_cluster = to_merge[0];
2093 if (total_size > into_cluster->GetMaxTxCount()) {
2094 // The into_merge cluster is too small to fit all transactions being merged. Construct a
2095 // a new Cluster using an implementation that matches the total size, and merge everything
2096 // in there.
2098 into_cluster = new_cluster.get();
2099 InsertCluster(level, std::move(new_cluster), QualityLevel::OPTIMAL);
2100 start_idx = 0;
2101 }
2102
2103 // Merge all further Clusters in the group into the result (first one, or new one), and delete
2104 // them.
2105 for (size_t i = start_idx; i < to_merge.size(); ++i) {
2106 into_cluster->Merge(*this, level, *to_merge[i]);
2107 DeleteCluster(*to_merge[i], level);
2108 }
2109 into_cluster->Compact();
2110 GetClusterSet(level).m_cluster_usage += into_cluster->TotalMemoryUsage();
2111}
2112
2113void TxGraphImpl::ApplyDependencies(int level) noexcept
2114{
2115 auto& clusterset = GetClusterSet(level);
2116 // Do not bother computing groups if we already know the result will be oversized.
2117 if (clusterset.m_oversized == true) return;
2118 // Compute the groups of to-be-merged Clusters (which also applies all removals, and splits).
2119 GroupClusters(level);
2120 Assume(clusterset.m_group_data.has_value());
2121 // Nothing to do if there are no dependencies to be added.
2122 if (clusterset.m_deps_to_add.empty()) return;
2123 // Dependencies cannot be applied if it would result in oversized clusters.
2124 if (clusterset.m_oversized == true) return;
2125
2126 // For each group of to-be-merged Clusters.
2127 for (const auto& group_entry : clusterset.m_group_data->m_groups) {
2128 auto cluster_span = std::span{clusterset.m_group_data->m_group_clusters}
2129 .subspan(group_entry.m_cluster_offset, group_entry.m_cluster_count);
2130 // Pull in all the Clusters that contain dependencies.
2131 if (level == 1) {
2132 for (Cluster*& cluster : cluster_span) {
2133 cluster = PullIn(cluster, cluster->GetLevel(*this));
2134 }
2135 }
2136 // Invoke Merge() to merge them into a single Cluster.
2137 Merge(cluster_span, level);
2138 // Actually apply all to-be-added dependencies (all parents and children from this grouping
2139 // belong to the same Cluster at this point because of the merging above).
2140 auto deps_span = std::span{clusterset.m_deps_to_add}
2141 .subspan(group_entry.m_deps_offset, group_entry.m_deps_count);
2142 Assume(!deps_span.empty());
2143 const auto& loc = m_entries[deps_span[0].second].m_locator[level];
2144 Assume(loc.IsPresent());
2145 loc.cluster->ApplyDependencies(*this, level, deps_span);
2146 }
2147
2148 // Wipe the list of to-be-added dependencies now that they are applied.
2149 clusterset.m_deps_to_add.clear();
2150 Compact();
2151 // Also no further Cluster mergings are needed (note that we clear, but don't set to
2152 // std::nullopt, as that would imply the groupings are unknown).
2153 clusterset.m_group_data = GroupData{};
2154}
2155
2156std::pair<uint64_t, bool> GenericClusterImpl::Relinearize(TxGraphImpl& graph, int level, uint64_t max_cost) noexcept
2157{
2158 // We can only relinearize Clusters that do not need splitting.
2160 // No work is required for Clusters which are already optimally linearized.
2161 if (IsOptimal()) return {0, false};
2162 // Invoke the actual linearization algorithm (passing in the existing one).
2163 uint64_t rng_seed = graph.m_rng.rand64();
2164 const auto fallback_order = [&](DepGraphIndex a, DepGraphIndex b) noexcept {
2165 const auto ref_a = graph.m_entries[m_mapping[a]].m_ref;
2166 const auto ref_b = graph.m_entries[m_mapping[b]].m_ref;
2167 return graph.m_fallback_order(*ref_a, *ref_b);
2168 };
2170 /*depgraph=*/m_depgraph,
2171 /*max_cost=*/max_cost,
2172 /*rng_seed=*/rng_seed,
2173 /*fallback_order=*/fallback_order,
2174 /*old_linearization=*/m_linearization,
2175 /*is_topological=*/IsTopological());
2176 // Postlinearize to improve the linearization (if optimal, only the sub-chunk order).
2177 // This also guarantees that all chunks are connected (even when non-optimal).
2178 PostLinearize(m_depgraph, linearization);
2179 // Update the linearization.
2180 m_linearization = std::move(linearization);
2181 // Update the Cluster's quality.
2182 bool improved = false;
2183 if (optimal) {
2184 graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::OPTIMAL);
2185 improved = true;
2186 } else if (max_cost >= graph.m_acceptable_cost && !IsAcceptable()) {
2187 graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::ACCEPTABLE);
2188 improved = true;
2189 } else if (!IsTopological()) {
2190 graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_RELINEARIZE);
2191 improved = true;
2192 }
2193 // Update the Entry objects.
2194 Updated(graph, /*level=*/level, /*rename=*/false);
2195 return {cost, improved};
2196}
2197
2198std::pair<uint64_t, bool> SingletonClusterImpl::Relinearize(TxGraphImpl& graph, int level, uint64_t max_cost) noexcept
2199{
2200 // All singletons are optimal, oversized, or need splitting. Each of these precludes
2201 // Relinearize from being called.
2202 assert(false);
2203 return {0, false};
2204}
2205
2206void TxGraphImpl::MakeAcceptable(Cluster& cluster, int level) noexcept
2207{
2208 // Relinearize the Cluster if needed.
2209 if (!cluster.NeedsSplitting() && !cluster.IsAcceptable() && !cluster.IsOversized()) {
2210 cluster.Relinearize(*this, level, m_acceptable_cost);
2211 }
2212}
2213
2214void TxGraphImpl::MakeAllAcceptable(int level) noexcept
2215{
2216 ApplyDependencies(level);
2217 auto& clusterset = GetClusterSet(level);
2218 if (clusterset.m_oversized == true) return;
2219 for (auto quality : {QualityLevel::NEEDS_FIX, QualityLevel::NEEDS_RELINEARIZE}) {
2220 auto& queue = clusterset.m_clusters[int(quality)];
2221 while (!queue.empty()) {
2222 MakeAcceptable(*queue.back().get(), level);
2223 }
2224 }
2225}
2226
2227GenericClusterImpl::GenericClusterImpl(uint64_t sequence) noexcept : Cluster{sequence} {}
2228
2229void TxGraphImpl::AddTransaction(Ref& arg, const FeePerWeight& feerate) noexcept
2230{
2231 Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
2232 Assume(feerate.size > 0);
2233 Assume(GetRefGraph(arg) == nullptr);
2234 // Construct a new Entry, and link it with the Ref.
2235 auto idx = m_entries.size();
2236 m_entries.emplace_back();
2237 auto& entry = m_entries.back();
2238 entry.m_main_chunkindex_iterator = m_main_chunkindex.end();
2239 entry.m_ref = &arg;
2240 GetRefGraph(arg) = this;
2241 GetRefIndex(arg) = idx;
2242 // Construct a new singleton Cluster (which is necessarily optimally linearized).
2243 bool oversized = uint64_t(feerate.size) > m_max_cluster_size;
2244 auto cluster = CreateEmptyCluster(1);
2245 cluster->AppendTransaction(idx, feerate);
2246 auto cluster_ptr = cluster.get();
2247 int level = GetTopLevel();
2248 auto& clusterset = GetClusterSet(level);
2249 InsertCluster(level, std::move(cluster), oversized ? QualityLevel::OVERSIZED_SINGLETON : QualityLevel::OPTIMAL);
2250 cluster_ptr->Updated(*this, /*level=*/level, /*rename=*/false);
2251 clusterset.m_cluster_usage += cluster_ptr->TotalMemoryUsage();
2252 ++clusterset.m_txcount;
2253 // Deal with individually oversized transactions.
2254 if (oversized) {
2255 ++clusterset.m_txcount_oversized;
2256 clusterset.m_oversized = true;
2257 clusterset.m_group_data = std::nullopt;
2258 }
2259}
2260
2261void TxGraphImpl::RemoveTransaction(const Ref& arg) noexcept
2262{
2263 // Don't do anything if the Ref is empty (which may be indicative of the transaction already
2264 // having been removed).
2265 if (GetRefGraph(arg) == nullptr) return;
2266 Assume(GetRefGraph(arg) == this);
2267 Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
2268 // Find the Cluster the transaction is in, and stop if it isn't in any.
2269 int level = GetTopLevel();
2270 auto cluster = FindCluster(GetRefIndex(arg), level);
2271 if (cluster == nullptr) return;
2272 // Remember that the transaction is to be removed.
2273 auto& clusterset = GetClusterSet(level);
2274 clusterset.m_to_remove.push_back(GetRefIndex(arg));
2275 // Wipe m_group_data (as it will need to be recomputed).
2276 clusterset.m_group_data.reset();
2277 if (clusterset.m_oversized == true) clusterset.m_oversized = std::nullopt;
2278}
2279
2280void TxGraphImpl::AddDependency(const Ref& parent, const Ref& child) noexcept
2281{
2282 // Don't do anything if either Ref is empty (which may be indicative of it having already been
2283 // removed).
2284 if (GetRefGraph(parent) == nullptr || GetRefGraph(child) == nullptr) return;
2285 Assume(GetRefGraph(parent) == this && GetRefGraph(child) == this);
2286 Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
2287 // Don't do anything if this is a dependency on self.
2288 if (GetRefIndex(parent) == GetRefIndex(child)) return;
2289 // Find the Cluster the parent and child transaction are in, and stop if either appears to be
2290 // already removed.
2291 int level = GetTopLevel();
2292 auto par_cluster = FindCluster(GetRefIndex(parent), level);
2293 if (par_cluster == nullptr) return;
2294 auto chl_cluster = FindCluster(GetRefIndex(child), level);
2295 if (chl_cluster == nullptr) return;
2296 // Remember that this dependency is to be applied.
2297 auto& clusterset = GetClusterSet(level);
2298 clusterset.m_deps_to_add.emplace_back(GetRefIndex(parent), GetRefIndex(child));
2299 // Wipe m_group_data (as it will need to be recomputed).
2300 clusterset.m_group_data.reset();
2301 if (clusterset.m_oversized == false) clusterset.m_oversized = std::nullopt;
2302}
2303
2304bool TxGraphImpl::Exists(const Ref& arg, Level level_select) noexcept
2305{
2306 if (GetRefGraph(arg) == nullptr) return false;
2307 Assume(GetRefGraph(arg) == this);
2308 size_t level = GetSpecifiedLevel(level_select);
2309 // Make sure the transaction isn't scheduled for removal.
2310 ApplyRemovals(level);
2311 auto cluster = FindCluster(GetRefIndex(arg), level);
2312 return cluster != nullptr;
2313}
2314
2315void GenericClusterImpl::GetAncestorRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept
2316{
2318 SetType ancestors_union;
2319 // Process elements from the front of args, as long as they apply.
2320 while (!args.empty()) {
2321 if (args.front().first != this) break;
2322 ancestors_union |= m_depgraph.Ancestors(args.front().second);
2323 args = args.subspan(1);
2324 }
2325 Assume(ancestors_union.Any());
2326 // Translate all ancestors (in arbitrary order) to Refs (if they have any), and return them.
2327 for (auto idx : ancestors_union) {
2328 const auto& entry = graph.m_entries[m_mapping[idx]];
2329 Assume(entry.m_ref != nullptr);
2330 output.push_back(entry.m_ref);
2331 }
2332}
2333
2334void SingletonClusterImpl::GetAncestorRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept
2335{
2336 Assume(GetTxCount());
2337 while (!args.empty()) {
2338 if (args.front().first != this) break;
2339 args = args.subspan(1);
2340 }
2341 const auto& entry = graph.m_entries[m_graph_index];
2342 Assume(entry.m_ref != nullptr);
2343 output.push_back(entry.m_ref);
2344}
2345
2346void GenericClusterImpl::GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept
2347{
2349 SetType descendants_union;
2350 // Process elements from the front of args, as long as they apply.
2351 while (!args.empty()) {
2352 if (args.front().first != this) break;
2353 descendants_union |= m_depgraph.Descendants(args.front().second);
2354 args = args.subspan(1);
2355 }
2357 // Translate all descendants (in arbitrary order) to Refs (if they have any), and return them.
2358 for (auto idx : descendants_union) {
2359 const auto& entry = graph.m_entries[m_mapping[idx]];
2360 Assume(entry.m_ref != nullptr);
2361 output.push_back(entry.m_ref);
2362 }
2363}
2364
2365void SingletonClusterImpl::GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept
2366{
2367 // In a singleton cluster, the ancestors or descendants are always just the entire cluster.
2368 GetAncestorRefs(graph, args, output);
2369}
2370
2371bool GenericClusterImpl::GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::Ref*> range, LinearizationIndex start_pos) noexcept
2372{
2373 // Translate the transactions in the Cluster (in linearization order, starting at start_pos in
2374 // the linearization) to Refs, and fill them in range.
2375 for (auto& ref : range) {
2376 Assume(start_pos < m_linearization.size());
2377 const auto& entry = graph.m_entries[m_mapping[m_linearization[start_pos++]]];
2378 Assume(entry.m_ref != nullptr);
2379 ref = entry.m_ref;
2380 }
2381 // Return whether start_pos has advanced to the end of the Cluster.
2382 return start_pos == m_linearization.size();
2383}
2384
2385bool SingletonClusterImpl::GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::Ref*> range, LinearizationIndex start_pos) noexcept
2386{
2387 Assume(!range.empty());
2388 Assume(GetTxCount());
2389 Assume(start_pos == 0);
2390 const auto& entry = graph.m_entries[m_graph_index];
2391 Assume(entry.m_ref != nullptr);
2392 range[0] = entry.m_ref;
2393 return true;
2394}
2395
2396FeePerWeight GenericClusterImpl::GetIndividualFeerate(DepGraphIndex idx) noexcept
2397{
2398 return FeePerWeight::FromFeeFrac(m_depgraph.FeeRate(idx));
2399}
2400
2401FeePerWeight SingletonClusterImpl::GetIndividualFeerate(DepGraphIndex idx) noexcept
2402{
2403 Assume(GetTxCount());
2404 Assume(idx == 0);
2405 return m_feerate;
2406}
2407
2408void GenericClusterImpl::MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept
2409{
2410 // Mark all transactions of a Cluster missing, needed when aborting staging, so that the
2411 // corresponding Locators don't retain references into aborted Clusters.
2412 for (auto ci : m_linearization) {
2413 GraphIndex idx = m_mapping[ci];
2414 auto& entry = graph.m_entries[idx];
2415 entry.m_locator[1].SetMissing();
2416 }
2417}
2418
2419void SingletonClusterImpl::MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept
2420{
2421 if (GetTxCount()) {
2422 auto& entry = graph.m_entries[m_graph_index];
2423 entry.m_locator[1].SetMissing();
2424 }
2425}
2426
2427std::vector<TxGraph::Ref*> TxGraphImpl::GetAncestors(const Ref& arg, Level level_select) noexcept
2428{
2429 // Return the empty vector if the Ref is empty.
2430 if (GetRefGraph(arg) == nullptr) return {};
2431 Assume(GetRefGraph(arg) == this);
2432 // Apply all removals and dependencies, as the result might be incorrect otherwise.
2433 size_t level = GetSpecifiedLevel(level_select);
2434 ApplyDependencies(level);
2435 // Ancestry cannot be known if unapplied dependencies remain.
2436 Assume(GetClusterSet(level).m_deps_to_add.empty());
2437 // Find the Cluster the argument is in, and return the empty vector if it isn't in any.
2438 auto [cluster, cluster_level] = FindClusterAndLevel(GetRefIndex(arg), level);
2439 if (cluster == nullptr) return {};
2440 // Dispatch to the Cluster.
2441 std::pair<Cluster*, DepGraphIndex> match = {cluster, m_entries[GetRefIndex(arg)].m_locator[cluster_level].index};
2442 auto matches = std::span(&match, 1);
2443 std::vector<TxGraph::Ref*> ret;
2444 cluster->GetAncestorRefs(*this, matches, ret);
2445 return ret;
2446}
2447
2448std::vector<TxGraph::Ref*> TxGraphImpl::GetDescendants(const Ref& arg, Level level_select) noexcept
2449{
2450 // Return the empty vector if the Ref is empty.
2451 if (GetRefGraph(arg) == nullptr) return {};
2452 Assume(GetRefGraph(arg) == this);
2453 // Apply all removals and dependencies, as the result might be incorrect otherwise.
2454 size_t level = GetSpecifiedLevel(level_select);
2455 ApplyDependencies(level);
2456 // Ancestry cannot be known if unapplied dependencies remain.
2457 Assume(GetClusterSet(level).m_deps_to_add.empty());
2458 // Find the Cluster the argument is in, and return the empty vector if it isn't in any.
2459 auto [cluster, cluster_level] = FindClusterAndLevel(GetRefIndex(arg), level);
2460 if (cluster == nullptr) return {};
2461 // Dispatch to the Cluster.
2462 std::pair<Cluster*, DepGraphIndex> match = {cluster, m_entries[GetRefIndex(arg)].m_locator[cluster_level].index};
2463 auto matches = std::span(&match, 1);
2464 std::vector<TxGraph::Ref*> ret;
2465 cluster->GetDescendantRefs(*this, matches, ret);
2466 return ret;
2467}
2468
2469std::vector<TxGraph::Ref*> TxGraphImpl::GetAncestorsUnion(std::span<const Ref* const> args, Level level_select) noexcept
2470{
2471 // Apply all dependencies, as the result might be incorrect otherwise.
2472 size_t level = GetSpecifiedLevel(level_select);
2473 ApplyDependencies(level);
2474 // Ancestry cannot be known if unapplied dependencies remain.
2475 Assume(GetClusterSet(level).m_deps_to_add.empty());
2476
2477 // Translate args to matches.
2478 std::vector<std::pair<Cluster*, DepGraphIndex>> matches;
2479 matches.reserve(args.size());
2480 for (auto arg : args) {
2481 Assume(arg);
2482 // Skip empty Refs.
2483 if (GetRefGraph(*arg) == nullptr) continue;
2484 Assume(GetRefGraph(*arg) == this);
2485 // Find the Cluster the argument is in, and skip if none is found.
2486 auto [cluster, cluster_level] = FindClusterAndLevel(GetRefIndex(*arg), level);
2487 if (cluster == nullptr) continue;
2488 // Append to matches.
2489 matches.emplace_back(cluster, m_entries[GetRefIndex(*arg)].m_locator[cluster_level].index);
2490 }
2491 // Group by Cluster.
2492 std::ranges::sort(matches, [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
2493 // Dispatch to the Clusters.
2494 std::span match_span(matches);
2495 std::vector<TxGraph::Ref*> ret;
2496 while (!match_span.empty()) {
2497 match_span.front().first->GetAncestorRefs(*this, match_span, ret);
2498 }
2499 return ret;
2500}
2501
2502std::vector<TxGraph::Ref*> TxGraphImpl::GetDescendantsUnion(std::span<const Ref* const> args, Level level_select) noexcept
2503{
2504 // Apply all dependencies, as the result might be incorrect otherwise.
2505 size_t level = GetSpecifiedLevel(level_select);
2506 ApplyDependencies(level);
2507 // Ancestry cannot be known if unapplied dependencies remain.
2508 Assume(GetClusterSet(level).m_deps_to_add.empty());
2509
2510 // Translate args to matches.
2511 std::vector<std::pair<Cluster*, DepGraphIndex>> matches;
2512 matches.reserve(args.size());
2513 for (auto arg : args) {
2514 Assume(arg);
2515 // Skip empty Refs.
2516 if (GetRefGraph(*arg) == nullptr) continue;
2517 Assume(GetRefGraph(*arg) == this);
2518 // Find the Cluster the argument is in, and skip if none is found.
2519 auto [cluster, cluster_level] = FindClusterAndLevel(GetRefIndex(*arg), level);
2520 if (cluster == nullptr) continue;
2521 // Append to matches.
2522 matches.emplace_back(cluster, m_entries[GetRefIndex(*arg)].m_locator[cluster_level].index);
2523 }
2524 // Group by Cluster.
2525 std::ranges::sort(matches, [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
2526 // Dispatch to the Clusters.
2527 std::span match_span(matches);
2528 std::vector<TxGraph::Ref*> ret;
2529 while (!match_span.empty()) {
2530 match_span.front().first->GetDescendantRefs(*this, match_span, ret);
2531 }
2532 return ret;
2533}
2534
2535std::vector<TxGraph::Ref*> TxGraphImpl::GetCluster(const Ref& arg, Level level_select) noexcept
2536{
2537 // Return the empty vector if the Ref is empty (which may be indicative of the transaction
2538 // having been removed already.
2539 if (GetRefGraph(arg) == nullptr) return {};
2540 Assume(GetRefGraph(arg) == this);
2541 // Apply all removals and dependencies, as the result might be incorrect otherwise.
2542 size_t level = GetSpecifiedLevel(level_select);
2543 ApplyDependencies(level);
2544 // Cluster linearization cannot be known if unapplied dependencies remain.
2545 Assume(GetClusterSet(level).m_deps_to_add.empty());
2546 // Find the Cluster the argument is in, and return the empty vector if it isn't in any.
2547 auto [cluster, cluster_level] = FindClusterAndLevel(GetRefIndex(arg), level);
2548 if (cluster == nullptr) return {};
2549 // Make sure the Cluster has an acceptable quality level, and then dispatch to it.
2550 MakeAcceptable(*cluster, cluster_level);
2551 std::vector<TxGraph::Ref*> ret(cluster->GetTxCount());
2552 cluster->GetClusterRefs(*this, ret, 0);
2553 return ret;
2554}
2555
2556TxGraph::GraphIndex TxGraphImpl::GetTransactionCount(Level level_select) noexcept
2557{
2558 size_t level = GetSpecifiedLevel(level_select);
2559 ApplyRemovals(level);
2560 return GetClusterSet(level).m_txcount;
2561}
2562
2563FeePerWeight TxGraphImpl::GetIndividualFeerate(const Ref& arg) noexcept
2564{
2565 // Return the empty FeePerWeight if the passed Ref is empty.
2566 if (GetRefGraph(arg) == nullptr) return {};
2567 Assume(GetRefGraph(arg) == this);
2568 // Find the cluster the argument is in (the level does not matter as individual feerates will
2569 // be identical if it occurs in both), and return the empty FeePerWeight if it isn't in any.
2570 Cluster* cluster{nullptr};
2571 int level;
2572 for (level = 0; level <= GetTopLevel(); ++level) {
2573 // Apply removals, so that we can correctly report FeePerWeight{} for non-existing
2574 // transactions.
2575 ApplyRemovals(level);
2576 if (m_entries[GetRefIndex(arg)].m_locator[level].IsPresent()) {
2577 cluster = m_entries[GetRefIndex(arg)].m_locator[level].cluster;
2578 break;
2579 }
2580 }
2581 if (cluster == nullptr) return {};
2582 // Dispatch to the Cluster.
2583 return cluster->GetIndividualFeerate(m_entries[GetRefIndex(arg)].m_locator[level].index);
2584}
2585
2586FeePerWeight TxGraphImpl::GetMainChunkFeerate(const Ref& arg) noexcept
2587{
2588 // Return the empty FeePerWeight if the passed Ref is empty.
2589 if (GetRefGraph(arg) == nullptr) return {};
2590 Assume(GetRefGraph(arg) == this);
2591 // Apply all removals and dependencies, as the result might be inaccurate otherwise.
2592 ApplyDependencies(/*level=*/0);
2593 // Chunk feerates cannot be accurately known if unapplied dependencies remain.
2594 Assume(m_main_clusterset.m_deps_to_add.empty());
2595 // Find the cluster the argument is in, and return the empty FeePerWeight if it isn't in any.
2596 auto [cluster, cluster_level] = FindClusterAndLevel(GetRefIndex(arg), /*level=*/0);
2597 if (cluster == nullptr) return {};
2598 // Make sure the Cluster has an acceptable quality level, and then return the transaction's
2599 // chunk feerate.
2600 MakeAcceptable(*cluster, cluster_level);
2601 const auto& entry = m_entries[GetRefIndex(arg)];
2602 return entry.m_main_chunk_feerate;
2603}
2604
2605bool TxGraphImpl::IsOversized(Level level_select) noexcept
2606{
2607 size_t level = GetSpecifiedLevel(level_select);
2608 auto& clusterset = GetClusterSet(level);
2609 if (clusterset.m_oversized.has_value()) {
2610 // Return cached value if known.
2611 return *clusterset.m_oversized;
2612 }
2613 ApplyRemovals(level);
2614 if (clusterset.m_txcount_oversized > 0) {
2615 clusterset.m_oversized = true;
2616 } else {
2617 // Find which Clusters will need to be merged together, as that is where the oversize
2618 // property is assessed.
2619 GroupClusters(level);
2620 }
2621 Assume(clusterset.m_oversized.has_value());
2622 return *clusterset.m_oversized;
2623}
2624
2625void TxGraphImpl::StartStaging() noexcept
2626{
2627 // Staging cannot already exist.
2628 Assume(!m_staging_clusterset.has_value());
2629 // Apply all remaining dependencies in main before creating a staging graph. Once staging
2630 // exists, we cannot merge Clusters anymore (because of interference with Clusters being
2631 // pulled into staging), so to make sure all inspectors are available (if not oversized), do
2632 // all merging work now. Call SplitAll() first, so that even if ApplyDependencies does not do
2633 // any thing due to knowing the result is oversized, splitting is still performed.
2634 SplitAll(0);
2636 // Construct the staging ClusterSet.
2637 m_staging_clusterset.emplace();
2638 // Copy statistics, precomputed data, and to-be-applied dependencies (only if oversized) to
2639 // the new graph. To-be-applied removals will always be empty at this point.
2640 m_staging_clusterset->m_txcount = m_main_clusterset.m_txcount;
2641 m_staging_clusterset->m_txcount_oversized = m_main_clusterset.m_txcount_oversized;
2642 m_staging_clusterset->m_deps_to_add = m_main_clusterset.m_deps_to_add;
2643 m_staging_clusterset->m_group_data = m_main_clusterset.m_group_data;
2644 m_staging_clusterset->m_oversized = m_main_clusterset.m_oversized;
2645 Assume(m_staging_clusterset->m_oversized.has_value());
2646 m_staging_clusterset->m_cluster_usage = 0;
2647}
2648
2649void TxGraphImpl::AbortStaging() noexcept
2650{
2651 // Staging must exist.
2652 Assume(m_staging_clusterset.has_value());
2653 // Mark all removed transactions as Missing (so the staging locator for these transactions
2654 // can be reused if another staging is created).
2655 for (auto idx : m_staging_clusterset->m_removed) {
2656 m_entries[idx].m_locator[1].SetMissing();
2657 }
2658 // Do the same with the non-removed transactions in staging Clusters.
2659 for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
2660 for (auto& cluster : m_staging_clusterset->m_clusters[quality]) {
2661 cluster->MakeStagingTransactionsMissing(*this);
2662 }
2663 }
2664 // Destroy the staging ClusterSet.
2665 m_staging_clusterset.reset();
2666 Compact();
2667 if (!m_main_clusterset.m_group_data.has_value()) {
2668 // In case m_oversized in main was kept after a Ref destruction while staging exists, we
2669 // need to re-evaluate m_oversized now.
2670 if (m_main_clusterset.m_to_remove.empty() && m_main_clusterset.m_txcount_oversized > 0) {
2671 // It is possible that a Ref destruction caused a removal in main while staging existed.
2672 // In this case, m_txcount_oversized may be inaccurate.
2673 m_main_clusterset.m_oversized = true;
2674 } else {
2675 m_main_clusterset.m_oversized = std::nullopt;
2676 }
2677 }
2678}
2679
2680void TxGraphImpl::CommitStaging() noexcept
2681{
2682 // Staging must exist.
2683 Assume(m_staging_clusterset.has_value());
2684 Assume(m_main_chunkindex_observers == 0);
2685 // Get rid of removed transactions in staging before moving to main, so they do not need to be
2686 // added to the chunk index there. Doing so is impossible if they were unlinked, and thus have
2687 // no Ref anymore to pass to the fallback comparator.
2688 ApplyRemovals(/*up_to_level=*/1);
2689 // Delete all conflicting Clusters in main, to make place for moving the staging ones
2690 // there. All of these have been copied to staging in PullIn().
2691 auto conflicts = GetConflicts();
2692 for (Cluster* conflict : conflicts) {
2693 conflict->Clear(*this, /*level=*/0);
2694 DeleteCluster(*conflict, /*level=*/0);
2695 }
2696 // Mark the removed transactions as Missing (so the staging locator for these transactions
2697 // can be reused if another staging is created).
2698 for (auto idx : m_staging_clusterset->m_removed) {
2699 m_entries[idx].m_locator[1].SetMissing();
2700 }
2701 // Then move all Clusters in staging to main.
2702 for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
2703 auto& stage_sets = m_staging_clusterset->m_clusters[quality];
2704 while (!stage_sets.empty()) {
2705 stage_sets.back()->MoveToMain(*this);
2706 }
2707 }
2708 // Move all statistics, precomputed data, and to-be-applied removals and dependencies.
2709 m_main_clusterset.m_deps_to_add = std::move(m_staging_clusterset->m_deps_to_add);
2710 m_main_clusterset.m_to_remove = std::move(m_staging_clusterset->m_to_remove);
2711 m_main_clusterset.m_group_data = std::move(m_staging_clusterset->m_group_data);
2712 m_main_clusterset.m_oversized = m_staging_clusterset->m_oversized;
2713 m_main_clusterset.m_txcount = m_staging_clusterset->m_txcount;
2714 m_main_clusterset.m_txcount_oversized = m_staging_clusterset->m_txcount_oversized;
2715 // Delete the old staging graph, after all its information was moved to main.
2716 m_staging_clusterset.reset();
2717 Compact();
2718}
2719
2720void GenericClusterImpl::SetFee(TxGraphImpl& graph, int level, DepGraphIndex idx, int64_t fee) noexcept
2721{
2722 // Make sure the specified DepGraphIndex exists in this Cluster.
2723 Assume(m_depgraph.Positions()[idx]);
2724 // Bail out if the fee isn't actually being changed.
2725 if (m_depgraph.FeeRate(idx).fee == fee) return;
2726 // Update the fee, remember that relinearization will be necessary, and update the Entries
2727 // in the same Cluster.
2728 m_depgraph.FeeRate(idx).fee = fee;
2729 if (m_quality == QualityLevel::OVERSIZED_SINGLETON) {
2730 // Nothing to do.
2731 } else if (IsAcceptable()) {
2732 graph.SetClusterQuality(level, m_quality, m_setindex, QualityLevel::NEEDS_RELINEARIZE);
2733 }
2734 Updated(graph, /*level=*/level, /*rename=*/false);
2735}
2736
2737void SingletonClusterImpl::SetFee(TxGraphImpl& graph, int level, DepGraphIndex idx, int64_t fee) noexcept
2738{
2739 Assume(GetTxCount());
2740 Assume(idx == 0);
2741 m_feerate.fee = fee;
2742 Updated(graph, /*level=*/level, /*rename=*/false);
2743}
2744
2745void TxGraphImpl::SetTransactionFee(const Ref& ref, int64_t fee) noexcept
2746{
2747 // Don't do anything if the passed Ref is empty.
2748 if (GetRefGraph(ref) == nullptr) return;
2749 Assume(GetRefGraph(ref) == this);
2750 Assume(m_main_chunkindex_observers == 0);
2751 // Find the entry, its locator, and inform its Cluster about the new feerate, if any.
2752 auto& entry = m_entries[GetRefIndex(ref)];
2753 for (int level = 0; level < MAX_LEVELS; ++level) {
2754 auto& locator = entry.m_locator[level];
2755 if (locator.IsPresent()) {
2756 locator.cluster->SetFee(*this, level, locator.index, fee);
2757 }
2758 }
2759}
2760
2761std::strong_ordering TxGraphImpl::CompareMainOrder(const Ref& a, const Ref& b) noexcept
2762{
2763 // The references must not be empty.
2764 Assume(GetRefGraph(a) == this);
2765 Assume(GetRefGraph(b) == this);
2766 // Apply dependencies in main.
2768 Assume(m_main_clusterset.m_deps_to_add.empty());
2769 // Make both involved Clusters acceptable, so chunk feerates are relevant.
2770 const auto& entry_a = m_entries[GetRefIndex(a)];
2771 const auto& entry_b = m_entries[GetRefIndex(b)];
2772 const auto& locator_a = entry_a.m_locator[0];
2773 const auto& locator_b = entry_b.m_locator[0];
2774 Assume(locator_a.IsPresent());
2775 Assume(locator_b.IsPresent());
2776 MakeAcceptable(*locator_a.cluster, /*level=*/0);
2777 MakeAcceptable(*locator_b.cluster, /*level=*/0);
2778 // Invoke comparison logic.
2779 return CompareMainTransactions(GetRefIndex(a), GetRefIndex(b));
2780}
2781
2782TxGraph::GraphIndex TxGraphImpl::CountDistinctClusters(std::span<const Ref* const> refs, Level level_select) noexcept
2783{
2784 size_t level = GetSpecifiedLevel(level_select);
2785 ApplyDependencies(level);
2786 auto& clusterset = GetClusterSet(level);
2787 Assume(clusterset.m_deps_to_add.empty());
2788 // Build a vector of Clusters that the specified Refs occur in.
2789 std::vector<Cluster*> clusters;
2790 clusters.reserve(refs.size());
2791 for (const Ref* ref : refs) {
2792 Assume(ref);
2793 if (GetRefGraph(*ref) == nullptr) continue;
2794 Assume(GetRefGraph(*ref) == this);
2795 auto cluster = FindCluster(GetRefIndex(*ref), level);
2796 if (cluster != nullptr) clusters.push_back(cluster);
2797 }
2798 // Count the number of distinct elements in clusters.
2799 std::ranges::sort(clusters, [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
2800 Cluster* last{nullptr};
2801 GraphIndex ret{0};
2802 for (Cluster* cluster : clusters) {
2803 ret += (cluster != last);
2804 last = cluster;
2805 }
2806 return ret;
2807}
2808
2809std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>> TxGraphImpl::GetMainStagingDiagrams() noexcept
2810{
2811 Assume(m_staging_clusterset.has_value());
2813 Assume(m_main_clusterset.m_deps_to_add.empty()); // can only fail if main is oversized
2815 Assume(m_staging_clusterset->m_deps_to_add.empty()); // can only fail if staging is oversized
2816 // For all Clusters in main which conflict with Clusters in staging (i.e., all that are removed
2817 // by, or replaced in, staging), gather their chunk feerates.
2818 auto main_clusters = GetConflicts();
2819 std::vector<FeeFrac> main_feerates, staging_feerates;
2820 for (Cluster* cluster : main_clusters) {
2821 cluster->AppendChunkFeerates(main_feerates);
2822 }
2823 // Do the same for the Clusters in staging themselves.
2824 for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
2825 for (const auto& cluster : m_staging_clusterset->m_clusters[quality]) {
2826 cluster->AppendChunkFeerates(staging_feerates);
2827 }
2828 }
2829 // Sort both by decreasing feerate to obtain diagrams, and return them.
2830 std::ranges::sort(main_feerates, std::greater<ByRatioNegSize<FeeFrac>>{});
2831 std::ranges::sort(staging_feerates, std::greater<ByRatioNegSize<FeeFrac>>{});
2832 return std::make_pair(std::move(main_feerates), std::move(staging_feerates));
2833}
2834
2835void GenericClusterImpl::SanityCheck(const TxGraphImpl& graph, int level) const
2836{
2837 // There must be an m_mapping for each m_depgraph position (including holes).
2838 assert(m_depgraph.PositionRange() == m_mapping.size());
2839 // The linearization for this Cluster must contain every transaction once.
2840 assert(m_depgraph.TxCount() == m_linearization.size());
2841 // Unless a split is to be applied, the cluster cannot have any holes.
2842 if (!NeedsSplitting()) {
2843 assert(m_depgraph.Positions() == SetType::Fill(m_depgraph.TxCount()));
2844 }
2845
2846 // Compute the chunking of m_linearization.
2847 auto linchunking = ChunkLinearizationInfo(m_depgraph, m_linearization);
2848 unsigned chunk_num{0};
2849
2850 // Verify m_linearization.
2851 SetType m_done;
2854 assert(m_depgraph.IsAcyclic());
2855 if (m_linearization.empty()) return;
2857 for (auto lin_pos : m_linearization) {
2858 assert(lin_pos < m_mapping.size());
2859 const auto& entry = graph.m_entries[m_mapping[lin_pos]];
2860 // Check that the linearization is topological.
2861 m_done.Set(lin_pos);
2862 if (IsTopological()) {
2863 assert(m_done.IsSupersetOf(m_depgraph.Ancestors(lin_pos)));
2864 }
2865 // Check that the Entry has a locator pointing back to this Cluster & position within it.
2866 assert(entry.m_locator[level].cluster == this);
2867 assert(entry.m_locator[level].index == lin_pos);
2868 // For main-level entries, check linearization position and chunk feerate.
2869 if (level == 0 && IsAcceptable()) {
2870 assert(entry.m_main_lin_index == linindex);
2871 ++linindex;
2872 if (!linchunking[chunk_num].transactions[lin_pos]) {
2873 // First transaction of a new chunk.
2874 ++chunk_num;
2875 assert(chunk_num < linchunking.size());
2876 chunk_pos = 0;
2879 } else {
2882 }
2883 }
2884 assert(entry.m_main_chunk_feerate == linchunking[chunk_num].feerate);
2885 assert(entry.m_main_equal_feerate_chunk_prefix_size == equal_feerate_prefix.size);
2886 // Verify that an entry in the chunk index exists for every chunk-ending transaction.
2887 ++chunk_pos;
2888 if (graph.m_main_clusterset.m_to_remove.empty()) {
2889 bool is_chunk_end = (chunk_pos == linchunking[chunk_num].transactions.Count());
2890 assert((entry.m_main_chunkindex_iterator != graph.m_main_chunkindex.end()) == is_chunk_end);
2891 if (is_chunk_end) {
2892 auto& chunk_data = *entry.m_main_chunkindex_iterator;
2893 if (m_done == m_depgraph.Positions() && chunk_pos == 1) {
2894 assert(chunk_data.m_chunk_count == LinearizationIndex(-1));
2895 } else {
2896 assert(chunk_data.m_chunk_count == chunk_pos);
2897 }
2898 }
2899 }
2900 // If this Cluster has an acceptable quality level, its chunks must be connected.
2901 assert(m_depgraph.IsConnected(linchunking[chunk_num].transactions));
2902 }
2903 }
2904 // Verify that each element of m_depgraph occurred in m_linearization.
2905 assert(m_done == m_depgraph.Positions());
2906}
2907
2908void SingletonClusterImpl::SanityCheck(const TxGraphImpl& graph, int level) const
2909{
2910 // All singletons are optimal, oversized, or need splitting.
2911 Assume(IsOptimal() || IsOversized() || NeedsSplitting());
2912 if (GetTxCount()) {
2913 const auto& entry = graph.m_entries[m_graph_index];
2914 // Check that the Entry has a locator pointing back to this Cluster & position within it.
2915 assert(entry.m_locator[level].cluster == this);
2916 assert(entry.m_locator[level].index == 0);
2917 // For main-level entries, check linearization position and chunk feerate.
2918 if (level == 0 && IsAcceptable()) {
2919 assert(entry.m_main_lin_index == 0);
2920 assert(entry.m_main_chunk_feerate == m_feerate);
2921 assert(entry.m_main_equal_feerate_chunk_prefix_size == m_feerate.size);
2922 if (graph.m_main_clusterset.m_to_remove.empty()) {
2923 assert(entry.m_main_chunkindex_iterator != graph.m_main_chunkindex.end());
2924 auto& chunk_data = *entry.m_main_chunkindex_iterator;
2925 assert(chunk_data.m_chunk_count == LinearizationIndex(-1));
2926 }
2927 }
2928 }
2929}
2930
2931void TxGraphImpl::SanityCheck() const
2932{
2934 std::set<GraphIndex> expected_unlinked;
2936 std::set<const Cluster*> expected_clusters[MAX_LEVELS];
2938 std::set<GraphIndex> expected_removed[MAX_LEVELS];
2940 std::set<uint64_t> sequences;
2942 std::set<const Cluster*> live_clusters;
2944 std::set<GraphIndex> expected_chunkindex;
2946 bool compact_possible{true};
2947
2948 // Go over all Entry objects in m_entries.
2949 for (GraphIndex idx = 0; idx < m_entries.size(); ++idx) {
2950 const auto& entry = m_entries[idx];
2951 if (entry.m_ref == nullptr) {
2952 // Unlinked Entry must have indexes appear in m_unlinked.
2953 expected_unlinked.insert(idx);
2954 } else {
2955 // Every non-unlinked Entry must have a Ref that points back to it.
2956 assert(GetRefGraph(*entry.m_ref) == this);
2957 assert(GetRefIndex(*entry.m_ref) == idx);
2958 }
2959 if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end()) {
2960 // Remember which entries we see a chunkindex entry for.
2961 assert(entry.m_locator[0].IsPresent());
2962 expected_chunkindex.insert(idx);
2963 }
2964 // Verify the Entry m_locators.
2965 bool was_present{false}, was_removed{false};
2966 for (int level = 0; level < MAX_LEVELS; ++level) {
2967 const auto& locator = entry.m_locator[level];
2968 // Every Locator must be in exactly one of these 3 states.
2969 assert(locator.IsMissing() + locator.IsRemoved() + locator.IsPresent() == 1);
2970 if (locator.IsPresent()) {
2971 // Once removed, a transaction cannot be revived.
2973 // Verify that the Cluster agrees with where the Locator claims the transaction is.
2974 assert(locator.cluster->GetClusterEntry(locator.index) == idx);
2975 // Remember that we expect said Cluster to appear in the ClusterSet::m_clusters.
2976 expected_clusters[level].insert(locator.cluster);
2977 was_present = true;
2978 } else if (locator.IsRemoved()) {
2979 // Level 0 (main) cannot have IsRemoved locators (IsMissing there means non-existing).
2980 assert(level > 0);
2981 // A Locator can only be IsRemoved if it was IsPresent before, and only once.
2983 // Remember that we expect this GraphIndex to occur in the ClusterSet::m_removed.
2984 expected_removed[level].insert(idx);
2985 was_removed = true;
2986 }
2987 }
2988 }
2989
2990 // For all levels (0 = main, 1 = staged)...
2991 for (int level = 0; level <= GetTopLevel(); ++level) {
2992 assert(level < MAX_LEVELS);
2993 auto& clusterset = GetClusterSet(level);
2994 std::set<const Cluster*> actual_clusters;
2995 size_t recomputed_cluster_usage{0};
2996
2997 // For all quality levels...
2998 for (int qual = 0; qual < int(QualityLevel::NONE); ++qual) {
2999 QualityLevel quality{qual};
3000 const auto& quality_clusters = clusterset.m_clusters[qual];
3001 // ... for all clusters in them ...
3003 const auto& cluster = *quality_clusters[setindex];
3004 live_clusters.insert(&cluster);
3005 // The number of transactions in a Cluster cannot exceed m_max_cluster_count.
3006 assert(cluster.GetTxCount() <= m_max_cluster_count);
3007 // The level must match the Cluster's own idea of what level it is in (but GetLevel
3008 // can only be called for non-empty Clusters).
3009 assert(cluster.GetTxCount() == 0 || level == cluster.GetLevel(*this));
3010 // The sum of their sizes cannot exceed m_max_cluster_size, unless it is an
3011 // individually oversized transaction singleton. Note that groups of to-be-merged
3012 // clusters which would exceed this limit are marked oversized, which means they
3013 // are never applied.
3014 assert(cluster.IsOversized() || cluster.GetTotalTxSize() <= m_max_cluster_size);
3015 // OVERSIZED clusters are singletons.
3016 assert(!cluster.IsOversized() || cluster.GetTxCount() == 1);
3017 // Transaction counts cannot exceed the Cluster implementation's maximum
3018 // supported transactions count.
3019 assert(cluster.GetTxCount() <= cluster.GetMaxTxCount());
3020 // Unless a Split is yet to be applied, the number of transactions must not be
3021 // below the Cluster implementation's intended transaction count.
3022 if (!cluster.NeedsSplitting()) {
3023 assert(cluster.GetTxCount() >= cluster.GetMinIntendedTxCount());
3024 }
3025
3026 // Check the sequence number.
3027 assert(cluster.m_sequence < m_next_sequence_counter);
3028 assert(!sequences.contains(cluster.m_sequence));
3029 sequences.insert(cluster.m_sequence);
3030 // Remember we saw this Cluster (only if it is non-empty; empty Clusters aren't
3031 // expected to be referenced by the Entry vector).
3032 if (cluster.GetTxCount() != 0) {
3033 actual_clusters.insert(&cluster);
3034 }
3035 // Sanity check the cluster, according to the Cluster's internal rules.
3036 cluster.SanityCheck(*this, level);
3037 // Check that the cluster's quality and setindex matches its position in the quality list.
3038 assert(cluster.m_quality == quality);
3039 assert(cluster.m_setindex == setindex);
3040 // Count memory usage.
3041 recomputed_cluster_usage += cluster.TotalMemoryUsage();
3042 }
3043 }
3044
3045 // Verify memory usage.
3046 assert(clusterset.m_cluster_usage == recomputed_cluster_usage);
3047
3048 // Verify that all to-be-removed transactions have valid identifiers.
3049 for (GraphIndex idx : clusterset.m_to_remove) {
3050 assert(idx < m_entries.size());
3051 // We cannot assert that all m_to_remove transactions are still present: ~Ref on a
3052 // (P,M) transaction (present in main, inherited in staging) will cause an m_to_remove
3053 // addition in both main and staging, but a subsequence ApplyRemovals in main will
3054 // cause it to disappear from staging too, leaving the m_to_remove in place.
3055 }
3056
3057 // Verify that all to-be-added dependencies have valid identifiers.
3058 for (auto [par_idx, chl_idx] : clusterset.m_deps_to_add) {
3060 assert(par_idx < m_entries.size());
3061 assert(chl_idx < m_entries.size());
3062 }
3063
3064 // Verify that the actually encountered clusters match the ones occurring in Entry vector.
3066
3067 // Validate m_group_data cluster pointers refer to live clusters at this
3068 // level or below (staging group data may reference not-yet-pulled-in
3069 // main clusters).
3070 if (clusterset.m_group_data.has_value()) {
3071 for (const Cluster* cl : clusterset.m_group_data->m_group_clusters) {
3072 assert(live_clusters.contains(cl));
3073 assert(cl->GetTxCount() > 0);
3074 assert(FindCluster(cl->GetClusterEntry(0), level) == cl);
3075 }
3076 }
3077 // Verify that the contents of m_removed matches what was expected based on the Entry vector.
3078 std::set<GraphIndex> actual_removed(clusterset.m_removed.begin(), clusterset.m_removed.end());
3079 for (auto i : expected_unlinked) {
3080 // If a transaction exists in both main and staging, and is removed from staging (adding
3081 // it to m_removed there), and consequently destroyed (wiping the locator completely),
3082 // it can remain in m_removed despite not having an IsRemoved() locator. Exclude those
3083 // transactions from the comparison here.
3084 actual_removed.erase(i);
3085 expected_removed[level].erase(i);
3086 }
3087
3089
3090 // If any GraphIndex entries remain in this ClusterSet, compact is not possible.
3091 if (!clusterset.m_deps_to_add.empty()) compact_possible = false;
3092 if (!clusterset.m_to_remove.empty()) compact_possible = false;
3093 if (!clusterset.m_removed.empty()) compact_possible = false;
3094
3095 // For non-top levels, m_oversized must be known (as it cannot change until the level
3096 // on top is gone).
3097 if (level < GetTopLevel()) assert(clusterset.m_oversized.has_value());
3098 }
3099
3100 // Verify that the contents of m_unlinked matches what was expected based on the Entry vector.
3101 std::set<GraphIndex> actual_unlinked(m_unlinked.begin(), m_unlinked.end());
3103
3104 // If compaction was possible, it should have been performed already, and m_unlinked must be
3105 // empty (to prevent memory leaks due to an ever-growing m_entries vector).
3106 if (compact_possible) {
3107 assert(actual_unlinked.empty());
3108 }
3109
3110 // Finally, check the chunk index.
3111 std::set<GraphIndex> actual_chunkindex;
3113 for (const auto& chunk : m_main_chunkindex) {
3114 GraphIndex idx = chunk.m_graph_index;
3115 actual_chunkindex.insert(idx);
3116 auto chunk_feerate = m_entries[idx].m_main_chunk_feerate;
3117 if (!last_chunk_feerate.IsEmpty()) {
3119 }
3121 }
3123}
3124
3125bool TxGraphImpl::DoWork(uint64_t max_cost) noexcept
3126{
3128 // First linearize everything in NEEDS_RELINEARIZE to an acceptable level. If more budget
3129 // remains after that, try to make everything optimal.
3130 for (QualityLevel quality : {QualityLevel::NEEDS_FIX, QualityLevel::NEEDS_RELINEARIZE, QualityLevel::ACCEPTABLE}) {
3131 // First linearize staging, if it exists, then main.
3132 for (int level = GetTopLevel(); level >= 0; --level) {
3133 // Do not modify main if it has any observers.
3134 if (level == 0 && m_main_chunkindex_observers != 0) continue;
3135 ApplyDependencies(level);
3136 auto& clusterset = GetClusterSet(level);
3137 // Do not modify oversized levels.
3138 if (clusterset.m_oversized == true) continue;
3139 auto& queue = clusterset.m_clusters[int(quality)];
3140 while (!queue.empty()) {
3141 if (cost_done >= max_cost) return false;
3142 // Randomize the order in which we process, so that if the first cluster somehow
3143 // needs more work than what max_cost allows, we don't keep spending it on the same
3144 // one.
3145 auto pos = m_rng.randrange<size_t>(queue.size());
3146 auto cost_now = max_cost - cost_done;
3147 if (quality == QualityLevel::NEEDS_FIX || quality == QualityLevel::NEEDS_RELINEARIZE) {
3148 // If we're working with clusters that need relinearization still, only perform
3149 // up to m_acceptable_cost work. If they become ACCEPTABLE, and we still
3150 // have budget after all other clusters are ACCEPTABLE too, we'll spend the
3151 // remaining budget on trying to make them OPTIMAL.
3152 cost_now = std::min(cost_now, m_acceptable_cost);
3153 }
3154 auto [cost, improved] = queue[pos].get()->Relinearize(*this, level, cost_now);
3155 cost_done += cost;
3156 // If no improvement was made to the Cluster, it means we've essentially run out of
3157 // budget. Even though it may be the case that cost_done < max_cost still, the
3158 // linearizer decided there wasn't enough budget left to attempt anything with.
3159 // To avoid an infinite loop that keeps trying clusters with minuscule budgets,
3160 // stop here too.
3161 if (!improved) return false;
3162 }
3163 }
3164 }
3165 // All possible work has been performed, so we can return true. Note that this does *not* mean
3166 // that all clusters are optimally linearized now. It may be that there is nothing to do left
3167 // because all non-optimal clusters are in oversized and/or observer-bearing levels.
3168 return true;
3169}
3170
3171void BlockBuilderImpl::Next() noexcept
3172{
3173 // Don't do anything if we're already done.
3174 if (m_cur_iter == m_graph->m_main_chunkindex.end()) return;
3175 while (true) {
3176 // Advance the pointer, and stop if we reach the end.
3177 ++m_cur_iter;
3178 m_cur_cluster = nullptr;
3179 if (m_cur_iter == m_graph->m_main_chunkindex.end()) break;
3180 // Find the cluster pointed to by m_cur_iter.
3181 const auto& chunk_data = *m_cur_iter;
3182 const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];
3183 m_cur_cluster = chunk_end_entry.m_locator[0].cluster;
3184 m_known_end_of_cluster = false;
3185 // If we previously skipped a chunk from this cluster we cannot include more from it.
3186 if (!m_excluded_clusters.contains(m_cur_cluster->m_sequence)) break;
3187 }
3188}
3189
3190std::optional<std::pair<std::vector<TxGraph::Ref*>, FeePerWeight>> BlockBuilderImpl::GetCurrentChunk() noexcept
3191{
3192 std::optional<std::pair<std::vector<TxGraph::Ref*>, FeePerWeight>> ret;
3193 // Populate the return value if we are not done.
3194 if (m_cur_iter != m_graph->m_main_chunkindex.end()) {
3195 ret.emplace();
3196 const auto& chunk_data = *m_cur_iter;
3197 const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];
3198 if (chunk_data.m_chunk_count == LinearizationIndex(-1)) {
3199 // Special case in case just a single transaction remains, avoiding the need to
3200 // dispatch to and dereference Cluster.
3201 ret->first.resize(1);
3202 Assume(chunk_end_entry.m_ref != nullptr);
3203 ret->first[0] = chunk_end_entry.m_ref;
3204 m_known_end_of_cluster = true;
3205 } else {
3206 Assume(m_cur_cluster);
3207 ret->first.resize(chunk_data.m_chunk_count);
3208 auto start_pos = chunk_end_entry.m_main_lin_index + 1 - chunk_data.m_chunk_count;
3209 m_known_end_of_cluster = m_cur_cluster->GetClusterRefs(*m_graph, ret->first, start_pos);
3210 // If the chunk size was 1 and at end of cluster, then the special case above should
3211 // have been used.
3212 Assume(!m_known_end_of_cluster || chunk_data.m_chunk_count > 1);
3213 }
3214 ret->second = chunk_end_entry.m_main_chunk_feerate;
3215 }
3216 return ret;
3217}
3218
3219BlockBuilderImpl::BlockBuilderImpl(TxGraphImpl& graph) noexcept : m_graph(&graph)
3220{
3221 // Make sure all clusters in main are up to date, and acceptable.
3222 m_graph->MakeAllAcceptable(0);
3223 // There cannot remain any inapplicable dependencies (only possible if main is oversized).
3224 Assume(m_graph->m_main_clusterset.m_deps_to_add.empty());
3225 // Remember that this object is observing the graph's index, so that we can detect concurrent
3226 // modifications.
3227 ++m_graph->m_main_chunkindex_observers;
3228 // Find the first chunk.
3229 m_cur_iter = m_graph->m_main_chunkindex.begin();
3230 m_cur_cluster = nullptr;
3231 if (m_cur_iter != m_graph->m_main_chunkindex.end()) {
3232 // Find the cluster pointed to by m_cur_iter.
3233 const auto& chunk_data = *m_cur_iter;
3234 const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];
3235 m_cur_cluster = chunk_end_entry.m_locator[0].cluster;
3236 }
3237}
3238
3239BlockBuilderImpl::~BlockBuilderImpl()
3240{
3241 Assume(m_graph->m_main_chunkindex_observers > 0);
3242 // Permit modifications to the main graph again after destroying the BlockBuilderImpl.
3243 --m_graph->m_main_chunkindex_observers;
3244}
3245
3246void BlockBuilderImpl::Include() noexcept
3247{
3248 // The actual inclusion of the chunk is done by the calling code. All we have to do is switch
3249 // to the next chunk.
3250 Next();
3251}
3252
3253void BlockBuilderImpl::Skip() noexcept
3254{
3255 // When skipping a chunk we need to not include anything more of the cluster, as that could make
3256 // the result topologically invalid. However, don't do this if the chunk is known to be the last
3257 // chunk of the cluster. This may significantly reduce the size of m_excluded_clusters,
3258 // especially when many singleton clusters are ignored.
3259 if (m_cur_cluster != nullptr && !m_known_end_of_cluster) {
3260 m_excluded_clusters.insert(m_cur_cluster->m_sequence);
3261 }
3262 Next();
3263}
3264
3265std::unique_ptr<TxGraph::BlockBuilder> TxGraphImpl::GetBlockBuilder() noexcept
3266{
3267 return std::make_unique<BlockBuilderImpl>(*this);
3268}
3269
3270std::pair<std::vector<TxGraph::Ref*>, FeePerWeight> TxGraphImpl::GetWorstMainChunk() noexcept
3271{
3272 std::pair<std::vector<Ref*>, FeePerWeight> ret;
3273 // Make sure all clusters in main are up to date, and acceptable.
3275 Assume(m_main_clusterset.m_deps_to_add.empty());
3276 // If the graph is not empty, populate ret.
3277 if (!m_main_chunkindex.empty()) {
3278 const auto& chunk_data = *m_main_chunkindex.rbegin();
3279 const auto& chunk_end_entry = m_entries[chunk_data.m_graph_index];
3280 Cluster* cluster = chunk_end_entry.m_locator[0].cluster;
3281 if (chunk_data.m_chunk_count == LinearizationIndex(-1) || chunk_data.m_chunk_count == 1) {
3282 // Special case for singletons.
3283 ret.first.resize(1);
3284 Assume(chunk_end_entry.m_ref != nullptr);
3285 ret.first[0] = chunk_end_entry.m_ref;
3286 } else {
3287 ret.first.resize(chunk_data.m_chunk_count);
3288 auto start_pos = chunk_end_entry.m_main_lin_index + 1 - chunk_data.m_chunk_count;
3289 cluster->GetClusterRefs(*this, ret.first, start_pos);
3290 std::reverse(ret.first.begin(), ret.first.end());
3291 }
3292 ret.second = chunk_end_entry.m_main_chunk_feerate;
3293 }
3294 return ret;
3295}
3296
3297std::vector<TxGraph::Ref*> TxGraphImpl::Trim() noexcept
3298{
3299 int level = GetTopLevel();
3300 Assume(m_main_chunkindex_observers == 0 || level != 0);
3301 std::vector<TxGraph::Ref*> ret;
3302
3303 // Compute the groups of to-be-merged Clusters (which also applies all removals, and splits).
3304 auto& clusterset = GetClusterSet(level);
3305 if (clusterset.m_oversized == false) return ret;
3306 GroupClusters(level);
3307 Assume(clusterset.m_group_data.has_value());
3308 // Nothing to do if not oversized.
3309 Assume(clusterset.m_oversized.has_value());
3310 if (clusterset.m_oversized == false) return ret;
3311
3312 // In this function, would-be clusters (as precomputed in m_group_data by GroupClusters) are
3313 // trimmed by removing transactions in them such that the resulting clusters satisfy the size
3314 // and count limits.
3315 //
3316 // It works by defining for each would-be cluster a rudimentary linearization: at every point
3317 // the highest-chunk-feerate remaining transaction is picked among those with no unmet
3318 // dependencies. "Dependency" here means either a to-be-added dependency (m_deps_to_add), or
3319 // an implicit dependency added between any two consecutive transaction in their current
3320 // cluster linearization. So it can be seen as a "merge sort" of the chunks of the clusters,
3321 // but respecting the dependencies being added.
3322 //
3323 // This rudimentary linearization is computed lazily, by putting all eligible (no unmet
3324 // dependencies) transactions in a heap, and popping the highest-feerate one from it. Along the
3325 // way, the counts and sizes of the would-be clusters up to that point are tracked (by
3326 // partitioning the involved transactions using a union-find structure). Any transaction whose
3327 // addition would cause a violation is removed, along with all their descendants.
3328 //
3329 // A next invocation of GroupClusters (after applying the removals) will compute the new
3330 // resulting clusters, and none of them will violate the limits.
3331
3334 std::vector<std::pair<GraphIndex, GraphIndex>> deps_by_parent;
3336 std::vector<std::pair<GraphIndex, GraphIndex>> deps_by_child;
3340 std::vector<TrimTxData> trim_data;
3343 std::vector<std::vector<TrimTxData>::iterator> trim_heap;
3345 std::vector<TrimTxData*> current_deps;
3346
3348 static constexpr auto cmp_fn = [](auto a, auto b) noexcept {
3349 // Sort by increasing chunk feerate, and then by decreasing size.
3350 // We do not need to sort by cluster or within clusters, because due to the implicit
3351 // dependency between consecutive linearization elements, no two transactions from the
3352 // same Cluster will ever simultaneously be in the heap.
3353 return ByRatioNegSize{a->m_chunk_feerate} < ByRatioNegSize{b->m_chunk_feerate};
3354 };
3355
3357 static constexpr auto find_fn = [](TrimTxData* arg) noexcept {
3358 while (arg != arg->m_uf_parent) {
3359 // Replace pointer to parent with pointer to grandparent (path splitting).
3360 // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Finding_set_representatives.
3361 auto par = arg->m_uf_parent;
3362 arg->m_uf_parent = par->m_uf_parent;
3363 arg = par;
3364 }
3365 return arg;
3366 };
3367
3370 static constexpr auto union_fn = [](TrimTxData* arg1, TrimTxData* arg2) noexcept {
3371 // Replace arg1 and arg2 by their representatives.
3372 auto rep1 = find_fn(arg1);
3373 auto rep2 = find_fn(arg2);
3374 // Bail out if both representatives are the same, because that means arg1 and arg2 are in
3375 // the same partition already.
3376 if (rep1 == rep2) return rep1;
3377 // Pick the lower-count root to become a child of the higher-count one.
3378 // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Union_by_size.
3379 if (rep1->m_uf_count < rep2->m_uf_count) std::swap(rep1, rep2);
3380 rep2->m_uf_parent = rep1;
3381 // Add the statistics of arg2 (which is no longer a representative) to those of arg1 (which
3382 // is now the representative for both).
3383 rep1->m_uf_size += rep2->m_uf_size;
3384 rep1->m_uf_count += rep2->m_uf_count;
3385 return rep1;
3386 };
3387
3389 auto locate_fn = [&](GraphIndex index) noexcept {
3390 auto it = std::lower_bound(trim_data.begin(), trim_data.end(), index, [](TrimTxData& elem, GraphIndex idx) noexcept {
3391 return elem.m_index < idx;
3392 });
3393 Assume(it != trim_data.end() && it->m_index == index);
3394 return it;
3395 };
3396
3397 // For each group of to-be-merged Clusters.
3398 for (const auto& group_data : clusterset.m_group_data->m_groups) {
3399 trim_data.clear();
3400 trim_heap.clear();
3401 deps_by_child.clear();
3402 deps_by_parent.clear();
3403
3404 // Gather trim data and implicit dependency data from all involved Clusters.
3405 auto cluster_span = std::span{clusterset.m_group_data->m_group_clusters}
3406 .subspan(group_data.m_cluster_offset, group_data.m_cluster_count);
3407 uint64_t size{0};
3408 for (Cluster* cluster : cluster_span) {
3409 MakeAcceptable(*cluster, cluster->GetLevel(*this));
3410 size += cluster->AppendTrimData(trim_data, deps_by_child);
3411 }
3412 // If this group of Clusters does not violate any limits, continue to the next group.
3413 if (trim_data.size() <= m_max_cluster_count && size <= m_max_cluster_size) continue;
3414 // Sort the trim data by GraphIndex. In what follows, we will treat this sorted vector as
3415 // a map from GraphIndex to TrimTxData via locate_fn, and its ordering will not change
3416 // anymore.
3417 std::ranges::sort(trim_data, [](auto& a, auto& b) noexcept { return a.m_index < b.m_index; });
3418
3419 // Add the explicitly added dependencies to deps_by_child.
3420 deps_by_child.insert(deps_by_child.end(),
3421 clusterset.m_deps_to_add.begin() + group_data.m_deps_offset,
3422 clusterset.m_deps_to_add.begin() + group_data.m_deps_offset + group_data.m_deps_count);
3423
3424 // Sort deps_by_child by child transaction GraphIndex. The order will not be changed
3425 // anymore after this.
3426 std::ranges::sort(deps_by_child, [](auto& a, auto& b) noexcept { return a.second < b.second; });
3427 // Fill m_parents_count and m_parents_offset in trim_data, as well as m_deps_left, and
3428 // initially populate trim_heap. Because of the sort above, all dependencies involving the
3429 // same child are grouped together, so a single linear scan suffices.
3430 auto deps_it = deps_by_child.begin();
3431 for (auto trim_it = trim_data.begin(); trim_it != trim_data.end(); ++trim_it) {
3432 trim_it->m_parent_offset = deps_it - deps_by_child.begin();
3433 trim_it->m_deps_left = 0;
3434 while (deps_it != deps_by_child.end() && deps_it->second == trim_it->m_index) {
3435 ++trim_it->m_deps_left;
3436 ++deps_it;
3437 }
3438 trim_it->m_parent_count = trim_it->m_deps_left;
3439 // If this transaction has no unmet dependencies, and is not oversized, add it to the
3440 // heap (just append for now, the heapification happens below).
3441 if (trim_it->m_deps_left == 0 && trim_it->m_tx_size <= m_max_cluster_size) {
3442 trim_heap.push_back(trim_it);
3443 }
3444 }
3445 Assume(deps_it == deps_by_child.end());
3446
3447 // Construct deps_by_parent, sorted by parent transaction GraphIndex. The order will not be
3448 // changed anymore after this.
3450 std::ranges::sort(deps_by_parent, [](auto& a, auto& b) noexcept { return a.first < b.first; });
3451 // Fill m_children_offset and m_children_count in trim_data. Because of the sort above, all
3452 // dependencies involving the same parent are grouped together, so a single linear scan
3453 // suffices.
3454 deps_it = deps_by_parent.begin();
3455 for (auto& trim_entry : trim_data) {
3456 trim_entry.m_children_count = 0;
3457 trim_entry.m_children_offset = deps_it - deps_by_parent.begin();
3458 while (deps_it != deps_by_parent.end() && deps_it->first == trim_entry.m_index) {
3459 ++trim_entry.m_children_count;
3460 ++deps_it;
3461 }
3462 }
3463 Assume(deps_it == deps_by_parent.end());
3464
3465 // Build a heap of all transactions with 0 unmet dependencies.
3466 std::make_heap(trim_heap.begin(), trim_heap.end(), cmp_fn);
3467
3468 // Iterate over to-be-included transactions, and convert them to included transactions, or
3469 // skip them if adding them would violate resource limits of the would-be cluster.
3470 //
3471 // It is possible that the heap empties without ever hitting either cluster limit, in case
3472 // the implied graph (to be added dependencies plus implicit dependency between each
3473 // original transaction and its predecessor in the linearization it came from) contains
3474 // cycles. Such cycles will be removed entirely, because each of the transactions in the
3475 // cycle permanently have unmet dependencies. However, this cannot occur in real scenarios
3476 // where Trim() is called to deal with reorganizations that would violate cluster limits,
3477 // as all added dependencies are in the same direction (from old mempool transactions to
3478 // new from-block transactions); cycles require dependencies in both directions to be
3479 // added.
3480 while (!trim_heap.empty()) {
3481 // Move the best remaining transaction to the end of trim_heap.
3482 std::pop_heap(trim_heap.begin(), trim_heap.end(), cmp_fn);
3483 // Pop it, and find its TrimTxData.
3484 auto& entry = *trim_heap.back();
3485 trim_heap.pop_back();
3486
3487 // Initialize it as a singleton partition.
3488 entry.m_uf_parent = &entry;
3489 entry.m_uf_count = 1;
3490 entry.m_uf_size = entry.m_tx_size;
3491
3492 // Find the distinct transaction partitions this entry depends on.
3493 current_deps.clear();
3494 for (auto& [par, chl] : std::span{deps_by_child}.subspan(entry.m_parent_offset, entry.m_parent_count)) {
3495 Assume(chl == entry.m_index);
3496 current_deps.push_back(find_fn(&*locate_fn(par)));
3497 }
3498 std::ranges::sort(current_deps);
3499 current_deps.erase(std::ranges::unique(current_deps).begin(), current_deps.end());
3500
3501 // Compute resource counts.
3502 uint32_t new_count = 1;
3503 uint64_t new_size = entry.m_tx_size;
3504 for (TrimTxData* ptr : current_deps) {
3505 new_count += ptr->m_uf_count;
3506 new_size += ptr->m_uf_size;
3507 }
3508 // Skip the entry if this would violate any limit.
3509 if (new_count > m_max_cluster_count || new_size > m_max_cluster_size) continue;
3510
3511 // Union the partitions this transaction and all its dependencies are in together.
3512 auto rep = &entry;
3513 for (TrimTxData* ptr : current_deps) rep = union_fn(ptr, rep);
3514 // Mark the entry as included (so the loop below will not remove the transaction).
3515 entry.m_deps_left = uint32_t(-1);
3516 // Mark each to-be-added dependency involving this transaction as parent satisfied.
3517 for (auto& [par, chl] : std::span{deps_by_parent}.subspan(entry.m_children_offset, entry.m_children_count)) {
3518 Assume(par == entry.m_index);
3519 auto chl_it = locate_fn(chl);
3520 // Reduce the number of unmet dependencies of chl_it, and if that brings the number
3521 // to zero, add it to the heap of includable transactions.
3522 Assume(chl_it->m_deps_left > 0);
3523 if (--chl_it->m_deps_left == 0) {
3524 trim_heap.push_back(chl_it);
3525 std::push_heap(trim_heap.begin(), trim_heap.end(), cmp_fn);
3526 }
3527 }
3528 }
3529
3530 // Remove all the transactions that were not processed above. Because nothing gets
3531 // processed until/unless all its dependencies are met, this automatically guarantees
3532 // that if a transaction is removed, all its descendants, or would-be descendants, are
3533 // removed as well.
3534 for (const auto& trim_entry : trim_data) {
3535 if (trim_entry.m_deps_left != uint32_t(-1)) {
3536 ret.push_back(m_entries[trim_entry.m_index].m_ref);
3537 clusterset.m_to_remove.push_back(trim_entry.m_index);
3538 }
3539 }
3540 }
3541 clusterset.m_group_data.reset();
3542 clusterset.m_oversized = false;
3543 Assume(!ret.empty());
3544 return ret;
3545}
3546
3547size_t TxGraphImpl::GetMainMemoryUsage() noexcept
3548{
3549 // Make sure splits/merges are applied, as memory usage may not be representative otherwise.
3550 SplitAll(/*up_to_level=*/0);
3551 ApplyDependencies(/*level=*/0);
3552 // Compute memory usage
3553 size_t usage = /* From clusters */
3554 m_main_clusterset.m_cluster_usage +
3555 /* From Entry objects. */
3556 sizeof(Entry) * m_main_clusterset.m_txcount +
3557 /* From the chunk index. */
3558 memusage::DynamicUsage(m_main_chunkindex);
3559 return usage;
3560}
3561
3562} // namespace
3563
3565{
3566 if (m_graph) {
3567 // Inform the TxGraph about the Ref being destroyed.
3568 m_graph->UnlinkRef(m_index);
3569 m_graph = nullptr;
3570 }
3571}
3572
3573TxGraph::Ref::Ref(Ref&& other) noexcept
3574{
3575 // Inform the TxGraph of other that its Ref is being moved.
3576 if (other.m_graph) other.m_graph->UpdateRef(other.m_index, *this);
3577 // Actually move the contents.
3578 std::swap(m_graph, other.m_graph);
3579 std::swap(m_index, other.m_index);
3580}
3581
3582std::unique_ptr<TxGraph> MakeTxGraph(
3583 unsigned max_cluster_count,
3584 uint64_t max_cluster_size,
3586 const std::function<std::strong_ordering(const TxGraph::Ref&, const TxGraph::Ref&)>& fallback_order) noexcept
3587{
3588 return std::make_unique<TxGraphImpl>(
3589 /*max_cluster_count=*/max_cluster_count,
3590 /*max_cluster_size=*/max_cluster_size,
3591 /*acceptable_cost=*/acceptable_cost,
3592 /*fallback_order=*/fallback_order);
3593}
int ret
ArgsManager & args
Definition bitcoind.cpp:280
std::conditional_t<(BITS<=32), bitset_detail::IntBitSet< uint32_t >, std::conditional_t<(BITS<=std::numeric_limits< size_t >::digits), bitset_detail::IntBitSet< size_t >, bitset_detail::MultiIntBitSet< size_t, CeilDiv(BITS, size_t{std::numeric_limits< size_t >::digits})> > > BitSet
Definition bitset.h:526
#define Assume(val)
Assume is the identity function.
Definition check.h:131
Wrapper around FeeFrac & derived types, which adds a feerate-based ordering which treats equal-feerat...
Definition feefrac.h:218
Wrapper around FeeFrac & derived types, which adds a total ordering which first sorts by feerate and ...
Definition feefrac.h:289
Fast randomness source.
Definition random.h:386
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
Definition random.h:254
Interface returned by GetBlockBuilder.
Definition txgraph.h:182
virtual ~Ref()
Destroy this Ref.
Definition txgraph.cpp:3564
Ref() noexcept=default
Construct an empty Ref.
Data structure to encapsulate fees, sizes, and dependencies for a set of transactions.
Definition txgraph.h:48
uint32_t GraphIndex
Internal identifier for a transaction within a TxGraph.
Definition txgraph.h:51
Data structure that holds a transaction graph's preprocessed data (fee, size, ancestors,...
const SetType & Ancestors(DepGraphIndex i) const noexcept
Get the ancestors of a given transaction i.
SetType GetReducedParents(DepGraphIndex i) const noexcept
Compute the (reduced) set of parents of node i in this graph.
const FeeFrac & FeeRate(DepGraphIndex i) const noexcept
Get the feerate of a given transaction i.
bool IsConnected(const SetType &subset) const noexcept
Determine if a subset is connected.
DepGraphIndex PositionRange() const noexcept
Get the range of positions in this DepGraph.
SetType FindConnectedComponent(const SetType &todo) const noexcept
Find some connected component within the subset "todo" of this graph.
DepGraphIndex AddTransaction(const FeeFrac &feefrac) noexcept
Add a new unconnected transaction to this transaction graph (in the first available position),...
void RemoveTransactions(const SetType &del) noexcept
Remove the specified positions from this DepGraph.
auto TxCount() const noexcept
Get the number of transactions in the graph.
const SetType & Descendants(DepGraphIndex i) const noexcept
Get the descendants of a given transaction i.
size_t DynamicMemoryUsage() const noexcept
const SetType & Positions() const noexcept
Get the set of transactions positions in use.
void Compact() noexcept
Reduce memory usage if possible.
void AddDependencies(const SetType &parents, DepGraphIndex child) noexcept
Modify this transaction graph, adding multiple parents to a specified child.
bool IsAcyclic() const noexcept
Check if this graph is acyclic.
static const PrecomputedData data
Precomputed COutPoint and CCoins values.
uint64_t fee
uint64_t sequence
std::vector< FeeFrac > ChunkLinearization(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the feerates of the chunks of linearization.
std::tuple< std::vector< DepGraphIndex >, bool, uint64_t > Linearize(const DepGraph< SetType > &depgraph, uint64_t max_cost, uint64_t rng_seed, const StrongComparator< DepGraphIndex > auto &fallback_order, std::span< const DepGraphIndex > old_linearization={}, bool is_topological=true) noexcept
Find or improve a linearization for a cluster.
uint32_t DepGraphIndex
Data type to represent transaction indices in DepGraphs and the clusters they represent.
void SanityCheck(const DepGraph< SetType > &depgraph)
Perform a sanity/consistency check on a DepGraph.
void PostLinearize(const DepGraph< SetType > &depgraph, std::span< DepGraphIndex > linearization)
Improve a given linearization.
std::vector< SetInfo< SetType > > ChunkLinearizationInfo(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the chunks of linearization as SetInfos.
static size_t DynamicUsage(const int8_t &v)
Dynamic memory usage for built-in types is zero.
Definition memusage.h:31
static size_t MallocUsage(size_t alloc)
Compute the total memory used by allocating alloc bytes.
Definition memusage.h:52
std::vector< T > Split(std::span< const char > sp LIFETIMEBOUND, std::string_view separators, bool include_sep=false)
Split a string on any char found in separators, returning a vector.
Definition string.h:120
Data structure storing a fee and size.
Definition feefrac.h:21
int64_t fee
Definition feefrac.h:88
int32_t size
Definition feefrac.h:89
Tagged wrapper around FeeFrac to avoid unit confusion.
Definition feefrac.h:191
static FeePerUnit FromFeeFrac(const FeeFrac &feefrac) noexcept
Convert a FeeFrac to a FeePerUnit.
Definition feefrac.h:196
consteval auto _(util::TranslatedLiteral str)
Definition translation.h:79
std::unique_ptr< TxGraph > MakeTxGraph(unsigned max_cluster_count, uint64_t max_cluster_size, uint64_t acceptable_cost, const std::function< std::strong_ordering(const TxGraph::Ref &, const TxGraph::Ref &)> &fallback_order) noexcept
Construct a new TxGraph with the specified limit on the number of transactions within a cluster,...
Definition txgraph.cpp:3582
constexpr unsigned MAX_CLUSTER_COUNT_LIMIT
Definition txgraph.h:18
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:82
assert(!tx.IsCoinBase())
void ClearShrink(V &v) noexcept
Clear a vector (or std::deque) and release its allocated memory.
Definition vector.h:56