Bitcoin Core 31.99.0
P2P Digital Currency
cluster_linearize.h
Go to the documentation of this file.
1// Copyright (c) The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#ifndef BITCOIN_CLUSTER_LINEARIZE_H
6#define BITCOIN_CLUSTER_LINEARIZE_H
7
8#include <algorithm>
9#include <cstdint>
10#include <numeric>
11#include <optional>
12#include <ranges>
13#include <utility>
14#include <vector>
15
16#include <attributes.h>
17#include <memusage.h>
18#include <random.h>
19#include <span.h>
20#include <util/feefrac.h>
21#include <util/vecdeque.h>
22
24
26using DepGraphIndex = uint32_t;
27
30template<typename SetType>
32{
34 struct Entry
35 {
39 SetType ancestors;
41 SetType descendants;
42
44 friend bool operator==(const Entry&, const Entry&) noexcept = default;
45
47 Entry() noexcept = default;
49 Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {}
50 };
51
53 std::vector<Entry> entries;
54
56 SetType m_used;
57
58public:
60 friend bool operator==(const DepGraph& a, const DepGraph& b) noexcept
61 {
62 if (a.m_used != b.m_used) return false;
63 // Only compare the used positions within the entries vector.
64 for (auto idx : a.m_used) {
65 if (a.entries[idx] != b.entries[idx]) return false;
66 }
67 return true;
68 }
69
70 // Default constructors.
71 DepGraph() noexcept = default;
72 DepGraph(const DepGraph&) noexcept = default;
73 DepGraph(DepGraph&&) noexcept = default;
74 DepGraph& operator=(const DepGraph&) noexcept = default;
75 DepGraph& operator=(DepGraph&&) noexcept = default;
76
92 DepGraph(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> mapping, DepGraphIndex pos_range) noexcept : entries(pos_range)
93 {
94 Assume(mapping.size() == depgraph.PositionRange());
95 Assume((pos_range == 0) == (depgraph.TxCount() == 0));
96 for (DepGraphIndex i : depgraph.Positions()) {
97 auto new_idx = mapping[i];
98 Assume(new_idx < pos_range);
99 // Add transaction.
100 entries[new_idx].ancestors = SetType::Singleton(new_idx);
101 entries[new_idx].descendants = SetType::Singleton(new_idx);
102 m_used.Set(new_idx);
103 // Fill in fee and size.
104 entries[new_idx].feerate = depgraph.entries[i].feerate;
105 }
106 for (DepGraphIndex i : depgraph.Positions()) {
107 // Fill in dependencies by mapping direct parents.
108 SetType parents;
109 for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]);
110 AddDependencies(parents, mapping[i]);
111 }
112 // Verify that the provided pos_range was correct (no unused positions at the end).
113 Assume(m_used.None() ? (pos_range == 0) : (pos_range == m_used.Last() + 1));
114 }
115
117 const SetType& Positions() const noexcept { return m_used; }
119 DepGraphIndex PositionRange() const noexcept { return entries.size(); }
121 auto TxCount() const noexcept { return m_used.Count(); }
123 const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; }
125 FeeFrac& FeeRate(DepGraphIndex i) noexcept { return entries[i].feerate; }
127 const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; }
129 const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; }
130
136 DepGraphIndex AddTransaction(const FeeFrac& feefrac) noexcept
137 {
138 static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size());
139 auto available = ALL_POSITIONS - m_used;
140 Assume(available.Any());
141 DepGraphIndex new_idx = available.First();
142 if (new_idx == entries.size()) {
143 entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
144 } else {
145 entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
146 }
147 m_used.Set(new_idx);
148 return new_idx;
149 }
150
160 void RemoveTransactions(const SetType& del) noexcept
161 {
162 m_used -= del;
163 // Remove now-unused trailing entries.
164 while (!entries.empty() && !m_used[entries.size() - 1]) {
165 entries.pop_back();
166 }
167 // Remove the deleted transactions from ancestors/descendants of other transactions. Note
168 // that the deleted positions will retain old feerate and dependency information. This does
169 // not matter as they will be overwritten by AddTransaction if they get used again.
170 for (auto& entry : entries) {
171 entry.ancestors &= m_used;
172 entry.descendants &= m_used;
173 }
174 }
175
180 void AddDependencies(const SetType& parents, DepGraphIndex child) noexcept
181 {
182 Assume(m_used[child]);
183 Assume(parents.IsSubsetOf(m_used));
184 // Compute the ancestors of parents that are not already ancestors of child.
185 SetType par_anc;
186 for (auto par : parents - Ancestors(child)) {
187 par_anc |= Ancestors(par);
188 }
189 par_anc -= Ancestors(child);
190 // Bail out if there are no such ancestors.
191 if (par_anc.None()) return;
192 // To each such ancestor, add as descendants the descendants of the child.
193 const auto& chl_des = entries[child].descendants;
194 for (auto anc_of_par : par_anc) {
195 entries[anc_of_par].descendants |= chl_des;
196 }
197 // To each descendant of the child, add those ancestors.
198 for (auto dec_of_chl : Descendants(child)) {
199 entries[dec_of_chl].ancestors |= par_anc;
200 }
201 }
202
211 SetType GetReducedParents(DepGraphIndex i) const noexcept
212 {
213 SetType parents = Ancestors(i);
214 parents.Reset(i);
215 for (auto parent : parents) {
216 if (parents[parent]) {
217 parents -= Ancestors(parent);
218 parents.Set(parent);
219 }
220 }
221 return parents;
222 }
223
232 SetType GetReducedChildren(DepGraphIndex i) const noexcept
233 {
234 SetType children = Descendants(i);
235 children.Reset(i);
236 for (auto child : children) {
237 if (children[child]) {
238 children -= Descendants(child);
239 children.Set(child);
240 }
241 }
242 return children;
243 }
244
249 FeeFrac FeeRate(const SetType& elems) const noexcept
250 {
251 FeeFrac ret;
252 for (auto pos : elems) ret += entries[pos].feerate;
253 return ret;
254 }
255
266 SetType GetConnectedComponent(const SetType& todo, DepGraphIndex tx) const noexcept
267 {
268 Assume(todo[tx]);
269 Assume(todo.IsSubsetOf(m_used));
270 auto to_add = SetType::Singleton(tx);
271 SetType ret;
272 do {
273 SetType old = ret;
274 for (auto add : to_add) {
275 ret |= Descendants(add);
276 ret |= Ancestors(add);
277 }
278 ret &= todo;
279 to_add = ret - old;
280 } while (to_add.Any());
281 return ret;
282 }
283
291 SetType FindConnectedComponent(const SetType& todo) const noexcept
292 {
293 if (todo.None()) return todo;
294 return GetConnectedComponent(todo, todo.First());
295 }
296
301 bool IsConnected(const SetType& subset) const noexcept
302 {
303 return FindConnectedComponent(subset) == subset;
304 }
305
310 bool IsConnected() const noexcept { return IsConnected(m_used); }
311
316 void AppendTopo(std::vector<DepGraphIndex>& list, const SetType& select) const noexcept
317 {
318 DepGraphIndex old_len = list.size();
319 for (auto i : select) list.push_back(i);
320 std::ranges::sort(std::span{list}.subspan(old_len), [&](DepGraphIndex a, DepGraphIndex b) noexcept {
321 const auto a_anc_count = entries[a].ancestors.Count();
322 const auto b_anc_count = entries[b].ancestors.Count();
323 if (a_anc_count != b_anc_count) return a_anc_count < b_anc_count;
324 return a < b;
325 });
326 }
327
329 bool IsAcyclic() const noexcept
330 {
331 for (auto i : Positions()) {
332 if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) {
333 return false;
334 }
335 }
336 return true;
337 }
338
339 unsigned CountDependencies() const noexcept
340 {
341 unsigned ret = 0;
342 for (auto i : Positions()) {
343 ret += GetReducedParents(i).Count();
344 }
345 return ret;
346 }
347
349 void Compact() noexcept
350 {
351 entries.shrink_to_fit();
352 }
353
354 size_t DynamicMemoryUsage() const noexcept
355 {
357 }
358};
359
361template<typename SetType>
363{
368
370 SetInfo() noexcept = default;
371
373 SetInfo(const SetType& txn, const FeeFrac& fr) noexcept : transactions(txn), feerate(fr) {}
374
376 explicit SetInfo(const DepGraph<SetType>& depgraph, DepGraphIndex pos) noexcept :
377 transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {}
378
380 explicit SetInfo(const DepGraph<SetType>& depgraph, const SetType& txn) noexcept :
381 transactions(txn), feerate(depgraph.FeeRate(txn)) {}
382
384 void Set(const DepGraph<SetType>& depgraph, DepGraphIndex pos) noexcept
385 {
386 Assume(!transactions[pos]);
387 transactions.Set(pos);
388 feerate += depgraph.FeeRate(pos);
389 }
390
392 SetInfo& operator|=(const SetInfo& other) noexcept
393 {
394 Assume(!transactions.Overlaps(other.transactions));
395 transactions |= other.transactions;
396 feerate += other.feerate;
397 return *this;
398 }
399
401 SetInfo& operator-=(const SetInfo& other) noexcept
402 {
403 Assume(other.transactions.IsSubsetOf(transactions));
404 transactions -= other.transactions;
405 feerate -= other.feerate;
406 return *this;
407 }
408
410 SetInfo operator-(const SetInfo& other) const noexcept
411 {
412 Assume(other.transactions.IsSubsetOf(transactions));
413 return {transactions - other.transactions, feerate - other.feerate};
414 }
415
417 friend void swap(SetInfo& a, SetInfo& b) noexcept
418 {
419 swap(a.transactions, b.transactions);
420 swap(a.feerate, b.feerate);
421 }
422
424 friend bool operator==(const SetInfo&, const SetInfo&) noexcept = default;
425};
426
428template<typename SetType>
429std::vector<SetInfo<SetType>> ChunkLinearizationInfo(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> linearization) noexcept
430{
431 std::vector<SetInfo<SetType>> ret;
432 for (DepGraphIndex i : linearization) {
434 SetInfo<SetType> new_chunk(depgraph, i);
435 // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
436 while (!ret.empty() && ByRatio{new_chunk.feerate} > ByRatio{ret.back().feerate}) {
437 new_chunk |= ret.back();
438 ret.pop_back();
439 }
440 // Actually move that new chunk into the chunking.
441 ret.emplace_back(std::move(new_chunk));
442 }
443 return ret;
444}
445
448template<typename SetType>
449std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> linearization) noexcept
450{
451 std::vector<FeeFrac> ret;
452 for (DepGraphIndex i : linearization) {
454 auto new_chunk = depgraph.FeeRate(i);
455 // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
456 while (!ret.empty() && ByRatio{new_chunk} > ByRatio{ret.back()}) {
457 new_chunk += ret.back();
458 ret.pop_back();
459 }
460 // Actually move that new chunk into the chunking.
461 ret.push_back(std::move(new_chunk));
462 }
463 return ret;
464}
465
467template<typename F, typename Arg>
469 std::regular_invocable<F, Arg, Arg> &&
470 std::is_same_v<std::invoke_result_t<F, Arg, Arg>, std::strong_ordering>;
471
474using IndexTxOrder = std::compare_three_way;
475
498{
499 uint64_t m_cost{0};
500
501public:
502 inline void InitializeBegin() noexcept {}
503 inline void InitializeEnd(int num_txns, int num_deps) noexcept
504 {
505 // Cost of initialization.
506 m_cost += 39 * num_txns;
507 // Cost of producing linearization at the end.
508 m_cost += 48 * num_txns + 4 * num_deps;
509 }
510 inline void GetLinearizationBegin() noexcept {}
511 inline void GetLinearizationEnd(int num_txns, int num_deps) noexcept
512 {
513 // Note that we account for the cost of the final linearization at the beginning (see
514 // InitializeEnd), because the cost budget decision needs to be made before calling
515 // GetLinearization.
516 // This function exists here to allow overriding it easily for benchmark purposes.
517 }
518 inline void MakeTopologicalBegin() noexcept {}
519 inline void MakeTopologicalEnd(int num_chunks, int num_steps) noexcept
520 {
521 m_cost += 20 * num_chunks + 28 * num_steps;
522 }
523 inline void StartOptimizingBegin() noexcept {}
524 inline void StartOptimizingEnd(int num_chunks) noexcept { m_cost += 13 * num_chunks; }
525 inline void ActivateBegin() noexcept {}
526 inline void ActivateEnd(int num_deps) noexcept { m_cost += 10 * num_deps + 1; }
527 inline void DeactivateBegin() noexcept {}
528 inline void DeactivateEnd(int num_deps) noexcept { m_cost += 11 * num_deps + 8; }
529 inline void MergeChunksBegin() noexcept {}
530 inline void MergeChunksMid(int num_txns) noexcept { m_cost += 2 * num_txns; }
531 inline void MergeChunksEnd(int num_steps) noexcept { m_cost += 3 * num_steps + 5; }
532 inline void PickMergeCandidateBegin() noexcept {}
533 inline void PickMergeCandidateEnd(int num_steps) noexcept { m_cost += 8 * num_steps; }
534 inline void PickChunkToOptimizeBegin() noexcept {}
535 inline void PickChunkToOptimizeEnd(int num_steps) noexcept { m_cost += num_steps + 4; }
536 inline void PickDependencyToSplitBegin() noexcept {}
537 inline void PickDependencyToSplitEnd(int num_txns) noexcept { m_cost += 8 * num_txns + 9; }
538 inline void StartMinimizingBegin() noexcept {}
539 inline void StartMinimizingEnd(int num_chunks) noexcept { m_cost += 18 * num_chunks; }
540 inline void MinimizeStepBegin() noexcept {}
541 inline void MinimizeStepMid(int num_txns) noexcept { m_cost += 11 * num_txns + 11; }
542 inline void MinimizeStepEnd(bool split) noexcept { m_cost += 17 * split + 7; }
543
544 inline uint64_t GetCost() const noexcept { return m_cost; }
545};
546
722template<typename SetType, typename CostModel = SFLDefaultCostModel>
724{
725private:
728
733 using SetIdx = std::conditional_t<(SetType::Size() <= 0xff),
734 uint8_t,
735 std::conditional_t<(SetType::Size() <= 0xffff),
736 uint16_t,
737 uint32_t>>;
739 static constexpr SetIdx INVALID_SET_IDX = SetIdx(-1);
740
742 struct TxData {
745 std::array<SetIdx, SetType::Size()> dep_top_idx;
747 SetType parents;
749 SetType children;
754 };
755
767 std::vector<TxData> m_tx_data;
769 std::vector<SetInfo<SetType>> m_set_info;
772 std::vector<std::pair<SetType, SetType>> m_reachable;
782
785
787 CostModel m_cost;
788
790 TxIdx PickRandomTx(const SetType& tx_idxs) noexcept
791 {
792 Assume(tx_idxs.Any());
793 unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count());
794 for (auto tx_idx : tx_idxs) {
795 if (pos == 0) return tx_idx;
796 --pos;
797 }
798 Assume(false);
799 return TxIdx(-1);
800 }
801
805 std::pair<SetType, SetType> GetReachable(const SetType& tx_idxs) const noexcept
806 {
807 SetType parents, children;
808 for (auto tx_idx : tx_idxs) {
809 const auto& tx_data = m_tx_data[tx_idx];
810 parents |= tx_data.parents;
811 children |= tx_data.children;
812 }
813 return {parents - tx_idxs, children - tx_idxs};
814 }
815
818 SetIdx Activate(TxIdx parent_idx, TxIdx child_idx) noexcept
819 {
820 m_cost.ActivateBegin();
821 // Gather and check information about the parent and child transactions.
822 auto& parent_data = m_tx_data[parent_idx];
823 auto& child_data = m_tx_data[child_idx];
824 Assume(parent_data.children[child_idx]);
825 Assume(!parent_data.active_children[child_idx]);
826 // Get the set index of the chunks the parent and child are currently in. The parent chunk
827 // will become the top set of the newly activated dependency, while the child chunk will be
828 // grown to become the merged chunk.
829 auto parent_chunk_idx = parent_data.chunk_idx;
830 auto child_chunk_idx = child_data.chunk_idx;
831 Assume(parent_chunk_idx != child_chunk_idx);
832 Assume(m_chunk_idxs[parent_chunk_idx]);
833 Assume(m_chunk_idxs[child_chunk_idx]);
834 auto& top_info = m_set_info[parent_chunk_idx];
835 auto& bottom_info = m_set_info[child_chunk_idx];
836
837 // Consider the following example:
838 //
839 // A A There are two chunks, ABC and DEF, and the inactive E->C dependency
840 // / \ / \ is activated, resulting in a single chunk ABCDEF.
841 // B C B C
842 // : ==> | Dependency | top set before | top set after | change
843 // D E D E B->A | AC | ACDEF | +DEF
844 // \ / \ / C->A | AB | AB |
845 // F F F->D | D | D |
846 // F->E | E | ABCE | +ABC
847 //
848 // The common pattern here is that any dependency which has the parent or child of the
849 // dependency being activated (E->C here) in its top set, will have the opposite part added
850 // to it. This is true for B->A and F->E, but not for C->A and F->D.
851 //
852 // Traverse the old parent chunk top_info (ABC in example), and add bottom_info (DEF) to
853 // every dependency's top set which has the parent (C) in it. At the same time, change the
854 // chunk_idx for each to be child_chunk_idx, which becomes the set for the merged chunk.
855 for (auto tx_idx : top_info.transactions) {
856 auto& tx_data = m_tx_data[tx_idx];
857 tx_data.chunk_idx = child_chunk_idx;
858 for (auto dep_child_idx : tx_data.active_children) {
859 auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]];
860 if (dep_top_info.transactions[parent_idx]) dep_top_info |= bottom_info;
861 }
862 }
863 // Traverse the old child chunk bottom_info (DEF in example), and add top_info (ABC) to
864 // every dependency's top set which has the child (E) in it.
865 for (auto tx_idx : bottom_info.transactions) {
866 auto& tx_data = m_tx_data[tx_idx];
867 for (auto dep_child_idx : tx_data.active_children) {
868 auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]];
869 if (dep_top_info.transactions[child_idx]) dep_top_info |= top_info;
870 }
871 }
872 // Merge top_info into bottom_info, which becomes the merged chunk.
873 bottom_info |= top_info;
874 // Compute merged sets of reachable transactions from the new chunk, based on the input
875 // chunks' reachable sets.
876 m_reachable[child_chunk_idx].first |= m_reachable[parent_chunk_idx].first;
877 m_reachable[child_chunk_idx].second |= m_reachable[parent_chunk_idx].second;
878 m_reachable[child_chunk_idx].first -= bottom_info.transactions;
879 m_reachable[child_chunk_idx].second -= bottom_info.transactions;
880 // Make parent chunk the set for the new active dependency.
881 parent_data.dep_top_idx[child_idx] = parent_chunk_idx;
882 parent_data.active_children.Set(child_idx);
883 m_chunk_idxs.Reset(parent_chunk_idx);
884 // Return the newly merged chunk.
885 m_cost.ActivateEnd(/*num_deps=*/bottom_info.transactions.Count() - 1);
886 return child_chunk_idx;
887 }
888
891 std::pair<SetIdx, SetIdx> Deactivate(TxIdx parent_idx, TxIdx child_idx) noexcept
892 {
893 m_cost.DeactivateBegin();
894 // Gather and check information about the parent transactions.
895 auto& parent_data = m_tx_data[parent_idx];
896 Assume(parent_data.children[child_idx]);
897 Assume(parent_data.active_children[child_idx]);
898 // Get the top set of the active dependency (which will become the parent chunk) and the
899 // chunk set the transactions are currently in (which will become the bottom chunk).
900 auto parent_chunk_idx = parent_data.dep_top_idx[child_idx];
901 auto child_chunk_idx = parent_data.chunk_idx;
902 Assume(parent_chunk_idx != child_chunk_idx);
903 Assume(m_chunk_idxs[child_chunk_idx]);
904 Assume(!m_chunk_idxs[parent_chunk_idx]); // top set, not a chunk
905 auto& top_info = m_set_info[parent_chunk_idx];
906 auto& bottom_info = m_set_info[child_chunk_idx];
907
908 // Remove the active dependency.
909 parent_data.active_children.Reset(child_idx);
910 m_chunk_idxs.Set(parent_chunk_idx);
911 auto ntx = bottom_info.transactions.Count();
912 // Subtract the top_info from the bottom_info, as it will become the child chunk.
913 bottom_info -= top_info;
914 // See the comment above in Activate(). We perform the opposite operations here, removing
915 // instead of adding. Simultaneously, aggregate the top/bottom's union of parents/children.
916 SetType top_parents, top_children;
917 for (auto tx_idx : top_info.transactions) {
918 auto& tx_data = m_tx_data[tx_idx];
919 tx_data.chunk_idx = parent_chunk_idx;
920 top_parents |= tx_data.parents;
921 top_children |= tx_data.children;
922 for (auto dep_child_idx : tx_data.active_children) {
923 auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]];
924 if (dep_top_info.transactions[parent_idx]) dep_top_info -= bottom_info;
925 }
926 }
927 SetType bottom_parents, bottom_children;
928 for (auto tx_idx : bottom_info.transactions) {
929 auto& tx_data = m_tx_data[tx_idx];
930 bottom_parents |= tx_data.parents;
931 bottom_children |= tx_data.children;
932 for (auto dep_child_idx : tx_data.active_children) {
933 auto& dep_top_info = m_set_info[tx_data.dep_top_idx[dep_child_idx]];
934 if (dep_top_info.transactions[child_idx]) dep_top_info -= top_info;
935 }
936 }
937 // Compute the new sets of reachable transactions for each new chunk, based on the
938 // top/bottom parents and children computed above.
939 m_reachable[parent_chunk_idx].first = top_parents - top_info.transactions;
940 m_reachable[parent_chunk_idx].second = top_children - top_info.transactions;
941 m_reachable[child_chunk_idx].first = bottom_parents - bottom_info.transactions;
942 m_reachable[child_chunk_idx].second = bottom_children - bottom_info.transactions;
943 // Return the two new set idxs.
944 m_cost.DeactivateEnd(/*num_deps=*/ntx - 1);
945 return {parent_chunk_idx, child_chunk_idx};
946 }
947
950 SetIdx MergeChunks(SetIdx top_idx, SetIdx bottom_idx) noexcept
951 {
952 m_cost.MergeChunksBegin();
953 Assume(m_chunk_idxs[top_idx]);
954 Assume(m_chunk_idxs[bottom_idx]);
955 auto& top_chunk_info = m_set_info[top_idx];
956 auto& bottom_chunk_info = m_set_info[bottom_idx];
957 // Count the number of dependencies between bottom_chunk and top_chunk, remembering the
958 // per-transaction counts so the picking loop below does not need to recompute the
959 // intersections.
960 unsigned num_deps{0};
961 std::array<SetIdx, SetType::Size()> counts;
962 for (auto tx_idx : top_chunk_info.transactions) {
963 auto& tx_data = m_tx_data[tx_idx];
964 auto count = (tx_data.children & bottom_chunk_info.transactions).Count();
965 counts[tx_idx] = count;
966 num_deps += count;
967 }
968 m_cost.MergeChunksMid(/*num_txns=*/top_chunk_info.transactions.Count());
969 Assume(num_deps > 0);
970 // Uniformly randomly pick one of them and activate it.
971 unsigned pick = m_rng.randrange(num_deps);
972 unsigned num_steps = 0;
973 for (auto tx_idx : top_chunk_info.transactions) {
974 ++num_steps;
975 auto count = counts[tx_idx];
976 if (pick < count) {
977 auto& tx_data = m_tx_data[tx_idx];
978 auto intersect = tx_data.children & bottom_chunk_info.transactions;
979 for (auto child_idx : intersect) {
980 if (pick == 0) {
981 m_cost.MergeChunksEnd(/*num_steps=*/num_steps);
982 return Activate(tx_idx, child_idx);
983 }
984 --pick;
985 }
986 Assume(false);
987 break;
988 }
989 pick -= count;
990 }
991 Assume(false);
992 return INVALID_SET_IDX;
993 }
994
997 template<bool DownWard>
998 SetIdx MergeChunksDirected(SetIdx chunk_idx, SetIdx merge_chunk_idx) noexcept
999 {
1000 if constexpr (DownWard) {
1001 return MergeChunks(chunk_idx, merge_chunk_idx);
1002 } else {
1003 return MergeChunks(merge_chunk_idx, chunk_idx);
1004 }
1005 }
1006
1008 template<bool DownWard>
1010 {
1011 m_cost.PickMergeCandidateBegin();
1013 Assume(m_chunk_idxs[chunk_idx]);
1014 auto& chunk_info = m_set_info[chunk_idx];
1015 // Iterate over all chunks reachable from this one. For those depended-on chunks,
1016 // remember the highest-feerate (if DownWard) or lowest-feerate (if !DownWard) one.
1017 // If multiple equal-feerate candidate chunks to merge with exist, pick a random one
1018 // among them.
1019
1023 FeeFrac best_other_chunk_feerate = chunk_info.feerate;
1025 SetIdx best_other_chunk_idx = INVALID_SET_IDX;
1028 uint64_t best_other_chunk_tiebreak{0};
1029
1031 auto todo = DownWard ? m_reachable[chunk_idx].second : m_reachable[chunk_idx].first;
1032 unsigned steps = 0;
1033 while (todo.Any()) {
1034 ++steps;
1035 // Find a chunk for a transaction in todo, and remove all its transactions from todo.
1036 auto reached_chunk_idx = m_tx_data[todo.First()].chunk_idx;
1037 auto& reached_chunk_info = m_set_info[reached_chunk_idx];
1038 todo -= reached_chunk_info.transactions;
1039 // See if it has an acceptable feerate.
1040 auto cmp = DownWard ? ByRatio{best_other_chunk_feerate} <=> ByRatio{reached_chunk_info.feerate}
1041 : ByRatio{reached_chunk_info.feerate} <=> ByRatio{best_other_chunk_feerate};
1042 if (cmp > 0) continue;
1043 uint64_t tiebreak = m_rng.rand64();
1044 if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) {
1045 best_other_chunk_feerate = reached_chunk_info.feerate;
1046 best_other_chunk_idx = reached_chunk_idx;
1047 best_other_chunk_tiebreak = tiebreak;
1048 }
1049 }
1050 Assume(steps <= m_set_info.size());
1051
1052 m_cost.PickMergeCandidateEnd(/*num_steps=*/steps);
1053 return best_other_chunk_idx;
1054 }
1055
1058 template<bool DownWard>
1059 SetIdx MergeStep(SetIdx chunk_idx) noexcept
1060 {
1061 auto merge_chunk_idx = PickMergeCandidate<DownWard>(chunk_idx);
1062 if (merge_chunk_idx == INVALID_SET_IDX) return INVALID_SET_IDX;
1063 chunk_idx = MergeChunksDirected<DownWard>(chunk_idx, merge_chunk_idx);
1064 Assume(chunk_idx != INVALID_SET_IDX);
1065 return chunk_idx;
1066 }
1067
1069 template<bool DownWard>
1070 void MergeSequence(SetIdx chunk_idx) noexcept
1071 {
1072 Assume(m_chunk_idxs[chunk_idx]);
1073 while (true) {
1074 auto merged_chunk_idx = MergeStep<DownWard>(chunk_idx);
1075 if (merged_chunk_idx == INVALID_SET_IDX) break;
1076 chunk_idx = merged_chunk_idx;
1077 }
1078 // Add the chunk to the queue of improvable chunks, if it wasn't already there.
1079 if (!m_suboptimal_idxs[chunk_idx]) {
1080 m_suboptimal_idxs.Set(chunk_idx);
1081 m_suboptimal_chunks.push_back(chunk_idx);
1082 }
1083 }
1084
1087 void Improve(TxIdx parent_idx, TxIdx child_idx) noexcept
1088 {
1089 // Deactivate the specified dependency, splitting it into two new chunks: a top containing
1090 // the parent, and a bottom containing the child. The top should have a higher feerate.
1091 auto [parent_chunk_idx, child_chunk_idx] = Deactivate(parent_idx, child_idx);
1092
1093 // At this point we have exactly two chunks which may violate topology constraints (the
1094 // parent chunk and child chunk that were produced by deactivation). We can fix
1095 // these using just merge sequences, one upwards and one downwards, avoiding the need for a
1096 // full MakeTopological.
1097 const auto& parent_reachable = m_reachable[parent_chunk_idx].first;
1098 const auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions;
1099 if (parent_reachable.Overlaps(child_chunk_txn)) {
1100 // The parent chunk has a dependency on a transaction in the child chunk. In this case,
1101 // the parent needs to merge back with the child chunk (a self-merge), and no other
1102 // merges are needed. Special-case this, so the overhead of PickMergeCandidate and
1103 // MergeSequence can be avoided.
1104
1105 // In the self-merge, the roles reverse: the parent chunk (from the split) depends
1106 // on the child chunk, so child_chunk_idx is the "top" and parent_chunk_idx is the
1107 // "bottom" for MergeChunks.
1108 auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx);
1109 if (!m_suboptimal_idxs[merged_chunk_idx]) {
1110 m_suboptimal_idxs.Set(merged_chunk_idx);
1111 m_suboptimal_chunks.push_back(merged_chunk_idx);
1112 }
1113 } else {
1114 // Merge the top chunk with lower-feerate chunks it depends on.
1115 MergeSequence<false>(parent_chunk_idx);
1116 // Merge the bottom chunk with higher-feerate chunks that depend on it.
1117 MergeSequence<true>(child_chunk_idx);
1118 }
1119 }
1120
1123 {
1124 m_cost.PickChunkToOptimizeBegin();
1125 unsigned steps{0};
1126 while (!m_suboptimal_chunks.empty()) {
1127 ++steps;
1128 // Pop an entry from the potentially-suboptimal chunk queue.
1129 SetIdx chunk_idx = m_suboptimal_chunks.front();
1130 Assume(m_suboptimal_idxs[chunk_idx]);
1131 m_suboptimal_idxs.Reset(chunk_idx);
1133 if (m_chunk_idxs[chunk_idx]) {
1134 m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps);
1135 return chunk_idx;
1136 }
1137 // If what was popped is not currently a chunk, continue. This may
1138 // happen when a split chunk merges in Improve() with one or more existing chunks that
1139 // are themselves on the suboptimal queue already.
1140 }
1141 m_cost.PickChunkToOptimizeEnd(/*num_steps=*/steps);
1142 return INVALID_SET_IDX;
1143 }
1144
1146 std::pair<TxIdx, TxIdx> PickDependencyToSplit(SetIdx chunk_idx) noexcept
1147 {
1148 m_cost.PickDependencyToSplitBegin();
1149 Assume(m_chunk_idxs[chunk_idx]);
1150 auto& chunk_info = m_set_info[chunk_idx];
1151
1152 // Remember the best dependency {par, chl} seen so far.
1153 std::pair<TxIdx, TxIdx> candidate_dep = {TxIdx(-1), TxIdx(-1)};
1154 uint64_t candidate_tiebreak = 0;
1155 // Iterate over all transactions.
1156 for (auto tx_idx : chunk_info.transactions) {
1157 const auto& tx_data = m_tx_data[tx_idx];
1158 // Iterate over all active child dependencies of the transaction.
1159 for (auto child_idx : tx_data.active_children) {
1160 auto& dep_top_info = m_set_info[tx_data.dep_top_idx[child_idx]];
1161 // Skip if this dependency is ineligible (the top chunk that would be created
1162 // does not have higher feerate than the chunk it is currently part of).
1163 auto cmp = ByRatio{dep_top_info.feerate} <=> ByRatio{chunk_info.feerate};
1164 if (cmp <= 0) continue;
1165 // Generate a random tiebreak for this dependency, and reject it if its tiebreak
1166 // is worse than the best so far. This means that among all eligible
1167 // dependencies, a uniformly random one will be chosen.
1168 uint64_t tiebreak = m_rng.rand64();
1169 if (tiebreak < candidate_tiebreak) continue;
1170 // Remember this as our (new) candidate dependency.
1171 candidate_dep = {tx_idx, child_idx};
1172 candidate_tiebreak = tiebreak;
1173 }
1174 }
1175 m_cost.PickDependencyToSplitEnd(/*num_txns=*/chunk_info.transactions.Count());
1176 return candidate_dep;
1177 }
1178
1179public:
1182 explicit SpanningForestState(const DepGraph<SetType>& depgraph LIFETIMEBOUND, uint64_t rng_seed, const CostModel& cost = CostModel{}) noexcept :
1183 m_rng(rng_seed), m_depgraph(depgraph), m_cost(cost)
1184 {
1185 m_cost.InitializeBegin();
1186 m_transaction_idxs = depgraph.Positions();
1187 auto num_transactions = m_transaction_idxs.Count();
1188 m_tx_data.resize(depgraph.PositionRange());
1189 m_set_info.resize(num_transactions);
1190 m_reachable.resize(num_transactions);
1191 m_suboptimal_chunks.reserve(num_transactions);
1192 size_t num_chunks = 0;
1193 size_t num_deps = 0;
1194 for (auto tx_idx : m_transaction_idxs) {
1195 // Fill in transaction data.
1196 auto& tx_data = m_tx_data[tx_idx];
1197 tx_data.parents = depgraph.GetReducedParents(tx_idx);
1198 for (auto parent_idx : tx_data.parents) {
1199 m_tx_data[parent_idx].children.Set(tx_idx);
1200 }
1201 num_deps += tx_data.parents.Count();
1202 // Create a singleton chunk for it.
1203 tx_data.chunk_idx = num_chunks;
1204 m_set_info[num_chunks++] = SetInfo(depgraph, tx_idx);
1205 }
1206 // Set the reachable transactions for each chunk to the transactions' parents and children.
1207 for (SetIdx chunk_idx = 0; chunk_idx < num_transactions; ++chunk_idx) {
1208 auto& tx_data = m_tx_data[m_set_info[chunk_idx].transactions.First()];
1209 m_reachable[chunk_idx].first = tx_data.parents;
1210 m_reachable[chunk_idx].second = tx_data.children;
1211 }
1212 Assume(num_chunks == num_transactions);
1213 // Mark all chunk sets as chunks.
1214 m_chunk_idxs = SetType::Fill(num_chunks);
1215 m_cost.InitializeEnd(/*num_txns=*/num_chunks, /*num_deps=*/num_deps);
1216 }
1217
1221 void LoadLinearization(std::span<const DepGraphIndex> old_linearization) noexcept
1222 {
1223 // Add transactions one by one, in order of existing linearization.
1224 for (DepGraphIndex tx_idx : old_linearization) {
1225 auto chunk_idx = m_tx_data[tx_idx].chunk_idx;
1226 // Merge the chunk upwards, as long as merging succeeds.
1227 while (true) {
1228 chunk_idx = MergeStep<false>(chunk_idx);
1229 if (chunk_idx == INVALID_SET_IDX) break;
1230 }
1231 }
1232 }
1233
1235 void MakeTopological() noexcept
1236 {
1237 m_cost.MakeTopologicalBegin();
1244 unsigned init_dir = m_rng.randbool();
1247 SetType merged_chunks;
1248 // Mark chunks as suboptimal.
1250 for (auto chunk_idx : m_chunk_idxs) {
1252 // Randomize the initial order of suboptimal chunks in the queue.
1254 if (j != m_suboptimal_chunks.size() - 1) {
1256 }
1257 }
1258 unsigned chunks = m_chunk_idxs.Count();
1259 unsigned steps = 0;
1260 while (!m_suboptimal_chunks.empty()) {
1261 ++steps;
1262 // Pop an entry from the potentially-suboptimal chunk queue.
1263 SetIdx chunk_idx = m_suboptimal_chunks.front();
1265 Assume(m_suboptimal_idxs[chunk_idx]);
1266 m_suboptimal_idxs.Reset(chunk_idx);
1267 // If what was popped is not currently a chunk, continue. This may
1268 // happen when it was merged with something else since being added.
1269 if (!m_chunk_idxs[chunk_idx]) continue;
1271 unsigned direction = merged_chunks[chunk_idx] ? 3 : init_dir + 1;
1272 int flip = m_rng.randbool();
1273 for (int i = 0; i < 2; ++i) {
1274 if (i ^ flip) {
1275 if (!(direction & 1)) continue;
1276 // Attempt to merge the chunk upwards.
1277 auto result_up = MergeStep<false>(chunk_idx);
1278 if (result_up != INVALID_SET_IDX) {
1279 if (!m_suboptimal_idxs[result_up]) {
1280 m_suboptimal_idxs.Set(result_up);
1281 m_suboptimal_chunks.push_back(result_up);
1282 }
1283 merged_chunks.Set(result_up);
1284 break;
1285 }
1286 } else {
1287 if (!(direction & 2)) continue;
1288 // Attempt to merge the chunk downwards.
1289 auto result_down = MergeStep<true>(chunk_idx);
1290 if (result_down != INVALID_SET_IDX) {
1291 if (!m_suboptimal_idxs[result_down]) {
1292 m_suboptimal_idxs.Set(result_down);
1293 m_suboptimal_chunks.push_back(result_down);
1294 }
1295 merged_chunks.Set(result_down);
1296 break;
1297 }
1298 }
1299 }
1300 }
1301 m_cost.MakeTopologicalEnd(/*num_chunks=*/chunks, /*num_steps=*/steps);
1302 }
1303
1305 void StartOptimizing() noexcept
1306 {
1307 m_cost.StartOptimizingBegin();
1309 // Mark chunks suboptimal.
1311 for (auto chunk_idx : m_chunk_idxs) {
1312 m_suboptimal_chunks.push_back(chunk_idx);
1313 // Randomize the initial order of suboptimal chunks in the queue.
1315 if (j != m_suboptimal_chunks.size() - 1) {
1317 }
1318 }
1319 m_cost.StartOptimizingEnd(/*num_chunks=*/m_suboptimal_chunks.size());
1320 }
1321
1323 bool OptimizeStep() noexcept
1324 {
1325 auto chunk_idx = PickChunkToOptimize();
1326 if (chunk_idx == INVALID_SET_IDX) {
1327 // No improvable chunk was found, we are done.
1328 return false;
1329 }
1330 auto [parent_idx, child_idx] = PickDependencyToSplit(chunk_idx);
1331 if (parent_idx == TxIdx(-1)) {
1332 // Nothing to improve in chunk_idx. Need to continue with other chunks, if any.
1333 return !m_suboptimal_chunks.empty();
1334 }
1335 // Deactivate the found dependency and then make the state topological again with a
1336 // sequence of merges.
1337 Improve(parent_idx, child_idx);
1338 return true;
1339 }
1340
1343 void StartMinimizing() noexcept
1344 {
1345 m_cost.StartMinimizingBegin();
1348 // Gather all chunks, and for each, add it with a random pivot in it, and a random initial
1349 // direction, to m_nonminimal_chunks.
1350 for (auto chunk_idx : m_chunk_idxs) {
1351 TxIdx pivot_idx = PickRandomTx(m_set_info[chunk_idx].transactions);
1352 m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, m_rng.randbits<1>());
1353 // Randomize the initial order of nonminimal chunks in the queue.
1355 if (j != m_nonminimal_chunks.size() - 1) {
1357 }
1358 }
1359 m_cost.StartMinimizingEnd(/*num_chunks=*/m_nonminimal_chunks.size());
1360 }
1361
1363 bool MinimizeStep() noexcept
1364 {
1365 // If the queue of potentially-non-minimal chunks is empty, we are done.
1366 if (m_nonminimal_chunks.empty()) return false;
1367 m_cost.MinimizeStepBegin();
1368 // Pop an entry from the potentially-non-minimal chunk queue.
1369 auto [chunk_idx, pivot_idx, flags] = m_nonminimal_chunks.front();
1371 auto& chunk_info = m_set_info[chunk_idx];
1373 bool move_pivot_down = flags & 1;
1375 bool second_stage = flags & 2;
1376
1377 // Find a random dependency whose top and bottom set feerates are equal, and which has
1378 // pivot in bottom set (if move_pivot_down) or in top set (if !move_pivot_down).
1379 std::pair<TxIdx, TxIdx> candidate_dep;
1380 uint64_t candidate_tiebreak{0};
1381 bool have_any = false;
1382 // Iterate over all transactions.
1383 for (auto tx_idx : chunk_info.transactions) {
1384 const auto& tx_data = m_tx_data[tx_idx];
1385 // Iterate over all active child dependencies of the transaction.
1386 for (auto child_idx : tx_data.active_children) {
1387 const auto& dep_top_info = m_set_info[tx_data.dep_top_idx[child_idx]];
1388 // Skip if this dependency does not have equal top and bottom set feerates. Note
1389 // that the top cannot have higher feerate than the bottom, or OptimizeSteps would
1390 // have dealt with it.
1391 if (ByRatio{dep_top_info.feerate} < ByRatio{chunk_info.feerate}) continue;
1392 have_any = true;
1393 // Skip if this dependency does not have pivot in the right place.
1394 if (move_pivot_down == dep_top_info.transactions[pivot_idx]) continue;
1395 // Remember this as our chosen dependency if it has a better tiebreak.
1396 uint64_t tiebreak = m_rng.rand64() | 1;
1397 if (tiebreak > candidate_tiebreak) {
1398 candidate_tiebreak = tiebreak;
1399 candidate_dep = {tx_idx, child_idx};
1400 }
1401 }
1402 }
1403 m_cost.MinimizeStepMid(/*num_txns=*/chunk_info.transactions.Count());
1404 // If no dependencies have equal top and bottom set feerate, this chunk is minimal.
1405 if (!have_any) return true;
1406 // If all found dependencies have the pivot in the wrong place, try moving it in the other
1407 // direction. If this was the second stage already, we are done.
1408 if (candidate_tiebreak == 0) {
1409 // Switch to other direction, and to second phase.
1410 flags ^= 3;
1411 if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_idx, pivot_idx, flags);
1412 return true;
1413 }
1414
1415 // Otherwise, deactivate the dependency that was found.
1416 auto [parent_chunk_idx, child_chunk_idx] = Deactivate(candidate_dep.first, candidate_dep.second);
1417 // Determine if there is a dependency from the new bottom to the new top (opposite from the
1418 // dependency that was just deactivated).
1419 auto& parent_reachable = m_reachable[parent_chunk_idx].first;
1420 auto& child_chunk_txn = m_set_info[child_chunk_idx].transactions;
1421 if (parent_reachable.Overlaps(child_chunk_txn)) {
1422 // A self-merge is needed. Note that the child_chunk_idx is the top, and
1423 // parent_chunk_idx is the bottom, because we activate a dependency in the reverse
1424 // direction compared to the deactivation above.
1425 auto merged_chunk_idx = MergeChunks(child_chunk_idx, parent_chunk_idx);
1426 // Re-insert the chunk into the queue, in the same direction. Note that the chunk_idx
1427 // will have changed.
1428 m_nonminimal_chunks.emplace_back(merged_chunk_idx, pivot_idx, flags);
1429 m_cost.MinimizeStepEnd(/*split=*/false);
1430 } else {
1431 // No self-merge happens, and thus we have found a way to split the chunk. Create two
1432 // smaller chunks, and add them to the queue. The one that contains the current pivot
1433 // gets to continue with it in the same direction, to minimize the number of times we
1434 // alternate direction. If we were in the second phase already, the newly created chunk
1435 // inherits that too, because we know no split with the pivot on the other side is
1436 // possible already. The new chunk without the current pivot gets a new randomly-chosen
1437 // one.
1438 if (move_pivot_down) {
1439 auto parent_pivot_idx = PickRandomTx(m_set_info[parent_chunk_idx].transactions);
1440 m_nonminimal_chunks.emplace_back(parent_chunk_idx, parent_pivot_idx, m_rng.randbits<1>());
1441 m_nonminimal_chunks.emplace_back(child_chunk_idx, pivot_idx, flags);
1442 } else {
1443 auto child_pivot_idx = PickRandomTx(m_set_info[child_chunk_idx].transactions);
1444 m_nonminimal_chunks.emplace_back(parent_chunk_idx, pivot_idx, flags);
1445 m_nonminimal_chunks.emplace_back(child_chunk_idx, child_pivot_idx, m_rng.randbits<1>());
1446 }
1447 if (m_rng.randbool()) {
1449 }
1450 m_cost.MinimizeStepEnd(/*split=*/true);
1451 }
1452 return true;
1453 }
1454
1471 std::vector<DepGraphIndex> GetLinearization(const StrongComparator<DepGraphIndex> auto& fallback_order) noexcept
1472 {
1473 m_cost.GetLinearizationBegin();
1475 std::vector<DepGraphIndex> ret;
1476 ret.reserve(m_set_info.size());
1480 std::array<std::pair<SetIdx, TxIdx>, SetType::Size()> ready_chunks;
1482 unsigned num_ready_chunks{0};
1485 std::array<TxIdx, SetType::Size()> chunk_deps;
1486 std::fill_n(chunk_deps.begin(), m_set_info.size(), TxIdx{0});
1489 std::array<TxIdx, SetType::Size()> tx_deps;
1490 std::fill_n(tx_deps.begin(), m_tx_data.size(), TxIdx{0});
1493 std::array<TxIdx, SetType::Size()> ready_tx;
1495 unsigned num_ready_tx{0};
1496 // Populate chunk_deps and tx_deps.
1497 unsigned num_deps{0};
1498 for (TxIdx chl_idx : m_transaction_idxs) {
1499 const auto& chl_data = m_tx_data[chl_idx];
1500 tx_deps[chl_idx] = chl_data.parents.Count();
1501 num_deps += tx_deps[chl_idx];
1502 auto chl_chunk_idx = chl_data.chunk_idx;
1503 auto& chl_chunk_info = m_set_info[chl_chunk_idx];
1504 chunk_deps[chl_chunk_idx] += (chl_data.parents - chl_chunk_info.transactions).Count();
1505 }
1507 auto max_fallback_fn = [&](SetIdx chunk_idx) noexcept {
1508 auto& chunk = m_set_info[chunk_idx].transactions;
1509 auto it = chunk.begin();
1510 DepGraphIndex ret = *it;
1511 ++it;
1512 while (it != chunk.end()) {
1513 if (fallback_order(*it, ret) > 0) ret = *it;
1514 ++it;
1515 }
1516 return ret;
1517 };
1520 auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept {
1521 // Bail out for identical transactions.
1522 if (a == b) return false;
1523 // First sort by increasing transaction feerate.
1524 auto& a_feerate = m_depgraph.FeeRate(a);
1525 auto& b_feerate = m_depgraph.FeeRate(b);
1526 auto feerate_cmp = ByRatio{a_feerate} <=> ByRatio{b_feerate};
1527 if (feerate_cmp != 0) return feerate_cmp < 0;
1528 // Then by decreasing transaction size.
1529 if (a_feerate.size != b_feerate.size) {
1530 return a_feerate.size > b_feerate.size;
1531 }
1532 // Tie-break by decreasing fallback_order.
1533 auto fallback_cmp = fallback_order(a, b);
1534 if (fallback_cmp != 0) return fallback_cmp > 0;
1535 // This should not be hit, because fallback_order defines a strong ordering.
1536 Assume(false);
1537 return a < b;
1538 };
1539 // Construct a heap with all chunks that have no out-of-chunk dependencies.
1542 auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept {
1543 // Bail out for identical chunks.
1544 if (a.first == b.first) return false;
1545 // First sort by increasing chunk feerate.
1546 auto& chunk_feerate_a = m_set_info[a.first].feerate;
1547 auto& chunk_feerate_b = m_set_info[b.first].feerate;
1548 auto feerate_cmp = ByRatio{chunk_feerate_a} <=> ByRatio{chunk_feerate_b};
1549 if (feerate_cmp != 0) return feerate_cmp < 0;
1550 // Then by decreasing chunk size.
1551 if (chunk_feerate_a.size != chunk_feerate_b.size) {
1552 return chunk_feerate_a.size > chunk_feerate_b.size;
1553 }
1554 // Tie-break by decreasing fallback_order.
1555 auto fallback_cmp = fallback_order(a.second, b.second);
1556 if (fallback_cmp != 0) return fallback_cmp > 0;
1557 // This should not be hit, because fallback_order defines a strong ordering.
1558 Assume(false);
1559 return a.second < b.second;
1560 };
1561 // Construct a heap with all chunks that have no out-of-chunk dependencies.
1562 for (SetIdx chunk_idx : m_chunk_idxs) {
1563 if (chunk_deps[chunk_idx] == 0) {
1564 ready_chunks[num_ready_chunks++] = {chunk_idx, max_fallback_fn(chunk_idx)};
1565 }
1566 }
1567 std::make_heap(ready_chunks.begin(), ready_chunks.begin() + num_ready_chunks, chunk_cmp_fn);
1568 // Pop chunks off the heap.
1569 while (num_ready_chunks > 0) {
1570 auto [chunk_idx, _rnd] = ready_chunks.front();
1571 std::pop_heap(ready_chunks.begin(), ready_chunks.begin() + num_ready_chunks, chunk_cmp_fn);
1572 --num_ready_chunks;
1573 Assume(chunk_deps[chunk_idx] == 0);
1574 const auto& chunk_txn = m_set_info[chunk_idx].transactions;
1575 // Build heap of all includable transactions in chunk.
1576 Assume(num_ready_tx == 0);
1577 for (TxIdx tx_idx : chunk_txn) {
1578 if (tx_deps[tx_idx] == 0) ready_tx[num_ready_tx++] = tx_idx;
1579 }
1580 Assume(num_ready_tx > 0);
1581 std::make_heap(ready_tx.begin(), ready_tx.begin() + num_ready_tx, tx_cmp_fn);
1582 // Pick transactions from the ready heap, append them to linearization, and decrement
1583 // dependency counts.
1584 while (num_ready_tx > 0) {
1585 // Pop an element from the tx_ready heap.
1586 auto tx_idx = ready_tx.front();
1587 std::pop_heap(ready_tx.begin(), ready_tx.begin() + num_ready_tx, tx_cmp_fn);
1588 --num_ready_tx;
1589 // Append to linearization.
1590 ret.push_back(tx_idx);
1591 // Decrement dependency counts.
1592 auto& tx_data = m_tx_data[tx_idx];
1593 for (TxIdx chl_idx : tx_data.children) {
1594 auto& chl_data = m_tx_data[chl_idx];
1595 // Decrement tx dependency count.
1596 Assume(tx_deps[chl_idx] > 0);
1597 if (--tx_deps[chl_idx] == 0 && chunk_txn[chl_idx]) {
1598 // Child tx has no dependencies left, and is in this chunk. Add it to the tx heap.
1599 ready_tx[num_ready_tx++] = chl_idx;
1600 std::push_heap(ready_tx.begin(), ready_tx.begin() + num_ready_tx, tx_cmp_fn);
1601 }
1602 // Decrement chunk dependency count if this is out-of-chunk dependency.
1603 if (chl_data.chunk_idx != chunk_idx) {
1604 Assume(chunk_deps[chl_data.chunk_idx] > 0);
1605 if (--chunk_deps[chl_data.chunk_idx] == 0) {
1606 // Child chunk has no dependencies left. Add it to the chunk heap.
1607 ready_chunks[num_ready_chunks++] = {chl_data.chunk_idx, max_fallback_fn(chl_data.chunk_idx)};
1608 std::push_heap(ready_chunks.begin(), ready_chunks.begin() + num_ready_chunks, chunk_cmp_fn);
1609 }
1610 }
1611 }
1612 }
1613 }
1614 Assume(ret.size() == m_set_info.size());
1615 m_cost.GetLinearizationEnd(/*num_txns=*/m_set_info.size(), /*num_deps=*/num_deps);
1616 return ret;
1617 }
1618
1632 std::vector<FeeFrac> GetDiagram() const noexcept
1633 {
1634 std::vector<FeeFrac> ret;
1635 for (auto chunk_idx : m_chunk_idxs) {
1636 ret.push_back(m_set_info[chunk_idx].feerate);
1637 }
1638 std::ranges::sort(ret, std::greater<ByRatioNegSize<FeeFrac>>{});
1639 return ret;
1640 }
1641
1643 uint64_t GetCost() const noexcept { return m_cost.GetCost(); }
1644
1646 void SanityCheck() const
1647 {
1648 //
1649 // Verify dependency parent/child information, and build list of (active) dependencies.
1650 //
1651 std::vector<std::pair<TxIdx, TxIdx>> expected_dependencies;
1652 std::vector<std::pair<TxIdx, TxIdx>> all_dependencies;
1653 std::vector<std::pair<TxIdx, TxIdx>> active_dependencies;
1654 for (auto parent_idx : m_depgraph.Positions()) {
1655 for (auto child_idx : m_depgraph.GetReducedChildren(parent_idx)) {
1656 expected_dependencies.emplace_back(parent_idx, child_idx);
1657 }
1658 }
1659 for (auto tx_idx : m_transaction_idxs) {
1660 for (auto child_idx : m_tx_data[tx_idx].children) {
1661 all_dependencies.emplace_back(tx_idx, child_idx);
1662 if (m_tx_data[tx_idx].active_children[child_idx]) {
1663 active_dependencies.emplace_back(tx_idx, child_idx);
1664 }
1665 }
1666 }
1667 std::ranges::sort(expected_dependencies);
1668 std::ranges::sort(all_dependencies);
1669 assert(expected_dependencies == all_dependencies);
1670
1671 //
1672 // Verify the chunks against the list of active dependencies
1673 //
1674 SetType chunk_cover;
1675 for (auto chunk_idx : m_chunk_idxs) {
1676 const auto& chunk_info = m_set_info[chunk_idx];
1677 // Verify that transactions in the chunk point back to it. This guarantees
1678 // that chunks are non-overlapping.
1679 for (auto tx_idx : chunk_info.transactions) {
1680 assert(m_tx_data[tx_idx].chunk_idx == chunk_idx);
1681 }
1682 assert(!chunk_cover.Overlaps(chunk_info.transactions));
1683 chunk_cover |= chunk_info.transactions;
1684 // Verify the chunk's transaction set: start from an arbitrary chunk transaction,
1685 // and for every active dependency, if it contains the parent or child, add the
1686 // other. It must have exactly N-1 active dependencies in it, guaranteeing it is
1687 // acyclic.
1688 assert(chunk_info.transactions.Any());
1689 SetType expected_chunk = SetType::Singleton(chunk_info.transactions.First());
1690 while (true) {
1691 auto old = expected_chunk;
1692 size_t active_dep_count{0};
1693 for (const auto& [par, chl] : active_dependencies) {
1694 if (expected_chunk[par] || expected_chunk[chl]) {
1695 expected_chunk.Set(par);
1696 expected_chunk.Set(chl);
1697 ++active_dep_count;
1698 }
1699 }
1700 if (old == expected_chunk) {
1701 assert(expected_chunk.Count() == active_dep_count + 1);
1702 break;
1703 }
1704 }
1705 assert(chunk_info.transactions == expected_chunk);
1706 // Verify the chunk's feerate.
1707 assert(chunk_info.feerate == m_depgraph.FeeRate(chunk_info.transactions));
1708 // Verify the chunk's reachable transactions.
1709 assert(m_reachable[chunk_idx] == GetReachable(expected_chunk));
1710 // Verify that the chunk's reachable transactions don't include its own transactions.
1711 assert(!m_reachable[chunk_idx].first.Overlaps(chunk_info.transactions));
1712 assert(!m_reachable[chunk_idx].second.Overlaps(chunk_info.transactions));
1713 }
1714 // Verify that together, the chunks cover all transactions.
1715 assert(chunk_cover == m_depgraph.Positions());
1716
1717 //
1718 // Verify transaction data.
1719 //
1720 assert(m_transaction_idxs == m_depgraph.Positions());
1721 for (auto tx_idx : m_transaction_idxs) {
1722 const auto& tx_data = m_tx_data[tx_idx];
1723 // Verify it has a valid chunk index, and that chunk includes this transaction.
1724 assert(m_chunk_idxs[tx_data.chunk_idx]);
1725 assert(m_set_info[tx_data.chunk_idx].transactions[tx_idx]);
1726 // Verify parents/children.
1727 assert(tx_data.parents == m_depgraph.GetReducedParents(tx_idx));
1728 assert(tx_data.children == m_depgraph.GetReducedChildren(tx_idx));
1729 // Verify active_children is a subset of children.
1730 assert(tx_data.active_children.IsSubsetOf(tx_data.children));
1731 // Verify each active child's dep_top_idx points to a valid non-chunk set.
1732 for (auto child_idx : tx_data.active_children) {
1733 assert(tx_data.dep_top_idx[child_idx] < m_set_info.size());
1734 assert(!m_chunk_idxs[tx_data.dep_top_idx[child_idx]]);
1735 }
1736 }
1737
1738 //
1739 // Verify active dependencies' top sets.
1740 //
1741 for (const auto& [par_idx, chl_idx] : active_dependencies) {
1742 // Verify the top set's transactions: it must contain the parent, and for every
1743 // active dependency, except the chl_idx->par_idx dependency itself, if it contains the
1744 // parent or child, it must contain both. It must have exactly N-1 active dependencies
1745 // in it, guaranteeing it is acyclic.
1746 SetType expected_top = SetType::Singleton(par_idx);
1747 while (true) {
1748 auto old = expected_top;
1749 size_t active_dep_count{0};
1750 for (const auto& [par2_idx, chl2_idx] : active_dependencies) {
1751 if (par_idx == par2_idx && chl_idx == chl2_idx) continue;
1752 if (expected_top[par2_idx] || expected_top[chl2_idx]) {
1753 expected_top.Set(par2_idx);
1754 expected_top.Set(chl2_idx);
1755 ++active_dep_count;
1756 }
1757 }
1758 if (old == expected_top) {
1759 assert(expected_top.Count() == active_dep_count + 1);
1760 break;
1761 }
1762 }
1763 assert(!expected_top[chl_idx]);
1764 auto& dep_top_info = m_set_info[m_tx_data[par_idx].dep_top_idx[chl_idx]];
1765 assert(dep_top_info.transactions == expected_top);
1766 // Verify the top set's feerate.
1767 assert(dep_top_info.feerate == m_depgraph.FeeRate(dep_top_info.transactions));
1768 }
1769
1770 //
1771 // Verify m_suboptimal_chunks.
1772 //
1773 SetType suboptimal_idxs;
1774 for (size_t i = 0; i < m_suboptimal_chunks.size(); ++i) {
1775 auto chunk_idx = m_suboptimal_chunks[i];
1776 assert(!suboptimal_idxs[chunk_idx]);
1777 suboptimal_idxs.Set(chunk_idx);
1778 }
1779 assert(m_suboptimal_idxs == suboptimal_idxs);
1780
1781 //
1782 // Verify m_nonminimal_chunks.
1783 //
1784 SetType nonminimal_idxs;
1785 for (size_t i = 0; i < m_nonminimal_chunks.size(); ++i) {
1786 auto [chunk_idx, pivot, flags] = m_nonminimal_chunks[i];
1787 assert(m_tx_data[pivot].chunk_idx == chunk_idx);
1788 assert(!nonminimal_idxs[chunk_idx]);
1789 nonminimal_idxs.Set(chunk_idx);
1790 }
1791 assert(nonminimal_idxs.IsSubsetOf(m_chunk_idxs));
1792 }
1793};
1794
1815template<typename SetType>
1816std::tuple<std::vector<DepGraphIndex>, bool, uint64_t> Linearize(
1817 const DepGraph<SetType>& depgraph,
1818 uint64_t max_cost,
1819 uint64_t rng_seed,
1820 const StrongComparator<DepGraphIndex> auto& fallback_order,
1821 std::span<const DepGraphIndex> old_linearization = {},
1822 bool is_topological = true) noexcept
1823{
1825 SpanningForestState forest(depgraph, rng_seed);
1826 if (!old_linearization.empty()) {
1827 forest.LoadLinearization(old_linearization);
1828 if (!is_topological) forest.MakeTopological();
1829 } else {
1830 forest.MakeTopological();
1831 }
1832 // Make improvement steps to it until we hit the max_iterations limit, or an optimal result
1833 // is found.
1834 if (forest.GetCost() < max_cost) {
1835 forest.StartOptimizing();
1836 do {
1837 if (!forest.OptimizeStep()) break;
1838 } while (forest.GetCost() < max_cost);
1839 }
1840 // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are
1841 // minimal.
1842 bool optimal = false;
1843 if (forest.GetCost() < max_cost) {
1844 forest.StartMinimizing();
1845 do {
1846 if (!forest.MinimizeStep()) {
1847 optimal = true;
1848 break;
1849 }
1850 } while (forest.GetCost() < max_cost);
1851 }
1852 return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()};
1853}
1854
1871template<typename SetType>
1872void PostLinearize(const DepGraph<SetType>& depgraph, std::span<DepGraphIndex> linearization)
1873{
1874 // This algorithm performs a number of passes (currently 2); the even ones operate from back to
1875 // front, the odd ones from front to back. Each results in an equal-or-better linearization
1876 // than the one started from.
1877 // - One pass in either direction guarantees that the resulting chunks are connected.
1878 // - Each direction corresponds to one shape of tree being linearized optimally (forward passes
1879 // guarantee this for graphs where each transaction has at most one child; backward passes
1880 // guarantee this for graphs where each transaction has at most one parent).
1881 // - Starting with a backward pass guarantees the moved-tree property.
1882 //
1883 // During an odd (forward) pass, the high-level operation is:
1884 // - Start with an empty list of groups L=[].
1885 // - For every transaction i in the old linearization, from front to back:
1886 // - Append a new group C=[i], containing just i, to the back of L.
1887 // - While L has at least one group before C, and the group immediately before C has feerate
1888 // lower than C:
1889 // - If C depends on P:
1890 // - Merge P into C, making C the concatenation of P+C, continuing with the combined C.
1891 // - Otherwise:
1892 // - Swap P with C, continuing with the now-moved C.
1893 // - The output linearization is the concatenation of the groups in L.
1894 //
1895 // During even (backward) passes, i iterates from the back to the front of the existing
1896 // linearization, and new groups are prepended instead of appended to the list L. To enable
1897 // more code reuse, both passes append groups, but during even passes the meanings of
1898 // parent/child, and of high/low feerate are reversed, and the final concatenation is reversed
1899 // on output.
1900 //
1901 // In the implementation below, the groups are represented by singly-linked lists (pointing
1902 // from the back to the front), which are themselves organized in a singly-linked circular
1903 // list (each group pointing to its predecessor, with a special sentinel group at the front
1904 // that points back to the last group).
1905 //
1906 // Information about transaction t is stored in entries[t + 1], while the sentinel is in
1907 // entries[0].
1908
1910 static constexpr DepGraphIndex SENTINEL{0};
1912 static constexpr DepGraphIndex NO_PREV_TX{0};
1913
1914
1916 struct TxEntry
1917 {
1920 DepGraphIndex prev_tx;
1921
1922 // The fields below are only used for transactions that are the last one in a group
1923 // (referred to as tail transactions below).
1924
1926 DepGraphIndex first_tx;
1929 DepGraphIndex prev_group;
1931 SetType group;
1933 SetType deps;
1935 FeeFrac feerate;
1936 };
1937
1938 // As an example, consider the state corresponding to the linearization [1,0,3,2], with
1939 // groups [1,0,3] and [2], in an odd pass. The linked lists would be:
1940 //
1941 // +-----+
1942 // 0<-P-- | 0 S | ---\ Legend:
1943 // +-----+ |
1944 // ^ | - digit in box: entries index
1945 // /--------------F---------+ G | (note: one more than tx value)
1946 // v \ | | - S: sentinel group
1947 // +-----+ +-----+ +-----+ | (empty feerate)
1948 // 0<-P-- | 2 | <--P-- | 1 | <--P-- | 4 T | | - T: tail transaction, contains
1949 // +-----+ +-----+ +-----+ | fields beyond prev_tv.
1950 // ^ | - P: prev_tx reference
1951 // G G - F: first_tx reference
1952 // | | - G: prev_group reference
1953 // +-----+ |
1954 // 0<-P-- | 3 T | <--/
1955 // +-----+
1956 // ^ |
1957 // \-F-/
1958 //
1959 // During an even pass, the diagram above would correspond to linearization [2,3,0,1], with
1960 // groups [2] and [3,0,1].
1961
1962 std::vector<TxEntry> entries(depgraph.PositionRange() + 1);
1963
1964 // Perform two passes over the linearization.
1965 for (int pass = 0; pass < 2; ++pass) {
1966 int rev = !(pass & 1);
1967 // Construct a sentinel group, identifying the start of the list.
1968 entries[SENTINEL].prev_group = SENTINEL;
1969 Assume(entries[SENTINEL].feerate.IsEmpty());
1970
1971 // Iterate over all elements in the existing linearization.
1972 for (DepGraphIndex i = 0; i < linearization.size(); ++i) {
1973 // Even passes are from back to front; odd passes from front to back.
1974 DepGraphIndex idx = linearization[rev ? linearization.size() - 1 - i : i];
1975 // Construct a new group containing just idx. In even passes, the meaning of
1976 // parent/child and high/low feerate are swapped.
1977 DepGraphIndex cur_group = idx + 1;
1978 entries[cur_group].group = SetType::Singleton(idx);
1979 entries[cur_group].deps = rev ? depgraph.Descendants(idx): depgraph.Ancestors(idx);
1980 entries[cur_group].feerate = depgraph.FeeRate(idx);
1981 if (rev) entries[cur_group].feerate.fee = -entries[cur_group].feerate.fee;
1982 entries[cur_group].prev_tx = NO_PREV_TX; // No previous transaction in group.
1983 entries[cur_group].first_tx = cur_group; // Transaction itself is first of group.
1984 // Insert the new group at the back of the groups linked list.
1985 entries[cur_group].prev_group = entries[SENTINEL].prev_group;
1986 entries[SENTINEL].prev_group = cur_group;
1987
1988 // Start merge/swap cycle.
1989 DepGraphIndex next_group = SENTINEL; // We inserted at the end, so next group is sentinel.
1990 DepGraphIndex prev_group = entries[cur_group].prev_group;
1991 // Continue as long as the current group has higher feerate than the previous one.
1992 while (ByRatio{entries[cur_group].feerate} > ByRatio{entries[prev_group].feerate}) {
1993 // prev_group/cur_group/next_group refer to (the last transactions of) 3
1994 // consecutive entries in groups list.
1995 Assume(cur_group == entries[next_group].prev_group);
1996 Assume(prev_group == entries[cur_group].prev_group);
1997 // The sentinel has empty feerate, which is neither higher or lower than other
1998 // feerates. Thus, the while loop we are in here guarantees that cur_group and
1999 // prev_group are not the sentinel.
2000 Assume(cur_group != SENTINEL);
2001 Assume(prev_group != SENTINEL);
2002 if (entries[cur_group].deps.Overlaps(entries[prev_group].group)) {
2003 // There is a dependency between cur_group and prev_group; merge prev_group
2004 // into cur_group. The group/deps/feerate fields of prev_group remain unchanged
2005 // but become unused.
2006 entries[cur_group].group |= entries[prev_group].group;
2007 entries[cur_group].deps |= entries[prev_group].deps;
2008 entries[cur_group].feerate += entries[prev_group].feerate;
2009 // Make the first of the current group point to the tail of the previous group.
2010 entries[entries[cur_group].first_tx].prev_tx = prev_group;
2011 // The first of the previous group becomes the first of the newly-merged group.
2012 entries[cur_group].first_tx = entries[prev_group].first_tx;
2013 // The previous group becomes whatever group was before the former one.
2014 prev_group = entries[prev_group].prev_group;
2015 entries[cur_group].prev_group = prev_group;
2016 } else {
2017 // There is no dependency between cur_group and prev_group; swap them.
2018 DepGraphIndex preprev_group = entries[prev_group].prev_group;
2019 // If PP, P, C, N were the old preprev, prev, cur, next groups, then the new
2020 // layout becomes [PP, C, P, N]. Update prev_groups to reflect that order.
2021 entries[next_group].prev_group = prev_group;
2022 entries[prev_group].prev_group = cur_group;
2023 entries[cur_group].prev_group = preprev_group;
2024 // The current group remains the same, but the groups before/after it have
2025 // changed.
2026 next_group = prev_group;
2027 prev_group = preprev_group;
2028 }
2029 }
2030 }
2031
2032 // Convert the entries back to linearization (overwriting the existing one).
2033 DepGraphIndex cur_group = entries[0].prev_group;
2034 DepGraphIndex done = 0;
2035 while (cur_group != SENTINEL) {
2036 DepGraphIndex cur_tx = cur_group;
2037 // Traverse the transactions of cur_group (from back to front), and write them in the
2038 // same order during odd passes, and reversed (front to back) in even passes.
2039 if (rev) {
2040 do {
2041 *(linearization.begin() + (done++)) = cur_tx - 1;
2042 cur_tx = entries[cur_tx].prev_tx;
2043 } while (cur_tx != NO_PREV_TX);
2044 } else {
2045 do {
2046 *(linearization.end() - (++done)) = cur_tx - 1;
2047 cur_tx = entries[cur_tx].prev_tx;
2048 } while (cur_tx != NO_PREV_TX);
2049 }
2050 cur_group = entries[cur_group].prev_group;
2051 }
2052 Assume(done == linearization.size());
2053 }
2054}
2055
2056} // namespace cluster_linearize
2057
2058#endif // BITCOIN_CLUSTER_LINEARIZE_H
#define LIFETIMEBOUND
Definition: attributes.h:16
int ret
int flags
Definition: bitcoin-tx.cpp:530
#define Assume(val)
Assume is the identity function.
Definition: check.h:128
Wrapper around FeeFrac & derived types, which adds a feerate-based ordering which treats equal-feerat...
Definition: feefrac.h:219
Wrapper around FeeFrac & derived types, which adds a total ordering which first sorts by feerate and ...
Definition: feefrac.h:290
xoroshiro128++ PRNG.
Definition: random.h:425
constexpr uint64_t rand64() noexcept
Definition: random.h:448
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
Definition: random.h:254
bool randbool() noexcept
Generate a random boolean.
Definition: random.h:325
uint64_t randbits(int bits) noexcept
Generate a random (bits)-bit integer.
Definition: random.h:204
bool empty() const noexcept
Test whether the contents of this deque is empty.
Definition: vecdeque.h:310
void clear() noexcept
Resize the deque to be size 0.
Definition: vecdeque.h:126
void push_back(T &&elem)
Move-construct a new element at the end of the deque.
Definition: vecdeque.h:227
void pop_front()
Remove the first element of the deque.
Definition: vecdeque.h:250
size_t size() const noexcept
Get the number of elements in this deque.
Definition: vecdeque.h:312
void emplace_back(Args &&... args)
Construct a new element at the end of the deque.
Definition: vecdeque.h:219
T & front() noexcept
Get a mutable reference to the first element of the deque.
Definition: vecdeque.h:268
void reserve(size_t capacity)
Increase the capacity to capacity.
Definition: vecdeque.h:206
T & back() noexcept
Get a mutable reference to the last element of the deque.
Definition: vecdeque.h:282
Data structure that holds a transaction graph's preprocessed data (fee, size, ancestors,...
unsigned CountDependencies() const noexcept
const SetType & Ancestors(DepGraphIndex i) const noexcept
Get the ancestors of a given transaction i.
FeeFrac & FeeRate(DepGraphIndex i) noexcept
Get the mutable feerate of a given transaction i.
SetType GetReducedChildren(DepGraphIndex i) const noexcept
Compute the (reduced) set of children of node i in this graph.
SetType GetReducedParents(DepGraphIndex i) const noexcept
Compute the (reduced) set of parents of node i in this graph.
void AppendTopo(std::vector< DepGraphIndex > &list, const SetType &select) const noexcept
Append the entries of select to list in a topologically valid order.
const FeeFrac & FeeRate(DepGraphIndex i) const noexcept
Get the feerate of a given transaction i.
SetType GetConnectedComponent(const SetType &todo, DepGraphIndex tx) const noexcept
Get the connected component within the subset "todo" that contains tx (which must be in todo).
bool IsConnected() const noexcept
Determine if this entire graph is connected.
bool IsConnected(const SetType &subset) const noexcept
Determine if a subset is connected.
DepGraphIndex PositionRange() const noexcept
Get the range of positions in this DepGraph.
SetType FindConnectedComponent(const SetType &todo) const noexcept
Find some connected component within the subset "todo" of this graph.
DepGraphIndex AddTransaction(const FeeFrac &feefrac) noexcept
Add a new unconnected transaction to this transaction graph (in the first available position),...
void RemoveTransactions(const SetType &del) noexcept
Remove the specified positions from this DepGraph.
auto TxCount() const noexcept
Get the number of transactions in the graph.
std::vector< Entry > entries
Data for each transaction.
const SetType & Descendants(DepGraphIndex i) const noexcept
Get the descendants of a given transaction i.
friend bool operator==(const DepGraph &a, const DepGraph &b) noexcept
Equality operator (primarily for testing purposes).
size_t DynamicMemoryUsage() const noexcept
DepGraph() noexcept=default
SetType m_used
Which positions are used.
const SetType & Positions() const noexcept
Get the set of transactions positions in use.
void Compact() noexcept
Reduce memory usage if possible.
FeeFrac FeeRate(const SetType &elems) const noexcept
Compute the aggregate feerate of a set of nodes in this graph.
void AddDependencies(const SetType &parents, DepGraphIndex child) noexcept
Modify this transaction graph, adding multiple parents to a specified child.
bool IsAcyclic() const noexcept
Check if this graph is acyclic.
A default cost model for SFL for SetType=BitSet<64>, based on benchmarks.
void PickChunkToOptimizeEnd(int num_steps) noexcept
void PickMergeCandidateEnd(int num_steps) noexcept
void MakeTopologicalEnd(int num_chunks, int num_steps) noexcept
void StartOptimizingEnd(int num_chunks) noexcept
void MergeChunksMid(int num_txns) noexcept
void StartMinimizingEnd(int num_chunks) noexcept
void MergeChunksEnd(int num_steps) noexcept
void InitializeEnd(int num_txns, int num_deps) noexcept
void PickDependencyToSplitEnd(int num_txns) noexcept
void GetLinearizationEnd(int num_txns, int num_deps) noexcept
void MinimizeStepMid(int num_txns) noexcept
void DeactivateEnd(int num_deps) noexcept
void ActivateEnd(int num_deps) noexcept
void MinimizeStepEnd(bool split) noexcept
Class to represent the internal state of the spanning-forest linearization (SFL) algorithm.
std::pair< TxIdx, TxIdx > PickDependencyToSplit(SetIdx chunk_idx) noexcept
Find a (parent, child) dependency to deactivate in chunk_idx, or (-1, -1) if none.
void StartMinimizing() noexcept
Initialize data structure for minimizing the chunks.
SetIdx PickChunkToOptimize() noexcept
Determine the next chunk to optimize, or INVALID_SET_IDX if none.
std::vector< DepGraphIndex > GetLinearization(const StrongComparator< DepGraphIndex > auto &fallback_order) noexcept
Construct a topologically-valid linearization from the current forest state.
SetIdx MergeChunksDirected(SetIdx chunk_idx, SetIdx merge_chunk_idx) noexcept
Activate a dependency from chunk_idx to merge_chunk_idx (if !DownWard), or a dependency from merge_ch...
SpanningForestState(const DepGraph< SetType > &depgraph LIFETIMEBOUND, uint64_t rng_seed, const CostModel &cost=CostModel{}) noexcept
Construct a spanning forest for the given DepGraph, with every transaction in its own chunk (not topo...
static constexpr SetIdx INVALID_SET_IDX
An invalid SetIdx.
bool MinimizeStep() noexcept
Try to reduce a chunk's size.
std::vector< TxData > m_tx_data
Information about each transaction (and chunks).
std::vector< FeeFrac > GetDiagram() const noexcept
Get the diagram for the current state, which must be topological.
CostModel m_cost
Accounting for the cost of this computation.
SetIdx MergeChunks(SetIdx top_idx, SetIdx bottom_idx) noexcept
Activate a dependency from the bottom set to the top set, which must exist.
void LoadLinearization(std::span< const DepGraphIndex > old_linearization) noexcept
Load an existing linearization.
SetIdx MergeStep(SetIdx chunk_idx) noexcept
Perform an upward or downward merge step, on the specified chunk.
std::vector< SetInfo< SetType > > m_set_info
Information about each set (chunk, or active dependency top set).
SetIdx PickMergeCandidate(SetIdx chunk_idx) noexcept
Determine which chunk to merge chunk_idx with, or INVALID_SET_IDX if none.
SetIdx Activate(TxIdx parent_idx, TxIdx child_idx) noexcept
Make the inactive dependency from child to parent, which must not be in the same chunk already,...
TxIdx PickRandomTx(const SetType &tx_idxs) noexcept
Pick a random transaction within a set (which must be non-empty).
bool OptimizeStep() noexcept
Try to improve the forest.
void StartOptimizing() noexcept
Initialize the data structure for optimization.
std::pair< SetType, SetType > GetReachable(const SetType &tx_idxs) const noexcept
Find the set of out-of-chunk transactions reachable from tx_idxs, both in upwards and downwards direc...
std::vector< std::pair< SetType, SetType > > m_reachable
For each chunk, indexed by SetIdx, the set of out-of-chunk reachable transactions,...
void Improve(TxIdx parent_idx, TxIdx child_idx) noexcept
Split a chunk, and then merge the resulting two chunks to make the graph topological again.
SetType m_transaction_idxs
The set of all TxIdx's of transactions in the cluster indexing into m_tx_data.
InsecureRandomContext m_rng
Internal RNG.
DepGraphIndex TxIdx
Data type to represent indexing into m_tx_data.
const DepGraph< SetType > & m_depgraph
The DepGraph we are trying to linearize.
VecDeque< std::tuple< SetIdx, TxIdx, unsigned > > m_nonminimal_chunks
A FIFO of chunk indexes with a pivot transaction in them, and a flag to indicate their status:
std::conditional_t<(SetType::Size()<=0xff), uint8_t, std::conditional_t<(SetType::Size()<=0xffff), uint16_t, uint32_t > > SetIdx
Data type to represent indexing into m_set_info.
void MergeSequence(SetIdx chunk_idx) noexcept
Perform an upward or downward merge sequence on the specified chunk.
void MakeTopological() noexcept
Make state topological.
std::pair< SetIdx, SetIdx > Deactivate(TxIdx parent_idx, TxIdx child_idx) noexcept
Make a specified active dependency inactive.
uint64_t GetCost() const noexcept
Determine how much work was performed so far.
SetType m_chunk_idxs
The set of all chunk SetIdx's.
VecDeque< SetIdx > m_suboptimal_chunks
A FIFO of chunk SetIdxs for chunks that may be improved still.
SetType m_suboptimal_idxs
The set of all SetIdx's that appear in m_suboptimal_chunks.
void SanityCheck() const
Verify internal consistency of the data structure.
Concept for function objects that return std::strong_ordering when invoked with two Args.
std::vector< FeeFrac > ChunkLinearization(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the feerates of the chunks of linearization.
std::tuple< std::vector< DepGraphIndex >, bool, uint64_t > Linearize(const DepGraph< SetType > &depgraph, uint64_t max_cost, uint64_t rng_seed, const StrongComparator< DepGraphIndex > auto &fallback_order, std::span< const DepGraphIndex > old_linearization={}, bool is_topological=true) noexcept
Find or improve a linearization for a cluster.
std::compare_three_way IndexTxOrder
Simple default transaction ordering function for SpanningForestState::GetLinearization() and Lineariz...
uint32_t DepGraphIndex
Data type to represent transaction indices in DepGraphs and the clusters they represent.
void PostLinearize(const DepGraph< SetType > &depgraph, std::span< DepGraphIndex > linearization)
Improve a given linearization.
std::vector< SetInfo< SetType > > ChunkLinearizationInfo(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the chunks of linearization as SetInfos.
static size_t DynamicUsage(const int8_t &v)
Dynamic memory usage for built-in types is zero.
Definition: memusage.h:31
static std::vector< std::string > split(const std::string &str, const std::string &delims=" \t")
Definition: subprocess.h:311
Data structure storing a fee and size.
Definition: feefrac.h:22
int64_t fee
Definition: feefrac.h:89
bool IsEmpty() const noexcept
Check if this is empty (size and fee are 0).
Definition: feefrac.h:102
Information about a single transaction.
SetType descendants
All descendants of the transaction (including itself).
friend bool operator==(const Entry &, const Entry &) noexcept=default
Equality operator (primarily for testing purposes).
Entry() noexcept=default
Construct an empty entry.
FeeFrac feerate
Fee and size of transaction itself.
SetType ancestors
All ancestors of the transaction (including itself).
A set of transactions together with their aggregate feerate.
SetInfo(const DepGraph< SetType > &depgraph, DepGraphIndex pos) noexcept
Construct a SetInfo for a given transaction in a depgraph.
SetInfo operator-(const SetInfo &other) const noexcept
Compute the difference between this and other SetInfo (which must be a subset).
FeeFrac feerate
Their combined fee and size.
SetInfo() noexcept=default
Construct a SetInfo for the empty set.
void Set(const DepGraph< SetType > &depgraph, DepGraphIndex pos) noexcept
Add a transaction to this SetInfo (which must not yet be in it).
SetType transactions
The transactions in the set.
friend void swap(SetInfo &a, SetInfo &b) noexcept
Swap two SetInfo objects.
SetInfo(const DepGraph< SetType > &depgraph, const SetType &txn) noexcept
Construct a SetInfo for a set of transactions in a depgraph.
SetInfo & operator|=(const SetInfo &other) noexcept
Add the transactions of other to this SetInfo (no overlap allowed).
SetInfo & operator-=(const SetInfo &other) noexcept
Remove the transactions of other from this SetInfo (which must be a subset).
friend bool operator==(const SetInfo &, const SetInfo &) noexcept=default
Permit equality testing.
Structure with information about a single transaction.
SetType active_children
The set of child transactions reachable through an active dependency.
SetType children
The set of child transactions of this transaction.
SetIdx chunk_idx
Which chunk this transaction belongs to.
std::array< SetIdx, SetType::Size()> dep_top_idx
The top set for every active child dependency this transaction has, indexed by child TxIdx.
SetType parents
The set of parent transactions of this transaction.
static int count
assert(!tx.IsCoinBase())