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