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 <memusage.h>
16#include <random.h>
17#include <span.h>
18#include <util/feefrac.h>
19#include <util/vecdeque.h>
20
22
24using DepGraphIndex = uint32_t;
25
28template<typename SetType>
30{
32 struct Entry
33 {
37 SetType ancestors;
39 SetType descendants;
40
42 friend bool operator==(const Entry&, const Entry&) noexcept = default;
43
45 Entry() noexcept = default;
47 Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {}
48 };
49
51 std::vector<Entry> entries;
52
54 SetType m_used;
55
56public:
58 friend bool operator==(const DepGraph& a, const DepGraph& b) noexcept
59 {
60 if (a.m_used != b.m_used) return false;
61 // Only compare the used positions within the entries vector.
62 for (auto idx : a.m_used) {
63 if (a.entries[idx] != b.entries[idx]) return false;
64 }
65 return true;
66 }
67
68 // Default constructors.
69 DepGraph() noexcept = default;
70 DepGraph(const DepGraph&) noexcept = default;
71 DepGraph(DepGraph&&) noexcept = default;
72 DepGraph& operator=(const DepGraph&) noexcept = default;
73 DepGraph& operator=(DepGraph&&) noexcept = default;
74
90 DepGraph(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> mapping, DepGraphIndex pos_range) noexcept : entries(pos_range)
91 {
92 Assume(mapping.size() == depgraph.PositionRange());
93 Assume((pos_range == 0) == (depgraph.TxCount() == 0));
94 for (DepGraphIndex i : depgraph.Positions()) {
95 auto new_idx = mapping[i];
96 Assume(new_idx < pos_range);
97 // Add transaction.
98 entries[new_idx].ancestors = SetType::Singleton(new_idx);
99 entries[new_idx].descendants = SetType::Singleton(new_idx);
100 m_used.Set(new_idx);
101 // Fill in fee and size.
102 entries[new_idx].feerate = depgraph.entries[i].feerate;
103 }
104 for (DepGraphIndex i : depgraph.Positions()) {
105 // Fill in dependencies by mapping direct parents.
106 SetType parents;
107 for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]);
108 AddDependencies(parents, mapping[i]);
109 }
110 // Verify that the provided pos_range was correct (no unused positions at the end).
111 Assume(m_used.None() ? (pos_range == 0) : (pos_range == m_used.Last() + 1));
112 }
113
115 const SetType& Positions() const noexcept { return m_used; }
117 DepGraphIndex PositionRange() const noexcept { return entries.size(); }
119 auto TxCount() const noexcept { return m_used.Count(); }
121 const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; }
123 FeeFrac& FeeRate(DepGraphIndex i) noexcept { return entries[i].feerate; }
125 const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; }
127 const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; }
128
134 DepGraphIndex AddTransaction(const FeeFrac& feefrac) noexcept
135 {
136 static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size());
137 auto available = ALL_POSITIONS - m_used;
138 Assume(available.Any());
139 DepGraphIndex new_idx = available.First();
140 if (new_idx == entries.size()) {
141 entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
142 } else {
143 entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
144 }
145 m_used.Set(new_idx);
146 return new_idx;
147 }
148
158 void RemoveTransactions(const SetType& del) noexcept
159 {
160 m_used -= del;
161 // Remove now-unused trailing entries.
162 while (!entries.empty() && !m_used[entries.size() - 1]) {
163 entries.pop_back();
164 }
165 // Remove the deleted transactions from ancestors/descendants of other transactions. Note
166 // that the deleted positions will retain old feerate and dependency information. This does
167 // not matter as they will be overwritten by AddTransaction if they get used again.
168 for (auto& entry : entries) {
169 entry.ancestors &= m_used;
170 entry.descendants &= m_used;
171 }
172 }
173
178 void AddDependencies(const SetType& parents, DepGraphIndex child) noexcept
179 {
180 Assume(m_used[child]);
181 Assume(parents.IsSubsetOf(m_used));
182 // Compute the ancestors of parents that are not already ancestors of child.
183 SetType par_anc;
184 for (auto par : parents - Ancestors(child)) {
185 par_anc |= Ancestors(par);
186 }
187 par_anc -= Ancestors(child);
188 // Bail out if there are no such ancestors.
189 if (par_anc.None()) return;
190 // To each such ancestor, add as descendants the descendants of the child.
191 const auto& chl_des = entries[child].descendants;
192 for (auto anc_of_par : par_anc) {
193 entries[anc_of_par].descendants |= chl_des;
194 }
195 // To each descendant of the child, add those ancestors.
196 for (auto dec_of_chl : Descendants(child)) {
197 entries[dec_of_chl].ancestors |= par_anc;
198 }
199 }
200
209 SetType GetReducedParents(DepGraphIndex i) const noexcept
210 {
211 SetType parents = Ancestors(i);
212 parents.Reset(i);
213 for (auto parent : parents) {
214 if (parents[parent]) {
215 parents -= Ancestors(parent);
216 parents.Set(parent);
217 }
218 }
219 return parents;
220 }
221
230 SetType GetReducedChildren(DepGraphIndex i) const noexcept
231 {
232 SetType children = Descendants(i);
233 children.Reset(i);
234 for (auto child : children) {
235 if (children[child]) {
236 children -= Descendants(child);
237 children.Set(child);
238 }
239 }
240 return children;
241 }
242
247 FeeFrac FeeRate(const SetType& elems) const noexcept
248 {
249 FeeFrac ret;
250 for (auto pos : elems) ret += entries[pos].feerate;
251 return ret;
252 }
253
264 SetType GetConnectedComponent(const SetType& todo, DepGraphIndex tx) const noexcept
265 {
266 Assume(todo[tx]);
267 Assume(todo.IsSubsetOf(m_used));
268 auto to_add = SetType::Singleton(tx);
269 SetType ret;
270 do {
271 SetType old = ret;
272 for (auto add : to_add) {
273 ret |= Descendants(add);
274 ret |= Ancestors(add);
275 }
276 ret &= todo;
277 to_add = ret - old;
278 } while (to_add.Any());
279 return ret;
280 }
281
289 SetType FindConnectedComponent(const SetType& todo) const noexcept
290 {
291 if (todo.None()) return todo;
292 return GetConnectedComponent(todo, todo.First());
293 }
294
299 bool IsConnected(const SetType& subset) const noexcept
300 {
301 return FindConnectedComponent(subset) == subset;
302 }
303
308 bool IsConnected() const noexcept { return IsConnected(m_used); }
309
314 void AppendTopo(std::vector<DepGraphIndex>& list, const SetType& select) const noexcept
315 {
316 DepGraphIndex old_len = list.size();
317 for (auto i : select) list.push_back(i);
318 std::sort(list.begin() + old_len, list.end(), [&](DepGraphIndex a, DepGraphIndex b) noexcept {
319 const auto a_anc_count = entries[a].ancestors.Count();
320 const auto b_anc_count = entries[b].ancestors.Count();
321 if (a_anc_count != b_anc_count) return a_anc_count < b_anc_count;
322 return a < b;
323 });
324 }
325
327 bool IsAcyclic() const noexcept
328 {
329 for (auto i : Positions()) {
330 if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) {
331 return false;
332 }
333 }
334 return true;
335 }
336
337 unsigned CountDependencies() const noexcept
338 {
339 unsigned ret = 0;
340 for (auto i : Positions()) {
341 ret += GetReducedParents(i).Count();
342 }
343 return ret;
344 }
345
347 void Compact() noexcept
348 {
349 entries.shrink_to_fit();
350 }
351
352 size_t DynamicMemoryUsage() const noexcept
353 {
355 }
356};
357
359template<typename SetType>
361{
366
368 SetInfo() noexcept = default;
369
371 SetInfo(const SetType& txn, const FeeFrac& fr) noexcept : transactions(txn), feerate(fr) {}
372
374 explicit SetInfo(const DepGraph<SetType>& depgraph, DepGraphIndex pos) noexcept :
375 transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {}
376
378 explicit SetInfo(const DepGraph<SetType>& depgraph, const SetType& txn) noexcept :
379 transactions(txn), feerate(depgraph.FeeRate(txn)) {}
380
382 void Set(const DepGraph<SetType>& depgraph, DepGraphIndex pos) noexcept
383 {
384 Assume(!transactions[pos]);
385 transactions.Set(pos);
386 feerate += depgraph.FeeRate(pos);
387 }
388
390 SetInfo& operator|=(const SetInfo& other) noexcept
391 {
392 Assume(!transactions.Overlaps(other.transactions));
393 transactions |= other.transactions;
394 feerate += other.feerate;
395 return *this;
396 }
397
399 SetInfo& operator-=(const SetInfo& other) noexcept
400 {
401 Assume(other.transactions.IsSubsetOf(transactions));
402 transactions -= other.transactions;
403 feerate -= other.feerate;
404 return *this;
405 }
406
408 SetInfo operator-(const SetInfo& other) const noexcept
409 {
410 Assume(other.transactions.IsSubsetOf(transactions));
411 return {transactions - other.transactions, feerate - other.feerate};
412 }
413
415 friend void swap(SetInfo& a, SetInfo& b) noexcept
416 {
417 swap(a.transactions, b.transactions);
418 swap(a.feerate, b.feerate);
419 }
420
422 friend bool operator==(const SetInfo&, const SetInfo&) noexcept = default;
423};
424
426template<typename SetType>
427std::vector<SetInfo<SetType>> ChunkLinearizationInfo(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> linearization) noexcept
428{
429 std::vector<SetInfo<SetType>> ret;
430 for (DepGraphIndex i : linearization) {
432 SetInfo<SetType> new_chunk(depgraph, i);
433 // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
434 while (!ret.empty() && new_chunk.feerate >> ret.back().feerate) {
435 new_chunk |= ret.back();
436 ret.pop_back();
437 }
438 // Actually move that new chunk into the chunking.
439 ret.emplace_back(std::move(new_chunk));
440 }
441 return ret;
442}
443
446template<typename SetType>
447std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> linearization) noexcept
448{
449 std::vector<FeeFrac> ret;
450 for (DepGraphIndex i : linearization) {
452 auto new_chunk = depgraph.FeeRate(i);
453 // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
454 while (!ret.empty() && new_chunk >> ret.back()) {
455 new_chunk += ret.back();
456 ret.pop_back();
457 }
458 // Actually move that new chunk into the chunking.
459 ret.push_back(std::move(new_chunk));
460 }
461 return ret;
462}
463
630template<typename SetType>
632{
633private:
636
638 using TxIdx = uint32_t;
640 using DepIdx = uint32_t;
641
644 struct TxData {
646 std::vector<DepIdx> child_deps;
648 SetType parents;
650 SetType children;
657 };
658
660 struct DepData {
662 bool active;
668 };
669
674 std::vector<TxData> m_tx_data;
676 std::vector<DepData> m_dep_data;
686
688 uint64_t m_cost{0};
689
691 TxIdx PickRandomTx(const SetType& tx_idxs) noexcept
692 {
693 Assume(tx_idxs.Any());
694 unsigned pos = m_rng.randrange<unsigned>(tx_idxs.Count());
695 for (auto tx_idx : tx_idxs) {
696 if (pos == 0) return tx_idx;
697 --pos;
698 }
699 Assume(false);
700 return TxIdx(-1);
701 }
702
708 template<bool Subtract>
709 void UpdateChunk(const SetType& chunk, TxIdx query, TxIdx chunk_rep, const SetInfo<SetType>& dep_change) noexcept
710 {
711 // Iterate over all the chunk's transactions.
712 for (auto tx_idx : chunk) {
713 auto& tx_data = m_tx_data[tx_idx];
714 // Update the chunk representative.
715 tx_data.chunk_rep = chunk_rep;
716 // Iterate over all active dependencies with tx_idx as parent. Combined with the outer
717 // loop this iterates over all internal active dependencies of the chunk.
718 auto child_deps = std::span{tx_data.child_deps};
719 for (auto dep_idx : child_deps) {
720 auto& dep_entry = m_dep_data[dep_idx];
721 Assume(dep_entry.parent == tx_idx);
722 // Skip inactive dependencies.
723 if (!dep_entry.active) continue;
724 // If this dependency's top_setinfo contains query, update it to add/remove
725 // dep_change.
726 if (dep_entry.top_setinfo.transactions[query]) {
727 if constexpr (Subtract) {
728 dep_entry.top_setinfo -= dep_change;
729 } else {
730 dep_entry.top_setinfo |= dep_change;
731 }
732 }
733 }
734 }
735 }
736
738 TxIdx Activate(DepIdx dep_idx) noexcept
739 {
740 auto& dep_data = m_dep_data[dep_idx];
741 Assume(!dep_data.active);
742 auto& child_tx_data = m_tx_data[dep_data.child];
743 auto& parent_tx_data = m_tx_data[dep_data.parent];
744
745 // Gather information about the parent and child chunks.
746 Assume(parent_tx_data.chunk_rep != child_tx_data.chunk_rep);
747 auto& par_chunk_data = m_tx_data[parent_tx_data.chunk_rep];
748 auto& chl_chunk_data = m_tx_data[child_tx_data.chunk_rep];
749 TxIdx top_rep = parent_tx_data.chunk_rep;
750 auto top_part = par_chunk_data.chunk_setinfo;
751 auto bottom_part = chl_chunk_data.chunk_setinfo;
752 // Update the parent chunk to also contain the child.
753 par_chunk_data.chunk_setinfo |= bottom_part;
754 m_cost += par_chunk_data.chunk_setinfo.transactions.Count();
755
756 // Consider the following example:
757 //
758 // A A There are two chunks, ABC and DEF, and the inactive E->C dependency
759 // / \ / \ is activated, resulting in a single chunk ABCDEF.
760 // B C B C
761 // : ==> | Dependency | top set before | top set after | change
762 // D E D E B->A | AC | ACDEF | +DEF
763 // \ / \ / C->A | AB | AB |
764 // F F F->D | D | D |
765 // F->E | E | ABCE | +ABC
766 //
767 // The common pattern here is that any dependency which has the parent or child of the
768 // dependency being activated (E->C here) in its top set, will have the opposite part added
769 // to it. This is true for B->A and F->E, but not for C->A and F->D.
770 //
771 // Let UpdateChunk traverse the old parent chunk top_part (ABC in example), and add
772 // bottom_part (DEF) to every dependency's top_set which has the parent (C) in it. The
773 // representative of each of these transactions was already top_rep, so that is not being
774 // changed here.
775 UpdateChunk<false>(/*chunk=*/top_part.transactions, /*query=*/dep_data.parent,
776 /*chunk_rep=*/top_rep, /*dep_change=*/bottom_part);
777 // Let UpdateChunk traverse the old child chunk bottom_part (DEF in example), and add
778 // top_part (ABC) to every dependency's top_set which has the child (E) in it. At the same
779 // time, change the representative of each of these transactions to be top_rep, which
780 // becomes the representative for the merged chunk.
781 UpdateChunk<false>(/*chunk=*/bottom_part.transactions, /*query=*/dep_data.child,
782 /*chunk_rep=*/top_rep, /*dep_change=*/top_part);
783 // Make active.
784 dep_data.active = true;
785 dep_data.top_setinfo = top_part;
786 return top_rep;
787 }
788
790 void Deactivate(DepIdx dep_idx) noexcept
791 {
792 auto& dep_data = m_dep_data[dep_idx];
793 Assume(dep_data.active);
794 auto& parent_tx_data = m_tx_data[dep_data.parent];
795 // Make inactive.
796 dep_data.active = false;
797 // Update representatives.
798 auto& chunk_data = m_tx_data[parent_tx_data.chunk_rep];
799 m_cost += chunk_data.chunk_setinfo.transactions.Count();
800 auto top_part = dep_data.top_setinfo;
801 auto bottom_part = chunk_data.chunk_setinfo - top_part;
802 TxIdx bottom_rep = dep_data.child;
803 auto& bottom_chunk_data = m_tx_data[bottom_rep];
804 bottom_chunk_data.chunk_setinfo = bottom_part;
805 TxIdx top_rep = dep_data.parent;
806 auto& top_chunk_data = m_tx_data[top_rep];
807 top_chunk_data.chunk_setinfo = top_part;
808
809 // See the comment above in Activate(). We perform the opposite operations here,
810 // removing instead of adding.
811 //
812 // Let UpdateChunk traverse the old parent chunk top_part, and remove bottom_part from
813 // every dependency's top_set which has the parent in it. At the same time, change the
814 // representative of each of these transactions to be top_rep.
815 UpdateChunk<true>(/*chunk=*/top_part.transactions, /*query=*/dep_data.parent,
816 /*chunk_rep=*/top_rep, /*dep_change=*/bottom_part);
817 // Let UpdateChunk traverse the old child chunk bottom_part, and remove top_part from every
818 // dependency's top_set which has the child in it. At the same time, change the
819 // representative of each of these transactions to be bottom_rep.
820 UpdateChunk<true>(/*chunk=*/bottom_part.transactions, /*query=*/dep_data.child,
821 /*chunk_rep=*/bottom_rep, /*dep_change=*/top_part);
822 }
823
827 TxIdx MergeChunks(TxIdx top_rep, TxIdx bottom_rep) noexcept
828 {
829 auto& top_chunk = m_tx_data[top_rep];
830 Assume(top_chunk.chunk_rep == top_rep);
831 auto& bottom_chunk = m_tx_data[bottom_rep];
832 Assume(bottom_chunk.chunk_rep == bottom_rep);
833 // Count the number of dependencies between bottom_chunk and top_chunk.
834 TxIdx num_deps{0};
835 for (auto tx : top_chunk.chunk_setinfo.transactions) {
836 auto& tx_data = m_tx_data[tx];
837 num_deps += (tx_data.children & bottom_chunk.chunk_setinfo.transactions).Count();
838 }
839 if (num_deps == 0) return TxIdx(-1);
840 // Uniformly randomly pick one of them and activate it.
841 TxIdx pick = m_rng.randrange(num_deps);
842 for (auto tx : top_chunk.chunk_setinfo.transactions) {
843 auto& tx_data = m_tx_data[tx];
844 auto intersect = tx_data.children & bottom_chunk.chunk_setinfo.transactions;
845 auto count = intersect.Count();
846 if (pick < count) {
847 for (auto dep : tx_data.child_deps) {
848 auto& dep_data = m_dep_data[dep];
849 if (bottom_chunk.chunk_setinfo.transactions[dep_data.child]) {
850 if (pick == 0) return Activate(dep);
851 --pick;
852 }
853 }
854 break;
855 }
856 pick -= count;
857 }
858 Assume(false);
859 return TxIdx(-1);
860 }
861
864 template<bool DownWard>
865 TxIdx MergeStep(TxIdx chunk_rep) noexcept
866 {
868 auto& chunk_data = m_tx_data[chunk_rep];
869 SetType chunk_txn = chunk_data.chunk_setinfo.transactions;
870 // Iterate over all transactions in the chunk, figuring out which other chunk each
871 // depends on, but only testing each other chunk once. For those depended-on chunks,
872 // remember the highest-feerate (if DownWard) or lowest-feerate (if !DownWard) one.
873 // If multiple equal-feerate candidate chunks to merge with exist, pick a random one
874 // among them.
875
878 SetType explored = chunk_txn;
882 FeeFrac best_other_chunk_feerate = chunk_data.chunk_setinfo.feerate;
884 TxIdx best_other_chunk_rep = TxIdx(-1);
887 uint64_t best_other_chunk_tiebreak{0};
888 for (auto tx : chunk_txn) {
889 auto& tx_data = m_tx_data[tx];
892 auto newly_reached = (DownWard ? tx_data.children : tx_data.parents) - explored;
893 explored |= newly_reached;
894 while (newly_reached.Any()) {
895 // Find a chunk inside newly_reached, and remove it from newly_reached.
896 auto reached_chunk_rep = m_tx_data[newly_reached.First()].chunk_rep;
897 auto& reached_chunk = m_tx_data[reached_chunk_rep].chunk_setinfo;
898 newly_reached -= reached_chunk.transactions;
899 // See if it has an acceptable feerate.
900 auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk.feerate)
901 : FeeRateCompare(reached_chunk.feerate, best_other_chunk_feerate);
902 if (cmp > 0) continue;
903 uint64_t tiebreak = m_rng.rand64();
904 if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) {
905 best_other_chunk_feerate = reached_chunk.feerate;
906 best_other_chunk_rep = reached_chunk_rep;
907 best_other_chunk_tiebreak = tiebreak;
908 }
909 }
910 }
911 // Stop if there are no candidate chunks to merge with.
912 if (best_other_chunk_rep == TxIdx(-1)) return TxIdx(-1);
913 if constexpr (DownWard) {
914 chunk_rep = MergeChunks(chunk_rep, best_other_chunk_rep);
915 } else {
916 chunk_rep = MergeChunks(best_other_chunk_rep, chunk_rep);
917 }
918 Assume(chunk_rep != TxIdx(-1));
919 return chunk_rep;
920 }
921
922
924 template<bool DownWard>
925 void MergeSequence(TxIdx tx_idx) noexcept
926 {
927 auto chunk_rep = m_tx_data[tx_idx].chunk_rep;
928 while (true) {
929 auto merged_rep = MergeStep<DownWard>(chunk_rep);
930 if (merged_rep == TxIdx(-1)) break;
931 chunk_rep = merged_rep;
932 }
933 // Add the chunk to the queue of improvable chunks.
935 }
936
939 void Improve(DepIdx dep_idx) noexcept
940 {
941 auto& dep_data = m_dep_data[dep_idx];
942 Assume(dep_data.active);
943 // Deactivate the specified dependency, splitting it into two new chunks: a top containing
944 // the parent, and a bottom containing the child. The top should have a higher feerate.
945 Deactivate(dep_idx);
946
947 // At this point we have exactly two chunks which may violate topology constraints (the
948 // parent chunk and child chunk that were produced by deactivating dep_idx). We can fix
949 // these using just merge sequences, one upwards and one downwards, avoiding the need for a
950 // full MakeTopological.
951
952 // Merge the top chunk with lower-feerate chunks it depends on (which may be the bottom it
953 // was just split from, or other pre-existing chunks).
954 MergeSequence<false>(dep_data.parent);
955 // Merge the bottom chunk with higher-feerate chunks that depend on it.
956 MergeSequence<true>(dep_data.child);
957 }
958
959public:
962 explicit SpanningForestState(const DepGraph<SetType>& depgraph, uint64_t rng_seed) noexcept : m_rng(rng_seed)
963 {
964 m_transaction_idxs = depgraph.Positions();
965 auto num_transactions = m_transaction_idxs.Count();
966 m_tx_data.resize(depgraph.PositionRange());
967 // Reserve the maximum number of (reserved) dependencies the cluster can have, so
968 // m_dep_data won't need any reallocations during construction. For a cluster with N
969 // transactions, the worst case consists of two sets of transactions, the parents and the
970 // children, where each child depends on each parent and nothing else. For even N, both
971 // sets can be sized N/2, which means N^2/4 dependencies. For odd N, one can be (N + 1)/2
972 // and the other can be (N - 1)/2, meaning (N^2 - 1)/4 dependencies. Because N^2 is odd in
973 // this case, N^2/4 (with rounding-down division) is the correct value in both cases.
974 m_dep_data.reserve((num_transactions * num_transactions) / 4);
975 for (auto tx : m_transaction_idxs) {
976 // Fill in transaction data.
977 auto& tx_data = m_tx_data[tx];
978 tx_data.chunk_rep = tx;
979 tx_data.chunk_setinfo.transactions = SetType::Singleton(tx);
980 tx_data.chunk_setinfo.feerate = depgraph.FeeRate(tx);
981 // Add its dependencies.
982 SetType parents = depgraph.GetReducedParents(tx);
983 for (auto par : parents) {
984 auto& par_tx_data = m_tx_data[par];
985 auto dep_idx = m_dep_data.size();
986 // Construct new dependency.
987 auto& dep = m_dep_data.emplace_back();
988 dep.active = false;
989 dep.parent = par;
990 dep.child = tx;
991 // Add it as parent of the child.
992 tx_data.parents.Set(par);
993 // Add it as child of the parent.
994 par_tx_data.child_deps.push_back(dep_idx);
995 par_tx_data.children.Set(tx);
996 }
997 }
998 }
999
1003 void LoadLinearization(std::span<const DepGraphIndex> old_linearization) noexcept
1004 {
1005 // Add transactions one by one, in order of existing linearization.
1006 for (DepGraphIndex tx : old_linearization) {
1007 auto chunk_rep = m_tx_data[tx].chunk_rep;
1008 // Merge the chunk upwards, as long as merging succeeds.
1009 while (true) {
1010 chunk_rep = MergeStep<false>(chunk_rep);
1011 if (chunk_rep == TxIdx(-1)) break;
1012 }
1013 }
1014 }
1015
1017 void MakeTopological() noexcept
1018 {
1019 for (auto tx : m_transaction_idxs) {
1020 auto& tx_data = m_tx_data[tx];
1021 if (tx_data.chunk_rep == tx) {
1023 // Randomize the initial order of suboptimal chunks in the queue.
1025 if (j != m_suboptimal_chunks.size() - 1) {
1027 }
1028 }
1029 }
1030 while (!m_suboptimal_chunks.empty()) {
1031 // Pop an entry from the potentially-suboptimal chunk queue.
1034 auto& chunk_data = m_tx_data[chunk];
1035 // If what was popped is not currently a chunk representative, continue. This may
1036 // happen when it was merged with something else since being added.
1037 if (chunk_data.chunk_rep != chunk) continue;
1038 int flip = m_rng.randbool();
1039 for (int i = 0; i < 2; ++i) {
1040 if (i ^ flip) {
1041 // Attempt to merge the chunk upwards.
1042 auto result_up = MergeStep<false>(chunk);
1043 if (result_up != TxIdx(-1)) {
1044 m_suboptimal_chunks.push_back(result_up);
1045 break;
1046 }
1047 } else {
1048 // Attempt to merge the chunk downwards.
1049 auto result_down = MergeStep<true>(chunk);
1050 if (result_down != TxIdx(-1)) {
1051 m_suboptimal_chunks.push_back(result_down);
1052 break;
1053 }
1054 }
1055 }
1056 }
1057 }
1058
1060 void StartOptimizing() noexcept
1061 {
1062 // Mark chunks suboptimal.
1063 for (auto tx : m_transaction_idxs) {
1064 auto& tx_data = m_tx_data[tx];
1065 if (tx_data.chunk_rep == tx) {
1067 // Randomize the initial order of suboptimal chunks in the queue.
1069 if (j != m_suboptimal_chunks.size() - 1) {
1071 }
1072 }
1073 }
1074 }
1075
1077 bool OptimizeStep() noexcept
1078 {
1079 while (!m_suboptimal_chunks.empty()) {
1080 // Pop an entry from the potentially-suboptimal chunk queue.
1083 auto& chunk_data = m_tx_data[chunk];
1084 // If what was popped is not currently a chunk representative, continue. This may
1085 // happen when a split chunk merges in Improve() with one or more existing chunks that
1086 // are themselves on the suboptimal queue already.
1087 if (chunk_data.chunk_rep != chunk) continue;
1088 // Remember the best dependency seen so far.
1089 DepIdx candidate_dep = DepIdx(-1);
1090 uint64_t candidate_tiebreak = 0;
1091 // Iterate over all transactions.
1092 for (auto tx : chunk_data.chunk_setinfo.transactions) {
1093 const auto& tx_data = m_tx_data[tx];
1094 // Iterate over all active child dependencies of the transaction.
1095 const auto children = std::span{tx_data.child_deps};
1096 for (DepIdx dep_idx : children) {
1097 const auto& dep_data = m_dep_data[dep_idx];
1098 if (!dep_data.active) continue;
1099 // Skip if this dependency is ineligible (the top chunk that would be created
1100 // does not have higher feerate than the chunk it is currently part of).
1101 auto cmp = FeeRateCompare(dep_data.top_setinfo.feerate, chunk_data.chunk_setinfo.feerate);
1102 if (cmp <= 0) continue;
1103 // Generate a random tiebreak for this dependency, and reject it if its tiebreak
1104 // is worse than the best so far. This means that among all eligible
1105 // dependencies, a uniformly random one will be chosen.
1106 uint64_t tiebreak = m_rng.rand64();
1107 if (tiebreak < candidate_tiebreak) continue;
1108 // Remember this as our (new) candidate dependency.
1109 candidate_dep = dep_idx;
1110 candidate_tiebreak = tiebreak;
1111 }
1112 }
1113 // If a candidate with positive gain was found, deactivate it and then make the state
1114 // topological again with a sequence of merges.
1115 if (candidate_dep != DepIdx(-1)) Improve(candidate_dep);
1116 // Stop processing for now, even if nothing was activated, as the loop above may have
1117 // had a nontrivial cost.
1118 return !m_suboptimal_chunks.empty();
1119 }
1120 // No improvable chunk was found, we are done.
1121 return false;
1122 }
1123
1126 void StartMinimizing() noexcept
1127 {
1130 // Gather all chunks, and for each, add it with a random pivot in it, and a random initial
1131 // direction, to m_nonminimal_chunks.
1132 for (auto tx : m_transaction_idxs) {
1133 auto& tx_data = m_tx_data[tx];
1134 if (tx_data.chunk_rep == tx) {
1135 TxIdx pivot_idx = PickRandomTx(tx_data.chunk_setinfo.transactions);
1136 m_nonminimal_chunks.emplace_back(tx, pivot_idx, m_rng.randbits<1>());
1137 // Randomize the initial order of nonminimal chunks in the queue.
1139 if (j != m_nonminimal_chunks.size() - 1) {
1141 }
1142 }
1143 }
1144 }
1145
1147 bool MinimizeStep() noexcept
1148 {
1149 // If the queue of potentially-non-minimal chunks is empty, we are done.
1150 if (m_nonminimal_chunks.empty()) return false;
1151 // Pop an entry from the potentially-non-minimal chunk queue.
1152 auto [chunk_rep, pivot_idx, flags] = m_nonminimal_chunks.front();
1154 auto& chunk_data = m_tx_data[chunk_rep];
1155 Assume(chunk_data.chunk_rep == chunk_rep);
1157 bool move_pivot_down = flags & 1;
1159 bool second_stage = flags & 2;
1160
1161 // Find a random dependency whose top and bottom set feerates are equal, and which has
1162 // pivot in bottom set (if move_pivot_down) or in top set (if !move_pivot_down).
1163 DepIdx candidate_dep = DepIdx(-1);
1164 uint64_t candidate_tiebreak{0};
1165 bool have_any = false;
1166 // Iterate over all transactions.
1167 for (auto tx_idx : chunk_data.chunk_setinfo.transactions) {
1168 const auto& tx_data = m_tx_data[tx_idx];
1169 // Iterate over all active child dependencies of the transaction.
1170 for (auto dep_idx : tx_data.child_deps) {
1171 auto& dep_data = m_dep_data[dep_idx];
1172 // Skip inactive child dependencies.
1173 if (!dep_data.active) continue;
1174 // Skip if this dependency does not have equal top and bottom set feerates. Note
1175 // that the top cannot have higher feerate than the bottom, or OptimizeSteps would
1176 // have dealt with it.
1177 if (dep_data.top_setinfo.feerate << chunk_data.chunk_setinfo.feerate) continue;
1178 have_any = true;
1179 // Skip if this dependency does not have pivot in the right place.
1180 if (move_pivot_down == dep_data.top_setinfo.transactions[pivot_idx]) continue;
1181 // Remember this as our chosen dependency if it has a better tiebreak.
1182 uint64_t tiebreak = m_rng.rand64() | 1;
1183 if (tiebreak > candidate_tiebreak) {
1184 candidate_tiebreak = tiebreak;
1185 candidate_dep = dep_idx;
1186 }
1187 }
1188 }
1189 // If no dependencies have equal top and bottom set feerate, this chunk is minimal.
1190 if (!have_any) return true;
1191 // If all found dependencies have the pivot in the wrong place, try moving it in the other
1192 // direction. If this was the second stage already, we are done.
1193 if (candidate_tiebreak == 0) {
1194 // Switch to other direction, and to second phase.
1195 flags ^= 3;
1196 if (!second_stage) m_nonminimal_chunks.emplace_back(chunk_rep, pivot_idx, flags);
1197 return true;
1198 }
1199
1200 // Otherwise, deactivate the dependency that was found.
1201 Deactivate(candidate_dep);
1202 auto& dep_data = m_dep_data[candidate_dep];
1203 auto parent_chunk_rep = m_tx_data[dep_data.parent].chunk_rep;
1204 auto child_chunk_rep = m_tx_data[dep_data.child].chunk_rep;
1205 // Try to activate a dependency between the new bottom and the new top (opposite from the
1206 // dependency that was just deactivated).
1207 auto merged_chunk_rep = MergeChunks(child_chunk_rep, parent_chunk_rep);
1208 if (merged_chunk_rep != TxIdx(-1)) {
1209 // A self-merge happened.
1210 // Re-insert the chunk into the queue, in the same direction. Note that the chunk_rep
1211 // will have changed.
1212 m_nonminimal_chunks.emplace_back(merged_chunk_rep, pivot_idx, flags);
1213 } else {
1214 // No self-merge happens, and thus we have found a way to split the chunk. Create two
1215 // smaller chunks, and add them to the queue. The one that contains the current pivot
1216 // gets to continue with it in the same direction, to minimize the number of times we
1217 // alternate direction. If we were in the second phase already, the newly created chunk
1218 // inherits that too, because we know no split with the pivot on the other side is
1219 // possible already. The new chunk without the current pivot gets a new randomly-chosen
1220 // one.
1221 if (move_pivot_down) {
1222 auto parent_pivot_idx = PickRandomTx(m_tx_data[parent_chunk_rep].chunk_setinfo.transactions);
1223 m_nonminimal_chunks.emplace_back(parent_chunk_rep, parent_pivot_idx, m_rng.randbits<1>());
1224 m_nonminimal_chunks.emplace_back(child_chunk_rep, pivot_idx, flags);
1225 } else {
1226 auto child_pivot_idx = PickRandomTx(m_tx_data[child_chunk_rep].chunk_setinfo.transactions);
1227 m_nonminimal_chunks.emplace_back(parent_chunk_rep, pivot_idx, flags);
1228 m_nonminimal_chunks.emplace_back(child_chunk_rep, child_pivot_idx, m_rng.randbits<1>());
1229 }
1230 if (m_rng.randbool()) {
1232 }
1233 }
1234 return true;
1235 }
1236
1239 std::vector<DepGraphIndex> GetLinearization() noexcept
1240 {
1242 std::vector<DepGraphIndex> ret;
1243 ret.reserve(m_transaction_idxs.Count());
1246 std::vector<std::pair<TxIdx, uint64_t>> ready_chunks;
1253 std::vector<std::pair<TxIdx, TxIdx>> chunk_deps(m_tx_data.size(), {0, 0});
1255 SetType chunk_reps;
1257 std::vector<TxIdx> ready_tx;
1258 // Populate chunk_deps[c] with the number of {out-of-chunk dependencies, dependencies} the
1259 // child has.
1260 for (TxIdx chl_idx : m_transaction_idxs) {
1261 const auto& chl_data = m_tx_data[chl_idx];
1262 chunk_deps[chl_idx].second = chl_data.parents.Count();
1263 auto chl_chunk_rep = chl_data.chunk_rep;
1264 chunk_reps.Set(chl_chunk_rep);
1265 for (auto par_idx : chl_data.parents) {
1266 auto par_chunk_rep = m_tx_data[par_idx].chunk_rep;
1267 chunk_deps[chl_chunk_rep].first += (par_chunk_rep != chl_chunk_rep);
1268 }
1269 }
1270 // Construct a heap with all chunks that have no out-of-chunk dependencies.
1272 auto chunk_cmp_fn = [&](const std::pair<TxIdx, uint64_t>& a, const std::pair<TxIdx, uint64_t>& b) noexcept {
1273 auto& chunk_a = m_tx_data[a.first];
1274 auto& chunk_b = m_tx_data[b.first];
1275 Assume(chunk_a.chunk_rep == a.first);
1276 Assume(chunk_b.chunk_rep == b.first);
1277 // First sort by chunk feerate.
1278 if (chunk_a.chunk_setinfo.feerate != chunk_b.chunk_setinfo.feerate) {
1279 return chunk_a.chunk_setinfo.feerate < chunk_b.chunk_setinfo.feerate;
1280 }
1281 // Tie-break randomly.
1282 if (a.second != b.second) return a.second < b.second;
1283 // Lastly, tie-break by chunk representative.
1284 return a.first < b.first;
1285 };
1286 for (TxIdx chunk_rep : chunk_reps) {
1287 if (chunk_deps[chunk_rep].first == 0) ready_chunks.emplace_back(chunk_rep, m_rng.rand64());
1288 }
1289 std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn);
1290 // Pop chunks off the heap, highest-feerate ones first.
1291 while (!ready_chunks.empty()) {
1292 auto [chunk_rep, _rnd] = ready_chunks.front();
1293 std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn);
1294 ready_chunks.pop_back();
1295 Assume(m_tx_data[chunk_rep].chunk_rep == chunk_rep);
1296 Assume(chunk_deps[chunk_rep].first == 0);
1297 const auto& chunk_txn = m_tx_data[chunk_rep].chunk_setinfo.transactions;
1298 // Build heap of all includable transactions in chunk.
1299 for (TxIdx tx_idx : chunk_txn) {
1300 if (chunk_deps[tx_idx].second == 0) {
1301 ready_tx.push_back(tx_idx);
1302 }
1303 }
1304 Assume(!ready_tx.empty());
1305 // Pick transactions from the ready queue, append them to linearization, and decrement
1306 // dependency counts.
1307 while (!ready_tx.empty()) {
1308 // Move a random queue element to the back.
1309 auto pos = m_rng.randrange(ready_tx.size());
1310 if (pos != ready_tx.size() - 1) std::swap(ready_tx.back(), ready_tx[pos]);
1311 // Pop from the back.
1312 auto tx_idx = ready_tx.back();
1313 Assume(chunk_txn[tx_idx]);
1314 ready_tx.pop_back();
1315 // Append to linearization.
1316 ret.push_back(tx_idx);
1317 // Decrement dependency counts.
1318 auto& tx_data = m_tx_data[tx_idx];
1319 for (TxIdx chl_idx : tx_data.children) {
1320 auto& chl_data = m_tx_data[chl_idx];
1321 // Decrement tx dependency count.
1322 Assume(chunk_deps[chl_idx].second > 0);
1323 if (--chunk_deps[chl_idx].second == 0 && chunk_txn[chl_idx]) {
1324 // Child tx has no dependencies left, and is in this chunk. Add it to the tx queue.
1325 ready_tx.push_back(chl_idx);
1326 }
1327 // Decrement chunk dependency count if this is out-of-chunk dependency.
1328 if (chl_data.chunk_rep != chunk_rep) {
1329 Assume(chunk_deps[chl_data.chunk_rep].first > 0);
1330 if (--chunk_deps[chl_data.chunk_rep].first == 0) {
1331 // Child chunk has no dependencies left. Add it to the chunk heap.
1332 ready_chunks.emplace_back(chl_data.chunk_rep, m_rng.rand64());
1333 std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn);
1334 }
1335 }
1336 }
1337 }
1338 }
1339 Assume(ret.size() == m_transaction_idxs.Count());
1340 return ret;
1341 }
1342
1356 std::vector<FeeFrac> GetDiagram() const noexcept
1357 {
1358 std::vector<FeeFrac> ret;
1359 for (auto tx : m_transaction_idxs) {
1360 if (m_tx_data[tx].chunk_rep == tx) {
1361 ret.push_back(m_tx_data[tx].chunk_setinfo.feerate);
1362 }
1363 }
1364 std::sort(ret.begin(), ret.end(), std::greater{});
1365 return ret;
1366 }
1367
1369 uint64_t GetCost() const noexcept { return m_cost; }
1370
1372 void SanityCheck(const DepGraph<SetType>& depgraph) const
1373 {
1374 //
1375 // Verify dependency parent/child information, and build list of (active) dependencies.
1376 //
1377 std::vector<std::pair<TxIdx, TxIdx>> expected_dependencies;
1378 std::vector<std::tuple<TxIdx, TxIdx, DepIdx>> all_dependencies;
1379 std::vector<std::tuple<TxIdx, TxIdx, DepIdx>> active_dependencies;
1380 for (auto parent_idx : depgraph.Positions()) {
1381 for (auto child_idx : depgraph.GetReducedChildren(parent_idx)) {
1382 expected_dependencies.emplace_back(parent_idx, child_idx);
1383 }
1384 }
1385 for (DepIdx dep_idx = 0; dep_idx < m_dep_data.size(); ++dep_idx) {
1386 const auto& dep_data = m_dep_data[dep_idx];
1387 all_dependencies.emplace_back(dep_data.parent, dep_data.child, dep_idx);
1388 // Also add to active_dependencies if it is active.
1389 if (m_dep_data[dep_idx].active) {
1390 active_dependencies.emplace_back(dep_data.parent, dep_data.child, dep_idx);
1391 }
1392 }
1393 std::sort(expected_dependencies.begin(), expected_dependencies.end());
1394 std::sort(all_dependencies.begin(), all_dependencies.end());
1395 assert(expected_dependencies.size() == all_dependencies.size());
1396 for (size_t i = 0; i < expected_dependencies.size(); ++i) {
1397 assert(expected_dependencies[i] ==
1398 std::make_pair(std::get<0>(all_dependencies[i]),
1399 std::get<1>(all_dependencies[i])));
1400 }
1401
1402 //
1403 // Verify the chunks against the list of active dependencies
1404 //
1405 for (auto tx_idx: depgraph.Positions()) {
1406 // Only process chunks for now.
1407 if (m_tx_data[tx_idx].chunk_rep == tx_idx) {
1408 const auto& chunk_data = m_tx_data[tx_idx];
1409 // Verify that transactions in the chunk point back to it. This guarantees
1410 // that chunks are non-overlapping.
1411 for (auto chunk_tx : chunk_data.chunk_setinfo.transactions) {
1412 assert(m_tx_data[chunk_tx].chunk_rep == tx_idx);
1413 }
1414 // Verify the chunk's transaction set: it must contain the representative, and for
1415 // every active dependency, if it contains the parent or child, it must contain
1416 // both. It must have exactly N-1 active dependencies in it, guaranteeing it is
1417 // acyclic.
1418 SetType expected_chunk = SetType::Singleton(tx_idx);
1419 while (true) {
1420 auto old = expected_chunk;
1421 size_t active_dep_count{0};
1422 for (const auto& [par, chl, _dep] : active_dependencies) {
1423 if (expected_chunk[par] || expected_chunk[chl]) {
1424 expected_chunk.Set(par);
1425 expected_chunk.Set(chl);
1426 ++active_dep_count;
1427 }
1428 }
1429 if (old == expected_chunk) {
1430 assert(expected_chunk.Count() == active_dep_count + 1);
1431 break;
1432 }
1433 }
1434 assert(chunk_data.chunk_setinfo.transactions == expected_chunk);
1435 // Verify the chunk's feerate.
1436 assert(chunk_data.chunk_setinfo.feerate ==
1437 depgraph.FeeRate(chunk_data.chunk_setinfo.transactions));
1438 }
1439 }
1440
1441 //
1442 // Verify other transaction data.
1443 //
1444 assert(m_transaction_idxs == depgraph.Positions());
1445 for (auto tx_idx : m_transaction_idxs) {
1446 const auto& tx_data = m_tx_data[tx_idx];
1447 // Verify it has a valid chunk representative, and that chunk includes this
1448 // transaction.
1449 assert(m_tx_data[tx_data.chunk_rep].chunk_rep == tx_data.chunk_rep);
1450 assert(m_tx_data[tx_data.chunk_rep].chunk_setinfo.transactions[tx_idx]);
1451 // Verify parents/children.
1452 assert(tx_data.parents == depgraph.GetReducedParents(tx_idx));
1453 assert(tx_data.children == depgraph.GetReducedChildren(tx_idx));
1454 // Verify list of child dependencies.
1455 std::vector<DepIdx> expected_child_deps;
1456 for (const auto& [par_idx, chl_idx, dep_idx] : all_dependencies) {
1457 if (tx_idx == par_idx) {
1458 assert(tx_data.children[chl_idx]);
1459 expected_child_deps.push_back(dep_idx);
1460 }
1461 }
1462 std::sort(expected_child_deps.begin(), expected_child_deps.end());
1463 auto child_deps_copy = tx_data.child_deps;
1464 std::sort(child_deps_copy.begin(), child_deps_copy.end());
1465 assert(expected_child_deps == child_deps_copy);
1466 }
1467
1468 //
1469 // Verify active dependencies' top_setinfo.
1470 //
1471 for (const auto& [par_idx, chl_idx, dep_idx] : active_dependencies) {
1472 const auto& dep_data = m_dep_data[dep_idx];
1473 // Verify the top_info's transactions: it must contain the parent, and for every
1474 // active dependency, except dep_idx itself, if it contains the parent or child, it
1475 // must contain both.
1476 SetType expected_top = SetType::Singleton(par_idx);
1477 while (true) {
1478 auto old = expected_top;
1479 for (const auto& [par2_idx, chl2_idx, dep2_idx] : active_dependencies) {
1480 if (dep2_idx != dep_idx && (expected_top[par2_idx] || expected_top[chl2_idx])) {
1481 expected_top.Set(par2_idx);
1482 expected_top.Set(chl2_idx);
1483 }
1484 }
1485 if (old == expected_top) break;
1486 }
1487 assert(!expected_top[chl_idx]);
1488 assert(dep_data.top_setinfo.transactions == expected_top);
1489 // Verify the top_info's feerate.
1490 assert(dep_data.top_setinfo.feerate ==
1491 depgraph.FeeRate(dep_data.top_setinfo.transactions));
1492 }
1493
1494 //
1495 // Verify m_suboptimal_chunks.
1496 //
1497 for (size_t i = 0; i < m_suboptimal_chunks.size(); ++i) {
1498 auto tx_idx = m_suboptimal_chunks[i];
1499 assert(m_transaction_idxs[tx_idx]);
1500 }
1501
1502 //
1503 // Verify m_nonminimal_chunks.
1504 //
1505 SetType nonminimal_reps;
1506 for (size_t i = 0; i < m_nonminimal_chunks.size(); ++i) {
1507 auto [chunk_rep, pivot, flags] = m_nonminimal_chunks[i];
1508 assert(m_tx_data[chunk_rep].chunk_rep == chunk_rep);
1509 assert(m_tx_data[pivot].chunk_rep == chunk_rep);
1510 assert(!nonminimal_reps[chunk_rep]);
1511 nonminimal_reps.Set(chunk_rep);
1512 }
1513 assert(nonminimal_reps.IsSubsetOf(m_transaction_idxs));
1514 }
1515};
1516
1534template<typename SetType>
1535std::tuple<std::vector<DepGraphIndex>, bool, uint64_t> Linearize(const DepGraph<SetType>& depgraph, uint64_t max_iterations, uint64_t rng_seed, std::span<const DepGraphIndex> old_linearization = {}, bool is_topological = true) noexcept
1536{
1538 SpanningForestState forest(depgraph, rng_seed);
1539 if (!old_linearization.empty()) {
1540 forest.LoadLinearization(old_linearization);
1541 if (!is_topological) forest.MakeTopological();
1542 } else {
1543 forest.MakeTopological();
1544 }
1545 // Make improvement steps to it until we hit the max_iterations limit, or an optimal result
1546 // is found.
1547 if (forest.GetCost() < max_iterations) {
1548 forest.StartOptimizing();
1549 do {
1550 if (!forest.OptimizeStep()) break;
1551 } while (forest.GetCost() < max_iterations);
1552 }
1553 // Make chunk minimization steps until we hit the max_iterations limit, or all chunks are
1554 // minimal.
1555 bool optimal = false;
1556 if (forest.GetCost() < max_iterations) {
1557 forest.StartMinimizing();
1558 do {
1559 if (!forest.MinimizeStep()) {
1560 optimal = true;
1561 break;
1562 }
1563 } while (forest.GetCost() < max_iterations);
1564 }
1565 return {forest.GetLinearization(), optimal, forest.GetCost()};
1566}
1567
1584template<typename SetType>
1585void PostLinearize(const DepGraph<SetType>& depgraph, std::span<DepGraphIndex> linearization)
1586{
1587 // This algorithm performs a number of passes (currently 2); the even ones operate from back to
1588 // front, the odd ones from front to back. Each results in an equal-or-better linearization
1589 // than the one started from.
1590 // - One pass in either direction guarantees that the resulting chunks are connected.
1591 // - Each direction corresponds to one shape of tree being linearized optimally (forward passes
1592 // guarantee this for graphs where each transaction has at most one child; backward passes
1593 // guarantee this for graphs where each transaction has at most one parent).
1594 // - Starting with a backward pass guarantees the moved-tree property.
1595 //
1596 // During an odd (forward) pass, the high-level operation is:
1597 // - Start with an empty list of groups L=[].
1598 // - For every transaction i in the old linearization, from front to back:
1599 // - Append a new group C=[i], containing just i, to the back of L.
1600 // - While L has at least one group before C, and the group immediately before C has feerate
1601 // lower than C:
1602 // - If C depends on P:
1603 // - Merge P into C, making C the concatenation of P+C, continuing with the combined C.
1604 // - Otherwise:
1605 // - Swap P with C, continuing with the now-moved C.
1606 // - The output linearization is the concatenation of the groups in L.
1607 //
1608 // During even (backward) passes, i iterates from the back to the front of the existing
1609 // linearization, and new groups are prepended instead of appended to the list L. To enable
1610 // more code reuse, both passes append groups, but during even passes the meanings of
1611 // parent/child, and of high/low feerate are reversed, and the final concatenation is reversed
1612 // on output.
1613 //
1614 // In the implementation below, the groups are represented by singly-linked lists (pointing
1615 // from the back to the front), which are themselves organized in a singly-linked circular
1616 // list (each group pointing to its predecessor, with a special sentinel group at the front
1617 // that points back to the last group).
1618 //
1619 // Information about transaction t is stored in entries[t + 1], while the sentinel is in
1620 // entries[0].
1621
1623 static constexpr DepGraphIndex SENTINEL{0};
1625 static constexpr DepGraphIndex NO_PREV_TX{0};
1626
1627
1629 struct TxEntry
1630 {
1633 DepGraphIndex prev_tx;
1634
1635 // The fields below are only used for transactions that are the last one in a group
1636 // (referred to as tail transactions below).
1637
1639 DepGraphIndex first_tx;
1642 DepGraphIndex prev_group;
1644 SetType group;
1646 SetType deps;
1648 FeeFrac feerate;
1649 };
1650
1651 // As an example, consider the state corresponding to the linearization [1,0,3,2], with
1652 // groups [1,0,3] and [2], in an odd pass. The linked lists would be:
1653 //
1654 // +-----+
1655 // 0<-P-- | 0 S | ---\ Legend:
1656 // +-----+ |
1657 // ^ | - digit in box: entries index
1658 // /--------------F---------+ G | (note: one more than tx value)
1659 // v \ | | - S: sentinel group
1660 // +-----+ +-----+ +-----+ | (empty feerate)
1661 // 0<-P-- | 2 | <--P-- | 1 | <--P-- | 4 T | | - T: tail transaction, contains
1662 // +-----+ +-----+ +-----+ | fields beyond prev_tv.
1663 // ^ | - P: prev_tx reference
1664 // G G - F: first_tx reference
1665 // | | - G: prev_group reference
1666 // +-----+ |
1667 // 0<-P-- | 3 T | <--/
1668 // +-----+
1669 // ^ |
1670 // \-F-/
1671 //
1672 // During an even pass, the diagram above would correspond to linearization [2,3,0,1], with
1673 // groups [2] and [3,0,1].
1674
1675 std::vector<TxEntry> entries(depgraph.PositionRange() + 1);
1676
1677 // Perform two passes over the linearization.
1678 for (int pass = 0; pass < 2; ++pass) {
1679 int rev = !(pass & 1);
1680 // Construct a sentinel group, identifying the start of the list.
1681 entries[SENTINEL].prev_group = SENTINEL;
1682 Assume(entries[SENTINEL].feerate.IsEmpty());
1683
1684 // Iterate over all elements in the existing linearization.
1685 for (DepGraphIndex i = 0; i < linearization.size(); ++i) {
1686 // Even passes are from back to front; odd passes from front to back.
1687 DepGraphIndex idx = linearization[rev ? linearization.size() - 1 - i : i];
1688 // Construct a new group containing just idx. In even passes, the meaning of
1689 // parent/child and high/low feerate are swapped.
1690 DepGraphIndex cur_group = idx + 1;
1691 entries[cur_group].group = SetType::Singleton(idx);
1692 entries[cur_group].deps = rev ? depgraph.Descendants(idx): depgraph.Ancestors(idx);
1693 entries[cur_group].feerate = depgraph.FeeRate(idx);
1694 if (rev) entries[cur_group].feerate.fee = -entries[cur_group].feerate.fee;
1695 entries[cur_group].prev_tx = NO_PREV_TX; // No previous transaction in group.
1696 entries[cur_group].first_tx = cur_group; // Transaction itself is first of group.
1697 // Insert the new group at the back of the groups linked list.
1698 entries[cur_group].prev_group = entries[SENTINEL].prev_group;
1699 entries[SENTINEL].prev_group = cur_group;
1700
1701 // Start merge/swap cycle.
1702 DepGraphIndex next_group = SENTINEL; // We inserted at the end, so next group is sentinel.
1703 DepGraphIndex prev_group = entries[cur_group].prev_group;
1704 // Continue as long as the current group has higher feerate than the previous one.
1705 while (entries[cur_group].feerate >> entries[prev_group].feerate) {
1706 // prev_group/cur_group/next_group refer to (the last transactions of) 3
1707 // consecutive entries in groups list.
1708 Assume(cur_group == entries[next_group].prev_group);
1709 Assume(prev_group == entries[cur_group].prev_group);
1710 // The sentinel has empty feerate, which is neither higher or lower than other
1711 // feerates. Thus, the while loop we are in here guarantees that cur_group and
1712 // prev_group are not the sentinel.
1713 Assume(cur_group != SENTINEL);
1714 Assume(prev_group != SENTINEL);
1715 if (entries[cur_group].deps.Overlaps(entries[prev_group].group)) {
1716 // There is a dependency between cur_group and prev_group; merge prev_group
1717 // into cur_group. The group/deps/feerate fields of prev_group remain unchanged
1718 // but become unused.
1719 entries[cur_group].group |= entries[prev_group].group;
1720 entries[cur_group].deps |= entries[prev_group].deps;
1721 entries[cur_group].feerate += entries[prev_group].feerate;
1722 // Make the first of the current group point to the tail of the previous group.
1723 entries[entries[cur_group].first_tx].prev_tx = prev_group;
1724 // The first of the previous group becomes the first of the newly-merged group.
1725 entries[cur_group].first_tx = entries[prev_group].first_tx;
1726 // The previous group becomes whatever group was before the former one.
1727 prev_group = entries[prev_group].prev_group;
1728 entries[cur_group].prev_group = prev_group;
1729 } else {
1730 // There is no dependency between cur_group and prev_group; swap them.
1731 DepGraphIndex preprev_group = entries[prev_group].prev_group;
1732 // If PP, P, C, N were the old preprev, prev, cur, next groups, then the new
1733 // layout becomes [PP, C, P, N]. Update prev_groups to reflect that order.
1734 entries[next_group].prev_group = prev_group;
1735 entries[prev_group].prev_group = cur_group;
1736 entries[cur_group].prev_group = preprev_group;
1737 // The current group remains the same, but the groups before/after it have
1738 // changed.
1739 next_group = prev_group;
1740 prev_group = preprev_group;
1741 }
1742 }
1743 }
1744
1745 // Convert the entries back to linearization (overwriting the existing one).
1746 DepGraphIndex cur_group = entries[0].prev_group;
1747 DepGraphIndex done = 0;
1748 while (cur_group != SENTINEL) {
1749 DepGraphIndex cur_tx = cur_group;
1750 // Traverse the transactions of cur_group (from back to front), and write them in the
1751 // same order during odd passes, and reversed (front to back) in even passes.
1752 if (rev) {
1753 do {
1754 *(linearization.begin() + (done++)) = cur_tx - 1;
1755 cur_tx = entries[cur_tx].prev_tx;
1756 } while (cur_tx != NO_PREV_TX);
1757 } else {
1758 do {
1759 *(linearization.end() - (++done)) = cur_tx - 1;
1760 cur_tx = entries[cur_tx].prev_tx;
1761 } while (cur_tx != NO_PREV_TX);
1762 }
1763 cur_group = entries[cur_group].prev_group;
1764 }
1765 Assume(done == linearization.size());
1766 }
1767}
1768
1769} // namespace cluster_linearize
1770
1771#endif // BITCOIN_CLUSTER_LINEARIZE_H
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.
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.
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...
uint32_t TxIdx
Data type to represent indexing into m_tx_data.
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.
SpanningForestState(const DepGraph< SetType > &depgraph, uint64_t rng_seed) noexcept
Construct a spanning forest for the given DepGraph, with every transaction in its own chunk (not topo...
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.
std::vector< DepGraphIndex > GetLinearization() noexcept
Construct a topologically-valid linearization from the current forest state.
bool MinimizeStep() noexcept
Try to reduce a chunk's size.
uint32_t DepIdx
Data type to represent indexing into m_dep_data.
SetType m_transaction_idxs
The set of all TxIdx's of transactions in the cluster indexing into m_tx_data.
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.
std::tuple< std::vector< DepGraphIndex >, bool, uint64_t > Linearize(const DepGraph< SetType > &depgraph, uint64_t max_iterations, uint64_t rng_seed, std::span< const DepGraphIndex > old_linearization={}, bool is_topological=true) noexcept
Find or improve a linearization for a cluster.
std::vector< FeeFrac > ChunkLinearization(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the feerates of the chunks of linearization.
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
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())