Bitcoin Core 30.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 <utility>
13#include <vector>
14
15#include <attributes.h>
16#include <memusage.h>
17#include <random.h>
18#include <span.h>
19#include <util/feefrac.h>
20#include <util/vecdeque.h>
21
23
25using DepGraphIndex = uint32_t;
26
29template<typename SetType>
31{
33 struct Entry
34 {
38 SetType ancestors;
40 SetType descendants;
41
43 friend bool operator==(const Entry&, const Entry&) noexcept = default;
44
46 Entry() noexcept = default;
48 Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {}
49 };
50
52 std::vector<Entry> entries;
53
55 SetType m_used;
56
57public:
59 friend bool operator==(const DepGraph& a, const DepGraph& b) noexcept
60 {
61 if (a.m_used != b.m_used) return false;
62 // Only compare the used positions within the entries vector.
63 for (auto idx : a.m_used) {
64 if (a.entries[idx] != b.entries[idx]) return false;
65 }
66 return true;
67 }
68
69 // Default constructors.
70 DepGraph() noexcept = default;
71 DepGraph(const DepGraph&) noexcept = default;
72 DepGraph(DepGraph&&) noexcept = default;
73 DepGraph& operator=(const DepGraph&) noexcept = default;
74 DepGraph& operator=(DepGraph&&) noexcept = default;
75
91 DepGraph(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> mapping, DepGraphIndex pos_range) noexcept : entries(pos_range)
92 {
93 Assume(mapping.size() == depgraph.PositionRange());
94 Assume((pos_range == 0) == (depgraph.TxCount() == 0));
95 for (DepGraphIndex i : depgraph.Positions()) {
96 auto new_idx = mapping[i];
97 Assume(new_idx < pos_range);
98 // Add transaction.
99 entries[new_idx].ancestors = SetType::Singleton(new_idx);
100 entries[new_idx].descendants = SetType::Singleton(new_idx);
101 m_used.Set(new_idx);
102 // Fill in fee and size.
103 entries[new_idx].feerate = depgraph.entries[i].feerate;
104 }
105 for (DepGraphIndex i : depgraph.Positions()) {
106 // Fill in dependencies by mapping direct parents.
107 SetType parents;
108 for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]);
109 AddDependencies(parents, mapping[i]);
110 }
111 // Verify that the provided pos_range was correct (no unused positions at the end).
112 Assume(m_used.None() ? (pos_range == 0) : (pos_range == m_used.Last() + 1));
113 }
114
116 const SetType& Positions() const noexcept { return m_used; }
118 DepGraphIndex PositionRange() const noexcept { return entries.size(); }
120 auto TxCount() const noexcept { return m_used.Count(); }
122 const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; }
124 FeeFrac& FeeRate(DepGraphIndex i) noexcept { return entries[i].feerate; }
126 const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; }
128 const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; }
129
135 DepGraphIndex AddTransaction(const FeeFrac& feefrac) noexcept
136 {
137 static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size());
138 auto available = ALL_POSITIONS - m_used;
139 Assume(available.Any());
140 DepGraphIndex new_idx = available.First();
141 if (new_idx == entries.size()) {
142 entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
143 } else {
144 entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
145 }
146 m_used.Set(new_idx);
147 return new_idx;
148 }
149
159 void RemoveTransactions(const SetType& del) noexcept
160 {
161 m_used -= del;
162 // Remove now-unused trailing entries.
163 while (!entries.empty() && !m_used[entries.size() - 1]) {
164 entries.pop_back();
165 }
166 // Remove the deleted transactions from ancestors/descendants of other transactions. Note
167 // that the deleted positions will retain old feerate and dependency information. This does
168 // not matter as they will be overwritten by AddTransaction if they get used again.
169 for (auto& entry : entries) {
170 entry.ancestors &= m_used;
171 entry.descendants &= m_used;
172 }
173 }
174
179 void AddDependencies(const SetType& parents, DepGraphIndex child) noexcept
180 {
181 Assume(m_used[child]);
182 Assume(parents.IsSubsetOf(m_used));
183 // Compute the ancestors of parents that are not already ancestors of child.
184 SetType par_anc;
185 for (auto par : parents - Ancestors(child)) {
186 par_anc |= Ancestors(par);
187 }
188 par_anc -= Ancestors(child);
189 // Bail out if there are no such ancestors.
190 if (par_anc.None()) return;
191 // To each such ancestor, add as descendants the descendants of the child.
192 const auto& chl_des = entries[child].descendants;
193 for (auto anc_of_par : par_anc) {
194 entries[anc_of_par].descendants |= chl_des;
195 }
196 // To each descendant of the child, add those ancestors.
197 for (auto dec_of_chl : Descendants(child)) {
198 entries[dec_of_chl].ancestors |= par_anc;
199 }
200 }
201
210 SetType GetReducedParents(DepGraphIndex i) const noexcept
211 {
212 SetType parents = Ancestors(i);
213 parents.Reset(i);
214 for (auto parent : parents) {
215 if (parents[parent]) {
216 parents -= Ancestors(parent);
217 parents.Set(parent);
218 }
219 }
220 return parents;
221 }
222
231 SetType GetReducedChildren(DepGraphIndex i) const noexcept
232 {
233 SetType children = Descendants(i);
234 children.Reset(i);
235 for (auto child : children) {
236 if (children[child]) {
237 children -= Descendants(child);
238 children.Set(child);
239 }
240 }
241 return children;
242 }
243
248 FeeFrac FeeRate(const SetType& elems) const noexcept
249 {
250 FeeFrac ret;
251 for (auto pos : elems) ret += entries[pos].feerate;
252 return ret;
253 }
254
265 SetType GetConnectedComponent(const SetType& todo, DepGraphIndex tx) const noexcept
266 {
267 Assume(todo[tx]);
268 Assume(todo.IsSubsetOf(m_used));
269 auto to_add = SetType::Singleton(tx);
270 SetType ret;
271 do {
272 SetType old = ret;
273 for (auto add : to_add) {
274 ret |= Descendants(add);
275 ret |= Ancestors(add);
276 }
277 ret &= todo;
278 to_add = ret - old;
279 } while (to_add.Any());
280 return ret;
281 }
282
290 SetType FindConnectedComponent(const SetType& todo) const noexcept
291 {
292 if (todo.None()) return todo;
293 return GetConnectedComponent(todo, todo.First());
294 }
295
300 bool IsConnected(const SetType& subset) const noexcept
301 {
302 return FindConnectedComponent(subset) == subset;
303 }
304
309 bool IsConnected() const noexcept { return IsConnected(m_used); }
310
315 void AppendTopo(std::vector<DepGraphIndex>& list, const SetType& select) const noexcept
316 {
317 DepGraphIndex old_len = list.size();
318 for (auto i : select) list.push_back(i);
319 std::sort(list.begin() + old_len, list.end(), [&](DepGraphIndex a, DepGraphIndex b) noexcept {
320 const auto a_anc_count = entries[a].ancestors.Count();
321 const auto b_anc_count = entries[b].ancestors.Count();
322 if (a_anc_count != b_anc_count) return a_anc_count < b_anc_count;
323 return a < b;
324 });
325 }
326
328 bool IsAcyclic() const noexcept
329 {
330 for (auto i : Positions()) {
331 if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) {
332 return false;
333 }
334 }
335 return true;
336 }
337
338 unsigned CountDependencies() const noexcept
339 {
340 unsigned ret = 0;
341 for (auto i : Positions()) {
342 ret += GetReducedParents(i).Count();
343 }
344 return ret;
345 }
346
348 void Compact() noexcept
349 {
350 entries.shrink_to_fit();
351 }
352
353 size_t DynamicMemoryUsage() const noexcept
354 {
356 }
357};
358
360template<typename SetType>
362{
367
369 SetInfo() noexcept = default;
370
372 SetInfo(const SetType& txn, const FeeFrac& fr) noexcept : transactions(txn), feerate(fr) {}
373
375 explicit SetInfo(const DepGraph<SetType>& depgraph, DepGraphIndex pos) noexcept :
376 transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {}
377
379 explicit SetInfo(const DepGraph<SetType>& depgraph, const SetType& txn) noexcept :
380 transactions(txn), feerate(depgraph.FeeRate(txn)) {}
381
383 void Set(const DepGraph<SetType>& depgraph, DepGraphIndex pos) noexcept
384 {
385 Assume(!transactions[pos]);
386 transactions.Set(pos);
387 feerate += depgraph.FeeRate(pos);
388 }
389
391 SetInfo& operator|=(const SetInfo& other) noexcept
392 {
393 Assume(!transactions.Overlaps(other.transactions));
394 transactions |= other.transactions;
395 feerate += other.feerate;
396 return *this;
397 }
398
400 SetInfo& operator-=(const SetInfo& other) noexcept
401 {
402 Assume(other.transactions.IsSubsetOf(transactions));
403 transactions -= other.transactions;
404 feerate -= other.feerate;
405 return *this;
406 }
407
409 SetInfo operator-(const SetInfo& other) const noexcept
410 {
411 Assume(other.transactions.IsSubsetOf(transactions));
412 return {transactions - other.transactions, feerate - other.feerate};
413 }
414
416 friend void swap(SetInfo& a, SetInfo& b) noexcept
417 {
418 swap(a.transactions, b.transactions);
419 swap(a.feerate, b.feerate);
420 }
421
423 friend bool operator==(const SetInfo&, const SetInfo&) noexcept = default;
424};
425
427template<typename SetType>
428std::vector<SetInfo<SetType>> ChunkLinearizationInfo(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> linearization) noexcept
429{
430 std::vector<SetInfo<SetType>> ret;
431 for (DepGraphIndex i : linearization) {
433 SetInfo<SetType> new_chunk(depgraph, i);
434 // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
435 while (!ret.empty() && new_chunk.feerate >> ret.back().feerate) {
436 new_chunk |= ret.back();
437 ret.pop_back();
438 }
439 // Actually move that new chunk into the chunking.
440 ret.emplace_back(std::move(new_chunk));
441 }
442 return ret;
443}
444
447template<typename SetType>
448std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> linearization) noexcept
449{
450 std::vector<FeeFrac> ret;
451 for (DepGraphIndex i : linearization) {
453 auto new_chunk = depgraph.FeeRate(i);
454 // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
455 while (!ret.empty() && new_chunk >> ret.back()) {
456 new_chunk += ret.back();
457 ret.pop_back();
458 }
459 // Actually move that new chunk into the chunking.
460 ret.push_back(std::move(new_chunk));
461 }
462 return ret;
463}
464
466template<typename F, typename Arg>
468 std::regular_invocable<F, Arg, Arg> &&
469 std::is_same_v<std::invoke_result_t<F, Arg, Arg>, std::strong_ordering>;
470
473using IndexTxOrder = std::compare_three_way;
474
641template<typename SetType>
643{
644private:
647
651 using DepIdx = uint32_t;
652
655 struct TxData {
657 std::vector<DepIdx> child_deps;
659 SetType parents;
661 SetType children;
668 };
669
671 struct DepData {
673 bool active;
679 };
680
685 std::vector<TxData> m_tx_data;
687 std::vector<DepData> m_dep_data;
697
699 uint64_t m_cost{0};
700
703
705 TxIdx PickRandomTx(const SetType& tx_idxs) noexcept
706 {
707 Assume(tx_idxs.Any());
708 unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count());
709 for (auto tx_idx : tx_idxs) {
710 if (pos == 0) return tx_idx;
711 --pos;
712 }
713 Assume(false);
714 return TxIdx(-1);
715 }
716
722 template<bool Subtract>
723 void UpdateChunk(const SetType& chunk, TxIdx query, TxIdx chunk_rep, const SetInfo<SetType>& dep_change) noexcept
724 {
725 // Iterate over all the chunk's transactions.
726 for (auto tx_idx : chunk) {
727 auto& tx_data = m_tx_data[tx_idx];
728 // Update the chunk representative.
729 tx_data.chunk_rep = chunk_rep;
730 // Iterate over all active dependencies with tx_idx as parent. Combined with the outer
731 // loop this iterates over all internal active dependencies of the chunk.
732 auto child_deps = std::span{tx_data.child_deps};
733 for (auto dep_idx : child_deps) {
734 auto& dep_entry = m_dep_data[dep_idx];
735 Assume(dep_entry.parent == tx_idx);
736 // Skip inactive dependencies.
737 if (!dep_entry.active) continue;
738 // If this dependency's top_setinfo contains query, update it to add/remove
739 // dep_change.
740 if (dep_entry.top_setinfo.transactions[query]) {
741 if constexpr (Subtract) {
742 dep_entry.top_setinfo -= dep_change;
743 } else {
744 dep_entry.top_setinfo |= dep_change;
745 }
746 }
747 }
748 }
749 }
750
752 TxIdx Activate(DepIdx dep_idx) noexcept
753 {
754 auto& dep_data = m_dep_data[dep_idx];
755 Assume(!dep_data.active);
756 auto& child_tx_data = m_tx_data[dep_data.child];
757 auto& parent_tx_data = m_tx_data[dep_data.parent];
758
759 // Gather information about the parent and child chunks.
760 Assume(parent_tx_data.chunk_rep != child_tx_data.chunk_rep);
761 auto& par_chunk_data = m_tx_data[parent_tx_data.chunk_rep];
762 auto& chl_chunk_data = m_tx_data[child_tx_data.chunk_rep];
763 TxIdx top_rep = parent_tx_data.chunk_rep;
764 auto top_part = par_chunk_data.chunk_setinfo;
765 auto bottom_part = chl_chunk_data.chunk_setinfo;
766 // Update the parent chunk to also contain the child.
767 par_chunk_data.chunk_setinfo |= bottom_part;
768 m_cost += par_chunk_data.chunk_setinfo.transactions.Count();
769
770 // Consider the following example:
771 //
772 // A A There are two chunks, ABC and DEF, and the inactive E->C dependency
773 // / \ / \ is activated, resulting in a single chunk ABCDEF.
774 // B C B C
775 // : ==> | Dependency | top set before | top set after | change
776 // D E D E B->A | AC | ACDEF | +DEF
777 // \ / \ / C->A | AB | AB |
778 // F F F->D | D | D |
779 // F->E | E | ABCE | +ABC
780 //
781 // The common pattern here is that any dependency which has the parent or child of the
782 // dependency being activated (E->C here) in its top set, will have the opposite part added
783 // to it. This is true for B->A and F->E, but not for C->A and F->D.
784 //
785 // Let UpdateChunk traverse the old parent chunk top_part (ABC in example), and add
786 // bottom_part (DEF) to every dependency's top_set which has the parent (C) in it. The
787 // representative of each of these transactions was already top_rep, so that is not being
788 // changed here.
789 UpdateChunk<false>(/*chunk=*/top_part.transactions, /*query=*/dep_data.parent,
790 /*chunk_rep=*/top_rep, /*dep_change=*/bottom_part);
791 // Let UpdateChunk traverse the old child chunk bottom_part (DEF in example), and add
792 // top_part (ABC) to every dependency's top_set which has the child (E) in it. At the same
793 // time, change the representative of each of these transactions to be top_rep, which
794 // becomes the representative for the merged chunk.
795 UpdateChunk<false>(/*chunk=*/bottom_part.transactions, /*query=*/dep_data.child,
796 /*chunk_rep=*/top_rep, /*dep_change=*/top_part);
797 // Make active.
798 dep_data.active = true;
799 dep_data.top_setinfo = top_part;
800 return top_rep;
801 }
802
804 void Deactivate(DepIdx dep_idx) noexcept
805 {
806 auto& dep_data = m_dep_data[dep_idx];
807 Assume(dep_data.active);
808 auto& parent_tx_data = m_tx_data[dep_data.parent];
809 // Make inactive.
810 dep_data.active = false;
811 // Update representatives.
812 auto& chunk_data = m_tx_data[parent_tx_data.chunk_rep];
813 m_cost += chunk_data.chunk_setinfo.transactions.Count();
814 auto top_part = dep_data.top_setinfo;
815 auto bottom_part = chunk_data.chunk_setinfo - top_part;
816 TxIdx bottom_rep = dep_data.child;
817 auto& bottom_chunk_data = m_tx_data[bottom_rep];
818 bottom_chunk_data.chunk_setinfo = bottom_part;
819 TxIdx top_rep = dep_data.parent;
820 auto& top_chunk_data = m_tx_data[top_rep];
821 top_chunk_data.chunk_setinfo = top_part;
822
823 // See the comment above in Activate(). We perform the opposite operations here,
824 // removing instead of adding.
825 //
826 // Let UpdateChunk traverse the old parent chunk top_part, and remove bottom_part from
827 // every dependency's top_set which has the parent in it. At the same time, change the
828 // representative of each of these transactions to be top_rep.
829 UpdateChunk<true>(/*chunk=*/top_part.transactions, /*query=*/dep_data.parent,
830 /*chunk_rep=*/top_rep, /*dep_change=*/bottom_part);
831 // Let UpdateChunk traverse the old child chunk bottom_part, and remove top_part from every
832 // dependency's top_set which has the child in it. At the same time, change the
833 // representative of each of these transactions to be bottom_rep.
834 UpdateChunk<true>(/*chunk=*/bottom_part.transactions, /*query=*/dep_data.child,
835 /*chunk_rep=*/bottom_rep, /*dep_change=*/top_part);
836 }
837
841 TxIdx MergeChunks(TxIdx top_rep, TxIdx bottom_rep) noexcept
842 {
843 auto& top_chunk = m_tx_data[top_rep];
844 Assume(top_chunk.chunk_rep == top_rep);
845 auto& bottom_chunk = m_tx_data[bottom_rep];
846 Assume(bottom_chunk.chunk_rep == bottom_rep);
847 // Count the number of dependencies between bottom_chunk and top_chunk.
848 TxIdx num_deps{0};
849 for (auto tx : top_chunk.chunk_setinfo.transactions) {
850 auto& tx_data = m_tx_data[tx];
851 num_deps += (tx_data.children & bottom_chunk.chunk_setinfo.transactions).Count();
852 }
853 if (num_deps == 0) return TxIdx(-1);
854 // Uniformly randomly pick one of them and activate it.
855 TxIdx pick = m_rng.randrange(num_deps);
856 for (auto tx : top_chunk.chunk_setinfo.transactions) {
857 auto& tx_data = m_tx_data[tx];
858 auto intersect = tx_data.children & bottom_chunk.chunk_setinfo.transactions;
859 auto count = intersect.Count();
860 if (pick < count) {
861 for (auto dep : tx_data.child_deps) {
862 auto& dep_data = m_dep_data[dep];
863 if (bottom_chunk.chunk_setinfo.transactions[dep_data.child]) {
864 if (pick == 0) return Activate(dep);
865 --pick;
866 }
867 }
868 break;
869 }
870 pick -= count;
871 }
872 Assume(false);
873 return TxIdx(-1);
874 }
875
878 template<bool DownWard>
879 TxIdx MergeStep(TxIdx chunk_rep) noexcept
880 {
882 auto& chunk_data = m_tx_data[chunk_rep];
883 SetType chunk_txn = chunk_data.chunk_setinfo.transactions;
884 // Iterate over all transactions in the chunk, figuring out which other chunk each
885 // depends on, but only testing each other chunk once. For those depended-on chunks,
886 // remember the highest-feerate (if DownWard) or lowest-feerate (if !DownWard) one.
887 // If multiple equal-feerate candidate chunks to merge with exist, pick a random one
888 // among them.
889
892 SetType explored = chunk_txn;
896 FeeFrac best_other_chunk_feerate = chunk_data.chunk_setinfo.feerate;
898 TxIdx best_other_chunk_rep = TxIdx(-1);
901 uint64_t best_other_chunk_tiebreak{0};
902 for (auto tx : chunk_txn) {
903 auto& tx_data = m_tx_data[tx];
906 auto newly_reached = (DownWard ? tx_data.children : tx_data.parents) - explored;
907 explored |= newly_reached;
908 while (newly_reached.Any()) {
909 // Find a chunk inside newly_reached, and remove it from newly_reached.
910 auto reached_chunk_rep = m_tx_data[newly_reached.First()].chunk_rep;
911 auto& reached_chunk = m_tx_data[reached_chunk_rep].chunk_setinfo;
912 newly_reached -= reached_chunk.transactions;
913 // See if it has an acceptable feerate.
914 auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk.feerate)
915 : FeeRateCompare(reached_chunk.feerate, best_other_chunk_feerate);
916 if (cmp > 0) continue;
917 uint64_t tiebreak = m_rng.rand64();
918 if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) {
919 best_other_chunk_feerate = reached_chunk.feerate;
920 best_other_chunk_rep = reached_chunk_rep;
921 best_other_chunk_tiebreak = tiebreak;
922 }
923 }
924 }
925 // Stop if there are no candidate chunks to merge with.
926 if (best_other_chunk_rep == TxIdx(-1)) return TxIdx(-1);
927 if constexpr (DownWard) {
928 chunk_rep = MergeChunks(chunk_rep, best_other_chunk_rep);
929 } else {
930 chunk_rep = MergeChunks(best_other_chunk_rep, chunk_rep);
931 }
932 Assume(chunk_rep != TxIdx(-1));
933 return chunk_rep;
934 }
935
936
938 template<bool DownWard>
939 void MergeSequence(TxIdx tx_idx) noexcept
940 {
941 auto chunk_rep = m_tx_data[tx_idx].chunk_rep;
942 while (true) {
943 auto merged_rep = MergeStep<DownWard>(chunk_rep);
944 if (merged_rep == TxIdx(-1)) break;
945 chunk_rep = merged_rep;
946 }
947 // Add the chunk to the queue of improvable chunks.
949 }
950
953 void Improve(DepIdx dep_idx) noexcept
954 {
955 auto& dep_data = m_dep_data[dep_idx];
956 Assume(dep_data.active);
957 // Deactivate the specified dependency, splitting it into two new chunks: a top containing
958 // the parent, and a bottom containing the child. The top should have a higher feerate.
959 Deactivate(dep_idx);
960
961 // At this point we have exactly two chunks which may violate topology constraints (the
962 // parent chunk and child chunk that were produced by deactivating dep_idx). We can fix
963 // these using just merge sequences, one upwards and one downwards, avoiding the need for a
964 // full MakeTopological.
965
966 // Merge the top chunk with lower-feerate chunks it depends on (which may be the bottom it
967 // was just split from, or other pre-existing chunks).
968 MergeSequence<false>(dep_data.parent);
969 // Merge the bottom chunk with higher-feerate chunks that depend on it.
970 MergeSequence<true>(dep_data.child);
971 }
972
973public:
976 explicit SpanningForestState(const DepGraph<SetType>& depgraph LIFETIMEBOUND, uint64_t rng_seed) noexcept :
977 m_rng(rng_seed), m_depgraph(depgraph)
978 {
979 m_transaction_idxs = depgraph.Positions();
980 auto num_transactions = m_transaction_idxs.Count();
981 m_tx_data.resize(depgraph.PositionRange());
982 // Reserve the maximum number of (reserved) dependencies the cluster can have, so
983 // m_dep_data won't need any reallocations during construction. For a cluster with N
984 // transactions, the worst case consists of two sets of transactions, the parents and the
985 // children, where each child depends on each parent and nothing else. For even N, both
986 // sets can be sized N/2, which means N^2/4 dependencies. For odd N, one can be (N + 1)/2
987 // and the other can be (N - 1)/2, meaning (N^2 - 1)/4 dependencies. Because N^2 is odd in
988 // this case, N^2/4 (with rounding-down division) is the correct value in both cases.
989 m_dep_data.reserve((num_transactions * num_transactions) / 4);
990 for (auto tx : m_transaction_idxs) {
991 // Fill in transaction data.
992 auto& tx_data = m_tx_data[tx];
993 tx_data.chunk_rep = tx;
994 tx_data.chunk_setinfo.transactions = SetType::Singleton(tx);
995 tx_data.chunk_setinfo.feerate = depgraph.FeeRate(tx);
996 // Add its dependencies.
997 SetType parents = depgraph.GetReducedParents(tx);
998 for (auto par : parents) {
999 auto& par_tx_data = m_tx_data[par];
1000 auto dep_idx = m_dep_data.size();
1001 // Construct new dependency.
1002 auto& dep = m_dep_data.emplace_back();
1003 dep.active = false;
1004 dep.parent = par;
1005 dep.child = tx;
1006 // Add it as parent of the child.
1007 tx_data.parents.Set(par);
1008 // Add it as child of the parent.
1009 par_tx_data.child_deps.push_back(dep_idx);
1010 par_tx_data.children.Set(tx);
1011 }
1012 }
1013 }
1014
1018 void LoadLinearization(std::span<const DepGraphIndex> old_linearization) noexcept
1019 {
1020 // Add transactions one by one, in order of existing linearization.
1021 for (DepGraphIndex tx : old_linearization) {
1022 auto chunk_rep = m_tx_data[tx].chunk_rep;
1023 // Merge the chunk upwards, as long as merging succeeds.
1024 while (true) {
1025 chunk_rep = MergeStep<false>(chunk_rep);
1026 if (chunk_rep == TxIdx(-1)) break;
1027 }
1028 }
1029 }
1030
1032 void MakeTopological() noexcept
1033 {
1034 for (auto tx : m_transaction_idxs) {
1035 auto& tx_data = m_tx_data[tx];
1036 if (tx_data.chunk_rep == tx) {
1038 // Randomize the initial order of suboptimal chunks in the queue.
1040 if (j != m_suboptimal_chunks.size() - 1) {
1042 }
1043 }
1044 }
1045 while (!m_suboptimal_chunks.empty()) {
1046 // Pop an entry from the potentially-suboptimal chunk queue.
1049 auto& chunk_data = m_tx_data[chunk];
1050 // If what was popped is not currently a chunk representative, continue. This may
1051 // happen when it was merged with something else since being added.
1052 if (chunk_data.chunk_rep != chunk) continue;
1053 int flip = m_rng.randbool();
1054 for (int i = 0; i < 2; ++i) {
1055 if (i ^ flip) {
1056 // Attempt to merge the chunk upwards.
1057 auto result_up = MergeStep<false>(chunk);
1058 if (result_up != TxIdx(-1)) {
1059 m_suboptimal_chunks.push_back(result_up);
1060 break;
1061 }
1062 } else {
1063 // Attempt to merge the chunk downwards.
1064 auto result_down = MergeStep<true>(chunk);
1065 if (result_down != TxIdx(-1)) {
1066 m_suboptimal_chunks.push_back(result_down);
1067 break;
1068 }
1069 }
1070 }
1071 }
1072 }
1073
1075 void StartOptimizing() noexcept
1076 {
1077 // Mark chunks suboptimal.
1078 for (auto tx : m_transaction_idxs) {
1079 auto& tx_data = m_tx_data[tx];
1080 if (tx_data.chunk_rep == tx) {
1082 // Randomize the initial order of suboptimal chunks in the queue.
1084 if (j != m_suboptimal_chunks.size() - 1) {
1086 }
1087 }
1088 }
1089 }
1090
1092 bool OptimizeStep() noexcept
1093 {
1094 while (!m_suboptimal_chunks.empty()) {
1095 // Pop an entry from the potentially-suboptimal chunk queue.
1098 auto& chunk_data = m_tx_data[chunk];
1099 // If what was popped is not currently a chunk representative, continue. This may
1100 // happen when a split chunk merges in Improve() with one or more existing chunks that
1101 // are themselves on the suboptimal queue already.
1102 if (chunk_data.chunk_rep != chunk) continue;
1103 // Remember the best dependency seen so far.
1104 DepIdx candidate_dep = DepIdx(-1);
1105 uint64_t candidate_tiebreak = 0;
1106 // Iterate over all transactions.
1107 for (auto tx : chunk_data.chunk_setinfo.transactions) {
1108 const auto& tx_data = m_tx_data[tx];
1109 // Iterate over all active child dependencies of the transaction.
1110 const auto children = std::span{tx_data.child_deps};
1111 for (DepIdx dep_idx : children) {
1112 const auto& dep_data = m_dep_data[dep_idx];
1113 if (!dep_data.active) continue;
1114 // Skip if this dependency is ineligible (the top chunk that would be created
1115 // does not have higher feerate than the chunk it is currently part of).
1116 auto cmp = FeeRateCompare(dep_data.top_setinfo.feerate, chunk_data.chunk_setinfo.feerate);
1117 if (cmp <= 0) continue;
1118 // Generate a random tiebreak for this dependency, and reject it if its tiebreak
1119 // is worse than the best so far. This means that among all eligible
1120 // dependencies, a uniformly random one will be chosen.
1121 uint64_t tiebreak = m_rng.rand64();
1122 if (tiebreak < candidate_tiebreak) continue;
1123 // Remember this as our (new) candidate dependency.
1124 candidate_dep = dep_idx;
1125 candidate_tiebreak = tiebreak;
1126 }
1127 }
1128 // If a candidate with positive gain was found, deactivate it and then make the state
1129 // topological again with a sequence of merges.
1130 if (candidate_dep != DepIdx(-1)) Improve(candidate_dep);
1131 // Stop processing for now, even if nothing was activated, as the loop above may have
1132 // had a nontrivial cost.
1133 return !m_suboptimal_chunks.empty();
1134 }
1135 // No improvable chunk was found, we are done.
1136 return false;
1137 }
1138
1141 void StartMinimizing() noexcept
1142 {
1145 // Gather all chunks, and for each, add it with a random pivot in it, and a random initial
1146 // direction, to m_nonminimal_chunks.
1147 for (auto tx : m_transaction_idxs) {
1148 auto& tx_data = m_tx_data[tx];
1149 if (tx_data.chunk_rep == tx) {
1150 TxIdx pivot_idx = PickRandomTx(tx_data.chunk_setinfo.transactions);
1151 m_nonminimal_chunks.emplace_back(tx, pivot_idx, m_rng.randbits<1>());
1152 // Randomize the initial order of nonminimal chunks in the queue.
1154 if (j != m_nonminimal_chunks.size() - 1) {
1156 }
1157 }
1158 }
1159 }
1160
1162 bool MinimizeStep() noexcept
1163 {
1164 // If the queue of potentially-non-minimal chunks is empty, we are done.
1165 if (m_nonminimal_chunks.empty()) return false;
1166 // Pop an entry from the potentially-non-minimal chunk queue.
1167 auto [chunk_rep, pivot_idx, flags] = m_nonminimal_chunks.front();
1169 auto& chunk_data = m_tx_data[chunk_rep];
1170 Assume(chunk_data.chunk_rep == chunk_rep);
1172 bool move_pivot_down = flags & 1;
1174 bool second_stage = flags & 2;
1175
1176 // Find a random dependency whose top and bottom set feerates are equal, and which has
1177 // pivot in bottom set (if move_pivot_down) or in top set (if !move_pivot_down).
1178 DepIdx candidate_dep = DepIdx(-1);
1179 uint64_t candidate_tiebreak{0};
1180 bool have_any = false;
1181 // Iterate over all transactions.
1182 for (auto tx_idx : chunk_data.chunk_setinfo.transactions) {
1183 const auto& tx_data = m_tx_data[tx_idx];
1184 // Iterate over all active child dependencies of the transaction.
1185 for (auto dep_idx : tx_data.child_deps) {
1186 auto& dep_data = m_dep_data[dep_idx];
1187 // Skip inactive child dependencies.
1188 if (!dep_data.active) continue;
1189 // Skip if this dependency does not have equal top and bottom set feerates. Note
1190 // that the top cannot have higher feerate than the bottom, or OptimizeSteps would
1191 // have dealt with it.
1192 if (dep_data.top_setinfo.feerate << chunk_data.chunk_setinfo.feerate) continue;
1193 have_any = true;
1194 // Skip if this dependency does not have pivot in the right place.
1195 if (move_pivot_down == dep_data.top_setinfo.transactions[pivot_idx]) continue;
1196 // Remember this as our chosen dependency if it has a better tiebreak.
1197 uint64_t tiebreak = m_rng.rand64() | 1;
1198 if (tiebreak > candidate_tiebreak) {
1199 candidate_tiebreak = tiebreak;
1200 candidate_dep = dep_idx;
1201 }
1202 }
1203 }
1204 // If no dependencies have equal top and bottom set feerate, this chunk is minimal.
1205 if (!have_any) return true;
1206 // If all found dependencies have the pivot in the wrong place, try moving it in the other
1207 // direction. If this was the second stage already, we are done.
1208 if (candidate_tiebreak == 0) {
1209 // Switch to other direction, and to second phase.
1210 flags ^= 3;
1211 if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_rep, pivot_idx, flags);
1212 return true;
1213 }
1214
1215 // Otherwise, deactivate the dependency that was found.
1216 Deactivate(candidate_dep);
1217 auto& dep_data = m_dep_data[candidate_dep];
1218 auto parent_chunk_rep = m_tx_data[dep_data.parent].chunk_rep;
1219 auto child_chunk_rep = m_tx_data[dep_data.child].chunk_rep;
1220 // Try to activate a dependency between the new bottom and the new top (opposite from the
1221 // dependency that was just deactivated).
1222 auto merged_chunk_rep = MergeChunks(child_chunk_rep, parent_chunk_rep);
1223 if (merged_chunk_rep != TxIdx(-1)) {
1224 // A self-merge happened.
1225 // Re-insert the chunk into the queue, in the same direction. Note that the chunk_rep
1226 // will have changed.
1227 m_nonminimal_chunks.emplace_back(merged_chunk_rep, pivot_idx, flags);
1228 } else {
1229 // No self-merge happens, and thus we have found a way to split the chunk. Create two
1230 // smaller chunks, and add them to the queue. The one that contains the current pivot
1231 // gets to continue with it in the same direction, to minimize the number of times we
1232 // alternate direction. If we were in the second phase already, the newly created chunk
1233 // inherits that too, because we know no split with the pivot on the other side is
1234 // possible already. The new chunk without the current pivot gets a new randomly-chosen
1235 // one.
1236 if (move_pivot_down) {
1237 auto parent_pivot_idx = PickRandomTx(m_tx_data[parent_chunk_rep].chunk_setinfo.transactions);
1238 m_nonminimal_chunks.emplace_back(parent_chunk_rep, parent_pivot_idx, m_rng.randbits<1>());
1239 m_nonminimal_chunks.emplace_back(child_chunk_rep, pivot_idx, flags);
1240 } else {
1241 auto child_pivot_idx = PickRandomTx(m_tx_data[child_chunk_rep].chunk_setinfo.transactions);
1242 m_nonminimal_chunks.emplace_back(parent_chunk_rep, pivot_idx, flags);
1243 m_nonminimal_chunks.emplace_back(child_chunk_rep, child_pivot_idx, m_rng.randbits<1>());
1244 }
1245 if (m_rng.randbool()) {
1247 }
1248 }
1249 return true;
1250 }
1251
1268 std::vector<DepGraphIndex> GetLinearization(const StrongComparator<DepGraphIndex> auto& fallback_order) const noexcept
1269 {
1271 std::vector<DepGraphIndex> ret;
1272 ret.reserve(m_transaction_idxs.Count());
1276 std::vector<std::pair<TxIdx, TxIdx>> ready_chunks;
1283 std::vector<std::pair<TxIdx, TxIdx>> chunk_deps(m_tx_data.size(), {0, 0});
1285 SetType chunk_reps;
1288 std::vector<TxIdx> ready_tx;
1289 // Populate chunk_deps[c] with the number of {out-of-chunk dependencies, dependencies} the
1290 // child has.
1291 for (TxIdx chl_idx : m_transaction_idxs) {
1292 const auto& chl_data = m_tx_data[chl_idx];
1293 chunk_deps[chl_idx].second = chl_data.parents.Count();
1294 auto chl_chunk_rep = chl_data.chunk_rep;
1295 chunk_reps.Set(chl_chunk_rep);
1296 for (auto par_idx : chl_data.parents) {
1297 auto par_chunk_rep = m_tx_data[par_idx].chunk_rep;
1298 chunk_deps[chl_chunk_rep].first += (par_chunk_rep != chl_chunk_rep);
1299 }
1300 }
1302 auto max_fallback_fn = [&](TxIdx chunk_rep) noexcept {
1303 auto& chunk = m_tx_data[chunk_rep].chunk_setinfo.transactions;
1304 auto it = chunk.begin();
1305 DepGraphIndex ret = *it;
1306 ++it;
1307 while (it != chunk.end()) {
1308 if (fallback_order(*it, ret) > 0) ret = *it;
1309 ++it;
1310 }
1311 return ret;
1312 };
1315 auto tx_cmp_fn = [&](const auto& a, const auto& b) noexcept {
1316 // Bail out for identical transactions.
1317 if (a == b) return false;
1318 // First sort by increasing transaction feerate.
1319 auto& a_feerate = m_depgraph.FeeRate(a);
1320 auto& b_feerate = m_depgraph.FeeRate(b);
1321 auto feerate_cmp = FeeRateCompare(a_feerate, b_feerate);
1322 if (feerate_cmp != 0) return feerate_cmp < 0;
1323 // Then by decreasing transaction size.
1324 if (a_feerate.size != b_feerate.size) {
1325 return a_feerate.size > b_feerate.size;
1326 }
1327 // Tie-break by decreasing fallback_order.
1328 auto fallback_cmp = fallback_order(a, b);
1329 if (fallback_cmp != 0) return fallback_cmp > 0;
1330 // This should not be hit, because fallback_order defines a strong ordering.
1331 Assume(false);
1332 return a < b;
1333 };
1334 // Construct a heap with all chunks that have no out-of-chunk dependencies.
1337 auto chunk_cmp_fn = [&](const auto& a, const auto& b) noexcept {
1338 // Bail out for identical chunks.
1339 if (a.first == b.first) return false;
1340 // First sort by increasing chunk feerate.
1341 auto& chunk_feerate_a = m_tx_data[a.first].chunk_setinfo.feerate;
1342 auto& chunk_feerate_b = m_tx_data[b.first].chunk_setinfo.feerate;
1343 auto feerate_cmp = FeeRateCompare(chunk_feerate_a, chunk_feerate_b);
1344 if (feerate_cmp != 0) return feerate_cmp < 0;
1345 // Then by decreasing chunk size.
1346 if (chunk_feerate_a.size != chunk_feerate_b.size) {
1347 return chunk_feerate_a.size > chunk_feerate_b.size;
1348 }
1349 // Tie-break by decreasing fallback_order.
1350 auto fallback_cmp = fallback_order(a.second, b.second);
1351 if (fallback_cmp != 0) return fallback_cmp > 0;
1352 // This should not be hit, because fallback_order defines a strong ordering.
1353 Assume(false);
1354 return a.second < b.second;
1355 };
1356 // Construct a heap with all chunks that have no out-of-chunk dependencies.
1357 for (TxIdx chunk_rep : chunk_reps) {
1358 if (chunk_deps[chunk_rep].first == 0) {
1359 ready_chunks.emplace_back(chunk_rep, max_fallback_fn(chunk_rep));
1360 }
1361 }
1362 std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn);
1363 // Pop chunks off the heap.
1364 while (!ready_chunks.empty()) {
1365 auto [chunk_rep, _rnd] = ready_chunks.front();
1366 std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn);
1367 ready_chunks.pop_back();
1368 Assume(m_tx_data[chunk_rep].chunk_rep == chunk_rep);
1369 Assume(chunk_deps[chunk_rep].first == 0);
1370 const auto& chunk_txn = m_tx_data[chunk_rep].chunk_setinfo.transactions;
1371 // Build heap of all includable transactions in chunk.
1372 Assume(ready_tx.empty());
1373 for (TxIdx tx_idx : chunk_txn) {
1374 if (chunk_deps[tx_idx].second == 0) ready_tx.push_back(tx_idx);
1375 }
1376 Assume(!ready_tx.empty());
1377 std::make_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn);
1378 // Pick transactions from the ready heap, append them to linearization, and decrement
1379 // dependency counts.
1380 while (!ready_tx.empty()) {
1381 // Pop an element from the tx_ready heap.
1382 auto tx_idx = ready_tx.front();
1383 std::pop_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn);
1384 ready_tx.pop_back();
1385 // Append to linearization.
1386 ret.push_back(tx_idx);
1387 // Decrement dependency counts.
1388 auto& tx_data = m_tx_data[tx_idx];
1389 for (TxIdx chl_idx : tx_data.children) {
1390 auto& chl_data = m_tx_data[chl_idx];
1391 // Decrement tx dependency count.
1392 Assume(chunk_deps[chl_idx].second > 0);
1393 if (--chunk_deps[chl_idx].second == 0 && chunk_txn[chl_idx]) {
1394 // Child tx has no dependencies left, and is in this chunk. Add it to the tx heap.
1395 ready_tx.push_back(chl_idx);
1396 std::push_heap(ready_tx.begin(), ready_tx.end(), tx_cmp_fn);
1397 }
1398 // Decrement chunk dependency count if this is out-of-chunk dependency.
1399 if (chl_data.chunk_rep != chunk_rep) {
1400 Assume(chunk_deps[chl_data.chunk_rep].first > 0);
1401 if (--chunk_deps[chl_data.chunk_rep].first == 0) {
1402 // Child chunk has no dependencies left. Add it to the chunk heap.
1403 ready_chunks.emplace_back(chl_data.chunk_rep, max_fallback_fn(chl_data.chunk_rep));
1404 std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn);
1405 }
1406 }
1407 }
1408 }
1409 }
1410 Assume(ret.size() == m_transaction_idxs.Count());
1411 return ret;
1412 }
1413
1427 std::vector<FeeFrac> GetDiagram() const noexcept
1428 {
1429 std::vector<FeeFrac> ret;
1430 for (auto tx : m_transaction_idxs) {
1431 if (m_tx_data[tx].chunk_rep == tx) {
1432 ret.push_back(m_tx_data[tx].chunk_setinfo.feerate);
1433 }
1434 }
1435 std::sort(ret.begin(), ret.end(), std::greater{});
1436 return ret;
1437 }
1438
1440 uint64_t GetCost() const noexcept { return m_cost; }
1441
1443 void SanityCheck(const DepGraph<SetType>& depgraph) const
1444 {
1445 //
1446 // Verify dependency parent/child information, and build list of (active) dependencies.
1447 //
1448 std::vector<std::pair<TxIdx, TxIdx>> expected_dependencies;
1449 std::vector<std::tuple<TxIdx, TxIdx, DepIdx>> all_dependencies;
1450 std::vector<std::tuple<TxIdx, TxIdx, DepIdx>> active_dependencies;
1451 for (auto parent_idx : depgraph.Positions()) {
1452 for (auto child_idx : depgraph.GetReducedChildren(parent_idx)) {
1453 expected_dependencies.emplace_back(parent_idx, child_idx);
1454 }
1455 }
1456 for (DepIdx dep_idx = 0; dep_idx < m_dep_data.size(); ++dep_idx) {
1457 const auto& dep_data = m_dep_data[dep_idx];
1458 all_dependencies.emplace_back(dep_data.parent, dep_data.child, dep_idx);
1459 // Also add to active_dependencies if it is active.
1460 if (m_dep_data[dep_idx].active) {
1461 active_dependencies.emplace_back(dep_data.parent, dep_data.child, dep_idx);
1462 }
1463 }
1464 std::sort(expected_dependencies.begin(), expected_dependencies.end());
1465 std::sort(all_dependencies.begin(), all_dependencies.end());
1466 assert(expected_dependencies.size() == all_dependencies.size());
1467 for (size_t i = 0; i < expected_dependencies.size(); ++i) {
1468 assert(expected_dependencies[i] ==
1469 std::make_pair(std::get<0>(all_dependencies[i]),
1470 std::get<1>(all_dependencies[i])));
1471 }
1472
1473 //
1474 // Verify the chunks against the list of active dependencies
1475 //
1476 for (auto tx_idx: depgraph.Positions()) {
1477 // Only process chunks for now.
1478 if (m_tx_data[tx_idx].chunk_rep == tx_idx) {
1479 const auto& chunk_data = m_tx_data[tx_idx];
1480 // Verify that transactions in the chunk point back to it. This guarantees
1481 // that chunks are non-overlapping.
1482 for (auto chunk_tx : chunk_data.chunk_setinfo.transactions) {
1483 assert(m_tx_data[chunk_tx].chunk_rep == tx_idx);
1484 }
1485 // Verify the chunk's transaction set: it must contain the representative, and for
1486 // every active dependency, if it contains the parent or child, it must contain
1487 // both. It must have exactly N-1 active dependencies in it, guaranteeing it is
1488 // acyclic.
1489 SetType expected_chunk = SetType::Singleton(tx_idx);
1490 while (true) {
1491 auto old = expected_chunk;
1492 size_t active_dep_count{0};
1493 for (const auto& [par, chl, _dep] : active_dependencies) {
1494 if (expected_chunk[par] || expected_chunk[chl]) {
1495 expected_chunk.Set(par);
1496 expected_chunk.Set(chl);
1497 ++active_dep_count;
1498 }
1499 }
1500 if (old == expected_chunk) {
1501 assert(expected_chunk.Count() == active_dep_count + 1);
1502 break;
1503 }
1504 }
1505 assert(chunk_data.chunk_setinfo.transactions == expected_chunk);
1506 // Verify the chunk's feerate.
1507 assert(chunk_data.chunk_setinfo.feerate ==
1508 depgraph.FeeRate(chunk_data.chunk_setinfo.transactions));
1509 }
1510 }
1511
1512 //
1513 // Verify other transaction data.
1514 //
1515 assert(m_transaction_idxs == depgraph.Positions());
1516 for (auto tx_idx : m_transaction_idxs) {
1517 const auto& tx_data = m_tx_data[tx_idx];
1518 // Verify it has a valid chunk representative, and that chunk includes this
1519 // transaction.
1520 assert(m_tx_data[tx_data.chunk_rep].chunk_rep == tx_data.chunk_rep);
1521 assert(m_tx_data[tx_data.chunk_rep].chunk_setinfo.transactions[tx_idx]);
1522 // Verify parents/children.
1523 assert(tx_data.parents == depgraph.GetReducedParents(tx_idx));
1524 assert(tx_data.children == depgraph.GetReducedChildren(tx_idx));
1525 // Verify list of child dependencies.
1526 std::vector<DepIdx> expected_child_deps;
1527 for (const auto& [par_idx, chl_idx, dep_idx] : all_dependencies) {
1528 if (tx_idx == par_idx) {
1529 assert(tx_data.children[chl_idx]);
1530 expected_child_deps.push_back(dep_idx);
1531 }
1532 }
1533 std::sort(expected_child_deps.begin(), expected_child_deps.end());
1534 auto child_deps_copy = tx_data.child_deps;
1535 std::sort(child_deps_copy.begin(), child_deps_copy.end());
1536 assert(expected_child_deps == child_deps_copy);
1537 }
1538
1539 //
1540 // Verify active dependencies' top_setinfo.
1541 //
1542 for (const auto& [par_idx, chl_idx, dep_idx] : active_dependencies) {
1543 const auto& dep_data = m_dep_data[dep_idx];
1544 // Verify the top_info's transactions: it must contain the parent, and for every
1545 // active dependency, except dep_idx itself, if it contains the parent or child, it
1546 // must contain both.
1547 SetType expected_top = SetType::Singleton(par_idx);
1548 while (true) {
1549 auto old = expected_top;
1550 for (const auto& [par2_idx, chl2_idx, dep2_idx] : active_dependencies) {
1551 if (dep2_idx != dep_idx && (expected_top[par2_idx] || expected_top[chl2_idx])) {
1552 expected_top.Set(par2_idx);
1553 expected_top.Set(chl2_idx);
1554 }
1555 }
1556 if (old == expected_top) break;
1557 }
1558 assert(!expected_top[chl_idx]);
1559 assert(dep_data.top_setinfo.transactions == expected_top);
1560 // Verify the top_info's feerate.
1561 assert(dep_data.top_setinfo.feerate ==
1562 depgraph.FeeRate(dep_data.top_setinfo.transactions));
1563 }
1564
1565 //
1566 // Verify m_suboptimal_chunks.
1567 //
1568 for (size_t i = 0; i < m_suboptimal_chunks.size(); ++i) {
1569 auto tx_idx = m_suboptimal_chunks[i];
1570 assert(m_transaction_idxs[tx_idx]);
1571 }
1572
1573 //
1574 // Verify m_nonminimal_chunks.
1575 //
1576 SetType nonminimal_reps;
1577 for (size_t i = 0; i < m_nonminimal_chunks.size(); ++i) {
1578 auto [chunk_rep, pivot, flags] = m_nonminimal_chunks[i];
1579 assert(m_tx_data[chunk_rep].chunk_rep == chunk_rep);
1580 assert(m_tx_data[pivot].chunk_rep == chunk_rep);
1581 assert(!nonminimal_reps[chunk_rep]);
1582 nonminimal_reps.Set(chunk_rep);
1583 }
1584 assert(nonminimal_reps.IsSubsetOf(m_transaction_idxs));
1585 }
1586};
1587
1608template<typename SetType>
1609std::tuple<std::vector<DepGraphIndex>, bool, uint64_t> Linearize(
1610 const DepGraph<SetType>& depgraph,
1611 uint64_t max_iterations,
1612 uint64_t rng_seed,
1613 const StrongComparator<DepGraphIndex> auto& fallback_order,
1614 std::span<const DepGraphIndex> old_linearization = {},
1615 bool is_topological = true) noexcept
1616{
1618 SpanningForestState forest(depgraph, rng_seed);
1619 if (!old_linearization.empty()) {
1620 forest.LoadLinearization(old_linearization);
1621 if (!is_topological) forest.MakeTopological();
1622 } else {
1623 forest.MakeTopological();
1624 }
1625 // Make improvement steps to it until we hit the max_iterations limit, or an optimal result
1626 // is found.
1627 if (forest.GetCost() < max_iterations) {
1628 forest.StartOptimizing();
1629 do {
1630 if (!forest.OptimizeStep()) break;
1631 } while (forest.GetCost() < max_iterations);
1632 }
1633 // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are
1634 // minimal.
1635 bool optimal = false;
1636 if (forest.GetCost() < max_iterations) {
1637 forest.StartMinimizing();
1638 do {
1639 if (!forest.MinimizeStep()) {
1640 optimal = true;
1641 break;
1642 }
1643 } while (forest.GetCost() < max_iterations);
1644 }
1645 return {forest.GetLinearization(fallback_order), optimal, forest.GetCost()};
1646}
1647
1664template<typename SetType>
1665void PostLinearize(const DepGraph<SetType>& depgraph, std::span<DepGraphIndex> linearization)
1666{
1667 // This algorithm performs a number of passes (currently 2); the even ones operate from back to
1668 // front, the odd ones from front to back. Each results in an equal-or-better linearization
1669 // than the one started from.
1670 // - One pass in either direction guarantees that the resulting chunks are connected.
1671 // - Each direction corresponds to one shape of tree being linearized optimally (forward passes
1672 // guarantee this for graphs where each transaction has at most one child; backward passes
1673 // guarantee this for graphs where each transaction has at most one parent).
1674 // - Starting with a backward pass guarantees the moved-tree property.
1675 //
1676 // During an odd (forward) pass, the high-level operation is:
1677 // - Start with an empty list of groups L=[].
1678 // - For every transaction i in the old linearization, from front to back:
1679 // - Append a new group C=[i], containing just i, to the back of L.
1680 // - While L has at least one group before C, and the group immediately before C has feerate
1681 // lower than C:
1682 // - If C depends on P:
1683 // - Merge P into C, making C the concatenation of P+C, continuing with the combined C.
1684 // - Otherwise:
1685 // - Swap P with C, continuing with the now-moved C.
1686 // - The output linearization is the concatenation of the groups in L.
1687 //
1688 // During even (backward) passes, i iterates from the back to the front of the existing
1689 // linearization, and new groups are prepended instead of appended to the list L. To enable
1690 // more code reuse, both passes append groups, but during even passes the meanings of
1691 // parent/child, and of high/low feerate are reversed, and the final concatenation is reversed
1692 // on output.
1693 //
1694 // In the implementation below, the groups are represented by singly-linked lists (pointing
1695 // from the back to the front), which are themselves organized in a singly-linked circular
1696 // list (each group pointing to its predecessor, with a special sentinel group at the front
1697 // that points back to the last group).
1698 //
1699 // Information about transaction t is stored in entries[t + 1], while the sentinel is in
1700 // entries[0].
1701
1703 static constexpr DepGraphIndex SENTINEL{0};
1705 static constexpr DepGraphIndex NO_PREV_TX{0};
1706
1707
1709 struct TxEntry
1710 {
1713 DepGraphIndex prev_tx;
1714
1715 // The fields below are only used for transactions that are the last one in a group
1716 // (referred to as tail transactions below).
1717
1719 DepGraphIndex first_tx;
1722 DepGraphIndex prev_group;
1724 SetType group;
1726 SetType deps;
1728 FeeFrac feerate;
1729 };
1730
1731 // As an example, consider the state corresponding to the linearization [1,0,3,2], with
1732 // groups [1,0,3] and [2], in an odd pass. The linked lists would be:
1733 //
1734 // +-----+
1735 // 0<-P-- | 0 S | ---\ Legend:
1736 // +-----+ |
1737 // ^ | - digit in box: entries index
1738 // /--------------F---------+ G | (note: one more than tx value)
1739 // v \ | | - S: sentinel group
1740 // +-----+ +-----+ +-----+ | (empty feerate)
1741 // 0<-P-- | 2 | <--P-- | 1 | <--P-- | 4 T | | - T: tail transaction, contains
1742 // +-----+ +-----+ +-----+ | fields beyond prev_tv.
1743 // ^ | - P: prev_tx reference
1744 // G G - F: first_tx reference
1745 // | | - G: prev_group reference
1746 // +-----+ |
1747 // 0<-P-- | 3 T | <--/
1748 // +-----+
1749 // ^ |
1750 // \-F-/
1751 //
1752 // During an even pass, the diagram above would correspond to linearization [2,3,0,1], with
1753 // groups [2] and [3,0,1].
1754
1755 std::vector<TxEntry> entries(depgraph.PositionRange() + 1);
1756
1757 // Perform two passes over the linearization.
1758 for (int pass = 0; pass < 2; ++pass) {
1759 int rev = !(pass & 1);
1760 // Construct a sentinel group, identifying the start of the list.
1761 entries[SENTINEL].prev_group = SENTINEL;
1762 Assume(entries[SENTINEL].feerate.IsEmpty());
1763
1764 // Iterate over all elements in the existing linearization.
1765 for (DepGraphIndex i = 0; i < linearization.size(); ++i) {
1766 // Even passes are from back to front; odd passes from front to back.
1767 DepGraphIndex idx = linearization[rev ? linearization.size() - 1 - i : i];
1768 // Construct a new group containing just idx. In even passes, the meaning of
1769 // parent/child and high/low feerate are swapped.
1770 DepGraphIndex cur_group = idx + 1;
1771 entries[cur_group].group = SetType::Singleton(idx);
1772 entries[cur_group].deps = rev ? depgraph.Descendants(idx): depgraph.Ancestors(idx);
1773 entries[cur_group].feerate = depgraph.FeeRate(idx);
1774 if (rev) entries[cur_group].feerate.fee = -entries[cur_group].feerate.fee;
1775 entries[cur_group].prev_tx = NO_PREV_TX; // No previous transaction in group.
1776 entries[cur_group].first_tx = cur_group; // Transaction itself is first of group.
1777 // Insert the new group at the back of the groups linked list.
1778 entries[cur_group].prev_group = entries[SENTINEL].prev_group;
1779 entries[SENTINEL].prev_group = cur_group;
1780
1781 // Start merge/swap cycle.
1782 DepGraphIndex next_group = SENTINEL; // We inserted at the end, so next group is sentinel.
1783 DepGraphIndex prev_group = entries[cur_group].prev_group;
1784 // Continue as long as the current group has higher feerate than the previous one.
1785 while (entries[cur_group].feerate >> entries[prev_group].feerate) {
1786 // prev_group/cur_group/next_group refer to (the last transactions of) 3
1787 // consecutive entries in groups list.
1788 Assume(cur_group == entries[next_group].prev_group);
1789 Assume(prev_group == entries[cur_group].prev_group);
1790 // The sentinel has empty feerate, which is neither higher or lower than other
1791 // feerates. Thus, the while loop we are in here guarantees that cur_group and
1792 // prev_group are not the sentinel.
1793 Assume(cur_group != SENTINEL);
1794 Assume(prev_group != SENTINEL);
1795 if (entries[cur_group].deps.Overlaps(entries[prev_group].group)) {
1796 // There is a dependency between cur_group and prev_group; merge prev_group
1797 // into cur_group. The group/deps/feerate fields of prev_group remain unchanged
1798 // but become unused.
1799 entries[cur_group].group |= entries[prev_group].group;
1800 entries[cur_group].deps |= entries[prev_group].deps;
1801 entries[cur_group].feerate += entries[prev_group].feerate;
1802 // Make the first of the current group point to the tail of the previous group.
1803 entries[entries[cur_group].first_tx].prev_tx = prev_group;
1804 // The first of the previous group becomes the first of the newly-merged group.
1805 entries[cur_group].first_tx = entries[prev_group].first_tx;
1806 // The previous group becomes whatever group was before the former one.
1807 prev_group = entries[prev_group].prev_group;
1808 entries[cur_group].prev_group = prev_group;
1809 } else {
1810 // There is no dependency between cur_group and prev_group; swap them.
1811 DepGraphIndex preprev_group = entries[prev_group].prev_group;
1812 // If PP, P, C, N were the old preprev, prev, cur, next groups, then the new
1813 // layout becomes [PP, C, P, N]. Update prev_groups to reflect that order.
1814 entries[next_group].prev_group = prev_group;
1815 entries[prev_group].prev_group = cur_group;
1816 entries[cur_group].prev_group = preprev_group;
1817 // The current group remains the same, but the groups before/after it have
1818 // changed.
1819 next_group = prev_group;
1820 prev_group = preprev_group;
1821 }
1822 }
1823 }
1824
1825 // Convert the entries back to linearization (overwriting the existing one).
1826 DepGraphIndex cur_group = entries[0].prev_group;
1827 DepGraphIndex done = 0;
1828 while (cur_group != SENTINEL) {
1829 DepGraphIndex cur_tx = cur_group;
1830 // Traverse the transactions of cur_group (from back to front), and write them in the
1831 // same order during odd passes, and reversed (front to back) in even passes.
1832 if (rev) {
1833 do {
1834 *(linearization.begin() + (done++)) = cur_tx - 1;
1835 cur_tx = entries[cur_tx].prev_tx;
1836 } while (cur_tx != NO_PREV_TX);
1837 } else {
1838 do {
1839 *(linearization.end() - (++done)) = cur_tx - 1;
1840 cur_tx = entries[cur_tx].prev_tx;
1841 } while (cur_tx != NO_PREV_TX);
1842 }
1843 cur_group = entries[cur_group].prev_group;
1844 }
1845 Assume(done == linearization.size());
1846 }
1847}
1848
1849} // namespace cluster_linearize
1850
1851#endif // BITCOIN_CLUSTER_LINEARIZE_H
#define LIFETIMEBOUND
Definition: attributes.h:16
int ret
int flags
Definition: bitcoin-tx.cpp:529
#define Assume(val)
Assume is the identity function.
Definition: check.h:125
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.
Class to represent the internal state of the spanning-forest linearization (SFL) algorithm.
void UpdateChunk(const SetType &chunk, TxIdx query, TxIdx chunk_rep, const SetInfo< SetType > &dep_change) noexcept
Update a chunk:
TxIdx PickRandomTx(const SetType &tx_idxs) noexcept
Pick a random transaction within a set (which must be non-empty).
std::vector< TxData > m_tx_data
Information about each transaction (and chunks).
InsecureRandomContext m_rng
Internal RNG.
DepGraphIndex TxIdx
Data type to represent indexing into m_tx_data.
bool OptimizeStep() noexcept
Try to improve the forest.
uint64_t GetCost() const noexcept
Determine how much work was performed so far.
TxIdx Activate(DepIdx dep_idx) noexcept
Make a specified inactive dependency active.
void Improve(DepIdx dep_idx) noexcept
Split a chunk, and then merge the resulting two chunks to make the graph topological again.
std::vector< FeeFrac > GetDiagram() const noexcept
Get the diagram for the current state, which must be topological.
SpanningForestState(const DepGraph< SetType > &depgraph LIFETIMEBOUND, uint64_t rng_seed) noexcept
Construct a spanning forest for the given DepGraph, with every transaction in its own chunk (not topo...
std::vector< DepData > m_dep_data
Information about each dependency.
void MakeTopological() noexcept
Make state topological.
TxIdx MergeChunks(TxIdx top_rep, TxIdx bottom_rep) noexcept
Activate a dependency from the chunk represented by bottom_idx to the chunk represented by top_idx.
void StartMinimizing() noexcept
Initialize data structure for minimizing the chunks.
VecDeque< std::tuple< TxIdx, TxIdx, unsigned > > m_nonminimal_chunks
A FIFO of chunk representatives with a pivot transaction in them, and a flag to indicate their status...
TxIdx MergeStep(TxIdx chunk_rep) noexcept
Perform an upward or downward merge step, on the specified chunk representative.
void SanityCheck(const DepGraph< SetType > &depgraph) const
Verify internal consistency of the data structure.
void StartOptimizing() noexcept
Initialize the data structure for optimization.
VecDeque< TxIdx > m_suboptimal_chunks
A FIFO of chunk representatives of chunks that may be improved still.
void LoadLinearization(std::span< const DepGraphIndex > old_linearization) noexcept
Load an existing linearization.
bool MinimizeStep() noexcept
Try to reduce a chunk's size.
uint32_t DepIdx
Data type to represent indexing into m_dep_data.
std::vector< DepGraphIndex > GetLinearization(const StrongComparator< DepGraphIndex > auto &fallback_order) const noexcept
Construct a topologically-valid linearization from the current forest state.
SetType m_transaction_idxs
The set of all TxIdx's of transactions in the cluster indexing into m_tx_data.
const DepGraph< SetType > & m_depgraph
The DepGraph we are trying to linearize.
void MergeSequence(TxIdx tx_idx) noexcept
Perform an upward or downward merge sequence on the specified transaction.
uint64_t m_cost
The number of updated transactions in activations/deactivations.
void Deactivate(DepIdx dep_idx) noexcept
Make a specified active dependency inactive.
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::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::tuple< std::vector< DepGraphIndex >, bool, uint64_t > Linearize(const DepGraph< SetType > &depgraph, uint64_t max_iterations, 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::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
Data structure storing a fee and size, ordered by increasing fee/size.
Definition: feefrac.h:40
int64_t fee
Definition: feefrac.h:107
bool IsEmpty() const noexcept
Check if this is empty (size and fee are 0).
Definition: feefrac.h:120
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 dependency.
bool active
Whether this dependency is active.
SetInfo< SetType > top_setinfo
(Only if this dependency is active) the would-be top chunk and its feerate that would be formed if th...
TxIdx parent
What the parent and child transactions are.
Structure with information about a single transaction.
std::vector< DepIdx > child_deps
The dependencies to children of this transaction.
SetType parents
The set of parent transactions of this transaction.
SetType children
The set of child transactions of this transaction.
TxIdx chunk_rep
Which transaction holds the chunk_setinfo for the chunk this transaction is in (the representative fo...
SetInfo< SetType > chunk_setinfo
(Only if this transaction is the representative for the chunk it is in) the total chunk set and feera...
static int count
assert(!tx.IsCoinBase())