Bitcoin Core 29.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 <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
23using DepGraphIndex = uint32_t;
24
27template<typename SetType>
29{
31 struct Entry
32 {
36 SetType ancestors;
38 SetType descendants;
39
41 friend bool operator==(const Entry&, const Entry&) noexcept = default;
42
44 Entry() noexcept = default;
46 Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {}
47 };
48
50 std::vector<Entry> entries;
51
53 SetType m_used;
54
55public:
57 friend bool operator==(const DepGraph& a, const DepGraph& b) noexcept
58 {
59 if (a.m_used != b.m_used) return false;
60 // Only compare the used positions within the entries vector.
61 for (auto idx : a.m_used) {
62 if (a.entries[idx] != b.entries[idx]) return false;
63 }
64 return true;
65 }
66
67 // Default constructors.
68 DepGraph() noexcept = default;
69 DepGraph(const DepGraph&) noexcept = default;
70 DepGraph(DepGraph&&) noexcept = default;
71 DepGraph& operator=(const DepGraph&) noexcept = default;
72 DepGraph& operator=(DepGraph&&) noexcept = default;
73
89 DepGraph(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> mapping, DepGraphIndex pos_range) noexcept : entries(pos_range)
90 {
91 Assume(mapping.size() == depgraph.PositionRange());
92 Assume((pos_range == 0) == (depgraph.TxCount() == 0));
93 for (DepGraphIndex i : depgraph.Positions()) {
94 auto new_idx = mapping[i];
95 Assume(new_idx < pos_range);
96 // Add transaction.
97 entries[new_idx].ancestors = SetType::Singleton(new_idx);
98 entries[new_idx].descendants = SetType::Singleton(new_idx);
99 m_used.Set(new_idx);
100 // Fill in fee and size.
101 entries[new_idx].feerate = depgraph.entries[i].feerate;
102 }
103 for (DepGraphIndex i : depgraph.Positions()) {
104 // Fill in dependencies by mapping direct parents.
105 SetType parents;
106 for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]);
107 AddDependencies(parents, mapping[i]);
108 }
109 // Verify that the provided pos_range was correct (no unused positions at the end).
110 Assume(m_used.None() ? (pos_range == 0) : (pos_range == m_used.Last() + 1));
111 }
112
114 const SetType& Positions() const noexcept { return m_used; }
116 DepGraphIndex PositionRange() const noexcept { return entries.size(); }
118 auto TxCount() const noexcept { return m_used.Count(); }
120 const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; }
122 FeeFrac& FeeRate(DepGraphIndex i) noexcept { return entries[i].feerate; }
124 const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; }
126 const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; }
127
133 DepGraphIndex AddTransaction(const FeeFrac& feefrac) noexcept
134 {
135 static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size());
136 auto available = ALL_POSITIONS - m_used;
137 Assume(available.Any());
138 DepGraphIndex new_idx = available.First();
139 if (new_idx == entries.size()) {
140 entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
141 } else {
142 entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
143 }
144 m_used.Set(new_idx);
145 return new_idx;
146 }
147
157 void RemoveTransactions(const SetType& del) noexcept
158 {
159 m_used -= del;
160 // Remove now-unused trailing entries.
161 while (!entries.empty() && !m_used[entries.size() - 1]) {
162 entries.pop_back();
163 }
164 // Remove the deleted transactions from ancestors/descendants of other transactions. Note
165 // that the deleted positions will retain old feerate and dependency information. This does
166 // not matter as they will be overwritten by AddTransaction if they get used again.
167 for (auto& entry : entries) {
168 entry.ancestors &= m_used;
169 entry.descendants &= m_used;
170 }
171 }
172
177 void AddDependencies(const SetType& parents, DepGraphIndex child) noexcept
178 {
179 Assume(m_used[child]);
180 Assume(parents.IsSubsetOf(m_used));
181 // Compute the ancestors of parents that are not already ancestors of child.
182 SetType par_anc;
183 for (auto par : parents - Ancestors(child)) {
184 par_anc |= Ancestors(par);
185 }
186 par_anc -= Ancestors(child);
187 // Bail out if there are no such ancestors.
188 if (par_anc.None()) return;
189 // To each such ancestor, add as descendants the descendants of the child.
190 const auto& chl_des = entries[child].descendants;
191 for (auto anc_of_par : par_anc) {
192 entries[anc_of_par].descendants |= chl_des;
193 }
194 // To each descendant of the child, add those ancestors.
195 for (auto dec_of_chl : Descendants(child)) {
196 entries[dec_of_chl].ancestors |= par_anc;
197 }
198 }
199
208 SetType GetReducedParents(DepGraphIndex i) const noexcept
209 {
210 SetType parents = Ancestors(i);
211 parents.Reset(i);
212 for (auto parent : parents) {
213 if (parents[parent]) {
214 parents -= Ancestors(parent);
215 parents.Set(parent);
216 }
217 }
218 return parents;
219 }
220
229 SetType GetReducedChildren(DepGraphIndex i) const noexcept
230 {
231 SetType children = Descendants(i);
232 children.Reset(i);
233 for (auto child : children) {
234 if (children[child]) {
235 children -= Descendants(child);
236 children.Set(child);
237 }
238 }
239 return children;
240 }
241
246 FeeFrac FeeRate(const SetType& elems) const noexcept
247 {
248 FeeFrac ret;
249 for (auto pos : elems) ret += entries[pos].feerate;
250 return ret;
251 }
252
263 SetType GetConnectedComponent(const SetType& todo, DepGraphIndex tx) const noexcept
264 {
265 Assume(todo[tx]);
266 Assume(todo.IsSubsetOf(m_used));
267 auto to_add = SetType::Singleton(tx);
268 SetType ret;
269 do {
270 SetType old = ret;
271 for (auto add : to_add) {
272 ret |= Descendants(add);
273 ret |= Ancestors(add);
274 }
275 ret &= todo;
276 to_add = ret - old;
277 } while (to_add.Any());
278 return ret;
279 }
280
288 SetType FindConnectedComponent(const SetType& todo) const noexcept
289 {
290 if (todo.None()) return todo;
291 return GetConnectedComponent(todo, todo.First());
292 }
293
298 bool IsConnected(const SetType& subset) const noexcept
299 {
300 return FindConnectedComponent(subset) == subset;
301 }
302
307 bool IsConnected() const noexcept { return IsConnected(m_used); }
308
313 void AppendTopo(std::vector<DepGraphIndex>& list, const SetType& select) const noexcept
314 {
315 DepGraphIndex old_len = list.size();
316 for (auto i : select) list.push_back(i);
317 std::sort(list.begin() + old_len, list.end(), [&](DepGraphIndex a, DepGraphIndex b) noexcept {
318 const auto a_anc_count = entries[a].ancestors.Count();
319 const auto b_anc_count = entries[b].ancestors.Count();
320 if (a_anc_count != b_anc_count) return a_anc_count < b_anc_count;
321 return a < b;
322 });
323 }
324
326 bool IsAcyclic() const noexcept
327 {
328 for (auto i : Positions()) {
329 if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) {
330 return false;
331 }
332 }
333 return true;
334 }
335};
336
338template<typename SetType>
340{
345
347 SetInfo() noexcept = default;
348
350 SetInfo(const SetType& txn, const FeeFrac& fr) noexcept : transactions(txn), feerate(fr) {}
351
353 explicit SetInfo(const DepGraph<SetType>& depgraph, DepGraphIndex pos) noexcept :
354 transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {}
355
357 explicit SetInfo(const DepGraph<SetType>& depgraph, const SetType& txn) noexcept :
358 transactions(txn), feerate(depgraph.FeeRate(txn)) {}
359
361 void Set(const DepGraph<SetType>& depgraph, DepGraphIndex pos) noexcept
362 {
363 Assume(!transactions[pos]);
364 transactions.Set(pos);
365 feerate += depgraph.FeeRate(pos);
366 }
367
369 SetInfo& operator|=(const SetInfo& other) noexcept
370 {
371 Assume(!transactions.Overlaps(other.transactions));
372 transactions |= other.transactions;
373 feerate += other.feerate;
374 return *this;
375 }
376
379 [[nodiscard]] SetInfo Add(const DepGraph<SetType>& depgraph, const SetType& txn) const noexcept
380 {
381 return {transactions | txn, feerate + depgraph.FeeRate(txn - transactions)};
382 }
383
385 friend void swap(SetInfo& a, SetInfo& b) noexcept
386 {
387 swap(a.transactions, b.transactions);
388 swap(a.feerate, b.feerate);
389 }
390
392 friend bool operator==(const SetInfo&, const SetInfo&) noexcept = default;
393};
394
396template<typename SetType>
397std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> linearization) noexcept
398{
399 std::vector<FeeFrac> ret;
400 for (DepGraphIndex i : linearization) {
402 auto new_chunk = depgraph.FeeRate(i);
403 // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
404 while (!ret.empty() && new_chunk >> ret.back()) {
405 new_chunk += ret.back();
406 ret.pop_back();
407 }
408 // Actually move that new chunk into the chunking.
409 ret.push_back(std::move(new_chunk));
410 }
411 return ret;
412}
413
415template<typename SetType>
417{
420
422 std::span<const DepGraphIndex> m_linearization;
423
425 std::vector<SetInfo<SetType>> m_chunks;
426
429
431 SetType m_todo;
432
434 void BuildChunks() noexcept
435 {
436 // Caller must clear m_chunks.
437 Assume(m_chunks.empty());
438
439 // Chop off the initial part of m_linearization that is already done.
440 while (!m_linearization.empty() && !m_todo[m_linearization.front()]) {
441 m_linearization = m_linearization.subspan(1);
442 }
443
444 // Iterate over the remaining entries in m_linearization. This is effectively the same
445 // algorithm as ChunkLinearization, but supports skipping parts of the linearization and
446 // keeps track of the sets themselves instead of just their feerates.
447 for (auto idx : m_linearization) {
448 if (!m_todo[idx]) continue;
449 // Start with an initial chunk containing just element idx.
450 SetInfo add(m_depgraph, idx);
451 // Absorb existing final chunks into add while they have lower feerate.
452 while (!m_chunks.empty() && add.feerate >> m_chunks.back().feerate) {
453 add |= m_chunks.back();
454 m_chunks.pop_back();
455 }
456 // Remember new chunk.
457 m_chunks.push_back(std::move(add));
458 }
459 }
460
461public:
463 explicit LinearizationChunking(const DepGraph<SetType>& depgraph LIFETIMEBOUND, std::span<const DepGraphIndex> lin LIFETIMEBOUND) noexcept :
464 m_depgraph(depgraph), m_linearization(lin)
465 {
466 // Mark everything in lin as todo still.
467 for (auto i : m_linearization) m_todo.Set(i);
468 // Compute the initial chunking.
469 m_chunks.reserve(depgraph.TxCount());
470 BuildChunks();
471 }
472
474 DepGraphIndex NumChunksLeft() const noexcept { return m_chunks.size() - m_chunks_skip; }
475
477 const SetInfo<SetType>& GetChunk(DepGraphIndex n) const noexcept
478 {
479 Assume(n + m_chunks_skip < m_chunks.size());
480 return m_chunks[n + m_chunks_skip];
481 }
482
484 void MarkDone(SetType subset) noexcept
485 {
486 Assume(subset.Any());
487 Assume(subset.IsSubsetOf(m_todo));
488 m_todo -= subset;
489 if (GetChunk(0).transactions == subset) {
490 // If the newly done transactions exactly match the first chunk of the remainder of
491 // the linearization, we do not need to rechunk; just remember to skip one
492 // additional chunk.
494 // With subset marked done, some prefix of m_linearization will be done now. How long
495 // that prefix is depends on how many done elements were interspersed with subset,
496 // but at least as many transactions as there are in subset.
497 m_linearization = m_linearization.subspan(subset.Count());
498 } else {
499 // Otherwise rechunk what remains of m_linearization.
500 m_chunks.clear();
501 m_chunks_skip = 0;
502 BuildChunks();
503 }
504 }
505
516 {
517 Assume(subset.transactions.IsSubsetOf(m_todo));
518 SetInfo<SetType> accumulator;
519 // Iterate over all chunks of the remaining linearization.
520 for (DepGraphIndex i = 0; i < NumChunksLeft(); ++i) {
521 // Find what (if any) intersection the chunk has with subset.
522 const SetType to_add = GetChunk(i).transactions & subset.transactions;
523 if (to_add.Any()) {
524 // If adding that to accumulator makes us hit all of subset, we are done as no
525 // shorter intersection with higher/equal feerate exists.
526 accumulator.transactions |= to_add;
527 if (accumulator.transactions == subset.transactions) break;
528 // Otherwise update the accumulator feerate.
529 accumulator.feerate += m_depgraph.FeeRate(to_add);
530 // If that does result in something better, or something with the same feerate but
531 // smaller, return that. Even if a longer, higher-feerate intersection exists, it
532 // does not hurt to return the shorter one (the remainder of the longer intersection
533 // will generally be found in the next call to Intersect, but even if not, it is not
534 // required for the improvement guarantee this function makes).
535 if (!(accumulator.feerate << subset.feerate)) return accumulator;
536 }
537 }
538 return subset;
539 }
540};
541
551template<typename SetType>
553{
557 SetType m_todo;
559 std::vector<FeeFrac> m_ancestor_set_feerates;
560
561public:
567 m_depgraph(depgraph),
568 m_todo{depgraph.Positions()},
569 m_ancestor_set_feerates(depgraph.PositionRange())
570 {
571 // Precompute ancestor-set feerates.
572 for (DepGraphIndex i : m_depgraph.Positions()) {
574 SetType anc_to_add = m_depgraph.Ancestors(i);
575 FeeFrac anc_feerate;
576 // Reuse accumulated feerate from first ancestor, if usable.
577 Assume(anc_to_add.Any());
578 DepGraphIndex first = anc_to_add.First();
579 if (first < i) {
580 anc_feerate = m_ancestor_set_feerates[first];
581 Assume(!anc_feerate.IsEmpty());
582 anc_to_add -= m_depgraph.Ancestors(first);
583 }
584 // Add in other ancestors (which necessarily include i itself).
585 Assume(anc_to_add[i]);
586 anc_feerate += m_depgraph.FeeRate(anc_to_add);
587 // Store the result.
588 m_ancestor_set_feerates[i] = anc_feerate;
589 }
590 }
591
598 void MarkDone(SetType select) noexcept
599 {
600 Assume(select.Any());
601 Assume(select.IsSubsetOf(m_todo));
602 m_todo -= select;
603 for (auto i : select) {
604 auto feerate = m_depgraph.FeeRate(i);
605 for (auto j : m_depgraph.Descendants(i) & m_todo) {
606 m_ancestor_set_feerates[j] -= feerate;
607 }
608 }
609 }
610
612 bool AllDone() const noexcept
613 {
614 return m_todo.None();
615 }
616
619 {
620 return m_todo.Count();
621 }
622
629 {
630 Assume(!AllDone());
631 std::optional<DepGraphIndex> best;
632 for (auto i : m_todo) {
633 if (best.has_value()) {
634 Assume(!m_ancestor_set_feerates[i].IsEmpty());
635 if (!(m_ancestor_set_feerates[i] > m_ancestor_set_feerates[*best])) continue;
636 }
637 best = i;
638 }
639 Assume(best.has_value());
640 return {m_depgraph.Ancestors(*best) & m_todo, m_ancestor_set_feerates[*best]};
641 }
642};
643
653template<typename SetType>
655{
659 std::vector<DepGraphIndex> m_sorted_to_original;
661 std::vector<DepGraphIndex> m_original_to_sorted;
666 SetType m_todo;
667
669 SetType SortedToOriginal(const SetType& arg) const noexcept
670 {
671 SetType ret;
672 for (auto pos : arg) ret.Set(m_sorted_to_original[pos]);
673 return ret;
674 }
675
677 SetType OriginalToSorted(const SetType& arg) const noexcept
678 {
679 SetType ret;
680 for (auto pos : arg) ret.Set(m_original_to_sorted[pos]);
681 return ret;
682 }
683
684public:
692 SearchCandidateFinder(const DepGraph<SetType>& depgraph, uint64_t rng_seed) noexcept :
693 m_rng(rng_seed),
694 m_sorted_to_original(depgraph.TxCount()),
695 m_original_to_sorted(depgraph.PositionRange())
696 {
697 // Determine reordering mapping, by sorting by decreasing feerate. Unused positions are
698 // not included, as they will never be looked up anyway.
699 DepGraphIndex sorted_pos{0};
700 for (auto i : depgraph.Positions()) {
701 m_sorted_to_original[sorted_pos++] = i;
702 }
703 std::sort(m_sorted_to_original.begin(), m_sorted_to_original.end(), [&](auto a, auto b) {
704 auto feerate_cmp = depgraph.FeeRate(a) <=> depgraph.FeeRate(b);
705 if (feerate_cmp == 0) return a < b;
706 return feerate_cmp > 0;
707 });
708 // Compute reverse mapping.
709 for (DepGraphIndex i = 0; i < m_sorted_to_original.size(); ++i) {
711 }
712 // Compute reordered dependency graph.
714 m_todo = m_sorted_depgraph.Positions();
715 }
716
718 bool AllDone() const noexcept
719 {
720 return m_todo.None();
721 }
722
740 std::pair<SetInfo<SetType>, uint64_t> FindCandidateSet(uint64_t max_iterations, SetInfo<SetType> best) noexcept
741 {
742 Assume(!AllDone());
743
744 // Convert the provided best to internal sorted indices.
745 best.transactions = OriginalToSorted(best.transactions);
746
748 struct WorkItem
749 {
756 SetType und;
763 FeeFrac pot_feerate;
764
766 WorkItem(SetInfo<SetType>&& i, SetType&& u, FeeFrac&& p_f) noexcept :
767 inc(std::move(i)), und(std::move(u)), pot_feerate(std::move(p_f))
768 {
769 Assume(pot_feerate.IsEmpty() == inc.feerate.IsEmpty());
770 }
771
773 void Swap(WorkItem& other) noexcept
774 {
775 swap(inc, other.inc);
776 swap(und, other.und);
777 swap(pot_feerate, other.pot_feerate);
778 }
779 };
780
782 VecDeque<WorkItem> queue;
783 queue.reserve(std::max<size_t>(256, 2 * m_todo.Count()));
784
785 // Create initial entries per connected component of m_todo. While clusters themselves are
786 // generally connected, this is not necessarily true after some parts have already been
787 // removed from m_todo. Without this, effort can be wasted on searching "inc" sets that
788 // span multiple components.
789 auto to_cover = m_todo;
790 do {
791 auto component = m_sorted_depgraph.FindConnectedComponent(to_cover);
792 to_cover -= component;
793 // If best is not provided, set it to the first component, so that during the work
794 // processing loop below, and during the add_fn/split_fn calls, we do not need to deal
795 // with the best=empty case.
796 if (best.feerate.IsEmpty()) best = SetInfo(m_sorted_depgraph, component);
797 queue.emplace_back(/*inc=*/SetInfo<SetType>{},
798 /*und=*/std::move(component),
799 /*pot_feerate=*/FeeFrac{});
800 } while (to_cover.Any());
801
803 uint64_t iterations_left = max_iterations;
804
806 SetType imp = m_todo;
807 while (imp.Any()) {
808 DepGraphIndex check = imp.Last();
809 if (m_sorted_depgraph.FeeRate(check) >> best.feerate) break;
810 imp.Reset(check);
811 }
812
820 auto add_fn = [&](SetInfo<SetType> inc, SetType und) noexcept {
823 auto pot = inc;
824 if (!inc.feerate.IsEmpty()) {
825 // Add entries to pot. We iterate over all undecided transactions whose feerate is
826 // higher than best. While undecided transactions of lower feerate may improve pot,
827 // the resulting pot feerate cannot possibly exceed best's (and this item will be
828 // skipped in split_fn anyway).
829 for (auto pos : imp & und) {
830 // Determine if adding transaction pos to pot (ignoring topology) would improve
831 // it. If not, we're done updating pot. This relies on the fact that
832 // m_sorted_depgraph, and thus the transactions iterated over, are in decreasing
833 // individual feerate order.
834 if (!(m_sorted_depgraph.FeeRate(pos) >> pot.feerate)) break;
835 pot.Set(m_sorted_depgraph, pos);
836 }
837
838 // The "jump ahead" optimization: whenever pot has a topologically-valid subset,
839 // that subset can be added to inc. Any subset of (pot - inc) has the property that
840 // its feerate exceeds that of any set compatible with this work item (superset of
841 // inc, subset of (inc | und)). Thus, if T is a topological subset of pot, and B is
842 // the best topologically-valid set compatible with this work item, and (T - B) is
843 // non-empty, then (T | B) is better than B and also topological. This is in
844 // contradiction with the assumption that B is best. Thus, (T - B) must be empty,
845 // or T must be a subset of B.
846 //
847 // See https://delvingbitcoin.org/t/how-to-linearize-your-cluster/303 section 2.4.
848 const auto init_inc = inc.transactions;
849 for (auto pos : pot.transactions - inc.transactions) {
850 // If the transaction's ancestors are a subset of pot, we can add it together
851 // with its ancestors to inc. Just update the transactions here; the feerate
852 // update happens below.
853 auto anc_todo = m_sorted_depgraph.Ancestors(pos) & m_todo;
854 if (anc_todo.IsSubsetOf(pot.transactions)) inc.transactions |= anc_todo;
855 }
856 // Finally update und and inc's feerate to account for the added transactions.
857 und -= inc.transactions;
858 inc.feerate += m_sorted_depgraph.FeeRate(inc.transactions - init_inc);
859
860 // If inc's feerate is better than best's, remember it as our new best.
861 if (inc.feerate > best.feerate) {
862 best = inc;
863 // See if we can remove any entries from imp now.
864 while (imp.Any()) {
865 DepGraphIndex check = imp.Last();
866 if (m_sorted_depgraph.FeeRate(check) >> best.feerate) break;
867 imp.Reset(check);
868 }
869 }
870
871 // If no potential transactions exist beyond the already included ones, no
872 // improvement is possible anymore.
873 if (pot.feerate.size == inc.feerate.size) return;
874 // At this point und must be non-empty. If it were empty then pot would equal inc.
875 Assume(und.Any());
876 } else {
877 Assume(inc.transactions.None());
878 // If inc is empty, we just make sure there are undecided transactions left to
879 // split on.
880 if (und.None()) return;
881 }
882
883 // Actually construct a new work item on the queue. Due to the switch to DFS when queue
884 // space runs out (see below), we know that no reallocation of the queue should ever
885 // occur.
886 Assume(queue.size() < queue.capacity());
887 queue.emplace_back(/*inc=*/std::move(inc),
888 /*und=*/std::move(und),
889 /*pot_feerate=*/std::move(pot.feerate));
890 };
891
895 auto split_fn = [&](WorkItem&& elem) noexcept {
896 // Any queue element must have undecided transactions left, otherwise there is nothing
897 // to explore anymore.
898 Assume(elem.und.Any());
899 // The included and undecided set are all subsets of m_todo.
900 Assume(elem.inc.transactions.IsSubsetOf(m_todo) && elem.und.IsSubsetOf(m_todo));
901 // Included transactions cannot be undecided.
902 Assume(!elem.inc.transactions.Overlaps(elem.und));
903 // If pot is empty, then so is inc.
904 Assume(elem.inc.feerate.IsEmpty() == elem.pot_feerate.IsEmpty());
905
906 const DepGraphIndex first = elem.und.First();
907 if (!elem.inc.feerate.IsEmpty()) {
908 // If no undecided transactions remain with feerate higher than best, this entry
909 // cannot be improved beyond best.
910 if (!elem.und.Overlaps(imp)) return;
911 // We can ignore any queue item whose potential feerate isn't better than the best
912 // seen so far.
913 if (elem.pot_feerate <= best.feerate) return;
914 } else {
915 // In case inc is empty use a simpler alternative check.
916 if (m_sorted_depgraph.FeeRate(first) <= best.feerate) return;
917 }
918
919 // Decide which transaction to split on. Splitting is how new work items are added, and
920 // how progress is made. One split transaction is chosen among the queue item's
921 // undecided ones, and:
922 // - A work item is (potentially) added with that transaction plus its remaining
923 // descendants excluded (removed from the und set).
924 // - A work item is (potentially) added with that transaction plus its remaining
925 // ancestors included (added to the inc set).
926 //
927 // To decide what to split on, consider the undecided ancestors of the highest
928 // individual feerate undecided transaction. Pick the one which reduces the search space
929 // most. Let I(t) be the size of the undecided set after including t, and E(t) the size
930 // of the undecided set after excluding t. Then choose the split transaction t such
931 // that 2^I(t) + 2^E(t) is minimal, tie-breaking by highest individual feerate for t.
933 const auto select = elem.und & m_sorted_depgraph.Ancestors(first);
934 Assume(select.Any());
935 std::optional<std::pair<DepGraphIndex, DepGraphIndex>> split_counts;
936 for (auto t : select) {
937 // Call max = max(I(t), E(t)) and min = min(I(t), E(t)). Let counts = {max,min}.
938 // Sorting by the tuple counts is equivalent to sorting by 2^I(t) + 2^E(t). This
939 // expression is equal to 2^max + 2^min = 2^max * (1 + 1/2^(max - min)). The second
940 // factor (1 + 1/2^(max - min)) there is in (1,2]. Thus increasing max will always
941 // increase it, even when min decreases. Because of this, we can first sort by max.
942 std::pair<DepGraphIndex, DepGraphIndex> counts{
943 (elem.und - m_sorted_depgraph.Ancestors(t)).Count(),
944 (elem.und - m_sorted_depgraph.Descendants(t)).Count()};
945 if (counts.first < counts.second) std::swap(counts.first, counts.second);
946 // Remember the t with the lowest counts.
947 if (!split_counts.has_value() || counts < *split_counts) {
948 split = t;
949 split_counts = counts;
950 }
951 }
952 // Since there was at least one transaction in select, we must always find one.
953 Assume(split_counts.has_value());
954
955 // Add a work item corresponding to exclusion of the split transaction.
956 const auto& desc = m_sorted_depgraph.Descendants(split);
957 add_fn(/*inc=*/elem.inc,
958 /*und=*/elem.und - desc);
959
960 // Add a work item corresponding to inclusion of the split transaction.
961 const auto anc = m_sorted_depgraph.Ancestors(split) & m_todo;
962 add_fn(/*inc=*/elem.inc.Add(m_sorted_depgraph, anc),
963 /*und=*/elem.und - anc);
964
965 // Account for the performed split.
966 --iterations_left;
967 };
968
969 // Work processing loop.
970 //
971 // New work items are always added at the back of the queue, but items to process use a
972 // hybrid approach where they can be taken from the front or the back.
973 //
974 // Depth-first search (DFS) corresponds to always taking from the back of the queue. This
975 // is very memory-efficient (linear in the number of transactions). Breadth-first search
976 // (BFS) corresponds to always taking from the front, which potentially uses more memory
977 // (up to exponential in the transaction count), but seems to work better in practice.
978 //
979 // The approach here combines the two: use BFS (plus random swapping) until the queue grows
980 // too large, at which point we temporarily switch to DFS until the size shrinks again.
981 while (!queue.empty()) {
982 // Randomly swap the first two items to randomize the search order.
983 if (queue.size() > 1 && m_rng.randbool()) {
984 queue[0].Swap(queue[1]);
985 }
986
987 // Processing the first queue item, and then using DFS for everything it gives rise to,
988 // may increase the queue size by the number of undecided elements in there, minus 1
989 // for the first queue item being removed. Thus, only when that pushes the queue over
990 // its capacity can we not process from the front (BFS), and should we use DFS.
991 while (queue.size() - 1 + queue.front().und.Count() > queue.capacity()) {
992 if (!iterations_left) break;
993 auto elem = queue.back();
994 queue.pop_back();
995 split_fn(std::move(elem));
996 }
997
998 // Process one entry from the front of the queue (BFS exploration)
999 if (!iterations_left) break;
1000 auto elem = queue.front();
1001 queue.pop_front();
1002 split_fn(std::move(elem));
1003 }
1004
1005 // Return the found best set (converted to the original transaction indices), and the
1006 // number of iterations performed.
1007 best.transactions = SortedToOriginal(best.transactions);
1008 return {std::move(best), max_iterations - iterations_left};
1009 }
1010
1015 void MarkDone(const SetType& done) noexcept
1016 {
1017 const auto done_sorted = OriginalToSorted(done);
1018 Assume(done_sorted.Any());
1019 Assume(done_sorted.IsSubsetOf(m_todo));
1020 m_todo -= done_sorted;
1021 }
1022};
1023
1041template<typename SetType>
1042std::pair<std::vector<DepGraphIndex>, bool> Linearize(const DepGraph<SetType>& depgraph, uint64_t max_iterations, uint64_t rng_seed, std::span<const DepGraphIndex> old_linearization = {}) noexcept
1043{
1044 Assume(old_linearization.empty() || old_linearization.size() == depgraph.TxCount());
1045 if (depgraph.TxCount() == 0) return {{}, true};
1046
1047 uint64_t iterations_left = max_iterations;
1048 std::vector<DepGraphIndex> linearization;
1049
1050 AncestorCandidateFinder anc_finder(depgraph);
1051 std::optional<SearchCandidateFinder<SetType>> src_finder;
1052 linearization.reserve(depgraph.TxCount());
1053 bool optimal = true;
1054
1055 // Treat the initialization of SearchCandidateFinder as taking N^2/64 (rounded up) iterations
1056 // (largely due to the cost of constructing the internal sorted-by-feerate DepGraph inside
1057 // SearchCandidateFinder), a rough approximation based on benchmark. If we don't have that
1058 // many, don't start it.
1059 uint64_t start_iterations = (uint64_t{depgraph.TxCount()} * depgraph.TxCount() + 63) / 64;
1060 if (iterations_left > start_iterations) {
1061 iterations_left -= start_iterations;
1062 src_finder.emplace(depgraph, rng_seed);
1063 }
1064
1066 LinearizationChunking old_chunking(depgraph, old_linearization);
1067
1068 while (true) {
1069 // Find the highest-feerate prefix of the remainder of old_linearization.
1070 SetInfo<SetType> best_prefix;
1071 if (old_chunking.NumChunksLeft()) best_prefix = old_chunking.GetChunk(0);
1072
1073 // Then initialize best to be either the best remaining ancestor set, or the first chunk.
1074 auto best = anc_finder.FindCandidateSet();
1075 if (!best_prefix.feerate.IsEmpty() && best_prefix.feerate >= best.feerate) best = best_prefix;
1076
1077 uint64_t iterations_done_now = 0;
1078 uint64_t max_iterations_now = 0;
1079 if (src_finder) {
1080 // Treat the invocation of SearchCandidateFinder::FindCandidateSet() as costing N/4
1081 // up-front (rounded up) iterations (largely due to the cost of connected-component
1082 // splitting), a rough approximation based on benchmarks.
1083 uint64_t base_iterations = (anc_finder.NumRemaining() + 3) / 4;
1084 if (iterations_left > base_iterations) {
1085 // Invoke bounded search to update best, with up to half of our remaining
1086 // iterations as limit.
1087 iterations_left -= base_iterations;
1088 max_iterations_now = (iterations_left + 1) / 2;
1089 std::tie(best, iterations_done_now) = src_finder->FindCandidateSet(max_iterations_now, best);
1090 iterations_left -= iterations_done_now;
1091 }
1092 }
1093
1094 if (iterations_done_now == max_iterations_now) {
1095 optimal = false;
1096 // If the search result is not (guaranteed to be) optimal, run intersections to make
1097 // sure we don't pick something that makes us unable to reach further diagram points
1098 // of the old linearization.
1099 if (old_chunking.NumChunksLeft() > 0) {
1100 best = old_chunking.IntersectPrefixes(best);
1101 }
1102 }
1103
1104 // Add to output in topological order.
1105 depgraph.AppendTopo(linearization, best.transactions);
1106
1107 // Update state to reflect best is no longer to be linearized.
1108 anc_finder.MarkDone(best.transactions);
1109 if (anc_finder.AllDone()) break;
1110 if (src_finder) src_finder->MarkDone(best.transactions);
1111 if (old_chunking.NumChunksLeft() > 0) {
1112 old_chunking.MarkDone(best.transactions);
1113 }
1114 }
1115
1116 return {std::move(linearization), optimal};
1117}
1118
1135template<typename SetType>
1136void PostLinearize(const DepGraph<SetType>& depgraph, std::span<DepGraphIndex> linearization)
1137{
1138 // This algorithm performs a number of passes (currently 2); the even ones operate from back to
1139 // front, the odd ones from front to back. Each results in an equal-or-better linearization
1140 // than the one started from.
1141 // - One pass in either direction guarantees that the resulting chunks are connected.
1142 // - Each direction corresponds to one shape of tree being linearized optimally (forward passes
1143 // guarantee this for graphs where each transaction has at most one child; backward passes
1144 // guarantee this for graphs where each transaction has at most one parent).
1145 // - Starting with a backward pass guarantees the moved-tree property.
1146 //
1147 // During an odd (forward) pass, the high-level operation is:
1148 // - Start with an empty list of groups L=[].
1149 // - For every transaction i in the old linearization, from front to back:
1150 // - Append a new group C=[i], containing just i, to the back of L.
1151 // - While L has at least one group before C, and the group immediately before C has feerate
1152 // lower than C:
1153 // - If C depends on P:
1154 // - Merge P into C, making C the concatenation of P+C, continuing with the combined C.
1155 // - Otherwise:
1156 // - Swap P with C, continuing with the now-moved C.
1157 // - The output linearization is the concatenation of the groups in L.
1158 //
1159 // During even (backward) passes, i iterates from the back to the front of the existing
1160 // linearization, and new groups are prepended instead of appended to the list L. To enable
1161 // more code reuse, both passes append groups, but during even passes the meanings of
1162 // parent/child, and of high/low feerate are reversed, and the final concatenation is reversed
1163 // on output.
1164 //
1165 // In the implementation below, the groups are represented by singly-linked lists (pointing
1166 // from the back to the front), which are themselves organized in a singly-linked circular
1167 // list (each group pointing to its predecessor, with a special sentinel group at the front
1168 // that points back to the last group).
1169 //
1170 // Information about transaction t is stored in entries[t + 1], while the sentinel is in
1171 // entries[0].
1172
1174 static constexpr DepGraphIndex SENTINEL{0};
1176 static constexpr DepGraphIndex NO_PREV_TX{0};
1177
1178
1180 struct TxEntry
1181 {
1184 DepGraphIndex prev_tx;
1185
1186 // The fields below are only used for transactions that are the last one in a group
1187 // (referred to as tail transactions below).
1188
1190 DepGraphIndex first_tx;
1193 DepGraphIndex prev_group;
1195 SetType group;
1197 SetType deps;
1199 FeeFrac feerate;
1200 };
1201
1202 // As an example, consider the state corresponding to the linearization [1,0,3,2], with
1203 // groups [1,0,3] and [2], in an odd pass. The linked lists would be:
1204 //
1205 // +-----+
1206 // 0<-P-- | 0 S | ---\ Legend:
1207 // +-----+ |
1208 // ^ | - digit in box: entries index
1209 // /--------------F---------+ G | (note: one more than tx value)
1210 // v \ | | - S: sentinel group
1211 // +-----+ +-----+ +-----+ | (empty feerate)
1212 // 0<-P-- | 2 | <--P-- | 1 | <--P-- | 4 T | | - T: tail transaction, contains
1213 // +-----+ +-----+ +-----+ | fields beyond prev_tv.
1214 // ^ | - P: prev_tx reference
1215 // G G - F: first_tx reference
1216 // | | - G: prev_group reference
1217 // +-----+ |
1218 // 0<-P-- | 3 T | <--/
1219 // +-----+
1220 // ^ |
1221 // \-F-/
1222 //
1223 // During an even pass, the diagram above would correspond to linearization [2,3,0,1], with
1224 // groups [2] and [3,0,1].
1225
1226 std::vector<TxEntry> entries(depgraph.PositionRange() + 1);
1227
1228 // Perform two passes over the linearization.
1229 for (int pass = 0; pass < 2; ++pass) {
1230 int rev = !(pass & 1);
1231 // Construct a sentinel group, identifying the start of the list.
1232 entries[SENTINEL].prev_group = SENTINEL;
1233 Assume(entries[SENTINEL].feerate.IsEmpty());
1234
1235 // Iterate over all elements in the existing linearization.
1236 for (DepGraphIndex i = 0; i < linearization.size(); ++i) {
1237 // Even passes are from back to front; odd passes from front to back.
1238 DepGraphIndex idx = linearization[rev ? linearization.size() - 1 - i : i];
1239 // Construct a new group containing just idx. In even passes, the meaning of
1240 // parent/child and high/low feerate are swapped.
1241 DepGraphIndex cur_group = idx + 1;
1242 entries[cur_group].group = SetType::Singleton(idx);
1243 entries[cur_group].deps = rev ? depgraph.Descendants(idx): depgraph.Ancestors(idx);
1244 entries[cur_group].feerate = depgraph.FeeRate(idx);
1245 if (rev) entries[cur_group].feerate.fee = -entries[cur_group].feerate.fee;
1246 entries[cur_group].prev_tx = NO_PREV_TX; // No previous transaction in group.
1247 entries[cur_group].first_tx = cur_group; // Transaction itself is first of group.
1248 // Insert the new group at the back of the groups linked list.
1249 entries[cur_group].prev_group = entries[SENTINEL].prev_group;
1250 entries[SENTINEL].prev_group = cur_group;
1251
1252 // Start merge/swap cycle.
1253 DepGraphIndex next_group = SENTINEL; // We inserted at the end, so next group is sentinel.
1254 DepGraphIndex prev_group = entries[cur_group].prev_group;
1255 // Continue as long as the current group has higher feerate than the previous one.
1256 while (entries[cur_group].feerate >> entries[prev_group].feerate) {
1257 // prev_group/cur_group/next_group refer to (the last transactions of) 3
1258 // consecutive entries in groups list.
1259 Assume(cur_group == entries[next_group].prev_group);
1260 Assume(prev_group == entries[cur_group].prev_group);
1261 // The sentinel has empty feerate, which is neither higher or lower than other
1262 // feerates. Thus, the while loop we are in here guarantees that cur_group and
1263 // prev_group are not the sentinel.
1264 Assume(cur_group != SENTINEL);
1265 Assume(prev_group != SENTINEL);
1266 if (entries[cur_group].deps.Overlaps(entries[prev_group].group)) {
1267 // There is a dependency between cur_group and prev_group; merge prev_group
1268 // into cur_group. The group/deps/feerate fields of prev_group remain unchanged
1269 // but become unused.
1270 entries[cur_group].group |= entries[prev_group].group;
1271 entries[cur_group].deps |= entries[prev_group].deps;
1272 entries[cur_group].feerate += entries[prev_group].feerate;
1273 // Make the first of the current group point to the tail of the previous group.
1274 entries[entries[cur_group].first_tx].prev_tx = prev_group;
1275 // The first of the previous group becomes the first of the newly-merged group.
1276 entries[cur_group].first_tx = entries[prev_group].first_tx;
1277 // The previous group becomes whatever group was before the former one.
1278 prev_group = entries[prev_group].prev_group;
1279 entries[cur_group].prev_group = prev_group;
1280 } else {
1281 // There is no dependency between cur_group and prev_group; swap them.
1282 DepGraphIndex preprev_group = entries[prev_group].prev_group;
1283 // If PP, P, C, N were the old preprev, prev, cur, next groups, then the new
1284 // layout becomes [PP, C, P, N]. Update prev_groups to reflect that order.
1285 entries[next_group].prev_group = prev_group;
1286 entries[prev_group].prev_group = cur_group;
1287 entries[cur_group].prev_group = preprev_group;
1288 // The current group remains the same, but the groups before/after it have
1289 // changed.
1290 next_group = prev_group;
1291 prev_group = preprev_group;
1292 }
1293 }
1294 }
1295
1296 // Convert the entries back to linearization (overwriting the existing one).
1297 DepGraphIndex cur_group = entries[0].prev_group;
1298 DepGraphIndex done = 0;
1299 while (cur_group != SENTINEL) {
1300 DepGraphIndex cur_tx = cur_group;
1301 // Traverse the transactions of cur_group (from back to front), and write them in the
1302 // same order during odd passes, and reversed (front to back) in even passes.
1303 if (rev) {
1304 do {
1305 *(linearization.begin() + (done++)) = cur_tx - 1;
1306 cur_tx = entries[cur_tx].prev_tx;
1307 } while (cur_tx != NO_PREV_TX);
1308 } else {
1309 do {
1310 *(linearization.end() - (++done)) = cur_tx - 1;
1311 cur_tx = entries[cur_tx].prev_tx;
1312 } while (cur_tx != NO_PREV_TX);
1313 }
1314 cur_group = entries[cur_group].prev_group;
1315 }
1316 Assume(done == linearization.size());
1317 }
1318}
1319
1324template<typename SetType>
1325std::vector<DepGraphIndex> MergeLinearizations(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> lin1, std::span<const DepGraphIndex> lin2)
1326{
1327 Assume(lin1.size() == depgraph.TxCount());
1328 Assume(lin2.size() == depgraph.TxCount());
1329
1331 LinearizationChunking chunking1(depgraph, lin1), chunking2(depgraph, lin2);
1333 std::vector<DepGraphIndex> ret;
1334 if (depgraph.TxCount() == 0) return ret;
1335 ret.reserve(depgraph.TxCount());
1336
1337 while (true) {
1338 // As long as we are not done, both linearizations must have chunks left.
1339 Assume(chunking1.NumChunksLeft() > 0);
1340 Assume(chunking2.NumChunksLeft() > 0);
1341 // Find the set to output by taking the best remaining chunk, and then intersecting it with
1342 // prefixes of remaining chunks of the other linearization.
1343 SetInfo<SetType> best;
1344 const auto& lin1_firstchunk = chunking1.GetChunk(0);
1345 const auto& lin2_firstchunk = chunking2.GetChunk(0);
1346 if (lin2_firstchunk.feerate >> lin1_firstchunk.feerate) {
1347 best = chunking1.IntersectPrefixes(lin2_firstchunk);
1348 } else {
1349 best = chunking2.IntersectPrefixes(lin1_firstchunk);
1350 }
1351 // Append the result to the output and mark it as done.
1352 depgraph.AppendTopo(ret, best.transactions);
1353 chunking1.MarkDone(best.transactions);
1354 if (chunking1.NumChunksLeft() == 0) break;
1355 chunking2.MarkDone(best.transactions);
1356 }
1357
1358 Assume(ret.size() == depgraph.TxCount());
1359 return ret;
1360}
1361
1363template<typename SetType>
1364void FixLinearization(const DepGraph<SetType>& depgraph, std::span<DepGraphIndex> linearization) noexcept
1365{
1366 // This algorithm can be summarized as moving every element in the linearization backwards
1367 // until it is placed after all its ancestors.
1368 SetType done;
1369 const auto len = linearization.size();
1370 // Iterate over the elements of linearization from back to front (i is distance from back).
1371 for (DepGraphIndex i = 0; i < len; ++i) {
1373 DepGraphIndex elem = linearization[len - 1 - i];
1375 DepGraphIndex j = i;
1376 // Figure out which elements need to be moved before elem.
1377 SetType place_before = done & depgraph.Ancestors(elem);
1378 // Find which position to place elem in (updating j), continuously moving the elements
1379 // in between forward.
1380 while (place_before.Any()) {
1381 // j cannot be 0 here; if it was, then there was necessarily nothing earlier which
1382 // elem needs to be placed before anymore, and place_before would be empty.
1383 Assume(j > 0);
1384 auto to_swap = linearization[len - 1 - (j - 1)];
1385 place_before.Reset(to_swap);
1386 linearization[len - 1 - (j--)] = to_swap;
1387 }
1388 // Put elem in its final position and mark it as done.
1389 linearization[len - 1 - j] = elem;
1390 done.Set(elem);
1391 }
1392}
1393
1394} // namespace cluster_linearize
1395
1396#endif // BITCOIN_CLUSTER_LINEARIZE_H
#define LIFETIMEBOUND
Definition: attributes.h:16
int ret
#define Assume(val)
Assume is the identity function.
Definition: check.h:97
xoroshiro128++ PRNG.
Definition: random.h:416
bool randbool() noexcept
Generate a random boolean.
Definition: random.h:316
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.
DepGraphIndex NumRemaining() const noexcept
Count the number of remaining unlinearized transactions.
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,...
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).
DepGraph() noexcept=default
SetType m_used
Which positions are used.
const SetType & Positions() const noexcept
Get the set of transactions positions in use.
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.
Data structure encapsulating the chunking of a linearization, permitting removal of subsets.
std::span< const DepGraphIndex > m_linearization
The linearization we started from, possibly with removed prefix stripped.
DepGraphIndex NumChunksLeft() const noexcept
Determine how many chunks remain in the linearization.
const SetInfo< SetType > & GetChunk(DepGraphIndex n) const noexcept
Access a chunk.
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...
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.
DepGraphIndex m_chunks_skip
How large a prefix of m_chunks corresponds to removed transactions.
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.
LinearizationChunking(const DepGraph< SetType > &depgraph LIFETIMEBOUND, std::span< const DepGraphIndex > lin LIFETIMEBOUND) noexcept
Initialize a LinearizationSubset object for a given length of linearization.
Class encapsulating the state needed to perform search for good candidate sets.
bool AllDone() const noexcept
Check whether any unlinearized transactions remain.
std::vector< DepGraphIndex > m_sorted_to_original
m_sorted_to_original[i] is the original position that sorted transaction position i had.
std::vector< DepGraphIndex > m_original_to_sorted
m_original_to_sorted[i] is the sorted position original transaction position i has.
void MarkDone(const SetType &done) noexcept
Remove a subset of transactions from the cluster being linearized.
InsecureRandomContext m_rng
Internal RNG.
SetType OriginalToSorted(const SetType &arg) const noexcept
Given a set of transactions with original indices, get their sorted indices.
SetType m_todo
Which transactions are left to do (indices in m_sorted_depgraph's order).
SetType SortedToOriginal(const SetType &arg) const noexcept
Given a set of transactions with sorted indices, get their original indices.
SearchCandidateFinder(const DepGraph< SetType > &depgraph, uint64_t rng_seed) noexcept
Construct a candidate finder for a graph.
DepGraph< SetType > m_sorted_depgraph
Internal dependency graph for the cluster (with transactions in decreasing individual feerate order).
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::vector< FeeFrac > ChunkLinearization(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the feerates of the chunks of linearization.
std::pair< std::vector< DepGraphIndex >, bool > Linearize(const DepGraph< SetType > &depgraph, uint64_t max_iterations, uint64_t rng_seed, std::span< const DepGraphIndex > old_linearization={}) noexcept
Find or improve a linearization for a cluster.
void FixLinearization(const DepGraph< SetType > &depgraph, std::span< DepGraphIndex > linearization) noexcept
Make linearization topological, retaining its ordering where possible.
std::vector< DepGraphIndex > MergeLinearizations(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > lin1, std::span< const DepGraphIndex > lin2)
Merge two linearizations for the same cluster into one that is as good as both.
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.
static std::vector< std::string > split(const std::string &str, const std::string &delims=" \t")
Definition: subprocess.h:303
Data structure storing a fee and size, ordered by increasing fee/size.
Definition: feefrac.h:39
int64_t fee
Definition: feefrac.h:106
int32_t size
Definition: feefrac.h:107
bool IsEmpty() const noexcept
Check if this is empty (size and fee are 0).
Definition: feefrac.h:119
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.
SetInfo(const DepGraph< SetType > &depgraph, DepGraphIndex pos) noexcept
Construct a SetInfo for a given transaction in a depgraph.
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 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.