Bitcoin Core  29.1.0
P2P Digital Currency
cluster_linearize.cpp
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 #include <cluster_linearize.h>
6 #include <random.h>
7 #include <serialize.h>
8 #include <streams.h>
9 #include <test/fuzz/fuzz.h>
12 #include <util/bitset.h>
13 #include <util/feefrac.h>
14 
15 #include <algorithm>
16 #include <stdint.h>
17 #include <vector>
18 #include <utility>
19 
20 using namespace cluster_linearize;
21 
22 namespace {
23 
29 template<typename SetType>
30 class SimpleCandidateFinder
31 {
33  const DepGraph<SetType>& m_depgraph;
35  SetType m_todo;
36 
37 public:
39  SimpleCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND) noexcept :
40  m_depgraph(depgraph), m_todo{depgraph.Positions()} {}
41 
43  void MarkDone(SetType select) noexcept { m_todo -= select; }
44 
46  bool AllDone() const noexcept { return m_todo.None(); }
47 
54  std::pair<SetInfo<SetType>, uint64_t> FindCandidateSet(uint64_t max_iterations) const noexcept
55  {
56  uint64_t iterations_left = max_iterations;
57  // Queue of work units. Each consists of:
58  // - inc: set of transactions definitely included
59  // - und: set of transactions that can be added to inc still
60  std::vector<std::pair<SetType, SetType>> queue;
61  // Initially we have just one queue element, with the entire graph in und.
62  queue.emplace_back(SetType{}, m_todo);
63  // Best solution so far.
64  SetInfo best(m_depgraph, m_todo);
65  // Process the queue.
66  while (!queue.empty() && iterations_left) {
67  --iterations_left;
68  // Pop top element of the queue.
69  auto [inc, und] = queue.back();
70  queue.pop_back();
71  // Look for a transaction to consider adding/removing.
72  bool inc_none = inc.None();
73  for (auto split : und) {
74  // If inc is empty, consider any split transaction. Otherwise only consider
75  // transactions that share ancestry with inc so far (which means only connected
76  // sets will be considered).
77  if (inc_none || inc.Overlaps(m_depgraph.Ancestors(split))) {
78  // Add a queue entry with split included.
79  SetInfo new_inc(m_depgraph, inc | (m_todo & m_depgraph.Ancestors(split)));
80  queue.emplace_back(new_inc.transactions, und - new_inc.transactions);
81  // Add a queue entry with split excluded.
82  queue.emplace_back(inc, und - m_depgraph.Descendants(split));
83  // Update statistics to account for the candidate new_inc.
84  if (new_inc.feerate > best.feerate) best = new_inc;
85  break;
86  }
87  }
88  }
89  return {std::move(best), max_iterations - iterations_left};
90  }
91 };
92 
99 template<typename SetType>
100 class ExhaustiveCandidateFinder
101 {
103  const DepGraph<SetType>& m_depgraph;
105  SetType m_todo;
106 
107 public:
109  ExhaustiveCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND) noexcept :
110  m_depgraph(depgraph), m_todo{depgraph.Positions()} {}
111 
113  void MarkDone(SetType select) noexcept { m_todo -= select; }
114 
116  bool AllDone() const noexcept { return m_todo.None(); }
117 
122  SetInfo<SetType> FindCandidateSet() const noexcept
123  {
124  // Best solution so far.
125  SetInfo<SetType> best{m_todo, m_depgraph.FeeRate(m_todo)};
126  // The number of combinations to try.
127  uint64_t limit = (uint64_t{1} << m_todo.Count()) - 1;
128  // Try the transitive closure of every non-empty subset of m_todo.
129  for (uint64_t x = 1; x < limit; ++x) {
130  // If bit number b is set in x, then the remaining ancestors of the b'th remaining
131  // transaction in m_todo are included.
132  SetType txn;
133  auto x_shifted{x};
134  for (auto i : m_todo) {
135  if (x_shifted & 1) txn |= m_depgraph.Ancestors(i);
136  x_shifted >>= 1;
137  }
138  SetInfo cur(m_depgraph, txn & m_todo);
139  if (cur.feerate > best.feerate) best = cur;
140  }
141  return best;
142  }
143 };
144 
151 template<typename SetType>
152 std::pair<std::vector<ClusterIndex>, bool> SimpleLinearize(const DepGraph<SetType>& depgraph, uint64_t max_iterations)
153 {
154  std::vector<ClusterIndex> linearization;
155  SimpleCandidateFinder finder(depgraph);
156  SetType todo = depgraph.Positions();
157  bool optimal = true;
158  while (todo.Any()) {
159  auto [candidate, iterations_done] = finder.FindCandidateSet(max_iterations);
160  if (iterations_done == max_iterations) optimal = false;
161  depgraph.AppendTopo(linearization, candidate.transactions);
162  todo -= candidate.transactions;
163  finder.MarkDone(candidate.transactions);
164  max_iterations -= iterations_done;
165  }
166  return {std::move(linearization), optimal};
167 }
168 
170 template<typename BS>
171 void MakeConnected(DepGraph<BS>& depgraph)
172 {
173  auto todo = depgraph.Positions();
174  auto comp = depgraph.FindConnectedComponent(todo);
175  Assume(depgraph.IsConnected(comp));
176  todo -= comp;
177  while (todo.Any()) {
178  auto nextcomp = depgraph.FindConnectedComponent(todo);
179  Assume(depgraph.IsConnected(nextcomp));
180  depgraph.AddDependencies(BS::Singleton(comp.Last()), nextcomp.First());
181  todo -= nextcomp;
182  comp = nextcomp;
183  }
184 }
185 
187 template<typename SetType>
188 SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& todo, SpanReader& reader)
189 {
190  uint64_t mask{0};
191  try {
192  reader >> VARINT(mask);
193  } catch(const std::ios_base::failure&) {}
194  SetType ret;
195  for (auto i : todo) {
196  if (!ret[i]) {
197  if (mask & 1) ret |= depgraph.Ancestors(i);
198  mask >>= 1;
199  }
200  }
201  return ret & todo;
202 }
203 
205 template<typename BS>
206 std::vector<ClusterIndex> ReadLinearization(const DepGraph<BS>& depgraph, SpanReader& reader)
207 {
208  std::vector<ClusterIndex> linearization;
209  TestBitSet todo = depgraph.Positions();
210  // In every iteration one topologically-valid transaction is appended to linearization.
211  while (todo.Any()) {
212  // Compute the set of transactions with no not-yet-included ancestors.
213  TestBitSet potential_next;
214  for (auto j : todo) {
215  if ((depgraph.Ancestors(j) & todo) == TestBitSet::Singleton(j)) {
216  potential_next.Set(j);
217  }
218  }
219  // There must always be one (otherwise there is a cycle in the graph).
220  assert(potential_next.Any());
221  // Read a number from reader, and interpret it as index into potential_next.
222  uint64_t idx{0};
223  try {
224  reader >> VARINT(idx);
225  } catch (const std::ios_base::failure&) {}
226  idx %= potential_next.Count();
227  // Find out which transaction that corresponds to.
228  for (auto j : potential_next) {
229  if (idx == 0) {
230  // When found, add it to linearization and remove it from todo.
231  linearization.push_back(j);
232  assert(todo[j]);
233  todo.Reset(j);
234  break;
235  }
236  --idx;
237  }
238  }
239  return linearization;
240 }
241 
242 } // namespace
243 
244 FUZZ_TARGET(clusterlin_depgraph_sim)
245 {
246  // Simulation test to verify the full behavior of DepGraph.
247 
248  FuzzedDataProvider provider(buffer.data(), buffer.size());
249 
254  std::array<std::optional<std::pair<FeeFrac, TestBitSet>>, TestBitSet::Size()> sim;
256  ClusterIndex num_tx_sim{0};
257 
259  auto idx_fn = [&]() {
260  auto offset = provider.ConsumeIntegralInRange<ClusterIndex>(0, num_tx_sim - 1);
261  for (ClusterIndex i = 0; i < sim.size(); ++i) {
262  if (!sim[i].has_value()) continue;
263  if (offset == 0) return i;
264  --offset;
265  }
266  assert(false);
267  return ClusterIndex(-1);
268  };
269 
271  auto subset_fn = [&]() {
272  auto range = (uint64_t{1} << num_tx_sim) - 1;
273  const auto mask = provider.ConsumeIntegralInRange<uint64_t>(0, range);
274  auto mask_shifted = mask;
275  TestBitSet subset;
276  for (ClusterIndex i = 0; i < sim.size(); ++i) {
277  if (!sim[i].has_value()) continue;
278  if (mask_shifted & 1) {
279  subset.Set(i);
280  }
281  mask_shifted >>= 1;
282  }
283  assert(mask_shifted == 0);
284  return subset;
285  };
286 
288  auto set_fn = [&]() {
289  auto range = (uint64_t{1} << sim.size()) - 1;
290  const auto mask = provider.ConsumeIntegralInRange<uint64_t>(0, range);
291  TestBitSet set;
292  for (ClusterIndex i = 0; i < sim.size(); ++i) {
293  if ((mask >> i) & 1) {
294  set.Set(i);
295  }
296  }
297  return set;
298  };
299 
301  auto anc_update_fn = [&]() {
302  while (true) {
303  bool updates{false};
304  for (ClusterIndex chl = 0; chl < sim.size(); ++chl) {
305  if (!sim[chl].has_value()) continue;
306  for (auto par : sim[chl]->second) {
307  if (!sim[chl]->second.IsSupersetOf(sim[par]->second)) {
308  sim[chl]->second |= sim[par]->second;
309  updates = true;
310  }
311  }
312  }
313  if (!updates) break;
314  }
315  };
316 
318  auto check_fn = [&](ClusterIndex i) {
319  // Compare used positions.
320  assert(real.Positions()[i] == sim[i].has_value());
321  if (sim[i].has_value()) {
322  // Compare feerate.
323  assert(real.FeeRate(i) == sim[i]->first);
324  // Compare ancestors (note that SanityCheck verifies correspondence between ancestors
325  // and descendants, so we can restrict ourselves to ancestors here).
326  assert(real.Ancestors(i) == sim[i]->second);
327  }
328  };
329 
330  LIMITED_WHILE(provider.remaining_bytes() > 0, 1000) {
331  uint8_t command = provider.ConsumeIntegral<uint8_t>();
332  if (num_tx_sim == 0 || ((command % 3) <= 0 && num_tx_sim < TestBitSet::Size())) {
333  // AddTransaction.
334  auto fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
335  auto size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
336  FeeFrac feerate{fee, size};
337  // Apply to DepGraph.
338  auto idx = real.AddTransaction(feerate);
339  // Verify that the returned index is correct.
340  assert(!sim[idx].has_value());
341  for (ClusterIndex i = 0; i < TestBitSet::Size(); ++i) {
342  if (!sim[i].has_value()) {
343  assert(idx == i);
344  break;
345  }
346  }
347  // Update sim.
348  sim[idx] = {feerate, TestBitSet::Singleton(idx)};
349  ++num_tx_sim;
350  continue;
351  }
352  if ((command % 3) <= 1 && num_tx_sim > 0) {
353  // AddDependencies.
354  ClusterIndex child = idx_fn();
355  auto parents = subset_fn();
356  // Apply to DepGraph.
357  real.AddDependencies(parents, child);
358  // Apply to sim.
359  sim[child]->second |= parents;
360  continue;
361  }
362  if (num_tx_sim > 0) {
363  // Remove transactions.
364  auto del = set_fn();
365  // Propagate all ancestry information before deleting anything in the simulation (as
366  // intermediary transactions may be deleted which impact connectivity).
367  anc_update_fn();
368  // Compare the state of the transactions being deleted.
369  for (auto i : del) check_fn(i);
370  // Apply to DepGraph.
371  real.RemoveTransactions(del);
372  // Apply to sim.
373  for (ClusterIndex i = 0; i < sim.size(); ++i) {
374  if (sim[i].has_value()) {
375  if (del[i]) {
376  --num_tx_sim;
377  sim[i] = std::nullopt;
378  } else {
379  sim[i]->second -= del;
380  }
381  }
382  }
383  continue;
384  }
385  // This should be unreachable (one of the 3 above actions should always be possible).
386  assert(false);
387  }
388 
389  // Compare the real obtained depgraph against the simulation.
390  anc_update_fn();
391  for (ClusterIndex i = 0; i < sim.size(); ++i) check_fn(i);
392  assert(real.TxCount() == num_tx_sim);
393  // Sanity check the result (which includes round-tripping serialization, if applicable).
394  SanityCheck(real);
395 }
396 
397 FUZZ_TARGET(clusterlin_depgraph_serialization)
398 {
399  // Verify that any deserialized depgraph is acyclic and roundtrips to an identical depgraph.
400 
401  // Construct a graph by deserializing.
402  SpanReader reader(buffer);
403  DepGraph<TestBitSet> depgraph;
404  try {
405  reader >> Using<DepGraphFormatter>(depgraph);
406  } catch (const std::ios_base::failure&) {}
407  SanityCheck(depgraph);
408 
409  // Verify the graph is a DAG.
410  assert(IsAcyclic(depgraph));
411 }
412 
413 FUZZ_TARGET(clusterlin_components)
414 {
415  // Verify the behavior of DepGraphs's FindConnectedComponent and IsConnected functions.
416 
417  // Construct a depgraph.
418  SpanReader reader(buffer);
419  DepGraph<TestBitSet> depgraph;
420  try {
421  reader >> Using<DepGraphFormatter>(depgraph);
422  } catch (const std::ios_base::failure&) {}
423 
424  TestBitSet todo = depgraph.Positions();
425  while (todo.Any()) {
426  // Find a connected component inside todo.
427  auto component = depgraph.FindConnectedComponent(todo);
428 
429  // The component must be a subset of todo and non-empty.
430  assert(component.IsSubsetOf(todo));
431  assert(component.Any());
432 
433  // If todo is the entire graph, and the entire graph is connected, then the component must
434  // be the entire graph.
435  if (todo == depgraph.Positions()) {
436  assert((component == todo) == depgraph.IsConnected());
437  }
438 
439  // If subset is connected, then component must match subset.
440  assert((component == todo) == depgraph.IsConnected(todo));
441 
442  // The component cannot have any ancestors or descendants outside of component but in todo.
443  for (auto i : component) {
444  assert((depgraph.Ancestors(i) & todo).IsSubsetOf(component));
445  assert((depgraph.Descendants(i) & todo).IsSubsetOf(component));
446  }
447 
448  // Starting from any component element, we must be able to reach every element.
449  for (auto i : component) {
450  // Start with just i as reachable.
451  TestBitSet reachable = TestBitSet::Singleton(i);
452  // Add in-todo descendants and ancestors to reachable until it does not change anymore.
453  while (true) {
454  TestBitSet new_reachable = reachable;
455  for (auto j : new_reachable) {
456  new_reachable |= depgraph.Ancestors(j) & todo;
457  new_reachable |= depgraph.Descendants(j) & todo;
458  }
459  if (new_reachable == reachable) break;
460  reachable = new_reachable;
461  }
462  // Verify that the result is the entire component.
463  assert(component == reachable);
464  }
465 
466  // Construct an arbitrary subset of todo.
467  uint64_t subset_bits{0};
468  try {
469  reader >> VARINT(subset_bits);
470  } catch (const std::ios_base::failure&) {}
471  TestBitSet subset;
472  for (ClusterIndex i : depgraph.Positions()) {
473  if (todo[i]) {
474  if (subset_bits & 1) subset.Set(i);
475  subset_bits >>= 1;
476  }
477  }
478  // Which must be non-empty.
479  if (subset.None()) subset = TestBitSet::Singleton(todo.First());
480  // Remove it from todo.
481  todo -= subset;
482  }
483 
484  // No components can be found in an empty subset.
485  assert(depgraph.FindConnectedComponent(todo).None());
486 }
487 
488 FUZZ_TARGET(clusterlin_make_connected)
489 {
490  // Verify that MakeConnected makes graphs connected.
491 
492  SpanReader reader(buffer);
493  DepGraph<TestBitSet> depgraph;
494  try {
495  reader >> Using<DepGraphFormatter>(depgraph);
496  } catch (const std::ios_base::failure&) {}
497  MakeConnected(depgraph);
498  SanityCheck(depgraph);
499  assert(depgraph.IsConnected());
500 }
501 
502 FUZZ_TARGET(clusterlin_chunking)
503 {
504  // Verify the correctness of the ChunkLinearization function.
505 
506  // Construct a graph by deserializing.
507  SpanReader reader(buffer);
508  DepGraph<TestBitSet> depgraph;
509  try {
510  reader >> Using<DepGraphFormatter>(depgraph);
511  } catch (const std::ios_base::failure&) {}
512 
513  // Read a valid linearization for depgraph.
514  auto linearization = ReadLinearization(depgraph, reader);
515 
516  // Invoke the chunking function.
517  auto chunking = ChunkLinearization(depgraph, linearization);
518 
519  // Verify that chunk feerates are monotonically non-increasing.
520  for (size_t i = 1; i < chunking.size(); ++i) {
521  assert(!(chunking[i] >> chunking[i - 1]));
522  }
523 
524  // Naively recompute the chunks (each is the highest-feerate prefix of what remains).
525  auto todo = depgraph.Positions();
526  for (const auto& chunk_feerate : chunking) {
527  assert(todo.Any());
528  SetInfo<TestBitSet> accumulator, best;
529  for (ClusterIndex idx : linearization) {
530  if (todo[idx]) {
531  accumulator.Set(depgraph, idx);
532  if (best.feerate.IsEmpty() || accumulator.feerate >> best.feerate) {
533  best = accumulator;
534  }
535  }
536  }
537  assert(chunk_feerate == best.feerate);
538  assert(best.transactions.IsSubsetOf(todo));
539  todo -= best.transactions;
540  }
541  assert(todo.None());
542 }
543 
544 FUZZ_TARGET(clusterlin_ancestor_finder)
545 {
546  // Verify that AncestorCandidateFinder works as expected.
547 
548  // Retrieve a depgraph from the fuzz input.
549  SpanReader reader(buffer);
550  DepGraph<TestBitSet> depgraph;
551  try {
552  reader >> Using<DepGraphFormatter>(depgraph);
553  } catch (const std::ios_base::failure&) {}
554 
555  AncestorCandidateFinder anc_finder(depgraph);
556  auto todo = depgraph.Positions();
557  while (todo.Any()) {
558  // Call the ancestor finder's FindCandidateSet for what remains of the graph.
559  assert(!anc_finder.AllDone());
560  assert(todo.Count() == anc_finder.NumRemaining());
561  auto best_anc = anc_finder.FindCandidateSet();
562  // Sanity check the result.
563  assert(best_anc.transactions.Any());
564  assert(best_anc.transactions.IsSubsetOf(todo));
565  assert(depgraph.FeeRate(best_anc.transactions) == best_anc.feerate);
566  assert(depgraph.IsConnected(best_anc.transactions));
567  // Check that it is topologically valid.
568  for (auto i : best_anc.transactions) {
569  assert((depgraph.Ancestors(i) & todo).IsSubsetOf(best_anc.transactions));
570  }
571 
572  // Compute all remaining ancestor sets.
573  std::optional<SetInfo<TestBitSet>> real_best_anc;
574  for (auto i : todo) {
575  SetInfo info(depgraph, todo & depgraph.Ancestors(i));
576  if (!real_best_anc.has_value() || info.feerate > real_best_anc->feerate) {
577  real_best_anc = info;
578  }
579  }
580  // The set returned by anc_finder must equal the real best ancestor sets.
581  assert(real_best_anc.has_value());
582  assert(*real_best_anc == best_anc);
583 
584  // Find a topologically valid subset of transactions to remove from the graph.
585  auto del_set = ReadTopologicalSet(depgraph, todo, reader);
586  // If we did not find anything, use best_anc itself, because we should remove something.
587  if (del_set.None()) del_set = best_anc.transactions;
588  todo -= del_set;
589  anc_finder.MarkDone(del_set);
590  }
591  assert(anc_finder.AllDone());
592  assert(anc_finder.NumRemaining() == 0);
593 }
594 
595 static constexpr auto MAX_SIMPLE_ITERATIONS = 300000;
596 
597 FUZZ_TARGET(clusterlin_search_finder)
598 {
599  // Verify that SearchCandidateFinder works as expected by sanity checking the results
600  // and comparing with the results from SimpleCandidateFinder, ExhaustiveCandidateFinder, and
601  // AncestorCandidateFinder.
602 
603  // Retrieve an RNG seed, a depgraph, and whether to make it connected, from the fuzz input.
604  SpanReader reader(buffer);
605  DepGraph<TestBitSet> depgraph;
606  uint64_t rng_seed{0};
607  uint8_t make_connected{1};
608  try {
609  reader >> Using<DepGraphFormatter>(depgraph) >> rng_seed >> make_connected;
610  } catch (const std::ios_base::failure&) {}
611  // The most complicated graphs are connected ones (other ones just split up). Optionally force
612  // the graph to be connected.
613  if (make_connected) MakeConnected(depgraph);
614 
615  // Instantiate ALL the candidate finders.
616  SearchCandidateFinder src_finder(depgraph, rng_seed);
617  SimpleCandidateFinder smp_finder(depgraph);
618  ExhaustiveCandidateFinder exh_finder(depgraph);
619  AncestorCandidateFinder anc_finder(depgraph);
620 
621  auto todo = depgraph.Positions();
622  while (todo.Any()) {
623  assert(!src_finder.AllDone());
624  assert(!smp_finder.AllDone());
625  assert(!exh_finder.AllDone());
626  assert(!anc_finder.AllDone());
627  assert(anc_finder.NumRemaining() == todo.Count());
628 
629  // For each iteration, read an iteration count limit from the fuzz input.
630  uint64_t max_iterations = 1;
631  try {
632  reader >> VARINT(max_iterations);
633  } catch (const std::ios_base::failure&) {}
634  max_iterations &= 0xfffff;
635 
636  // Read an initial subset from the fuzz input.
637  SetInfo init_best(depgraph, ReadTopologicalSet(depgraph, todo, reader));
638 
639  // Call the search finder's FindCandidateSet for what remains of the graph.
640  auto [found, iterations_done] = src_finder.FindCandidateSet(max_iterations, init_best);
641 
642  // Sanity check the result.
643  assert(iterations_done <= max_iterations);
644  assert(found.transactions.Any());
645  assert(found.transactions.IsSubsetOf(todo));
646  assert(depgraph.FeeRate(found.transactions) == found.feerate);
647  if (!init_best.feerate.IsEmpty()) assert(found.feerate >= init_best.feerate);
648  // Check that it is topologically valid.
649  for (auto i : found.transactions) {
650  assert(found.transactions.IsSupersetOf(depgraph.Ancestors(i) & todo));
651  }
652 
653  // At most 2^(N-1) iterations can be required: the maximum number of non-empty topological
654  // subsets a (connected) cluster with N transactions can have. Even when the cluster is no
655  // longer connected after removing certain transactions, this holds, because the connected
656  // components are searched separately.
657  assert(iterations_done <= (uint64_t{1} << (todo.Count() - 1)));
658  // Additionally, test that no more than sqrt(2^N)+1 iterations are required. This is just
659  // an empirical bound that seems to hold, without proof. Still, add a test for it so we
660  // can learn about counterexamples if they exist.
661  if (iterations_done >= 1 && todo.Count() <= 63) {
662  Assume((iterations_done - 1) * (iterations_done - 1) <= uint64_t{1} << todo.Count());
663  }
664 
665  // Perform quality checks only if SearchCandidateFinder claims an optimal result.
666  if (iterations_done < max_iterations) {
667  // Optimal sets are always connected.
668  assert(depgraph.IsConnected(found.transactions));
669 
670  // Compare with SimpleCandidateFinder.
671  auto [simple, simple_iters] = smp_finder.FindCandidateSet(MAX_SIMPLE_ITERATIONS);
672  assert(found.feerate >= simple.feerate);
673  if (simple_iters < MAX_SIMPLE_ITERATIONS) {
674  assert(found.feerate == simple.feerate);
675  }
676 
677  // Compare with AncestorCandidateFinder;
678  auto anc = anc_finder.FindCandidateSet();
679  assert(found.feerate >= anc.feerate);
680 
681  // Compare with ExhaustiveCandidateFinder. This quickly gets computationally expensive
682  // for large clusters (O(2^n)), so only do it for sufficiently small ones.
683  if (todo.Count() <= 12) {
684  auto exhaustive = exh_finder.FindCandidateSet();
685  assert(exhaustive.feerate == found.feerate);
686  // Also compare ExhaustiveCandidateFinder with SimpleCandidateFinder (this is
687  // primarily a test for SimpleCandidateFinder's correctness).
688  assert(exhaustive.feerate >= simple.feerate);
689  if (simple_iters < MAX_SIMPLE_ITERATIONS) {
690  assert(exhaustive.feerate == simple.feerate);
691  }
692  }
693  }
694 
695  // Find a topologically valid subset of transactions to remove from the graph.
696  auto del_set = ReadTopologicalSet(depgraph, todo, reader);
697  // If we did not find anything, use found itself, because we should remove something.
698  if (del_set.None()) del_set = found.transactions;
699  todo -= del_set;
700  src_finder.MarkDone(del_set);
701  smp_finder.MarkDone(del_set);
702  exh_finder.MarkDone(del_set);
703  anc_finder.MarkDone(del_set);
704  }
705 
706  assert(src_finder.AllDone());
707  assert(smp_finder.AllDone());
708  assert(exh_finder.AllDone());
709  assert(anc_finder.AllDone());
710  assert(anc_finder.NumRemaining() == 0);
711 }
712 
713 FUZZ_TARGET(clusterlin_linearization_chunking)
714 {
715  // Verify the behavior of LinearizationChunking.
716 
717  // Retrieve a depgraph from the fuzz input.
718  SpanReader reader(buffer);
719  DepGraph<TestBitSet> depgraph;
720  try {
721  reader >> Using<DepGraphFormatter>(depgraph);
722  } catch (const std::ios_base::failure&) {}
723 
724  // Retrieve a topologically-valid subset of depgraph.
725  auto todo = depgraph.Positions();
726  auto subset = SetInfo(depgraph, ReadTopologicalSet(depgraph, todo, reader));
727 
728  // Retrieve a valid linearization for depgraph.
729  auto linearization = ReadLinearization(depgraph, reader);
730 
731  // Construct a LinearizationChunking object, initially for the whole linearization.
732  LinearizationChunking chunking(depgraph, linearization);
733 
734  // Incrementally remove transactions from the chunking object, and check various properties at
735  // every step.
736  while (todo.Any()) {
737  assert(chunking.NumChunksLeft() > 0);
738 
739  // Construct linearization with just todo.
740  std::vector<ClusterIndex> linearization_left;
741  for (auto i : linearization) {
742  if (todo[i]) linearization_left.push_back(i);
743  }
744 
745  // Compute the chunking for linearization_left.
746  auto chunking_left = ChunkLinearization(depgraph, linearization_left);
747 
748  // Verify that it matches the feerates of the chunks of chunking.
749  assert(chunking.NumChunksLeft() == chunking_left.size());
750  for (ClusterIndex i = 0; i < chunking.NumChunksLeft(); ++i) {
751  assert(chunking.GetChunk(i).feerate == chunking_left[i]);
752  }
753 
754  // Check consistency of chunking.
755  TestBitSet combined;
756  for (ClusterIndex i = 0; i < chunking.NumChunksLeft(); ++i) {
757  const auto& chunk_info = chunking.GetChunk(i);
758  // Chunks must be non-empty.
759  assert(chunk_info.transactions.Any());
760  // Chunk feerates must be monotonically non-increasing.
761  if (i > 0) assert(!(chunk_info.feerate >> chunking.GetChunk(i - 1).feerate));
762  // Chunks must be a subset of what is left of the linearization.
763  assert(chunk_info.transactions.IsSubsetOf(todo));
764  // Chunks' claimed feerates must match their transactions' aggregate feerate.
765  assert(depgraph.FeeRate(chunk_info.transactions) == chunk_info.feerate);
766  // Chunks must be the highest-feerate remaining prefix.
767  SetInfo<TestBitSet> accumulator, best;
768  for (auto j : linearization) {
769  if (todo[j] && !combined[j]) {
770  accumulator.Set(depgraph, j);
771  if (best.feerate.IsEmpty() || accumulator.feerate > best.feerate) {
772  best = accumulator;
773  }
774  }
775  }
776  assert(best.transactions == chunk_info.transactions);
777  assert(best.feerate == chunk_info.feerate);
778  // Chunks cannot overlap.
779  assert(!chunk_info.transactions.Overlaps(combined));
780  combined |= chunk_info.transactions;
781  // Chunks must be topological.
782  for (auto idx : chunk_info.transactions) {
783  assert((depgraph.Ancestors(idx) & todo).IsSubsetOf(combined));
784  }
785  }
786  assert(combined == todo);
787 
788  // Verify the expected properties of LinearizationChunking::IntersectPrefixes:
789  auto intersect = chunking.IntersectPrefixes(subset);
790  // - Intersecting again doesn't change the result.
791  assert(chunking.IntersectPrefixes(intersect) == intersect);
792  // - The intersection is topological.
793  TestBitSet intersect_anc;
794  for (auto idx : intersect.transactions) {
795  intersect_anc |= (depgraph.Ancestors(idx) & todo);
796  }
797  assert(intersect.transactions == intersect_anc);
798  // - The claimed intersection feerate matches its transactions.
799  assert(intersect.feerate == depgraph.FeeRate(intersect.transactions));
800  // - The intersection may only be empty if its input is empty.
801  assert(intersect.transactions.Any() == subset.transactions.Any());
802  // - The intersection feerate must be as high as the input.
803  assert(intersect.feerate >= subset.feerate);
804  // - No non-empty intersection between the intersection and a prefix of the chunks of the
805  // remainder of the linearization may be better than the intersection.
806  TestBitSet prefix;
807  for (ClusterIndex i = 0; i < chunking.NumChunksLeft(); ++i) {
808  prefix |= chunking.GetChunk(i).transactions;
809  auto reintersect = SetInfo(depgraph, prefix & intersect.transactions);
810  if (!reintersect.feerate.IsEmpty()) {
811  assert(reintersect.feerate <= intersect.feerate);
812  }
813  }
814 
815  // Find a subset to remove from linearization.
816  auto done = ReadTopologicalSet(depgraph, todo, reader);
817  if (done.None()) {
818  // We need to remove a non-empty subset, so fall back to the unlinearized ancestors of
819  // the first transaction in todo if done is empty.
820  done = depgraph.Ancestors(todo.First()) & todo;
821  }
822  todo -= done;
823  chunking.MarkDone(done);
824  subset = SetInfo(depgraph, subset.transactions - done);
825  }
826 
827  assert(chunking.NumChunksLeft() == 0);
828 }
829 
830 FUZZ_TARGET(clusterlin_linearize)
831 {
832  // Verify the behavior of Linearize().
833 
834  // Retrieve an RNG seed, an iteration count, a depgraph, and whether to make it connected from
835  // the fuzz input.
836  SpanReader reader(buffer);
837  DepGraph<TestBitSet> depgraph;
838  uint64_t rng_seed{0};
839  uint64_t iter_count{0};
840  uint8_t make_connected{1};
841  try {
842  reader >> VARINT(iter_count) >> Using<DepGraphFormatter>(depgraph) >> rng_seed >> make_connected;
843  } catch (const std::ios_base::failure&) {}
844  // The most complicated graphs are connected ones (other ones just split up). Optionally force
845  // the graph to be connected.
846  if (make_connected) MakeConnected(depgraph);
847 
848  // Optionally construct an old linearization for it.
849  std::vector<ClusterIndex> old_linearization;
850  {
851  uint8_t have_old_linearization{0};
852  try {
853  reader >> have_old_linearization;
854  } catch(const std::ios_base::failure&) {}
855  if (have_old_linearization & 1) {
856  old_linearization = ReadLinearization(depgraph, reader);
857  SanityCheck(depgraph, old_linearization);
858  }
859  }
860 
861  // Invoke Linearize().
862  iter_count &= 0x7ffff;
863  auto [linearization, optimal] = Linearize(depgraph, iter_count, rng_seed, old_linearization);
864  SanityCheck(depgraph, linearization);
865  auto chunking = ChunkLinearization(depgraph, linearization);
866 
867  // Linearization must always be as good as the old one, if provided.
868  if (!old_linearization.empty()) {
869  auto old_chunking = ChunkLinearization(depgraph, old_linearization);
870  auto cmp = CompareChunks(chunking, old_chunking);
871  assert(cmp >= 0);
872  }
873 
874  // If the iteration count is sufficiently high, an optimal linearization must be found.
875  // Each linearization step can use up to 2^(k-1) iterations, with steps k=1..n. That sum is
876  // 2^n - 1.
877  const uint64_t n = depgraph.TxCount();
878  if (n <= 19 && iter_count > (uint64_t{1} << n)) {
879  assert(optimal);
880  }
881  // Additionally, if the assumption of sqrt(2^k)+1 iterations per step holds, plus ceil(k/4)
882  // start-up cost per step, plus ceil(n^2/64) start-up cost overall, we can compute the upper
883  // bound for a whole linearization (summing for k=1..n) using the Python expression
884  // [sum((k+3)//4 + int(math.sqrt(2**k)) + 1 for k in range(1, n + 1)) + (n**2 + 63) // 64 for n in range(0, 35)]:
885  static constexpr uint64_t MAX_OPTIMAL_ITERS[] = {
886  0, 4, 8, 12, 18, 26, 37, 51, 70, 97, 133, 182, 251, 346, 480, 666, 927, 1296, 1815, 2545,
887  3576, 5031, 7087, 9991, 14094, 19895, 28096, 39690, 56083, 79263, 112041, 158391, 223936,
888  316629, 447712
889  };
890  if (n < std::size(MAX_OPTIMAL_ITERS) && iter_count >= MAX_OPTIMAL_ITERS[n]) {
891  Assume(optimal);
892  }
893 
894  // If Linearize claims optimal result, run quality tests.
895  if (optimal) {
896  // It must be as good as SimpleLinearize.
897  auto [simple_linearization, simple_optimal] = SimpleLinearize(depgraph, MAX_SIMPLE_ITERATIONS);
898  SanityCheck(depgraph, simple_linearization);
899  auto simple_chunking = ChunkLinearization(depgraph, simple_linearization);
900  auto cmp = CompareChunks(chunking, simple_chunking);
901  assert(cmp >= 0);
902  // If SimpleLinearize finds the optimal result too, they must be equal (if not,
903  // SimpleLinearize is broken).
904  if (simple_optimal) assert(cmp == 0);
905 
906  // Only for very small clusters, test every topologically-valid permutation.
907  if (depgraph.TxCount() <= 7) {
908  std::vector<ClusterIndex> perm_linearization;
909  for (ClusterIndex i : depgraph.Positions()) perm_linearization.push_back(i);
910  // Iterate over all valid permutations.
911  do {
912  // Determine whether perm_linearization is topological.
913  TestBitSet perm_done;
914  bool perm_is_topo{true};
915  for (auto i : perm_linearization) {
916  perm_done.Set(i);
917  if (!depgraph.Ancestors(i).IsSubsetOf(perm_done)) {
918  perm_is_topo = false;
919  break;
920  }
921  }
922  // If so, verify that the obtained linearization is as good as the permutation.
923  if (perm_is_topo) {
924  auto perm_chunking = ChunkLinearization(depgraph, perm_linearization);
925  auto cmp = CompareChunks(chunking, perm_chunking);
926  assert(cmp >= 0);
927  }
928  } while(std::next_permutation(perm_linearization.begin(), perm_linearization.end()));
929  }
930  }
931 }
932 
933 FUZZ_TARGET(clusterlin_postlinearize)
934 {
935  // Verify expected properties of PostLinearize() on arbitrary linearizations.
936 
937  // Retrieve a depgraph from the fuzz input.
938  SpanReader reader(buffer);
939  DepGraph<TestBitSet> depgraph;
940  try {
941  reader >> Using<DepGraphFormatter>(depgraph);
942  } catch (const std::ios_base::failure&) {}
943 
944  // Retrieve a linearization from the fuzz input.
945  std::vector<ClusterIndex> linearization;
946  linearization = ReadLinearization(depgraph, reader);
947  SanityCheck(depgraph, linearization);
948 
949  // Produce a post-processed version.
950  auto post_linearization = linearization;
951  PostLinearize(depgraph, post_linearization);
952  SanityCheck(depgraph, post_linearization);
953 
954  // Compare diagrams: post-linearization cannot worsen anywhere.
955  auto chunking = ChunkLinearization(depgraph, linearization);
956  auto post_chunking = ChunkLinearization(depgraph, post_linearization);
957  auto cmp = CompareChunks(post_chunking, chunking);
958  assert(cmp >= 0);
959 
960  // Run again, things can keep improving (and never get worse)
961  auto post_post_linearization = post_linearization;
962  PostLinearize(depgraph, post_post_linearization);
963  SanityCheck(depgraph, post_post_linearization);
964  auto post_post_chunking = ChunkLinearization(depgraph, post_post_linearization);
965  cmp = CompareChunks(post_post_chunking, post_chunking);
966  assert(cmp >= 0);
967 
968  // The chunks that come out of postlinearizing are always connected.
969  LinearizationChunking linchunking(depgraph, post_linearization);
970  while (linchunking.NumChunksLeft()) {
971  assert(depgraph.IsConnected(linchunking.GetChunk(0).transactions));
972  linchunking.MarkDone(linchunking.GetChunk(0).transactions);
973  }
974 }
975 
976 FUZZ_TARGET(clusterlin_postlinearize_tree)
977 {
978  // Verify expected properties of PostLinearize() on linearizations of graphs that form either
979  // an upright or reverse tree structure.
980 
981  // Construct a direction, RNG seed, and an arbitrary graph from the fuzz input.
982  SpanReader reader(buffer);
983  uint64_t rng_seed{0};
984  DepGraph<TestBitSet> depgraph_gen;
985  uint8_t direction{0};
986  try {
987  reader >> direction >> rng_seed >> Using<DepGraphFormatter>(depgraph_gen);
988  } catch (const std::ios_base::failure&) {}
989 
990  // Now construct a new graph, copying the nodes, but leaving only the first parent (even
991  // direction) or the first child (odd direction).
992  DepGraph<TestBitSet> depgraph_tree;
993  for (ClusterIndex i = 0; i < depgraph_gen.PositionRange(); ++i) {
994  if (depgraph_gen.Positions()[i]) {
995  depgraph_tree.AddTransaction(depgraph_gen.FeeRate(i));
996  } else {
997  // For holes, add a dummy transaction which is deleted below, so that non-hole
998  // transactions retain their position.
999  depgraph_tree.AddTransaction(FeeFrac{});
1000  }
1001  }
1002  depgraph_tree.RemoveTransactions(TestBitSet::Fill(depgraph_gen.PositionRange()) - depgraph_gen.Positions());
1003 
1004  if (direction & 1) {
1005  for (ClusterIndex i = 0; i < depgraph_gen.TxCount(); ++i) {
1006  auto children = depgraph_gen.GetReducedChildren(i);
1007  if (children.Any()) {
1008  depgraph_tree.AddDependencies(TestBitSet::Singleton(i), children.First());
1009  }
1010  }
1011  } else {
1012  for (ClusterIndex i = 0; i < depgraph_gen.TxCount(); ++i) {
1013  auto parents = depgraph_gen.GetReducedParents(i);
1014  if (parents.Any()) {
1015  depgraph_tree.AddDependencies(TestBitSet::Singleton(parents.First()), i);
1016  }
1017  }
1018  }
1019 
1020  // Retrieve a linearization from the fuzz input.
1021  std::vector<ClusterIndex> linearization;
1022  linearization = ReadLinearization(depgraph_tree, reader);
1023  SanityCheck(depgraph_tree, linearization);
1024 
1025  // Produce a postlinearized version.
1026  auto post_linearization = linearization;
1027  PostLinearize(depgraph_tree, post_linearization);
1028  SanityCheck(depgraph_tree, post_linearization);
1029 
1030  // Compare diagrams.
1031  auto chunking = ChunkLinearization(depgraph_tree, linearization);
1032  auto post_chunking = ChunkLinearization(depgraph_tree, post_linearization);
1033  auto cmp = CompareChunks(post_chunking, chunking);
1034  assert(cmp >= 0);
1035 
1036  // Verify that post-linearizing again does not change the diagram. The result must be identical
1037  // as post_linearization ought to be optimal already with a tree-structured graph.
1038  auto post_post_linearization = post_linearization;
1039  PostLinearize(depgraph_tree, post_linearization);
1040  SanityCheck(depgraph_tree, post_linearization);
1041  auto post_post_chunking = ChunkLinearization(depgraph_tree, post_post_linearization);
1042  auto cmp_post = CompareChunks(post_post_chunking, post_chunking);
1043  assert(cmp_post == 0);
1044 
1045  // Try to find an even better linearization directly. This must not change the diagram for the
1046  // same reason.
1047  auto [opt_linearization, _optimal] = Linearize(depgraph_tree, 100000, rng_seed, post_linearization);
1048  auto opt_chunking = ChunkLinearization(depgraph_tree, opt_linearization);
1049  auto cmp_opt = CompareChunks(opt_chunking, post_chunking);
1050  assert(cmp_opt == 0);
1051 }
1052 
1053 FUZZ_TARGET(clusterlin_postlinearize_moved_leaf)
1054 {
1055  // Verify that taking an existing linearization, and moving a leaf to the back, potentially
1056  // increasing its fee, and then post-linearizing, results in something as good as the
1057  // original. This guarantees that in an RBF that replaces a transaction with one of the same
1058  // size but higher fee, applying the "remove conflicts, append new transaction, postlinearize"
1059  // process will never worsen linearization quality.
1060 
1061  // Construct an arbitrary graph and a fee from the fuzz input.
1062  SpanReader reader(buffer);
1063  DepGraph<TestBitSet> depgraph;
1064  int32_t fee_inc{0};
1065  try {
1066  uint64_t fee_inc_code;
1067  reader >> Using<DepGraphFormatter>(depgraph) >> VARINT(fee_inc_code);
1068  fee_inc = fee_inc_code & 0x3ffff;
1069  } catch (const std::ios_base::failure&) {}
1070  if (depgraph.TxCount() == 0) return;
1071 
1072  // Retrieve two linearizations from the fuzz input.
1073  auto lin = ReadLinearization(depgraph, reader);
1074  auto lin_leaf = ReadLinearization(depgraph, reader);
1075 
1076  // Construct a linearization identical to lin, but with the tail end of lin_leaf moved to the
1077  // back.
1078  std::vector<ClusterIndex> lin_moved;
1079  for (auto i : lin) {
1080  if (i != lin_leaf.back()) lin_moved.push_back(i);
1081  }
1082  lin_moved.push_back(lin_leaf.back());
1083 
1084  // Postlinearize lin_moved.
1085  PostLinearize(depgraph, lin_moved);
1086  SanityCheck(depgraph, lin_moved);
1087 
1088  // Compare diagrams (applying the fee delta after computing the old one).
1089  auto old_chunking = ChunkLinearization(depgraph, lin);
1090  depgraph.FeeRate(lin_leaf.back()).fee += fee_inc;
1091  auto new_chunking = ChunkLinearization(depgraph, lin_moved);
1092  auto cmp = CompareChunks(new_chunking, old_chunking);
1093  assert(cmp >= 0);
1094 }
1095 
1096 FUZZ_TARGET(clusterlin_merge)
1097 {
1098  // Construct an arbitrary graph from the fuzz input.
1099  SpanReader reader(buffer);
1100  DepGraph<TestBitSet> depgraph;
1101  try {
1102  reader >> Using<DepGraphFormatter>(depgraph);
1103  } catch (const std::ios_base::failure&) {}
1104 
1105  // Retrieve two linearizations from the fuzz input.
1106  auto lin1 = ReadLinearization(depgraph, reader);
1107  auto lin2 = ReadLinearization(depgraph, reader);
1108 
1109  // Merge the two.
1110  auto lin_merged = MergeLinearizations(depgraph, lin1, lin2);
1111 
1112  // Compute chunkings and compare.
1113  auto chunking1 = ChunkLinearization(depgraph, lin1);
1114  auto chunking2 = ChunkLinearization(depgraph, lin2);
1115  auto chunking_merged = ChunkLinearization(depgraph, lin_merged);
1116  auto cmp1 = CompareChunks(chunking_merged, chunking1);
1117  assert(cmp1 >= 0);
1118  auto cmp2 = CompareChunks(chunking_merged, chunking2);
1119  assert(cmp2 >= 0);
1120 }
#define VARINT(obj)
Definition: serialize.h:500
A set of transactions together with their aggregate feerate.
int ret
Data structure encapsulating the chunking of a linearization, permitting removal of subsets...
ClusterIndex AddTransaction(const FeeFrac &feefrac) noexcept
Add a new unconnected transaction to this transaction graph (in the first available position)...
const SetType & Positions() const noexcept
Get the set of transactions positions in use.
assert(!tx.IsCoinBase())
SetType FindConnectedComponent(const SetType &todo) const noexcept
Find some connected component within the subset "todo" of this graph.
bool AllDone() const noexcept
Check whether any unlinearized transactions remain.
void AddDependencies(const SetType &parents, ClusterIndex child) noexcept
Modify this transaction graph, adding multiple parents to a specified child.
FeeFrac feerate
Their combined fee and size.
uint32_t ClusterIndex
Data type to represent transaction indices in clusters.
const char * prefix
Definition: rest.cpp:1009
static constexpr auto MAX_SIMPLE_ITERATIONS
SetInfo< SetType > IntersectPrefixes(const SetInfo< SetType > &subset) const noexcept
Find the shortest intersection between subset and the prefixes of remaining chunks of the linearizati...
const SetType & Descendants(ClusterIndex i) const noexcept
Get the descendants of a given transaction i.
const SetInfo< SetType > & GetChunk(ClusterIndex n) const noexcept
Access a chunk.
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
void MarkDone(SetType select) noexcept
Remove a set of transactions from the set of to-be-linearized ones.
Minimal stream for reading from an existing byte array by Span.
Definition: streams.h:100
bool AllDone() const noexcept
Check whether any unlinearized transactions remain.
SetType GetReducedParents(ClusterIndex i) const noexcept
Compute the (reduced) set of parents of node i in this graph.
#define LIFETIMEBOUND
Definition: attributes.h:16
void MarkDone(SetType subset) noexcept
Remove some subset of transactions from the linearization.
bool IsConnected(const SetType &subset) const noexcept
Determine if a subset is connected.
ClusterIndex PositionRange() const noexcept
Get the range of positions in this DepGraph.
Class encapsulating the state needed to find the best remaining ancestor set.
SetType GetReducedChildren(ClusterIndex i) const noexcept
Compute the (reduced) set of children of node i in this graph.
std::pair< SetInfo< SetType >, uint64_t > FindCandidateSet(uint64_t max_iterations, SetInfo< SetType > best) noexcept
Find a high-feerate topologically-valid subset of what remains of the cluster.
bool IsEmpty() const noexcept
Check if this is empty (size and fee are 0).
Definition: feefrac.h:76
FUZZ_TARGET(clusterlin_depgraph_sim)
#define Assume(val)
Assume is the identity function.
Definition: check.h:97
std::partial_ordering CompareChunks(Span< const FeeFrac > chunks0, Span< const FeeFrac > chunks1)
Compare the feerate diagrams implied by the provided sorted chunks data.
Definition: feefrac.cpp:10
void Set(const DepGraph< SetType > &depgraph, ClusterIndex pos) noexcept
Add a transaction to this SetInfo (which must not yet be in it).
Data structure storing a fee and size, ordered by increasing fee/size.
Definition: feefrac.h:38
const auto command
ClusterIndex NumRemaining() const noexcept
Count the number of remaining unlinearized transactions.
Data structure that holds a transaction graph&#39;s preprocessed data (fee, size, ancestors, descendants).
std::pair< std::vector< ClusterIndex >, bool > Linearize(const DepGraph< SetType > &depgraph, uint64_t max_iterations, uint64_t rng_seed, Span< const ClusterIndex > old_linearization={}) noexcept
Find or improve a linearization for a cluster.
SetInfo< SetType > FindCandidateSet() const noexcept
Find the best (highest-feerate, smallest among those in case of a tie) ancestor set among the remaini...
static std::vector< std::string > split(const std::string &str, const std::string &delims=" \)
Definition: subprocess.h:303
ClusterIndex NumChunksLeft() const noexcept
Determine how many chunks remain in the linearization.
std::vector< ClusterIndex > MergeLinearizations(const DepGraph< SetType > &depgraph, Span< const ClusterIndex > lin1, Span< const ClusterIndex > lin2)
Merge two linearizations for the same cluster into one that is as good as both.
void PostLinearize(const DepGraph< SetType > &depgraph, Span< ClusterIndex > linearization)
Improve a given linearization.
const FeeFrac & FeeRate(ClusterIndex i) const noexcept
Get the feerate of a given transaction i.
std::vector< FeeFrac > ChunkLinearization(const DepGraph< SetType > &depgraph, Span< const ClusterIndex > linearization) noexcept
Compute the feerates of the chunks of linearization.
void RemoveTransactions(const SetType &del) noexcept
Remove the specified positions from this DepGraph.
uint64_t fee
const SetType & Ancestors(ClusterIndex i) const noexcept
Get the ancestors of a given transaction i.
auto TxCount() const noexcept
Get the number of transactions in the graph.
void MarkDone(const SetType &done) noexcept
Remove a subset of transactions from the cluster being linearized.
Class encapsulating the state needed to perform search for good candidate sets.
void AppendTopo(std::vector< ClusterIndex > &list, const SetType &select) const noexcept
Append the entries of select to list in a topologically valid order.