Bitcoin Core 30.99.0
P2P Digital Currency
cluster_linearize.h
Go to the documentation of this file.
1// Copyright (c) The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#ifndef BITCOIN_CLUSTER_LINEARIZE_H
6#define BITCOIN_CLUSTER_LINEARIZE_H
7
8#include <algorithm>
9#include <cstdint>
10#include <numeric>
11#include <optional>
12#include <utility>
13#include <vector>
14
15#include <memusage.h>
16#include <random.h>
17#include <span.h>
18#include <util/feefrac.h>
19#include <util/vecdeque.h>
20
22
24using DepGraphIndex = uint32_t;
25
28template<typename SetType>
30{
32 struct Entry
33 {
37 SetType ancestors;
39 SetType descendants;
40
42 friend bool operator==(const Entry&, const Entry&) noexcept = default;
43
45 Entry() noexcept = default;
47 Entry(const FeeFrac& f, const SetType& a, const SetType& d) noexcept : feerate(f), ancestors(a), descendants(d) {}
48 };
49
51 std::vector<Entry> entries;
52
54 SetType m_used;
55
56public:
58 friend bool operator==(const DepGraph& a, const DepGraph& b) noexcept
59 {
60 if (a.m_used != b.m_used) return false;
61 // Only compare the used positions within the entries vector.
62 for (auto idx : a.m_used) {
63 if (a.entries[idx] != b.entries[idx]) return false;
64 }
65 return true;
66 }
67
68 // Default constructors.
69 DepGraph() noexcept = default;
70 DepGraph(const DepGraph&) noexcept = default;
71 DepGraph(DepGraph&&) noexcept = default;
72 DepGraph& operator=(const DepGraph&) noexcept = default;
73 DepGraph& operator=(DepGraph&&) noexcept = default;
74
90 DepGraph(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> mapping, DepGraphIndex pos_range) noexcept : entries(pos_range)
91 {
92 Assume(mapping.size() == depgraph.PositionRange());
93 Assume((pos_range == 0) == (depgraph.TxCount() == 0));
94 for (DepGraphIndex i : depgraph.Positions()) {
95 auto new_idx = mapping[i];
96 Assume(new_idx < pos_range);
97 // Add transaction.
98 entries[new_idx].ancestors = SetType::Singleton(new_idx);
99 entries[new_idx].descendants = SetType::Singleton(new_idx);
100 m_used.Set(new_idx);
101 // Fill in fee and size.
102 entries[new_idx].feerate = depgraph.entries[i].feerate;
103 }
104 for (DepGraphIndex i : depgraph.Positions()) {
105 // Fill in dependencies by mapping direct parents.
106 SetType parents;
107 for (auto j : depgraph.GetReducedParents(i)) parents.Set(mapping[j]);
108 AddDependencies(parents, mapping[i]);
109 }
110 // Verify that the provided pos_range was correct (no unused positions at the end).
111 Assume(m_used.None() ? (pos_range == 0) : (pos_range == m_used.Last() + 1));
112 }
113
115 const SetType& Positions() const noexcept { return m_used; }
117 DepGraphIndex PositionRange() const noexcept { return entries.size(); }
119 auto TxCount() const noexcept { return m_used.Count(); }
121 const FeeFrac& FeeRate(DepGraphIndex i) const noexcept { return entries[i].feerate; }
123 FeeFrac& FeeRate(DepGraphIndex i) noexcept { return entries[i].feerate; }
125 const SetType& Ancestors(DepGraphIndex i) const noexcept { return entries[i].ancestors; }
127 const SetType& Descendants(DepGraphIndex i) const noexcept { return entries[i].descendants; }
128
134 DepGraphIndex AddTransaction(const FeeFrac& feefrac) noexcept
135 {
136 static constexpr auto ALL_POSITIONS = SetType::Fill(SetType::Size());
137 auto available = ALL_POSITIONS - m_used;
138 Assume(available.Any());
139 DepGraphIndex new_idx = available.First();
140 if (new_idx == entries.size()) {
141 entries.emplace_back(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
142 } else {
143 entries[new_idx] = Entry(feefrac, SetType::Singleton(new_idx), SetType::Singleton(new_idx));
144 }
145 m_used.Set(new_idx);
146 return new_idx;
147 }
148
158 void RemoveTransactions(const SetType& del) noexcept
159 {
160 m_used -= del;
161 // Remove now-unused trailing entries.
162 while (!entries.empty() && !m_used[entries.size() - 1]) {
163 entries.pop_back();
164 }
165 // Remove the deleted transactions from ancestors/descendants of other transactions. Note
166 // that the deleted positions will retain old feerate and dependency information. This does
167 // not matter as they will be overwritten by AddTransaction if they get used again.
168 for (auto& entry : entries) {
169 entry.ancestors &= m_used;
170 entry.descendants &= m_used;
171 }
172 }
173
178 void AddDependencies(const SetType& parents, DepGraphIndex child) noexcept
179 {
180 Assume(m_used[child]);
181 Assume(parents.IsSubsetOf(m_used));
182 // Compute the ancestors of parents that are not already ancestors of child.
183 SetType par_anc;
184 for (auto par : parents - Ancestors(child)) {
185 par_anc |= Ancestors(par);
186 }
187 par_anc -= Ancestors(child);
188 // Bail out if there are no such ancestors.
189 if (par_anc.None()) return;
190 // To each such ancestor, add as descendants the descendants of the child.
191 const auto& chl_des = entries[child].descendants;
192 for (auto anc_of_par : par_anc) {
193 entries[anc_of_par].descendants |= chl_des;
194 }
195 // To each descendant of the child, add those ancestors.
196 for (auto dec_of_chl : Descendants(child)) {
197 entries[dec_of_chl].ancestors |= par_anc;
198 }
199 }
200
209 SetType GetReducedParents(DepGraphIndex i) const noexcept
210 {
211 SetType parents = Ancestors(i);
212 parents.Reset(i);
213 for (auto parent : parents) {
214 if (parents[parent]) {
215 parents -= Ancestors(parent);
216 parents.Set(parent);
217 }
218 }
219 return parents;
220 }
221
230 SetType GetReducedChildren(DepGraphIndex i) const noexcept
231 {
232 SetType children = Descendants(i);
233 children.Reset(i);
234 for (auto child : children) {
235 if (children[child]) {
236 children -= Descendants(child);
237 children.Set(child);
238 }
239 }
240 return children;
241 }
242
247 FeeFrac FeeRate(const SetType& elems) const noexcept
248 {
249 FeeFrac ret;
250 for (auto pos : elems) ret += entries[pos].feerate;
251 return ret;
252 }
253
264 SetType GetConnectedComponent(const SetType& todo, DepGraphIndex tx) const noexcept
265 {
266 Assume(todo[tx]);
267 Assume(todo.IsSubsetOf(m_used));
268 auto to_add = SetType::Singleton(tx);
269 SetType ret;
270 do {
271 SetType old = ret;
272 for (auto add : to_add) {
273 ret |= Descendants(add);
274 ret |= Ancestors(add);
275 }
276 ret &= todo;
277 to_add = ret - old;
278 } while (to_add.Any());
279 return ret;
280 }
281
289 SetType FindConnectedComponent(const SetType& todo) const noexcept
290 {
291 if (todo.None()) return todo;
292 return GetConnectedComponent(todo, todo.First());
293 }
294
299 bool IsConnected(const SetType& subset) const noexcept
300 {
301 return FindConnectedComponent(subset) == subset;
302 }
303
308 bool IsConnected() const noexcept { return IsConnected(m_used); }
309
314 void AppendTopo(std::vector<DepGraphIndex>& list, const SetType& select) const noexcept
315 {
316 DepGraphIndex old_len = list.size();
317 for (auto i : select) list.push_back(i);
318 std::sort(list.begin() + old_len, list.end(), [&](DepGraphIndex a, DepGraphIndex b) noexcept {
319 const auto a_anc_count = entries[a].ancestors.Count();
320 const auto b_anc_count = entries[b].ancestors.Count();
321 if (a_anc_count != b_anc_count) return a_anc_count < b_anc_count;
322 return a < b;
323 });
324 }
325
327 bool IsAcyclic() const noexcept
328 {
329 for (auto i : Positions()) {
330 if ((Ancestors(i) & Descendants(i)) != SetType::Singleton(i)) {
331 return false;
332 }
333 }
334 return true;
335 }
336
338 void Compact() noexcept
339 {
340 entries.shrink_to_fit();
341 }
342
343 size_t DynamicMemoryUsage() const noexcept
344 {
346 }
347};
348
350template<typename SetType>
352{
357
359 SetInfo() noexcept = default;
360
362 SetInfo(const SetType& txn, const FeeFrac& fr) noexcept : transactions(txn), feerate(fr) {}
363
365 explicit SetInfo(const DepGraph<SetType>& depgraph, DepGraphIndex pos) noexcept :
366 transactions(SetType::Singleton(pos)), feerate(depgraph.FeeRate(pos)) {}
367
369 explicit SetInfo(const DepGraph<SetType>& depgraph, const SetType& txn) noexcept :
370 transactions(txn), feerate(depgraph.FeeRate(txn)) {}
371
373 void Set(const DepGraph<SetType>& depgraph, DepGraphIndex pos) noexcept
374 {
375 Assume(!transactions[pos]);
376 transactions.Set(pos);
377 feerate += depgraph.FeeRate(pos);
378 }
379
381 SetInfo& operator|=(const SetInfo& other) noexcept
382 {
383 Assume(!transactions.Overlaps(other.transactions));
384 transactions |= other.transactions;
385 feerate += other.feerate;
386 return *this;
387 }
388
391 [[nodiscard]] SetInfo Add(const DepGraph<SetType>& depgraph, const SetType& txn) const noexcept
392 {
393 return {transactions | txn, feerate + depgraph.FeeRate(txn - transactions)};
394 }
395
397 friend void swap(SetInfo& a, SetInfo& b) noexcept
398 {
399 swap(a.transactions, b.transactions);
400 swap(a.feerate, b.feerate);
401 }
402
404 friend bool operator==(const SetInfo&, const SetInfo&) noexcept = default;
405};
406
408template<typename SetType>
409std::vector<FeeFrac> ChunkLinearization(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> linearization) noexcept
410{
411 std::vector<FeeFrac> ret;
412 for (DepGraphIndex i : linearization) {
414 auto new_chunk = depgraph.FeeRate(i);
415 // As long as the new chunk has a higher feerate than the last chunk so far, absorb it.
416 while (!ret.empty() && new_chunk >> ret.back()) {
417 new_chunk += ret.back();
418 ret.pop_back();
419 }
420 // Actually move that new chunk into the chunking.
421 ret.push_back(std::move(new_chunk));
422 }
423 return ret;
424}
425
427template<typename SetType>
429{
432
434 std::span<const DepGraphIndex> m_linearization;
435
437 std::vector<SetInfo<SetType>> m_chunks;
438
441
443 SetType m_todo;
444
446 void BuildChunks() noexcept
447 {
448 // Caller must clear m_chunks.
449 Assume(m_chunks.empty());
450
451 // Chop off the initial part of m_linearization that is already done.
452 while (!m_linearization.empty() && !m_todo[m_linearization.front()]) {
453 m_linearization = m_linearization.subspan(1);
454 }
455
456 // Iterate over the remaining entries in m_linearization. This is effectively the same
457 // algorithm as ChunkLinearization, but supports skipping parts of the linearization and
458 // keeps track of the sets themselves instead of just their feerates.
459 for (auto idx : m_linearization) {
460 if (!m_todo[idx]) continue;
461 // Start with an initial chunk containing just element idx.
462 SetInfo add(m_depgraph, idx);
463 // Absorb existing final chunks into add while they have lower feerate.
464 while (!m_chunks.empty() && add.feerate >> m_chunks.back().feerate) {
465 add |= m_chunks.back();
466 m_chunks.pop_back();
467 }
468 // Remember new chunk.
469 m_chunks.push_back(std::move(add));
470 }
471 }
472
473public:
475 explicit LinearizationChunking(const DepGraph<SetType>& depgraph LIFETIMEBOUND, std::span<const DepGraphIndex> lin LIFETIMEBOUND) noexcept :
476 m_depgraph(depgraph), m_linearization(lin)
477 {
478 // Mark everything in lin as todo still.
479 for (auto i : m_linearization) m_todo.Set(i);
480 // Compute the initial chunking.
481 m_chunks.reserve(depgraph.TxCount());
482 BuildChunks();
483 }
484
486 DepGraphIndex NumChunksLeft() const noexcept { return m_chunks.size() - m_chunks_skip; }
487
489 const SetInfo<SetType>& GetChunk(DepGraphIndex n) const noexcept
490 {
491 Assume(n + m_chunks_skip < m_chunks.size());
492 return m_chunks[n + m_chunks_skip];
493 }
494
496 void MarkDone(SetType subset) noexcept
497 {
498 Assume(subset.Any());
499 Assume(subset.IsSubsetOf(m_todo));
500 m_todo -= subset;
501 if (GetChunk(0).transactions == subset) {
502 // If the newly done transactions exactly match the first chunk of the remainder of
503 // the linearization, we do not need to rechunk; just remember to skip one
504 // additional chunk.
506 // With subset marked done, some prefix of m_linearization will be done now. How long
507 // that prefix is depends on how many done elements were interspersed with subset,
508 // but at least as many transactions as there are in subset.
509 m_linearization = m_linearization.subspan(subset.Count());
510 } else {
511 // Otherwise rechunk what remains of m_linearization.
512 m_chunks.clear();
513 m_chunks_skip = 0;
514 BuildChunks();
515 }
516 }
517
528 {
529 Assume(subset.transactions.IsSubsetOf(m_todo));
530 SetInfo<SetType> accumulator;
531 // Iterate over all chunks of the remaining linearization.
532 for (DepGraphIndex i = 0; i < NumChunksLeft(); ++i) {
533 // Find what (if any) intersection the chunk has with subset.
534 const SetType to_add = GetChunk(i).transactions & subset.transactions;
535 if (to_add.Any()) {
536 // If adding that to accumulator makes us hit all of subset, we are done as no
537 // shorter intersection with higher/equal feerate exists.
538 accumulator.transactions |= to_add;
539 if (accumulator.transactions == subset.transactions) break;
540 // Otherwise update the accumulator feerate.
541 accumulator.feerate += m_depgraph.FeeRate(to_add);
542 // If that does result in something better, or something with the same feerate but
543 // smaller, return that. Even if a longer, higher-feerate intersection exists, it
544 // does not hurt to return the shorter one (the remainder of the longer intersection
545 // will generally be found in the next call to Intersect, but even if not, it is not
546 // required for the improvement guarantee this function makes).
547 if (!(accumulator.feerate << subset.feerate)) return accumulator;
548 }
549 }
550 return subset;
551 }
552};
553
563template<typename SetType>
565{
569 SetType m_todo;
571 std::vector<FeeFrac> m_ancestor_set_feerates;
572
573public:
579 m_depgraph(depgraph),
580 m_todo{depgraph.Positions()},
581 m_ancestor_set_feerates(depgraph.PositionRange())
582 {
583 // Precompute ancestor-set feerates.
584 for (DepGraphIndex i : m_depgraph.Positions()) {
586 SetType anc_to_add = m_depgraph.Ancestors(i);
587 FeeFrac anc_feerate;
588 // Reuse accumulated feerate from first ancestor, if usable.
589 Assume(anc_to_add.Any());
590 DepGraphIndex first = anc_to_add.First();
591 if (first < i) {
592 anc_feerate = m_ancestor_set_feerates[first];
593 Assume(!anc_feerate.IsEmpty());
594 anc_to_add -= m_depgraph.Ancestors(first);
595 }
596 // Add in other ancestors (which necessarily include i itself).
597 Assume(anc_to_add[i]);
598 anc_feerate += m_depgraph.FeeRate(anc_to_add);
599 // Store the result.
600 m_ancestor_set_feerates[i] = anc_feerate;
601 }
602 }
603
610 void MarkDone(SetType select) noexcept
611 {
612 Assume(select.Any());
613 Assume(select.IsSubsetOf(m_todo));
614 m_todo -= select;
615 for (auto i : select) {
616 auto feerate = m_depgraph.FeeRate(i);
617 for (auto j : m_depgraph.Descendants(i) & m_todo) {
618 m_ancestor_set_feerates[j] -= feerate;
619 }
620 }
621 }
622
624 bool AllDone() const noexcept
625 {
626 return m_todo.None();
627 }
628
631 {
632 return m_todo.Count();
633 }
634
641 {
642 Assume(!AllDone());
643 std::optional<DepGraphIndex> best;
644 for (auto i : m_todo) {
645 if (best.has_value()) {
646 Assume(!m_ancestor_set_feerates[i].IsEmpty());
647 if (!(m_ancestor_set_feerates[i] > m_ancestor_set_feerates[*best])) continue;
648 }
649 best = i;
650 }
651 Assume(best.has_value());
652 return {m_depgraph.Ancestors(*best) & m_todo, m_ancestor_set_feerates[*best]};
653 }
654};
655
665template<typename SetType>
667{
671 std::vector<DepGraphIndex> m_sorted_to_original;
673 std::vector<DepGraphIndex> m_original_to_sorted;
678 SetType m_todo;
679
681 SetType SortedToOriginal(const SetType& arg) const noexcept
682 {
683 SetType ret;
684 for (auto pos : arg) ret.Set(m_sorted_to_original[pos]);
685 return ret;
686 }
687
689 SetType OriginalToSorted(const SetType& arg) const noexcept
690 {
691 SetType ret;
692 for (auto pos : arg) ret.Set(m_original_to_sorted[pos]);
693 return ret;
694 }
695
696public:
704 SearchCandidateFinder(const DepGraph<SetType>& depgraph, uint64_t rng_seed) noexcept :
705 m_rng(rng_seed),
706 m_sorted_to_original(depgraph.TxCount()),
707 m_original_to_sorted(depgraph.PositionRange())
708 {
709 // Determine reordering mapping, by sorting by decreasing feerate. Unused positions are
710 // not included, as they will never be looked up anyway.
711 DepGraphIndex sorted_pos{0};
712 for (auto i : depgraph.Positions()) {
713 m_sorted_to_original[sorted_pos++] = i;
714 }
715 std::sort(m_sorted_to_original.begin(), m_sorted_to_original.end(), [&](auto a, auto b) {
716 auto feerate_cmp = depgraph.FeeRate(a) <=> depgraph.FeeRate(b);
717 if (feerate_cmp == 0) return a < b;
718 return feerate_cmp > 0;
719 });
720 // Compute reverse mapping.
721 for (DepGraphIndex i = 0; i < m_sorted_to_original.size(); ++i) {
723 }
724 // Compute reordered dependency graph.
726 m_todo = m_sorted_depgraph.Positions();
727 }
728
730 bool AllDone() const noexcept
731 {
732 return m_todo.None();
733 }
734
752 std::pair<SetInfo<SetType>, uint64_t> FindCandidateSet(uint64_t max_iterations, SetInfo<SetType> best) noexcept
753 {
754 Assume(!AllDone());
755
756 // Convert the provided best to internal sorted indices.
757 best.transactions = OriginalToSorted(best.transactions);
758
760 struct WorkItem
761 {
768 SetType und;
775 FeeFrac pot_feerate;
776
778 WorkItem(SetInfo<SetType>&& i, SetType&& u, FeeFrac&& p_f) noexcept :
779 inc(std::move(i)), und(std::move(u)), pot_feerate(std::move(p_f))
780 {
781 Assume(pot_feerate.IsEmpty() == inc.feerate.IsEmpty());
782 }
783
785 void Swap(WorkItem& other) noexcept
786 {
787 swap(inc, other.inc);
788 swap(und, other.und);
789 swap(pot_feerate, other.pot_feerate);
790 }
791 };
792
794 VecDeque<WorkItem> queue;
795 queue.reserve(std::max<size_t>(256, 2 * m_todo.Count()));
796
797 // Create initial entries per connected component of m_todo. While clusters themselves are
798 // generally connected, this is not necessarily true after some parts have already been
799 // removed from m_todo. Without this, effort can be wasted on searching "inc" sets that
800 // span multiple components.
801 auto to_cover = m_todo;
802 do {
803 auto component = m_sorted_depgraph.FindConnectedComponent(to_cover);
804 to_cover -= component;
805 // If best is not provided, set it to the first component, so that during the work
806 // processing loop below, and during the add_fn/split_fn calls, we do not need to deal
807 // with the best=empty case.
808 if (best.feerate.IsEmpty()) best = SetInfo(m_sorted_depgraph, component);
809 queue.emplace_back(/*inc=*/SetInfo<SetType>{},
810 /*und=*/std::move(component),
811 /*pot_feerate=*/FeeFrac{});
812 } while (to_cover.Any());
813
815 uint64_t iterations_left = max_iterations;
816
818 SetType imp = m_todo;
819 while (imp.Any()) {
820 DepGraphIndex check = imp.Last();
821 if (m_sorted_depgraph.FeeRate(check) >> best.feerate) break;
822 imp.Reset(check);
823 }
824
832 auto add_fn = [&](SetInfo<SetType> inc, SetType und) noexcept {
835 auto pot = inc;
836 if (!inc.feerate.IsEmpty()) {
837 // Add entries to pot. We iterate over all undecided transactions whose feerate is
838 // higher than best. While undecided transactions of lower feerate may improve pot,
839 // the resulting pot feerate cannot possibly exceed best's (and this item will be
840 // skipped in split_fn anyway).
841 for (auto pos : imp & und) {
842 // Determine if adding transaction pos to pot (ignoring topology) would improve
843 // it. If not, we're done updating pot. This relies on the fact that
844 // m_sorted_depgraph, and thus the transactions iterated over, are in decreasing
845 // individual feerate order.
846 if (!(m_sorted_depgraph.FeeRate(pos) >> pot.feerate)) break;
847 pot.Set(m_sorted_depgraph, pos);
848 }
849
850 // The "jump ahead" optimization: whenever pot has a topologically-valid subset,
851 // that subset can be added to inc. Any subset of (pot - inc) has the property that
852 // its feerate exceeds that of any set compatible with this work item (superset of
853 // inc, subset of (inc | und)). Thus, if T is a topological subset of pot, and B is
854 // the best topologically-valid set compatible with this work item, and (T - B) is
855 // non-empty, then (T | B) is better than B and also topological. This is in
856 // contradiction with the assumption that B is best. Thus, (T - B) must be empty,
857 // or T must be a subset of B.
858 //
859 // See https://delvingbitcoin.org/t/how-to-linearize-your-cluster/303 section 2.4.
860 const auto init_inc = inc.transactions;
861 for (auto pos : pot.transactions - inc.transactions) {
862 // If the transaction's ancestors are a subset of pot, we can add it together
863 // with its ancestors to inc. Just update the transactions here; the feerate
864 // update happens below.
865 auto anc_todo = m_sorted_depgraph.Ancestors(pos) & m_todo;
866 if (anc_todo.IsSubsetOf(pot.transactions)) inc.transactions |= anc_todo;
867 }
868 // Finally update und and inc's feerate to account for the added transactions.
869 und -= inc.transactions;
870 inc.feerate += m_sorted_depgraph.FeeRate(inc.transactions - init_inc);
871
872 // If inc's feerate is better than best's, remember it as our new best.
873 if (inc.feerate > best.feerate) {
874 best = inc;
875 // See if we can remove any entries from imp now.
876 while (imp.Any()) {
877 DepGraphIndex check = imp.Last();
878 if (m_sorted_depgraph.FeeRate(check) >> best.feerate) break;
879 imp.Reset(check);
880 }
881 }
882
883 // If no potential transactions exist beyond the already included ones, no
884 // improvement is possible anymore.
885 if (pot.feerate.size == inc.feerate.size) return;
886 // At this point und must be non-empty. If it were empty then pot would equal inc.
887 Assume(und.Any());
888 } else {
889 Assume(inc.transactions.None());
890 // If inc is empty, we just make sure there are undecided transactions left to
891 // split on.
892 if (und.None()) return;
893 }
894
895 // Actually construct a new work item on the queue. Due to the switch to DFS when queue
896 // space runs out (see below), we know that no reallocation of the queue should ever
897 // occur.
898 Assume(queue.size() < queue.capacity());
899 queue.emplace_back(/*inc=*/std::move(inc),
900 /*und=*/std::move(und),
901 /*pot_feerate=*/std::move(pot.feerate));
902 };
903
907 auto split_fn = [&](WorkItem&& elem) noexcept {
908 // Any queue element must have undecided transactions left, otherwise there is nothing
909 // to explore anymore.
910 Assume(elem.und.Any());
911 // The included and undecided set are all subsets of m_todo.
912 Assume(elem.inc.transactions.IsSubsetOf(m_todo) && elem.und.IsSubsetOf(m_todo));
913 // Included transactions cannot be undecided.
914 Assume(!elem.inc.transactions.Overlaps(elem.und));
915 // If pot is empty, then so is inc.
916 Assume(elem.inc.feerate.IsEmpty() == elem.pot_feerate.IsEmpty());
917
918 const DepGraphIndex first = elem.und.First();
919 if (!elem.inc.feerate.IsEmpty()) {
920 // If no undecided transactions remain with feerate higher than best, this entry
921 // cannot be improved beyond best.
922 if (!elem.und.Overlaps(imp)) return;
923 // We can ignore any queue item whose potential feerate isn't better than the best
924 // seen so far.
925 if (elem.pot_feerate <= best.feerate) return;
926 } else {
927 // In case inc is empty use a simpler alternative check.
928 if (m_sorted_depgraph.FeeRate(first) <= best.feerate) return;
929 }
930
931 // Decide which transaction to split on. Splitting is how new work items are added, and
932 // how progress is made. One split transaction is chosen among the queue item's
933 // undecided ones, and:
934 // - A work item is (potentially) added with that transaction plus its remaining
935 // descendants excluded (removed from the und set).
936 // - A work item is (potentially) added with that transaction plus its remaining
937 // ancestors included (added to the inc set).
938 //
939 // To decide what to split on, consider the undecided ancestors of the highest
940 // individual feerate undecided transaction. Pick the one which reduces the search space
941 // most. Let I(t) be the size of the undecided set after including t, and E(t) the size
942 // of the undecided set after excluding t. Then choose the split transaction t such
943 // that 2^I(t) + 2^E(t) is minimal, tie-breaking by highest individual feerate for t.
945 const auto select = elem.und & m_sorted_depgraph.Ancestors(first);
946 Assume(select.Any());
947 std::optional<std::pair<DepGraphIndex, DepGraphIndex>> split_counts;
948 for (auto t : select) {
949 // Call max = max(I(t), E(t)) and min = min(I(t), E(t)). Let counts = {max,min}.
950 // Sorting by the tuple counts is equivalent to sorting by 2^I(t) + 2^E(t). This
951 // expression is equal to 2^max + 2^min = 2^max * (1 + 1/2^(max - min)). The second
952 // factor (1 + 1/2^(max - min)) there is in (1,2]. Thus increasing max will always
953 // increase it, even when min decreases. Because of this, we can first sort by max.
954 std::pair<DepGraphIndex, DepGraphIndex> counts{
955 (elem.und - m_sorted_depgraph.Ancestors(t)).Count(),
956 (elem.und - m_sorted_depgraph.Descendants(t)).Count()};
957 if (counts.first < counts.second) std::swap(counts.first, counts.second);
958 // Remember the t with the lowest counts.
959 if (!split_counts.has_value() || counts < *split_counts) {
960 split = t;
961 split_counts = counts;
962 }
963 }
964 // Since there was at least one transaction in select, we must always find one.
965 Assume(split_counts.has_value());
966
967 // Add a work item corresponding to exclusion of the split transaction.
968 const auto& desc = m_sorted_depgraph.Descendants(split);
969 add_fn(/*inc=*/elem.inc,
970 /*und=*/elem.und - desc);
971
972 // Add a work item corresponding to inclusion of the split transaction.
973 const auto anc = m_sorted_depgraph.Ancestors(split) & m_todo;
974 add_fn(/*inc=*/elem.inc.Add(m_sorted_depgraph, anc),
975 /*und=*/elem.und - anc);
976
977 // Account for the performed split.
978 --iterations_left;
979 };
980
981 // Work processing loop.
982 //
983 // New work items are always added at the back of the queue, but items to process use a
984 // hybrid approach where they can be taken from the front or the back.
985 //
986 // Depth-first search (DFS) corresponds to always taking from the back of the queue. This
987 // is very memory-efficient (linear in the number of transactions). Breadth-first search
988 // (BFS) corresponds to always taking from the front, which potentially uses more memory
989 // (up to exponential in the transaction count), but seems to work better in practice.
990 //
991 // The approach here combines the two: use BFS (plus random swapping) until the queue grows
992 // too large, at which point we temporarily switch to DFS until the size shrinks again.
993 while (!queue.empty()) {
994 // Randomly swap the first two items to randomize the search order.
995 if (queue.size() > 1 && m_rng.randbool()) {
996 queue[0].Swap(queue[1]);
997 }
998
999 // Processing the first queue item, and then using DFS for everything it gives rise to,
1000 // may increase the queue size by the number of undecided elements in there, minus 1
1001 // for the first queue item being removed. Thus, only when that pushes the queue over
1002 // its capacity can we not process from the front (BFS), and should we use DFS.
1003 while (queue.size() - 1 + queue.front().und.Count() > queue.capacity()) {
1004 if (!iterations_left) break;
1005 auto elem = queue.back();
1006 queue.pop_back();
1007 split_fn(std::move(elem));
1008 }
1009
1010 // Process one entry from the front of the queue (BFS exploration)
1011 if (!iterations_left) break;
1012 auto elem = queue.front();
1013 queue.pop_front();
1014 split_fn(std::move(elem));
1015 }
1016
1017 // Return the found best set (converted to the original transaction indices), and the
1018 // number of iterations performed.
1019 best.transactions = SortedToOriginal(best.transactions);
1020 return {std::move(best), max_iterations - iterations_left};
1021 }
1022
1027 void MarkDone(const SetType& done) noexcept
1028 {
1029 const auto done_sorted = OriginalToSorted(done);
1030 Assume(done_sorted.Any());
1031 Assume(done_sorted.IsSubsetOf(m_todo));
1032 m_todo -= done_sorted;
1033 }
1034};
1035
1054template<typename SetType>
1055std::tuple<std::vector<DepGraphIndex>, bool, uint64_t> Linearize(const DepGraph<SetType>& depgraph, uint64_t max_iterations, uint64_t rng_seed, std::span<const DepGraphIndex> old_linearization = {}) noexcept
1056{
1057 Assume(old_linearization.empty() || old_linearization.size() == depgraph.TxCount());
1058 if (depgraph.TxCount() == 0) return {{}, true, 0};
1059
1060 uint64_t iterations_left = max_iterations;
1061 std::vector<DepGraphIndex> linearization;
1062
1063 AncestorCandidateFinder anc_finder(depgraph);
1064 std::optional<SearchCandidateFinder<SetType>> src_finder;
1065 linearization.reserve(depgraph.TxCount());
1066 bool optimal = true;
1067
1068 // Treat the initialization of SearchCandidateFinder as taking N^2/64 (rounded up) iterations
1069 // (largely due to the cost of constructing the internal sorted-by-feerate DepGraph inside
1070 // SearchCandidateFinder), a rough approximation based on benchmark. If we don't have that
1071 // many, don't start it.
1072 uint64_t start_iterations = (uint64_t{depgraph.TxCount()} * depgraph.TxCount() + 63) / 64;
1073 if (iterations_left > start_iterations) {
1074 iterations_left -= start_iterations;
1075 src_finder.emplace(depgraph, rng_seed);
1076 }
1077
1079 LinearizationChunking old_chunking(depgraph, old_linearization);
1080
1081 while (true) {
1082 // Find the highest-feerate prefix of the remainder of old_linearization.
1083 SetInfo<SetType> best_prefix;
1084 if (old_chunking.NumChunksLeft()) best_prefix = old_chunking.GetChunk(0);
1085
1086 // Then initialize best to be either the best remaining ancestor set, or the first chunk.
1087 auto best = anc_finder.FindCandidateSet();
1088 if (!best_prefix.feerate.IsEmpty() && best_prefix.feerate >= best.feerate) best = best_prefix;
1089
1090 uint64_t iterations_done_now = 0;
1091 uint64_t max_iterations_now = 0;
1092 if (src_finder) {
1093 // Treat the invocation of SearchCandidateFinder::FindCandidateSet() as costing N/4
1094 // up-front (rounded up) iterations (largely due to the cost of connected-component
1095 // splitting), a rough approximation based on benchmarks.
1096 uint64_t base_iterations = (anc_finder.NumRemaining() + 3) / 4;
1097 if (iterations_left > base_iterations) {
1098 // Invoke bounded search to update best, with up to half of our remaining
1099 // iterations as limit.
1100 iterations_left -= base_iterations;
1101 max_iterations_now = (iterations_left + 1) / 2;
1102 std::tie(best, iterations_done_now) = src_finder->FindCandidateSet(max_iterations_now, best);
1103 iterations_left -= iterations_done_now;
1104 }
1105 }
1106
1107 if (iterations_done_now == max_iterations_now) {
1108 optimal = false;
1109 // If the search result is not (guaranteed to be) optimal, run intersections to make
1110 // sure we don't pick something that makes us unable to reach further diagram points
1111 // of the old linearization.
1112 if (old_chunking.NumChunksLeft() > 0) {
1113 best = old_chunking.IntersectPrefixes(best);
1114 }
1115 }
1116
1117 // Add to output in topological order.
1118 depgraph.AppendTopo(linearization, best.transactions);
1119
1120 // Update state to reflect best is no longer to be linearized.
1121 anc_finder.MarkDone(best.transactions);
1122 if (anc_finder.AllDone()) break;
1123 if (src_finder) src_finder->MarkDone(best.transactions);
1124 if (old_chunking.NumChunksLeft() > 0) {
1125 old_chunking.MarkDone(best.transactions);
1126 }
1127 }
1128
1129 return {std::move(linearization), optimal, max_iterations - iterations_left};
1130}
1131
1148template<typename SetType>
1149void PostLinearize(const DepGraph<SetType>& depgraph, std::span<DepGraphIndex> linearization)
1150{
1151 // This algorithm performs a number of passes (currently 2); the even ones operate from back to
1152 // front, the odd ones from front to back. Each results in an equal-or-better linearization
1153 // than the one started from.
1154 // - One pass in either direction guarantees that the resulting chunks are connected.
1155 // - Each direction corresponds to one shape of tree being linearized optimally (forward passes
1156 // guarantee this for graphs where each transaction has at most one child; backward passes
1157 // guarantee this for graphs where each transaction has at most one parent).
1158 // - Starting with a backward pass guarantees the moved-tree property.
1159 //
1160 // During an odd (forward) pass, the high-level operation is:
1161 // - Start with an empty list of groups L=[].
1162 // - For every transaction i in the old linearization, from front to back:
1163 // - Append a new group C=[i], containing just i, to the back of L.
1164 // - While L has at least one group before C, and the group immediately before C has feerate
1165 // lower than C:
1166 // - If C depends on P:
1167 // - Merge P into C, making C the concatenation of P+C, continuing with the combined C.
1168 // - Otherwise:
1169 // - Swap P with C, continuing with the now-moved C.
1170 // - The output linearization is the concatenation of the groups in L.
1171 //
1172 // During even (backward) passes, i iterates from the back to the front of the existing
1173 // linearization, and new groups are prepended instead of appended to the list L. To enable
1174 // more code reuse, both passes append groups, but during even passes the meanings of
1175 // parent/child, and of high/low feerate are reversed, and the final concatenation is reversed
1176 // on output.
1177 //
1178 // In the implementation below, the groups are represented by singly-linked lists (pointing
1179 // from the back to the front), which are themselves organized in a singly-linked circular
1180 // list (each group pointing to its predecessor, with a special sentinel group at the front
1181 // that points back to the last group).
1182 //
1183 // Information about transaction t is stored in entries[t + 1], while the sentinel is in
1184 // entries[0].
1185
1187 static constexpr DepGraphIndex SENTINEL{0};
1189 static constexpr DepGraphIndex NO_PREV_TX{0};
1190
1191
1193 struct TxEntry
1194 {
1197 DepGraphIndex prev_tx;
1198
1199 // The fields below are only used for transactions that are the last one in a group
1200 // (referred to as tail transactions below).
1201
1203 DepGraphIndex first_tx;
1206 DepGraphIndex prev_group;
1208 SetType group;
1210 SetType deps;
1212 FeeFrac feerate;
1213 };
1214
1215 // As an example, consider the state corresponding to the linearization [1,0,3,2], with
1216 // groups [1,0,3] and [2], in an odd pass. The linked lists would be:
1217 //
1218 // +-----+
1219 // 0<-P-- | 0 S | ---\ Legend:
1220 // +-----+ |
1221 // ^ | - digit in box: entries index
1222 // /--------------F---------+ G | (note: one more than tx value)
1223 // v \ | | - S: sentinel group
1224 // +-----+ +-----+ +-----+ | (empty feerate)
1225 // 0<-P-- | 2 | <--P-- | 1 | <--P-- | 4 T | | - T: tail transaction, contains
1226 // +-----+ +-----+ +-----+ | fields beyond prev_tv.
1227 // ^ | - P: prev_tx reference
1228 // G G - F: first_tx reference
1229 // | | - G: prev_group reference
1230 // +-----+ |
1231 // 0<-P-- | 3 T | <--/
1232 // +-----+
1233 // ^ |
1234 // \-F-/
1235 //
1236 // During an even pass, the diagram above would correspond to linearization [2,3,0,1], with
1237 // groups [2] and [3,0,1].
1238
1239 std::vector<TxEntry> entries(depgraph.PositionRange() + 1);
1240
1241 // Perform two passes over the linearization.
1242 for (int pass = 0; pass < 2; ++pass) {
1243 int rev = !(pass & 1);
1244 // Construct a sentinel group, identifying the start of the list.
1245 entries[SENTINEL].prev_group = SENTINEL;
1246 Assume(entries[SENTINEL].feerate.IsEmpty());
1247
1248 // Iterate over all elements in the existing linearization.
1249 for (DepGraphIndex i = 0; i < linearization.size(); ++i) {
1250 // Even passes are from back to front; odd passes from front to back.
1251 DepGraphIndex idx = linearization[rev ? linearization.size() - 1 - i : i];
1252 // Construct a new group containing just idx. In even passes, the meaning of
1253 // parent/child and high/low feerate are swapped.
1254 DepGraphIndex cur_group = idx + 1;
1255 entries[cur_group].group = SetType::Singleton(idx);
1256 entries[cur_group].deps = rev ? depgraph.Descendants(idx): depgraph.Ancestors(idx);
1257 entries[cur_group].feerate = depgraph.FeeRate(idx);
1258 if (rev) entries[cur_group].feerate.fee = -entries[cur_group].feerate.fee;
1259 entries[cur_group].prev_tx = NO_PREV_TX; // No previous transaction in group.
1260 entries[cur_group].first_tx = cur_group; // Transaction itself is first of group.
1261 // Insert the new group at the back of the groups linked list.
1262 entries[cur_group].prev_group = entries[SENTINEL].prev_group;
1263 entries[SENTINEL].prev_group = cur_group;
1264
1265 // Start merge/swap cycle.
1266 DepGraphIndex next_group = SENTINEL; // We inserted at the end, so next group is sentinel.
1267 DepGraphIndex prev_group = entries[cur_group].prev_group;
1268 // Continue as long as the current group has higher feerate than the previous one.
1269 while (entries[cur_group].feerate >> entries[prev_group].feerate) {
1270 // prev_group/cur_group/next_group refer to (the last transactions of) 3
1271 // consecutive entries in groups list.
1272 Assume(cur_group == entries[next_group].prev_group);
1273 Assume(prev_group == entries[cur_group].prev_group);
1274 // The sentinel has empty feerate, which is neither higher or lower than other
1275 // feerates. Thus, the while loop we are in here guarantees that cur_group and
1276 // prev_group are not the sentinel.
1277 Assume(cur_group != SENTINEL);
1278 Assume(prev_group != SENTINEL);
1279 if (entries[cur_group].deps.Overlaps(entries[prev_group].group)) {
1280 // There is a dependency between cur_group and prev_group; merge prev_group
1281 // into cur_group. The group/deps/feerate fields of prev_group remain unchanged
1282 // but become unused.
1283 entries[cur_group].group |= entries[prev_group].group;
1284 entries[cur_group].deps |= entries[prev_group].deps;
1285 entries[cur_group].feerate += entries[prev_group].feerate;
1286 // Make the first of the current group point to the tail of the previous group.
1287 entries[entries[cur_group].first_tx].prev_tx = prev_group;
1288 // The first of the previous group becomes the first of the newly-merged group.
1289 entries[cur_group].first_tx = entries[prev_group].first_tx;
1290 // The previous group becomes whatever group was before the former one.
1291 prev_group = entries[prev_group].prev_group;
1292 entries[cur_group].prev_group = prev_group;
1293 } else {
1294 // There is no dependency between cur_group and prev_group; swap them.
1295 DepGraphIndex preprev_group = entries[prev_group].prev_group;
1296 // If PP, P, C, N were the old preprev, prev, cur, next groups, then the new
1297 // layout becomes [PP, C, P, N]. Update prev_groups to reflect that order.
1298 entries[next_group].prev_group = prev_group;
1299 entries[prev_group].prev_group = cur_group;
1300 entries[cur_group].prev_group = preprev_group;
1301 // The current group remains the same, but the groups before/after it have
1302 // changed.
1303 next_group = prev_group;
1304 prev_group = preprev_group;
1305 }
1306 }
1307 }
1308
1309 // Convert the entries back to linearization (overwriting the existing one).
1310 DepGraphIndex cur_group = entries[0].prev_group;
1311 DepGraphIndex done = 0;
1312 while (cur_group != SENTINEL) {
1313 DepGraphIndex cur_tx = cur_group;
1314 // Traverse the transactions of cur_group (from back to front), and write them in the
1315 // same order during odd passes, and reversed (front to back) in even passes.
1316 if (rev) {
1317 do {
1318 *(linearization.begin() + (done++)) = cur_tx - 1;
1319 cur_tx = entries[cur_tx].prev_tx;
1320 } while (cur_tx != NO_PREV_TX);
1321 } else {
1322 do {
1323 *(linearization.end() - (++done)) = cur_tx - 1;
1324 cur_tx = entries[cur_tx].prev_tx;
1325 } while (cur_tx != NO_PREV_TX);
1326 }
1327 cur_group = entries[cur_group].prev_group;
1328 }
1329 Assume(done == linearization.size());
1330 }
1331}
1332
1337template<typename SetType>
1338std::vector<DepGraphIndex> MergeLinearizations(const DepGraph<SetType>& depgraph, std::span<const DepGraphIndex> lin1, std::span<const DepGraphIndex> lin2)
1339{
1340 Assume(lin1.size() == depgraph.TxCount());
1341 Assume(lin2.size() == depgraph.TxCount());
1342
1344 LinearizationChunking chunking1(depgraph, lin1), chunking2(depgraph, lin2);
1346 std::vector<DepGraphIndex> ret;
1347 if (depgraph.TxCount() == 0) return ret;
1348 ret.reserve(depgraph.TxCount());
1349
1350 while (true) {
1351 // As long as we are not done, both linearizations must have chunks left.
1352 Assume(chunking1.NumChunksLeft() > 0);
1353 Assume(chunking2.NumChunksLeft() > 0);
1354 // Find the set to output by taking the best remaining chunk, and then intersecting it with
1355 // prefixes of remaining chunks of the other linearization.
1356 SetInfo<SetType> best;
1357 const auto& lin1_firstchunk = chunking1.GetChunk(0);
1358 const auto& lin2_firstchunk = chunking2.GetChunk(0);
1359 if (lin2_firstchunk.feerate >> lin1_firstchunk.feerate) {
1360 best = chunking1.IntersectPrefixes(lin2_firstchunk);
1361 } else {
1362 best = chunking2.IntersectPrefixes(lin1_firstchunk);
1363 }
1364 // Append the result to the output and mark it as done.
1365 depgraph.AppendTopo(ret, best.transactions);
1366 chunking1.MarkDone(best.transactions);
1367 if (chunking1.NumChunksLeft() == 0) break;
1368 chunking2.MarkDone(best.transactions);
1369 }
1370
1371 Assume(ret.size() == depgraph.TxCount());
1372 return ret;
1373}
1374
1376template<typename SetType>
1377void FixLinearization(const DepGraph<SetType>& depgraph, std::span<DepGraphIndex> linearization) noexcept
1378{
1379 // This algorithm can be summarized as moving every element in the linearization backwards
1380 // until it is placed after all its ancestors.
1381 SetType done;
1382 const auto len = linearization.size();
1383 // Iterate over the elements of linearization from back to front (i is distance from back).
1384 for (DepGraphIndex i = 0; i < len; ++i) {
1386 DepGraphIndex elem = linearization[len - 1 - i];
1388 DepGraphIndex j = i;
1389 // Figure out which elements need to be moved before elem.
1390 SetType place_before = done & depgraph.Ancestors(elem);
1391 // Find which position to place elem in (updating j), continuously moving the elements
1392 // in between forward.
1393 while (place_before.Any()) {
1394 // j cannot be 0 here; if it was, then there was necessarily nothing earlier which
1395 // elem needs to be placed before anymore, and place_before would be empty.
1396 Assume(j > 0);
1397 auto to_swap = linearization[len - 1 - (j - 1)];
1398 place_before.Reset(to_swap);
1399 linearization[len - 1 - (j--)] = to_swap;
1400 }
1401 // Put elem in its final position and mark it as done.
1402 linearization[len - 1 - j] = elem;
1403 done.Set(elem);
1404 }
1405}
1406
1407} // namespace cluster_linearize
1408
1409#endif // BITCOIN_CLUSTER_LINEARIZE_H
#define LIFETIMEBOUND
Definition: attributes.h:16
int ret
#define Assume(val)
Assume is the identity function.
Definition: check.h:127
xoroshiro128++ PRNG.
Definition: random.h:425
bool randbool() noexcept
Generate a random boolean.
Definition: random.h:325
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).
size_t DynamicMemoryUsage() const noexcept
DepGraph() noexcept=default
SetType m_used
Which positions are used.
const SetType & Positions() const noexcept
Get the set of transactions positions in use.
void Compact() noexcept
Reduce memory usage if possible.
FeeFrac FeeRate(const SetType &elems) const noexcept
Compute the aggregate feerate of a set of nodes in this graph.
void AddDependencies(const SetType &parents, DepGraphIndex child) noexcept
Modify this transaction graph, adding multiple parents to a specified child.
bool IsAcyclic() const noexcept
Check if this graph is acyclic.
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::tuple< std::vector< DepGraphIndex >, bool, uint64_t > Linearize(const DepGraph< SetType > &depgraph, uint64_t max_iterations, uint64_t rng_seed, std::span< const DepGraphIndex > old_linearization={}) 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 size_t DynamicUsage(const int8_t &v)
Dynamic memory usage for built-in types is zero.
Definition: memusage.h:31
static std::vector< std::string > split(const std::string &str, const std::string &delims=" \t")
Definition: subprocess.h:315
Data structure storing a fee and size, ordered by increasing fee/size.
Definition: feefrac.h:40
int64_t fee
Definition: feefrac.h:107
int32_t size
Definition: feefrac.h:108
bool IsEmpty() const noexcept
Check if this is empty (size and fee are 0).
Definition: feefrac.h:120
Information about a single transaction.
SetType descendants
All descendants of the transaction (including itself).
friend bool operator==(const Entry &, const Entry &) noexcept=default
Equality operator (primarily for 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.