Bitcoin Core 28.0.0
P2P Digital Currency
Loading...
Searching...
No Matches
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 <numeric>
10#include <optional>
11#include <stdint.h>
12#include <vector>
13#include <utility>
14
15#include <random.h>
16#include <span.h>
17#include <util/feefrac.h>
18#include <util/vecdeque.h>
19
21
27template<typename SetType>
28using Cluster = std::vector<std::pair<FeeFrac, SetType>>;
29
31using ClusterIndex = uint32_t;
32
35template<typename SetType>
37{
39 struct Entry
40 {
44 SetType ancestors;
46 SetType descendants;
47
49 friend bool operator==(const Entry&, const Entry&) noexcept = default;
50
52 Entry() noexcept = default;
54 Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {}
55 };
56
58 std::vector<Entry> entries;
59
60public:
62 friend bool operator==(const DepGraph&, const DepGraph&) noexcept = default;
63
64 // Default constructors.
65 DepGraph() noexcept = default;
66 DepGraph(const DepGraph&) noexcept = default;
67 DepGraph(DepGraph&&) noexcept = default;
68 DepGraph& operator=(const DepGraph&) noexcept = default;
69 DepGraph& operator=(DepGraph&&) noexcept = default;
70
75 explicit DepGraph(ClusterIndex ntx) noexcept
76 {
77 Assume(ntx <= SetType::Size());
78 entries.resize(ntx);
79 for (ClusterIndex i = 0; i < ntx; ++i) {
80 entries[i].ancestors = SetType::Singleton(i);
81 entries[i].descendants = SetType::Singleton(i);
82 }
83 }
84
89 explicit DepGraph(const Cluster<SetType>& cluster) noexcept : entries(cluster.size())
90 {
91 for (ClusterIndex i = 0; i < cluster.size(); ++i) {
92 // Fill in fee and size.
93 entries[i].feerate = cluster[i].first;
94 // Fill in direct parents as ancestors.
95 entries[i].ancestors = cluster[i].second;
96 // Make sure transactions are ancestors of themselves.
97 entries[i].ancestors.Set(i);
98 }
99
100 // Propagate ancestor information.
101 for (ClusterIndex i = 0; i < entries.size(); ++i) {
102 // At this point, entries[a].ancestors[b] is true iff b is an ancestor of a and there
103 // is a path from a to b through the subgraph consisting of {a, b} union
104 // {0, 1, ..., (i-1)}.
105 SetType to_merge = entries[i].ancestors;
106 for (ClusterIndex j = 0; j < entries.size(); ++j) {
107 if (entries[j].ancestors[i]) {
108 entries[j].ancestors |= to_merge;
109 }
110 }
111 }
112
113 // Fill in descendant information by transposing the ancestor information.
114 for (ClusterIndex i = 0; i < entries.size(); ++i) {
115 for (auto j : entries[i].ancestors) {
116 entries[j].descendants.Set(i);
117 }
118 }
119 }
120
122 auto TxCount() const noexcept { return entries.size(); }
124 const FeeFrac& FeeRate(ClusterIndex i) const noexcept { return entries[i].feerate; }
126 FeeFrac& FeeRate(ClusterIndex i) noexcept { return entries[i].feerate; }
128 const SetType& Ancestors(ClusterIndex i) const noexcept { return entries[i].ancestors; }
130 const SetType& Descendants(ClusterIndex i) const noexcept { return entries[i].descendants; }
131
137 ClusterIndex AddTransaction(const FeeFrac& feefrac) noexcept
138 {
139 Assume(TxCount() < SetType::Size());
140 ClusterIndex new_idx = TxCount();
141 entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
142 return new_idx;
143 }
144
149 void AddDependency(ClusterIndex parent, ClusterIndex child) noexcept
150 {
151 // Bail out if dependency is already implied.
152 if (entries[child].ancestors[parent]) return;
153 // To each ancestor of the parent, add as descendants the descendants of the child.
154 const auto& chl_des = entries[child].descendants;
155 for (auto anc_of_par : Ancestors(parent)) {
156 entries[anc_of_par].descendants |= chl_des;
157 }
158 // To each descendant of the child, add as ancestors the ancestors of the parent.
159 const auto& par_anc = entries[parent].ancestors;
160 for (auto dec_of_chl : Descendants(child)) {
161 entries[dec_of_chl].ancestors |= par_anc;
162 }
163 }
164
169 FeeFrac FeeRate(const SetType& elems) const noexcept
170 {
171 FeeFrac ret;
172 for (auto pos : elems) ret += entries[pos].feerate;
173 return ret;
174 }
175
188 SetType FindConnectedComponent(const SetType& todo) const noexcept
189 {
190 if (todo.None()) return todo;
191 auto to_add = SetType::Singleton(todo.First());
192 SetType ret;
193 do {
194 SetType old = ret;
195 for (auto add : to_add) {
196 ret |= Descendants(add);
197 ret |= Ancestors(add);
198 }
199 ret &= todo;
200 to_add = ret - old;
201 } while (to_add.Any());
202 return ret;
203 }
204
209 bool IsConnected(const SetType& subset) const noexcept
210 {
211 return FindConnectedComponent(subset) == subset;
212 }
213
218 bool IsConnected() const noexcept { return IsConnected(SetType::Fill(TxCount())); }
219
224 void AppendTopo(std::vector<ClusterIndex>& list, const SetType& select) const noexcept
225 {
226 ClusterIndex old_len = list.size();
227 for (auto i : select) list.push_back(i);
228 std::sort(list.begin() + old_len, list.end(), [&](ClusterIndex a, ClusterIndex b) noexcept {
229 const auto a_anc_count = entries[a].ancestors.Count();
230 const auto b_anc_count = entries[b].ancestors.Count();
231 if (a_anc_count != b_anc_count) return a_anc_count < b_anc_count;
232 return a < b;
233 });
234 }
235};
236
238template<typename SetType>
240{
245
247 SetInfo() noexcept = default;
248
250 SetInfo(const SetType& txn, const FeeFrac& fr) noexcept : transactions(txn), feerate(fr) {}
251
253 explicit SetInfo(const DepGraph<SetType>& depgraph, ClusterIndex pos) noexcept :
254 transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {}
255
257 explicit SetInfo(const DepGraph<SetType>& depgraph, const SetType& txn) noexcept :
258 transactions(txn), feerate(depgraph.FeeRate(txn)) {}
259
261 SetInfo& operator|=(const SetInfo& other) noexcept
262 {
263 Assume(!transactions.Overlaps(other.transactions));
264 transactions |= other.transactions;
265 feerate += other.feerate;
266 return *this;
267 }
268
271 [[nodiscard]] SetInfo Add(const DepGraph<SetType>& depgraph, const SetType& txn) const noexcept
272 {
273 return {transactions | txn, feerate + depgraph.FeeRate(txn - transactions)};
274 }
275
277 friend void swap(SetInfo& a, SetInfo& b) noexcept
278 {
279 swap(a.transactions, b.transactions);
280 swap(a.feerate, b.feerate);
281 }
282
284 friend bool operator==(const SetInfo&, const SetInfo&) noexcept = default;
285};
286
288template<typename SetType>
289std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, Span<const ClusterIndex> linearization) noexcept
290{
291 std::vector<FeeFrac> ret;
292 for (ClusterIndex i : linearization) {
294 auto new_chunk = depgraph.FeeRate(i);
295 // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
296 while (!ret.empty() && new_chunk >> ret.back()) {
297 new_chunk += ret.back();
298 ret.pop_back();
299 }
300 // Actually move that new chunk into the chunking.
301 ret.push_back(std::move(new_chunk));
302 }
303 return ret;
304}
305
307template<typename SetType>
309{
312
315
317 std::vector<SetInfo<SetType>> m_chunks;
318
321
323 SetType m_todo;
324
326 void BuildChunks() noexcept
327 {
328 // Caller must clear m_chunks.
329 Assume(m_chunks.empty());
330
331 // Chop off the initial part of m_linearization that is already done.
334 }
335
336 // Iterate over the remaining entries in m_linearization. This is effectively the same
337 // algorithm as ChunkLinearization, but supports skipping parts of the linearization and
338 // keeps track of the sets themselves instead of just their feerates.
339 for (auto idx : m_linearization) {
340 if (!m_todo[idx]) continue;
341 // Start with an initial chunk containing just element idx.
342 SetInfo add(m_depgraph, idx);
343 // Absorb existing final chunks into add while they have lower feerate.
344 while (!m_chunks.empty() && add.feerate >> m_chunks.back().feerate) {
345 add |= m_chunks.back();
346 m_chunks.pop_back();
347 }
348 // Remember new chunk.
349 m_chunks.push_back(std::move(add));
350 }
351 }
352
353public:
356 m_depgraph(depgraph), m_linearization(lin)
357 {
358 // Mark everything in lin as todo still.
359 for (auto i : m_linearization) m_todo.Set(i);
360 // Compute the initial chunking.
361 m_chunks.reserve(depgraph.TxCount());
362 BuildChunks();
363 }
364
366 ClusterIndex NumChunksLeft() const noexcept { return m_chunks.size() - m_chunks_skip; }
367
369 const SetInfo<SetType>& GetChunk(ClusterIndex n) const noexcept
370 {
371 Assume(n + m_chunks_skip < m_chunks.size());
372 return m_chunks[n + m_chunks_skip];
373 }
374
376 void MarkDone(SetType subset) noexcept
377 {
378 Assume(subset.Any());
379 Assume(subset.IsSubsetOf(m_todo));
380 m_todo -= subset;
381 if (GetChunk(0).transactions == subset) {
382 // If the newly done transactions exactly match the first chunk of the remainder of
383 // the linearization, we do not need to rechunk; just remember to skip one
384 // additional chunk.
386 // With subset marked done, some prefix of m_linearization will be done now. How long
387 // that prefix is depends on how many done elements were interspersed with subset,
388 // but at least as many transactions as there are in subset.
389 m_linearization = m_linearization.subspan(subset.Count());
390 } else {
391 // Otherwise rechunk what remains of m_linearization.
392 m_chunks.clear();
393 m_chunks_skip = 0;
394 BuildChunks();
395 }
396 }
397
408 {
409 Assume(subset.transactions.IsSubsetOf(m_todo));
410 SetInfo<SetType> accumulator;
411 // Iterate over all chunks of the remaining linearization.
412 for (ClusterIndex i = 0; i < NumChunksLeft(); ++i) {
413 // Find what (if any) intersection the chunk has with subset.
414 const SetType to_add = GetChunk(i).transactions & subset.transactions;
415 if (to_add.Any()) {
416 // If adding that to accumulator makes us hit all of subset, we are done as no
417 // shorter intersection with higher/equal feerate exists.
418 accumulator.transactions |= to_add;
419 if (accumulator.transactions == subset.transactions) break;
420 // Otherwise update the accumulator feerate.
421 accumulator.feerate += m_depgraph.FeeRate(to_add);
422 // If that does result in something better, or something with the same feerate but
423 // smaller, return that. Even if a longer, higher-feerate intersection exists, it
424 // does not hurt to return the shorter one (the remainder of the longer intersection
425 // will generally be found in the next call to Intersect, but even if not, it is not
426 // required for the improvement guarantee this function makes).
427 if (!(accumulator.feerate << subset.feerate)) return accumulator;
428 }
429 }
430 return subset;
431 }
432};
433
443template<typename SetType>
445{
449 SetType m_todo;
451 std::vector<FeeFrac> m_ancestor_set_feerates;
452
453public:
459 m_depgraph(depgraph),
460 m_todo{SetType::Fill(depgraph.TxCount())},
461 m_ancestor_set_feerates(depgraph.TxCount())
462 {
463 // Precompute ancestor-set feerates.
464 for (ClusterIndex i = 0; i < depgraph.TxCount(); ++i) {
466 SetType anc_to_add = m_depgraph.Ancestors(i);
467 FeeFrac anc_feerate;
468 // Reuse accumulated feerate from first ancestor, if usable.
469 Assume(anc_to_add.Any());
470 ClusterIndex first = anc_to_add.First();
471 if (first < i) {
472 anc_feerate = m_ancestor_set_feerates[first];
473 Assume(!anc_feerate.IsEmpty());
474 anc_to_add -= m_depgraph.Ancestors(first);
475 }
476 // Add in other ancestors (which necessarily include i itself).
477 Assume(anc_to_add[i]);
478 anc_feerate += m_depgraph.FeeRate(anc_to_add);
479 // Store the result.
480 m_ancestor_set_feerates[i] = anc_feerate;
481 }
482 }
483
490 void MarkDone(SetType select) noexcept
491 {
492 Assume(select.Any());
493 Assume(select.IsSubsetOf(m_todo));
494 m_todo -= select;
495 for (auto i : select) {
496 auto feerate = m_depgraph.FeeRate(i);
497 for (auto j : m_depgraph.Descendants(i) & m_todo) {
498 m_ancestor_set_feerates[j] -= feerate;
499 }
500 }
501 }
502
504 bool AllDone() const noexcept
505 {
506 return m_todo.None();
507 }
508
515 {
516 Assume(!AllDone());
517 std::optional<ClusterIndex> best;
518 for (auto i : m_todo) {
519 if (best.has_value()) {
520 Assume(!m_ancestor_set_feerates[i].IsEmpty());
521 if (!(m_ancestor_set_feerates[i] > m_ancestor_set_feerates[*best])) continue;
522 }
523 best = i;
524 }
525 Assume(best.has_value());
526 return {m_depgraph.Ancestors(*best) & m_todo, m_ancestor_set_feerates[*best]};
527 }
528};
529
539template<typename SetType>
541{
547 SetType m_todo;
548
549public:
557 SearchCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND, uint64_t rng_seed) noexcept :
558 m_rng(rng_seed),
559 m_depgraph(depgraph),
560 m_todo(SetType::Fill(depgraph.TxCount())) {}
561
563 bool AllDone() const noexcept
564 {
565 return m_todo.None();
566 }
567
585 std::pair<SetInfo<SetType>, uint64_t> FindCandidateSet(uint64_t max_iterations, SetInfo<SetType> best) noexcept
586 {
587 Assume(!AllDone());
588
590 struct WorkItem
591 {
598 SetType und;
599
601 WorkItem(SetInfo<SetType>&& i, SetType&& u) noexcept :
602 inc(std::move(i)), und(std::move(u)) {}
603
605 void Swap(WorkItem& other) noexcept
606 {
607 swap(inc, other.inc);
608 swap(und, other.und);
609 }
610 };
611
613 VecDeque<WorkItem> queue;
614 queue.reserve(std::max<size_t>(256, 2 * m_todo.Count()));
615
616 // Create an initial entry with m_todo as undecided. Also use it as best if not provided,
617 // so that during the work processing loop below, and during the add_fn/split_fn calls, we
618 // do not need to deal with the best=empty case.
619 if (best.feerate.IsEmpty()) best = SetInfo(m_depgraph, m_todo);
620 queue.emplace_back(SetInfo<SetType>{}, SetType{m_todo});
621
623 uint64_t iterations_left = max_iterations;
624
631 auto add_fn = [&](SetInfo<SetType> inc, SetType und) noexcept {
632 if (!inc.feerate.IsEmpty()) {
633 // If inc's feerate is better than best's, remember it as our new best.
634 if (inc.feerate > best.feerate) {
635 best = inc;
636 }
637 } else {
638 Assume(inc.transactions.None());
639 }
640
641 // Make sure there are undecided transactions left to split on.
642 if (und.None()) return;
643
644 // Actually construct a new work item on the queue. Due to the switch to DFS when queue
645 // space runs out (see below), we know that no reallocation of the queue should ever
646 // occur.
647 Assume(queue.size() < queue.capacity());
648 queue.emplace_back(std::move(inc), std::move(und));
649 };
650
654 auto split_fn = [&](WorkItem&& elem) noexcept {
655 // Any queue element must have undecided transactions left, otherwise there is nothing
656 // to explore anymore.
657 Assume(elem.und.Any());
658 // The included and undecided set are all subsets of m_todo.
659 Assume(elem.inc.transactions.IsSubsetOf(m_todo) && elem.und.IsSubsetOf(m_todo));
660 // Included transactions cannot be undecided.
661 Assume(!elem.inc.transactions.Overlaps(elem.und));
662
663 // Pick the first undecided transaction as the one to split on.
664 const ClusterIndex split = elem.und.First();
665
666 // Add a work item corresponding to exclusion of the split transaction.
667 const auto& desc = m_depgraph.Descendants(split);
668 add_fn(/*inc=*/elem.inc,
669 /*und=*/elem.und - desc);
670
671 // Add a work item corresponding to inclusion of the split transaction.
672 const auto anc = m_depgraph.Ancestors(split) & m_todo;
673 add_fn(/*inc=*/elem.inc.Add(m_depgraph, anc),
674 /*und=*/elem.und - anc);
675
676 // Account for the performed split.
677 --iterations_left;
678 };
679
680 // Work processing loop.
681 //
682 // New work items are always added at the back of the queue, but items to process use a
683 // hybrid approach where they can be taken from the front or the back.
684 //
685 // Depth-first search (DFS) corresponds to always taking from the back of the queue. This
686 // is very memory-efficient (linear in the number of transactions). Breadth-first search
687 // (BFS) corresponds to always taking from the front, which potentially uses more memory
688 // (up to exponential in the transaction count), but seems to work better in practice.
689 //
690 // The approach here combines the two: use BFS (plus random swapping) until the queue grows
691 // too large, at which point we temporarily switch to DFS until the size shrinks again.
692 while (!queue.empty()) {
693 // Randomly swap the first two items to randomize the search order.
694 if (queue.size() > 1 && m_rng.randbool()) {
695 queue[0].Swap(queue[1]);
696 }
697
698 // Processing the first queue item, and then using DFS for everything it gives rise to,
699 // may increase the queue size by the number of undecided elements in there, minus 1
700 // for the first queue item being removed. Thus, only when that pushes the queue over
701 // its capacity can we not process from the front (BFS), and should we use DFS.
702 while (queue.size() - 1 + queue.front().und.Count() > queue.capacity()) {
703 if (!iterations_left) break;
704 auto elem = queue.back();
705 queue.pop_back();
706 split_fn(std::move(elem));
707 }
708
709 // Process one entry from the front of the queue (BFS exploration)
710 if (!iterations_left) break;
711 auto elem = queue.front();
712 queue.pop_front();
713 split_fn(std::move(elem));
714 }
715
716 // Return the found best set and the number of iterations performed.
717 return {std::move(best), max_iterations - iterations_left};
718 }
719
724 void MarkDone(const SetType& done) noexcept
725 {
726 Assume(done.Any());
727 Assume(done.IsSubsetOf(m_todo));
728 m_todo -= done;
729 }
730};
731
749template<typename SetType>
750std::pair<std::vector<ClusterIndex>, bool> Linearize(const DepGraph<SetType>& depgraph, uint64_t max_iterations, uint64_t rng_seed, Span<const ClusterIndex> old_linearization = {}) noexcept
751{
752 Assume(old_linearization.empty() || old_linearization.size() == depgraph.TxCount());
753 if (depgraph.TxCount() == 0) return {{}, true};
754
755 uint64_t iterations_left = max_iterations;
756 std::vector<ClusterIndex> linearization;
757
758 AncestorCandidateFinder anc_finder(depgraph);
759 SearchCandidateFinder src_finder(depgraph, rng_seed);
760 linearization.reserve(depgraph.TxCount());
761 bool optimal = true;
762
764 LinearizationChunking old_chunking(depgraph, old_linearization);
765
766 while (true) {
767 // Find the highest-feerate prefix of the remainder of old_linearization.
768 SetInfo<SetType> best_prefix;
769 if (old_chunking.NumChunksLeft()) best_prefix = old_chunking.GetChunk(0);
770
771 // Then initialize best to be either the best remaining ancestor set, or the first chunk.
772 auto best = anc_finder.FindCandidateSet();
773 if (!best_prefix.feerate.IsEmpty() && best_prefix.feerate >= best.feerate) best = best_prefix;
774
775 // Invoke bounded search to update best, with up to half of our remaining iterations as
776 // limit.
777 uint64_t max_iterations_now = (iterations_left + 1) / 2;
778 uint64_t iterations_done_now = 0;
779 std::tie(best, iterations_done_now) = src_finder.FindCandidateSet(max_iterations_now, best);
780 iterations_left -= iterations_done_now;
781
782 if (iterations_done_now == max_iterations_now) {
783 optimal = false;
784 // If the search result is not (guaranteed to be) optimal, run intersections to make
785 // sure we don't pick something that makes us unable to reach further diagram points
786 // of the old linearization.
787 if (old_chunking.NumChunksLeft() > 0) {
788 best = old_chunking.IntersectPrefixes(best);
789 }
790 }
791
792 // Add to output in topological order.
793 depgraph.AppendTopo(linearization, best.transactions);
794
795 // Update state to reflect best is no longer to be linearized.
796 anc_finder.MarkDone(best.transactions);
797 if (anc_finder.AllDone()) break;
798 src_finder.MarkDone(best.transactions);
799 if (old_chunking.NumChunksLeft() > 0) {
800 old_chunking.MarkDone(best.transactions);
801 }
802 }
803
804 return {std::move(linearization), optimal};
805}
806
823template<typename SetType>
824void PostLinearize(const DepGraph<SetType>& depgraph, Span<ClusterIndex> linearization)
825{
826 // This algorithm performs a number of passes (currently 2); the even ones operate from back to
827 // front, the odd ones from front to back. Each results in an equal-or-better linearization
828 // than the one started from.
829 // - One pass in either direction guarantees that the resulting chunks are connected.
830 // - Each direction corresponds to one shape of tree being linearized optimally (forward passes
831 // guarantee this for graphs where each transaction has at most one child; backward passes
832 // guarantee this for graphs where each transaction has at most one parent).
833 // - Starting with a backward pass guarantees the moved-tree property.
834 //
835 // During an odd (forward) pass, the high-level operation is:
836 // - Start with an empty list of groups L=[].
837 // - For every transaction i in the old linearization, from front to back:
838 // - Append a new group C=[i], containing just i, to the back of L.
839 // - While L has at least one group before C, and the group immediately before C has feerate
840 // lower than C:
841 // - If C depends on P:
842 // - Merge P into C, making C the concatenation of P+C, continuing with the combined C.
843 // - Otherwise:
844 // - Swap P with C, continuing with the now-moved C.
845 // - The output linearization is the concatenation of the groups in L.
846 //
847 // During even (backward) passes, i iterates from the back to the front of the existing
848 // linearization, and new groups are prepended instead of appended to the list L. To enable
849 // more code reuse, both passes append groups, but during even passes the meanings of
850 // parent/child, and of high/low feerate are reversed, and the final concatenation is reversed
851 // on output.
852 //
853 // In the implementation below, the groups are represented by singly-linked lists (pointing
854 // from the back to the front), which are themselves organized in a singly-linked circular
855 // list (each group pointing to its predecessor, with a special sentinel group at the front
856 // that points back to the last group).
857 //
858 // Information about transaction t is stored in entries[t + 1], while the sentinel is in
859 // entries[0].
860
862 static constexpr ClusterIndex SENTINEL{0};
864 static constexpr ClusterIndex NO_PREV_TX{0};
865
866
868 struct TxEntry
869 {
872 ClusterIndex prev_tx;
873
874 // The fields below are only used for transactions that are the last one in a group
875 // (referred to as tail transactions below).
876
878 ClusterIndex first_tx;
881 ClusterIndex prev_group;
883 SetType group;
885 SetType deps;
887 FeeFrac feerate;
888 };
889
890 // As an example, consider the state corresponding to the linearization [1,0,3,2], with
891 // groups [1,0,3] and [2], in an odd pass. The linked lists would be:
892 //
893 // +-----+
894 // 0<-P-- | 0 S | ---\ Legend:
895 // +-----+ |
896 // ^ | - digit in box: entries index
897 // /--------------F---------+ G | (note: one more than tx value)
898 // v \ | | - S: sentinel group
899 // +-----+ +-----+ +-----+ | (empty feerate)
900 // 0<-P-- | 2 | <--P-- | 1 | <--P-- | 4 T | | - T: tail transaction, contains
901 // +-----+ +-----+ +-----+ | fields beyond prev_tv.
902 // ^ | - P: prev_tx reference
903 // G G - F: first_tx reference
904 // | | - G: prev_group reference
905 // +-----+ |
906 // 0<-P-- | 3 T | <--/
907 // +-----+
908 // ^ |
909 // \-F-/
910 //
911 // During an even pass, the diagram above would correspond to linearization [2,3,0,1], with
912 // groups [2] and [3,0,1].
913
914 std::vector<TxEntry> entries(linearization.size() + 1);
915
916 // Perform two passes over the linearization.
917 for (int pass = 0; pass < 2; ++pass) {
918 int rev = !(pass & 1);
919 // Construct a sentinel group, identifying the start of the list.
920 entries[SENTINEL].prev_group = SENTINEL;
921 Assume(entries[SENTINEL].feerate.IsEmpty());
922
923 // Iterate over all elements in the existing linearization.
924 for (ClusterIndex i = 0; i < linearization.size(); ++i) {
925 // Even passes are from back to front; odd passes from front to back.
926 ClusterIndex idx = linearization[rev ? linearization.size() - 1 - i : i];
927 // Construct a new group containing just idx. In even passes, the meaning of
928 // parent/child and high/low feerate are swapped.
929 ClusterIndex cur_group = idx + 1;
930 entries[cur_group].group = SetType::Singleton(idx);
931 entries[cur_group].deps = rev ? depgraph.Descendants(idx): depgraph.Ancestors(idx);
932 entries[cur_group].feerate = depgraph.FeeRate(idx);
933 if (rev) entries[cur_group].feerate.fee = -entries[cur_group].feerate.fee;
934 entries[cur_group].prev_tx = NO_PREV_TX; // No previous transaction in group.
935 entries[cur_group].first_tx = cur_group; // Transaction itself is first of group.
936 // Insert the new group at the back of the groups linked list.
937 entries[cur_group].prev_group = entries[SENTINEL].prev_group;
938 entries[SENTINEL].prev_group = cur_group;
939
940 // Start merge/swap cycle.
941 ClusterIndex next_group = SENTINEL; // We inserted at the end, so next group is sentinel.
942 ClusterIndex prev_group = entries[cur_group].prev_group;
943 // Continue as long as the current group has higher feerate than the previous one.
944 while (entries[cur_group].feerate >> entries[prev_group].feerate) {
945 // prev_group/cur_group/next_group refer to (the last transactions of) 3
946 // consecutive entries in groups list.
947 Assume(cur_group == entries[next_group].prev_group);
948 Assume(prev_group == entries[cur_group].prev_group);
949 // The sentinel has empty feerate, which is neither higher or lower than other
950 // feerates. Thus, the while loop we are in here guarantees that cur_group and
951 // prev_group are not the sentinel.
952 Assume(cur_group != SENTINEL);
953 Assume(prev_group != SENTINEL);
954 if (entries[cur_group].deps.Overlaps(entries[prev_group].group)) {
955 // There is a dependency between cur_group and prev_group; merge prev_group
956 // into cur_group. The group/deps/feerate fields of prev_group remain unchanged
957 // but become unused.
958 entries[cur_group].group |= entries[prev_group].group;
959 entries[cur_group].deps |= entries[prev_group].deps;
960 entries[cur_group].feerate += entries[prev_group].feerate;
961 // Make the first of the current group point to the tail of the previous group.
962 entries[entries[cur_group].first_tx].prev_tx = prev_group;
963 // The first of the previous group becomes the first of the newly-merged group.
964 entries[cur_group].first_tx = entries[prev_group].first_tx;
965 // The previous group becomes whatever group was before the former one.
966 prev_group = entries[prev_group].prev_group;
967 entries[cur_group].prev_group = prev_group;
968 } else {
969 // There is no dependency between cur_group and prev_group; swap them.
970 ClusterIndex preprev_group = entries[prev_group].prev_group;
971 // If PP, P, C, N were the old preprev, prev, cur, next groups, then the new
972 // layout becomes [PP, C, P, N]. Update prev_groups to reflect that order.
973 entries[next_group].prev_group = prev_group;
974 entries[prev_group].prev_group = cur_group;
975 entries[cur_group].prev_group = preprev_group;
976 // The current group remains the same, but the groups before/after it have
977 // changed.
978 next_group = prev_group;
979 prev_group = preprev_group;
980 }
981 }
982 }
983
984 // Convert the entries back to linearization (overwriting the existing one).
985 ClusterIndex cur_group = entries[0].prev_group;
986 ClusterIndex done = 0;
987 while (cur_group != SENTINEL) {
988 ClusterIndex cur_tx = cur_group;
989 // Traverse the transactions of cur_group (from back to front), and write them in the
990 // same order during odd passes, and reversed (front to back) in even passes.
991 if (rev) {
992 do {
993 *(linearization.begin() + (done++)) = cur_tx - 1;
994 cur_tx = entries[cur_tx].prev_tx;
995 } while (cur_tx != NO_PREV_TX);
996 } else {
997 do {
998 *(linearization.end() - (++done)) = cur_tx - 1;
999 cur_tx = entries[cur_tx].prev_tx;
1000 } while (cur_tx != NO_PREV_TX);
1001 }
1002 cur_group = entries[cur_group].prev_group;
1003 }
1004 Assume(done == linearization.size());
1005 }
1006}
1007
1012template<typename SetType>
1014{
1015 Assume(lin1.size() == depgraph.TxCount());
1016 Assume(lin2.size() == depgraph.TxCount());
1017
1019 LinearizationChunking chunking1(depgraph, lin1), chunking2(depgraph, lin2);
1021 std::vector<ClusterIndex> ret;
1022 if (depgraph.TxCount() == 0) return ret;
1023 ret.reserve(depgraph.TxCount());
1024
1025 while (true) {
1026 // As long as we are not done, both linearizations must have chunks left.
1027 Assume(chunking1.NumChunksLeft() > 0);
1028 Assume(chunking2.NumChunksLeft() > 0);
1029 // Find the set to output by taking the best remaining chunk, and then intersecting it with
1030 // prefixes of remaining chunks of the other linearization.
1031 SetInfo<SetType> best;
1032 const auto& lin1_firstchunk = chunking1.GetChunk(0);
1033 const auto& lin2_firstchunk = chunking2.GetChunk(0);
1034 if (lin2_firstchunk.feerate >> lin1_firstchunk.feerate) {
1035 best = chunking1.IntersectPrefixes(lin2_firstchunk);
1036 } else {
1037 best = chunking2.IntersectPrefixes(lin1_firstchunk);
1038 }
1039 // Append the result to the output and mark it as done.
1040 depgraph.AppendTopo(ret, best.transactions);
1041 chunking1.MarkDone(best.transactions);
1042 if (chunking1.NumChunksLeft() == 0) break;
1043 chunking2.MarkDone(best.transactions);
1044 }
1045
1046 Assume(ret.size() == depgraph.TxCount());
1047 return ret;
1048}
1049
1050} // namespace cluster_linearize
1051
1052#endif // BITCOIN_CLUSTER_LINEARIZE_H
#define LIFETIMEBOUND
Definition attributes.h:16
int ret
#define Assume(val)
Assume is the identity function.
Definition check.h:89
xoroshiro128++ PRNG.
Definition random.h:416
bool randbool() noexcept
Generate a random boolean.
Definition random.h:316
A Span is an object that can refer to a contiguous sequence of objects.
Definition span.h:98
constexpr std::size_t size() const noexcept
Definition span.h:187
CONSTEXPR_IF_NOT_DEBUG Span< C > subspan(std::size_t offset) const noexcept
Definition span.h:195
constexpr C * begin() const noexcept
Definition span.h:175
constexpr bool empty() const noexcept
Definition span.h:189
constexpr C * end() const noexcept
Definition span.h:176
CONSTEXPR_IF_NOT_DEBUG C & front() const noexcept
Definition span.h:177
Data structure largely mimicking std::deque, but using single preallocated ring buffer.
Definition vecdeque.h:25
bool empty() const noexcept
Test whether the contents of this deque is empty.
Definition vecdeque.h:310
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 pop_back()
Remove the last element of the deque.
Definition vecdeque.h:260
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
size_t capacity() const noexcept
Get the capacity of this deque (maximum size it can have without reallocating).
Definition vecdeque.h:314
T & back() noexcept
Get a mutable reference to the last element of the deque.
Definition vecdeque.h:282
Class encapsulating the state needed to find the best remaining ancestor set.
void MarkDone(SetType select) noexcept
Remove a set of transactions from the set of to-be-linearized ones.
SetInfo< SetType > FindCandidateSet() const noexcept
Find the best (highest-feerate, smallest among those in case of a tie) ancestor set among the remaini...
const DepGraph< SetType > & m_depgraph
Internal dependency graph.
AncestorCandidateFinder(const DepGraph< SetType > &depgraph LIFETIMEBOUND) noexcept
Construct an AncestorCandidateFinder for a given cluster.
std::vector< FeeFrac > m_ancestor_set_feerates
Precomputed ancestor-set feerates (only kept up-to-date for indices in m_todo).
SetType m_todo
Which transaction are left to include.
bool AllDone() const noexcept
Check whether any unlinearized transactions remain.
Data structure that holds a transaction graph's preprocessed data (fee, size, ancestors,...
void AppendTopo(std::vector< ClusterIndex > &list, const SetType &select) const noexcept
Append the entries of select to list in a topologically valid order.
void AddDependency(ClusterIndex parent, ClusterIndex child) noexcept
Modify this transaction graph, adding a dependency between a specified parent and child.
const FeeFrac & FeeRate(ClusterIndex i) const noexcept
Get the feerate of a given transaction i.
friend bool operator==(const DepGraph &, const DepGraph &) noexcept=default
Equality operator (primarily for testing purposes).
const SetType & Ancestors(ClusterIndex i) const noexcept
Get the ancestors of a given transaction i.
bool IsConnected() const noexcept
Determine if this entire graph is connected.
bool IsConnected(const SetType &subset) const noexcept
Determine if a subset is connected.
SetType FindConnectedComponent(const SetType &todo) const noexcept
Find some connected component within the subset "todo" of this graph.
auto TxCount() const noexcept
Get the number of transactions in the graph.
std::vector< Entry > entries
Data for each transaction, in the same order as the Cluster it was constructed from.
DepGraph() noexcept=default
DepGraph(const Cluster< SetType > &cluster) noexcept
Construct a DepGraph object given a cluster.
FeeFrac FeeRate(const SetType &elems) const noexcept
Compute the aggregate feerate of a set of nodes in this graph.
FeeFrac & FeeRate(ClusterIndex i) noexcept
Get the mutable feerate of a given transaction i.
const SetType & Descendants(ClusterIndex i) const noexcept
Get the descendants of a given transaction i.
ClusterIndex AddTransaction(const FeeFrac &feefrac) noexcept
Add a new unconnected transaction to this transaction graph (at the end), and return its ClusterIndex...
Data structure encapsulating the chunking of a linearization, permitting removal of subsets.
ClusterIndex NumChunksLeft() const noexcept
Determine how many chunks remain in the linearization.
SetType m_todo
Which transactions remain in the linearization.
SetInfo< SetType > IntersectPrefixes(const SetInfo< SetType > &subset) const noexcept
Find the shortest intersection between subset and the prefixes of remaining chunks of the linearizati...
ClusterIndex m_chunks_skip
How large a prefix of m_chunks corresponds to removed transactions.
Span< const ClusterIndex > m_linearization
The linearization we started from, possibly with removed prefix stripped.
LinearizationChunking(const DepGraph< SetType > &depgraph LIFETIMEBOUND, Span< const ClusterIndex > lin LIFETIMEBOUND) noexcept
Initialize a LinearizationSubset object for a given length of linearization.
const SetInfo< SetType > & GetChunk(ClusterIndex n) const noexcept
Access a chunk.
void BuildChunks() noexcept
Fill the m_chunks variable, and remove the done prefix of m_linearization.
void MarkDone(SetType subset) noexcept
Remove some subset of transactions from the linearization.
const DepGraph< SetType > & m_depgraph
The depgraph this linearization is for.
std::vector< SetInfo< SetType > > m_chunks
Chunk sets and their feerates, of what remains of the linearization.
Class encapsulating the state needed to perform search for good candidate sets.
bool AllDone() const noexcept
Check whether any unlinearized transactions remain.
void MarkDone(const SetType &done) noexcept
Remove a subset of transactions from the cluster being linearized.
InsecureRandomContext m_rng
Internal RNG.
SetType m_todo
Which transactions are left to do (sorted indices).
const DepGraph< SetType > & m_depgraph
Internal dependency graph for the cluster.
SearchCandidateFinder(const DepGraph< SetType > &depgraph LIFETIMEBOUND, uint64_t rng_seed) noexcept
Construct a candidate finder for a graph.
std::pair< SetInfo< SetType >, uint64_t > FindCandidateSet(uint64_t max_iterations, SetInfo< SetType > best) noexcept
Find a high-feerate topologically-valid subset of what remains of the cluster.
std::pair< std::vector< ClusterIndex >, bool > Linearize(const DepGraph< SetType > &depgraph, uint64_t max_iterations, uint64_t rng_seed, Span< const ClusterIndex > old_linearization={}) noexcept
Find or improve a linearization for a cluster.
uint32_t ClusterIndex
Data type to represent transaction indices in clusters.
std::vector< FeeFrac > ChunkLinearization(const DepGraph< SetType > &depgraph, Span< const ClusterIndex > linearization) noexcept
Compute the feerates of the chunks of linearization.
std::vector< std::pair< FeeFrac, SetType > > Cluster
Data type to represent cluster input.
void PostLinearize(const DepGraph< SetType > &depgraph, Span< ClusterIndex > linearization)
Improve a given linearization.
std::vector< ClusterIndex > MergeLinearizations(const DepGraph< SetType > &depgraph, Span< const ClusterIndex > lin1, Span< const ClusterIndex > lin2)
Merge two linearizations for the same cluster into one that is as good as both.
Data structure storing a fee and size, ordered by increasing fee/size.
Definition feefrac.h:39
int64_t fee
Definition feefrac.h:63
bool IsEmpty() const noexcept
Check if this is empty (size and fee are 0).
Definition feefrac.h:76
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 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.
FeeFrac feerate
Their combined fee and size.
SetInfo() noexcept=default
Construct a SetInfo for the empty set.
SetInfo(const DepGraph< SetType > &depgraph, ClusterIndex pos) noexcept
Construct a SetInfo for a given transaction in a depgraph.
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 Add(const DepGraph< SetType > &depgraph, const SetType &txn) const noexcept
Construct a new SetInfo equal to this, with more transactions added (which may overlap with the exist...
SetInfo & operator|=(const SetInfo &other) noexcept
Add the transactions of other to this SetInfo (no overlap allowed).
friend bool operator==(const SetInfo &, const SetInfo &) noexcept=default
Permit equality testing.