Bitcoin Core  31.0.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>
10 #include <test/fuzz/fuzz.h>
12 #include <util/bitset.h>
13 #include <util/feefrac.h>
14 
15 #include <algorithm>
16 #include <cstdint>
17 #include <utility>
18 #include <vector>
19 
20 /*
21  * The tests in this file primarily cover the candidate finder classes and linearization algorithms.
22  *
23  * <----: An implementation (at the start of the line --) is tested in the test marked with *,
24  * possibly by comparison with other implementations (at the end of the line ->).
25  * <<---: The right side is implemented using the left side.
26  *
27  * +---------------------+ +-----------+
28  * | SpanningForestState | <<-------------------- | Linearize |
29  * +---------------------+ +-----------+
30  * | |
31  * | | ^^ PRODUCTION CODE
32  * | | ||
33  * ==============================================================================================
34  * | | ||
35  * |-clusterlin_sfl* | vv TEST CODE
36  * | |
37  * \------------------------------------\ |-clusterlin_linearize*
38  * | |
39  * v v
40  * +-----------------------+ +-----------------+
41  * | SimpleCandidateFinder | <<-------------------| SimpleLinearize |
42  * +-----------------------+ +-----------------+
43  * | |
44  * |-clusterlin_simple_finder* |-clusterlin_simple_linearize*
45  * v v
46  * +---------------------------+ +---------------------+
47  * | ExhaustiveCandidateFinder | | ExhaustiveLinearize |
48  * +---------------------------+ +---------------------+
49  *
50  * More tests are included for lower-level and related functions and classes:
51  * - DepGraph tests:
52  * - clusterlin_depgraph_sim
53  * - clusterlin_depgraph_serialization
54  * - clusterlin_components
55  * - ChunkLinearization and ChunkLinearizationInfo tests:
56  * - clusterlin_chunking
57  * - PostLinearize tests:
58  * - clusterlin_postlinearize
59  * - clusterlin_postlinearize_tree
60  * - clusterlin_postlinearize_moved_leaf
61  * - MakeConnected tests (a test-only function):
62  * - clusterlin_make_connected
63  */
64 
65 using namespace cluster_linearize;
66 
67 namespace {
68 
71 template<typename SetType>
72 class SimpleCandidateFinder
73 {
75  const DepGraph<SetType>& m_depgraph;
77  SetType m_todo;
78 
79 public:
81  SimpleCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND) noexcept :
82  m_depgraph(depgraph), m_todo{depgraph.Positions()} {}
83 
85  void MarkDone(SetType select) noexcept { m_todo -= select; }
86 
88  bool AllDone() const noexcept { return m_todo.None(); }
89 
98  std::pair<SetInfo<SetType>, uint64_t> FindCandidateSet(uint64_t max_iterations) const noexcept
99  {
100  uint64_t iterations_left = max_iterations;
101  // Queue of work units. Each consists of:
102  // - inc: set of transactions definitely included
103  // - und: set of transactions that can be added to inc still
104  std::vector<std::pair<SetType, SetType>> queue;
105  // Initially we have just one queue element, with the entire graph in und.
106  queue.emplace_back(SetType{}, m_todo);
107  // Best solution so far. Initialize with the remaining ancestors of the first remaining
108  // transaction.
109  SetInfo best(m_depgraph, m_depgraph.Ancestors(m_todo.First()) & m_todo);
110  // Process the queue.
111  while (!queue.empty() && iterations_left) {
112  // Pop top element of the queue.
113  auto [inc, und] = queue.back();
114  queue.pop_back();
115  // Look for a transaction to consider adding/removing.
116  bool inc_none = inc.None();
117  for (auto split : und) {
118  // If inc is empty, consider any split transaction. Otherwise only consider
119  // transactions that share ancestry with inc so far (which means only connected
120  // sets will be considered).
121  if (inc_none || inc.Overlaps(m_depgraph.Ancestors(split))) {
122  --iterations_left;
123  // Add a queue entry with split included.
124  SetInfo new_inc(m_depgraph, inc | (m_todo & m_depgraph.Ancestors(split)));
125  queue.emplace_back(new_inc.transactions, und - new_inc.transactions);
126  // Add a queue entry with split excluded.
127  queue.emplace_back(inc, und - m_depgraph.Descendants(split));
128  // Update statistics to account for the candidate new_inc.
129  if (new_inc.feerate > best.feerate) best = new_inc;
130  break;
131  }
132  }
133  }
134  return {std::move(best), max_iterations - iterations_left};
135  }
136 };
137 
144 template<typename SetType>
145 class ExhaustiveCandidateFinder
146 {
148  const DepGraph<SetType>& m_depgraph;
150  SetType m_todo;
151 
152 public:
154  ExhaustiveCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND) noexcept :
155  m_depgraph(depgraph), m_todo{depgraph.Positions()} {}
156 
158  void MarkDone(SetType select) noexcept { m_todo -= select; }
159 
161  bool AllDone() const noexcept { return m_todo.None(); }
162 
167  SetInfo<SetType> FindCandidateSet() const noexcept
168  {
169  // Best solution so far.
170  SetInfo<SetType> best{m_todo, m_depgraph.FeeRate(m_todo)};
171  // The number of combinations to try.
172  uint64_t limit = (uint64_t{1} << m_todo.Count()) - 1;
173  // Try the transitive closure of every non-empty subset of m_todo.
174  for (uint64_t x = 1; x < limit; ++x) {
175  // If bit number b is set in x, then the remaining ancestors of the b'th remaining
176  // transaction in m_todo are included.
177  SetType txn;
178  auto x_shifted{x};
179  for (auto i : m_todo) {
180  if (x_shifted & 1) txn |= m_depgraph.Ancestors(i);
181  x_shifted >>= 1;
182  }
183  SetInfo cur(m_depgraph, txn & m_todo);
184  if (cur.feerate > best.feerate) best = cur;
185  }
186  return best;
187  }
188 };
189 
196 template<typename SetType>
197 std::pair<std::vector<DepGraphIndex>, bool> SimpleLinearize(const DepGraph<SetType>& depgraph, uint64_t max_iterations)
198 {
199  std::vector<DepGraphIndex> linearization;
200  SimpleCandidateFinder finder(depgraph);
201  SetType todo = depgraph.Positions();
202  bool optimal = true;
203  while (todo.Any()) {
204  auto [candidate, iterations_done] = finder.FindCandidateSet(max_iterations);
205  if (iterations_done == max_iterations) optimal = false;
206  depgraph.AppendTopo(linearization, candidate.transactions);
207  todo -= candidate.transactions;
208  finder.MarkDone(candidate.transactions);
209  max_iterations -= iterations_done;
210  }
211  return {std::move(linearization), optimal};
212 }
213 
221 template<typename SetType>
222 std::vector<DepGraphIndex> ExhaustiveLinearize(const DepGraph<SetType>& depgraph)
223 {
224  // The best linearization so far, and its chunking.
225  std::vector<DepGraphIndex> linearization;
226  std::vector<FeeFrac> chunking;
227 
228  std::vector<DepGraphIndex> perm_linearization;
229  // Initialize with the lexicographically-first linearization.
230  for (DepGraphIndex i : depgraph.Positions()) perm_linearization.push_back(i);
231  // Iterate over all valid permutations.
232  do {
234  DepGraphIndex topo_length{0};
235  TestBitSet perm_done;
236  while (topo_length < perm_linearization.size()) {
237  auto i = perm_linearization[topo_length];
238  perm_done.Set(i);
239  if (!depgraph.Ancestors(i).IsSubsetOf(perm_done)) break;
240  ++topo_length;
241  }
242  if (topo_length == perm_linearization.size()) {
243  // If all of perm_linearization is topological, check if it is perhaps our best
244  // linearization so far.
245  auto perm_chunking = ChunkLinearization(depgraph, perm_linearization);
246  auto cmp = CompareChunks(perm_chunking, chunking);
247  // If the diagram is better, or if it is equal but with more chunks (because we
248  // prefer minimal chunks), consider this better.
249  if (linearization.empty() || cmp > 0 || (cmp == 0 && perm_chunking.size() > chunking.size())) {
250  linearization = perm_linearization;
251  chunking = perm_chunking;
252  }
253  } else {
254  // Otherwise, fast forward to the last permutation with the same non-topological
255  // prefix.
256  auto first_non_topo = perm_linearization.begin() + topo_length;
257  assert(std::is_sorted(first_non_topo + 1, perm_linearization.end()));
258  std::reverse(first_non_topo + 1, perm_linearization.end());
259  }
260  } while(std::next_permutation(perm_linearization.begin(), perm_linearization.end()));
261 
262  return linearization;
263 }
264 
265 
267 template<typename BS>
268 void MakeConnected(DepGraph<BS>& depgraph)
269 {
270  auto todo = depgraph.Positions();
271  auto comp = depgraph.FindConnectedComponent(todo);
272  Assume(depgraph.IsConnected(comp));
273  todo -= comp;
274  while (todo.Any()) {
275  auto nextcomp = depgraph.FindConnectedComponent(todo);
276  Assume(depgraph.IsConnected(nextcomp));
277  depgraph.AddDependencies(BS::Singleton(comp.Last()), nextcomp.First());
278  todo -= nextcomp;
279  comp = nextcomp;
280  }
281 }
282 
284 template<typename SetType>
285 SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& todo, SpanReader& reader, bool non_empty)
286 {
287  // Read a bitmask from the fuzzing input. Add 1 if non_empty, so the mask is definitely not
288  // zero in that case.
289  uint64_t mask{0};
290  try {
291  reader >> VARINT(mask);
292  } catch(const std::ios_base::failure&) {}
293  if (mask != uint64_t(-1)) mask += non_empty;
294 
295  SetType ret;
296  for (auto i : todo) {
297  if (!ret[i]) {
298  if (mask & 1) ret |= depgraph.Ancestors(i);
299  mask >>= 1;
300  }
301  }
302  ret &= todo;
303 
304  // While mask starts off non-zero if non_empty is true, it is still possible that all its low
305  // bits are 0, and ret ends up being empty. As a last resort, use the in-todo ancestry of the
306  // first todo position.
307  if (non_empty && ret.None()) {
308  Assume(todo.Any());
309  ret = depgraph.Ancestors(todo.First()) & todo;
310  Assume(ret.Any());
311  }
312  return ret;
313 }
314 
316 template<typename BS>
317 std::vector<DepGraphIndex> ReadLinearization(const DepGraph<BS>& depgraph, SpanReader& reader, bool topological=true)
318 {
319  std::vector<DepGraphIndex> linearization;
320  TestBitSet todo = depgraph.Positions();
321  // In every iteration one transaction is appended to linearization.
322  while (todo.Any()) {
323  // Compute the set of transactions to select from.
324  TestBitSet potential_next;
325  if (topological) {
326  // Find all transactions with no not-yet-included ancestors.
327  for (auto j : todo) {
328  if ((depgraph.Ancestors(j) & todo) == TestBitSet::Singleton(j)) {
329  potential_next.Set(j);
330  }
331  }
332  } else {
333  // Allow any element to be selected next, regardless of topology.
334  potential_next = todo;
335  }
336  // There must always be one (otherwise there is a cycle in the graph).
337  assert(potential_next.Any());
338  // Read a number from reader, and interpret it as index into potential_next.
339  uint64_t idx{0};
340  try {
341  reader >> VARINT(idx);
342  } catch (const std::ios_base::failure&) {}
343  idx %= potential_next.Count();
344  // Find out which transaction that corresponds to.
345  for (auto j : potential_next) {
346  if (idx == 0) {
347  // When found, add it to linearization and remove it from todo.
348  linearization.push_back(j);
349  assert(todo[j]);
350  todo.Reset(j);
351  break;
352  }
353  --idx;
354  }
355  }
356  return linearization;
357 }
358 
364 template<typename BS>
365 DepGraph<BS> BuildTreeGraph(const DepGraph<BS>& depgraph, uint8_t direction)
366 {
367  DepGraph<BS> depgraph_tree;
368  for (DepGraphIndex i = 0; i < depgraph.PositionRange(); ++i) {
369  if (depgraph.Positions()[i]) {
370  depgraph_tree.AddTransaction(depgraph.FeeRate(i));
371  } else {
372  // For holes, add a dummy transaction which is deleted below, so that non-hole
373  // transactions retain their position.
374  depgraph_tree.AddTransaction(FeeFrac{});
375  }
376  }
377  depgraph_tree.RemoveTransactions(BS::Fill(depgraph.PositionRange()) - depgraph.Positions());
378 
379  if (direction & 1) {
380  for (DepGraphIndex i : depgraph.Positions()) {
381  auto children = depgraph.GetReducedChildren(i);
382  if (children.Any()) {
383  depgraph_tree.AddDependencies(BS::Singleton(i), children.First());
384  }
385  }
386  } else {
387  for (DepGraphIndex i : depgraph.Positions()) {
388  auto parents = depgraph.GetReducedParents(i);
389  if (parents.Any()) {
390  depgraph_tree.AddDependencies(BS::Singleton(parents.First()), i);
391  }
392  }
393  }
394 
395  return depgraph_tree;
396 }
397 
398 } // namespace
399 
400 FUZZ_TARGET(clusterlin_depgraph_sim)
401 {
402  // Simulation test to verify the full behavior of DepGraph.
403 
404  FuzzedDataProvider provider(buffer.data(), buffer.size());
405 
410  std::array<std::optional<std::pair<FeeFrac, TestBitSet>>, TestBitSet::Size()> sim;
412  DepGraphIndex num_tx_sim{0};
413 
415  auto idx_fn = [&]() {
416  auto offset = provider.ConsumeIntegralInRange<DepGraphIndex>(0, num_tx_sim - 1);
417  for (DepGraphIndex i = 0; i < sim.size(); ++i) {
418  if (!sim[i].has_value()) continue;
419  if (offset == 0) return i;
420  --offset;
421  }
422  assert(false);
423  return DepGraphIndex(-1);
424  };
425 
427  auto subset_fn = [&]() {
428  auto range = (uint64_t{1} << num_tx_sim) - 1;
429  const auto mask = provider.ConsumeIntegralInRange<uint64_t>(0, range);
430  auto mask_shifted = mask;
431  TestBitSet subset;
432  for (DepGraphIndex i = 0; i < sim.size(); ++i) {
433  if (!sim[i].has_value()) continue;
434  if (mask_shifted & 1) {
435  subset.Set(i);
436  }
437  mask_shifted >>= 1;
438  }
439  assert(mask_shifted == 0);
440  return subset;
441  };
442 
444  auto set_fn = [&]() {
445  auto range = (uint64_t{1} << sim.size()) - 1;
446  const auto mask = provider.ConsumeIntegralInRange<uint64_t>(0, range);
447  TestBitSet set;
448  for (DepGraphIndex i = 0; i < sim.size(); ++i) {
449  if ((mask >> i) & 1) {
450  set.Set(i);
451  }
452  }
453  return set;
454  };
455 
457  auto anc_update_fn = [&]() {
458  while (true) {
459  bool updates{false};
460  for (DepGraphIndex chl = 0; chl < sim.size(); ++chl) {
461  if (!sim[chl].has_value()) continue;
462  for (auto par : sim[chl]->second) {
463  if (!sim[chl]->second.IsSupersetOf(sim[par]->second)) {
464  sim[chl]->second |= sim[par]->second;
465  updates = true;
466  }
467  }
468  }
469  if (!updates) break;
470  }
471  };
472 
474  auto check_fn = [&](DepGraphIndex i) {
475  // Compare used positions.
476  assert(real.Positions()[i] == sim[i].has_value());
477  if (sim[i].has_value()) {
478  // Compare feerate.
479  assert(real.FeeRate(i) == sim[i]->first);
480  // Compare ancestors (note that SanityCheck verifies correspondence between ancestors
481  // and descendants, so we can restrict ourselves to ancestors here).
482  assert(real.Ancestors(i) == sim[i]->second);
483  }
484  };
485 
486  auto last_compaction_pos{real.PositionRange()};
487 
488  LIMITED_WHILE(provider.remaining_bytes() > 0, 1000) {
489  int command = provider.ConsumeIntegral<uint8_t>() % 4;
490  while (true) {
491  // Iterate decreasing command until an applicable branch is found.
492  if (num_tx_sim < TestBitSet::Size() && command-- == 0) {
493  // AddTransaction.
494  auto fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
495  auto size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
496  FeeFrac feerate{fee, size};
497  // Apply to DepGraph.
498  auto idx = real.AddTransaction(feerate);
499  // Verify that the returned index is correct.
500  assert(!sim[idx].has_value());
501  for (DepGraphIndex i = 0; i < TestBitSet::Size(); ++i) {
502  if (!sim[i].has_value()) {
503  assert(idx == i);
504  break;
505  }
506  }
507  // Update sim.
508  sim[idx] = {feerate, TestBitSet::Singleton(idx)};
509  ++num_tx_sim;
510  break;
511  } else if (num_tx_sim > 0 && command-- == 0) {
512  // AddDependencies.
513  DepGraphIndex child = idx_fn();
514  auto parents = subset_fn();
515  // Apply to DepGraph.
516  real.AddDependencies(parents, child);
517  // Apply to sim.
518  sim[child]->second |= parents;
519  break;
520  } else if (num_tx_sim > 0 && command-- == 0) {
521  // Remove transactions.
522  auto del = set_fn();
523  // Propagate all ancestry information before deleting anything in the simulation (as
524  // intermediary transactions may be deleted which impact connectivity).
525  anc_update_fn();
526  // Compare the state of the transactions being deleted.
527  for (auto i : del) check_fn(i);
528  // Apply to DepGraph.
529  real.RemoveTransactions(del);
530  // Apply to sim.
531  for (DepGraphIndex i = 0; i < sim.size(); ++i) {
532  if (sim[i].has_value()) {
533  if (del[i]) {
534  --num_tx_sim;
535  sim[i] = std::nullopt;
536  } else {
537  sim[i]->second -= del;
538  }
539  }
540  }
541  break;
542  } else if (command-- == 0) {
543  // Compact.
544  const size_t mem_before{real.DynamicMemoryUsage()};
545  real.Compact();
546  const size_t mem_after{real.DynamicMemoryUsage()};
547  assert(real.PositionRange() < last_compaction_pos ? mem_after < mem_before : mem_after <= mem_before);
548  last_compaction_pos = real.PositionRange();
549  break;
550  }
551  }
552  }
553 
554  // Compare the real obtained depgraph against the simulation.
555  anc_update_fn();
556  for (DepGraphIndex i = 0; i < sim.size(); ++i) check_fn(i);
557  assert(real.TxCount() == num_tx_sim);
558  // Sanity check the result (which includes round-tripping serialization, if applicable).
559  SanityCheck(real);
560 }
561 
562 FUZZ_TARGET(clusterlin_depgraph_serialization)
563 {
564  // Verify that any deserialized depgraph is acyclic and roundtrips to an identical depgraph.
565 
566  // Construct a graph by deserializing.
567  SpanReader reader(buffer);
568  DepGraph<TestBitSet> depgraph;
569  DepGraphIndex par_code{0}, chl_code{0};
570  try {
571  reader >> Using<DepGraphFormatter>(depgraph) >> VARINT(par_code) >> VARINT(chl_code);
572  } catch (const std::ios_base::failure&) {}
573  SanityCheck(depgraph);
574 
575  // Verify the graph is a DAG.
576  assert(depgraph.IsAcyclic());
577 
578  // Introduce a cycle, and then test that IsAcyclic returns false.
579  if (depgraph.TxCount() < 2) return;
580  DepGraphIndex par(0), chl(0);
581  // Pick any transaction of depgraph as parent.
582  par_code %= depgraph.TxCount();
583  for (auto i : depgraph.Positions()) {
584  if (par_code == 0) {
585  par = i;
586  break;
587  }
588  --par_code;
589  }
590  // Pick any ancestor of par (excluding itself) as child, if any.
591  auto ancestors = depgraph.Ancestors(par) - TestBitSet::Singleton(par);
592  if (ancestors.None()) return;
593  chl_code %= ancestors.Count();
594  for (auto i : ancestors) {
595  if (chl_code == 0) {
596  chl = i;
597  break;
598  }
599  --chl_code;
600  }
601  // Add the cycle-introducing dependency.
602  depgraph.AddDependencies(TestBitSet::Singleton(par), chl);
603  // Check that we now detect a cycle.
604  assert(!depgraph.IsAcyclic());
605 }
606 
607 FUZZ_TARGET(clusterlin_components)
608 {
609  // Verify the behavior of DepGraphs's FindConnectedComponent and IsConnected functions.
610 
611  // Construct a depgraph.
612  SpanReader reader(buffer);
613  DepGraph<TestBitSet> depgraph;
614  std::vector<DepGraphIndex> linearization;
615  try {
616  reader >> Using<DepGraphFormatter>(depgraph);
617  } catch (const std::ios_base::failure&) {}
618 
619  TestBitSet todo = depgraph.Positions();
620  while (todo.Any()) {
621  // Pick a transaction in todo, or nothing.
622  std::optional<DepGraphIndex> picked;
623  {
624  uint64_t picked_num{0};
625  try {
626  reader >> VARINT(picked_num);
627  } catch (const std::ios_base::failure&) {}
628  if (picked_num < todo.Size() && todo[picked_num]) {
629  picked = picked_num;
630  }
631  }
632 
633  // Find a connected component inside todo, including picked if any.
634  auto component = picked ? depgraph.GetConnectedComponent(todo, *picked)
635  : depgraph.FindConnectedComponent(todo);
636 
637  // The component must be a subset of todo and non-empty.
638  assert(component.IsSubsetOf(todo));
639  assert(component.Any());
640 
641  // If picked was provided, the component must include it.
642  if (picked) assert(component[*picked]);
643 
644  // If todo is the entire graph, and the entire graph is connected, then the component must
645  // be the entire graph.
646  if (todo == depgraph.Positions()) {
647  assert((component == todo) == depgraph.IsConnected());
648  }
649 
650  // If subset is connected, then component must match subset.
651  assert((component == todo) == depgraph.IsConnected(todo));
652 
653  // The component cannot have any ancestors or descendants outside of component but in todo.
654  for (auto i : component) {
655  assert((depgraph.Ancestors(i) & todo).IsSubsetOf(component));
656  assert((depgraph.Descendants(i) & todo).IsSubsetOf(component));
657  }
658 
659  // Starting from any component element, we must be able to reach every element.
660  for (auto i : component) {
661  // Start with just i as reachable.
662  TestBitSet reachable = TestBitSet::Singleton(i);
663  // Add in-todo descendants and ancestors to reachable until it does not change anymore.
664  while (true) {
665  TestBitSet new_reachable = reachable;
666  for (auto j : new_reachable) {
667  new_reachable |= depgraph.Ancestors(j) & todo;
668  new_reachable |= depgraph.Descendants(j) & todo;
669  }
670  if (new_reachable == reachable) break;
671  reachable = new_reachable;
672  }
673  // Verify that the result is the entire component.
674  assert(component == reachable);
675  }
676 
677  // Construct an arbitrary subset of todo.
678  uint64_t subset_bits{0};
679  try {
680  reader >> VARINT(subset_bits);
681  } catch (const std::ios_base::failure&) {}
682  TestBitSet subset;
683  for (DepGraphIndex i : depgraph.Positions()) {
684  if (todo[i]) {
685  if (subset_bits & 1) subset.Set(i);
686  subset_bits >>= 1;
687  }
688  }
689  // Which must be non-empty.
690  if (subset.None()) subset = TestBitSet::Singleton(todo.First());
691  // Remove it from todo.
692  todo -= subset;
693  }
694 
695  // No components can be found in an empty subset.
696  assert(depgraph.FindConnectedComponent(todo).None());
697 }
698 
699 FUZZ_TARGET(clusterlin_make_connected)
700 {
701  // Verify that MakeConnected makes graphs connected.
702 
703  SpanReader reader(buffer);
704  DepGraph<TestBitSet> depgraph;
705  try {
706  reader >> Using<DepGraphFormatter>(depgraph);
707  } catch (const std::ios_base::failure&) {}
708  MakeConnected(depgraph);
709  SanityCheck(depgraph);
710  assert(depgraph.IsConnected());
711 }
712 
713 FUZZ_TARGET(clusterlin_chunking)
714 {
715  // Verify the correctness of the ChunkLinearization function.
716 
717  // Construct a graph by deserializing.
718  SpanReader reader(buffer);
719  DepGraph<TestBitSet> depgraph;
720  try {
721  reader >> Using<DepGraphFormatter>(depgraph);
722  } catch (const std::ios_base::failure&) {}
723 
724  // Read a valid linearization for depgraph.
725  auto linearization = ReadLinearization(depgraph, reader);
726 
727  // Invoke the chunking functions.
728  auto chunking = ChunkLinearization(depgraph, linearization);
729  auto chunking_info = ChunkLinearizationInfo(depgraph, linearization);
730 
731  // Verify consistency between the two functions.
732  assert(chunking.size() == chunking_info.size());
733  for (size_t i = 0; i < chunking.size(); ++i) {
734  assert(chunking[i] == chunking_info[i].feerate);
735  assert(SetInfo(depgraph, chunking_info[i].transactions) == chunking_info[i]);
736  }
737 
738  // Verify that chunk feerates are monotonically non-increasing.
739  for (size_t i = 1; i < chunking.size(); ++i) {
740  assert(!(chunking[i] >> chunking[i - 1]));
741  }
742 
743  // Naively recompute the chunks (each is the highest-feerate prefix of what remains).
744  auto todo = depgraph.Positions();
745  for (const auto& [chunk_set, chunk_feerate] : chunking_info) {
746  assert(todo.Any());
747  SetInfo<TestBitSet> accumulator, best;
748  for (DepGraphIndex idx : linearization) {
749  if (todo[idx]) {
750  accumulator.Set(depgraph, idx);
751  if (best.feerate.IsEmpty() || accumulator.feerate >> best.feerate) {
752  best = accumulator;
753  }
754  }
755  }
756  assert(chunk_feerate == best.feerate);
757  assert(chunk_set == best.transactions);
758  assert(best.transactions.IsSubsetOf(todo));
759  todo -= best.transactions;
760  }
761  assert(todo.None());
762 }
763 
764 static constexpr auto MAX_SIMPLE_ITERATIONS = 300000;
765 
766 FUZZ_TARGET(clusterlin_simple_finder)
767 {
768  // Verify that SimpleCandidateFinder works as expected by sanity checking the results
769  // and comparing them (if claimed to be optimal) against the sets found by
770  // ExhaustiveCandidateFinder.
771  //
772  // Note that SimpleCandidateFinder is only used in tests; the purpose of this fuzz test is to
773  // establish confidence in SimpleCandidateFinder, so that it can be used in SimpleLinearize,
774  // which is then used to test Linearize below.
775 
776  // Retrieve a depgraph from the fuzz input.
777  SpanReader reader(buffer);
778  DepGraph<TestBitSet> depgraph;
779  try {
780  reader >> Using<DepGraphFormatter>(depgraph);
781  } catch (const std::ios_base::failure&) {}
782 
783  // Instantiate the SimpleCandidateFinder to be tested, and the ExhaustiveCandidateFinder it is
784  // being tested against.
785  SimpleCandidateFinder smp_finder(depgraph);
786  ExhaustiveCandidateFinder exh_finder(depgraph);
787 
788  auto todo = depgraph.Positions();
789  while (todo.Any()) {
790  assert(!smp_finder.AllDone());
791  assert(!exh_finder.AllDone());
792 
793  // Call SimpleCandidateFinder.
794  auto [found, iterations_done] = smp_finder.FindCandidateSet(MAX_SIMPLE_ITERATIONS);
795  bool optimal = (iterations_done != MAX_SIMPLE_ITERATIONS);
796 
797  // Sanity check the result.
798  assert(iterations_done <= MAX_SIMPLE_ITERATIONS);
799  assert(found.transactions.Any());
800  assert(found.transactions.IsSubsetOf(todo));
801  assert(depgraph.FeeRate(found.transactions) == found.feerate);
802  // Check that it is topologically valid.
803  for (auto i : found.transactions) {
804  assert(found.transactions.IsSupersetOf(depgraph.Ancestors(i) & todo));
805  }
806 
807  // At most 2^(N-1) iterations can be required: the number of non-empty connected subsets a
808  // graph with N transactions can have. If MAX_SIMPLE_ITERATIONS exceeds this number, the
809  // result is necessarily optimal.
810  assert(iterations_done <= (uint64_t{1} << (todo.Count() - 1)));
811  if (MAX_SIMPLE_ITERATIONS > (uint64_t{1} << (todo.Count() - 1))) assert(optimal);
812 
813  // SimpleCandidateFinder only finds connected sets.
814  assert(depgraph.IsConnected(found.transactions));
815 
816  // Perform further quality checks only if SimpleCandidateFinder claims an optimal result.
817  if (optimal) {
818  if (todo.Count() <= 12) {
819  // Compare with ExhaustiveCandidateFinder. This quickly gets computationally
820  // expensive for large clusters (O(2^n)), so only do it for sufficiently small ones.
821  auto exhaustive = exh_finder.FindCandidateSet();
822  assert(exhaustive.feerate == found.feerate);
823  }
824 
825  // Compare with a non-empty topological set read from the fuzz input (comparing with an
826  // empty set is not interesting).
827  auto read_topo = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
828  assert(found.feerate >= depgraph.FeeRate(read_topo));
829  }
830 
831  // Find a non-empty topologically valid subset of transactions to remove from the graph.
832  // Using an empty set would mean the next iteration is identical to the current one, and
833  // could cause an infinite loop.
834  auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
835  todo -= del_set;
836  smp_finder.MarkDone(del_set);
837  exh_finder.MarkDone(del_set);
838  }
839 
840  assert(smp_finder.AllDone());
841  assert(exh_finder.AllDone());
842 }
843 
844 FUZZ_TARGET(clusterlin_simple_linearize)
845 {
846  // Verify the behavior of SimpleLinearize(). Note that SimpleLinearize is only used in tests;
847  // the purpose of this fuzz test is to establish confidence in SimpleLinearize, so that it can
848  // be used to test the real Linearize function in the fuzz test below.
849 
850  // Retrieve an iteration count and a depgraph from the fuzz input.
851  SpanReader reader(buffer);
852  uint64_t iter_count{0};
853  DepGraph<TestBitSet> depgraph;
854  try {
855  reader >> VARINT(iter_count) >> Using<DepGraphFormatter>(depgraph);
856  } catch (const std::ios_base::failure&) {}
857  iter_count %= MAX_SIMPLE_ITERATIONS;
858 
859  // Invoke SimpleLinearize().
860  auto [linearization, optimal] = SimpleLinearize(depgraph, iter_count);
861  SanityCheck(depgraph, linearization);
862  auto simple_chunking = ChunkLinearization(depgraph, linearization);
863 
864  // If the iteration count is sufficiently high, an optimal linearization must be found.
865  // SimpleLinearize on k transactions can take up to 2^(k-1) iterations (one per non-empty
866  // connected topologically valid subset), which sums over k=1..n to (2^n)-1.
867  const uint64_t n = depgraph.TxCount();
868  if (n <= 63 && (iter_count >> n)) {
869  assert(optimal);
870  }
871 
872  // If SimpleLinearize claims optimal result, and the cluster is sufficiently small (there are
873  // n! linearizations), test that the result is as good as every valid linearization.
874  if (optimal && depgraph.TxCount() <= 8) {
875  auto exh_linearization = ExhaustiveLinearize(depgraph);
876  auto exh_chunking = ChunkLinearization(depgraph, exh_linearization);
877  auto cmp = CompareChunks(simple_chunking, exh_chunking);
878  assert(cmp == 0);
879  assert(simple_chunking.size() == exh_chunking.size());
880  }
881 
882  if (optimal) {
883  // Compare with a linearization read from the fuzz input.
884  auto read = ReadLinearization(depgraph, reader);
885  auto read_chunking = ChunkLinearization(depgraph, read);
886  auto cmp = CompareChunks(simple_chunking, read_chunking);
887  assert(cmp >= 0);
888  }
889 }
890 
891 FUZZ_TARGET(clusterlin_sfl)
892 {
893  // Verify the individual steps of the SFL algorithm.
894 
895  SpanReader reader(buffer);
896  DepGraph<TestBitSet> depgraph;
897  uint8_t flags{1};
898  uint64_t rng_seed{0};
899  try {
900  reader >> rng_seed >> flags >> Using<DepGraphFormatter>(depgraph);
901  } catch (const std::ios_base::failure&) {}
902  if (depgraph.TxCount() <= 1) return;
903  InsecureRandomContext rng(rng_seed);
905  const bool make_connected = flags & 1;
907  const bool load_linearization = flags & 2;
909  const bool load_topological = load_linearization && (flags & 4);
910 
911  // Initialize SFL state.
912  if (make_connected) MakeConnected(depgraph);
913  SpanningForestState sfl(depgraph, rng.rand64());
914 
915  // Function to test the state.
916  std::vector<FeeFrac> last_diagram;
917  bool was_optimal{false};
918  auto test_fn = [&](bool is_optimal = false, bool is_minimal = false) {
919  if (rng.randbits(4) == 0) {
920  // Perform sanity checks from time to time (too computationally expensive to do after
921  // every step).
922  sfl.SanityCheck();
923  }
924  auto diagram = sfl.GetDiagram();
925  if (rng.randbits(4) == 0) {
926  // Verify that the diagram of GetLinearization() is at least as good as GetDiagram(),
927  // from time to time.
928  auto lin = sfl.GetLinearization(IndexTxOrder{});
929  auto lin_diagram = ChunkLinearization(depgraph, lin);
930  auto cmp_lin = CompareChunks(lin_diagram, diagram);
931  assert(cmp_lin >= 0);
932  // If we're in an allegedly optimal state, they must match.
933  if (is_optimal) assert(cmp_lin == 0);
934  // If we're in an allegedly minimal state, they must also have the same number of
935  // segments.
936  if (is_minimal) assert(diagram.size() == lin_diagram.size());
937  }
938  // Verify that subsequent calls to GetDiagram() never get worse/incomparable.
939  if (!last_diagram.empty()) {
940  auto cmp = CompareChunks(diagram, last_diagram);
941  assert(cmp >= 0);
942  // If the last diagram was already optimal, the new one cannot be better.
943  if (was_optimal) assert(cmp == 0);
944  // Also, if the diagram was already optimal, the number of segments can only increase.
945  if (was_optimal) assert(diagram.size() >= last_diagram.size());
946  }
947  last_diagram = std::move(diagram);
948  was_optimal = is_optimal;
949  };
950 
951  if (load_linearization) {
952  auto input_lin = ReadLinearization(depgraph, reader, load_topological);
953  sfl.LoadLinearization(input_lin);
954  if (load_topological) {
955  // The diagram of the loaded linearization forms an initial lower bound on future
956  // diagrams.
957  last_diagram = ChunkLinearization(depgraph, input_lin);
958  } else {
959  // The input linearization may have been non-topological, so invoke MakeTopological to
960  // fix it still.
961  sfl.MakeTopological();
962  }
963  } else {
964  // Invoke MakeTopological to create an initial from-scratch topological state.
965  sfl.MakeTopological();
966  }
967 
968  // Loop until optimal.
969  test_fn();
970  sfl.StartOptimizing();
971  while (true) {
972  test_fn();
973  if (!sfl.OptimizeStep()) break;
974  }
975 
976  // Loop until minimal.
977  test_fn(/*is_optimal=*/true);
978  sfl.StartMinimizing();
979  while (true) {
980  test_fn(/*is_optimal=*/true);
981  if (!sfl.MinimizeStep()) break;
982  }
983  test_fn(/*is_optimal=*/true, /*is_minimal=*/true);
984 
985  // Verify that optimality is reached within an expected amount of work. This protects against
986  // hypothetical bugs that hugely increase the amount of work needed to reach optimality.
987  assert(sfl.GetCost() <= MaxOptimalLinearizationCost(depgraph.TxCount()));
988 
989  // The result must be as good as SimpleLinearize.
990  auto [simple_linearization, simple_optimal] = SimpleLinearize(depgraph, MAX_SIMPLE_ITERATIONS / 10);
991  auto simple_diagram = ChunkLinearization(depgraph, simple_linearization);
992  auto simple_cmp = CompareChunks(last_diagram, simple_diagram);
993  assert(simple_cmp >= 0);
994  if (simple_optimal) assert(simple_cmp == 0);
995  // If the diagram matches, we must also have at least as many segments (because the SFL state
996  // and its produced diagram are minimal);
997  if (simple_cmp == 0) assert(last_diagram.size() >= simple_diagram.size());
998 
999  // We can compare with any arbitrary linearization, and the diagram must be at least as good as
1000  // each.
1001  for (int i = 0; i < 10; ++i) {
1002  auto read_lin = ReadLinearization(depgraph, reader);
1003  auto read_diagram = ChunkLinearization(depgraph, read_lin);
1004  auto cmp = CompareChunks(last_diagram, read_diagram);
1005  assert(cmp >= 0);
1006  if (cmp == 0) assert(last_diagram.size() >= read_diagram.size());
1007  }
1008 }
1009 
1010 FUZZ_TARGET(clusterlin_linearize)
1011 {
1012  // Verify the behavior of Linearize().
1013 
1014  // Retrieve an RNG seed, a maximum amount of work, a depgraph, and whether to make it connected
1015  // from the fuzz input.
1016  SpanReader reader(buffer);
1017  DepGraph<TestBitSet> depgraph;
1018  uint64_t rng_seed{0};
1019  uint64_t max_cost{0};
1020  uint8_t flags{7};
1021  try {
1022  reader >> VARINT(max_cost) >> Using<DepGraphFormatter>(depgraph) >> rng_seed >> flags;
1023  } catch (const std::ios_base::failure&) {}
1024  if (depgraph.TxCount() <= 1) return;
1025  bool make_connected = flags & 1;
1026  // The following 3 booleans have 4 combinations:
1027  // - (flags & 6) == 0: do not provide input linearization.
1028  // - (flags & 6) == 2: provide potentially non-topological input.
1029  // - (flags & 6) == 4: provide topological input linearization, but do not claim it is
1030  // topological.
1031  // - (flags & 6) == 6: provide topological input linearization, and claim it is topological.
1032  bool provide_input = flags & 6;
1033  bool provide_topological_input = flags & 4;
1034  bool claim_topological_input = (flags & 6) == 6;
1035  // The most complicated graphs are connected ones (other ones just split up). Optionally force
1036  // the graph to be connected.
1037  if (make_connected) MakeConnected(depgraph);
1038 
1039  // Optionally construct an old linearization for it.
1040  std::vector<DepGraphIndex> old_linearization;
1041  if (provide_input) {
1042  old_linearization = ReadLinearization(depgraph, reader, /*topological=*/provide_topological_input);
1043  if (provide_topological_input) SanityCheck(depgraph, old_linearization);
1044  }
1045 
1046  // Invoke Linearize().
1047  max_cost &= 0x3fffff;
1048  auto [linearization, optimal, cost] = Linearize(
1049  /*depgraph=*/depgraph,
1050  /*max_cost=*/max_cost,
1051  /*rng_seed=*/rng_seed,
1052  /*fallback_order=*/IndexTxOrder{},
1053  /*old_linearization=*/old_linearization,
1054  /*is_topological=*/claim_topological_input);
1055  SanityCheck(depgraph, linearization);
1056  auto chunking = ChunkLinearization(depgraph, linearization);
1057 
1058  // Linearization must always be as good as the old one, if provided and topological (even when
1059  // not claimed to be topological).
1060  if (provide_topological_input) {
1061  auto old_chunking = ChunkLinearization(depgraph, old_linearization);
1062  auto cmp = CompareChunks(chunking, old_chunking);
1063  assert(cmp >= 0);
1064  }
1065 
1066  // If the maximum amount of work is sufficiently high, an optimal linearization must be found.
1067  if (max_cost > MaxOptimalLinearizationCost(depgraph.TxCount())) {
1068  assert(optimal);
1069  }
1070 
1071  // If Linearize claims optimal result, run quality tests.
1072  if (optimal) {
1073  // It must be as good as SimpleLinearize.
1074  auto [simple_linearization, simple_optimal] = SimpleLinearize(depgraph, MAX_SIMPLE_ITERATIONS);
1075  SanityCheck(depgraph, simple_linearization);
1076  auto simple_chunking = ChunkLinearization(depgraph, simple_linearization);
1077  auto cmp = CompareChunks(chunking, simple_chunking);
1078  assert(cmp >= 0);
1079  // If SimpleLinearize finds the optimal result too, they must be equal (if not,
1080  // SimpleLinearize is broken).
1081  if (simple_optimal) assert(cmp == 0);
1082 
1083  // If simple_chunking is diagram-optimal, it cannot have more chunks than chunking (as
1084  // chunking is claimed to be optimal, which implies minimal chunks).
1085  if (cmp == 0) assert(chunking.size() >= simple_chunking.size());
1086 
1087  // Compare with a linearization read from the fuzz input.
1088  auto read = ReadLinearization(depgraph, reader);
1089  auto read_chunking = ChunkLinearization(depgraph, read);
1090  auto cmp_read = CompareChunks(chunking, read_chunking);
1091  assert(cmp_read >= 0);
1092 
1093  // Verify that within every chunk, the transactions are in a valid order. For any pair of
1094  // transactions, it should not be possible to swap them; either due to a missing
1095  // dependency, or because the order would be inconsistent with decreasing feerate,
1096  // increasing size, and fallback order (just DepGraphIndex value here).
1097  auto chunking_info = ChunkLinearizationInfo(depgraph, linearization);
1100  TestBitSet done;
1101  unsigned pos{0};
1102  for (const auto& chunk : chunking_info) {
1103  auto chunk_start = pos;
1104  auto chunk_end = pos + chunk.transactions.Count() - 1;
1105  // Go over all pairs of transactions. done is the set of transactions seen before pos1.
1106  for (unsigned pos1 = chunk_start; pos1 <= chunk_end; ++pos1) {
1107  auto tx1 = linearization[pos1];
1108  for (unsigned pos2 = pos1 + 1; pos2 <= chunk_end; ++pos2) {
1109  auto tx2 = linearization[pos2];
1110  // Check whether tx2 only depends on transactions that precede tx1.
1111  if ((depgraph.Ancestors(tx2) - done).Count() == 1) {
1112  // tx2 could take position pos1.
1113  // Verify that individual transaction feerate is decreasing (note that >=
1114  // tie-breaks by size).
1115  assert(depgraph.FeeRate(tx1) >= depgraph.FeeRate(tx2));
1116  // If feerate and size are equal, compare by DepGraphIndex.
1117  if (depgraph.FeeRate(tx1) == depgraph.FeeRate(tx2)) {
1118  assert(tx1 < tx2);
1119  }
1120  }
1121  }
1122  done.Set(tx1);
1123  }
1124  pos += chunk.transactions.Count();
1125  }
1126 
1127  // Verify that chunks themselves are in a valid order. For any pair of chunks, it should
1128  // not be possible to swap them; either due to a missing dependency, or because the order
1129  // would be inconsistent with decreasing chunk feerate, increasing chunk size, and order
1130  // of maximum fallback-ordered element (just maximum DepGraphIndex element here).
1131  done = {};
1132  // Go over all pairs of chunks. done is the set of transactions seen before chunk_num1.
1133  for (unsigned chunk_num1 = 0; chunk_num1 < chunking_info.size(); ++chunk_num1) {
1134  const auto& chunk1 = chunking_info[chunk_num1];
1135  for (unsigned chunk_num2 = chunk_num1 + 1; chunk_num2 < chunking_info.size(); ++chunk_num2) {
1136  const auto& chunk2 = chunking_info[chunk_num2];
1137  TestBitSet chunk2_ancestors;
1138  for (auto tx : chunk2.transactions) chunk2_ancestors |= depgraph.Ancestors(tx);
1139  // Check whether chunk2 only depends on transactions that precede chunk1.
1140  if ((chunk2_ancestors - done).IsSubsetOf(chunk2.transactions)) {
1141  // chunk2 could take position chunk_num1.
1142  // Verify that chunk feerate is decreasing (note that >= tie-breaks by size).
1143  assert(chunk1.feerate >= chunk2.feerate);
1144  // If feerate and size are equal, compare by maximum DepGraphIndex element.
1145  if (chunk1.feerate == chunk2.feerate) {
1146  assert(chunk1.transactions.Last() < chunk2.transactions.Last());
1147  }
1148  }
1149  }
1150  done |= chunk1.transactions;
1151  }
1152 
1153  // Redo from scratch with a different rng_seed. The resulting linearization should be
1154  // deterministic, if both are optimal.
1155  auto [linearization2, optimal2, cost2] = Linearize(depgraph, MaxOptimalLinearizationCost(depgraph.TxCount()) + 1, rng_seed ^ 0x1337, IndexTxOrder{});
1156  assert(optimal2);
1157  assert(linearization2 == linearization);
1158  }
1159 }
1160 
1161 FUZZ_TARGET(clusterlin_postlinearize)
1162 {
1163  // Verify expected properties of PostLinearize() on arbitrary linearizations.
1164 
1165  // Retrieve a depgraph from the fuzz input.
1166  SpanReader reader(buffer);
1167  DepGraph<TestBitSet> depgraph;
1168  try {
1169  reader >> Using<DepGraphFormatter>(depgraph);
1170  } catch (const std::ios_base::failure&) {}
1171 
1172  // Retrieve a linearization from the fuzz input.
1173  std::vector<DepGraphIndex> linearization;
1174  linearization = ReadLinearization(depgraph, reader);
1175  SanityCheck(depgraph, linearization);
1176 
1177  // Produce a post-processed version.
1178  auto post_linearization = linearization;
1179  PostLinearize(depgraph, post_linearization);
1180  SanityCheck(depgraph, post_linearization);
1181 
1182  // Compare diagrams: post-linearization cannot worsen anywhere.
1183  auto chunking = ChunkLinearization(depgraph, linearization);
1184  auto post_chunking = ChunkLinearization(depgraph, post_linearization);
1185  auto cmp = CompareChunks(post_chunking, chunking);
1186  assert(cmp >= 0);
1187 
1188  // Run again, things can keep improving (and never get worse)
1189  auto post_post_linearization = post_linearization;
1190  PostLinearize(depgraph, post_post_linearization);
1191  SanityCheck(depgraph, post_post_linearization);
1192  auto post_post_chunking = ChunkLinearization(depgraph, post_post_linearization);
1193  cmp = CompareChunks(post_post_chunking, post_chunking);
1194  assert(cmp >= 0);
1195 
1196  // The chunks that come out of postlinearizing are always connected.
1197  auto linchunking = ChunkLinearizationInfo(depgraph, post_linearization);
1198  for (const auto& [chunk_set, _chunk_feerate] : linchunking) {
1199  assert(depgraph.IsConnected(chunk_set));
1200  }
1201 }
1202 
1203 FUZZ_TARGET(clusterlin_postlinearize_tree)
1204 {
1205  // Verify expected properties of PostLinearize() on linearizations of graphs that form either
1206  // an upright or reverse tree structure.
1207 
1208  // Construct a direction, RNG seed, and an arbitrary graph from the fuzz input.
1209  SpanReader reader(buffer);
1210  uint64_t rng_seed{0};
1211  DepGraph<TestBitSet> depgraph_gen;
1212  uint8_t direction{0};
1213  try {
1214  reader >> direction >> rng_seed >> Using<DepGraphFormatter>(depgraph_gen);
1215  } catch (const std::ios_base::failure&) {}
1216 
1217  auto depgraph_tree = BuildTreeGraph(depgraph_gen, direction);
1218 
1219  // Retrieve a linearization from the fuzz input.
1220  std::vector<DepGraphIndex> linearization;
1221  linearization = ReadLinearization(depgraph_tree, reader);
1222  SanityCheck(depgraph_tree, linearization);
1223 
1224  // Produce a postlinearized version.
1225  auto post_linearization = linearization;
1226  PostLinearize(depgraph_tree, post_linearization);
1227  SanityCheck(depgraph_tree, post_linearization);
1228 
1229  // Compare diagrams.
1230  auto chunking = ChunkLinearization(depgraph_tree, linearization);
1231  auto post_chunking = ChunkLinearization(depgraph_tree, post_linearization);
1232  auto cmp = CompareChunks(post_chunking, chunking);
1233  assert(cmp >= 0);
1234 
1235  // Verify that post-linearizing again does not change the diagram. The result must be identical
1236  // as post_linearization ought to be optimal already with a tree-structured graph.
1237  auto post_post_linearization = post_linearization;
1238  PostLinearize(depgraph_tree, post_post_linearization);
1239  SanityCheck(depgraph_tree, post_post_linearization);
1240  auto post_post_chunking = ChunkLinearization(depgraph_tree, post_post_linearization);
1241  auto cmp_post = CompareChunks(post_post_chunking, post_chunking);
1242  assert(cmp_post == 0);
1243 
1244  // Try to find an even better linearization directly. This must not change the diagram for the
1245  // same reason.
1246  auto [opt_linearization, _optimal, _cost] = Linearize(depgraph_tree, 1000000, rng_seed, IndexTxOrder{}, post_linearization);
1247  auto opt_chunking = ChunkLinearization(depgraph_tree, opt_linearization);
1248  auto cmp_opt = CompareChunks(opt_chunking, post_chunking);
1249  assert(cmp_opt == 0);
1250 }
1251 
1252 FUZZ_TARGET(clusterlin_postlinearize_moved_leaf)
1253 {
1254  // Verify that taking an existing linearization, and moving a leaf to the back, potentially
1255  // increasing its fee, and then post-linearizing, results in something as good as the
1256  // original. This guarantees that in an RBF that replaces a transaction with one of the same
1257  // size but higher fee, applying the "remove conflicts, append new transaction, postlinearize"
1258  // process will never worsen linearization quality.
1259 
1260  // Construct an arbitrary graph and a fee from the fuzz input.
1261  SpanReader reader(buffer);
1262  DepGraph<TestBitSet> depgraph;
1263  int32_t fee_inc{0};
1264  try {
1265  uint64_t fee_inc_code;
1266  reader >> Using<DepGraphFormatter>(depgraph) >> VARINT(fee_inc_code);
1267  fee_inc = fee_inc_code & 0x3ffff;
1268  } catch (const std::ios_base::failure&) {}
1269  if (depgraph.TxCount() == 0) return;
1270 
1271  // Retrieve two linearizations from the fuzz input.
1272  auto lin = ReadLinearization(depgraph, reader);
1273  auto lin_leaf = ReadLinearization(depgraph, reader);
1274 
1275  // Construct a linearization identical to lin, but with the tail end of lin_leaf moved to the
1276  // back.
1277  std::vector<DepGraphIndex> lin_moved;
1278  for (auto i : lin) {
1279  if (i != lin_leaf.back()) lin_moved.push_back(i);
1280  }
1281  lin_moved.push_back(lin_leaf.back());
1282 
1283  // Postlinearize lin_moved.
1284  PostLinearize(depgraph, lin_moved);
1285  SanityCheck(depgraph, lin_moved);
1286 
1287  // Compare diagrams (applying the fee delta after computing the old one).
1288  auto old_chunking = ChunkLinearization(depgraph, lin);
1289  depgraph.FeeRate(lin_leaf.back()).fee += fee_inc;
1290  auto new_chunking = ChunkLinearization(depgraph, lin_moved);
1291  auto cmp = CompareChunks(new_chunking, old_chunking);
1292  assert(cmp >= 0);
1293 }
#define VARINT(obj)
Definition: serialize.h:491
void AppendTopo(std::vector< DepGraphIndex > &list, const SetType &select) const noexcept
Append the entries of select to list in a topologically valid order.
A set of transactions together with their aggregate feerate.
int ret
void(* test_fn)(void)
Definition: unit_test.h:49
const SetType & Positions() const noexcept
Get the set of transactions positions in use.
void Set(const DepGraph< SetType > &depgraph, DepGraphIndex pos) noexcept
Add a transaction to this SetInfo (which must not yet be in it).
assert(!tx.IsCoinBase())
constexpr uint64_t rand64() noexcept
Definition: random.h:448
SetType FindConnectedComponent(const SetType &todo) const noexcept
Find some connected component within the subset "todo" of this graph.
std::vector< FeeFrac > ChunkLinearization(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the feerates of the chunks of linearization.
static constexpr auto MAX_SIMPLE_ITERATIONS
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
std::tuple< std::vector< DepGraphIndex >, bool, uint64_t > Linearize(const DepGraph< SetType > &depgraph, uint64_t max_cost, uint64_t rng_seed, const StrongComparator< DepGraphIndex > auto &fallback_order, std::span< const DepGraphIndex > old_linearization={}, bool is_topological=true) noexcept
Find or improve a linearization for a cluster.
const SetType & Descendants(DepGraphIndex i) const noexcept
Get the descendants of a given transaction i.
void PostLinearize(const DepGraph< SetType > &depgraph, std::span< DepGraphIndex > linearization)
Improve a given linearization.
Minimal stream for reading from an existing byte array by std::span.
Definition: streams.h:82
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)...
std::partial_ordering CompareChunks(std::span< const FeeFrac > chunks0, std::span< const FeeFrac > chunks1)
Compare the feerate diagrams implied by the provided sorted chunks data.
Definition: feefrac.cpp:10
void AddDependencies(const SetType &parents, DepGraphIndex child) noexcept
Modify this transaction graph, adding multiple parents to a specified child.
DepGraphIndex AddTransaction(const FeeFrac &feefrac) noexcept
Add a new unconnected transaction to this transaction graph (in the first available position)...
#define LIFETIMEBOUND
Definition: attributes.h:16
Class to represent the internal state of the spanning-forest linearization (SFL) algorithm.
bool IsConnected(const SetType &subset) const noexcept
Determine if a subset is connected.
bool IsAcyclic() const noexcept
Check if this graph is acyclic.
xoroshiro128++ PRNG.
Definition: random.h:424
std::compare_three_way IndexTxOrder
Simple default transaction ordering function for SpanningForestState::GetLinearization() and Lineariz...
FUZZ_TARGET(clusterlin_depgraph_sim)
#define Assume(val)
Assume is the identity function.
Definition: check.h:125
int flags
Definition: bitcoin-tx.cpp:529
Data structure storing a fee and size, ordered by increasing fee/size.
Definition: feefrac.h:39
const auto command
uint64_t randbits(int bits) noexcept
Generate a random (bits)-bit integer.
Definition: random.h:204
Data structure that holds a transaction graph&#39;s preprocessed data (fee, size, ancestors, descendants).
DepGraphIndex PositionRange() const noexcept
Get the range of positions in this DepGraph.
static std::vector< std::string > split(const std::string &str, const std::string &delims=" \)
Definition: subprocess.h:311
const SetType & Ancestors(DepGraphIndex i) const noexcept
Get the ancestors of a given transaction i.
std::vector< SetInfo< SetType > > ChunkLinearizationInfo(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the chunks of linearization as SetInfos.
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 RemoveTransactions(const SetType &del) noexcept
Remove the specified positions from this DepGraph.
uint64_t fee
auto TxCount() const noexcept
Get the number of transactions in the graph.
const FeeFrac & FeeRate(DepGraphIndex i) const noexcept
Get the feerate of a given transaction i.
uint32_t DepGraphIndex
Data type to represent transaction indices in DepGraphs and the clusters they represent.