Bitcoin Core 29.99.0
P2P Digital Currency
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 <memory>
16#include <set>
17#include <span>
18#include <utility>
19
20namespace {
21
22using namespace cluster_linearize;
23
25static constexpr int MAX_LEVELS{2};
26
27// Forward declare the TxGraph implementation class.
28class TxGraphImpl;
29
31using LinearizationIndex = uint32_t;
33using ClusterSetIndex = uint32_t;
34
36enum class QualityLevel
37{
40 OVERSIZED_SINGLETON,
42 NEEDS_SPLIT,
44 NEEDS_SPLIT_ACCEPTABLE,
46 NEEDS_RELINEARIZE,
48 ACCEPTABLE,
50 OPTIMAL,
53 NONE,
54};
55
57struct TrimTxData
58{
59 // Fields populated by Cluster::AppendTrimData(). These are immutable after TrimTxData
60 // construction.
62 FeePerWeight m_chunk_feerate;
64 TxGraph::GraphIndex m_index;
66 uint32_t m_tx_size;
67
68 // Fields only used internally by TxGraphImpl::Trim():
70 uint32_t m_deps_left;
72 uint32_t m_parent_count;
74 uint32_t m_parent_offset;
76 uint32_t m_children_count;
78 uint32_t m_children_offset;
79
80 // Fields only used internally by TxGraphImpl::Trim()'s union-find implementation, and only for
81 // transactions that are definitely included or definitely rejected.
82 //
83 // As transactions get processed, they get organized into trees which form partitions
84 // representing the would-be clusters up to that point. The root of each tree is a
85 // representative for that partition. See
86 // https://en.wikipedia.org/wiki/Disjoint-set_data_structure.
87 //
90 TrimTxData* m_uf_parent;
92 uint32_t m_uf_count;
94 uint64_t m_uf_size;
95};
96
98class Cluster
99{
100 friend class TxGraphImpl;
101 using GraphIndex = TxGraph::GraphIndex;
102 using SetType = BitSet<MAX_CLUSTER_COUNT_LIMIT>;
104 DepGraph<SetType> m_depgraph;
108 std::vector<GraphIndex> m_mapping;
111 std::vector<DepGraphIndex> m_linearization;
113 QualityLevel m_quality{QualityLevel::NONE};
115 ClusterSetIndex m_setindex{ClusterSetIndex(-1)};
117 int m_level{-1};
120 uint64_t m_sequence;
121
122public:
123 Cluster() noexcept = delete;
125 explicit Cluster(uint64_t sequence) noexcept;
127 explicit Cluster(uint64_t sequence, TxGraphImpl& graph, const FeePerWeight& feerate, GraphIndex graph_index) noexcept;
128
129 // Cannot move or copy (would invalidate Cluster* in Locator and ClusterSet). */
130 Cluster(const Cluster&) = delete;
131 Cluster& operator=(const Cluster&) = delete;
132 Cluster(Cluster&&) = delete;
133 Cluster& operator=(Cluster&&) = delete;
134
135 // Generic helper functions.
136
138 bool IsAcceptable(bool after_split = false) const noexcept
139 {
140 return m_quality == QualityLevel::ACCEPTABLE || m_quality == QualityLevel::OPTIMAL ||
141 (after_split && m_quality == QualityLevel::NEEDS_SPLIT_ACCEPTABLE);
142 }
144 bool IsOptimal() const noexcept
145 {
146 return m_quality == QualityLevel::OPTIMAL;
147 }
151 bool IsOversized() const noexcept { return m_quality == QualityLevel::OVERSIZED_SINGLETON; }
153 bool NeedsSplitting() const noexcept
154 {
155 return m_quality == QualityLevel::NEEDS_SPLIT ||
156 m_quality == QualityLevel::NEEDS_SPLIT_ACCEPTABLE;
157 }
159 LinearizationIndex GetTxCount() const noexcept { return m_linearization.size(); }
161 uint64_t GetTotalTxSize() const noexcept;
163 GraphIndex GetClusterEntry(DepGraphIndex index) const noexcept { return m_mapping[index]; }
165 void UpdateMapping(DepGraphIndex cluster_idx, GraphIndex graph_idx) noexcept { m_mapping[cluster_idx] = graph_idx; }
167 void Updated(TxGraphImpl& graph) noexcept;
169 Cluster* CopyToStaging(TxGraphImpl& graph) const noexcept;
171 void GetConflicts(const TxGraphImpl& graph, std::vector<Cluster*>& out) const noexcept;
174 void MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept;
176 void Clear(TxGraphImpl& graph) noexcept;
178 void MoveToMain(TxGraphImpl& graph) noexcept;
179
180 // Functions that implement the Cluster-specific side of internal TxGraphImpl mutations.
181
184 void ApplyRemovals(TxGraphImpl& graph, std::span<GraphIndex>& to_remove) noexcept;
187 [[nodiscard]] bool Split(TxGraphImpl& graph) noexcept;
189 void Merge(TxGraphImpl& graph, Cluster& cluster) noexcept;
191 void ApplyDependencies(TxGraphImpl& graph, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept;
194 std::pair<uint64_t, bool> Relinearize(TxGraphImpl& graph, uint64_t max_iters) noexcept;
196 void AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept;
200 uint64_t AppendTrimData(std::vector<TrimTxData>& ret, std::vector<std::pair<GraphIndex, GraphIndex>>& deps) const noexcept;
201
202 // Functions that implement the Cluster-specific side of public TxGraph functions.
203
206 void GetAncestorRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept;
209 void GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept;
213 bool GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::Ref*> range, LinearizationIndex start_pos) noexcept;
215 FeePerWeight GetIndividualFeerate(DepGraphIndex idx) noexcept;
217 void SetFee(TxGraphImpl& graph, DepGraphIndex idx, int64_t fee) noexcept;
218
219 // Debugging functions.
220
221 void SanityCheck(const TxGraphImpl& graph, int level) const;
222};
223
224
248class TxGraphImpl final : public TxGraph
249{
250 friend class Cluster;
251 friend class BlockBuilderImpl;
252private:
254 FastRandomContext m_rng;
256 const DepGraphIndex m_max_cluster_count;
258 const uint64_t m_max_cluster_size;
261 const uint64_t m_acceptable_iters;
262
264 struct GroupEntry
265 {
267 uint32_t m_cluster_offset;
269 uint32_t m_cluster_count;
271 uint32_t m_deps_offset;
273 uint32_t m_deps_count;
274 };
275
277 struct GroupData
278 {
280 std::vector<GroupEntry> m_groups;
282 std::vector<Cluster*> m_group_clusters;
283 };
284
286 struct ClusterSet
287 {
289 std::array<std::vector<std::unique_ptr<Cluster>>, int(QualityLevel::NONE)> m_clusters;
291 std::vector<GraphIndex> m_to_remove;
294 std::vector<std::pair<GraphIndex, GraphIndex>> m_deps_to_add;
296 std::optional<GroupData> m_group_data = GroupData{};
300 std::vector<GraphIndex> m_removed;
303 GraphIndex m_txcount{0};
305 GraphIndex m_txcount_oversized{0};
307 std::optional<bool> m_oversized{false};
308
309 ClusterSet() noexcept = default;
310 };
311
313 ClusterSet m_main_clusterset;
315 std::optional<ClusterSet> m_staging_clusterset;
317 uint64_t m_next_sequence_counter{0};
318
320 struct ChunkData
321 {
323 mutable GraphIndex m_graph_index;
325 LinearizationIndex m_chunk_count;
326
327 ChunkData(GraphIndex graph_index, LinearizationIndex chunk_count) noexcept :
328 m_graph_index{graph_index}, m_chunk_count{chunk_count} {}
329 };
330
332 static std::strong_ordering CompareClusters(Cluster* a, Cluster* b) noexcept
333 {
334 // The nullptr pointer compares before everything else.
335 if (a == nullptr || b == nullptr) {
336 return (a != nullptr) <=> (b != nullptr);
337 }
338 // If neither pointer is nullptr, compare the Clusters' sequence numbers.
339 Assume(a == b || a->m_sequence != b->m_sequence);
340 return a->m_sequence <=> b->m_sequence;
341 }
342
344 std::strong_ordering CompareMainTransactions(GraphIndex a, GraphIndex b) const noexcept
345 {
346 Assume(a < m_entries.size() && b < m_entries.size());
347 const auto& entry_a = m_entries[a];
348 const auto& entry_b = m_entries[b];
349 // Compare chunk feerates, and return result if it differs.
350 auto feerate_cmp = FeeRateCompare(entry_b.m_main_chunk_feerate, entry_a.m_main_chunk_feerate);
351 if (feerate_cmp < 0) return std::strong_ordering::less;
352 if (feerate_cmp > 0) return std::strong_ordering::greater;
353 // Compare Cluster m_sequence as tie-break for equal chunk feerates.
354 const auto& locator_a = entry_a.m_locator[0];
355 const auto& locator_b = entry_b.m_locator[0];
356 Assume(locator_a.IsPresent() && locator_b.IsPresent());
357 if (locator_a.cluster != locator_b.cluster) {
358 return CompareClusters(locator_a.cluster, locator_b.cluster);
359 }
360 // As final tie-break, compare position within cluster linearization.
361 return entry_a.m_main_lin_index <=> entry_b.m_main_lin_index;
362 }
363
365 class ChunkOrder
366 {
367 const TxGraphImpl* const m_graph;
368 public:
369 explicit ChunkOrder(const TxGraphImpl* graph) : m_graph(graph) {}
370
371 bool operator()(const ChunkData& a, const ChunkData& b) const noexcept
372 {
373 return m_graph->CompareMainTransactions(a.m_graph_index, b.m_graph_index) < 0;
374 }
375 };
376
378 using ChunkIndex = std::set<ChunkData, ChunkOrder>;
379
382 ChunkIndex m_main_chunkindex;
384 size_t m_main_chunkindex_observers{0};
386 std::vector<ChunkIndex::node_type> m_main_chunkindex_discarded;
387
418 struct Locator
419 {
421 Cluster* cluster{nullptr};
423 DepGraphIndex index{0};
424
426 void SetMissing() noexcept { cluster = nullptr; index = 0; }
428 void SetRemoved() noexcept { cluster = nullptr; index = DepGraphIndex(-1); }
430 void SetPresent(Cluster* c, DepGraphIndex i) noexcept { cluster = c; index = i; }
432 bool IsMissing() const noexcept { return cluster == nullptr && index == 0; }
434 bool IsRemoved() const noexcept { return cluster == nullptr && index == DepGraphIndex(-1); }
436 bool IsPresent() const noexcept { return cluster != nullptr; }
437 };
438
440 struct Entry
441 {
443 Ref* m_ref{nullptr};
446 ChunkIndex::iterator m_main_chunkindex_iterator;
448 Locator m_locator[MAX_LEVELS];
450 FeePerWeight m_main_chunk_feerate;
452 LinearizationIndex m_main_lin_index;
453 };
454
456 std::vector<Entry> m_entries;
457
459 std::vector<GraphIndex> m_unlinked;
460
461public:
463 explicit TxGraphImpl(DepGraphIndex max_cluster_count, uint64_t max_cluster_size, uint64_t acceptable_iters) noexcept :
464 m_max_cluster_count(max_cluster_count),
465 m_max_cluster_size(max_cluster_size),
466 m_acceptable_iters(acceptable_iters),
467 m_main_chunkindex(ChunkOrder(this))
468 {
469 Assume(max_cluster_count >= 1);
470 Assume(max_cluster_count <= MAX_CLUSTER_COUNT_LIMIT);
471 }
472
474 ~TxGraphImpl() noexcept;
475
476 // Cannot move or copy (would invalidate TxGraphImpl* in Ref, MiningOrder, EvictionOrder).
477 TxGraphImpl(const TxGraphImpl&) = delete;
478 TxGraphImpl& operator=(const TxGraphImpl&) = delete;
479 TxGraphImpl(TxGraphImpl&&) = delete;
480 TxGraphImpl& operator=(TxGraphImpl&&) = delete;
481
482 // Simple helper functions.
483
485 void SwapIndexes(GraphIndex a, GraphIndex b) noexcept;
488 Cluster* FindCluster(GraphIndex idx, int level) const noexcept;
490 std::unique_ptr<Cluster> ExtractCluster(int level, QualityLevel quality, ClusterSetIndex setindex) noexcept;
492 void DeleteCluster(Cluster& cluster) noexcept;
494 ClusterSetIndex InsertCluster(int level, std::unique_ptr<Cluster>&& cluster, QualityLevel quality) noexcept;
496 void SetClusterQuality(int level, QualityLevel old_quality, ClusterSetIndex old_index, QualityLevel new_quality) noexcept;
498 int GetTopLevel() const noexcept { return m_staging_clusterset.has_value(); }
500 int GetSpecifiedLevel(bool main_only) const noexcept { return m_staging_clusterset.has_value() && !main_only; }
502 ClusterSet& GetClusterSet(int level) noexcept;
503 const ClusterSet& GetClusterSet(int level) const noexcept;
507 void ClearLocator(int level, GraphIndex index, bool oversized_tx) noexcept;
509 std::vector<Cluster*> GetConflicts() const noexcept;
511 void ClearChunkData(Entry& entry) noexcept;
513 void CreateChunkData(GraphIndex idx, LinearizationIndex chunk_count) noexcept;
514
515 // Functions for handling Refs.
516
518 void UpdateRef(GraphIndex idx, Ref& new_location) noexcept final
519 {
520 auto& entry = m_entries[idx];
521 Assume(entry.m_ref != nullptr);
522 entry.m_ref = &new_location;
523 }
524
526 void UnlinkRef(GraphIndex idx) noexcept final
527 {
528 auto& entry = m_entries[idx];
529 Assume(entry.m_ref != nullptr);
530 Assume(m_main_chunkindex_observers == 0 || !entry.m_locator[0].IsPresent());
531 entry.m_ref = nullptr;
532 // Mark the transaction as to be removed in all levels where it explicitly or implicitly
533 // exists.
534 bool exists_anywhere{false};
535 bool exists{false};
536 for (int level = 0; level <= GetTopLevel(); ++level) {
537 if (entry.m_locator[level].IsPresent()) {
538 exists_anywhere = true;
539 exists = true;
540 } else if (entry.m_locator[level].IsRemoved()) {
541 exists = false;
542 }
543 if (exists) {
544 auto& clusterset = GetClusterSet(level);
545 clusterset.m_to_remove.push_back(idx);
546 // Force recomputation of grouping data.
547 clusterset.m_group_data = std::nullopt;
548 // Do not wipe the oversized state of main if staging exists. The reason for this
549 // is that the alternative would mean that cluster merges may need to be applied to
550 // a formerly-oversized main graph while staging exists (to satisfy chunk feerate
551 // queries into main, for example), and such merges could conflict with pulls of
552 // some of their constituents into staging.
553 if (level == GetTopLevel() && clusterset.m_oversized == true) {
554 clusterset.m_oversized = std::nullopt;
555 }
556 }
557 }
558 m_unlinked.push_back(idx);
559 if (!exists_anywhere) Compact();
560 }
561
562 // Functions related to various normalization/application steps.
566 void Compact() noexcept;
570 Cluster* PullIn(Cluster* cluster) noexcept;
573 void ApplyRemovals(int up_to_level) noexcept;
575 void Split(Cluster& cluster) noexcept;
577 void SplitAll(int up_to_level) noexcept;
579 void GroupClusters(int level) noexcept;
581 void Merge(std::span<Cluster*> to_merge) noexcept;
583 void ApplyDependencies(int level) noexcept;
585 void MakeAcceptable(Cluster& cluster) noexcept;
587 void MakeAllAcceptable(int level) noexcept;
588
589 // Implementations for the public TxGraph interface.
590
591 Ref AddTransaction(const FeePerWeight& feerate) noexcept final;
592 void RemoveTransaction(const Ref& arg) noexcept final;
593 void AddDependency(const Ref& parent, const Ref& child) noexcept final;
594 void SetTransactionFee(const Ref&, int64_t fee) noexcept final;
595
596 bool DoWork(uint64_t iters) noexcept final;
597
598 void StartStaging() noexcept final;
599 void CommitStaging() noexcept final;
600 void AbortStaging() noexcept final;
601 bool HaveStaging() const noexcept final { return m_staging_clusterset.has_value(); }
602
603 bool Exists(const Ref& arg, bool main_only = false) noexcept final;
604 FeePerWeight GetMainChunkFeerate(const Ref& arg) noexcept final;
605 FeePerWeight GetIndividualFeerate(const Ref& arg) noexcept final;
606 std::vector<Ref*> GetCluster(const Ref& arg, bool main_only = false) noexcept final;
607 std::vector<Ref*> GetAncestors(const Ref& arg, bool main_only = false) noexcept final;
608 std::vector<Ref*> GetDescendants(const Ref& arg, bool main_only = false) noexcept final;
609 std::vector<Ref*> GetAncestorsUnion(std::span<const Ref* const> args, bool main_only = false) noexcept final;
610 std::vector<Ref*> GetDescendantsUnion(std::span<const Ref* const> args, bool main_only = false) noexcept final;
611 GraphIndex GetTransactionCount(bool main_only = false) noexcept final;
612 bool IsOversized(bool main_only = false) noexcept final;
613 std::strong_ordering CompareMainOrder(const Ref& a, const Ref& b) noexcept final;
614 GraphIndex CountDistinctClusters(std::span<const Ref* const> refs, bool main_only = false) noexcept final;
615 std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>> GetMainStagingDiagrams() noexcept final;
616 std::vector<Ref*> Trim() noexcept final;
617
618 std::unique_ptr<BlockBuilder> GetBlockBuilder() noexcept final;
619 std::pair<std::vector<Ref*>, FeePerWeight> GetWorstMainChunk() noexcept final;
620
621 void SanityCheck() const final;
622};
623
624TxGraphImpl::ClusterSet& TxGraphImpl::GetClusterSet(int level) noexcept
625{
626 if (level == 0) return m_main_clusterset;
627 Assume(level == 1);
628 Assume(m_staging_clusterset.has_value());
629 return *m_staging_clusterset;
630}
631
632const TxGraphImpl::ClusterSet& TxGraphImpl::GetClusterSet(int level) const noexcept
633{
634 if (level == 0) return m_main_clusterset;
635 Assume(level == 1);
636 Assume(m_staging_clusterset.has_value());
637 return *m_staging_clusterset;
638}
639
641class BlockBuilderImpl final : public TxGraph::BlockBuilder
642{
645 TxGraphImpl* const m_graph;
647 std::set<Cluster*> m_excluded_clusters;
649 TxGraphImpl::ChunkIndex::const_iterator m_cur_iter;
652 Cluster* m_cur_cluster;
654 bool m_known_end_of_cluster;
655
656 // Move m_cur_iter / m_cur_cluster to the next acceptable chunk.
657 void Next() noexcept;
658
659public:
661 BlockBuilderImpl(TxGraphImpl& graph) noexcept;
662
663 // Implement the public interface.
664 ~BlockBuilderImpl() final;
665 std::optional<std::pair<std::vector<TxGraph::Ref*>, FeePerWeight>> GetCurrentChunk() noexcept final;
666 void Include() noexcept final;
667 void Skip() noexcept final;
668};
669
670void TxGraphImpl::ClearChunkData(Entry& entry) noexcept
671{
672 if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end()) {
673 Assume(m_main_chunkindex_observers == 0);
674 // If the Entry has a non-empty m_main_chunkindex_iterator, extract it, and move the handle
675 // to the cache of discarded chunkindex entries.
676 m_main_chunkindex_discarded.emplace_back(m_main_chunkindex.extract(entry.m_main_chunkindex_iterator));
677 entry.m_main_chunkindex_iterator = m_main_chunkindex.end();
678 }
679}
680
681void TxGraphImpl::CreateChunkData(GraphIndex idx, LinearizationIndex chunk_count) noexcept
682{
683 auto& entry = m_entries[idx];
684 if (!m_main_chunkindex_discarded.empty()) {
685 // Reuse an discarded node handle.
686 auto& node = m_main_chunkindex_discarded.back().value();
687 node.m_graph_index = idx;
688 node.m_chunk_count = chunk_count;
689 auto insert_result = m_main_chunkindex.insert(std::move(m_main_chunkindex_discarded.back()));
690 Assume(insert_result.inserted);
691 entry.m_main_chunkindex_iterator = insert_result.position;
692 m_main_chunkindex_discarded.pop_back();
693 } else {
694 // Construct a new entry.
695 auto emplace_result = m_main_chunkindex.emplace(idx, chunk_count);
696 Assume(emplace_result.second);
697 entry.m_main_chunkindex_iterator = emplace_result.first;
698 }
699}
700
701uint64_t Cluster::GetTotalTxSize() const noexcept
702{
703 uint64_t ret{0};
704 for (auto i : m_linearization) {
705 ret += m_depgraph.FeeRate(i).size;
706 }
707 return ret;
708}
709
710void TxGraphImpl::ClearLocator(int level, GraphIndex idx, bool oversized_tx) noexcept
711{
712 auto& entry = m_entries[idx];
713 auto& clusterset = GetClusterSet(level);
714 Assume(entry.m_locator[level].IsPresent());
715 // Change the locator from Present to Missing or Removed.
716 if (level == 0 || !entry.m_locator[level - 1].IsPresent()) {
717 entry.m_locator[level].SetMissing();
718 } else {
719 entry.m_locator[level].SetRemoved();
720 clusterset.m_removed.push_back(idx);
721 }
722 // Update the transaction count.
723 --clusterset.m_txcount;
724 clusterset.m_txcount_oversized -= oversized_tx;
725 // If clearing main, adjust the status of Locators of this transaction in staging, if it exists.
726 if (level == 0 && GetTopLevel() == 1) {
727 if (entry.m_locator[1].IsRemoved()) {
728 entry.m_locator[1].SetMissing();
729 } else if (!entry.m_locator[1].IsPresent()) {
730 --m_staging_clusterset->m_txcount;
731 m_staging_clusterset->m_txcount_oversized -= oversized_tx;
732 }
733 }
734 if (level == 0) ClearChunkData(entry);
735}
736
737void Cluster::Updated(TxGraphImpl& graph) noexcept
738{
739 // Update all the Locators for this Cluster's Entry objects.
740 for (DepGraphIndex idx : m_linearization) {
741 auto& entry = graph.m_entries[m_mapping[idx]];
742 // Discard any potential ChunkData prior to modifying the Cluster (as that could
743 // invalidate its ordering).
744 if (m_level == 0) graph.ClearChunkData(entry);
745 entry.m_locator[m_level].SetPresent(this, idx);
746 }
747 // If this is for the main graph (level = 0), and the Cluster's quality is ACCEPTABLE or
748 // OPTIMAL, compute its chunking and store its information in the Entry's m_main_lin_index
749 // and m_main_chunk_feerate. These fields are only accessed after making the entire graph
750 // ACCEPTABLE, so it is pointless to compute these if we haven't reached that quality level
751 // yet.
752 if (m_level == 0 && IsAcceptable()) {
753 const LinearizationChunking chunking(m_depgraph, m_linearization);
754 LinearizationIndex lin_idx{0};
755 // Iterate over the chunks.
756 for (unsigned chunk_idx = 0; chunk_idx < chunking.NumChunksLeft(); ++chunk_idx) {
757 auto chunk = chunking.GetChunk(chunk_idx);
758 auto chunk_count = chunk.transactions.Count();
759 Assume(chunk_count > 0);
760 // Iterate over the transactions in the linearization, which must match those in chunk.
761 while (true) {
762 DepGraphIndex idx = m_linearization[lin_idx];
763 GraphIndex graph_idx = m_mapping[idx];
764 auto& entry = graph.m_entries[graph_idx];
765 entry.m_main_lin_index = lin_idx++;
766 entry.m_main_chunk_feerate = FeePerWeight::FromFeeFrac(chunk.feerate);
767 Assume(chunk.transactions[idx]);
768 chunk.transactions.Reset(idx);
769 if (chunk.transactions.None()) {
770 // Last transaction in the chunk.
771 if (chunk_count == 1 && chunk_idx + 1 == chunking.NumChunksLeft()) {
772 // If this is the final chunk of the cluster, and it contains just a single
773 // transaction (which will always be true for the very common singleton
774 // clusters), store the special value -1 as chunk count.
775 chunk_count = LinearizationIndex(-1);
776 }
777 graph.CreateChunkData(graph_idx, chunk_count);
778 break;
779 }
780 }
781 }
782 }
783}
784
785void Cluster::GetConflicts(const TxGraphImpl& graph, std::vector<Cluster*>& out) const noexcept
786{
787 Assume(m_level == 1);
788 for (auto i : m_linearization) {
789 auto& entry = graph.m_entries[m_mapping[i]];
790 // For every transaction Entry in this Cluster, if it also exists in a lower-level Cluster,
791 // then that Cluster conflicts.
792 if (entry.m_locator[0].IsPresent()) {
793 out.push_back(entry.m_locator[0].cluster);
794 }
795 }
796}
797
798std::vector<Cluster*> TxGraphImpl::GetConflicts() const noexcept
799{
800 Assume(GetTopLevel() == 1);
801 auto& clusterset = GetClusterSet(1);
802 std::vector<Cluster*> ret;
803 // All main Clusters containing transactions in m_removed (so (P,R) ones) are conflicts.
804 for (auto i : clusterset.m_removed) {
805 auto& entry = m_entries[i];
806 if (entry.m_locator[0].IsPresent()) {
807 ret.push_back(entry.m_locator[0].cluster);
808 }
809 }
810 // Then go over all Clusters at this level, and find their conflicts (the (P,P) ones).
811 for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
812 auto& clusters = clusterset.m_clusters[quality];
813 for (const auto& cluster : clusters) {
814 cluster->GetConflicts(*this, ret);
815 }
816 }
817 // Deduplicate the result (the same Cluster may appear multiple times).
818 std::sort(ret.begin(), ret.end(), [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
819 ret.erase(std::unique(ret.begin(), ret.end()), ret.end());
820 return ret;
821}
822
823Cluster* Cluster::CopyToStaging(TxGraphImpl& graph) const noexcept
824{
825 // Construct an empty Cluster.
826 auto ret = std::make_unique<Cluster>(graph.m_next_sequence_counter++);
827 auto ptr = ret.get();
828 // Copy depgraph, mapping, and linearization/
829 ptr->m_depgraph = m_depgraph;
830 ptr->m_mapping = m_mapping;
831 ptr->m_linearization = m_linearization;
832 // Insert the new Cluster into the graph.
833 graph.InsertCluster(1, std::move(ret), m_quality);
834 // Update its Locators.
835 ptr->Updated(graph);
836 return ptr;
837}
838
839void Cluster::ApplyRemovals(TxGraphImpl& graph, std::span<GraphIndex>& to_remove) noexcept
840{
841 // Iterate over the prefix of to_remove that applies to this cluster.
842 Assume(!to_remove.empty());
843 SetType todo;
844 do {
845 GraphIndex idx = to_remove.front();
846 Assume(idx < graph.m_entries.size());
847 auto& entry = graph.m_entries[idx];
848 auto& locator = entry.m_locator[m_level];
849 // Stop once we hit an entry that applies to another Cluster.
850 if (locator.cluster != this) break;
851 // - Remember it in a set of to-remove DepGraphIndexes.
852 todo.Set(locator.index);
853 // - Remove from m_mapping. This isn't strictly necessary as unused positions in m_mapping
854 // are just never accessed, but set it to -1 here to increase the ability to detect a bug
855 // that causes it to be accessed regardless.
856 m_mapping[locator.index] = GraphIndex(-1);
857 // - Remove its linearization index from the Entry (if in main).
858 if (m_level == 0) {
859 entry.m_main_lin_index = LinearizationIndex(-1);
860 }
861 // - Mark it as missing/removed in the Entry's locator.
862 graph.ClearLocator(m_level, idx, m_quality == QualityLevel::OVERSIZED_SINGLETON);
863 to_remove = to_remove.subspan(1);
864 } while(!to_remove.empty());
865
866 auto quality = m_quality;
867 Assume(todo.Any());
868 // Wipe from the Cluster's DepGraph (this is O(n) regardless of the number of entries
869 // removed, so we benefit from batching all the removals).
870 m_depgraph.RemoveTransactions(todo);
871 m_mapping.resize(m_depgraph.PositionRange());
872
873 // First remove all removals at the end of the linearization.
874 while (!m_linearization.empty() && todo[m_linearization.back()]) {
875 todo.Reset(m_linearization.back());
876 m_linearization.pop_back();
877 }
878 if (todo.None()) {
879 // If no further removals remain, and thus all removals were at the end, we may be able
880 // to leave the cluster at a better quality level.
881 if (IsAcceptable(/*after_split=*/true)) {
882 quality = QualityLevel::NEEDS_SPLIT_ACCEPTABLE;
883 } else {
884 quality = QualityLevel::NEEDS_SPLIT;
885 }
886 } else {
887 // If more removals remain, filter those out of m_linearization.
888 m_linearization.erase(std::remove_if(
889 m_linearization.begin(),
890 m_linearization.end(),
891 [&](auto pos) { return todo[pos]; }), m_linearization.end());
892 quality = QualityLevel::NEEDS_SPLIT;
893 }
894 graph.SetClusterQuality(m_level, m_quality, m_setindex, quality);
895 Updated(graph);
896}
897
898void Cluster::Clear(TxGraphImpl& graph) noexcept
899{
900 for (auto i : m_linearization) {
901 graph.ClearLocator(m_level, m_mapping[i], m_quality == QualityLevel::OVERSIZED_SINGLETON);
902 }
903 m_depgraph = {};
904 m_linearization.clear();
905 m_mapping.clear();
906}
907
908void Cluster::MoveToMain(TxGraphImpl& graph) noexcept
909{
910 Assume(m_level == 1);
911 for (auto i : m_linearization) {
912 GraphIndex idx = m_mapping[i];
913 auto& entry = graph.m_entries[idx];
914 entry.m_locator[1].SetMissing();
915 }
916 auto quality = m_quality;
917 auto cluster = graph.ExtractCluster(1, quality, m_setindex);
918 graph.InsertCluster(0, std::move(cluster), quality);
919 Updated(graph);
920}
921
922void Cluster::AppendChunkFeerates(std::vector<FeeFrac>& ret) const noexcept
923{
924 auto chunk_feerates = ChunkLinearization(m_depgraph, m_linearization);
925 ret.reserve(ret.size() + chunk_feerates.size());
926 ret.insert(ret.end(), chunk_feerates.begin(), chunk_feerates.end());
927}
928
929uint64_t Cluster::AppendTrimData(std::vector<TrimTxData>& ret, std::vector<std::pair<GraphIndex, GraphIndex>>& deps) const noexcept
930{
931 const LinearizationChunking linchunking(m_depgraph, m_linearization);
932 LinearizationIndex pos{0};
933 uint64_t size{0};
934 auto prev_index = GraphIndex(-1);
935 // Iterate over the chunks of this cluster's linearization.
936 for (unsigned i = 0; i < linchunking.NumChunksLeft(); ++i) {
937 const auto& [chunk, chunk_feerate] = linchunking.GetChunk(i);
938 // Iterate over the transactions of that chunk, in linearization order.
939 auto chunk_tx_count = chunk.Count();
940 for (unsigned j = 0; j < chunk_tx_count; ++j) {
941 auto cluster_idx = m_linearization[pos];
942 // The transaction must appear in the chunk.
943 Assume(chunk[cluster_idx]);
944 // Construct a new element in ret.
945 auto& entry = ret.emplace_back();
946 entry.m_chunk_feerate = FeePerWeight::FromFeeFrac(chunk_feerate);
947 entry.m_index = m_mapping[cluster_idx];
948 // If this is not the first transaction of the cluster linearization, it has an
949 // implicit dependency on its predecessor.
950 if (pos != 0) deps.emplace_back(prev_index, entry.m_index);
951 prev_index = entry.m_index;
952 entry.m_tx_size = m_depgraph.FeeRate(cluster_idx).size;
953 size += entry.m_tx_size;
954 ++pos;
955 }
956 }
957 return size;
958}
959
960bool Cluster::Split(TxGraphImpl& graph) noexcept
961{
962 // This function can only be called when the Cluster needs splitting.
963 Assume(NeedsSplitting());
964 // Determine the new quality the split-off Clusters will have.
965 QualityLevel new_quality = IsAcceptable(/*after_split=*/true) ? QualityLevel::ACCEPTABLE
966 : QualityLevel::NEEDS_RELINEARIZE;
967 // If we're going to produce ACCEPTABLE clusters (i.e., when in NEEDS_SPLIT_ACCEPTABLE), we
968 // need to post-linearize to make sure the split-out versions are all connected (as
969 // connectivity may have changed by removing part of the cluster). This could be done on each
970 // resulting split-out cluster separately, but it is simpler to do it once up front before
971 // splitting. This step is not necessary if the resulting clusters are NEEDS_RELINEARIZE, as
972 // they will be post-linearized anyway in MakeAcceptable().
973 if (new_quality == QualityLevel::ACCEPTABLE) {
974 PostLinearize(m_depgraph, m_linearization);
975 }
977 auto todo = m_depgraph.Positions();
980 std::vector<std::pair<Cluster*, DepGraphIndex>> remap(m_depgraph.PositionRange());
981 std::vector<Cluster*> new_clusters;
982 bool first{true};
983 // Iterate over the connected components of this Cluster's m_depgraph.
984 while (todo.Any()) {
985 auto component = m_depgraph.FindConnectedComponent(todo);
986 auto split_quality = component.Count() <= 2 ? QualityLevel::OPTIMAL : new_quality;
987 if (first && component == todo) {
988 // The existing Cluster is an entire component. Leave it be, but update its quality.
989 Assume(todo == m_depgraph.Positions());
990 graph.SetClusterQuality(m_level, m_quality, m_setindex, split_quality);
991 // If this made the quality ACCEPTABLE or OPTIMAL, we need to compute and cache its
992 // chunking.
993 Updated(graph);
994 return false;
995 }
996 first = false;
997 // Construct a new Cluster to hold the found component.
998 auto new_cluster = std::make_unique<Cluster>(graph.m_next_sequence_counter++);
999 new_clusters.push_back(new_cluster.get());
1000 // Remember that all the component's transactions go to this new Cluster. The positions
1001 // will be determined below, so use -1 for now.
1002 for (auto i : component) {
1003 remap[i] = {new_cluster.get(), DepGraphIndex(-1)};
1004 }
1005 graph.InsertCluster(m_level, std::move(new_cluster), split_quality);
1006 todo -= component;
1007 }
1008 // Redistribute the transactions.
1009 for (auto i : m_linearization) {
1011 Cluster* new_cluster = remap[i].first;
1012 // Copy the transaction to the new cluster's depgraph, and remember the position.
1013 remap[i].second = new_cluster->m_depgraph.AddTransaction(m_depgraph.FeeRate(i));
1014 // Create new mapping entry.
1015 new_cluster->m_mapping.push_back(m_mapping[i]);
1016 // Create a new linearization entry. As we're only appending transactions, they equal the
1017 // DepGraphIndex.
1018 new_cluster->m_linearization.push_back(remap[i].second);
1019 }
1020 // Redistribute the dependencies.
1021 for (auto i : m_linearization) {
1023 Cluster* new_cluster = remap[i].first;
1024 // Copy its parents, translating positions.
1025 SetType new_parents;
1026 for (auto par : m_depgraph.GetReducedParents(i)) new_parents.Set(remap[par].second);
1027 new_cluster->m_depgraph.AddDependencies(new_parents, remap[i].second);
1028 }
1029 // Update all the Locators of moved transactions.
1030 for (Cluster* new_cluster : new_clusters) {
1031 new_cluster->Updated(graph);
1032 }
1033 // Wipe this Cluster, and return that it needs to be deleted.
1034 m_depgraph = DepGraph<SetType>{};
1035 m_mapping.clear();
1036 m_linearization.clear();
1037 return true;
1038}
1039
1040void Cluster::Merge(TxGraphImpl& graph, Cluster& other) noexcept
1041{
1043 std::vector<DepGraphIndex> remap(other.m_depgraph.PositionRange());
1044 // Iterate over all transactions in the other Cluster (the one being absorbed).
1045 for (auto pos : other.m_linearization) {
1046 auto idx = other.m_mapping[pos];
1047 // Copy the transaction into this Cluster, and remember its position.
1048 auto new_pos = m_depgraph.AddTransaction(other.m_depgraph.FeeRate(pos));
1049 remap[pos] = new_pos;
1050 if (new_pos == m_mapping.size()) {
1051 m_mapping.push_back(idx);
1052 } else {
1053 m_mapping[new_pos] = idx;
1054 }
1055 m_linearization.push_back(new_pos);
1056 // Copy the transaction's dependencies, translating them using remap. Note that since
1057 // pos iterates over other.m_linearization, which is in topological order, all parents
1058 // of pos should already be in remap.
1059 SetType parents;
1060 for (auto par : other.m_depgraph.GetReducedParents(pos)) {
1061 parents.Set(remap[par]);
1062 }
1063 m_depgraph.AddDependencies(parents, remap[pos]);
1064 // Update the transaction's Locator. There is no need to call Updated() to update chunk
1065 // feerates, as Updated() will be invoked by Cluster::ApplyDependencies on the resulting
1066 // merged Cluster later anyway).
1067 auto& entry = graph.m_entries[idx];
1068 // Discard any potential ChunkData prior to modifying the Cluster (as that could
1069 // invalidate its ordering).
1070 if (m_level == 0) graph.ClearChunkData(entry);
1071 entry.m_locator[m_level].SetPresent(this, new_pos);
1072 }
1073 // Purge the other Cluster, now that everything has been moved.
1074 other.m_depgraph = DepGraph<SetType>{};
1075 other.m_linearization.clear();
1076 other.m_mapping.clear();
1077}
1078
1079void Cluster::ApplyDependencies(TxGraphImpl& graph, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept
1080{
1081 // This function is invoked by TxGraphImpl::ApplyDependencies after merging groups of Clusters
1082 // between which dependencies are added, which simply concatenates their linearizations. Invoke
1083 // PostLinearize, which has the effect that the linearization becomes a merge-sort of the
1084 // constituent linearizations. Do this here rather than in Cluster::Merge, because this
1085 // function is only invoked once per merged Cluster, rather than once per constituent one.
1086 // This concatenation + post-linearization could be replaced with an explicit merge-sort.
1087 PostLinearize(m_depgraph, m_linearization);
1088
1089 // Sort the list of dependencies to apply by child, so those can be applied in batch.
1090 std::sort(to_apply.begin(), to_apply.end(), [](auto& a, auto& b) { return a.second < b.second; });
1091 // Iterate over groups of to-be-added dependencies with the same child.
1092 auto it = to_apply.begin();
1093 while (it != to_apply.end()) {
1094 auto& first_child = graph.m_entries[it->second].m_locator[m_level];
1095 const auto child_idx = first_child.index;
1096 // Iterate over all to-be-added dependencies within that same child, gather the relevant
1097 // parents.
1098 SetType parents;
1099 while (it != to_apply.end()) {
1100 auto& child = graph.m_entries[it->second].m_locator[m_level];
1101 auto& parent = graph.m_entries[it->first].m_locator[m_level];
1102 Assume(child.cluster == this && parent.cluster == this);
1103 if (child.index != child_idx) break;
1104 parents.Set(parent.index);
1105 ++it;
1106 }
1107 // Push all dependencies to the underlying DepGraph. Note that this is O(N) in the size of
1108 // the cluster, regardless of the number of parents being added, so batching them together
1109 // has a performance benefit.
1110 m_depgraph.AddDependencies(parents, child_idx);
1111 }
1112
1113 // Finally fix the linearization, as the new dependencies may have invalidated the
1114 // linearization, and post-linearize it to fix up the worst problems with it.
1115 FixLinearization(m_depgraph, m_linearization);
1116 PostLinearize(m_depgraph, m_linearization);
1117 Assume(!NeedsSplitting());
1118 Assume(!IsOversized());
1119 if (IsAcceptable()) {
1120 graph.SetClusterQuality(m_level, m_quality, m_setindex, QualityLevel::NEEDS_RELINEARIZE);
1121 }
1122
1123 // Finally push the changes to graph.m_entries.
1124 Updated(graph);
1125}
1126
1127TxGraphImpl::~TxGraphImpl() noexcept
1128{
1129 // If Refs outlive the TxGraphImpl they refer to, unlink them, so that their destructor does not
1130 // try to reach into a non-existing TxGraphImpl anymore.
1131 for (auto& entry : m_entries) {
1132 if (entry.m_ref != nullptr) {
1133 GetRefGraph(*entry.m_ref) = nullptr;
1134 }
1135 }
1136}
1137
1138std::unique_ptr<Cluster> TxGraphImpl::ExtractCluster(int level, QualityLevel quality, ClusterSetIndex setindex) noexcept
1139{
1140 Assume(quality != QualityLevel::NONE);
1141
1142 auto& clusterset = GetClusterSet(level);
1143 auto& quality_clusters = clusterset.m_clusters[int(quality)];
1144 Assume(setindex < quality_clusters.size());
1145
1146 // Extract the Cluster-owning unique_ptr.
1147 std::unique_ptr<Cluster> ret = std::move(quality_clusters[setindex]);
1148 ret->m_quality = QualityLevel::NONE;
1149 ret->m_setindex = ClusterSetIndex(-1);
1150 ret->m_level = -1;
1151
1152 // Clean up space in quality_cluster.
1153 auto max_setindex = quality_clusters.size() - 1;
1154 if (setindex != max_setindex) {
1155 // If the cluster was not the last element of quality_clusters, move that to take its place.
1156 quality_clusters.back()->m_setindex = setindex;
1157 quality_clusters.back()->m_level = level;
1158 quality_clusters[setindex] = std::move(quality_clusters.back());
1159 }
1160 // The last element of quality_clusters is now unused; drop it.
1161 quality_clusters.pop_back();
1162
1163 return ret;
1164}
1165
1166ClusterSetIndex TxGraphImpl::InsertCluster(int level, std::unique_ptr<Cluster>&& cluster, QualityLevel quality) noexcept
1167{
1168 // Cannot insert with quality level NONE (as that would mean not inserted).
1169 Assume(quality != QualityLevel::NONE);
1170 // The passed-in Cluster must not currently be in the TxGraphImpl.
1171 Assume(cluster->m_quality == QualityLevel::NONE);
1172
1173 // Append it at the end of the relevant TxGraphImpl::m_cluster.
1174 auto& clusterset = GetClusterSet(level);
1175 auto& quality_clusters = clusterset.m_clusters[int(quality)];
1176 ClusterSetIndex ret = quality_clusters.size();
1177 cluster->m_quality = quality;
1178 cluster->m_setindex = ret;
1179 cluster->m_level = level;
1180 quality_clusters.push_back(std::move(cluster));
1181 return ret;
1182}
1183
1184void TxGraphImpl::SetClusterQuality(int level, QualityLevel old_quality, ClusterSetIndex old_index, QualityLevel new_quality) noexcept
1185{
1186 Assume(new_quality != QualityLevel::NONE);
1187
1188 // Don't do anything if the quality did not change.
1189 if (old_quality == new_quality) return;
1190 // Extract the cluster from where it currently resides.
1191 auto cluster_ptr = ExtractCluster(level, old_quality, old_index);
1192 // And re-insert it where it belongs.
1193 InsertCluster(level, std::move(cluster_ptr), new_quality);
1194}
1195
1196void TxGraphImpl::DeleteCluster(Cluster& cluster) noexcept
1197{
1198 // Extract the cluster from where it currently resides.
1199 auto cluster_ptr = ExtractCluster(cluster.m_level, cluster.m_quality, cluster.m_setindex);
1200 // And throw it away.
1201 cluster_ptr.reset();
1202}
1203
1204Cluster* TxGraphImpl::FindCluster(GraphIndex idx, int level) const noexcept
1205{
1206 Assume(level >= 0 && level <= GetTopLevel());
1207 auto& entry = m_entries[idx];
1208 // Search the entry's locators from top to bottom.
1209 for (int l = level; l >= 0; --l) {
1210 // If the locator is missing, dig deeper; it may exist at a lower level and therefore be
1211 // implicitly existing at this level too.
1212 if (entry.m_locator[l].IsMissing()) continue;
1213 // If the locator has the entry marked as explicitly removed, stop.
1214 if (entry.m_locator[l].IsRemoved()) break;
1215 // Otherwise, we have found the topmost ClusterSet that contains this entry.
1216 return entry.m_locator[l].cluster;
1217 }
1218 // If no non-empty locator was found, or an explicitly removed was hit, return nothing.
1219 return nullptr;
1220}
1221
1222Cluster* TxGraphImpl::PullIn(Cluster* cluster) noexcept
1223{
1224 int to_level = GetTopLevel();
1225 Assume(to_level == 1);
1226 int level = cluster->m_level;
1227 Assume(level <= to_level);
1228 // Copy the Cluster from main to staging, if it's not already there.
1229 if (level == 0) {
1230 // Make the Cluster Acceptable before copying. This isn't strictly necessary, but doing it
1231 // now avoids doing double work later.
1232 MakeAcceptable(*cluster);
1233 cluster = cluster->CopyToStaging(*this);
1234 }
1235 return cluster;
1236}
1237
1238void TxGraphImpl::ApplyRemovals(int up_to_level) noexcept
1239{
1240 Assume(up_to_level >= 0 && up_to_level <= GetTopLevel());
1241 for (int level = 0; level <= up_to_level; ++level) {
1242 auto& clusterset = GetClusterSet(level);
1243 auto& to_remove = clusterset.m_to_remove;
1244 // Skip if there is nothing to remove in this level.
1245 if (to_remove.empty()) continue;
1246 // Pull in all Clusters that are not in staging.
1247 if (level == 1) {
1248 for (GraphIndex index : to_remove) {
1249 auto cluster = FindCluster(index, level);
1250 if (cluster != nullptr) PullIn(cluster);
1251 }
1252 }
1253 // Group the set of to-be-removed entries by Cluster::m_sequence.
1254 std::sort(to_remove.begin(), to_remove.end(), [&](GraphIndex a, GraphIndex b) noexcept {
1255 Cluster* cluster_a = m_entries[a].m_locator[level].cluster;
1256 Cluster* cluster_b = m_entries[b].m_locator[level].cluster;
1257 return CompareClusters(cluster_a, cluster_b) < 0;
1258 });
1259 // Process per Cluster.
1260 std::span to_remove_span{to_remove};
1261 while (!to_remove_span.empty()) {
1262 Cluster* cluster = m_entries[to_remove_span.front()].m_locator[level].cluster;
1263 if (cluster != nullptr) {
1264 // If the first to_remove_span entry's Cluster exists, hand to_remove_span to it, so it
1265 // can pop off whatever applies to it.
1266 cluster->ApplyRemovals(*this, to_remove_span);
1267 } else {
1268 // Otherwise, skip this already-removed entry. This may happen when
1269 // RemoveTransaction was called twice on the same Ref, for example.
1270 to_remove_span = to_remove_span.subspan(1);
1271 }
1272 }
1273 to_remove.clear();
1274 }
1275 Compact();
1276}
1277
1278void TxGraphImpl::SwapIndexes(GraphIndex a, GraphIndex b) noexcept
1279{
1280 Assume(a < m_entries.size());
1281 Assume(b < m_entries.size());
1282 // Swap the Entry objects.
1283 std::swap(m_entries[a], m_entries[b]);
1284 // Iterate over both objects.
1285 for (int i = 0; i < 2; ++i) {
1286 GraphIndex idx = i ? b : a;
1287 Entry& entry = m_entries[idx];
1288 // Update linked Ref, if any exists.
1289 if (entry.m_ref) GetRefIndex(*entry.m_ref) = idx;
1290 // Update linked chunk index entries, if any exist.
1291 if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end()) {
1292 entry.m_main_chunkindex_iterator->m_graph_index = idx;
1293 }
1294 // Update the locators for both levels. The rest of the Entry information will not change,
1295 // so no need to invoke Cluster::Updated().
1296 for (int level = 0; level < MAX_LEVELS; ++level) {
1297 Locator& locator = entry.m_locator[level];
1298 if (locator.IsPresent()) {
1299 locator.cluster->UpdateMapping(locator.index, idx);
1300 }
1301 }
1302 }
1303}
1304
1305void TxGraphImpl::Compact() noexcept
1306{
1307 // We cannot compact while any to-be-applied operations or staged removals remain as we'd need
1308 // to rewrite them. It is easier to delay the compaction until they have been applied.
1309 if (!m_main_clusterset.m_deps_to_add.empty()) return;
1310 if (!m_main_clusterset.m_to_remove.empty()) return;
1311 Assume(m_main_clusterset.m_removed.empty()); // non-staging m_removed is always empty
1312 if (m_staging_clusterset.has_value()) {
1313 if (!m_staging_clusterset->m_deps_to_add.empty()) return;
1314 if (!m_staging_clusterset->m_to_remove.empty()) return;
1315 if (!m_staging_clusterset->m_removed.empty()) return;
1316 }
1317
1318 // Release memory used by discarded ChunkData index entries.
1319 ClearShrink(m_main_chunkindex_discarded);
1320
1321 // Sort the GraphIndexes that need to be cleaned up. They are sorted in reverse, so the last
1322 // ones get processed first. This means earlier-processed GraphIndexes will not cause moving of
1323 // later-processed ones during the "swap with end of m_entries" step below (which might
1324 // invalidate them).
1325 std::sort(m_unlinked.begin(), m_unlinked.end(), std::greater{});
1326
1327 auto last = GraphIndex(-1);
1328 for (GraphIndex idx : m_unlinked) {
1329 // m_unlinked should never contain the same GraphIndex twice (the code below would fail
1330 // if so, because GraphIndexes get invalidated by removing them).
1331 Assume(idx != last);
1332 last = idx;
1333
1334 // Make sure the entry is unlinked.
1335 Entry& entry = m_entries[idx];
1336 Assume(entry.m_ref == nullptr);
1337 // Make sure the entry does not occur in the graph.
1338 for (int level = 0; level < MAX_LEVELS; ++level) {
1339 Assume(!entry.m_locator[level].IsPresent());
1340 }
1341
1342 // Move the entry to the end.
1343 if (idx != m_entries.size() - 1) SwapIndexes(idx, m_entries.size() - 1);
1344 // Drop the entry for idx, now that it is at the end.
1345 m_entries.pop_back();
1346 }
1347 m_unlinked.clear();
1348}
1349
1350void TxGraphImpl::Split(Cluster& cluster) noexcept
1351{
1352 // To split a Cluster, first make sure all removals are applied (as we might need to split
1353 // again afterwards otherwise).
1354 ApplyRemovals(cluster.m_level);
1355 bool del = cluster.Split(*this);
1356 if (del) {
1357 // Cluster::Split reports whether the Cluster is to be deleted.
1358 DeleteCluster(cluster);
1359 }
1360}
1361
1362void TxGraphImpl::SplitAll(int up_to_level) noexcept
1363{
1364 Assume(up_to_level >= 0 && up_to_level <= GetTopLevel());
1365 // Before splitting all Cluster, first make sure all removals are applied.
1366 ApplyRemovals(up_to_level);
1367 for (int level = 0; level <= up_to_level; ++level) {
1368 for (auto quality : {QualityLevel::NEEDS_SPLIT, QualityLevel::NEEDS_SPLIT_ACCEPTABLE}) {
1369 auto& queue = GetClusterSet(level).m_clusters[int(quality)];
1370 while (!queue.empty()) {
1371 Split(*queue.back().get());
1372 }
1373 }
1374 }
1375}
1376
1377void TxGraphImpl::GroupClusters(int level) noexcept
1378{
1379 auto& clusterset = GetClusterSet(level);
1380 // If the groupings have been computed already, nothing is left to be done.
1381 if (clusterset.m_group_data.has_value()) return;
1382
1383 // Before computing which Clusters need to be merged together, first apply all removals and
1384 // split the Clusters into connected components. If we would group first, we might end up
1385 // with inefficient and/or oversized Clusters which just end up being split again anyway.
1386 SplitAll(level);
1387
1391 std::vector<std::pair<Cluster*, uint64_t>> an_clusters;
1396 std::vector<std::pair<std::pair<GraphIndex, GraphIndex>, uint64_t>> an_deps;
1397
1398 // Construct an an_clusters entry for every oversized cluster, including ones from levels below,
1399 // as they may be inherited in this one.
1400 for (int level_iter = 0; level_iter <= level; ++level_iter) {
1401 for (auto& cluster : GetClusterSet(level_iter).m_clusters[int(QualityLevel::OVERSIZED_SINGLETON)]) {
1402 auto graph_idx = cluster->GetClusterEntry(0);
1403 auto cur_cluster = FindCluster(graph_idx, level);
1404 if (cur_cluster == nullptr) continue;
1405 an_clusters.emplace_back(cur_cluster, cur_cluster->m_sequence);
1406 }
1407 }
1408
1409 // Construct a an_clusters entry for every parent and child in the to-be-applied dependencies,
1410 // and an an_deps entry for each dependency to be applied.
1411 an_deps.reserve(clusterset.m_deps_to_add.size());
1412 for (const auto& [par, chl] : clusterset.m_deps_to_add) {
1413 auto par_cluster = FindCluster(par, level);
1414 auto chl_cluster = FindCluster(chl, level);
1415 // Skip dependencies for which the parent or child transaction is removed.
1416 if (par_cluster == nullptr || chl_cluster == nullptr) continue;
1417 an_clusters.emplace_back(par_cluster, par_cluster->m_sequence);
1418 // Do not include a duplicate when parent and child are identical, as it'll be removed
1419 // below anyway.
1420 if (chl_cluster != par_cluster) an_clusters.emplace_back(chl_cluster, chl_cluster->m_sequence);
1421 // Add entry to an_deps, using the child sequence number.
1422 an_deps.emplace_back(std::pair{par, chl}, chl_cluster->m_sequence);
1423 }
1424 // Sort and deduplicate an_clusters, so we end up with a sorted list of all involved Clusters
1425 // to which dependencies apply, or which are oversized.
1426 std::sort(an_clusters.begin(), an_clusters.end(), [](auto& a, auto& b) noexcept { return a.second < b.second; });
1427 an_clusters.erase(std::unique(an_clusters.begin(), an_clusters.end()), an_clusters.end());
1428 // Sort an_deps by applying the same order to the involved child cluster.
1429 std::sort(an_deps.begin(), an_deps.end(), [&](auto& a, auto& b) noexcept { return a.second < b.second; });
1430
1431 // Run the union-find algorithm to to find partitions of the input Clusters which need to be
1432 // grouped together. See https://en.wikipedia.org/wiki/Disjoint-set_data_structure.
1433 {
1435 struct PartitionData
1436 {
1438 uint64_t sequence;
1443 PartitionData* parent;
1446 unsigned rank;
1447 };
1449 std::vector<PartitionData> partition_data;
1450
1452 auto locate_fn = [&](uint64_t sequence) noexcept -> PartitionData* {
1453 auto it = std::lower_bound(partition_data.begin(), partition_data.end(), sequence,
1454 [](auto& a, uint64_t seq) noexcept { return a.sequence < seq; });
1455 Assume(it != partition_data.end());
1456 Assume(it->sequence == sequence);
1457 return &*it;
1458 };
1459
1461 static constexpr auto find_root_fn = [](PartitionData* data) noexcept -> PartitionData* {
1462 while (data->parent != data) {
1463 // Replace pointers to parents with pointers to grandparents.
1464 // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Finding_set_representatives.
1465 auto par = data->parent;
1466 data->parent = par->parent;
1467 data = par;
1468 }
1469 return data;
1470 };
1471
1474 static constexpr auto union_fn = [](PartitionData* arg1, PartitionData* arg2) noexcept {
1475 // Find the roots of the trees, and bail out if they are already equal (which would
1476 // mean they are in the same partition already).
1477 auto rep1 = find_root_fn(arg1);
1478 auto rep2 = find_root_fn(arg2);
1479 if (rep1 == rep2) return rep1;
1480 // Pick the lower-rank root to become a child of the higher-rank one.
1481 // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Union_by_rank.
1482 if (rep1->rank < rep2->rank) std::swap(rep1, rep2);
1483 rep2->parent = rep1;
1484 rep1->rank += (rep1->rank == rep2->rank);
1485 return rep1;
1486 };
1487
1488 // Start by initializing every Cluster as its own singleton partition.
1489 partition_data.resize(an_clusters.size());
1490 for (size_t i = 0; i < an_clusters.size(); ++i) {
1491 partition_data[i].sequence = an_clusters[i].first->m_sequence;
1492 partition_data[i].parent = &partition_data[i];
1493 partition_data[i].rank = 0;
1494 }
1495
1496 // Run through all parent/child pairs in an_deps, and union the partitions their Clusters
1497 // are in.
1498 Cluster* last_chl_cluster{nullptr};
1499 PartitionData* last_partition{nullptr};
1500 for (const auto& [dep, _] : an_deps) {
1501 auto [par, chl] = dep;
1502 auto par_cluster = FindCluster(par, level);
1503 auto chl_cluster = FindCluster(chl, level);
1504 Assume(chl_cluster != nullptr && par_cluster != nullptr);
1505 // Nothing to do if parent and child are in the same Cluster.
1506 if (par_cluster == chl_cluster) continue;
1507 Assume(par != chl);
1508 if (chl_cluster == last_chl_cluster) {
1509 // If the child Clusters is the same as the previous iteration, union with the
1510 // tree they were in, avoiding the need for another lookup. Note that an_deps
1511 // is sorted by child Cluster, so batches with the same child are expected.
1512 last_partition = union_fn(locate_fn(par_cluster->m_sequence), last_partition);
1513 } else {
1514 last_chl_cluster = chl_cluster;
1515 last_partition = union_fn(locate_fn(par_cluster->m_sequence), locate_fn(chl_cluster->m_sequence));
1516 }
1517 }
1518
1519 // Update the sequence numbers in an_clusters and an_deps to be those of the partition
1520 // representative.
1521 auto deps_it = an_deps.begin();
1522 for (size_t i = 0; i < partition_data.size(); ++i) {
1523 auto& data = partition_data[i];
1524 // Find the sequence of the representative of the partition Cluster i is in, and store
1525 // it with the Cluster.
1526 auto rep_seq = find_root_fn(&data)->sequence;
1527 an_clusters[i].second = rep_seq;
1528 // Find all dependencies whose child Cluster is Cluster i, and annotate them with rep.
1529 while (deps_it != an_deps.end()) {
1530 auto [par, chl] = deps_it->first;
1531 auto chl_cluster = FindCluster(chl, level);
1532 Assume(chl_cluster != nullptr);
1533 if (chl_cluster->m_sequence > data.sequence) break;
1534 deps_it->second = rep_seq;
1535 ++deps_it;
1536 }
1537 }
1538 }
1539
1540 // Sort both an_clusters and an_deps by sequence number of the representative of the
1541 // partition they are in, grouping all those applying to the same partition together.
1542 std::sort(an_deps.begin(), an_deps.end(), [](auto& a, auto& b) noexcept { return a.second < b.second; });
1543 std::sort(an_clusters.begin(), an_clusters.end(), [](auto& a, auto& b) noexcept { return a.second < b.second; });
1544
1545 // Translate the resulting cluster groups to the m_group_data structure, and the dependencies
1546 // back to m_deps_to_add.
1547 clusterset.m_group_data = GroupData{};
1548 clusterset.m_group_data->m_group_clusters.reserve(an_clusters.size());
1549 clusterset.m_deps_to_add.clear();
1550 clusterset.m_deps_to_add.reserve(an_deps.size());
1551 clusterset.m_oversized = false;
1552 auto an_deps_it = an_deps.begin();
1553 auto an_clusters_it = an_clusters.begin();
1554 while (an_clusters_it != an_clusters.end()) {
1555 // Process all clusters/dependencies belonging to the partition with representative rep.
1556 auto rep = an_clusters_it->second;
1557 // Create and initialize a new GroupData entry for the partition.
1558 auto& new_entry = clusterset.m_group_data->m_groups.emplace_back();
1559 new_entry.m_cluster_offset = clusterset.m_group_data->m_group_clusters.size();
1560 new_entry.m_cluster_count = 0;
1561 new_entry.m_deps_offset = clusterset.m_deps_to_add.size();
1562 new_entry.m_deps_count = 0;
1563 uint32_t total_count{0};
1564 uint64_t total_size{0};
1565 // Add all its clusters to it (copying those from an_clusters to m_group_clusters).
1566 while (an_clusters_it != an_clusters.end() && an_clusters_it->second == rep) {
1567 clusterset.m_group_data->m_group_clusters.push_back(an_clusters_it->first);
1568 total_count += an_clusters_it->first->GetTxCount();
1569 total_size += an_clusters_it->first->GetTotalTxSize();
1570 ++an_clusters_it;
1571 ++new_entry.m_cluster_count;
1572 }
1573 // Add all its dependencies to it (copying those back from an_deps to m_deps_to_add).
1574 while (an_deps_it != an_deps.end() && an_deps_it->second == rep) {
1575 clusterset.m_deps_to_add.push_back(an_deps_it->first);
1576 ++an_deps_it;
1577 ++new_entry.m_deps_count;
1578 }
1579 // Detect oversizedness.
1580 if (total_count > m_max_cluster_count || total_size > m_max_cluster_size) {
1581 clusterset.m_oversized = true;
1582 }
1583 }
1584 Assume(an_deps_it == an_deps.end());
1585 Assume(an_clusters_it == an_clusters.end());
1586 Compact();
1587}
1588
1589void TxGraphImpl::Merge(std::span<Cluster*> to_merge) noexcept
1590{
1591 Assume(!to_merge.empty());
1592 // Nothing to do if a group consists of just a single Cluster.
1593 if (to_merge.size() == 1) return;
1594
1595 // Move the largest Cluster to the front of to_merge. As all transactions in other to-be-merged
1596 // Clusters will be moved to that one, putting the largest one first minimizes the number of
1597 // moves.
1598 size_t max_size_pos{0};
1599 DepGraphIndex max_size = to_merge[max_size_pos]->GetTxCount();
1600 for (size_t i = 1; i < to_merge.size(); ++i) {
1601 DepGraphIndex size = to_merge[i]->GetTxCount();
1602 if (size > max_size) {
1603 max_size_pos = i;
1604 max_size = size;
1605 }
1606 }
1607 if (max_size_pos != 0) std::swap(to_merge[0], to_merge[max_size_pos]);
1608
1609 // Merge all further Clusters in the group into the first one, and delete them.
1610 for (size_t i = 1; i < to_merge.size(); ++i) {
1611 to_merge[0]->Merge(*this, *to_merge[i]);
1612 DeleteCluster(*to_merge[i]);
1613 }
1614}
1615
1616void TxGraphImpl::ApplyDependencies(int level) noexcept
1617{
1618 auto& clusterset = GetClusterSet(level);
1619 // Do not bother computing groups if we already know the result will be oversized.
1620 if (clusterset.m_oversized == true) return;
1621 // Compute the groups of to-be-merged Clusters (which also applies all removals, and splits).
1622 GroupClusters(level);
1623 Assume(clusterset.m_group_data.has_value());
1624 // Nothing to do if there are no dependencies to be added.
1625 if (clusterset.m_deps_to_add.empty()) return;
1626 // Dependencies cannot be applied if it would result in oversized clusters.
1627 if (clusterset.m_oversized == true) return;
1628
1629 // For each group of to-be-merged Clusters.
1630 for (const auto& group_entry : clusterset.m_group_data->m_groups) {
1631 auto cluster_span = std::span{clusterset.m_group_data->m_group_clusters}
1632 .subspan(group_entry.m_cluster_offset, group_entry.m_cluster_count);
1633 // Pull in all the Clusters that contain dependencies.
1634 if (level == 1) {
1635 for (Cluster*& cluster : cluster_span) {
1636 cluster = PullIn(cluster);
1637 }
1638 }
1639 // Invoke Merge() to merge them into a single Cluster.
1640 Merge(cluster_span);
1641 // Actually apply all to-be-added dependencies (all parents and children from this grouping
1642 // belong to the same Cluster at this point because of the merging above).
1643 auto deps_span = std::span{clusterset.m_deps_to_add}
1644 .subspan(group_entry.m_deps_offset, group_entry.m_deps_count);
1645 Assume(!deps_span.empty());
1646 const auto& loc = m_entries[deps_span[0].second].m_locator[level];
1647 Assume(loc.IsPresent());
1648 loc.cluster->ApplyDependencies(*this, deps_span);
1649 }
1650
1651 // Wipe the list of to-be-added dependencies now that they are applied.
1652 clusterset.m_deps_to_add.clear();
1653 Compact();
1654 // Also no further Cluster mergings are needed (note that we clear, but don't set to
1655 // std::nullopt, as that would imply the groupings are unknown).
1656 clusterset.m_group_data = GroupData{};
1657}
1658
1659std::pair<uint64_t, bool> Cluster::Relinearize(TxGraphImpl& graph, uint64_t max_iters) noexcept
1660{
1661 // We can only relinearize Clusters that do not need splitting.
1662 Assume(!NeedsSplitting());
1663 // No work is required for Clusters which are already optimally linearized.
1664 if (IsOptimal()) return {0, false};
1665 // Invoke the actual linearization algorithm (passing in the existing one).
1666 uint64_t rng_seed = graph.m_rng.rand64();
1667 auto [linearization, optimal, cost] = Linearize(m_depgraph, max_iters, rng_seed, m_linearization);
1668 // Postlinearize if the result isn't optimal already. This guarantees (among other things)
1669 // that the chunks of the resulting linearization are all connected.
1670 if (!optimal) PostLinearize(m_depgraph, linearization);
1671 // Update the linearization.
1672 m_linearization = std::move(linearization);
1673 // Update the Cluster's quality.
1674 bool improved = false;
1675 if (optimal) {
1676 graph.SetClusterQuality(m_level, m_quality, m_setindex, QualityLevel::OPTIMAL);
1677 improved = true;
1678 } else if (max_iters >= graph.m_acceptable_iters && !IsAcceptable()) {
1679 graph.SetClusterQuality(m_level, m_quality, m_setindex, QualityLevel::ACCEPTABLE);
1680 improved = true;
1681 }
1682 // Update the Entry objects.
1683 Updated(graph);
1684 return {cost, improved};
1685}
1686
1687void TxGraphImpl::MakeAcceptable(Cluster& cluster) noexcept
1688{
1689 // Relinearize the Cluster if needed.
1690 if (!cluster.NeedsSplitting() && !cluster.IsAcceptable() && !cluster.IsOversized()) {
1691 cluster.Relinearize(*this, m_acceptable_iters);
1692 }
1693}
1694
1695void TxGraphImpl::MakeAllAcceptable(int level) noexcept
1696{
1697 ApplyDependencies(level);
1698 auto& clusterset = GetClusterSet(level);
1699 if (clusterset.m_oversized == true) return;
1700 auto& queue = clusterset.m_clusters[int(QualityLevel::NEEDS_RELINEARIZE)];
1701 while (!queue.empty()) {
1702 MakeAcceptable(*queue.back().get());
1703 }
1704}
1705
1706Cluster::Cluster(uint64_t sequence) noexcept : m_sequence{sequence} {}
1707
1708Cluster::Cluster(uint64_t sequence, TxGraphImpl& graph, const FeePerWeight& feerate, GraphIndex graph_index) noexcept :
1709 m_sequence{sequence}
1710{
1711 // Create a new transaction in the DepGraph, and remember its position in m_mapping.
1712 auto cluster_idx = m_depgraph.AddTransaction(feerate);
1713 m_mapping.push_back(graph_index);
1714 m_linearization.push_back(cluster_idx);
1715}
1716
1717TxGraph::Ref TxGraphImpl::AddTransaction(const FeePerWeight& feerate) noexcept
1718{
1719 Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
1720 Assume(feerate.size > 0);
1721 // Construct a new Ref.
1722 Ref ret;
1723 // Construct a new Entry, and link it with the Ref.
1724 auto idx = m_entries.size();
1725 m_entries.emplace_back();
1726 auto& entry = m_entries.back();
1727 entry.m_main_chunkindex_iterator = m_main_chunkindex.end();
1728 entry.m_ref = &ret;
1729 GetRefGraph(ret) = this;
1730 GetRefIndex(ret) = idx;
1731 // Construct a new singleton Cluster (which is necessarily optimally linearized).
1732 bool oversized = uint64_t(feerate.size) > m_max_cluster_size;
1733 auto cluster = std::make_unique<Cluster>(m_next_sequence_counter++, *this, feerate, idx);
1734 auto cluster_ptr = cluster.get();
1735 int level = GetTopLevel();
1736 auto& clusterset = GetClusterSet(level);
1737 InsertCluster(level, std::move(cluster), oversized ? QualityLevel::OVERSIZED_SINGLETON : QualityLevel::OPTIMAL);
1738 cluster_ptr->Updated(*this);
1739 ++clusterset.m_txcount;
1740 // Deal with individually oversized transactions.
1741 if (oversized) {
1742 ++clusterset.m_txcount_oversized;
1743 clusterset.m_oversized = true;
1744 clusterset.m_group_data = std::nullopt;
1745 }
1746 // Return the Ref.
1747 return ret;
1748}
1749
1750void TxGraphImpl::RemoveTransaction(const Ref& arg) noexcept
1751{
1752 // Don't do anything if the Ref is empty (which may be indicative of the transaction already
1753 // having been removed).
1754 if (GetRefGraph(arg) == nullptr) return;
1755 Assume(GetRefGraph(arg) == this);
1756 Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
1757 // Find the Cluster the transaction is in, and stop if it isn't in any.
1758 int level = GetTopLevel();
1759 auto cluster = FindCluster(GetRefIndex(arg), level);
1760 if (cluster == nullptr) return;
1761 // Remember that the transaction is to be removed.
1762 auto& clusterset = GetClusterSet(level);
1763 clusterset.m_to_remove.push_back(GetRefIndex(arg));
1764 // Wipe m_group_data (as it will need to be recomputed).
1765 clusterset.m_group_data.reset();
1766 if (clusterset.m_oversized == true) clusterset.m_oversized = std::nullopt;
1767}
1768
1769void TxGraphImpl::AddDependency(const Ref& parent, const Ref& child) noexcept
1770{
1771 // Don't do anything if either Ref is empty (which may be indicative of it having already been
1772 // removed).
1773 if (GetRefGraph(parent) == nullptr || GetRefGraph(child) == nullptr) return;
1774 Assume(GetRefGraph(parent) == this && GetRefGraph(child) == this);
1775 Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0);
1776 // Don't do anything if this is a dependency on self.
1777 if (GetRefIndex(parent) == GetRefIndex(child)) return;
1778 // Find the Cluster the parent and child transaction are in, and stop if either appears to be
1779 // already removed.
1780 int level = GetTopLevel();
1781 auto par_cluster = FindCluster(GetRefIndex(parent), level);
1782 if (par_cluster == nullptr) return;
1783 auto chl_cluster = FindCluster(GetRefIndex(child), level);
1784 if (chl_cluster == nullptr) return;
1785 // Remember that this dependency is to be applied.
1786 auto& clusterset = GetClusterSet(level);
1787 clusterset.m_deps_to_add.emplace_back(GetRefIndex(parent), GetRefIndex(child));
1788 // Wipe m_group_data (as it will need to be recomputed).
1789 clusterset.m_group_data.reset();
1790 if (clusterset.m_oversized == false) clusterset.m_oversized = std::nullopt;
1791}
1792
1793bool TxGraphImpl::Exists(const Ref& arg, bool main_only) noexcept
1794{
1795 if (GetRefGraph(arg) == nullptr) return false;
1796 Assume(GetRefGraph(arg) == this);
1797 size_t level = GetSpecifiedLevel(main_only);
1798 // Make sure the transaction isn't scheduled for removal.
1799 ApplyRemovals(level);
1800 auto cluster = FindCluster(GetRefIndex(arg), level);
1801 return cluster != nullptr;
1802}
1803
1804void Cluster::GetAncestorRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept
1805{
1807 SetType ancestors_union;
1808 // Process elements from the front of args, as long as they apply.
1809 while (!args.empty()) {
1810 if (args.front().first != this) break;
1811 ancestors_union |= m_depgraph.Ancestors(args.front().second);
1812 args = args.subspan(1);
1813 }
1814 Assume(ancestors_union.Any());
1815 // Translate all ancestors (in arbitrary order) to Refs (if they have any), and return them.
1816 for (auto idx : ancestors_union) {
1817 const auto& entry = graph.m_entries[m_mapping[idx]];
1818 Assume(entry.m_ref != nullptr);
1819 output.push_back(entry.m_ref);
1820 }
1821}
1822
1823void Cluster::GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept
1824{
1826 SetType descendants_union;
1827 // Process elements from the front of args, as long as they apply.
1828 while (!args.empty()) {
1829 if (args.front().first != this) break;
1830 descendants_union |= m_depgraph.Descendants(args.front().second);
1831 args = args.subspan(1);
1832 }
1833 Assume(descendants_union.Any());
1834 // Translate all descendants (in arbitrary order) to Refs (if they have any), and return them.
1835 for (auto idx : descendants_union) {
1836 const auto& entry = graph.m_entries[m_mapping[idx]];
1837 Assume(entry.m_ref != nullptr);
1838 output.push_back(entry.m_ref);
1839 }
1840}
1841
1842bool Cluster::GetClusterRefs(TxGraphImpl& graph, std::span<TxGraph::Ref*> range, LinearizationIndex start_pos) noexcept
1843{
1844 // Translate the transactions in the Cluster (in linearization order, starting at start_pos in
1845 // the linearization) to Refs, and fill them in range.
1846 for (auto& ref : range) {
1847 Assume(start_pos < m_linearization.size());
1848 const auto& entry = graph.m_entries[m_mapping[m_linearization[start_pos++]]];
1849 Assume(entry.m_ref != nullptr);
1850 ref = entry.m_ref;
1851 }
1852 // Return whether start_pos has advanced to the end of the Cluster.
1853 return start_pos == m_linearization.size();
1854}
1855
1856FeePerWeight Cluster::GetIndividualFeerate(DepGraphIndex idx) noexcept
1857{
1858 return FeePerWeight::FromFeeFrac(m_depgraph.FeeRate(idx));
1859}
1860
1861void Cluster::MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept
1862{
1863 Assume(m_level == 1);
1864 // Mark all transactions of a Cluster missing, needed when aborting staging, so that the
1865 // corresponding Locators don't retain references into aborted Clusters.
1866 for (auto ci : m_linearization) {
1867 GraphIndex idx = m_mapping[ci];
1868 auto& entry = graph.m_entries[idx];
1869 entry.m_locator[1].SetMissing();
1870 }
1871}
1872
1873std::vector<TxGraph::Ref*> TxGraphImpl::GetAncestors(const Ref& arg, bool main_only) noexcept
1874{
1875 // Return the empty vector if the Ref is empty.
1876 if (GetRefGraph(arg) == nullptr) return {};
1877 Assume(GetRefGraph(arg) == this);
1878 // Apply all removals and dependencies, as the result might be incorrect otherwise.
1879 size_t level = GetSpecifiedLevel(main_only);
1880 ApplyDependencies(level);
1881 // Ancestry cannot be known if unapplied dependencies remain.
1882 Assume(GetClusterSet(level).m_deps_to_add.empty());
1883 // Find the Cluster the argument is in, and return the empty vector if it isn't in any.
1884 auto cluster = FindCluster(GetRefIndex(arg), level);
1885 if (cluster == nullptr) return {};
1886 // Dispatch to the Cluster.
1887 std::pair<Cluster*, DepGraphIndex> match = {cluster, m_entries[GetRefIndex(arg)].m_locator[cluster->m_level].index};
1888 auto matches = std::span(&match, 1);
1889 std::vector<TxGraph::Ref*> ret;
1890 cluster->GetAncestorRefs(*this, matches, ret);
1891 return ret;
1892}
1893
1894std::vector<TxGraph::Ref*> TxGraphImpl::GetDescendants(const Ref& arg, bool main_only) noexcept
1895{
1896 // Return the empty vector if the Ref is empty.
1897 if (GetRefGraph(arg) == nullptr) return {};
1898 Assume(GetRefGraph(arg) == this);
1899 // Apply all removals and dependencies, as the result might be incorrect otherwise.
1900 size_t level = GetSpecifiedLevel(main_only);
1901 ApplyDependencies(level);
1902 // Ancestry cannot be known if unapplied dependencies remain.
1903 Assume(GetClusterSet(level).m_deps_to_add.empty());
1904 // Find the Cluster the argument is in, and return the empty vector if it isn't in any.
1905 auto cluster = FindCluster(GetRefIndex(arg), level);
1906 if (cluster == nullptr) return {};
1907 // Dispatch to the Cluster.
1908 std::pair<Cluster*, DepGraphIndex> match = {cluster, m_entries[GetRefIndex(arg)].m_locator[cluster->m_level].index};
1909 auto matches = std::span(&match, 1);
1910 std::vector<TxGraph::Ref*> ret;
1911 cluster->GetDescendantRefs(*this, matches, ret);
1912 return ret;
1913}
1914
1915std::vector<TxGraph::Ref*> TxGraphImpl::GetAncestorsUnion(std::span<const Ref* const> args, bool main_only) noexcept
1916{
1917 // Apply all dependencies, as the result might be incorrect otherwise.
1918 size_t level = GetSpecifiedLevel(main_only);
1919 ApplyDependencies(level);
1920 // Ancestry cannot be known if unapplied dependencies remain.
1921 Assume(GetClusterSet(level).m_deps_to_add.empty());
1922
1923 // Translate args to matches.
1924 std::vector<std::pair<Cluster*, DepGraphIndex>> matches;
1925 matches.reserve(args.size());
1926 for (auto arg : args) {
1927 Assume(arg);
1928 // Skip empty Refs.
1929 if (GetRefGraph(*arg) == nullptr) continue;
1930 Assume(GetRefGraph(*arg) == this);
1931 // Find the Cluster the argument is in, and skip if none is found.
1932 auto cluster = FindCluster(GetRefIndex(*arg), level);
1933 if (cluster == nullptr) continue;
1934 // Append to matches.
1935 matches.emplace_back(cluster, m_entries[GetRefIndex(*arg)].m_locator[cluster->m_level].index);
1936 }
1937 // Group by Cluster.
1938 std::sort(matches.begin(), matches.end(), [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
1939 // Dispatch to the Clusters.
1940 std::span match_span(matches);
1941 std::vector<TxGraph::Ref*> ret;
1942 while (!match_span.empty()) {
1943 match_span.front().first->GetAncestorRefs(*this, match_span, ret);
1944 }
1945 return ret;
1946}
1947
1948std::vector<TxGraph::Ref*> TxGraphImpl::GetDescendantsUnion(std::span<const Ref* const> args, bool main_only) noexcept
1949{
1950 // Apply all dependencies, as the result might be incorrect otherwise.
1951 size_t level = GetSpecifiedLevel(main_only);
1952 ApplyDependencies(level);
1953 // Ancestry cannot be known if unapplied dependencies remain.
1954 Assume(GetClusterSet(level).m_deps_to_add.empty());
1955
1956 // Translate args to matches.
1957 std::vector<std::pair<Cluster*, DepGraphIndex>> matches;
1958 matches.reserve(args.size());
1959 for (auto arg : args) {
1960 Assume(arg);
1961 // Skip empty Refs.
1962 if (GetRefGraph(*arg) == nullptr) continue;
1963 Assume(GetRefGraph(*arg) == this);
1964 // Find the Cluster the argument is in, and skip if none is found.
1965 auto cluster = FindCluster(GetRefIndex(*arg), level);
1966 if (cluster == nullptr) continue;
1967 // Append to matches.
1968 matches.emplace_back(cluster, m_entries[GetRefIndex(*arg)].m_locator[cluster->m_level].index);
1969 }
1970 // Group by Cluster.
1971 std::sort(matches.begin(), matches.end(), [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
1972 // Dispatch to the Clusters.
1973 std::span match_span(matches);
1974 std::vector<TxGraph::Ref*> ret;
1975 while (!match_span.empty()) {
1976 match_span.front().first->GetDescendantRefs(*this, match_span, ret);
1977 }
1978 return ret;
1979}
1980
1981std::vector<TxGraph::Ref*> TxGraphImpl::GetCluster(const Ref& arg, bool main_only) noexcept
1982{
1983 // Return the empty vector if the Ref is empty (which may be indicative of the transaction
1984 // having been removed already.
1985 if (GetRefGraph(arg) == nullptr) return {};
1986 Assume(GetRefGraph(arg) == this);
1987 // Apply all removals and dependencies, as the result might be incorrect otherwise.
1988 size_t level = GetSpecifiedLevel(main_only);
1989 ApplyDependencies(level);
1990 // Cluster linearization cannot be known if unapplied dependencies remain.
1991 Assume(GetClusterSet(level).m_deps_to_add.empty());
1992 // Find the Cluster the argument is in, and return the empty vector if it isn't in any.
1993 auto cluster = FindCluster(GetRefIndex(arg), level);
1994 if (cluster == nullptr) return {};
1995 // Make sure the Cluster has an acceptable quality level, and then dispatch to it.
1996 MakeAcceptable(*cluster);
1997 std::vector<TxGraph::Ref*> ret(cluster->GetTxCount());
1998 cluster->GetClusterRefs(*this, ret, 0);
1999 return ret;
2000}
2001
2002TxGraph::GraphIndex TxGraphImpl::GetTransactionCount(bool main_only) noexcept
2003{
2004 size_t level = GetSpecifiedLevel(main_only);
2005 ApplyRemovals(level);
2006 return GetClusterSet(level).m_txcount;
2007}
2008
2009FeePerWeight TxGraphImpl::GetIndividualFeerate(const Ref& arg) noexcept
2010{
2011 // Return the empty FeePerWeight if the passed Ref is empty.
2012 if (GetRefGraph(arg) == nullptr) return {};
2013 Assume(GetRefGraph(arg) == this);
2014 // Find the cluster the argument is in (the level does not matter as individual feerates will
2015 // be identical if it occurs in both), and return the empty FeePerWeight if it isn't in any.
2016 Cluster* cluster{nullptr};
2017 for (int level = 0; level <= GetTopLevel(); ++level) {
2018 // Apply removals, so that we can correctly report FeePerWeight{} for non-existing
2019 // transactions.
2020 ApplyRemovals(level);
2021 if (m_entries[GetRefIndex(arg)].m_locator[level].IsPresent()) {
2022 cluster = m_entries[GetRefIndex(arg)].m_locator[level].cluster;
2023 break;
2024 }
2025 }
2026 if (cluster == nullptr) return {};
2027 // Dispatch to the Cluster.
2028 return cluster->GetIndividualFeerate(m_entries[GetRefIndex(arg)].m_locator[cluster->m_level].index);
2029}
2030
2031FeePerWeight TxGraphImpl::GetMainChunkFeerate(const Ref& arg) noexcept
2032{
2033 // Return the empty FeePerWeight if the passed Ref is empty.
2034 if (GetRefGraph(arg) == nullptr) return {};
2035 Assume(GetRefGraph(arg) == this);
2036 // Apply all removals and dependencies, as the result might be inaccurate otherwise.
2037 ApplyDependencies(/*level=*/0);
2038 // Chunk feerates cannot be accurately known if unapplied dependencies remain.
2039 Assume(m_main_clusterset.m_deps_to_add.empty());
2040 // Find the cluster the argument is in, and return the empty FeePerWeight if it isn't in any.
2041 auto cluster = FindCluster(GetRefIndex(arg), 0);
2042 if (cluster == nullptr) return {};
2043 // Make sure the Cluster has an acceptable quality level, and then return the transaction's
2044 // chunk feerate.
2045 MakeAcceptable(*cluster);
2046 const auto& entry = m_entries[GetRefIndex(arg)];
2047 return entry.m_main_chunk_feerate;
2048}
2049
2050bool TxGraphImpl::IsOversized(bool main_only) noexcept
2051{
2052 size_t level = GetSpecifiedLevel(main_only);
2053 auto& clusterset = GetClusterSet(level);
2054 if (clusterset.m_oversized.has_value()) {
2055 // Return cached value if known.
2056 return *clusterset.m_oversized;
2057 }
2058 ApplyRemovals(level);
2059 if (clusterset.m_txcount_oversized > 0) {
2060 clusterset.m_oversized = true;
2061 } else {
2062 // Find which Clusters will need to be merged together, as that is where the oversize
2063 // property is assessed.
2064 GroupClusters(level);
2065 }
2066 Assume(clusterset.m_oversized.has_value());
2067 return *clusterset.m_oversized;
2068}
2069
2070void TxGraphImpl::StartStaging() noexcept
2071{
2072 // Staging cannot already exist.
2073 Assume(!m_staging_clusterset.has_value());
2074 // Apply all remaining dependencies in main before creating a staging graph. Once staging
2075 // exists, we cannot merge Clusters anymore (because of interference with Clusters being
2076 // pulled into staging), so to make sure all inspectors are available (if not oversized), do
2077 // all merging work now. Call SplitAll() first, so that even if ApplyDependencies does not do
2078 // any thing due to knowing the result is oversized, splitting is still performed.
2079 SplitAll(0);
2080 ApplyDependencies(0);
2081 // Construct the staging ClusterSet.
2082 m_staging_clusterset.emplace();
2083 // Copy statistics, precomputed data, and to-be-applied dependencies (only if oversized) to
2084 // the new graph. To-be-applied removals will always be empty at this point.
2085 m_staging_clusterset->m_txcount = m_main_clusterset.m_txcount;
2086 m_staging_clusterset->m_txcount_oversized = m_main_clusterset.m_txcount_oversized;
2087 m_staging_clusterset->m_deps_to_add = m_main_clusterset.m_deps_to_add;
2088 m_staging_clusterset->m_group_data = m_main_clusterset.m_group_data;
2089 m_staging_clusterset->m_oversized = m_main_clusterset.m_oversized;
2090 Assume(m_staging_clusterset->m_oversized.has_value());
2091}
2092
2093void TxGraphImpl::AbortStaging() noexcept
2094{
2095 // Staging must exist.
2096 Assume(m_staging_clusterset.has_value());
2097 // Mark all removed transactions as Missing (so the staging locator for these transactions
2098 // can be reused if another staging is created).
2099 for (auto idx : m_staging_clusterset->m_removed) {
2100 m_entries[idx].m_locator[1].SetMissing();
2101 }
2102 // Do the same with the non-removed transactions in staging Clusters.
2103 for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
2104 for (auto& cluster : m_staging_clusterset->m_clusters[quality]) {
2105 cluster->MakeStagingTransactionsMissing(*this);
2106 }
2107 }
2108 // Destroy the staging ClusterSet.
2109 m_staging_clusterset.reset();
2110 Compact();
2111 if (!m_main_clusterset.m_group_data.has_value()) {
2112 // In case m_oversized in main was kept after a Ref destruction while staging exists, we
2113 // need to re-evaluate m_oversized now.
2114 if (m_main_clusterset.m_to_remove.empty() && m_main_clusterset.m_txcount_oversized > 0) {
2115 // It is possible that a Ref destruction caused a removal in main while staging existed.
2116 // In this case, m_txcount_oversized may be inaccurate.
2117 m_main_clusterset.m_oversized = true;
2118 } else {
2119 m_main_clusterset.m_oversized = std::nullopt;
2120 }
2121 }
2122}
2123
2124void TxGraphImpl::CommitStaging() noexcept
2125{
2126 // Staging must exist.
2127 Assume(m_staging_clusterset.has_value());
2128 Assume(m_main_chunkindex_observers == 0);
2129 // Delete all conflicting Clusters in main, to make place for moving the staging ones
2130 // there. All of these have been copied to staging in PullIn().
2131 auto conflicts = GetConflicts();
2132 for (Cluster* conflict : conflicts) {
2133 conflict->Clear(*this);
2134 DeleteCluster(*conflict);
2135 }
2136 // Mark the removed transactions as Missing (so the staging locator for these transactions
2137 // can be reused if another staging is created).
2138 for (auto idx : m_staging_clusterset->m_removed) {
2139 m_entries[idx].m_locator[1].SetMissing();
2140 }
2141 // Then move all Clusters in staging to main.
2142 for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
2143 auto& stage_sets = m_staging_clusterset->m_clusters[quality];
2144 while (!stage_sets.empty()) {
2145 stage_sets.back()->MoveToMain(*this);
2146 }
2147 }
2148 // Move all statistics, precomputed data, and to-be-applied removals and dependencies.
2149 m_main_clusterset.m_deps_to_add = std::move(m_staging_clusterset->m_deps_to_add);
2150 m_main_clusterset.m_to_remove = std::move(m_staging_clusterset->m_to_remove);
2151 m_main_clusterset.m_group_data = std::move(m_staging_clusterset->m_group_data);
2152 m_main_clusterset.m_oversized = std::move(m_staging_clusterset->m_oversized);
2153 m_main_clusterset.m_txcount = std::move(m_staging_clusterset->m_txcount);
2154 m_main_clusterset.m_txcount_oversized = std::move(m_staging_clusterset->m_txcount_oversized);
2155 // Delete the old staging graph, after all its information was moved to main.
2156 m_staging_clusterset.reset();
2157 Compact();
2158}
2159
2160void Cluster::SetFee(TxGraphImpl& graph, DepGraphIndex idx, int64_t fee) noexcept
2161{
2162 // Make sure the specified DepGraphIndex exists in this Cluster.
2163 Assume(m_depgraph.Positions()[idx]);
2164 // Bail out if the fee isn't actually being changed.
2165 if (m_depgraph.FeeRate(idx).fee == fee) return;
2166 // Update the fee, remember that relinearization will be necessary, and update the Entries
2167 // in the same Cluster.
2168 m_depgraph.FeeRate(idx).fee = fee;
2169 if (m_quality == QualityLevel::OVERSIZED_SINGLETON) {
2170 // Nothing to do.
2171 } else if (!NeedsSplitting()) {
2172 graph.SetClusterQuality(m_level, m_quality, m_setindex, QualityLevel::NEEDS_RELINEARIZE);
2173 } else {
2174 graph.SetClusterQuality(m_level, m_quality, m_setindex, QualityLevel::NEEDS_SPLIT);
2175 }
2176 Updated(graph);
2177}
2178
2179void TxGraphImpl::SetTransactionFee(const Ref& ref, int64_t fee) noexcept
2180{
2181 // Don't do anything if the passed Ref is empty.
2182 if (GetRefGraph(ref) == nullptr) return;
2183 Assume(GetRefGraph(ref) == this);
2184 Assume(m_main_chunkindex_observers == 0);
2185 // Find the entry, its locator, and inform its Cluster about the new feerate, if any.
2186 auto& entry = m_entries[GetRefIndex(ref)];
2187 for (int level = 0; level < MAX_LEVELS; ++level) {
2188 auto& locator = entry.m_locator[level];
2189 if (locator.IsPresent()) {
2190 locator.cluster->SetFee(*this, locator.index, fee);
2191 }
2192 }
2193}
2194
2195std::strong_ordering TxGraphImpl::CompareMainOrder(const Ref& a, const Ref& b) noexcept
2196{
2197 // The references must not be empty.
2198 Assume(GetRefGraph(a) == this);
2199 Assume(GetRefGraph(b) == this);
2200 // Apply dependencies in main.
2201 ApplyDependencies(0);
2202 Assume(m_main_clusterset.m_deps_to_add.empty());
2203 // Make both involved Clusters acceptable, so chunk feerates are relevant.
2204 const auto& entry_a = m_entries[GetRefIndex(a)];
2205 const auto& entry_b = m_entries[GetRefIndex(b)];
2206 const auto& locator_a = entry_a.m_locator[0];
2207 const auto& locator_b = entry_b.m_locator[0];
2208 Assume(locator_a.IsPresent());
2209 Assume(locator_b.IsPresent());
2210 MakeAcceptable(*locator_a.cluster);
2211 MakeAcceptable(*locator_b.cluster);
2212 // Invoke comparison logic.
2213 return CompareMainTransactions(GetRefIndex(a), GetRefIndex(b));
2214}
2215
2216TxGraph::GraphIndex TxGraphImpl::CountDistinctClusters(std::span<const Ref* const> refs, bool main_only) noexcept
2217{
2218 size_t level = GetSpecifiedLevel(main_only);
2219 ApplyDependencies(level);
2220 auto& clusterset = GetClusterSet(level);
2221 Assume(clusterset.m_deps_to_add.empty());
2222 // Build a vector of Clusters that the specified Refs occur in.
2223 std::vector<Cluster*> clusters;
2224 clusters.reserve(refs.size());
2225 for (const Ref* ref : refs) {
2226 Assume(ref);
2227 if (GetRefGraph(*ref) == nullptr) continue;
2228 Assume(GetRefGraph(*ref) == this);
2229 auto cluster = FindCluster(GetRefIndex(*ref), level);
2230 if (cluster != nullptr) clusters.push_back(cluster);
2231 }
2232 // Count the number of distinct elements in clusters.
2233 std::sort(clusters.begin(), clusters.end(), [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
2234 Cluster* last{nullptr};
2235 GraphIndex ret{0};
2236 for (Cluster* cluster : clusters) {
2237 ret += (cluster != last);
2238 last = cluster;
2239 }
2240 return ret;
2241}
2242
2243std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>> TxGraphImpl::GetMainStagingDiagrams() noexcept
2244{
2245 Assume(m_staging_clusterset.has_value());
2246 MakeAllAcceptable(0);
2247 Assume(m_main_clusterset.m_deps_to_add.empty()); // can only fail if main is oversized
2248 MakeAllAcceptable(1);
2249 Assume(m_staging_clusterset->m_deps_to_add.empty()); // can only fail if staging is oversized
2250 // For all Clusters in main which conflict with Clusters in staging (i.e., all that are removed
2251 // by, or replaced in, staging), gather their chunk feerates.
2252 auto main_clusters = GetConflicts();
2253 std::vector<FeeFrac> main_feerates, staging_feerates;
2254 for (Cluster* cluster : main_clusters) {
2255 cluster->AppendChunkFeerates(main_feerates);
2256 }
2257 // Do the same for the Clusters in staging themselves.
2258 for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
2259 for (const auto& cluster : m_staging_clusterset->m_clusters[quality]) {
2260 cluster->AppendChunkFeerates(staging_feerates);
2261 }
2262 }
2263 // Sort both by decreasing feerate to obtain diagrams, and return them.
2264 std::sort(main_feerates.begin(), main_feerates.end(), [](auto& a, auto& b) { return a > b; });
2265 std::sort(staging_feerates.begin(), staging_feerates.end(), [](auto& a, auto& b) { return a > b; });
2266 return std::make_pair(std::move(main_feerates), std::move(staging_feerates));
2267}
2268
2269void Cluster::SanityCheck(const TxGraphImpl& graph, int level) const
2270{
2271 // There must be an m_mapping for each m_depgraph position (including holes).
2272 assert(m_depgraph.PositionRange() == m_mapping.size());
2273 // The linearization for this Cluster must contain every transaction once.
2274 assert(m_depgraph.TxCount() == m_linearization.size());
2275 // The number of transactions in a Cluster cannot exceed m_max_cluster_count.
2276 assert(m_linearization.size() <= graph.m_max_cluster_count);
2277 // The level must match the level the Cluster occurs in.
2278 assert(m_level == level);
2279 // The sum of their sizes cannot exceed m_max_cluster_size, unless it is an individually
2280 // oversized transaction singleton. Note that groups of to-be-merged clusters which would
2281 // exceed this limit are marked oversized, which means they are never applied.
2282 assert(m_quality == QualityLevel::OVERSIZED_SINGLETON || GetTotalTxSize() <= graph.m_max_cluster_size);
2283 // m_quality and m_setindex are checked in TxGraphImpl::SanityCheck.
2284
2285 // OVERSIZED clusters are singletons.
2286 assert(m_quality != QualityLevel::OVERSIZED_SINGLETON || m_linearization.size() == 1);
2287
2288 // Compute the chunking of m_linearization.
2289 LinearizationChunking linchunking(m_depgraph, m_linearization);
2290
2291 // Verify m_linearization.
2292 SetType m_done;
2293 LinearizationIndex linindex{0};
2294 DepGraphIndex chunk_pos{0};
2295 assert(m_depgraph.IsAcyclic());
2296 for (auto lin_pos : m_linearization) {
2297 assert(lin_pos < m_mapping.size());
2298 const auto& entry = graph.m_entries[m_mapping[lin_pos]];
2299 // Check that the linearization is topological.
2300 m_done.Set(lin_pos);
2301 assert(m_done.IsSupersetOf(m_depgraph.Ancestors(lin_pos)));
2302 // Check that the Entry has a locator pointing back to this Cluster & position within it.
2303 assert(entry.m_locator[level].cluster == this);
2304 assert(entry.m_locator[level].index == lin_pos);
2305 // For main-level entries, check linearization position and chunk feerate.
2306 if (level == 0 && IsAcceptable()) {
2307 assert(entry.m_main_lin_index == linindex);
2308 ++linindex;
2309 if (!linchunking.GetChunk(0).transactions[lin_pos]) {
2310 linchunking.MarkDone(linchunking.GetChunk(0).transactions);
2311 chunk_pos = 0;
2312 }
2313 assert(entry.m_main_chunk_feerate == linchunking.GetChunk(0).feerate);
2314 // Verify that an entry in the chunk index exists for every chunk-ending transaction.
2315 ++chunk_pos;
2316 bool is_chunk_end = (chunk_pos == linchunking.GetChunk(0).transactions.Count());
2317 assert((entry.m_main_chunkindex_iterator != graph.m_main_chunkindex.end()) == is_chunk_end);
2318 if (is_chunk_end) {
2319 auto& chunk_data = *entry.m_main_chunkindex_iterator;
2320 if (m_done == m_depgraph.Positions() && chunk_pos == 1) {
2321 assert(chunk_data.m_chunk_count == LinearizationIndex(-1));
2322 } else {
2323 assert(chunk_data.m_chunk_count == chunk_pos);
2324 }
2325 }
2326 // If this Cluster has an acceptable quality level, its chunks must be connected.
2327 assert(m_depgraph.IsConnected(linchunking.GetChunk(0).transactions));
2328 }
2329 }
2330 // Verify that each element of m_depgraph occurred in m_linearization.
2331 assert(m_done == m_depgraph.Positions());
2332}
2333
2334void TxGraphImpl::SanityCheck() const
2335{
2337 std::set<GraphIndex> expected_unlinked;
2339 std::set<const Cluster*> expected_clusters[MAX_LEVELS];
2341 std::set<GraphIndex> expected_removed[MAX_LEVELS];
2343 std::set<uint64_t> sequences;
2345 std::set<GraphIndex> expected_chunkindex;
2347 bool compact_possible{true};
2348
2349 // Go over all Entry objects in m_entries.
2350 for (GraphIndex idx = 0; idx < m_entries.size(); ++idx) {
2351 const auto& entry = m_entries[idx];
2352 if (entry.m_ref == nullptr) {
2353 // Unlinked Entry must have indexes appear in m_unlinked.
2354 expected_unlinked.insert(idx);
2355 } else {
2356 // Every non-unlinked Entry must have a Ref that points back to it.
2357 assert(GetRefGraph(*entry.m_ref) == this);
2358 assert(GetRefIndex(*entry.m_ref) == idx);
2359 }
2360 if (entry.m_main_chunkindex_iterator != m_main_chunkindex.end()) {
2361 // Remember which entries we see a chunkindex entry for.
2362 assert(entry.m_locator[0].IsPresent());
2363 expected_chunkindex.insert(idx);
2364 }
2365 // Verify the Entry m_locators.
2366 bool was_present{false}, was_removed{false};
2367 for (int level = 0; level < MAX_LEVELS; ++level) {
2368 const auto& locator = entry.m_locator[level];
2369 // Every Locator must be in exactly one of these 3 states.
2370 assert(locator.IsMissing() + locator.IsRemoved() + locator.IsPresent() == 1);
2371 if (locator.IsPresent()) {
2372 // Once removed, a transaction cannot be revived.
2373 assert(!was_removed);
2374 // Verify that the Cluster agrees with where the Locator claims the transaction is.
2375 assert(locator.cluster->GetClusterEntry(locator.index) == idx);
2376 // Remember that we expect said Cluster to appear in the ClusterSet::m_clusters.
2377 expected_clusters[level].insert(locator.cluster);
2378 was_present = true;
2379 } else if (locator.IsRemoved()) {
2380 // Level 0 (main) cannot have IsRemoved locators (IsMissing there means non-existing).
2381 assert(level > 0);
2382 // A Locator can only be IsRemoved if it was IsPresent before, and only once.
2383 assert(was_present && !was_removed);
2384 // Remember that we expect this GraphIndex to occur in the ClusterSet::m_removed.
2385 expected_removed[level].insert(idx);
2386 was_removed = true;
2387 }
2388 }
2389 }
2390
2391 // For all levels (0 = main, 1 = staged)...
2392 for (int level = 0; level <= GetTopLevel(); ++level) {
2393 assert(level < MAX_LEVELS);
2394 auto& clusterset = GetClusterSet(level);
2395 std::set<const Cluster*> actual_clusters;
2396
2397 // For all quality levels...
2398 for (int qual = 0; qual < int(QualityLevel::NONE); ++qual) {
2399 QualityLevel quality{qual};
2400 const auto& quality_clusters = clusterset.m_clusters[qual];
2401 // ... for all clusters in them ...
2402 for (ClusterSetIndex setindex = 0; setindex < quality_clusters.size(); ++setindex) {
2403 const auto& cluster = *quality_clusters[setindex];
2404 // Check the sequence number.
2405 assert(cluster.m_sequence < m_next_sequence_counter);
2406 assert(sequences.count(cluster.m_sequence) == 0);
2407 sequences.insert(cluster.m_sequence);
2408 // Remember we saw this Cluster (only if it is non-empty; empty Clusters aren't
2409 // expected to be referenced by the Entry vector).
2410 if (cluster.GetTxCount() != 0) {
2411 actual_clusters.insert(&cluster);
2412 }
2413 // Sanity check the cluster, according to the Cluster's internal rules.
2414 cluster.SanityCheck(*this, level);
2415 // Check that the cluster's quality and setindex matches its position in the quality list.
2416 assert(cluster.m_quality == quality);
2417 assert(cluster.m_setindex == setindex);
2418 }
2419 }
2420
2421 // Verify that all to-be-removed transactions have valid identifiers.
2422 for (GraphIndex idx : clusterset.m_to_remove) {
2423 assert(idx < m_entries.size());
2424 // We cannot assert that all m_to_remove transactions are still present: ~Ref on a
2425 // (P,M) transaction (present in main, inherited in staging) will cause an m_to_remove
2426 // addition in both main and staging, but a subsequence ApplyRemovals in main will
2427 // cause it to disappear from staging too, leaving the m_to_remove in place.
2428 }
2429
2430 // Verify that all to-be-added dependencies have valid identifiers.
2431 for (auto [par_idx, chl_idx] : clusterset.m_deps_to_add) {
2432 assert(par_idx != chl_idx);
2433 assert(par_idx < m_entries.size());
2434 assert(chl_idx < m_entries.size());
2435 }
2436
2437 // Verify that the actually encountered clusters match the ones occurring in Entry vector.
2438 assert(actual_clusters == expected_clusters[level]);
2439
2440 // Verify that the contents of m_removed matches what was expected based on the Entry vector.
2441 std::set<GraphIndex> actual_removed(clusterset.m_removed.begin(), clusterset.m_removed.end());
2442 for (auto i : expected_unlinked) {
2443 // If a transaction exists in both main and staging, and is removed from staging (adding
2444 // it to m_removed there), and consequently destroyed (wiping the locator completely),
2445 // it can remain in m_removed despite not having an IsRemoved() locator. Exclude those
2446 // transactions from the comparison here.
2447 actual_removed.erase(i);
2448 expected_removed[level].erase(i);
2449 }
2450
2451 assert(actual_removed == expected_removed[level]);
2452
2453 // If any GraphIndex entries remain in this ClusterSet, compact is not possible.
2454 if (!clusterset.m_deps_to_add.empty()) compact_possible = false;
2455 if (!clusterset.m_to_remove.empty()) compact_possible = false;
2456 if (!clusterset.m_removed.empty()) compact_possible = false;
2457
2458 // For non-top levels, m_oversized must be known (as it cannot change until the level
2459 // on top is gone).
2460 if (level < GetTopLevel()) assert(clusterset.m_oversized.has_value());
2461 }
2462
2463 // Verify that the contents of m_unlinked matches what was expected based on the Entry vector.
2464 std::set<GraphIndex> actual_unlinked(m_unlinked.begin(), m_unlinked.end());
2465 assert(actual_unlinked == expected_unlinked);
2466
2467 // If compaction was possible, it should have been performed already, and m_unlinked must be
2468 // empty (to prevent memory leaks due to an ever-growing m_entries vector).
2469 if (compact_possible) {
2470 assert(actual_unlinked.empty());
2471 }
2472
2473 // Finally, check the chunk index.
2474 std::set<GraphIndex> actual_chunkindex;
2475 FeeFrac last_chunk_feerate;
2476 for (const auto& chunk : m_main_chunkindex) {
2477 GraphIndex idx = chunk.m_graph_index;
2478 actual_chunkindex.insert(idx);
2479 auto chunk_feerate = m_entries[idx].m_main_chunk_feerate;
2480 if (!last_chunk_feerate.IsEmpty()) {
2481 assert(FeeRateCompare(last_chunk_feerate, chunk_feerate) >= 0);
2482 }
2483 last_chunk_feerate = chunk_feerate;
2484 }
2485 assert(actual_chunkindex == expected_chunkindex);
2486}
2487
2488bool TxGraphImpl::DoWork(uint64_t iters) noexcept
2489{
2490 uint64_t iters_done{0};
2491 // First linearize everything in NEEDS_RELINEARIZE to an acceptable level. If more budget
2492 // remains after that, try to make everything optimal.
2493 for (QualityLevel quality : {QualityLevel::NEEDS_RELINEARIZE, QualityLevel::ACCEPTABLE}) {
2494 // First linearize staging, if it exists, then main.
2495 for (int level = GetTopLevel(); level >= 0; --level) {
2496 // Do not modify main if it has any observers.
2497 if (level == 0 && m_main_chunkindex_observers != 0) continue;
2498 ApplyDependencies(level);
2499 auto& clusterset = GetClusterSet(level);
2500 // Do not modify oversized levels.
2501 if (clusterset.m_oversized == true) continue;
2502 auto& queue = clusterset.m_clusters[int(quality)];
2503 while (!queue.empty()) {
2504 if (iters_done >= iters) return false;
2505 // Randomize the order in which we process, so that if the first cluster somehow
2506 // needs more work than what iters allows, we don't keep spending it on the same
2507 // one.
2508 auto pos = m_rng.randrange<size_t>(queue.size());
2509 auto iters_now = iters - iters_done;
2510 if (quality == QualityLevel::NEEDS_RELINEARIZE) {
2511 // If we're working with clusters that need relinearization still, only perform
2512 // up to m_acceptable_iters iterations. If they become ACCEPTABLE, and we still
2513 // have budget after all other clusters are ACCEPTABLE too, we'll spend the
2514 // remaining budget on trying to make them OPTIMAL.
2515 iters_now = std::min(iters_now, m_acceptable_iters);
2516 }
2517 auto [cost, improved] = queue[pos].get()->Relinearize(*this, iters_now);
2518 iters_done += cost;
2519 // If no improvement was made to the Cluster, it means we've essentially run out of
2520 // budget. Even though it may be the case that iters_done < iters still, the
2521 // linearizer decided there wasn't enough budget left to attempt anything with.
2522 // To avoid an infinite loop that keeps trying clusters with minuscule budgets,
2523 // stop here too.
2524 if (!improved) return false;
2525 }
2526 }
2527 }
2528 // All possible work has been performed, so we can return true. Note that this does *not* mean
2529 // that all clusters are optimally linearized now. It may be that there is nothing to do left
2530 // because all non-optimal clusters are in oversized and/or observer-bearing levels.
2531 return true;
2532}
2533
2534void BlockBuilderImpl::Next() noexcept
2535{
2536 // Don't do anything if we're already done.
2537 if (m_cur_iter == m_graph->m_main_chunkindex.end()) return;
2538 while (true) {
2539 // Advance the pointer, and stop if we reach the end.
2540 ++m_cur_iter;
2541 m_cur_cluster = nullptr;
2542 if (m_cur_iter == m_graph->m_main_chunkindex.end()) break;
2543 // Find the cluster pointed to by m_cur_iter.
2544 const auto& chunk_data = *m_cur_iter;
2545 const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];
2546 m_cur_cluster = chunk_end_entry.m_locator[0].cluster;
2547 m_known_end_of_cluster = false;
2548 // If we previously skipped a chunk from this cluster we cannot include more from it.
2549 if (!m_excluded_clusters.contains(m_cur_cluster)) break;
2550 }
2551}
2552
2553std::optional<std::pair<std::vector<TxGraph::Ref*>, FeePerWeight>> BlockBuilderImpl::GetCurrentChunk() noexcept
2554{
2555 std::optional<std::pair<std::vector<TxGraph::Ref*>, FeePerWeight>> ret;
2556 // Populate the return value if we are not done.
2557 if (m_cur_iter != m_graph->m_main_chunkindex.end()) {
2558 ret.emplace();
2559 const auto& chunk_data = *m_cur_iter;
2560 const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];
2561 if (chunk_data.m_chunk_count == LinearizationIndex(-1)) {
2562 // Special case in case just a single transaction remains, avoiding the need to
2563 // dispatch to and dereference Cluster.
2564 ret->first.resize(1);
2565 Assume(chunk_end_entry.m_ref != nullptr);
2566 ret->first[0] = chunk_end_entry.m_ref;
2567 m_known_end_of_cluster = true;
2568 } else {
2569 Assume(m_cur_cluster);
2570 ret->first.resize(chunk_data.m_chunk_count);
2571 auto start_pos = chunk_end_entry.m_main_lin_index + 1 - chunk_data.m_chunk_count;
2572 m_known_end_of_cluster = m_cur_cluster->GetClusterRefs(*m_graph, ret->first, start_pos);
2573 // If the chunk size was 1 and at end of cluster, then the special case above should
2574 // have been used.
2575 Assume(!m_known_end_of_cluster || chunk_data.m_chunk_count > 1);
2576 }
2577 ret->second = chunk_end_entry.m_main_chunk_feerate;
2578 }
2579 return ret;
2580}
2581
2582BlockBuilderImpl::BlockBuilderImpl(TxGraphImpl& graph) noexcept : m_graph(&graph)
2583{
2584 // Make sure all clusters in main are up to date, and acceptable.
2585 m_graph->MakeAllAcceptable(0);
2586 // There cannot remain any inapplicable dependencies (only possible if main is oversized).
2587 Assume(m_graph->m_main_clusterset.m_deps_to_add.empty());
2588 // Remember that this object is observing the graph's index, so that we can detect concurrent
2589 // modifications.
2590 ++m_graph->m_main_chunkindex_observers;
2591 // Find the first chunk.
2592 m_cur_iter = m_graph->m_main_chunkindex.begin();
2593 m_cur_cluster = nullptr;
2594 if (m_cur_iter != m_graph->m_main_chunkindex.end()) {
2595 // Find the cluster pointed to by m_cur_iter.
2596 const auto& chunk_data = *m_cur_iter;
2597 const auto& chunk_end_entry = m_graph->m_entries[chunk_data.m_graph_index];
2598 m_cur_cluster = chunk_end_entry.m_locator[0].cluster;
2599 }
2600}
2601
2602BlockBuilderImpl::~BlockBuilderImpl()
2603{
2604 Assume(m_graph->m_main_chunkindex_observers > 0);
2605 // Permit modifications to the main graph again after destroying the BlockBuilderImpl.
2606 --m_graph->m_main_chunkindex_observers;
2607}
2608
2609void BlockBuilderImpl::Include() noexcept
2610{
2611 // The actual inclusion of the chunk is done by the calling code. All we have to do is switch
2612 // to the next chunk.
2613 Next();
2614}
2615
2616void BlockBuilderImpl::Skip() noexcept
2617{
2618 // When skipping a chunk we need to not include anything more of the cluster, as that could make
2619 // the result topologically invalid. However, don't do this if the chunk is known to be the last
2620 // chunk of the cluster. This may significantly reduce the size of m_excluded_clusters,
2621 // especially when many singleton clusters are ignored.
2622 if (m_cur_cluster != nullptr && !m_known_end_of_cluster) {
2623 m_excluded_clusters.insert(m_cur_cluster);
2624 }
2625 Next();
2626}
2627
2628std::unique_ptr<TxGraph::BlockBuilder> TxGraphImpl::GetBlockBuilder() noexcept
2629{
2630 return std::make_unique<BlockBuilderImpl>(*this);
2631}
2632
2633std::pair<std::vector<TxGraph::Ref*>, FeePerWeight> TxGraphImpl::GetWorstMainChunk() noexcept
2634{
2635 std::pair<std::vector<Ref*>, FeePerWeight> ret;
2636 // Make sure all clusters in main are up to date, and acceptable.
2637 MakeAllAcceptable(0);
2638 Assume(m_main_clusterset.m_deps_to_add.empty());
2639 // If the graph is not empty, populate ret.
2640 if (!m_main_chunkindex.empty()) {
2641 const auto& chunk_data = *m_main_chunkindex.rbegin();
2642 const auto& chunk_end_entry = m_entries[chunk_data.m_graph_index];
2643 Cluster* cluster = chunk_end_entry.m_locator[0].cluster;
2644 if (chunk_data.m_chunk_count == LinearizationIndex(-1) || chunk_data.m_chunk_count == 1) {
2645 // Special case for singletons.
2646 ret.first.resize(1);
2647 Assume(chunk_end_entry.m_ref != nullptr);
2648 ret.first[0] = chunk_end_entry.m_ref;
2649 } else {
2650 ret.first.resize(chunk_data.m_chunk_count);
2651 auto start_pos = chunk_end_entry.m_main_lin_index + 1 - chunk_data.m_chunk_count;
2652 cluster->GetClusterRefs(*this, ret.first, start_pos);
2653 std::reverse(ret.first.begin(), ret.first.end());
2654 }
2655 ret.second = chunk_end_entry.m_main_chunk_feerate;
2656 }
2657 return ret;
2658}
2659
2660std::vector<TxGraph::Ref*> TxGraphImpl::Trim() noexcept
2661{
2662 int level = GetTopLevel();
2663 Assume(m_main_chunkindex_observers == 0 || level != 0);
2664 std::vector<TxGraph::Ref*> ret;
2665
2666 // Compute the groups of to-be-merged Clusters (which also applies all removals, and splits).
2667 auto& clusterset = GetClusterSet(level);
2668 if (clusterset.m_oversized == false) return ret;
2669 GroupClusters(level);
2670 Assume(clusterset.m_group_data.has_value());
2671 // Nothing to do if not oversized.
2672 Assume(clusterset.m_oversized.has_value());
2673 if (clusterset.m_oversized == false) return ret;
2674
2675 // In this function, would-be clusters (as precomputed in m_group_data by GroupClusters) are
2676 // trimmed by removing transactions in them such that the resulting clusters satisfy the size
2677 // and count limits.
2678 //
2679 // It works by defining for each would-be cluster a rudimentary linearization: at every point
2680 // the highest-chunk-feerate remaining transaction is picked among those with no unmet
2681 // dependencies. "Dependency" here means either a to-be-added dependency (m_deps_to_add), or
2682 // an implicit dependency added between any two consecutive transaction in their current
2683 // cluster linearization. So it can be seen as a "merge sort" of the chunks of the clusters,
2684 // but respecting the dependencies being added.
2685 //
2686 // This rudimentary linearization is computed lazily, by putting all eligible (no unmet
2687 // dependencies) transactions in a heap, and popping the highest-feerate one from it. Along the
2688 // way, the counts and sizes of the would-be clusters up to that point are tracked (by
2689 // partitioning the involved transactions using a union-find structure). Any transaction whose
2690 // addition would cause a violation is removed, along with all their descendants.
2691 //
2692 // A next invocation of GroupClusters (after applying the removals) will compute the new
2693 // resulting clusters, and none of them will violate the limits.
2694
2697 std::vector<std::pair<GraphIndex, GraphIndex>> deps_by_parent;
2699 std::vector<std::pair<GraphIndex, GraphIndex>> deps_by_child;
2703 std::vector<TrimTxData> trim_data;
2706 std::vector<std::vector<TrimTxData>::iterator> trim_heap;
2708 std::vector<TrimTxData*> current_deps;
2709
2711 static constexpr auto cmp_fn = [](auto a, auto b) noexcept {
2712 // Sort by increasing chunk feerate, and then by decreasing size.
2713 // We do not need to sort by cluster or within clusters, because due to the implicit
2714 // dependency between consecutive linearization elements, no two transactions from the
2715 // same Cluster will ever simultaneously be in the heap.
2716 return a->m_chunk_feerate < b->m_chunk_feerate;
2717 };
2718
2720 static constexpr auto find_fn = [](TrimTxData* arg) noexcept {
2721 while (arg != arg->m_uf_parent) {
2722 // Replace pointer to parent with pointer to grandparent (path splitting).
2723 // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Finding_set_representatives.
2724 auto par = arg->m_uf_parent;
2725 arg->m_uf_parent = par->m_uf_parent;
2726 arg = par;
2727 }
2728 return arg;
2729 };
2730
2733 static constexpr auto union_fn = [](TrimTxData* arg1, TrimTxData* arg2) noexcept {
2734 // Replace arg1 and arg2 by their representatives.
2735 auto rep1 = find_fn(arg1);
2736 auto rep2 = find_fn(arg2);
2737 // Bail out if both representatives are the same, because that means arg1 and arg2 are in
2738 // the same partition already.
2739 if (rep1 == rep2) return rep1;
2740 // Pick the lower-count root to become a child of the higher-count one.
2741 // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Union_by_size.
2742 if (rep1->m_uf_count < rep2->m_uf_count) std::swap(rep1, rep2);
2743 rep2->m_uf_parent = rep1;
2744 // Add the statistics of arg2 (which is no longer a representative) to those of arg1 (which
2745 // is now the representative for both).
2746 rep1->m_uf_size += rep2->m_uf_size;
2747 rep1->m_uf_count += rep2->m_uf_count;
2748 return rep1;
2749 };
2750
2752 auto locate_fn = [&](GraphIndex index) noexcept {
2753 auto it = std::lower_bound(trim_data.begin(), trim_data.end(), index, [](TrimTxData& elem, GraphIndex idx) noexcept {
2754 return elem.m_index < idx;
2755 });
2756 Assume(it != trim_data.end() && it->m_index == index);
2757 return it;
2758 };
2759
2760 // For each group of to-be-merged Clusters.
2761 for (const auto& group_data : clusterset.m_group_data->m_groups) {
2762 trim_data.clear();
2763 trim_heap.clear();
2764 deps_by_child.clear();
2765 deps_by_parent.clear();
2766
2767 // Gather trim data and implicit dependency data from all involved Clusters.
2768 auto cluster_span = std::span{clusterset.m_group_data->m_group_clusters}
2769 .subspan(group_data.m_cluster_offset, group_data.m_cluster_count);
2770 uint64_t size{0};
2771 for (Cluster* cluster : cluster_span) {
2772 size += cluster->AppendTrimData(trim_data, deps_by_child);
2773 }
2774 // If this group of Clusters does not violate any limits, continue to the next group.
2775 if (trim_data.size() <= m_max_cluster_count && size <= m_max_cluster_size) continue;
2776 // Sort the trim data by GraphIndex. In what follows, we will treat this sorted vector as
2777 // a map from GraphIndex to TrimTxData via locate_fn, and its ordering will not change
2778 // anymore.
2779 std::sort(trim_data.begin(), trim_data.end(), [](auto& a, auto& b) noexcept { return a.m_index < b.m_index; });
2780
2781 // Add the explicitly added dependencies to deps_by_child.
2782 deps_by_child.insert(deps_by_child.end(),
2783 clusterset.m_deps_to_add.begin() + group_data.m_deps_offset,
2784 clusterset.m_deps_to_add.begin() + group_data.m_deps_offset + group_data.m_deps_count);
2785
2786 // Sort deps_by_child by child transaction GraphIndex. The order will not be changed
2787 // anymore after this.
2788 std::sort(deps_by_child.begin(), deps_by_child.end(), [](auto& a, auto& b) noexcept { return a.second < b.second; });
2789 // Fill m_parents_count and m_parents_offset in trim_data, as well as m_deps_left, and
2790 // initially populate trim_heap. Because of the sort above, all dependencies involving the
2791 // same child are grouped together, so a single linear scan suffices.
2792 auto deps_it = deps_by_child.begin();
2793 for (auto trim_it = trim_data.begin(); trim_it != trim_data.end(); ++trim_it) {
2794 trim_it->m_parent_offset = deps_it - deps_by_child.begin();
2795 trim_it->m_deps_left = 0;
2796 while (deps_it != deps_by_child.end() && deps_it->second == trim_it->m_index) {
2797 ++trim_it->m_deps_left;
2798 ++deps_it;
2799 }
2800 trim_it->m_parent_count = trim_it->m_deps_left;
2801 // If this transaction has no unmet dependencies, and is not oversized, add it to the
2802 // heap (just append for now, the heapification happens below).
2803 if (trim_it->m_deps_left == 0 && trim_it->m_tx_size <= m_max_cluster_size) {
2804 trim_heap.push_back(trim_it);
2805 }
2806 }
2807 Assume(deps_it == deps_by_child.end());
2808
2809 // Construct deps_by_parent, sorted by parent transaction GraphIndex. The order will not be
2810 // changed anymore after this.
2811 deps_by_parent = deps_by_child;
2812 std::sort(deps_by_parent.begin(), deps_by_parent.end(), [](auto& a, auto& b) noexcept { return a.first < b.first; });
2813 // Fill m_children_offset and m_children_count in trim_data. Because of the sort above, all
2814 // dependencies involving the same parent are grouped together, so a single linear scan
2815 // suffices.
2816 deps_it = deps_by_parent.begin();
2817 for (auto& trim_entry : trim_data) {
2818 trim_entry.m_children_count = 0;
2819 trim_entry.m_children_offset = deps_it - deps_by_parent.begin();
2820 while (deps_it != deps_by_parent.end() && deps_it->first == trim_entry.m_index) {
2821 ++trim_entry.m_children_count;
2822 ++deps_it;
2823 }
2824 }
2825 Assume(deps_it == deps_by_parent.end());
2826
2827 // Build a heap of all transactions with 0 unmet dependencies.
2828 std::make_heap(trim_heap.begin(), trim_heap.end(), cmp_fn);
2829
2830 // Iterate over to-be-included transactions, and convert them to included transactions, or
2831 // skip them if adding them would violate resource limits of the would-be cluster.
2832 //
2833 // It is possible that the heap empties without ever hitting either cluster limit, in case
2834 // the implied graph (to be added dependencies plus implicit dependency between each
2835 // original transaction and its predecessor in the linearization it came from) contains
2836 // cycles. Such cycles will be removed entirely, because each of the transactions in the
2837 // cycle permanently have unmet dependencies. However, this cannot occur in real scenarios
2838 // where Trim() is called to deal with reorganizations that would violate cluster limits,
2839 // as all added dependencies are in the same direction (from old mempool transactions to
2840 // new from-block transactions); cycles require dependencies in both directions to be
2841 // added.
2842 while (!trim_heap.empty()) {
2843 // Move the best remaining transaction to the end of trim_heap.
2844 std::pop_heap(trim_heap.begin(), trim_heap.end(), cmp_fn);
2845 // Pop it, and find its TrimTxData.
2846 auto& entry = *trim_heap.back();
2847 trim_heap.pop_back();
2848
2849 // Initialize it as a singleton partition.
2850 entry.m_uf_parent = &entry;
2851 entry.m_uf_count = 1;
2852 entry.m_uf_size = entry.m_tx_size;
2853
2854 // Find the distinct transaction partitions this entry depends on.
2855 current_deps.clear();
2856 for (auto& [par, chl] : std::span{deps_by_child}.subspan(entry.m_parent_offset, entry.m_parent_count)) {
2857 Assume(chl == entry.m_index);
2858 current_deps.push_back(find_fn(&*locate_fn(par)));
2859 }
2860 std::sort(current_deps.begin(), current_deps.end());
2861 current_deps.erase(std::unique(current_deps.begin(), current_deps.end()), current_deps.end());
2862
2863 // Compute resource counts.
2864 uint32_t new_count = 1;
2865 uint64_t new_size = entry.m_tx_size;
2866 for (TrimTxData* ptr : current_deps) {
2867 new_count += ptr->m_uf_count;
2868 new_size += ptr->m_uf_size;
2869 }
2870 // Skip the entry if this would violate any limit.
2871 if (new_count > m_max_cluster_count || new_size > m_max_cluster_size) continue;
2872
2873 // Union the partitions this transaction and all its dependencies are in together.
2874 auto rep = &entry;
2875 for (TrimTxData* ptr : current_deps) rep = union_fn(ptr, rep);
2876 // Mark the entry as included (so the loop below will not remove the transaction).
2877 entry.m_deps_left = uint32_t(-1);
2878 // Mark each to-be-added dependency involving this transaction as parent satisfied.
2879 for (auto& [par, chl] : std::span{deps_by_parent}.subspan(entry.m_children_offset, entry.m_children_count)) {
2880 Assume(par == entry.m_index);
2881 auto chl_it = locate_fn(chl);
2882 // Reduce the number of unmet dependencies of chl_it, and if that brings the number
2883 // to zero, add it to the heap of includable transactions.
2884 Assume(chl_it->m_deps_left > 0);
2885 if (--chl_it->m_deps_left == 0) {
2886 trim_heap.push_back(chl_it);
2887 std::push_heap(trim_heap.begin(), trim_heap.end(), cmp_fn);
2888 }
2889 }
2890 }
2891
2892 // Remove all the transactions that were not processed above. Because nothing gets
2893 // processed until/unless all its dependencies are met, this automatically guarantees
2894 // that if a transaction is removed, all its descendants, or would-be descendants, are
2895 // removed as well.
2896 for (const auto& trim_entry : trim_data) {
2897 if (trim_entry.m_deps_left != uint32_t(-1)) {
2898 ret.push_back(m_entries[trim_entry.m_index].m_ref);
2899 clusterset.m_to_remove.push_back(trim_entry.m_index);
2900 }
2901 }
2902 }
2903 clusterset.m_group_data.reset();
2904 clusterset.m_oversized = false;
2905 Assume(!ret.empty());
2906 return ret;
2907}
2908
2909} // namespace
2910
2912{
2913 if (m_graph) {
2914 // Inform the TxGraph about the Ref being destroyed.
2915 m_graph->UnlinkRef(m_index);
2916 m_graph = nullptr;
2917 }
2918}
2919
2921{
2922 // Unlink the current graph, if any.
2923 if (m_graph) m_graph->UnlinkRef(m_index);
2924 // Inform the other's graph about the move, if any.
2925 if (other.m_graph) other.m_graph->UpdateRef(other.m_index, *this);
2926 // Actually update the contents.
2927 m_graph = other.m_graph;
2928 m_index = other.m_index;
2929 other.m_graph = nullptr;
2930 other.m_index = GraphIndex(-1);
2931 return *this;
2932}
2933
2934TxGraph::Ref::Ref(Ref&& other) noexcept
2935{
2936 // Inform the TxGraph of other that its Ref is being moved.
2937 if (other.m_graph) other.m_graph->UpdateRef(other.m_index, *this);
2938 // Actually move the contents.
2939 std::swap(m_graph, other.m_graph);
2940 std::swap(m_index, other.m_index);
2941}
2942
2943std::unique_ptr<TxGraph> MakeTxGraph(unsigned max_cluster_count, uint64_t max_cluster_size, uint64_t acceptable_iters) noexcept
2944{
2945 return std::make_unique<TxGraphImpl>(max_cluster_count, max_cluster_size, acceptable_iters);
2946}
int ret
ArgsManager & args
Definition: bitcoind.cpp:277
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,(BITS+std::numeric_limits< size_t >::digits - 1)/std::numeric_limits< size_t >::digits > > > BitSet
Definition: bitset.h:525
#define Assume(val)
Assume is the identity function.
Definition: check.h:118
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:181
TxGraph * m_graph
Which Graph the Entry lives in.
Definition: txgraph.h:229
virtual ~Ref()
Destroy this Ref.
Definition: txgraph.cpp:2911
Ref & operator=(Ref &&other) noexcept
Definition: txgraph.cpp:2920
Ref() noexcept=default
Construct an empty Ref.
Data structure to encapsulate fees, sizes, and dependencies for a set of transactions.
Definition: txgraph.h:47
virtual void UpdateRef(GraphIndex index, Ref &new_location) noexcept=0
Inform the TxGraph implementation that a TxGraph::Ref has moved.
uint32_t GraphIndex
Internal identifier for a transaction within a TxGraph.
Definition: txgraph.h:50
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.
const SetType & Positions() const noexcept
Get the set of transactions positions in use.
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.
Data structure encapsulating the chunking of a linearization, permitting removal of subsets.
uint64_t fee
uint64_t sequence
@ NONE
Definition: logging.h:65
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_iterations, uint64_t rng_seed, std::span< const DepGraphIndex > old_linearization={}) noexcept
Find or improve a linearization for a cluster.
void FixLinearization(const DepGraph< SetType > &depgraph, std::span< DepGraphIndex > linearization) noexcept
Make linearization topological, retaining its ordering where possible.
uint32_t DepGraphIndex
Data type to represent transaction indices in DepGraphs and the clusters they represent.
void PostLinearize(const DepGraph< SetType > &depgraph, std::span< DepGraphIndex > linearization)
Improve a given linearization.
Definition: messages.h:20
std::vector< T > Split(const std::span< const char > &sp, std::string_view separators, bool include_sep=false)
Split a string on any char found in separators, returning a vector.
Definition: string.h:115
Data structure storing a fee and size, ordered by increasing fee/size.
Definition: feefrac.h:40
int64_t fee
Definition: feefrac.h:107
int32_t size
Definition: feefrac.h:108
bool IsEmpty() const noexcept
Check if this is empty (size and fee are 0).
Definition: feefrac.h:120
Tagged wrapper around FeeFrac to avoid unit confusion.
Definition: feefrac.h:239
static FeePerUnit FromFeeFrac(const FeeFrac &feefrac) noexcept
Convert a FeeFrac to a FeePerUnit.
Definition: feefrac.h:244
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_iters) noexcept
Construct a new TxGraph with the specified limit on the number of transactions within a cluster,...
Definition: txgraph.cpp:2943
static constexpr unsigned MAX_CLUSTER_COUNT_LIMIT
Definition: txgraph.h:17
assert(!tx.IsCoinBase())
void ClearShrink(V &v) noexcept
Clear a vector (or std::deque) and release its allocated memory.
Definition: vector.h:56