fuzz coverage

Coverage Report

Created: 2025-06-01 19:34

/Users/eugenesiegel/btc/bitcoin/src/txgraph.cpp
Line
Count
Source (jump to first uncovered line)
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
13
#include <compare>
14
#include <memory>
15
#include <set>
16
#include <span>
17
#include <utility>
18
19
namespace {
20
21
using namespace cluster_linearize;
22
23
/** The maximum number of levels a TxGraph can have (0 = main, 1 = staging). */
24
static constexpr int MAX_LEVELS{2};
25
26
// Forward declare the TxGraph implementation class.
27
class TxGraphImpl;
28
29
/** Position of a DepGraphIndex within a Cluster::m_linearization. */
30
using LinearizationIndex = uint32_t;
31
/** Position of a Cluster within Graph::ClusterSet::m_clusters. */
32
using ClusterSetIndex = uint32_t;
33
34
/** Quality levels for cached cluster linearizations. */
35
enum class QualityLevel
36
{
37
    /** This cluster may have multiple disconnected components, which are all NEEDS_RELINEARIZE. */
38
    NEEDS_SPLIT,
39
    /** This cluster may have multiple disconnected components, which are all ACCEPTABLE. */
40
    NEEDS_SPLIT_ACCEPTABLE,
41
    /** This cluster has undergone changes that warrant re-linearization. */
42
    NEEDS_RELINEARIZE,
43
    /** The minimal level of linearization has been performed, but it is not known to be optimal. */
44
    ACCEPTABLE,
45
    /** The linearization is known to be optimal. */
46
    OPTIMAL,
47
    /** This cluster is not registered in any ClusterSet::m_clusters.
48
     *  This must be the last entry in QualityLevel as ClusterSet::m_clusters is sized using it. */
49
    NONE,
50
};
51
52
/** A grouping of connected transactions inside a TxGraphImpl::ClusterSet. */
53
class Cluster
54
{
55
    friend class TxGraphImpl;
56
    using GraphIndex = TxGraph::GraphIndex;
57
    using SetType = BitSet<MAX_CLUSTER_COUNT_LIMIT>;
58
    /** The DepGraph for this cluster, holding all feerates, and ancestors/descendants. */
59
    DepGraph<SetType> m_depgraph;
60
    /** m_mapping[i] gives the GraphIndex for the position i transaction in m_depgraph. Values for
61
     *  positions i that do not exist in m_depgraph shouldn't ever be accessed and thus don't
62
     *  matter. m_mapping.size() equals m_depgraph.PositionRange(). */
63
    std::vector<GraphIndex> m_mapping;
64
    /** The current linearization of the cluster. m_linearization.size() equals
65
     *  m_depgraph.TxCount(). This is always kept topological. */
66
    std::vector<DepGraphIndex> m_linearization;
67
    /** The quality level of m_linearization. */
68
    QualityLevel m_quality{QualityLevel::NONE};
69
    /** Which position this Cluster has in Graph::ClusterSet::m_clusters[m_quality]. */
70
    ClusterSetIndex m_setindex{ClusterSetIndex(-1)};
71
    /** Which level this Cluster is at in the graph (-1=not inserted, 0=main, 1=staging). */
72
    int m_level{-1};
73
    /** Sequence number for this Cluster (for tie-breaking comparison between equal-chunk-feerate
74
        transactions in distinct clusters). */
75
    uint64_t m_sequence;
76
77
public:
78
    Cluster() noexcept = delete;
79
    /** Construct an empty Cluster. */
80
    explicit Cluster(uint64_t sequence) noexcept;
81
    /** Construct a singleton Cluster. */
82
    explicit Cluster(uint64_t sequence, TxGraphImpl& graph, const FeePerWeight& feerate, GraphIndex graph_index) noexcept;
83
84
    // Cannot move or copy (would invalidate Cluster* in Locator and ClusterSet). */
85
    Cluster(const Cluster&) = delete;
86
    Cluster& operator=(const Cluster&) = delete;
87
    Cluster(Cluster&&) = delete;
88
    Cluster& operator=(Cluster&&) = delete;
89
90
    // Generic helper functions.
91
92
    /** Whether the linearization of this Cluster can be exposed. */
93
    bool IsAcceptable(bool after_split = false) const noexcept
94
0
    {
95
0
        return m_quality == QualityLevel::ACCEPTABLE || m_quality == QualityLevel::OPTIMAL ||
96
0
               (after_split && m_quality == QualityLevel::NEEDS_SPLIT_ACCEPTABLE);
97
0
    }
98
    /** Whether the linearization of this Cluster is optimal. */
99
    bool IsOptimal() const noexcept
100
0
    {
101
0
        return m_quality == QualityLevel::OPTIMAL;
102
0
    }
103
    /** Whether this cluster requires splitting. */
104
    bool NeedsSplitting() const noexcept
105
0
    {
106
0
        return m_quality == QualityLevel::NEEDS_SPLIT ||
107
0
               m_quality == QualityLevel::NEEDS_SPLIT_ACCEPTABLE;
108
0
    }
109
    /** Get the number of transactions in this Cluster. */
110
0
    LinearizationIndex GetTxCount() const noexcept { return m_linearization.size(); }
111
    /** Given a DepGraphIndex into this Cluster, find the corresponding GraphIndex. */
112
0
    GraphIndex GetClusterEntry(DepGraphIndex index) const noexcept { return m_mapping[index]; }
113
    /** Only called by Graph::SwapIndexes. */
114
0
    void UpdateMapping(DepGraphIndex cluster_idx, GraphIndex graph_idx) noexcept { m_mapping[cluster_idx] = graph_idx; }
115
    /** Push changes to Cluster and its linearization to the TxGraphImpl Entry objects. */
116
    void Updated(TxGraphImpl& graph) noexcept;
117
    /** Create a copy of this Cluster in staging, returning a pointer to it (used by PullIn). */
118
    Cluster* CopyToStaging(TxGraphImpl& graph) const noexcept;
119
    /** Get the list of Clusters in main that conflict with this one (which is assumed to be in staging). */
120
    void GetConflicts(const TxGraphImpl& graph, std::vector<Cluster*>& out) const noexcept;
121
    /** Mark all the Entry objects belonging to this staging Cluster as missing. The Cluster must be
122
     *  deleted immediately after. */
123
    void MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept;
124
    /** Remove all transactions from a Cluster. */
125
    void Clear(TxGraphImpl& graph) noexcept;
126
    /** Change a Cluster's level from 1 (staging) to 0 (main). */
127
    void MoveToMain(TxGraphImpl& graph) noexcept;
128
129
    // Functions that implement the Cluster-specific side of internal TxGraphImpl mutations.
130
131
    /** Apply all removals from the front of to_remove that apply to this Cluster, popping them
132
     *  off. These must be at least one such entry. */
133
    void ApplyRemovals(TxGraphImpl& graph, std::span<GraphIndex>& to_remove) noexcept;
134
    /** Split this cluster (must have a NEEDS_SPLIT* quality). Returns whether to delete this
135
     *  Cluster afterwards. */
136
    [[nodiscard]] bool Split(TxGraphImpl& graph) noexcept;
137
    /** Move all transactions from cluster to *this (as separate components). */
138
    void Merge(TxGraphImpl& graph, Cluster& cluster) noexcept;
139
    /** Given a span of (parent, child) pairs that all belong to this Cluster, apply them. */
140
    void ApplyDependencies(TxGraphImpl& graph, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept;
141
    /** Improve the linearization of this Cluster. */
142
    void Relinearize(TxGraphImpl& graph, uint64_t max_iters) noexcept;
143
144
    // Functions that implement the Cluster-specific side of public TxGraph functions.
145
146
    /** Process elements from the front of args that apply to this cluster, and append Refs for the
147
     *  union of their ancestors to output. */
148
    void GetAncestorRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept;
149
    /** Process elements from the front of args that apply to this cluster, and append Refs for the
150
     *  union of their descendants to output. */
151
    void GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept;
152
    /** Get a vector of Refs for all elements of this Cluster, in linearization order. */
153
    std::vector<TxGraph::Ref*> GetClusterRefs(const TxGraphImpl& graph) noexcept;
154
    /** Get the individual transaction feerate of a Cluster element. */
155
    FeePerWeight GetIndividualFeerate(DepGraphIndex idx) noexcept;
156
    /** Modify the fee of a Cluster element. */
157
    void SetFee(TxGraphImpl& graph, DepGraphIndex idx, int64_t fee) noexcept;
158
159
    // Debugging functions.
160
161
    void SanityCheck(const TxGraphImpl& graph, int level) const;
162
};
163
164
165
/** The transaction graph, including staged changes.
166
 *
167
 * The overall design of the data structure consists of 3 interlinked representations:
168
 * - The transactions (held as a vector of TxGraphImpl::Entry inside TxGraphImpl).
169
 * - The clusters (Cluster objects in per-quality vectors inside TxGraphImpl::ClusterSet).
170
 * - The Refs (TxGraph::Ref objects, held externally by users of the TxGraph class)
171
 *
172
 * The Clusters are kept in one or two ClusterSet objects, one for the "main" graph, and one for
173
 * the proposed changes ("staging"). If a transaction occurs in both, they share the same Entry,
174
 * but there will be a separate Cluster per graph.
175
 *
176
 * Clusters and Refs contain the index of the Entry objects they refer to, and the Entry objects
177
 * refer back to the Clusters and Refs the corresponding transaction is contained in.
178
 *
179
 * While redundant, this permits moving all of them independently, without invalidating things
180
 * or costly iteration to fix up everything:
181
 * - Entry objects can be moved to fill holes left by removed transactions in the Entry vector
182
 *   (see TxGraphImpl::Compact).
183
 * - Clusters can be rewritten continuously (removals can cause them to split, new dependencies
184
 *   can cause them to be merged).
185
 * - Ref objects can be held outside the class, while permitting them to be moved around, and
186
 *   inherited from.
187
 */
188
class TxGraphImpl final : public TxGraph
189
{
190
    friend class Cluster;
191
private:
192
    /** Internal RNG. */
193
    FastRandomContext m_rng;
194
    /** This TxGraphImpl's maximum cluster count limit. */
195
    const DepGraphIndex m_max_cluster_count;
196
197
    /** Information about one group of Clusters to be merged. */
198
    struct GroupEntry
199
    {
200
        /** Where the clusters to be merged start in m_group_clusters. */
201
        uint32_t m_cluster_offset;
202
        /** How many clusters to merge. */
203
        uint32_t m_cluster_count;
204
        /** Where the dependencies for this cluster group in m_deps_to_add start. */
205
        uint32_t m_deps_offset;
206
        /** How many dependencies to add. */
207
        uint32_t m_deps_count;
208
    };
209
210
    /** Information about all groups of Clusters to be merged. */
211
    struct GroupData
212
    {
213
        /** The groups of Clusters to be merged. */
214
        std::vector<GroupEntry> m_groups;
215
        /** Which clusters are to be merged. GroupEntry::m_cluster_offset indexes into this. */
216
        std::vector<Cluster*> m_group_clusters;
217
        /** Whether at least one of the groups cannot be applied because it would result in a
218
         *  Cluster that violates the cluster count limit. */
219
        bool m_group_oversized;
220
    };
221
222
    /** The collection of all Clusters in main or staged. */
223
    struct ClusterSet
224
    {
225
        /** The vectors of clusters, one vector per quality level. ClusterSetIndex indexes into each. */
226
        std::array<std::vector<std::unique_ptr<Cluster>>, int(QualityLevel::NONE)> m_clusters;
227
        /** Which removals have yet to be applied. */
228
        std::vector<GraphIndex> m_to_remove;
229
        /** Which dependencies are to be added ((parent,child) pairs). GroupData::m_deps_offset indexes
230
         *  into this. */
231
        std::vector<std::pair<GraphIndex, GraphIndex>> m_deps_to_add;
232
        /** Information about the merges to be performed, if known. */
233
        std::optional<GroupData> m_group_data = GroupData{};
234
        /** Which entries were removed in this ClusterSet (so they can be wiped on abort). This
235
         *  includes all entries which have an (R) removed locator at this level (staging only),
236
         *  plus optionally any transaction in m_unlinked. */
237
        std::vector<GraphIndex> m_removed;
238
        /** Total number of transactions in this graph (sum of all transaction counts in all
239
         *  Clusters, and for staging also those inherited from the main ClusterSet). */
240
        GraphIndex m_txcount{0};
241
        /** Whether this graph is oversized (if known). This roughly matches
242
         *  m_group_data->m_group_oversized, but may be known even if m_group_data is not. */
243
        std::optional<bool> m_oversized{false};
244
245
0
        ClusterSet() noexcept = default;
246
    };
247
248
    /** The main ClusterSet. */
249
    ClusterSet m_main_clusterset;
250
    /** The staging ClusterSet, if any. */
251
    std::optional<ClusterSet> m_staging_clusterset;
252
    /** Next sequence number to assign to created Clusters. */
253
    uint64_t m_next_sequence_counter{0};
254
255
    /** A Locator that describes whether, where, and in which Cluster an Entry appears.
256
     *  Every Entry has MAX_LEVELS locators, as it may appear in one Cluster per level.
257
     *
258
     *  Each level of a Locator is in one of three states:
259
     *
260
     *  - (P)resent: actually occurs in a Cluster at that level.
261
     *
262
     *  - (M)issing:
263
     *    - In the main graph:    the transaction does not exist in main.
264
     *    - In the staging graph: the transaction's existence is the same as in main. If it doesn't
265
     *                            exist in main, (M) in staging means it does not exist there
266
     *                            either. If it does exist in main, (M) in staging means the
267
     *                            cluster it is in has not been modified in staging, and thus the
268
     *                            transaction implicitly exists in staging too (without explicit
269
     *                            Cluster object; see PullIn() to create it in staging too).
270
     *
271
     *  - (R)emoved: only possible in staging; it means the transaction exists in main, but is
272
     *               removed in staging.
273
     *
274
     * The following combinations are possible:
275
     * - (M,M): the transaction doesn't exist in either graph.
276
     * - (P,M): the transaction exists in both, but only exists explicitly in a Cluster object in
277
     *          main. Its existence in staging is inherited from main.
278
     * - (P,P): the transaction exists in both, and is materialized in both. Thus, the clusters
279
     *          and/or their linearizations may be different in main and staging.
280
     * - (M,P): the transaction is added in staging, and does not exist in main.
281
     * - (P,R): the transaction exists in main, but is removed in staging.
282
     *
283
     * When staging does not exist, only (M,M) and (P,M) are possible.
284
     */
285
    struct Locator
286
    {
287
        /** Which Cluster the Entry appears in (nullptr = missing). */
288
        Cluster* cluster{nullptr};
289
        /** Where in the Cluster it appears (if cluster == nullptr: 0 = missing, -1 = removed). */
290
        DepGraphIndex index{0};
291
292
        /** Mark this Locator as missing (= same as lower level, or non-existing if level 0). */
293
0
        void SetMissing() noexcept { cluster = nullptr; index = 0; }
294
        /** Mark this Locator as removed (not allowed in level 0). */
295
0
        void SetRemoved() noexcept { cluster = nullptr; index = DepGraphIndex(-1); }
296
        /** Mark this Locator as present, in the specified Cluster. */
297
0
        void SetPresent(Cluster* c, DepGraphIndex i) noexcept { cluster = c; index = i; }
298
        /** Check if this Locator is missing. */
299
0
        bool IsMissing() const noexcept { return cluster == nullptr && index == 0; }
300
        /** Check if this Locator is removed. */
301
0
        bool IsRemoved() const noexcept { return cluster == nullptr && index == DepGraphIndex(-1); }
302
        /** Check if this Locator is present (in some Cluster). */
303
0
        bool IsPresent() const noexcept { return cluster != nullptr; }
304
    };
305
306
    /** Internal information about each transaction in a TxGraphImpl. */
307
    struct Entry
308
    {
309
        /** Pointer to the corresponding Ref object if any, or nullptr if unlinked. */
310
        Ref* m_ref{nullptr};
311
        /** Which Cluster and position therein this Entry appears in. ([0] = main, [1] = staged). */
312
        Locator m_locator[MAX_LEVELS];
313
        /** The chunk feerate of this transaction in main (if present in m_locator[0]). */
314
        FeePerWeight m_main_chunk_feerate;
315
        /** The position this transaction has in the main linearization (if present). */
316
        LinearizationIndex m_main_lin_index;
317
    };
318
319
    /** The set of all transactions (in all levels combined). GraphIndex values index into this. */
320
    std::vector<Entry> m_entries;
321
322
    /** Set of Entries which have no linked Ref anymore. */
323
    std::vector<GraphIndex> m_unlinked;
324
325
    /** Compare two Cluster* by their m_sequence value (while supporting nullptr). */
326
    static std::strong_ordering CompareClusters(Cluster* a, Cluster* b) noexcept
327
0
    {
328
        // The nullptr pointer compares before everything else.
329
0
        if (a == nullptr || b == nullptr) {
330
0
            return (a != nullptr) <=> (b != nullptr);
331
0
        }
332
        // If neither pointer is nullptr, compare the Clusters' sequence numbers.
333
0
        Assume(a == b || a->m_sequence != b->m_sequence);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
334
0
        return a->m_sequence <=> b->m_sequence;
335
0
    }
336
337
public:
338
    /** Construct a new TxGraphImpl with the specified maximum cluster count. */
339
    explicit TxGraphImpl(DepGraphIndex max_cluster_count) noexcept :
340
0
        m_max_cluster_count(max_cluster_count)
341
0
    {
342
0
        Assume(max_cluster_count >= 1);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
343
0
        Assume(max_cluster_count <= MAX_CLUSTER_COUNT_LIMIT);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
344
0
    }
345
346
    /** Destructor. */
347
    ~TxGraphImpl() noexcept;
348
349
    // Cannot move or copy (would invalidate TxGraphImpl* in Ref, MiningOrder, EvictionOrder).
350
    TxGraphImpl(const TxGraphImpl&) = delete;
351
    TxGraphImpl& operator=(const TxGraphImpl&) = delete;
352
    TxGraphImpl(TxGraphImpl&&) = delete;
353
    TxGraphImpl& operator=(TxGraphImpl&&) = delete;
354
355
    // Simple helper functions.
356
357
    /** Swap the Entry referred to by a and the one referred to by b. */
358
    void SwapIndexes(GraphIndex a, GraphIndex b) noexcept;
359
    /** If idx exists in the specified level ClusterSet (explicitly, or in the level below and not
360
    *   removed), return the Cluster it is in. Otherwise, return nullptr. */
361
    Cluster* FindCluster(GraphIndex idx, int level) const noexcept;
362
    /** Extract a Cluster from its ClusterSet. */
363
    std::unique_ptr<Cluster> ExtractCluster(int level, QualityLevel quality, ClusterSetIndex setindex) noexcept;
364
    /** Delete a Cluster. */
365
    void DeleteCluster(Cluster& cluster) noexcept;
366
    /** Insert a Cluster into its ClusterSet. */
367
    ClusterSetIndex InsertCluster(int level, std::unique_ptr<Cluster>&& cluster, QualityLevel quality) noexcept;
368
    /** Change the QualityLevel of a Cluster (identified by old_quality and old_index). */
369
    void SetClusterQuality(int level, QualityLevel old_quality, ClusterSetIndex old_index, QualityLevel new_quality) noexcept;
370
    /** Get the index of the top level ClusterSet (staging if it exists, main otherwise). */
371
0
    int GetTopLevel() const noexcept { return m_staging_clusterset.has_value(); }
372
    /** Get the specified level (staging if it exists and main_only is not specified, main otherwise). */
373
0
    int GetSpecifiedLevel(bool main_only) const noexcept { return m_staging_clusterset.has_value() && !main_only; }
374
    /** Get a reference to the ClusterSet at the specified level (which must exist). */
375
    ClusterSet& GetClusterSet(int level) noexcept;
376
    const ClusterSet& GetClusterSet(int level) const noexcept;
377
    /** Make a transaction not exist at a specified level. It must currently exist there. */
378
    void ClearLocator(int level, GraphIndex index) noexcept;
379
    /** Find which Clusters in main conflict with ones in staging. */
380
    std::vector<Cluster*> GetConflicts() const noexcept;
381
382
    // Functions for handling Refs.
383
384
    /** Only called by Ref's move constructor/assignment to update Ref locations. */
385
    void UpdateRef(GraphIndex idx, Ref& new_location) noexcept final
386
0
    {
387
0
        auto& entry = m_entries[idx];
388
0
        Assume(entry.m_ref != nullptr);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
389
0
        entry.m_ref = &new_location;
390
0
    }
391
392
    /** Only called by Ref::~Ref to unlink Refs, and Ref's move assignment. */
393
    void UnlinkRef(GraphIndex idx) noexcept final
394
0
    {
395
0
        auto& entry = m_entries[idx];
396
0
        Assume(entry.m_ref != nullptr);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
397
0
        entry.m_ref = nullptr;
398
        // Mark the transaction as to be removed in all levels where it explicitly or implicitly
399
        // exists.
400
0
        bool exists_anywhere{false};
401
0
        bool exists{false};
402
0
        for (int level = 0; level <= GetTopLevel(); ++level) {
403
0
            if (entry.m_locator[level].IsPresent()) {
404
0
                exists_anywhere = true;
405
0
                exists = true;
406
0
            } else if (entry.m_locator[level].IsRemoved()) {
407
0
                exists = false;
408
0
            }
409
0
            if (exists) {
410
0
                auto& clusterset = GetClusterSet(level);
411
0
                clusterset.m_to_remove.push_back(idx);
412
                // Force recomputation of grouping data.
413
0
                clusterset.m_group_data = std::nullopt;
414
                // Do not wipe the oversized state of main if staging exists. The reason for this
415
                // is that the alternative would mean that cluster merges may need to be applied to
416
                // a formerly-oversized main graph while staging exists (to satisfy chunk feerate
417
                // queries into main, for example), and such merges could conflict with pulls of
418
                // some of their constituents into staging.
419
0
                if (level == GetTopLevel() && clusterset.m_oversized == true) {
420
0
                    clusterset.m_oversized = std::nullopt;
421
0
                }
422
0
            }
423
0
        }
424
0
        m_unlinked.push_back(idx);
425
0
        if (!exists_anywhere) Compact();
426
0
    }
427
428
    // Functions related to various normalization/application steps.
429
    /** Get rid of unlinked Entry objects in m_entries, if possible (this changes the GraphIndex
430
     *  values for remaining Entry objects, so this only does something when no to-be-applied
431
     *  operations or staged removals referring to GraphIndexes remain). */
432
    void Compact() noexcept;
433
    /** If cluster is not in staging, copy it there, and return a pointer to it.
434
    *   Staging must exist, and this modifies the locators of its
435
    *   transactions from inherited (P,M) to explicit (P,P). */
436
    Cluster* PullIn(Cluster* cluster) noexcept;
437
    /** Apply all removals queued up in m_to_remove to the relevant Clusters (which get a
438
     *  NEEDS_SPLIT* QualityLevel) up to the specified level. */
439
    void ApplyRemovals(int up_to_level) noexcept;
440
    /** Split an individual cluster. */
441
    void Split(Cluster& cluster) noexcept;
442
    /** Split all clusters that need splitting up to the specified level. */
443
    void SplitAll(int up_to_level) noexcept;
444
    /** Populate m_group_data based on m_deps_to_add in the specified level. */
445
    void GroupClusters(int level) noexcept;
446
    /** Merge the specified clusters. */
447
    void Merge(std::span<Cluster*> to_merge) noexcept;
448
    /** Apply all m_deps_to_add to the relevant Clusters in the specified level. */
449
    void ApplyDependencies(int level) noexcept;
450
    /** Make a specified Cluster have quality ACCEPTABLE or OPTIMAL. */
451
    void MakeAcceptable(Cluster& cluster) noexcept;
452
    /** Make all Clusters at the specified level have quality ACCEPTABLE or OPTIMAL. */
453
    void MakeAllAcceptable(int level) noexcept;
454
455
    // Implementations for the public TxGraph interface.
456
457
    Ref AddTransaction(const FeePerWeight& feerate) noexcept final;
458
    void RemoveTransaction(const Ref& arg) noexcept final;
459
    void AddDependency(const Ref& parent, const Ref& child) noexcept final;
460
    void SetTransactionFee(const Ref&, int64_t fee) noexcept final;
461
462
    void DoWork() noexcept final;
463
464
    void StartStaging() noexcept final;
465
    void CommitStaging() noexcept final;
466
    void AbortStaging() noexcept final;
467
0
    bool HaveStaging() const noexcept final { return m_staging_clusterset.has_value(); }
468
469
    bool Exists(const Ref& arg, bool main_only = false) noexcept final;
470
    FeePerWeight GetMainChunkFeerate(const Ref& arg) noexcept final;
471
    FeePerWeight GetIndividualFeerate(const Ref& arg) noexcept final;
472
    std::vector<Ref*> GetCluster(const Ref& arg, bool main_only = false) noexcept final;
473
    std::vector<Ref*> GetAncestors(const Ref& arg, bool main_only = false) noexcept final;
474
    std::vector<Ref*> GetDescendants(const Ref& arg, bool main_only = false) noexcept final;
475
    std::vector<Ref*> GetAncestorsUnion(std::span<const Ref* const> args, bool main_only = false) noexcept final;
476
    std::vector<Ref*> GetDescendantsUnion(std::span<const Ref* const> args, bool main_only = false) noexcept final;
477
    GraphIndex GetTransactionCount(bool main_only = false) noexcept final;
478
    bool IsOversized(bool main_only = false) noexcept final;
479
    std::strong_ordering CompareMainOrder(const Ref& a, const Ref& b) noexcept final;
480
    GraphIndex CountDistinctClusters(std::span<const Ref* const> refs, bool main_only = false) noexcept final;
481
482
    void SanityCheck() const final;
483
};
484
485
TxGraphImpl::ClusterSet& TxGraphImpl::GetClusterSet(int level) noexcept
486
0
{
487
0
    if (level == 0) return m_main_clusterset;
488
0
    Assume(level == 1);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
489
0
    Assume(m_staging_clusterset.has_value());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
490
0
    return *m_staging_clusterset;
491
0
}
492
493
const TxGraphImpl::ClusterSet& TxGraphImpl::GetClusterSet(int level) const noexcept
494
0
{
495
0
    if (level == 0) return m_main_clusterset;
496
0
    Assume(level == 1);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
497
0
    Assume(m_staging_clusterset.has_value());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
498
0
    return *m_staging_clusterset;
499
0
}
500
501
void TxGraphImpl::ClearLocator(int level, GraphIndex idx) noexcept
502
0
{
503
0
    auto& entry = m_entries[idx];
504
0
    auto& clusterset = GetClusterSet(level);
505
0
    Assume(entry.m_locator[level].IsPresent());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
506
    // Change the locator from Present to Missing or Removed.
507
0
    if (level == 0 || !entry.m_locator[level - 1].IsPresent()) {
508
0
        entry.m_locator[level].SetMissing();
509
0
    } else {
510
0
        entry.m_locator[level].SetRemoved();
511
0
        clusterset.m_removed.push_back(idx);
512
0
    }
513
    // Update the transaction count.
514
0
    --clusterset.m_txcount;
515
    // If clearing main, adjust the status of Locators of this transaction in staging, if it exists.
516
0
    if (level == 0 && GetTopLevel() == 1) {
517
0
        if (entry.m_locator[1].IsRemoved()) {
518
0
            entry.m_locator[1].SetMissing();
519
0
        } else if (!entry.m_locator[1].IsPresent()) {
520
0
            --m_staging_clusterset->m_txcount;
521
0
        }
522
0
    }
523
0
}
524
525
void Cluster::Updated(TxGraphImpl& graph) noexcept
526
0
{
527
    // Update all the Locators for this Cluster's Entry objects.
528
0
    for (DepGraphIndex idx : m_linearization) {
529
0
        auto& entry = graph.m_entries[m_mapping[idx]];
530
0
        entry.m_locator[m_level].SetPresent(this, idx);
531
0
    }
532
    // If this is for the main graph (level = 0), and the Cluster's quality is ACCEPTABLE or
533
    // OPTIMAL, compute its chunking and store its information in the Entry's m_main_lin_index
534
    // and m_main_chunk_feerate. These fields are only accessed after making the entire graph
535
    // ACCEPTABLE, so it is pointless to compute these if we haven't reached that quality level
536
    // yet.
537
0
    if (m_level == 0 && IsAcceptable()) {
538
0
        LinearizationChunking chunking(m_depgraph, m_linearization);
539
0
        LinearizationIndex lin_idx{0};
540
        // Iterate over the chunks.
541
0
        for (unsigned chunk_idx = 0; chunk_idx < chunking.NumChunksLeft(); ++chunk_idx) {
542
0
            auto chunk = chunking.GetChunk(chunk_idx);
543
0
            Assume(chunk.transactions.Any());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
544
            // Iterate over the transactions in the linearization, which must match those in chunk.
545
0
            do {
546
0
                DepGraphIndex idx = m_linearization[lin_idx];
547
0
                GraphIndex graph_idx = m_mapping[idx];
548
0
                auto& entry = graph.m_entries[graph_idx];
549
0
                entry.m_main_lin_index = lin_idx++;
550
0
                entry.m_main_chunk_feerate = FeePerWeight::FromFeeFrac(chunk.feerate);
551
0
                Assume(chunk.transactions[idx]);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
552
0
                chunk.transactions.Reset(idx);
553
0
            } while(chunk.transactions.Any());
554
0
        }
555
0
    }
556
0
}
557
558
void Cluster::GetConflicts(const TxGraphImpl& graph, std::vector<Cluster*>& out) const noexcept
559
0
{
560
0
    Assume(m_level == 1);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
561
0
    for (auto i : m_linearization) {
562
0
        auto& entry = graph.m_entries[m_mapping[i]];
563
        // For every transaction Entry in this Cluster, if it also exists in a lower-level Cluster,
564
        // then that Cluster conflicts.
565
0
        if (entry.m_locator[0].IsPresent()) {
566
0
            out.push_back(entry.m_locator[0].cluster);
567
0
        }
568
0
    }
569
0
}
570
571
std::vector<Cluster*> TxGraphImpl::GetConflicts() const noexcept
572
0
{
573
0
    Assume(GetTopLevel() == 1);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
574
0
    auto& clusterset = GetClusterSet(1);
575
0
    std::vector<Cluster*> ret;
576
    // All main Clusters containing transactions in m_removed (so (P,R) ones) are conflicts.
577
0
    for (auto i : clusterset.m_removed) {
578
0
        auto& entry = m_entries[i];
579
0
        if (entry.m_locator[0].IsPresent()) {
580
0
            ret.push_back(entry.m_locator[0].cluster);
581
0
        }
582
0
    }
583
    // Then go over all Clusters at this level, and find their conflicts (the (P,P) ones).
584
0
    for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
585
0
        auto& clusters = clusterset.m_clusters[quality];
586
0
        for (const auto& cluster : clusters) {
587
0
            cluster->GetConflicts(*this, ret);
588
0
        }
589
0
    }
590
    // Deduplicate the result (the same Cluster may appear multiple times).
591
0
    std::sort(ret.begin(), ret.end(), [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
592
0
    ret.erase(std::unique(ret.begin(), ret.end()), ret.end());
593
0
    return ret;
594
0
}
595
596
Cluster* Cluster::CopyToStaging(TxGraphImpl& graph) const noexcept
597
0
{
598
    // Construct an empty Cluster.
599
0
    auto ret = std::make_unique<Cluster>(graph.m_next_sequence_counter++);
600
0
    auto ptr = ret.get();
601
    // Copy depgraph, mapping, and linearization/
602
0
    ptr->m_depgraph = m_depgraph;
603
0
    ptr->m_mapping = m_mapping;
604
0
    ptr->m_linearization = m_linearization;
605
    // Insert the new Cluster into the graph.
606
0
    graph.InsertCluster(1, std::move(ret), m_quality);
607
    // Update its Locators.
608
0
    ptr->Updated(graph);
609
0
    return ptr;
610
0
}
611
612
void Cluster::ApplyRemovals(TxGraphImpl& graph, std::span<GraphIndex>& to_remove) noexcept
613
0
{
614
    // Iterate over the prefix of to_remove that applies to this cluster.
615
0
    Assume(!to_remove.empty());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
616
0
    SetType todo;
617
0
    do {
618
0
        GraphIndex idx = to_remove.front();
619
0
        Assume(idx < graph.m_entries.size());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
620
0
        auto& entry = graph.m_entries[idx];
621
0
        auto& locator = entry.m_locator[m_level];
622
        // Stop once we hit an entry that applies to another Cluster.
623
0
        if (locator.cluster != this) break;
624
        // - Remember it in a set of to-remove DepGraphIndexes.
625
0
        todo.Set(locator.index);
626
        // - Remove from m_mapping. This isn't strictly necessary as unused positions in m_mapping
627
        //   are just never accessed, but set it to -1 here to increase the ability to detect a bug
628
        //   that causes it to be accessed regardless.
629
0
        m_mapping[locator.index] = GraphIndex(-1);
630
        // - Remove its linearization index from the Entry (if in main).
631
0
        if (m_level == 0) {
632
0
            entry.m_main_lin_index = LinearizationIndex(-1);
633
0
        }
634
        // - Mark it as missing/removed in the Entry's locator.
635
0
        graph.ClearLocator(m_level, idx);
636
0
        to_remove = to_remove.subspan(1);
637
0
    } while(!to_remove.empty());
638
639
0
    auto quality = m_quality;
640
0
    Assume(todo.Any());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
641
    // Wipe from the Cluster's DepGraph (this is O(n) regardless of the number of entries
642
    // removed, so we benefit from batching all the removals).
643
0
    m_depgraph.RemoveTransactions(todo);
644
0
    m_mapping.resize(m_depgraph.PositionRange());
645
646
    // First remove all removals at the end of the linearization.
647
0
    while (!m_linearization.empty() && todo[m_linearization.back()]) {
648
0
        todo.Reset(m_linearization.back());
649
0
        m_linearization.pop_back();
650
0
    }
651
0
    if (todo.None()) {
652
        // If no further removals remain, and thus all removals were at the end, we may be able
653
        // to leave the cluster at a better quality level.
654
0
        if (IsAcceptable(/*after_split=*/true)) {
655
0
            quality = QualityLevel::NEEDS_SPLIT_ACCEPTABLE;
656
0
        } else {
657
0
            quality = QualityLevel::NEEDS_SPLIT;
658
0
        }
659
0
    } else {
660
        // If more removals remain, filter those out of m_linearization.
661
0
        m_linearization.erase(std::remove_if(
662
0
            m_linearization.begin(),
663
0
            m_linearization.end(),
664
0
            [&](auto pos) { return todo[pos]; }), m_linearization.end());
665
0
        quality = QualityLevel::NEEDS_SPLIT;
666
0
    }
667
0
    graph.SetClusterQuality(m_level, m_quality, m_setindex, quality);
668
0
    Updated(graph);
669
0
}
670
671
void Cluster::Clear(TxGraphImpl& graph) noexcept
672
0
{
673
0
    for (auto i : m_linearization) {
674
0
        graph.ClearLocator(m_level, m_mapping[i]);
675
0
    }
676
0
    m_depgraph = {};
677
0
    m_linearization.clear();
678
0
    m_mapping.clear();
679
0
}
680
681
void Cluster::MoveToMain(TxGraphImpl& graph) noexcept
682
0
{
683
0
    Assume(m_level == 1);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
684
0
    for (auto i : m_linearization) {
685
0
        GraphIndex idx = m_mapping[i];
686
0
        auto& entry = graph.m_entries[idx];
687
0
        entry.m_locator[1].SetMissing();
688
0
    }
689
0
    auto quality = m_quality;
690
0
    auto cluster = graph.ExtractCluster(1, quality, m_setindex);
691
0
    graph.InsertCluster(0, std::move(cluster), quality);
692
0
    Updated(graph);
693
0
}
694
695
bool Cluster::Split(TxGraphImpl& graph) noexcept
696
0
{
697
    // This function can only be called when the Cluster needs splitting.
698
0
    Assume(NeedsSplitting());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
699
    // Determine the new quality the split-off Clusters will have.
700
0
    QualityLevel new_quality = IsAcceptable(/*after_split=*/true) ? QualityLevel::ACCEPTABLE
701
0
                                                                  : QualityLevel::NEEDS_RELINEARIZE;
702
    // If we're going to produce ACCEPTABLE clusters (i.e., when in NEEDS_SPLIT_ACCEPTABLE), we
703
    // need to post-linearize to make sure the split-out versions are all connected (as
704
    // connectivity may have changed by removing part of the cluster). This could be done on each
705
    // resulting split-out cluster separately, but it is simpler to do it once up front before
706
    // splitting. This step is not necessary if the resulting clusters are NEEDS_RELINEARIZE, as
707
    // they will be post-linearized anyway in MakeAcceptable().
708
0
    if (new_quality == QualityLevel::ACCEPTABLE) {
709
0
        PostLinearize(m_depgraph, m_linearization);
710
0
    }
711
    /** Which positions are still left in this Cluster. */
712
0
    auto todo = m_depgraph.Positions();
713
    /** Mapping from transaction positions in this Cluster to the Cluster where it ends up, and
714
     *  its position therein. */
715
0
    std::vector<std::pair<Cluster*, DepGraphIndex>> remap(m_depgraph.PositionRange());
716
0
    std::vector<Cluster*> new_clusters;
717
0
    bool first{true};
718
    // Iterate over the connected components of this Cluster's m_depgraph.
719
0
    while (todo.Any()) {
720
0
        auto component = m_depgraph.FindConnectedComponent(todo);
721
0
        if (first && component == todo) {
722
            // The existing Cluster is an entire component. Leave it be, but update its quality.
723
0
            Assume(todo == m_depgraph.Positions());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
724
0
            graph.SetClusterQuality(m_level, m_quality, m_setindex, new_quality);
725
            // If this made the quality ACCEPTABLE or OPTIMAL, we need to compute and cache its
726
            // chunking.
727
0
            Updated(graph);
728
0
            return false;
729
0
        }
730
0
        first = false;
731
        // Construct a new Cluster to hold the found component.
732
0
        auto new_cluster = std::make_unique<Cluster>(graph.m_next_sequence_counter++);
733
0
        new_clusters.push_back(new_cluster.get());
734
        // Remember that all the component's transactions go to this new Cluster. The positions
735
        // will be determined below, so use -1 for now.
736
0
        for (auto i : component) {
737
0
            remap[i] = {new_cluster.get(), DepGraphIndex(-1)};
738
0
        }
739
0
        graph.InsertCluster(m_level, std::move(new_cluster), new_quality);
740
0
        todo -= component;
741
0
    }
742
    // Redistribute the transactions.
743
0
    for (auto i : m_linearization) {
744
        /** The cluster which transaction originally in position i is moved to. */
745
0
        Cluster* new_cluster = remap[i].first;
746
        // Copy the transaction to the new cluster's depgraph, and remember the position.
747
0
        remap[i].second = new_cluster->m_depgraph.AddTransaction(m_depgraph.FeeRate(i));
748
        // Create new mapping entry.
749
0
        new_cluster->m_mapping.push_back(m_mapping[i]);
750
        // Create a new linearization entry. As we're only appending transactions, they equal the
751
        // DepGraphIndex.
752
0
        new_cluster->m_linearization.push_back(remap[i].second);
753
0
    }
754
    // Redistribute the dependencies.
755
0
    for (auto i : m_linearization) {
756
        /** The cluster transaction in position i is moved to. */
757
0
        Cluster* new_cluster = remap[i].first;
758
        // Copy its parents, translating positions.
759
0
        SetType new_parents;
760
0
        for (auto par : m_depgraph.GetReducedParents(i)) new_parents.Set(remap[par].second);
761
0
        new_cluster->m_depgraph.AddDependencies(new_parents, remap[i].second);
762
0
    }
763
    // Update all the Locators of moved transactions.
764
0
    for (Cluster* new_cluster : new_clusters) {
765
0
        new_cluster->Updated(graph);
766
0
    }
767
    // Wipe this Cluster, and return that it needs to be deleted.
768
0
    m_depgraph = DepGraph<SetType>{};
769
0
    m_mapping.clear();
770
0
    m_linearization.clear();
771
0
    return true;
772
0
}
773
774
void Cluster::Merge(TxGraphImpl& graph, Cluster& other) noexcept
775
0
{
776
    /** Vector to store the positions in this Cluster for each position in other. */
777
0
    std::vector<DepGraphIndex> remap(other.m_depgraph.PositionRange());
778
    // Iterate over all transactions in the other Cluster (the one being absorbed).
779
0
    for (auto pos : other.m_linearization) {
780
0
        auto idx = other.m_mapping[pos];
781
        // Copy the transaction into this Cluster, and remember its position.
782
0
        auto new_pos = m_depgraph.AddTransaction(other.m_depgraph.FeeRate(pos));
783
0
        remap[pos] = new_pos;
784
0
        if (new_pos == m_mapping.size()) {
785
0
            m_mapping.push_back(idx);
786
0
        } else {
787
0
            m_mapping[new_pos] = idx;
788
0
        }
789
0
        m_linearization.push_back(new_pos);
790
        // Copy the transaction's dependencies, translating them using remap. Note that since
791
        // pos iterates over other.m_linearization, which is in topological order, all parents
792
        // of pos should already be in remap.
793
0
        SetType parents;
794
0
        for (auto par : other.m_depgraph.GetReducedParents(pos)) {
795
0
            parents.Set(remap[par]);
796
0
        }
797
0
        m_depgraph.AddDependencies(parents, remap[pos]);
798
        // Update the transaction's Locator. There is no need to call Updated() to update chunk
799
        // feerates, as Updated() will be invoked by Cluster::ApplyDependencies on the resulting
800
        // merged Cluster later anyway).
801
0
        graph.m_entries[idx].m_locator[m_level].SetPresent(this, new_pos);
802
0
    }
803
    // Purge the other Cluster, now that everything has been moved.
804
0
    other.m_depgraph = DepGraph<SetType>{};
805
0
    other.m_linearization.clear();
806
0
    other.m_mapping.clear();
807
0
}
808
809
void Cluster::ApplyDependencies(TxGraphImpl& graph, std::span<std::pair<GraphIndex, GraphIndex>> to_apply) noexcept
810
0
{
811
    // This function is invoked by TxGraphImpl::ApplyDependencies after merging groups of Clusters
812
    // between which dependencies are added, which simply concatenates their linearizations. Invoke
813
    // PostLinearize, which has the effect that the linearization becomes a merge-sort of the
814
    // constituent linearizations. Do this here rather than in Cluster::Merge, because this
815
    // function is only invoked once per merged Cluster, rather than once per constituent one.
816
    // This concatenation + post-linearization could be replaced with an explicit merge-sort.
817
0
    PostLinearize(m_depgraph, m_linearization);
818
819
    // Sort the list of dependencies to apply by child, so those can be applied in batch.
820
0
    std::sort(to_apply.begin(), to_apply.end(), [](auto& a, auto& b) { return a.second < b.second; });
821
    // Iterate over groups of to-be-added dependencies with the same child.
822
0
    auto it = to_apply.begin();
823
0
    while (it != to_apply.end()) {
824
0
        auto& first_child = graph.m_entries[it->second].m_locator[m_level];
825
0
        const auto child_idx = first_child.index;
826
        // Iterate over all to-be-added dependencies within that same child, gather the relevant
827
        // parents.
828
0
        SetType parents;
829
0
        while (it != to_apply.end()) {
830
0
            auto& child = graph.m_entries[it->second].m_locator[m_level];
831
0
            auto& parent = graph.m_entries[it->first].m_locator[m_level];
832
0
            Assume(child.cluster == this && parent.cluster == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
833
0
            if (child.index != child_idx) break;
834
0
            parents.Set(parent.index);
835
0
            ++it;
836
0
        }
837
        // Push all dependencies to the underlying DepGraph. Note that this is O(N) in the size of
838
        // the cluster, regardless of the number of parents being added, so batching them together
839
        // has a performance benefit.
840
0
        m_depgraph.AddDependencies(parents, child_idx);
841
0
    }
842
843
    // Finally fix the linearization, as the new dependencies may have invalidated the
844
    // linearization, and post-linearize it to fix up the worst problems with it.
845
0
    FixLinearization(m_depgraph, m_linearization);
846
0
    PostLinearize(m_depgraph, m_linearization);
847
848
    // Finally push the changes to graph.m_entries.
849
0
    Updated(graph);
850
0
}
851
852
TxGraphImpl::~TxGraphImpl() noexcept
853
0
{
854
    // If Refs outlive the TxGraphImpl they refer to, unlink them, so that their destructor does not
855
    // try to reach into a non-existing TxGraphImpl anymore.
856
0
    for (auto& entry : m_entries) {
857
0
        if (entry.m_ref != nullptr) {
858
0
            GetRefGraph(*entry.m_ref) = nullptr;
859
0
        }
860
0
    }
861
0
}
862
863
std::unique_ptr<Cluster> TxGraphImpl::ExtractCluster(int level, QualityLevel quality, ClusterSetIndex setindex) noexcept
864
0
{
865
0
    Assume(quality != QualityLevel::NONE);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
866
867
0
    auto& clusterset = GetClusterSet(level);
868
0
    auto& quality_clusters = clusterset.m_clusters[int(quality)];
869
0
    Assume(setindex < quality_clusters.size());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
870
871
    // Extract the Cluster-owning unique_ptr.
872
0
    std::unique_ptr<Cluster> ret = std::move(quality_clusters[setindex]);
873
0
    ret->m_quality = QualityLevel::NONE;
874
0
    ret->m_setindex = ClusterSetIndex(-1);
875
0
    ret->m_level = -1;
876
877
    // Clean up space in quality_cluster.
878
0
    auto max_setindex = quality_clusters.size() - 1;
879
0
    if (setindex != max_setindex) {
880
        // If the cluster was not the last element of quality_clusters, move that to take its place.
881
0
        quality_clusters.back()->m_setindex = setindex;
882
0
        quality_clusters.back()->m_level = level;
883
0
        quality_clusters[setindex] = std::move(quality_clusters.back());
884
0
    }
885
    // The last element of quality_clusters is now unused; drop it.
886
0
    quality_clusters.pop_back();
887
888
0
    return ret;
889
0
}
890
891
ClusterSetIndex TxGraphImpl::InsertCluster(int level, std::unique_ptr<Cluster>&& cluster, QualityLevel quality) noexcept
892
0
{
893
    // Cannot insert with quality level NONE (as that would mean not inserted).
894
0
    Assume(quality != QualityLevel::NONE);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
895
    // The passed-in Cluster must not currently be in the TxGraphImpl.
896
0
    Assume(cluster->m_quality == QualityLevel::NONE);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
897
898
    // Append it at the end of the relevant TxGraphImpl::m_cluster.
899
0
    auto& clusterset = GetClusterSet(level);
900
0
    auto& quality_clusters = clusterset.m_clusters[int(quality)];
901
0
    ClusterSetIndex ret = quality_clusters.size();
902
0
    cluster->m_quality = quality;
903
0
    cluster->m_setindex = ret;
904
0
    cluster->m_level = level;
905
0
    quality_clusters.push_back(std::move(cluster));
906
0
    return ret;
907
0
}
908
909
void TxGraphImpl::SetClusterQuality(int level, QualityLevel old_quality, ClusterSetIndex old_index, QualityLevel new_quality) noexcept
910
0
{
911
0
    Assume(new_quality != QualityLevel::NONE);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
912
913
    // Don't do anything if the quality did not change.
914
0
    if (old_quality == new_quality) return;
915
    // Extract the cluster from where it currently resides.
916
0
    auto cluster_ptr = ExtractCluster(level, old_quality, old_index);
917
    // And re-insert it where it belongs.
918
0
    InsertCluster(level, std::move(cluster_ptr), new_quality);
919
0
}
920
921
void TxGraphImpl::DeleteCluster(Cluster& cluster) noexcept
922
0
{
923
    // Extract the cluster from where it currently resides.
924
0
    auto cluster_ptr = ExtractCluster(cluster.m_level, cluster.m_quality, cluster.m_setindex);
925
    // And throw it away.
926
0
    cluster_ptr.reset();
927
0
}
928
929
Cluster* TxGraphImpl::FindCluster(GraphIndex idx, int level) const noexcept
930
0
{
931
0
    Assume(level >= 0 && level <= GetTopLevel());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
932
0
    auto& entry = m_entries[idx];
933
    // Search the entry's locators from top to bottom.
934
0
    for (int l = level; l >= 0; --l) {
935
        // If the locator is missing, dig deeper; it may exist at a lower level and therefore be
936
        // implicitly existing at this level too.
937
0
        if (entry.m_locator[l].IsMissing()) continue;
938
        // If the locator has the entry marked as explicitly removed, stop.
939
0
        if (entry.m_locator[l].IsRemoved()) break;
940
        // Otherwise, we have found the topmost ClusterSet that contains this entry.
941
0
        return entry.m_locator[l].cluster;
942
0
    }
943
    // If no non-empty locator was found, or an explicitly removed was hit, return nothing.
944
0
    return nullptr;
945
0
}
946
947
Cluster* TxGraphImpl::PullIn(Cluster* cluster) noexcept
948
0
{
949
0
    int to_level = GetTopLevel();
950
0
    Assume(to_level == 1);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
951
0
    int level = cluster->m_level;
952
0
    Assume(level <= to_level);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
953
    // Copy the Cluster from main to staging, if it's not already there.
954
0
    if (level == 0) {
955
        // Make the Cluster Acceptable before copying. This isn't strictly necessary, but doing it
956
        // now avoids doing double work later.
957
0
        MakeAcceptable(*cluster);
958
0
        cluster = cluster->CopyToStaging(*this);
959
0
    }
960
0
    return cluster;
961
0
}
962
963
void TxGraphImpl::ApplyRemovals(int up_to_level) noexcept
964
0
{
965
0
    Assume(up_to_level >= 0 && up_to_level <= GetTopLevel());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
966
0
    for (int level = 0; level <= up_to_level; ++level) {
967
0
        auto& clusterset = GetClusterSet(level);
968
0
        auto& to_remove = clusterset.m_to_remove;
969
        // Skip if there is nothing to remove in this level.
970
0
        if (to_remove.empty()) continue;
971
        // Pull in all Clusters that are not in staging.
972
0
        if (level == 1) {
973
0
            for (GraphIndex index : to_remove) {
974
0
                auto cluster = FindCluster(index, level);
975
0
                if (cluster != nullptr) PullIn(cluster);
976
0
            }
977
0
        }
978
        // Group the set of to-be-removed entries by Cluster::m_sequence.
979
0
        std::sort(to_remove.begin(), to_remove.end(), [&](GraphIndex a, GraphIndex b) noexcept {
980
0
            Cluster* cluster_a = m_entries[a].m_locator[level].cluster;
981
0
            Cluster* cluster_b = m_entries[b].m_locator[level].cluster;
982
0
            return CompareClusters(cluster_a, cluster_b) < 0;
983
0
        });
984
        // Process per Cluster.
985
0
        std::span to_remove_span{to_remove};
986
0
        while (!to_remove_span.empty()) {
987
0
            Cluster* cluster = m_entries[to_remove_span.front()].m_locator[level].cluster;
988
0
            if (cluster != nullptr) {
989
                // If the first to_remove_span entry's Cluster exists, hand to_remove_span to it, so it
990
                // can pop off whatever applies to it.
991
0
                cluster->ApplyRemovals(*this, to_remove_span);
992
0
            } else {
993
                // Otherwise, skip this already-removed entry. This may happen when
994
                // RemoveTransaction was called twice on the same Ref, for example.
995
0
                to_remove_span = to_remove_span.subspan(1);
996
0
            }
997
0
        }
998
0
        to_remove.clear();
999
0
    }
1000
0
    Compact();
1001
0
}
1002
1003
void TxGraphImpl::SwapIndexes(GraphIndex a, GraphIndex b) noexcept
1004
0
{
1005
0
    Assume(a < m_entries.size());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1006
0
    Assume(b < m_entries.size());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1007
    // Swap the Entry objects.
1008
0
    std::swap(m_entries[a], m_entries[b]);
1009
    // Iterate over both objects.
1010
0
    for (int i = 0; i < 2; ++i) {
1011
0
        GraphIndex idx = i ? b : a;
1012
0
        Entry& entry = m_entries[idx];
1013
        // Update linked Ref, if any exists.
1014
0
        if (entry.m_ref) GetRefIndex(*entry.m_ref) = idx;
1015
        // Update the locators for both levels. The rest of the Entry information will not change,
1016
        // so no need to invoke Cluster::Updated().
1017
0
        for (int level = 0; level < MAX_LEVELS; ++level) {
1018
0
            Locator& locator = entry.m_locator[level];
1019
0
            if (locator.IsPresent()) {
1020
0
                locator.cluster->UpdateMapping(locator.index, idx);
1021
0
            }
1022
0
        }
1023
0
    }
1024
0
}
1025
1026
void TxGraphImpl::Compact() noexcept
1027
0
{
1028
    // We cannot compact while any to-be-applied operations or staged removals remain as we'd need
1029
    // to rewrite them. It is easier to delay the compaction until they have been applied.
1030
0
    if (!m_main_clusterset.m_deps_to_add.empty()) return;
1031
0
    if (!m_main_clusterset.m_to_remove.empty()) return;
1032
0
    Assume(m_main_clusterset.m_removed.empty()); // non-staging m_removed is always empty
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1033
0
    if (m_staging_clusterset.has_value()) {
1034
0
        if (!m_staging_clusterset->m_deps_to_add.empty()) return;
1035
0
        if (!m_staging_clusterset->m_to_remove.empty()) return;
1036
0
        if (!m_staging_clusterset->m_removed.empty()) return;
1037
0
    }
1038
1039
    // Sort the GraphIndexes that need to be cleaned up. They are sorted in reverse, so the last
1040
    // ones get processed first. This means earlier-processed GraphIndexes will not cause moving of
1041
    // later-processed ones during the "swap with end of m_entries" step below (which might
1042
    // invalidate them).
1043
0
    std::sort(m_unlinked.begin(), m_unlinked.end(), std::greater{});
1044
1045
0
    auto last = GraphIndex(-1);
1046
0
    for (GraphIndex idx : m_unlinked) {
1047
        // m_unlinked should never contain the same GraphIndex twice (the code below would fail
1048
        // if so, because GraphIndexes get invalidated by removing them).
1049
0
        Assume(idx != last);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1050
0
        last = idx;
1051
1052
        // Make sure the entry is unlinked.
1053
0
        Entry& entry = m_entries[idx];
1054
0
        Assume(entry.m_ref == nullptr);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1055
        // Make sure the entry does not occur in the graph.
1056
0
        for (int level = 0; level < MAX_LEVELS; ++level) {
1057
0
            Assume(!entry.m_locator[level].IsPresent());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1058
0
        }
1059
1060
        // Move the entry to the end.
1061
0
        if (idx != m_entries.size() - 1) SwapIndexes(idx, m_entries.size() - 1);
1062
        // Drop the entry for idx, now that it is at the end.
1063
0
        m_entries.pop_back();
1064
0
    }
1065
0
    m_unlinked.clear();
1066
0
}
1067
1068
void TxGraphImpl::Split(Cluster& cluster) noexcept
1069
0
{
1070
    // To split a Cluster, first make sure all removals are applied (as we might need to split
1071
    // again afterwards otherwise).
1072
0
    ApplyRemovals(cluster.m_level);
1073
0
    bool del = cluster.Split(*this);
1074
0
    if (del) {
1075
        // Cluster::Split reports whether the Cluster is to be deleted.
1076
0
        DeleteCluster(cluster);
1077
0
    }
1078
0
}
1079
1080
void TxGraphImpl::SplitAll(int up_to_level) noexcept
1081
0
{
1082
0
    Assume(up_to_level >= 0 && up_to_level <= GetTopLevel());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1083
    // Before splitting all Cluster, first make sure all removals are applied.
1084
0
    ApplyRemovals(up_to_level);
1085
0
    for (int level = 0; level <= up_to_level; ++level) {
1086
0
        for (auto quality : {QualityLevel::NEEDS_SPLIT, QualityLevel::NEEDS_SPLIT_ACCEPTABLE}) {
1087
0
            auto& queue = GetClusterSet(level).m_clusters[int(quality)];
1088
0
            while (!queue.empty()) {
1089
0
                Split(*queue.back().get());
1090
0
            }
1091
0
        }
1092
0
    }
1093
0
}
1094
1095
void TxGraphImpl::GroupClusters(int level) noexcept
1096
0
{
1097
0
    auto& clusterset = GetClusterSet(level);
1098
    // If the groupings have been computed already, nothing is left to be done.
1099
0
    if (clusterset.m_group_data.has_value()) return;
1100
1101
    // Before computing which Clusters need to be merged together, first apply all removals and
1102
    // split the Clusters into connected components. If we would group first, we might end up
1103
    // with inefficient and/or oversized Clusters which just end up being split again anyway.
1104
0
    SplitAll(level);
1105
1106
    /** Annotated clusters: an entry for each Cluster, together with the sequence number for the
1107
     *  representative for the partition it is in (initially its own, later that of the
1108
     *  to-be-merged group). */
1109
0
    std::vector<std::pair<Cluster*, uint64_t>> an_clusters;
1110
    /** Annotated dependencies: an entry for each m_deps_to_add entry (excluding ones that apply
1111
     *  to removed transactions), together with the sequence number of the representative root of
1112
     *  Clusters it applies to (initially that of the child Cluster, later that of the
1113
     *  to-be-merged group). */
1114
0
    std::vector<std::pair<std::pair<GraphIndex, GraphIndex>, uint64_t>> an_deps;
1115
1116
    // Construct a an_clusters entry for every parent and child in the to-be-applied dependencies,
1117
    // and an an_deps entry for each dependency to be applied.
1118
0
    an_deps.reserve(clusterset.m_deps_to_add.size());
1119
0
    for (const auto& [par, chl] : clusterset.m_deps_to_add) {
1120
0
        auto par_cluster = FindCluster(par, level);
1121
0
        auto chl_cluster = FindCluster(chl, level);
1122
        // Skip dependencies for which the parent or child transaction is removed.
1123
0
        if (par_cluster == nullptr || chl_cluster == nullptr) continue;
1124
0
        an_clusters.emplace_back(par_cluster, par_cluster->m_sequence);
1125
        // Do not include a duplicate when parent and child are identical, as it'll be removed
1126
        // below anyway.
1127
0
        if (chl_cluster != par_cluster) an_clusters.emplace_back(chl_cluster, chl_cluster->m_sequence);
1128
        // Add entry to an_deps, using the child sequence number.
1129
0
        an_deps.emplace_back(std::pair{par, chl}, chl_cluster->m_sequence);
1130
0
    }
1131
    // Sort and deduplicate an_clusters, so we end up with a sorted list of all involved Clusters
1132
    // to which dependencies apply.
1133
0
    std::sort(an_clusters.begin(), an_clusters.end(), [](auto& a, auto& b) noexcept { return a.second < b.second; });
1134
0
    an_clusters.erase(std::unique(an_clusters.begin(), an_clusters.end()), an_clusters.end());
1135
    // Sort an_deps by applying the same order to the involved child cluster.
1136
0
    std::sort(an_deps.begin(), an_deps.end(), [&](auto& a, auto& b) noexcept { return a.second < b.second; });
1137
1138
    // Run the union-find algorithm to to find partitions of the input Clusters which need to be
1139
    // grouped together. See https://en.wikipedia.org/wiki/Disjoint-set_data_structure.
1140
0
    {
1141
        /** Each PartitionData entry contains information about a single input Cluster. */
1142
0
        struct PartitionData
1143
0
        {
1144
            /** The sequence number of the cluster this holds information for. */
1145
0
            uint64_t sequence;
1146
            /** All PartitionData entries belonging to the same partition are organized in a tree.
1147
             *  Each element points to its parent, or to itself if it is the root. The root is then
1148
             *  a representative for the entire tree, and can be found by walking upwards from any
1149
             *  element. */
1150
0
            PartitionData* parent;
1151
            /** (only if this is a root, so when parent == this) An upper bound on the height of
1152
             *  tree for this partition. */
1153
0
            unsigned rank;
1154
0
        };
1155
        /** Information about each input Cluster. Sorted by Cluster::m_sequence. */
1156
0
        std::vector<PartitionData> partition_data;
1157
1158
        /** Given a Cluster, find its corresponding PartitionData. */
1159
0
        auto locate_fn = [&](uint64_t sequence) noexcept -> PartitionData* {
1160
0
            auto it = std::lower_bound(partition_data.begin(), partition_data.end(), sequence,
1161
0
                                       [](auto& a, uint64_t seq) noexcept { return a.sequence < seq; });
1162
0
            Assume(it != partition_data.end());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1163
0
            Assume(it->sequence == sequence);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1164
0
            return &*it;
1165
0
        };
1166
1167
        /** Given a PartitionData, find the root of the tree it is in (its representative). */
1168
0
        static constexpr auto find_root_fn = [](PartitionData* data) noexcept -> PartitionData* {
1169
0
            while (data->parent != data) {
1170
                // Replace pointers to parents with pointers to grandparents.
1171
                // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Finding_set_representatives.
1172
0
                auto par = data->parent;
1173
0
                data->parent = par->parent;
1174
0
                data = par;
1175
0
            }
1176
0
            return data;
1177
0
        };
1178
1179
        /** Given two PartitionDatas, union the partitions they are in, and return their
1180
         *  representative. */
1181
0
        static constexpr auto union_fn = [](PartitionData* arg1, PartitionData* arg2) noexcept {
1182
            // Find the roots of the trees, and bail out if they are already equal (which would
1183
            // mean they are in the same partition already).
1184
0
            auto rep1 = find_root_fn(arg1);
1185
0
            auto rep2 = find_root_fn(arg2);
1186
0
            if (rep1 == rep2) return rep1;
1187
            // Pick the lower-rank root to become a child of the higher-rank one.
1188
            // See https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Union_by_rank.
1189
0
            if (rep1->rank < rep2->rank) std::swap(rep1, rep2);
1190
0
            rep2->parent = rep1;
1191
0
            rep1->rank += (rep1->rank == rep2->rank);
1192
0
            return rep1;
1193
0
        };
1194
1195
        // Start by initializing every Cluster as its own singleton partition.
1196
0
        partition_data.resize(an_clusters.size());
1197
0
        for (size_t i = 0; i < an_clusters.size(); ++i) {
1198
0
            partition_data[i].sequence = an_clusters[i].first->m_sequence;
1199
0
            partition_data[i].parent = &partition_data[i];
1200
0
            partition_data[i].rank = 0;
1201
0
        }
1202
1203
        // Run through all parent/child pairs in an_deps, and union the partitions their Clusters
1204
        // are in.
1205
0
        Cluster* last_chl_cluster{nullptr};
1206
0
        PartitionData* last_partition{nullptr};
1207
0
        for (const auto& [dep, _] : an_deps) {
1208
0
            auto [par, chl] = dep;
1209
0
            auto par_cluster = FindCluster(par, level);
1210
0
            auto chl_cluster = FindCluster(chl, level);
1211
0
            Assume(chl_cluster != nullptr && par_cluster != nullptr);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1212
            // Nothing to do if parent and child are in the same Cluster.
1213
0
            if (par_cluster == chl_cluster) continue;
1214
0
            Assume(par != chl);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1215
0
            if (chl_cluster == last_chl_cluster) {
1216
                // If the child Clusters is the same as the previous iteration, union with the
1217
                // tree they were in, avoiding the need for another lookup. Note that an_deps
1218
                // is sorted by child Cluster, so batches with the same child are expected.
1219
0
                last_partition = union_fn(locate_fn(par_cluster->m_sequence), last_partition);
1220
0
            } else {
1221
0
                last_chl_cluster = chl_cluster;
1222
0
                last_partition = union_fn(locate_fn(par_cluster->m_sequence), locate_fn(chl_cluster->m_sequence));
1223
0
            }
1224
0
        }
1225
1226
        // Update the sequence numbers in an_clusters and an_deps to be those of the partition
1227
        // representative.
1228
0
        auto deps_it = an_deps.begin();
1229
0
        for (size_t i = 0; i < partition_data.size(); ++i) {
1230
0
            auto& data = partition_data[i];
1231
            // Find the sequence of the representative of the partition Cluster i is in, and store
1232
            // it with the Cluster.
1233
0
            auto rep_seq = find_root_fn(&data)->sequence;
1234
0
            an_clusters[i].second = rep_seq;
1235
            // Find all dependencies whose child Cluster is Cluster i, and annotate them with rep.
1236
0
            while (deps_it != an_deps.end()) {
1237
0
                auto [par, chl] = deps_it->first;
1238
0
                auto chl_cluster = FindCluster(chl, level);
1239
0
                Assume(chl_cluster != nullptr);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1240
0
                if (chl_cluster->m_sequence > data.sequence) break;
1241
0
                deps_it->second = rep_seq;
1242
0
                ++deps_it;
1243
0
            }
1244
0
        }
1245
0
    }
1246
1247
    // Sort both an_clusters and an_deps by sequence number of the representative of the
1248
    // partition they are in, grouping all those applying to the same partition together.
1249
0
    std::sort(an_deps.begin(), an_deps.end(), [](auto& a, auto& b) noexcept { return a.second < b.second; });
1250
0
    std::sort(an_clusters.begin(), an_clusters.end(), [](auto& a, auto& b) noexcept { return a.second < b.second; });
1251
1252
    // Translate the resulting cluster groups to the m_group_data structure, and the dependencies
1253
    // back to m_deps_to_add.
1254
0
    clusterset.m_group_data = GroupData{};
1255
0
    clusterset.m_group_data->m_group_clusters.reserve(an_clusters.size());
1256
0
    clusterset.m_group_data->m_group_oversized = false;
1257
0
    clusterset.m_deps_to_add.clear();
1258
0
    clusterset.m_deps_to_add.reserve(an_deps.size());
1259
0
    auto an_deps_it = an_deps.begin();
1260
0
    auto an_clusters_it = an_clusters.begin();
1261
0
    while (an_clusters_it != an_clusters.end()) {
1262
        // Process all clusters/dependencies belonging to the partition with representative rep.
1263
0
        auto rep = an_clusters_it->second;
1264
        // Create and initialize a new GroupData entry for the partition.
1265
0
        auto& new_entry = clusterset.m_group_data->m_groups.emplace_back();
1266
0
        new_entry.m_cluster_offset = clusterset.m_group_data->m_group_clusters.size();
1267
0
        new_entry.m_cluster_count = 0;
1268
0
        new_entry.m_deps_offset = clusterset.m_deps_to_add.size();
1269
0
        new_entry.m_deps_count = 0;
1270
0
        uint32_t total_count{0};
1271
        // Add all its clusters to it (copying those from an_clusters to m_group_clusters).
1272
0
        while (an_clusters_it != an_clusters.end() && an_clusters_it->second == rep) {
1273
0
            clusterset.m_group_data->m_group_clusters.push_back(an_clusters_it->first);
1274
0
            total_count += an_clusters_it->first->GetTxCount();
1275
0
            ++an_clusters_it;
1276
0
            ++new_entry.m_cluster_count;
1277
0
        }
1278
        // Add all its dependencies to it (copying those back from an_deps to m_deps_to_add).
1279
0
        while (an_deps_it != an_deps.end() && an_deps_it->second == rep) {
1280
0
            clusterset.m_deps_to_add.push_back(an_deps_it->first);
1281
0
            ++an_deps_it;
1282
0
            ++new_entry.m_deps_count;
1283
0
        }
1284
        // Detect oversizedness.
1285
0
        if (total_count > m_max_cluster_count) {
1286
0
            clusterset.m_group_data->m_group_oversized = true;
1287
0
        }
1288
0
    }
1289
0
    Assume(an_deps_it == an_deps.end());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1290
0
    Assume(an_clusters_it == an_clusters.end());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1291
0
    clusterset.m_oversized = clusterset.m_group_data->m_group_oversized;
1292
0
    Compact();
1293
0
}
1294
1295
void TxGraphImpl::Merge(std::span<Cluster*> to_merge) noexcept
1296
0
{
1297
0
    Assume(!to_merge.empty());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1298
    // Nothing to do if a group consists of just a single Cluster.
1299
0
    if (to_merge.size() == 1) return;
1300
1301
    // Move the largest Cluster to the front of to_merge. As all transactions in other to-be-merged
1302
    // Clusters will be moved to that one, putting the largest one first minimizes the number of
1303
    // moves.
1304
0
    size_t max_size_pos{0};
1305
0
    DepGraphIndex max_size = to_merge[max_size_pos]->GetTxCount();
1306
0
    for (size_t i = 1; i < to_merge.size(); ++i) {
1307
0
        DepGraphIndex size = to_merge[i]->GetTxCount();
1308
0
        if (size > max_size) {
1309
0
            max_size_pos = i;
1310
0
            max_size = size;
1311
0
        }
1312
0
    }
1313
0
    if (max_size_pos != 0) std::swap(to_merge[0], to_merge[max_size_pos]);
1314
1315
    // Merge all further Clusters in the group into the first one, and delete them.
1316
0
    for (size_t i = 1; i < to_merge.size(); ++i) {
1317
0
        to_merge[0]->Merge(*this, *to_merge[i]);
1318
0
        DeleteCluster(*to_merge[i]);
1319
0
    }
1320
0
}
1321
1322
void TxGraphImpl::ApplyDependencies(int level) noexcept
1323
0
{
1324
0
    auto& clusterset = GetClusterSet(level);
1325
    // Do not bother computing groups if we already know the result will be oversized.
1326
0
    if (clusterset.m_oversized == true) return;
1327
    // Compute the groups of to-be-merged Clusters (which also applies all removals, and splits).
1328
0
    GroupClusters(level);
1329
0
    Assume(clusterset.m_group_data.has_value());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1330
    // Nothing to do if there are no dependencies to be added.
1331
0
    if (clusterset.m_deps_to_add.empty()) return;
1332
    // Dependencies cannot be applied if it would result in oversized clusters.
1333
0
    if (clusterset.m_group_data->m_group_oversized) return;
1334
1335
    // For each group of to-be-merged Clusters.
1336
0
    for (const auto& group_entry : clusterset.m_group_data->m_groups) {
1337
0
        auto cluster_span = std::span{clusterset.m_group_data->m_group_clusters}
1338
0
                                .subspan(group_entry.m_cluster_offset, group_entry.m_cluster_count);
1339
        // Pull in all the Clusters that contain dependencies.
1340
0
        if (level == 1) {
1341
0
            for (Cluster*& cluster : cluster_span) {
1342
0
                cluster = PullIn(cluster);
1343
0
            }
1344
0
        }
1345
        // Invoke Merge() to merge them into a single Cluster.
1346
0
        Merge(cluster_span);
1347
        // Actually apply all to-be-added dependencies (all parents and children from this grouping
1348
        // belong to the same Cluster at this point because of the merging above).
1349
0
        auto deps_span = std::span{clusterset.m_deps_to_add}
1350
0
                             .subspan(group_entry.m_deps_offset, group_entry.m_deps_count);
1351
0
        Assume(!deps_span.empty());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1352
0
        const auto& loc = m_entries[deps_span[0].second].m_locator[level];
1353
0
        Assume(loc.IsPresent());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1354
0
        loc.cluster->ApplyDependencies(*this, deps_span);
1355
0
    }
1356
1357
    // Wipe the list of to-be-added dependencies now that they are applied.
1358
0
    clusterset.m_deps_to_add.clear();
1359
0
    Compact();
1360
    // Also no further Cluster mergings are needed (note that we clear, but don't set to
1361
    // std::nullopt, as that would imply the groupings are unknown).
1362
0
    clusterset.m_group_data = GroupData{};
1363
0
}
1364
1365
void Cluster::Relinearize(TxGraphImpl& graph, uint64_t max_iters) noexcept
1366
0
{
1367
    // We can only relinearize Clusters that do not need splitting.
1368
0
    Assume(!NeedsSplitting());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1369
    // No work is required for Clusters which are already optimally linearized.
1370
0
    if (IsOptimal()) return;
1371
    // Invoke the actual linearization algorithm (passing in the existing one).
1372
0
    uint64_t rng_seed = graph.m_rng.rand64();
1373
0
    auto [linearization, optimal] = Linearize(m_depgraph, max_iters, rng_seed, m_linearization);
1374
    // Postlinearize if the result isn't optimal already. This guarantees (among other things)
1375
    // that the chunks of the resulting linearization are all connected.
1376
0
    if (!optimal) PostLinearize(m_depgraph, linearization);
1377
    // Update the linearization.
1378
0
    m_linearization = std::move(linearization);
1379
    // Update the Cluster's quality.
1380
0
    auto new_quality = optimal ? QualityLevel::OPTIMAL : QualityLevel::ACCEPTABLE;
1381
0
    graph.SetClusterQuality(m_level, m_quality, m_setindex, new_quality);
1382
    // Update the Entry objects.
1383
0
    Updated(graph);
1384
0
}
1385
1386
void TxGraphImpl::MakeAcceptable(Cluster& cluster) noexcept
1387
0
{
1388
    // Relinearize the Cluster if needed.
1389
0
    if (!cluster.NeedsSplitting() && !cluster.IsAcceptable()) {
1390
0
        cluster.Relinearize(*this, 10000);
1391
0
    }
1392
0
}
1393
1394
void TxGraphImpl::MakeAllAcceptable(int level) noexcept
1395
0
{
1396
0
    ApplyDependencies(level);
1397
0
    auto& clusterset = GetClusterSet(level);
1398
0
    if (clusterset.m_oversized == true) return;
1399
0
    auto& queue = clusterset.m_clusters[int(QualityLevel::NEEDS_RELINEARIZE)];
1400
0
    while (!queue.empty()) {
1401
0
        MakeAcceptable(*queue.back().get());
1402
0
    }
1403
0
}
1404
1405
0
Cluster::Cluster(uint64_t sequence) noexcept : m_sequence{sequence} {}
1406
1407
Cluster::Cluster(uint64_t sequence, TxGraphImpl& graph, const FeePerWeight& feerate, GraphIndex graph_index) noexcept :
1408
0
    m_sequence{sequence}
1409
0
{
1410
    // Create a new transaction in the DepGraph, and remember its position in m_mapping.
1411
0
    auto cluster_idx = m_depgraph.AddTransaction(feerate);
1412
0
    m_mapping.push_back(graph_index);
1413
0
    m_linearization.push_back(cluster_idx);
1414
0
}
1415
1416
TxGraph::Ref TxGraphImpl::AddTransaction(const FeePerWeight& feerate) noexcept
1417
0
{
1418
    // Construct a new Ref.
1419
0
    Ref ret;
1420
    // Construct a new Entry, and link it with the Ref.
1421
0
    auto idx = m_entries.size();
1422
0
    m_entries.emplace_back();
1423
0
    auto& entry = m_entries.back();
1424
0
    entry.m_ref = &ret;
1425
0
    GetRefGraph(ret) = this;
1426
0
    GetRefIndex(ret) = idx;
1427
    // Construct a new singleton Cluster (which is necessarily optimally linearized).
1428
0
    auto cluster = std::make_unique<Cluster>(m_next_sequence_counter++, *this, feerate, idx);
1429
0
    auto cluster_ptr = cluster.get();
1430
0
    int level = GetTopLevel();
1431
0
    auto& clusterset = GetClusterSet(level);
1432
0
    InsertCluster(level, std::move(cluster), QualityLevel::OPTIMAL);
1433
0
    cluster_ptr->Updated(*this);
1434
0
    ++clusterset.m_txcount;
1435
    // Return the Ref.
1436
0
    return ret;
1437
0
}
1438
1439
void TxGraphImpl::RemoveTransaction(const Ref& arg) noexcept
1440
0
{
1441
    // Don't do anything if the Ref is empty (which may be indicative of the transaction already
1442
    // having been removed).
1443
0
    if (GetRefGraph(arg) == nullptr) return;
1444
0
    Assume(GetRefGraph(arg) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1445
    // Find the Cluster the transaction is in, and stop if it isn't in any.
1446
0
    int level = GetTopLevel();
1447
0
    auto cluster = FindCluster(GetRefIndex(arg), level);
1448
0
    if (cluster == nullptr) return;
1449
    // Remember that the transaction is to be removed.
1450
0
    auto& clusterset = GetClusterSet(level);
1451
0
    clusterset.m_to_remove.push_back(GetRefIndex(arg));
1452
    // Wipe m_group_data (as it will need to be recomputed).
1453
0
    clusterset.m_group_data.reset();
1454
0
    if (clusterset.m_oversized == true) clusterset.m_oversized = std::nullopt;
1455
0
}
1456
1457
void TxGraphImpl::AddDependency(const Ref& parent, const Ref& child) noexcept
1458
0
{
1459
    // Don't do anything if either Ref is empty (which may be indicative of it having already been
1460
    // removed).
1461
0
    if (GetRefGraph(parent) == nullptr || GetRefGraph(child) == nullptr) return;
1462
0
    Assume(GetRefGraph(parent) == this && GetRefGraph(child) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1463
    // Don't do anything if this is a dependency on self.
1464
0
    if (GetRefIndex(parent) == GetRefIndex(child)) return;
1465
    // Find the Cluster the parent and child transaction are in, and stop if either appears to be
1466
    // already removed.
1467
0
    int level = GetTopLevel();
1468
0
    auto par_cluster = FindCluster(GetRefIndex(parent), level);
1469
0
    if (par_cluster == nullptr) return;
1470
0
    auto chl_cluster = FindCluster(GetRefIndex(child), level);
1471
0
    if (chl_cluster == nullptr) return;
1472
    // Remember that this dependency is to be applied.
1473
0
    auto& clusterset = GetClusterSet(level);
1474
0
    clusterset.m_deps_to_add.emplace_back(GetRefIndex(parent), GetRefIndex(child));
1475
    // Wipe m_group_data (as it will need to be recomputed).
1476
0
    clusterset.m_group_data.reset();
1477
0
    if (clusterset.m_oversized == false) clusterset.m_oversized = std::nullopt;
1478
0
}
1479
1480
bool TxGraphImpl::Exists(const Ref& arg, bool main_only) noexcept
1481
0
{
1482
0
    if (GetRefGraph(arg) == nullptr) return false;
1483
0
    Assume(GetRefGraph(arg) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1484
0
    size_t level = GetSpecifiedLevel(main_only);
1485
    // Make sure the transaction isn't scheduled for removal.
1486
0
    ApplyRemovals(level);
1487
0
    auto cluster = FindCluster(GetRefIndex(arg), level);
1488
0
    return cluster != nullptr;
1489
0
}
1490
1491
void Cluster::GetAncestorRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept
1492
0
{
1493
    /** The union of all ancestors to be returned. */
1494
0
    SetType ancestors_union;
1495
    // Process elements from the front of args, as long as they apply.
1496
0
    while (!args.empty()) {
1497
0
        if (args.front().first != this) break;
1498
0
        ancestors_union |= m_depgraph.Ancestors(args.front().second);
1499
0
        args = args.subspan(1);
1500
0
    }
1501
0
    Assume(ancestors_union.Any());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1502
    // Translate all ancestors (in arbitrary order) to Refs (if they have any), and return them.
1503
0
    for (auto idx : ancestors_union) {
1504
0
        const auto& entry = graph.m_entries[m_mapping[idx]];
1505
0
        Assume(entry.m_ref != nullptr);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1506
0
        output.push_back(entry.m_ref);
1507
0
    }
1508
0
}
1509
1510
void Cluster::GetDescendantRefs(const TxGraphImpl& graph, std::span<std::pair<Cluster*, DepGraphIndex>>& args, std::vector<TxGraph::Ref*>& output) noexcept
1511
0
{
1512
    /** The union of all descendants to be returned. */
1513
0
    SetType descendants_union;
1514
    // Process elements from the front of args, as long as they apply.
1515
0
    while (!args.empty()) {
1516
0
        if (args.front().first != this) break;
1517
0
        descendants_union |= m_depgraph.Descendants(args.front().second);
1518
0
        args = args.subspan(1);
1519
0
    }
1520
0
    Assume(descendants_union.Any());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1521
    // Translate all descendants (in arbitrary order) to Refs (if they have any), and return them.
1522
0
    for (auto idx : descendants_union) {
1523
0
        const auto& entry = graph.m_entries[m_mapping[idx]];
1524
0
        Assume(entry.m_ref != nullptr);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1525
0
        output.push_back(entry.m_ref);
1526
0
    }
1527
0
}
1528
1529
std::vector<TxGraph::Ref*> Cluster::GetClusterRefs(const TxGraphImpl& graph) noexcept
1530
0
{
1531
0
    std::vector<TxGraph::Ref*> ret;
1532
0
    ret.reserve(m_linearization.size());
1533
    // Translate all transactions in the Cluster (in linearization order) to Refs.
1534
0
    for (auto idx : m_linearization) {
1535
0
        const auto& entry = graph.m_entries[m_mapping[idx]];
1536
0
        Assume(entry.m_ref != nullptr);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1537
0
        ret.push_back(entry.m_ref);
1538
0
    }
1539
0
    return ret;
1540
0
}
1541
1542
FeePerWeight Cluster::GetIndividualFeerate(DepGraphIndex idx) noexcept
1543
0
{
1544
0
    return FeePerWeight::FromFeeFrac(m_depgraph.FeeRate(idx));
1545
0
}
1546
1547
void Cluster::MakeStagingTransactionsMissing(TxGraphImpl& graph) noexcept
1548
0
{
1549
0
    Assume(m_level == 1);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1550
    // Mark all transactions of a Cluster missing, needed when aborting staging, so that the
1551
    // corresponding Locators don't retain references into aborted Clusters.
1552
0
    for (auto ci : m_linearization) {
1553
0
        GraphIndex idx = m_mapping[ci];
1554
0
        auto& entry = graph.m_entries[idx];
1555
0
        entry.m_locator[1].SetMissing();
1556
0
    }
1557
0
}
1558
1559
std::vector<TxGraph::Ref*> TxGraphImpl::GetAncestors(const Ref& arg, bool main_only) noexcept
1560
0
{
1561
    // Return the empty vector if the Ref is empty.
1562
0
    if (GetRefGraph(arg) == nullptr) return {};
1563
0
    Assume(GetRefGraph(arg) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1564
    // Apply all removals and dependencies, as the result might be incorrect otherwise.
1565
0
    size_t level = GetSpecifiedLevel(main_only);
1566
0
    ApplyDependencies(level);
1567
    // Ancestry cannot be known if unapplied dependencies remain.
1568
0
    Assume(GetClusterSet(level).m_deps_to_add.empty());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1569
    // Find the Cluster the argument is in, and return the empty vector if it isn't in any.
1570
0
    auto cluster = FindCluster(GetRefIndex(arg), level);
1571
0
    if (cluster == nullptr) return {};
1572
    // Dispatch to the Cluster.
1573
0
    std::pair<Cluster*, DepGraphIndex> match = {cluster, m_entries[GetRefIndex(arg)].m_locator[cluster->m_level].index};
1574
0
    auto matches = std::span(&match, 1);
1575
0
    std::vector<TxGraph::Ref*> ret;
1576
0
    cluster->GetAncestorRefs(*this, matches, ret);
1577
0
    return ret;
1578
0
}
1579
1580
std::vector<TxGraph::Ref*> TxGraphImpl::GetDescendants(const Ref& arg, bool main_only) noexcept
1581
0
{
1582
    // Return the empty vector if the Ref is empty.
1583
0
    if (GetRefGraph(arg) == nullptr) return {};
1584
0
    Assume(GetRefGraph(arg) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1585
    // Apply all removals and dependencies, as the result might be incorrect otherwise.
1586
0
    size_t level = GetSpecifiedLevel(main_only);
1587
0
    ApplyDependencies(level);
1588
    // Ancestry cannot be known if unapplied dependencies remain.
1589
0
    Assume(GetClusterSet(level).m_deps_to_add.empty());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1590
    // Find the Cluster the argument is in, and return the empty vector if it isn't in any.
1591
0
    auto cluster = FindCluster(GetRefIndex(arg), level);
1592
0
    if (cluster == nullptr) return {};
1593
    // Dispatch to the Cluster.
1594
0
    std::pair<Cluster*, DepGraphIndex> match = {cluster, m_entries[GetRefIndex(arg)].m_locator[cluster->m_level].index};
1595
0
    auto matches = std::span(&match, 1);
1596
0
    std::vector<TxGraph::Ref*> ret;
1597
0
    cluster->GetDescendantRefs(*this, matches, ret);
1598
0
    return ret;
1599
0
}
1600
1601
std::vector<TxGraph::Ref*> TxGraphImpl::GetAncestorsUnion(std::span<const Ref* const> args, bool main_only) noexcept
1602
0
{
1603
    // Apply all dependencies, as the result might be incorrect otherwise.
1604
0
    size_t level = GetSpecifiedLevel(main_only);
1605
0
    ApplyDependencies(level);
1606
    // Ancestry cannot be known if unapplied dependencies remain.
1607
0
    Assume(GetClusterSet(level).m_deps_to_add.empty());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1608
1609
    // Translate args to matches.
1610
0
    std::vector<std::pair<Cluster*, DepGraphIndex>> matches;
1611
0
    matches.reserve(args.size());
1612
0
    for (auto arg : args) {
1613
0
        Assume(arg);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1614
        // Skip empty Refs.
1615
0
        if (GetRefGraph(*arg) == nullptr) continue;
1616
0
        Assume(GetRefGraph(*arg) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1617
        // Find the Cluster the argument is in, and skip if none is found.
1618
0
        auto cluster = FindCluster(GetRefIndex(*arg), level);
1619
0
        if (cluster == nullptr) continue;
1620
        // Append to matches.
1621
0
        matches.emplace_back(cluster, m_entries[GetRefIndex(*arg)].m_locator[cluster->m_level].index);
1622
0
    }
1623
    // Group by Cluster.
1624
0
    std::sort(matches.begin(), matches.end(), [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
1625
    // Dispatch to the Clusters.
1626
0
    std::span match_span(matches);
1627
0
    std::vector<TxGraph::Ref*> ret;
1628
0
    while (!match_span.empty()) {
1629
0
        match_span.front().first->GetAncestorRefs(*this, match_span, ret);
1630
0
    }
1631
0
    return ret;
1632
0
}
1633
1634
std::vector<TxGraph::Ref*> TxGraphImpl::GetDescendantsUnion(std::span<const Ref* const> args, bool main_only) noexcept
1635
0
{
1636
    // Apply all dependencies, as the result might be incorrect otherwise.
1637
0
    size_t level = GetSpecifiedLevel(main_only);
1638
0
    ApplyDependencies(level);
1639
    // Ancestry cannot be known if unapplied dependencies remain.
1640
0
    Assume(GetClusterSet(level).m_deps_to_add.empty());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1641
1642
    // Translate args to matches.
1643
0
    std::vector<std::pair<Cluster*, DepGraphIndex>> matches;
1644
0
    matches.reserve(args.size());
1645
0
    for (auto arg : args) {
1646
0
        Assume(arg);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1647
        // Skip empty Refs.
1648
0
        if (GetRefGraph(*arg) == nullptr) continue;
1649
0
        Assume(GetRefGraph(*arg) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1650
        // Find the Cluster the argument is in, and skip if none is found.
1651
0
        auto cluster = FindCluster(GetRefIndex(*arg), level);
1652
0
        if (cluster == nullptr) continue;
1653
        // Append to matches.
1654
0
        matches.emplace_back(cluster, m_entries[GetRefIndex(*arg)].m_locator[cluster->m_level].index);
1655
0
    }
1656
    // Group by Cluster.
1657
0
    std::sort(matches.begin(), matches.end(), [](auto& a, auto& b) noexcept { return CompareClusters(a.first, b.first) < 0; });
1658
    // Dispatch to the Clusters.
1659
0
    std::span match_span(matches);
1660
0
    std::vector<TxGraph::Ref*> ret;
1661
0
    while (!match_span.empty()) {
1662
0
        match_span.front().first->GetDescendantRefs(*this, match_span, ret);
1663
0
    }
1664
0
    return ret;
1665
0
}
1666
1667
std::vector<TxGraph::Ref*> TxGraphImpl::GetCluster(const Ref& arg, bool main_only) noexcept
1668
0
{
1669
    // Return the empty vector if the Ref is empty (which may be indicative of the transaction
1670
    // having been removed already.
1671
0
    if (GetRefGraph(arg) == nullptr) return {};
1672
0
    Assume(GetRefGraph(arg) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1673
    // Apply all removals and dependencies, as the result might be incorrect otherwise.
1674
0
    size_t level = GetSpecifiedLevel(main_only);
1675
0
    ApplyDependencies(level);
1676
    // Cluster linearization cannot be known if unapplied dependencies remain.
1677
0
    Assume(GetClusterSet(level).m_deps_to_add.empty());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1678
    // Find the Cluster the argument is in, and return the empty vector if it isn't in any.
1679
0
    auto cluster = FindCluster(GetRefIndex(arg), level);
1680
0
    if (cluster == nullptr) return {};
1681
    // Make sure the Cluster has an acceptable quality level, and then dispatch to it.
1682
0
    MakeAcceptable(*cluster);
1683
0
    return cluster->GetClusterRefs(*this);
1684
0
}
1685
1686
TxGraph::GraphIndex TxGraphImpl::GetTransactionCount(bool main_only) noexcept
1687
0
{
1688
0
    size_t level = GetSpecifiedLevel(main_only);
1689
0
    ApplyRemovals(level);
1690
0
    return GetClusterSet(level).m_txcount;
1691
0
}
1692
1693
FeePerWeight TxGraphImpl::GetIndividualFeerate(const Ref& arg) noexcept
1694
0
{
1695
    // Return the empty FeePerWeight if the passed Ref is empty.
1696
0
    if (GetRefGraph(arg) == nullptr) return {};
1697
0
    Assume(GetRefGraph(arg) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1698
    // Find the cluster the argument is in (the level does not matter as individual feerates will
1699
    // be identical if it occurs in both), and return the empty FeePerWeight if it isn't in any.
1700
0
    Cluster* cluster{nullptr};
1701
0
    for (int level = 0; level <= GetTopLevel(); ++level) {
1702
        // Apply removals, so that we can correctly report FeePerWeight{} for non-existing
1703
        // transactions.
1704
0
        ApplyRemovals(level);
1705
0
        if (m_entries[GetRefIndex(arg)].m_locator[level].IsPresent()) {
1706
0
            cluster = m_entries[GetRefIndex(arg)].m_locator[level].cluster;
1707
0
            break;
1708
0
        }
1709
0
    }
1710
0
    if (cluster == nullptr) return {};
1711
    // Dispatch to the Cluster.
1712
0
    return cluster->GetIndividualFeerate(m_entries[GetRefIndex(arg)].m_locator[cluster->m_level].index);
1713
0
}
1714
1715
FeePerWeight TxGraphImpl::GetMainChunkFeerate(const Ref& arg) noexcept
1716
0
{
1717
    // Return the empty FeePerWeight if the passed Ref is empty.
1718
0
    if (GetRefGraph(arg) == nullptr) return {};
1719
0
    Assume(GetRefGraph(arg) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1720
    // Apply all removals and dependencies, as the result might be inaccurate otherwise.
1721
0
    ApplyDependencies(/*level=*/0);
1722
    // Chunk feerates cannot be accurately known if unapplied dependencies remain.
1723
0
    Assume(m_main_clusterset.m_deps_to_add.empty());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1724
    // Find the cluster the argument is in, and return the empty FeePerWeight if it isn't in any.
1725
0
    auto cluster = FindCluster(GetRefIndex(arg), 0);
1726
0
    if (cluster == nullptr) return {};
1727
    // Make sure the Cluster has an acceptable quality level, and then return the transaction's
1728
    // chunk feerate.
1729
0
    MakeAcceptable(*cluster);
1730
0
    const auto& entry = m_entries[GetRefIndex(arg)];
1731
0
    return entry.m_main_chunk_feerate;
1732
0
}
1733
1734
bool TxGraphImpl::IsOversized(bool main_only) noexcept
1735
0
{
1736
0
    size_t level = GetSpecifiedLevel(main_only);
1737
0
    auto& clusterset = GetClusterSet(level);
1738
0
    if (clusterset.m_oversized.has_value()) {
1739
        // Return cached value if known.
1740
0
        return *clusterset.m_oversized;
1741
0
    }
1742
    // Find which Clusters will need to be merged together, as that is where the oversize
1743
    // property is assessed.
1744
0
    GroupClusters(level);
1745
0
    Assume(clusterset.m_group_data.has_value());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1746
0
    clusterset.m_oversized = clusterset.m_group_data->m_group_oversized;
1747
0
    return *clusterset.m_oversized;
1748
0
}
1749
1750
void TxGraphImpl::StartStaging() noexcept
1751
0
{
1752
    // Staging cannot already exist.
1753
0
    Assume(!m_staging_clusterset.has_value());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1754
    // Apply all remaining dependencies in main before creating a staging graph. Once staging
1755
    // exists, we cannot merge Clusters anymore (because of interference with Clusters being
1756
    // pulled into staging), so to make sure all inspectors are available (if not oversized), do
1757
    // all merging work now. Call SplitAll() first, so that even if ApplyDependencies does not do
1758
    // any thing due to knowing the result is oversized, splitting is still performed.
1759
0
    SplitAll(0);
1760
0
    ApplyDependencies(0);
1761
    // Construct the staging ClusterSet.
1762
0
    m_staging_clusterset.emplace();
1763
    // Copy statistics, precomputed data, and to-be-applied dependencies (only if oversized) to
1764
    // the new graph. To-be-applied removals will always be empty at this point.
1765
0
    m_staging_clusterset->m_txcount = m_main_clusterset.m_txcount;
1766
0
    m_staging_clusterset->m_deps_to_add = m_main_clusterset.m_deps_to_add;
1767
0
    m_staging_clusterset->m_group_data = m_main_clusterset.m_group_data;
1768
0
    m_staging_clusterset->m_oversized = m_main_clusterset.m_oversized;
1769
0
    Assume(m_staging_clusterset->m_oversized.has_value());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1770
0
}
1771
1772
void TxGraphImpl::AbortStaging() noexcept
1773
0
{
1774
    // Staging must exist.
1775
0
    Assume(m_staging_clusterset.has_value());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1776
    // Mark all removed transactions as Missing (so the staging locator for these transactions
1777
    // can be reused if another staging is created).
1778
0
    for (auto idx : m_staging_clusterset->m_removed) {
1779
0
        m_entries[idx].m_locator[1].SetMissing();
1780
0
    }
1781
    // Do the same with the non-removed transactions in staging Clusters.
1782
0
    for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
1783
0
        for (auto& cluster : m_staging_clusterset->m_clusters[quality]) {
1784
0
            cluster->MakeStagingTransactionsMissing(*this);
1785
0
        }
1786
0
    }
1787
    // Destroy the staging ClusterSet.
1788
0
    m_staging_clusterset.reset();
1789
0
    Compact();
1790
0
    if (!m_main_clusterset.m_group_data.has_value()) {
1791
        // In case m_oversized in main was kept after a Ref destruction while staging exists, we
1792
        // need to re-evaluate m_oversized now.
1793
0
        m_main_clusterset.m_oversized = std::nullopt;
1794
0
    }
1795
0
}
1796
1797
void TxGraphImpl::CommitStaging() noexcept
1798
0
{
1799
    // Staging must exist.
1800
0
    Assume(m_staging_clusterset.has_value());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1801
    // Delete all conflicting Clusters in main, to make place for moving the staging ones
1802
    // there. All of these have been copied to staging in PullIn().
1803
0
    auto conflicts = GetConflicts();
1804
0
    for (Cluster* conflict : conflicts) {
1805
0
        conflict->Clear(*this);
1806
0
        DeleteCluster(*conflict);
1807
0
    }
1808
    // Mark the removed transactions as Missing (so the staging locator for these transactions
1809
    // can be reused if another staging is created).
1810
0
    for (auto idx : m_staging_clusterset->m_removed) {
1811
0
        m_entries[idx].m_locator[1].SetMissing();
1812
0
    }
1813
    // Then move all Clusters in staging to main.
1814
0
    for (int quality = 0; quality < int(QualityLevel::NONE); ++quality) {
1815
0
        auto& stage_sets = m_staging_clusterset->m_clusters[quality];
1816
0
        while (!stage_sets.empty()) {
1817
0
            stage_sets.back()->MoveToMain(*this);
1818
0
        }
1819
0
    }
1820
    // Move all statistics, precomputed data, and to-be-applied removals and dependencies.
1821
0
    m_main_clusterset.m_deps_to_add = std::move(m_staging_clusterset->m_deps_to_add);
1822
0
    m_main_clusterset.m_to_remove = std::move(m_staging_clusterset->m_to_remove);
1823
0
    m_main_clusterset.m_group_data = std::move(m_staging_clusterset->m_group_data);
1824
0
    m_main_clusterset.m_oversized = std::move(m_staging_clusterset->m_oversized);
1825
0
    m_main_clusterset.m_txcount = std::move(m_staging_clusterset->m_txcount);
1826
    // Delete the old staging graph, after all its information was moved to main.
1827
0
    m_staging_clusterset.reset();
1828
0
    Compact();
1829
0
}
1830
1831
void Cluster::SetFee(TxGraphImpl& graph, DepGraphIndex idx, int64_t fee) noexcept
1832
0
{
1833
    // Make sure the specified DepGraphIndex exists in this Cluster.
1834
0
    Assume(m_depgraph.Positions()[idx]);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1835
    // Bail out if the fee isn't actually being changed.
1836
0
    if (m_depgraph.FeeRate(idx).fee == fee) return;
1837
    // Update the fee, remember that relinearization will be necessary, and update the Entries
1838
    // in the same Cluster.
1839
0
    m_depgraph.FeeRate(idx).fee = fee;
1840
0
    if (!NeedsSplitting()) {
1841
0
        graph.SetClusterQuality(m_level, m_quality, m_setindex, QualityLevel::NEEDS_RELINEARIZE);
1842
0
    } else {
1843
0
        graph.SetClusterQuality(m_level, m_quality, m_setindex, QualityLevel::NEEDS_SPLIT);
1844
0
    }
1845
0
    Updated(graph);
1846
0
}
1847
1848
void TxGraphImpl::SetTransactionFee(const Ref& ref, int64_t fee) noexcept
1849
0
{
1850
    // Don't do anything if the passed Ref is empty.
1851
0
    if (GetRefGraph(ref) == nullptr) return;
1852
0
    Assume(GetRefGraph(ref) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1853
    // Find the entry, its locator, and inform its Cluster about the new feerate, if any.
1854
0
    auto& entry = m_entries[GetRefIndex(ref)];
1855
0
    for (int level = 0; level < MAX_LEVELS; ++level) {
1856
0
        auto& locator = entry.m_locator[level];
1857
0
        if (locator.IsPresent()) {
1858
0
            locator.cluster->SetFee(*this, locator.index, fee);
1859
0
        }
1860
0
    }
1861
0
}
1862
1863
std::strong_ordering TxGraphImpl::CompareMainOrder(const Ref& a, const Ref& b) noexcept
1864
0
{
1865
    // The references must not be empty.
1866
0
    Assume(GetRefGraph(a) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1867
0
    Assume(GetRefGraph(b) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1868
    // Apply dependencies in main.
1869
0
    ApplyDependencies(0);
1870
0
    Assume(m_main_clusterset.m_deps_to_add.empty());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1871
    // Make both involved Clusters acceptable, so chunk feerates are relevant.
1872
0
    const auto& entry_a = m_entries[GetRefIndex(a)];
1873
0
    const auto& entry_b = m_entries[GetRefIndex(b)];
1874
0
    const auto& locator_a = entry_a.m_locator[0];
1875
0
    const auto& locator_b = entry_b.m_locator[0];
1876
0
    Assume(locator_a.IsPresent());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1877
0
    Assume(locator_b.IsPresent());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1878
0
    MakeAcceptable(*locator_a.cluster);
1879
0
    MakeAcceptable(*locator_b.cluster);
1880
    // Compare chunk feerates, and return result if it differs.
1881
0
    auto feerate_cmp = FeeRateCompare(entry_b.m_main_chunk_feerate, entry_a.m_main_chunk_feerate);
1882
0
    if (feerate_cmp < 0) return std::strong_ordering::less;
1883
0
    if (feerate_cmp > 0) return std::strong_ordering::greater;
1884
    // Compare Cluster* as tie-break for equal chunk feerates.
1885
0
    if (locator_a.cluster != locator_b.cluster) {
1886
0
        return CompareClusters(locator_a.cluster, locator_b.cluster);
1887
0
    }
1888
    // As final tie-break, compare position within cluster linearization.
1889
0
    return entry_a.m_main_lin_index <=> entry_b.m_main_lin_index;
1890
0
}
1891
1892
TxGraph::GraphIndex TxGraphImpl::CountDistinctClusters(std::span<const Ref* const> refs, bool main_only) noexcept
1893
0
{
1894
0
    size_t level = GetSpecifiedLevel(main_only);
1895
0
    ApplyDependencies(level);
1896
0
    auto& clusterset = GetClusterSet(level);
1897
0
    Assume(clusterset.m_deps_to_add.empty());
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1898
    // Build a vector of Clusters that the specified Refs occur in.
1899
0
    std::vector<Cluster*> clusters;
1900
0
    clusters.reserve(refs.size());
1901
0
    for (const Ref* ref : refs) {
1902
0
        Assume(ref);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1903
0
        if (GetRefGraph(*ref) == nullptr) continue;
1904
0
        Assume(GetRefGraph(*ref) == this);
Line
Count
Source
118
0
#define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val)
1905
0
        auto cluster = FindCluster(GetRefIndex(*ref), level);
1906
0
        if (cluster != nullptr) clusters.push_back(cluster);
1907
0
    }
1908
    // Count the number of distinct elements in clusters.
1909
0
    std::sort(clusters.begin(), clusters.end(), [](Cluster* a, Cluster* b) noexcept { return CompareClusters(a, b) < 0; });
1910
0
    Cluster* last{nullptr};
1911
0
    GraphIndex ret{0};
1912
0
    for (Cluster* cluster : clusters) {
1913
0
        ret += (cluster != last);
1914
0
        last = cluster;
1915
0
    }
1916
0
    return ret;
1917
0
}
1918
1919
void Cluster::SanityCheck(const TxGraphImpl& graph, int level) const
1920
0
{
1921
    // There must be an m_mapping for each m_depgraph position (including holes).
1922
0
    assert(m_depgraph.PositionRange() == m_mapping.size());
1923
    // The linearization for this Cluster must contain every transaction once.
1924
0
    assert(m_depgraph.TxCount() == m_linearization.size());
1925
    // The number of transactions in a Cluster cannot exceed m_max_cluster_count.
1926
0
    assert(m_linearization.size() <= graph.m_max_cluster_count);
1927
    // The level must match the level the Cluster occurs in.
1928
0
    assert(m_level == level);
1929
    // m_quality and m_setindex are checked in TxGraphImpl::SanityCheck.
1930
1931
    // Compute the chunking of m_linearization.
1932
0
    LinearizationChunking linchunking(m_depgraph, m_linearization);
1933
1934
    // Verify m_linearization.
1935
0
    SetType m_done;
1936
0
    LinearizationIndex linindex{0};
1937
0
    assert(m_depgraph.IsAcyclic());
1938
0
    for (auto lin_pos : m_linearization) {
1939
0
        assert(lin_pos < m_mapping.size());
1940
0
        const auto& entry = graph.m_entries[m_mapping[lin_pos]];
1941
        // Check that the linearization is topological.
1942
0
        m_done.Set(lin_pos);
1943
0
        assert(m_done.IsSupersetOf(m_depgraph.Ancestors(lin_pos)));
1944
        // Check that the Entry has a locator pointing back to this Cluster & position within it.
1945
0
        assert(entry.m_locator[level].cluster == this);
1946
0
        assert(entry.m_locator[level].index == lin_pos);
1947
        // For main-level entries, check linearization position and chunk feerate.
1948
0
        if (level == 0 && IsAcceptable()) {
1949
0
            assert(entry.m_main_lin_index == linindex);
1950
0
            ++linindex;
1951
0
            if (!linchunking.GetChunk(0).transactions[lin_pos]) {
1952
0
                linchunking.MarkDone(linchunking.GetChunk(0).transactions);
1953
0
            }
1954
0
            assert(entry.m_main_chunk_feerate == linchunking.GetChunk(0).feerate);
1955
            // If this Cluster has an acceptable quality level, its chunks must be connected.
1956
0
            assert(m_depgraph.IsConnected(linchunking.GetChunk(0).transactions));
1957
0
        }
1958
0
    }
1959
    // Verify that each element of m_depgraph occurred in m_linearization.
1960
0
    assert(m_done == m_depgraph.Positions());
1961
0
}
1962
1963
void TxGraphImpl::SanityCheck() const
1964
0
{
1965
    /** Which GraphIndexes ought to occur in m_unlinked, based on m_entries. */
1966
0
    std::set<GraphIndex> expected_unlinked;
1967
    /** Which Clusters ought to occur in ClusterSet::m_clusters, based on m_entries. */
1968
0
    std::set<const Cluster*> expected_clusters[MAX_LEVELS];
1969
    /** Which GraphIndexes ought to occur in ClusterSet::m_removed, based on m_entries. */
1970
0
    std::set<GraphIndex> expected_removed[MAX_LEVELS];
1971
    /** Which Cluster::m_sequence values have been encountered. */
1972
0
    std::set<uint64_t> sequences;
1973
    /** Whether compaction is possible in the current state. */
1974
0
    bool compact_possible{true};
1975
1976
    // Go over all Entry objects in m_entries.
1977
0
    for (GraphIndex idx = 0; idx < m_entries.size(); ++idx) {
1978
0
        const auto& entry = m_entries[idx];
1979
0
        if (entry.m_ref == nullptr) {
1980
            // Unlinked Entry must have indexes appear in m_unlinked.
1981
0
            expected_unlinked.insert(idx);
1982
0
        } else {
1983
            // Every non-unlinked Entry must have a Ref that points back to it.
1984
0
            assert(GetRefGraph(*entry.m_ref) == this);
1985
0
            assert(GetRefIndex(*entry.m_ref) == idx);
1986
0
        }
1987
        // Verify the Entry m_locators.
1988
0
        bool was_present{false}, was_removed{false};
1989
0
        for (int level = 0; level < MAX_LEVELS; ++level) {
1990
0
            const auto& locator = entry.m_locator[level];
1991
            // Every Locator must be in exactly one of these 3 states.
1992
0
            assert(locator.IsMissing() + locator.IsRemoved() + locator.IsPresent() == 1);
1993
0
            if (locator.IsPresent()) {
1994
                // Once removed, a transaction cannot be revived.
1995
0
                assert(!was_removed);
1996
                // Verify that the Cluster agrees with where the Locator claims the transaction is.
1997
0
                assert(locator.cluster->GetClusterEntry(locator.index) == idx);
1998
                // Remember that we expect said Cluster to appear in the ClusterSet::m_clusters.
1999
0
                expected_clusters[level].insert(locator.cluster);
2000
0
                was_present = true;
2001
0
            } else if (locator.IsRemoved()) {
2002
                // Level 0 (main) cannot have IsRemoved locators (IsMissing there means non-existing).
2003
0
                assert(level > 0);
2004
                // A Locator can only be IsRemoved if it was IsPresent before, and only once.
2005
0
                assert(was_present && !was_removed);
2006
                // Remember that we expect this GraphIndex to occur in the ClusterSet::m_removed.
2007
0
                expected_removed[level].insert(idx);
2008
0
                was_removed = true;
2009
0
            }
2010
0
        }
2011
0
    }
2012
2013
    // For all levels (0 = main, 1 = staged)...
2014
0
    for (int level = 0; level <= GetTopLevel(); ++level) {
2015
0
        assert(level < MAX_LEVELS);
2016
0
        auto& clusterset = GetClusterSet(level);
2017
0
        std::set<const Cluster*> actual_clusters;
2018
2019
        // For all quality levels...
2020
0
        for (int qual = 0; qual < int(QualityLevel::NONE); ++qual) {
2021
0
            QualityLevel quality{qual};
2022
0
            const auto& quality_clusters = clusterset.m_clusters[qual];
2023
            // ... for all clusters in them ...
2024
0
            for (ClusterSetIndex setindex = 0; setindex < quality_clusters.size(); ++setindex) {
2025
0
                const auto& cluster = *quality_clusters[setindex];
2026
                // Check the sequence number.
2027
0
                assert(cluster.m_sequence < m_next_sequence_counter);
2028
0
                assert(sequences.count(cluster.m_sequence) == 0);
2029
0
                sequences.insert(cluster.m_sequence);
2030
                // Remember we saw this Cluster (only if it is non-empty; empty Clusters aren't
2031
                // expected to be referenced by the Entry vector).
2032
0
                if (cluster.GetTxCount() != 0) {
2033
0
                    actual_clusters.insert(&cluster);
2034
0
                }
2035
                // Sanity check the cluster, according to the Cluster's internal rules.
2036
0
                cluster.SanityCheck(*this, level);
2037
                // Check that the cluster's quality and setindex matches its position in the quality list.
2038
0
                assert(cluster.m_quality == quality);
2039
0
                assert(cluster.m_setindex == setindex);
2040
0
            }
2041
0
        }
2042
2043
        // Verify that all to-be-removed transactions have valid identifiers.
2044
0
        for (GraphIndex idx : clusterset.m_to_remove) {
2045
0
            assert(idx < m_entries.size());
2046
            // We cannot assert that all m_to_remove transactions are still present: ~Ref on a
2047
            // (P,M) transaction (present in main, inherited in staging) will cause an m_to_remove
2048
            // addition in both main and staging, but a subsequence ApplyRemovals in main will
2049
            // cause it to disappear from staging too, leaving the m_to_remove in place.
2050
0
        }
2051
2052
        // Verify that all to-be-added dependencies have valid identifiers.
2053
0
        for (auto [par_idx, chl_idx] : clusterset.m_deps_to_add) {
2054
0
            assert(par_idx != chl_idx);
2055
0
            assert(par_idx < m_entries.size());
2056
0
            assert(chl_idx < m_entries.size());
2057
0
        }
2058
2059
        // Verify that the actually encountered clusters match the ones occurring in Entry vector.
2060
0
        assert(actual_clusters == expected_clusters[level]);
2061
2062
        // Verify that the contents of m_removed matches what was expected based on the Entry vector.
2063
0
        std::set<GraphIndex> actual_removed(clusterset.m_removed.begin(), clusterset.m_removed.end());
2064
0
        for (auto i : expected_unlinked) {
2065
            // If a transaction exists in both main and staging, and is removed from staging (adding
2066
            // it to m_removed there), and consequently destroyed (wiping the locator completely),
2067
            // it can remain in m_removed despite not having an IsRemoved() locator. Exclude those
2068
            // transactions from the comparison here.
2069
0
            actual_removed.erase(i);
2070
0
            expected_removed[level].erase(i);
2071
0
        }
2072
2073
0
        assert(actual_removed == expected_removed[level]);
2074
2075
        // If any GraphIndex entries remain in this ClusterSet, compact is not possible.
2076
0
        if (!clusterset.m_deps_to_add.empty()) compact_possible = false;
2077
0
        if (!clusterset.m_to_remove.empty()) compact_possible = false;
2078
0
        if (!clusterset.m_removed.empty()) compact_possible = false;
2079
2080
        // If m_group_data exists, its m_group_oversized must match m_oversized.
2081
0
        if (clusterset.m_group_data.has_value()) {
2082
0
            assert(clusterset.m_oversized == clusterset.m_group_data->m_group_oversized);
2083
0
        }
2084
2085
        // For non-top levels, m_oversized must be known (as it cannot change until the level
2086
        // on top is gone).
2087
0
        if (level < GetTopLevel()) assert(clusterset.m_oversized.has_value());
2088
0
    }
2089
2090
    // Verify that the contents of m_unlinked matches what was expected based on the Entry vector.
2091
0
    std::set<GraphIndex> actual_unlinked(m_unlinked.begin(), m_unlinked.end());
2092
0
    assert(actual_unlinked == expected_unlinked);
2093
2094
    // If compaction was possible, it should have been performed already, and m_unlinked must be
2095
    // empty (to prevent memory leaks due to an ever-growing m_entries vector).
2096
0
    if (compact_possible) {
2097
0
        assert(actual_unlinked.empty());
2098
0
    }
2099
0
}
2100
2101
void TxGraphImpl::DoWork() noexcept
2102
0
{
2103
0
    for (int level = 0; level <= GetTopLevel(); ++level) {
2104
0
        MakeAllAcceptable(level);
2105
0
    }
2106
0
}
2107
2108
} // namespace
2109
2110
TxGraph::Ref::~Ref()
2111
0
{
2112
0
    if (m_graph) {
2113
        // Inform the TxGraph about the Ref being destroyed.
2114
0
        m_graph->UnlinkRef(m_index);
2115
0
        m_graph = nullptr;
2116
0
    }
2117
0
}
2118
2119
TxGraph::Ref& TxGraph::Ref::operator=(Ref&& other) noexcept
2120
0
{
2121
    // Unlink the current graph, if any.
2122
0
    if (m_graph) m_graph->UnlinkRef(m_index);
2123
    // Inform the other's graph about the move, if any.
2124
0
    if (other.m_graph) other.m_graph->UpdateRef(other.m_index, *this);
2125
    // Actually update the contents.
2126
0
    m_graph = other.m_graph;
2127
0
    m_index = other.m_index;
2128
0
    other.m_graph = nullptr;
2129
0
    other.m_index = GraphIndex(-1);
2130
0
    return *this;
2131
0
}
2132
2133
TxGraph::Ref::Ref(Ref&& other) noexcept
2134
0
{
2135
    // Inform the TxGraph of other that its Ref is being moved.
2136
0
    if (other.m_graph) other.m_graph->UpdateRef(other.m_index, *this);
2137
    // Actually move the contents.
2138
0
    std::swap(m_graph, other.m_graph);
2139
0
    std::swap(m_index, other.m_index);
2140
0
}
2141
2142
std::unique_ptr<TxGraph> MakeTxGraph(unsigned max_cluster_count) noexcept
2143
0
{
2144
0
    return std::make_unique<TxGraphImpl>(max_cluster_count);
2145
0
}