/root/bitcoin/src/txgraph.cpp
Line | Count | Source |
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); |
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()); |
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()); |
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); |
470 | 0 | Assume(max_cluster_count <= MAX_CLUSTER_COUNT_LIMIT); |
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); |
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); |
530 | 0 | Assume(m_main_chunkindex_observers == 0 || !entry.m_locator[0].IsPresent()); |
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); |
628 | 0 | Assume(m_staging_clusterset.has_value()); |
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); |
636 | 0 | Assume(m_staging_clusterset.has_value()); |
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); |
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); |
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); |
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()); |
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); |
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]); |
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); |
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); |
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()); |
843 | 0 | SetType todo; |
844 | 0 | do { |
845 | 0 | GraphIndex idx = to_remove.front(); |
846 | 0 | Assume(idx < graph.m_entries.size()); |
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()); |
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); |
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]); |
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()); |
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()); |
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); |
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()); |
1118 | 0 | Assume(!IsOversized()); |
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); |
1141 | |
|
1142 | 0 | auto& clusterset = GetClusterSet(level); |
1143 | 0 | auto& quality_clusters = clusterset.m_clusters[int(quality)]; |
1144 | 0 | Assume(setindex < quality_clusters.size()); |
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); |
1170 | | // The passed-in Cluster must not currently be in the TxGraphImpl. |
1171 | 0 | Assume(cluster->m_quality == QualityLevel::NONE); |
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); |
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()); |
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); |
1226 | 0 | int level = cluster->m_level; |
1227 | 0 | Assume(level <= to_level); |
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()); |
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()); |
1281 | 0 | Assume(b < m_entries.size()); |
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 |
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); |
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); |
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()); |
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()); |
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()); |
1456 | 0 | Assume(it->sequence == sequence); |
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); |
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); |
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); |
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()); |
1585 | 0 | Assume(an_clusters_it == an_clusters.end()); |
1586 | 0 | Compact(); |
1587 | 0 | } |
1588 | | |
1589 | | void TxGraphImpl::Merge(std::span<Cluster*> to_merge) noexcept |
1590 | 0 | { |
1591 | 0 | Assume(!to_merge.empty()); |
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()); |
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()); |
1646 | 0 | const auto& loc = m_entries[deps_span[0].second].m_locator[level]; |
1647 | 0 | Assume(loc.IsPresent()); |
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()); |
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); |
1720 | 0 | Assume(feerate.size > 0); |
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); |
1756 | 0 | Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0); |
1757 | | // Find the Cluster the transaction is in, and stop if it isn't in any. |
1758 | 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); |
1775 | 0 | Assume(m_main_chunkindex_observers == 0 || GetTopLevel() != 0); |
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); |
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()); |
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); |
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()); |
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); |
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()); |
1848 | 0 | const auto& entry = graph.m_entries[m_mapping[m_linearization[start_pos++]]]; |
1849 | 0 | Assume(entry.m_ref != nullptr); |
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); |
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); |
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()); |
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); |
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()); |
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()); |
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); |
1928 | | // Skip empty Refs. |
1929 | 0 | if (GetRefGraph(*arg) == nullptr) continue; |
1930 | 0 | Assume(GetRefGraph(*arg) == this); |
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()); |
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); |
1961 | | // Skip empty Refs. |
1962 | 0 | if (GetRefGraph(*arg) == nullptr) continue; |
1963 | 0 | Assume(GetRefGraph(*arg) == this); |
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); |
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()); |
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); |
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); |
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()); |
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()); |
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()); |
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()); |
2091 | 0 | } |
2092 | | |
2093 | | void TxGraphImpl::AbortStaging() noexcept |
2094 | 0 | { |
2095 | | // Staging must exist. |
2096 | 0 | Assume(m_staging_clusterset.has_value()); |
2097 | | // Mark all removed transactions as Missing (so the staging locator for these transactions |
2098 | | // can be reused if another staging is created). |
2099 | 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()); |
2128 | 0 | Assume(m_main_chunkindex_observers == 0); |
2129 | | // Delete all conflicting Clusters in main, to make place for moving the staging ones |
2130 | | // there. All of these have been copied to staging in PullIn(). |
2131 | 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]); |
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); |
2184 | 0 | Assume(m_main_chunkindex_observers == 0); |
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); |
2199 | 0 | Assume(GetRefGraph(b) == this); |
2200 | | // Apply dependencies in main. |
2201 | 0 | ApplyDependencies(0); |
2202 | 0 | Assume(m_main_clusterset.m_deps_to_add.empty()); |
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()); |
2209 | 0 | Assume(locator_b.IsPresent()); |
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()); |
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); |
2227 | 0 | if (GetRefGraph(*ref) == nullptr) continue; |
2228 | 0 | Assume(GetRefGraph(*ref) == this); |
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()); |
2246 | 0 | MakeAllAcceptable(0); |
2247 | 0 | Assume(m_main_clusterset.m_deps_to_add.empty()); // can only fail if main is oversized |
2248 | 0 | MakeAllAcceptable(1); |
2249 | 0 | Assume(m_staging_clusterset->m_deps_to_add.empty()); // can only fail if staging is oversized |
2250 | | // For all Clusters in main which conflict with Clusters in staging (i.e., all that are removed |
2251 | | // by, or replaced in, staging), gather their chunk feerates. |
2252 | 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); |
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); |
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); |
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()); |
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); |
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()); |
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); |
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); |
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()); |
2671 | | // Nothing to do if not oversized. |
2672 | 0 | Assume(clusterset.m_oversized.has_value()); |
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); |
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()); |
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()); |
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); |
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); |
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); |
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()); |
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 | } |