Bitcoin Core 29.99.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 * | SearchCandidateFinder | <<---------------------\
29 * +-----------------------+ |
30 * | +-----------+
31 * | | Linearize |
32 * | +-----------+
33 * | +-------------------------+ | |
34 * | | AncestorCandidateFinder | <<--------/ |
35 * | +-------------------------+ |
36 * | | ^ | ^^ PRODUCTION CODE
37 * | | | | ||
38 * ==============================================================================================
39 * | | | | ||
40 * | clusterlin_ancestor_finder* | | vv TEST CODE
41 * | | |
42 * |-clusterlin_search_finder* | |-clusterlin_linearize*
43 * | | |
44 * v | v
45 * +-----------------------+ | +-----------------+
46 * | SimpleCandidateFinder | <<-------------------| SimpleLinearize |
47 * +-----------------------+ | +-----------------+
48 * | | |
49 * +-------------------/ |
50 * | |
51 * |-clusterlin_simple_finder* |-clusterlin_simple_linearize*
52 * v v
53 * +---------------------------+ +---------------------+
54 * | ExhaustiveCandidateFinder | | ExhaustiveLinearize |
55 * +---------------------------+ +---------------------+
56 *
57 * More tests are included for lower-level and related functions and classes:
58 * - DepGraph tests:
59 * - clusterlin_depgraph_sim
60 * - clusterlin_depgraph_serialization
61 * - clusterlin_components
62 * - ChunkLinearization and LinearizationChunking tests:
63 * - clusterlin_chunking
64 * - clusterlin_linearization_chunking
65 * - PostLinearize tests:
66 * - clusterlin_postlinearize
67 * - clusterlin_postlinearize_tree
68 * - clusterlin_postlinearize_moved_leaf
69 * - MergeLinearization tests:
70 * - clusterlin_merge
71 * - FixLinearization tests:
72 * - clusterlin_fix_linearization
73 * - MakeConnected tests (a test-only function):
74 * - clusterlin_make_connected
75 */
76
77using namespace cluster_linearize;
78
79namespace {
80
86template<typename SetType>
87class SimpleCandidateFinder
88{
90 const DepGraph<SetType>& m_depgraph;
92 SetType m_todo;
93
94public:
96 SimpleCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND) noexcept :
97 m_depgraph(depgraph), m_todo{depgraph.Positions()} {}
98
100 void MarkDone(SetType select) noexcept { m_todo -= select; }
101
103 bool AllDone() const noexcept { return m_todo.None(); }
104
113 std::pair<SetInfo<SetType>, uint64_t> FindCandidateSet(uint64_t max_iterations) const noexcept
114 {
115 uint64_t iterations_left = max_iterations;
116 // Queue of work units. Each consists of:
117 // - inc: set of transactions definitely included
118 // - und: set of transactions that can be added to inc still
119 std::vector<std::pair<SetType, SetType>> queue;
120 // Initially we have just one queue element, with the entire graph in und.
121 queue.emplace_back(SetType{}, m_todo);
122 // Best solution so far. Initialize with the remaining ancestors of the first remaining
123 // transaction.
124 SetInfo best(m_depgraph, m_depgraph.Ancestors(m_todo.First()) & m_todo);
125 // Process the queue.
126 while (!queue.empty() && iterations_left) {
127 // Pop top element of the queue.
128 auto [inc, und] = queue.back();
129 queue.pop_back();
130 // Look for a transaction to consider adding/removing.
131 bool inc_none = inc.None();
132 for (auto split : und) {
133 // If inc is empty, consider any split transaction. Otherwise only consider
134 // transactions that share ancestry with inc so far (which means only connected
135 // sets will be considered).
136 if (inc_none || inc.Overlaps(m_depgraph.Ancestors(split))) {
137 --iterations_left;
138 // Add a queue entry with split included.
139 SetInfo new_inc(m_depgraph, inc | (m_todo & m_depgraph.Ancestors(split)));
140 queue.emplace_back(new_inc.transactions, und - new_inc.transactions);
141 // Add a queue entry with split excluded.
142 queue.emplace_back(inc, und - m_depgraph.Descendants(split));
143 // Update statistics to account for the candidate new_inc.
144 if (new_inc.feerate > best.feerate) best = new_inc;
145 break;
146 }
147 }
148 }
149 return {std::move(best), max_iterations - iterations_left};
150 }
151};
152
158template<typename SetType>
159class ExhaustiveCandidateFinder
160{
162 const DepGraph<SetType>& m_depgraph;
164 SetType m_todo;
165
166public:
168 ExhaustiveCandidateFinder(const DepGraph<SetType>& depgraph LIFETIMEBOUND) noexcept :
169 m_depgraph(depgraph), m_todo{depgraph.Positions()} {}
170
172 void MarkDone(SetType select) noexcept { m_todo -= select; }
173
175 bool AllDone() const noexcept { return m_todo.None(); }
176
181 SetInfo<SetType> FindCandidateSet() const noexcept
182 {
183 // Best solution so far.
184 SetInfo<SetType> best{m_todo, m_depgraph.FeeRate(m_todo)};
185 // The number of combinations to try.
186 uint64_t limit = (uint64_t{1} << m_todo.Count()) - 1;
187 // Try the transitive closure of every non-empty subset of m_todo.
188 for (uint64_t x = 1; x < limit; ++x) {
189 // If bit number b is set in x, then the remaining ancestors of the b'th remaining
190 // transaction in m_todo are included.
191 SetType txn;
192 auto x_shifted{x};
193 for (auto i : m_todo) {
194 if (x_shifted & 1) txn |= m_depgraph.Ancestors(i);
195 x_shifted >>= 1;
196 }
197 SetInfo cur(m_depgraph, txn & m_todo);
198 if (cur.feerate > best.feerate) best = cur;
199 }
200 return best;
201 }
202};
203
210template<typename SetType>
211std::pair<std::vector<DepGraphIndex>, bool> SimpleLinearize(const DepGraph<SetType>& depgraph, uint64_t max_iterations)
212{
213 std::vector<DepGraphIndex> linearization;
214 SimpleCandidateFinder finder(depgraph);
215 SetType todo = depgraph.Positions();
216 bool optimal = true;
217 while (todo.Any()) {
218 auto [candidate, iterations_done] = finder.FindCandidateSet(max_iterations);
219 if (iterations_done == max_iterations) optimal = false;
220 depgraph.AppendTopo(linearization, candidate.transactions);
221 todo -= candidate.transactions;
222 finder.MarkDone(candidate.transactions);
223 max_iterations -= iterations_done;
224 }
225 return {std::move(linearization), optimal};
226}
227
235template<typename SetType>
236std::vector<DepGraphIndex> ExhaustiveLinearize(const DepGraph<SetType>& depgraph)
237{
238 // The best linearization so far, and its chunking.
239 std::vector<DepGraphIndex> linearization;
240 std::vector<FeeFrac> chunking;
241
242 std::vector<DepGraphIndex> perm_linearization;
243 // Initialize with the lexicographically-first linearization.
244 for (DepGraphIndex i : depgraph.Positions()) perm_linearization.push_back(i);
245 // Iterate over all valid permutations.
246 do {
248 DepGraphIndex topo_length{0};
249 TestBitSet perm_done;
250 while (topo_length < perm_linearization.size()) {
251 auto i = perm_linearization[topo_length];
252 perm_done.Set(i);
253 if (!depgraph.Ancestors(i).IsSubsetOf(perm_done)) break;
254 ++topo_length;
255 }
256 if (topo_length == perm_linearization.size()) {
257 // If all of perm_linearization is topological, check if it is perhaps our best
258 // linearization so far.
259 auto perm_chunking = ChunkLinearization(depgraph, perm_linearization);
260 auto cmp = CompareChunks(perm_chunking, chunking);
261 // If the diagram is better, or if it is equal but with more chunks (because we
262 // prefer minimal chunks), consider this better.
263 if (linearization.empty() || cmp > 0 || (cmp == 0 && perm_chunking.size() > chunking.size())) {
264 linearization = perm_linearization;
265 chunking = perm_chunking;
266 }
267 } else {
268 // Otherwise, fast forward to the last permutation with the same non-topological
269 // prefix.
270 auto first_non_topo = perm_linearization.begin() + topo_length;
271 assert(std::is_sorted(first_non_topo + 1, perm_linearization.end()));
272 std::reverse(first_non_topo + 1, perm_linearization.end());
273 }
274 } while(std::next_permutation(perm_linearization.begin(), perm_linearization.end()));
275
276 return linearization;
277}
278
279
281template<typename BS>
282void MakeConnected(DepGraph<BS>& depgraph)
283{
284 auto todo = depgraph.Positions();
285 auto comp = depgraph.FindConnectedComponent(todo);
286 Assume(depgraph.IsConnected(comp));
287 todo -= comp;
288 while (todo.Any()) {
289 auto nextcomp = depgraph.FindConnectedComponent(todo);
290 Assume(depgraph.IsConnected(nextcomp));
291 depgraph.AddDependencies(BS::Singleton(comp.Last()), nextcomp.First());
292 todo -= nextcomp;
293 comp = nextcomp;
294 }
295}
296
298template<typename SetType>
299SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& todo, SpanReader& reader, bool non_empty)
300{
301 // Read a bitmask from the fuzzing input. Add 1 if non_empty, so the mask is definitely not
302 // zero in that case.
303 uint64_t mask{0};
304 try {
305 reader >> VARINT(mask);
306 } catch(const std::ios_base::failure&) {}
307 if (mask != uint64_t(-1)) mask += non_empty;
308
309 SetType ret;
310 for (auto i : todo) {
311 if (!ret[i]) {
312 if (mask & 1) ret |= depgraph.Ancestors(i);
313 mask >>= 1;
314 }
315 }
316 ret &= todo;
317
318 // While mask starts off non-zero if non_empty is true, it is still possible that all its low
319 // bits are 0, and ret ends up being empty. As a last resort, use the in-todo ancestry of the
320 // first todo position.
321 if (non_empty && ret.None()) {
322 Assume(todo.Any());
323 ret = depgraph.Ancestors(todo.First()) & todo;
324 Assume(ret.Any());
325 }
326 return ret;
327}
328
330template<typename BS>
331std::vector<DepGraphIndex> ReadLinearization(const DepGraph<BS>& depgraph, SpanReader& reader)
332{
333 std::vector<DepGraphIndex> linearization;
334 TestBitSet todo = depgraph.Positions();
335 // In every iteration one topologically-valid transaction is appended to linearization.
336 while (todo.Any()) {
337 // Compute the set of transactions with no not-yet-included ancestors.
338 TestBitSet potential_next;
339 for (auto j : todo) {
340 if ((depgraph.Ancestors(j) & todo) == TestBitSet::Singleton(j)) {
341 potential_next.Set(j);
342 }
343 }
344 // There must always be one (otherwise there is a cycle in the graph).
345 assert(potential_next.Any());
346 // Read a number from reader, and interpret it as index into potential_next.
347 uint64_t idx{0};
348 try {
349 reader >> VARINT(idx);
350 } catch (const std::ios_base::failure&) {}
351 idx %= potential_next.Count();
352 // Find out which transaction that corresponds to.
353 for (auto j : potential_next) {
354 if (idx == 0) {
355 // When found, add it to linearization and remove it from todo.
356 linearization.push_back(j);
357 assert(todo[j]);
358 todo.Reset(j);
359 break;
360 }
361 --idx;
362 }
363 }
364 return linearization;
365}
366
367} // namespace
368
369FUZZ_TARGET(clusterlin_depgraph_sim)
370{
371 // Simulation test to verify the full behavior of DepGraph.
372
373 FuzzedDataProvider provider(buffer.data(), buffer.size());
374
379 std::array<std::optional<std::pair<FeeFrac, TestBitSet>>, TestBitSet::Size()> sim;
381 DepGraphIndex num_tx_sim{0};
382
384 auto idx_fn = [&]() {
385 auto offset = provider.ConsumeIntegralInRange<DepGraphIndex>(0, num_tx_sim - 1);
386 for (DepGraphIndex i = 0; i < sim.size(); ++i) {
387 if (!sim[i].has_value()) continue;
388 if (offset == 0) return i;
389 --offset;
390 }
391 assert(false);
392 return DepGraphIndex(-1);
393 };
394
396 auto subset_fn = [&]() {
397 auto range = (uint64_t{1} << num_tx_sim) - 1;
398 const auto mask = provider.ConsumeIntegralInRange<uint64_t>(0, range);
399 auto mask_shifted = mask;
400 TestBitSet subset;
401 for (DepGraphIndex i = 0; i < sim.size(); ++i) {
402 if (!sim[i].has_value()) continue;
403 if (mask_shifted & 1) {
404 subset.Set(i);
405 }
406 mask_shifted >>= 1;
407 }
408 assert(mask_shifted == 0);
409 return subset;
410 };
411
413 auto set_fn = [&]() {
414 auto range = (uint64_t{1} << sim.size()) - 1;
415 const auto mask = provider.ConsumeIntegralInRange<uint64_t>(0, range);
416 TestBitSet set;
417 for (DepGraphIndex i = 0; i < sim.size(); ++i) {
418 if ((mask >> i) & 1) {
419 set.Set(i);
420 }
421 }
422 return set;
423 };
424
426 auto anc_update_fn = [&]() {
427 while (true) {
428 bool updates{false};
429 for (DepGraphIndex chl = 0; chl < sim.size(); ++chl) {
430 if (!sim[chl].has_value()) continue;
431 for (auto par : sim[chl]->second) {
432 if (!sim[chl]->second.IsSupersetOf(sim[par]->second)) {
433 sim[chl]->second |= sim[par]->second;
434 updates = true;
435 }
436 }
437 }
438 if (!updates) break;
439 }
440 };
441
443 auto check_fn = [&](DepGraphIndex i) {
444 // Compare used positions.
445 assert(real.Positions()[i] == sim[i].has_value());
446 if (sim[i].has_value()) {
447 // Compare feerate.
448 assert(real.FeeRate(i) == sim[i]->first);
449 // Compare ancestors (note that SanityCheck verifies correspondence between ancestors
450 // and descendants, so we can restrict ourselves to ancestors here).
451 assert(real.Ancestors(i) == sim[i]->second);
452 }
453 };
454
455 LIMITED_WHILE(provider.remaining_bytes() > 0, 1000) {
456 uint8_t command = provider.ConsumeIntegral<uint8_t>();
457 if (num_tx_sim == 0 || ((command % 3) <= 0 && num_tx_sim < TestBitSet::Size())) {
458 // AddTransaction.
459 auto fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
460 auto size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
461 FeeFrac feerate{fee, size};
462 // Apply to DepGraph.
463 auto idx = real.AddTransaction(feerate);
464 // Verify that the returned index is correct.
465 assert(!sim[idx].has_value());
466 for (DepGraphIndex i = 0; i < TestBitSet::Size(); ++i) {
467 if (!sim[i].has_value()) {
468 assert(idx == i);
469 break;
470 }
471 }
472 // Update sim.
473 sim[idx] = {feerate, TestBitSet::Singleton(idx)};
474 ++num_tx_sim;
475 continue;
476 }
477 if ((command % 3) <= 1 && num_tx_sim > 0) {
478 // AddDependencies.
479 DepGraphIndex child = idx_fn();
480 auto parents = subset_fn();
481 // Apply to DepGraph.
482 real.AddDependencies(parents, child);
483 // Apply to sim.
484 sim[child]->second |= parents;
485 continue;
486 }
487 if (num_tx_sim > 0) {
488 // Remove transactions.
489 auto del = set_fn();
490 // Propagate all ancestry information before deleting anything in the simulation (as
491 // intermediary transactions may be deleted which impact connectivity).
492 anc_update_fn();
493 // Compare the state of the transactions being deleted.
494 for (auto i : del) check_fn(i);
495 // Apply to DepGraph.
496 real.RemoveTransactions(del);
497 // Apply to sim.
498 for (DepGraphIndex i = 0; i < sim.size(); ++i) {
499 if (sim[i].has_value()) {
500 if (del[i]) {
501 --num_tx_sim;
502 sim[i] = std::nullopt;
503 } else {
504 sim[i]->second -= del;
505 }
506 }
507 }
508 continue;
509 }
510 // This should be unreachable (one of the 3 above actions should always be possible).
511 assert(false);
512 }
513
514 // Compare the real obtained depgraph against the simulation.
515 anc_update_fn();
516 for (DepGraphIndex i = 0; i < sim.size(); ++i) check_fn(i);
517 assert(real.TxCount() == num_tx_sim);
518 // Sanity check the result (which includes round-tripping serialization, if applicable).
519 SanityCheck(real);
520}
521
522FUZZ_TARGET(clusterlin_depgraph_serialization)
523{
524 // Verify that any deserialized depgraph is acyclic and roundtrips to an identical depgraph.
525
526 // Construct a graph by deserializing.
527 SpanReader reader(buffer);
528 DepGraph<TestBitSet> depgraph;
529 DepGraphIndex par_code{0}, chl_code{0};
530 try {
531 reader >> Using<DepGraphFormatter>(depgraph) >> VARINT(par_code) >> VARINT(chl_code);
532 } catch (const std::ios_base::failure&) {}
533 SanityCheck(depgraph);
534
535 // Verify the graph is a DAG.
536 assert(depgraph.IsAcyclic());
537
538 // Introduce a cycle, and then test that IsAcyclic returns false.
539 if (depgraph.TxCount() < 2) return;
540 DepGraphIndex par(0), chl(0);
541 // Pick any transaction of depgraph as parent.
542 par_code %= depgraph.TxCount();
543 for (auto i : depgraph.Positions()) {
544 if (par_code == 0) {
545 par = i;
546 break;
547 }
548 --par_code;
549 }
550 // Pick any ancestor of par (excluding itself) as child, if any.
551 auto ancestors = depgraph.Ancestors(par) - TestBitSet::Singleton(par);
552 if (ancestors.None()) return;
553 chl_code %= ancestors.Count();
554 for (auto i : ancestors) {
555 if (chl_code == 0) {
556 chl = i;
557 break;
558 }
559 --chl_code;
560 }
561 // Add the cycle-introducing dependency.
562 depgraph.AddDependencies(TestBitSet::Singleton(par), chl);
563 // Check that we now detect a cycle.
564 assert(!depgraph.IsAcyclic());
565}
566
567FUZZ_TARGET(clusterlin_components)
568{
569 // Verify the behavior of DepGraphs's FindConnectedComponent and IsConnected functions.
570
571 // Construct a depgraph.
572 SpanReader reader(buffer);
573 DepGraph<TestBitSet> depgraph;
574 std::vector<DepGraphIndex> linearization;
575 try {
576 reader >> Using<DepGraphFormatter>(depgraph);
577 } catch (const std::ios_base::failure&) {}
578
579 TestBitSet todo = depgraph.Positions();
580 while (todo.Any()) {
581 // Pick a transaction in todo, or nothing.
582 std::optional<DepGraphIndex> picked;
583 {
584 uint64_t picked_num{0};
585 try {
586 reader >> VARINT(picked_num);
587 } catch (const std::ios_base::failure&) {}
588 if (picked_num < todo.Size() && todo[picked_num]) {
589 picked = picked_num;
590 }
591 }
592
593 // Find a connected component inside todo, including picked if any.
594 auto component = picked ? depgraph.GetConnectedComponent(todo, *picked)
595 : depgraph.FindConnectedComponent(todo);
596
597 // The component must be a subset of todo and non-empty.
598 assert(component.IsSubsetOf(todo));
599 assert(component.Any());
600
601 // If picked was provided, the component must include it.
602 if (picked) assert(component[*picked]);
603
604 // If todo is the entire graph, and the entire graph is connected, then the component must
605 // be the entire graph.
606 if (todo == depgraph.Positions()) {
607 assert((component == todo) == depgraph.IsConnected());
608 }
609
610 // If subset is connected, then component must match subset.
611 assert((component == todo) == depgraph.IsConnected(todo));
612
613 // The component cannot have any ancestors or descendants outside of component but in todo.
614 for (auto i : component) {
615 assert((depgraph.Ancestors(i) & todo).IsSubsetOf(component));
616 assert((depgraph.Descendants(i) & todo).IsSubsetOf(component));
617 }
618
619 // Starting from any component element, we must be able to reach every element.
620 for (auto i : component) {
621 // Start with just i as reachable.
622 TestBitSet reachable = TestBitSet::Singleton(i);
623 // Add in-todo descendants and ancestors to reachable until it does not change anymore.
624 while (true) {
625 TestBitSet new_reachable = reachable;
626 for (auto j : new_reachable) {
627 new_reachable |= depgraph.Ancestors(j) & todo;
628 new_reachable |= depgraph.Descendants(j) & todo;
629 }
630 if (new_reachable == reachable) break;
631 reachable = new_reachable;
632 }
633 // Verify that the result is the entire component.
634 assert(component == reachable);
635 }
636
637 // Construct an arbitrary subset of todo.
638 uint64_t subset_bits{0};
639 try {
640 reader >> VARINT(subset_bits);
641 } catch (const std::ios_base::failure&) {}
642 TestBitSet subset;
643 for (DepGraphIndex i : depgraph.Positions()) {
644 if (todo[i]) {
645 if (subset_bits & 1) subset.Set(i);
646 subset_bits >>= 1;
647 }
648 }
649 // Which must be non-empty.
650 if (subset.None()) subset = TestBitSet::Singleton(todo.First());
651 // Remove it from todo.
652 todo -= subset;
653 }
654
655 // No components can be found in an empty subset.
656 assert(depgraph.FindConnectedComponent(todo).None());
657}
658
659FUZZ_TARGET(clusterlin_make_connected)
660{
661 // Verify that MakeConnected makes graphs connected.
662
663 SpanReader reader(buffer);
664 DepGraph<TestBitSet> depgraph;
665 try {
666 reader >> Using<DepGraphFormatter>(depgraph);
667 } catch (const std::ios_base::failure&) {}
668 MakeConnected(depgraph);
669 SanityCheck(depgraph);
670 assert(depgraph.IsConnected());
671}
672
673FUZZ_TARGET(clusterlin_chunking)
674{
675 // Verify the correctness of the ChunkLinearization function.
676
677 // Construct a graph by deserializing.
678 SpanReader reader(buffer);
679 DepGraph<TestBitSet> depgraph;
680 try {
681 reader >> Using<DepGraphFormatter>(depgraph);
682 } catch (const std::ios_base::failure&) {}
683
684 // Read a valid linearization for depgraph.
685 auto linearization = ReadLinearization(depgraph, reader);
686
687 // Invoke the chunking function.
688 auto chunking = ChunkLinearization(depgraph, linearization);
689
690 // Verify that chunk feerates are monotonically non-increasing.
691 for (size_t i = 1; i < chunking.size(); ++i) {
692 assert(!(chunking[i] >> chunking[i - 1]));
693 }
694
695 // Naively recompute the chunks (each is the highest-feerate prefix of what remains).
696 auto todo = depgraph.Positions();
697 for (const auto& chunk_feerate : chunking) {
698 assert(todo.Any());
699 SetInfo<TestBitSet> accumulator, best;
700 for (DepGraphIndex idx : linearization) {
701 if (todo[idx]) {
702 accumulator.Set(depgraph, idx);
703 if (best.feerate.IsEmpty() || accumulator.feerate >> best.feerate) {
704 best = accumulator;
705 }
706 }
707 }
708 assert(chunk_feerate == best.feerate);
709 assert(best.transactions.IsSubsetOf(todo));
710 todo -= best.transactions;
711 }
712 assert(todo.None());
713}
714
715FUZZ_TARGET(clusterlin_ancestor_finder)
716{
717 // Verify that AncestorCandidateFinder works as expected.
718
719 // Retrieve a depgraph from the fuzz input.
720 SpanReader reader(buffer);
721 DepGraph<TestBitSet> depgraph;
722 try {
723 reader >> Using<DepGraphFormatter>(depgraph);
724 } catch (const std::ios_base::failure&) {}
725
726 AncestorCandidateFinder anc_finder(depgraph);
727 auto todo = depgraph.Positions();
728 while (todo.Any()) {
729 // Call the ancestor finder's FindCandidateSet for what remains of the graph.
730 assert(!anc_finder.AllDone());
731 assert(todo.Count() == anc_finder.NumRemaining());
732 auto best_anc = anc_finder.FindCandidateSet();
733 // Sanity check the result.
734 assert(best_anc.transactions.Any());
735 assert(best_anc.transactions.IsSubsetOf(todo));
736 assert(depgraph.FeeRate(best_anc.transactions) == best_anc.feerate);
737 assert(depgraph.IsConnected(best_anc.transactions));
738 // Check that it is topologically valid.
739 for (auto i : best_anc.transactions) {
740 assert((depgraph.Ancestors(i) & todo).IsSubsetOf(best_anc.transactions));
741 }
742
743 // Compute all remaining ancestor sets.
744 std::optional<SetInfo<TestBitSet>> real_best_anc;
745 for (auto i : todo) {
746 SetInfo info(depgraph, todo & depgraph.Ancestors(i));
747 if (!real_best_anc.has_value() || info.feerate > real_best_anc->feerate) {
748 real_best_anc = info;
749 }
750 }
751 // The set returned by anc_finder must equal the real best ancestor sets.
752 assert(real_best_anc.has_value());
753 assert(*real_best_anc == best_anc);
754
755 // Find a non-empty topologically valid subset of transactions to remove from the graph.
756 // Using an empty set would mean the next iteration is identical to the current one, and
757 // could cause an infinite loop.
758 auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
759 todo -= del_set;
760 anc_finder.MarkDone(del_set);
761 }
762 assert(anc_finder.AllDone());
763 assert(anc_finder.NumRemaining() == 0);
764}
765
766static constexpr auto MAX_SIMPLE_ITERATIONS = 300000;
767
768FUZZ_TARGET(clusterlin_simple_finder)
769{
770 // Verify that SimpleCandidateFinder works as expected by sanity checking the results
771 // and comparing them (if claimed to be optimal) against the sets found by
772 // ExhaustiveCandidateFinder and AncestorCandidateFinder.
773 //
774 // Note that SimpleCandidateFinder is only used in tests; the purpose of this fuzz test is to
775 // establish confidence in SimpleCandidateFinder, so that it can be used to test
776 // SearchCandidateFinder below.
777
778 // Retrieve a depgraph from the fuzz input.
779 SpanReader reader(buffer);
780 DepGraph<TestBitSet> depgraph;
781 try {
782 reader >> Using<DepGraphFormatter>(depgraph);
783 } catch (const std::ios_base::failure&) {}
784
785 // Instantiate the SimpleCandidateFinder to be tested, and the ExhaustiveCandidateFinder and
786 // AncestorCandidateFinder it is being tested against.
787 SimpleCandidateFinder smp_finder(depgraph);
788 ExhaustiveCandidateFinder exh_finder(depgraph);
789 AncestorCandidateFinder anc_finder(depgraph);
790
791 auto todo = depgraph.Positions();
792 while (todo.Any()) {
793 assert(!smp_finder.AllDone());
794 assert(!exh_finder.AllDone());
795 assert(!anc_finder.AllDone());
796 assert(anc_finder.NumRemaining() == todo.Count());
797
798 // Call SimpleCandidateFinder.
799 auto [found, iterations_done] = smp_finder.FindCandidateSet(MAX_SIMPLE_ITERATIONS);
800 bool optimal = (iterations_done != MAX_SIMPLE_ITERATIONS);
801
802 // Sanity check the result.
803 assert(iterations_done <= MAX_SIMPLE_ITERATIONS);
804 assert(found.transactions.Any());
805 assert(found.transactions.IsSubsetOf(todo));
806 assert(depgraph.FeeRate(found.transactions) == found.feerate);
807 // Check that it is topologically valid.
808 for (auto i : found.transactions) {
809 assert(found.transactions.IsSupersetOf(depgraph.Ancestors(i) & todo));
810 }
811
812 // At most 2^(N-1) iterations can be required: the number of non-empty connected subsets a
813 // graph with N transactions can have. If MAX_SIMPLE_ITERATIONS exceeds this number, the
814 // result is necessarily optimal.
815 assert(iterations_done <= (uint64_t{1} << (todo.Count() - 1)));
816 if (MAX_SIMPLE_ITERATIONS > (uint64_t{1} << (todo.Count() - 1))) assert(optimal);
817
818 // SimpleCandidateFinder only finds connected sets.
819 assert(depgraph.IsConnected(found.transactions));
820
821 // Perform further quality checks only if SimpleCandidateFinder claims an optimal result.
822 if (optimal) {
823 // Compare with AncestorCandidateFinder.
824 auto anc = anc_finder.FindCandidateSet();
825 assert(anc.feerate <= found.feerate);
826
827 if (todo.Count() <= 12) {
828 // Compare with ExhaustiveCandidateFinder. This quickly gets computationally
829 // expensive for large clusters (O(2^n)), so only do it for sufficiently small ones.
830 auto exhaustive = exh_finder.FindCandidateSet();
831 assert(exhaustive.feerate == found.feerate);
832 }
833
834 // Compare with a non-empty topological set read from the fuzz input (comparing with an
835 // empty set is not interesting).
836 auto read_topo = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
837 assert(found.feerate >= depgraph.FeeRate(read_topo));
838 }
839
840 // Find a non-empty topologically valid subset of transactions to remove from the graph.
841 // Using an empty set would mean the next iteration is identical to the current one, and
842 // could cause an infinite loop.
843 auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
844 todo -= del_set;
845 smp_finder.MarkDone(del_set);
846 exh_finder.MarkDone(del_set);
847 anc_finder.MarkDone(del_set);
848 }
849
850 assert(smp_finder.AllDone());
851 assert(exh_finder.AllDone());
852 assert(anc_finder.AllDone());
853 assert(anc_finder.NumRemaining() == 0);
854}
855
856FUZZ_TARGET(clusterlin_search_finder)
857{
858 // Verify that SearchCandidateFinder works as expected by sanity checking the results
859 // and comparing with the results from SimpleCandidateFinder and AncestorCandidateFinder,
860 // if the result is claimed to be optimal.
861
862 // Retrieve an RNG seed, a depgraph, and whether to make it connected, from the fuzz input.
863 SpanReader reader(buffer);
864 DepGraph<TestBitSet> depgraph;
865 uint64_t rng_seed{0};
866 uint8_t make_connected{1};
867 try {
868 reader >> Using<DepGraphFormatter>(depgraph) >> rng_seed >> make_connected;
869 } catch (const std::ios_base::failure&) {}
870 // The most complicated graphs are connected ones (other ones just split up). Optionally force
871 // the graph to be connected.
872 if (make_connected) MakeConnected(depgraph);
873
874 // Instantiate the candidate finders.
875 SearchCandidateFinder src_finder(depgraph, rng_seed);
876 SimpleCandidateFinder smp_finder(depgraph);
877 AncestorCandidateFinder anc_finder(depgraph);
878
879 auto todo = depgraph.Positions();
880 while (todo.Any()) {
881 assert(!src_finder.AllDone());
882 assert(!smp_finder.AllDone());
883 assert(!anc_finder.AllDone());
884 assert(anc_finder.NumRemaining() == todo.Count());
885
886 // For each iteration, read an iteration count limit from the fuzz input.
887 uint64_t max_iterations = 1;
888 try {
889 reader >> VARINT(max_iterations);
890 } catch (const std::ios_base::failure&) {}
891 max_iterations &= 0xfffff;
892
893 // Read an initial subset from the fuzz input (allowed to be empty).
894 auto init_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/false);
895 SetInfo init_best(depgraph, init_set);
896
897 // Call the search finder's FindCandidateSet for what remains of the graph.
898 auto [found, iterations_done] = src_finder.FindCandidateSet(max_iterations, init_best);
899 bool optimal = iterations_done < max_iterations;
900
901 // Sanity check the result.
902 assert(iterations_done <= max_iterations);
903 assert(found.transactions.Any());
904 assert(found.transactions.IsSubsetOf(todo));
905 assert(depgraph.FeeRate(found.transactions) == found.feerate);
906 if (!init_best.feerate.IsEmpty()) assert(found.feerate >= init_best.feerate);
907 // Check that it is topologically valid.
908 for (auto i : found.transactions) {
909 assert(found.transactions.IsSupersetOf(depgraph.Ancestors(i) & todo));
910 }
911
912 // At most 2^(N-1) iterations can be required: the maximum number of non-empty topological
913 // subsets a (connected) cluster with N transactions can have. Even when the cluster is no
914 // longer connected after removing certain transactions, this holds, because the connected
915 // components are searched separately.
916 assert(iterations_done <= (uint64_t{1} << (todo.Count() - 1)));
917 // Additionally, test that no more than sqrt(2^N)+1 iterations are required. This is just
918 // an empirical bound that seems to hold, without proof. Still, add a test for it so we
919 // can learn about counterexamples if they exist.
920 if (iterations_done >= 1 && todo.Count() <= 63) {
921 Assume((iterations_done - 1) * (iterations_done - 1) <= uint64_t{1} << todo.Count());
922 }
923
924 // Perform quality checks only if SearchCandidateFinder claims an optimal result.
925 if (optimal) {
926 // Optimal sets are always connected.
927 assert(depgraph.IsConnected(found.transactions));
928
929 // Compare with SimpleCandidateFinder.
930 auto [simple, simple_iters] = smp_finder.FindCandidateSet(MAX_SIMPLE_ITERATIONS);
931 assert(found.feerate >= simple.feerate);
932 if (simple_iters < MAX_SIMPLE_ITERATIONS) {
933 assert(found.feerate == simple.feerate);
934 }
935
936 // Compare with AncestorCandidateFinder;
937 auto anc = anc_finder.FindCandidateSet();
938 assert(found.feerate >= anc.feerate);
939
940 // Compare with a non-empty topological set read from the fuzz input (comparing with an
941 // empty set is not interesting).
942 auto read_topo = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
943 assert(found.feerate >= depgraph.FeeRate(read_topo));
944 }
945
946 // Find a non-empty topologically valid subset of transactions to remove from the graph.
947 // Using an empty set would mean the next iteration is identical to the current one, and
948 // could cause an infinite loop.
949 auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
950 todo -= del_set;
951 src_finder.MarkDone(del_set);
952 smp_finder.MarkDone(del_set);
953 anc_finder.MarkDone(del_set);
954 }
955
956 assert(src_finder.AllDone());
957 assert(smp_finder.AllDone());
958 assert(anc_finder.AllDone());
959 assert(anc_finder.NumRemaining() == 0);
960}
961
962FUZZ_TARGET(clusterlin_linearization_chunking)
963{
964 // Verify the behavior of LinearizationChunking.
965
966 // Retrieve a depgraph from the fuzz input.
967 SpanReader reader(buffer);
968 DepGraph<TestBitSet> depgraph;
969 try {
970 reader >> Using<DepGraphFormatter>(depgraph);
971 } catch (const std::ios_base::failure&) {}
972
973 // Retrieve a topologically-valid subset of depgraph (allowed to be empty, because the argument
974 // to LinearizationChunking::Intersect is allowed to be empty).
975 auto todo = depgraph.Positions();
976 auto subset = SetInfo(depgraph, ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/false));
977
978 // Retrieve a valid linearization for depgraph.
979 auto linearization = ReadLinearization(depgraph, reader);
980
981 // Construct a LinearizationChunking object, initially for the whole linearization.
982 LinearizationChunking chunking(depgraph, linearization);
983
984 // Incrementally remove transactions from the chunking object, and check various properties at
985 // every step.
986 while (todo.Any()) {
987 assert(chunking.NumChunksLeft() > 0);
988
989 // Construct linearization with just todo.
990 std::vector<DepGraphIndex> linearization_left;
991 for (auto i : linearization) {
992 if (todo[i]) linearization_left.push_back(i);
993 }
994
995 // Compute the chunking for linearization_left.
996 auto chunking_left = ChunkLinearization(depgraph, linearization_left);
997
998 // Verify that it matches the feerates of the chunks of chunking.
999 assert(chunking.NumChunksLeft() == chunking_left.size());
1000 for (DepGraphIndex i = 0; i < chunking.NumChunksLeft(); ++i) {
1001 assert(chunking.GetChunk(i).feerate == chunking_left[i]);
1002 }
1003
1004 // Check consistency of chunking.
1005 TestBitSet combined;
1006 for (DepGraphIndex i = 0; i < chunking.NumChunksLeft(); ++i) {
1007 const auto& chunk_info = chunking.GetChunk(i);
1008 // Chunks must be non-empty.
1009 assert(chunk_info.transactions.Any());
1010 // Chunk feerates must be monotonically non-increasing.
1011 if (i > 0) assert(!(chunk_info.feerate >> chunking.GetChunk(i - 1).feerate));
1012 // Chunks must be a subset of what is left of the linearization.
1013 assert(chunk_info.transactions.IsSubsetOf(todo));
1014 // Chunks' claimed feerates must match their transactions' aggregate feerate.
1015 assert(depgraph.FeeRate(chunk_info.transactions) == chunk_info.feerate);
1016 // Chunks must be the highest-feerate remaining prefix.
1017 SetInfo<TestBitSet> accumulator, best;
1018 for (auto j : linearization) {
1019 if (todo[j] && !combined[j]) {
1020 accumulator.Set(depgraph, j);
1021 if (best.feerate.IsEmpty() || accumulator.feerate > best.feerate) {
1022 best = accumulator;
1023 }
1024 }
1025 }
1026 assert(best.transactions == chunk_info.transactions);
1027 assert(best.feerate == chunk_info.feerate);
1028 // Chunks cannot overlap.
1029 assert(!chunk_info.transactions.Overlaps(combined));
1030 combined |= chunk_info.transactions;
1031 // Chunks must be topological.
1032 for (auto idx : chunk_info.transactions) {
1033 assert((depgraph.Ancestors(idx) & todo).IsSubsetOf(combined));
1034 }
1035 }
1036 assert(combined == todo);
1037
1038 // Verify the expected properties of LinearizationChunking::IntersectPrefixes:
1039 auto intersect = chunking.IntersectPrefixes(subset);
1040 // - Intersecting again doesn't change the result.
1041 assert(chunking.IntersectPrefixes(intersect) == intersect);
1042 // - The intersection is topological.
1043 TestBitSet intersect_anc;
1044 for (auto idx : intersect.transactions) {
1045 intersect_anc |= (depgraph.Ancestors(idx) & todo);
1046 }
1047 assert(intersect.transactions == intersect_anc);
1048 // - The claimed intersection feerate matches its transactions.
1049 assert(intersect.feerate == depgraph.FeeRate(intersect.transactions));
1050 // - The intersection may only be empty if its input is empty.
1051 assert(intersect.transactions.Any() == subset.transactions.Any());
1052 // - The intersection feerate must be as high as the input.
1053 assert(intersect.feerate >= subset.feerate);
1054 // - No non-empty intersection between the intersection and a prefix of the chunks of the
1055 // remainder of the linearization may be better than the intersection.
1056 TestBitSet prefix;
1057 for (DepGraphIndex i = 0; i < chunking.NumChunksLeft(); ++i) {
1058 prefix |= chunking.GetChunk(i).transactions;
1059 auto reintersect = SetInfo(depgraph, prefix & intersect.transactions);
1060 if (!reintersect.feerate.IsEmpty()) {
1061 assert(reintersect.feerate <= intersect.feerate);
1062 }
1063 }
1064
1065 // Find a non-empty topologically valid subset of transactions to remove from the graph.
1066 // Using an empty set would mean the next iteration is identical to the current one, and
1067 // could cause an infinite loop.
1068 auto done = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
1069 todo -= done;
1070 chunking.MarkDone(done);
1071 subset = SetInfo(depgraph, subset.transactions - done);
1072 }
1073
1074 assert(chunking.NumChunksLeft() == 0);
1075}
1076
1077FUZZ_TARGET(clusterlin_simple_linearize)
1078{
1079 // Verify the behavior of SimpleLinearize(). Note that SimpleLinearize is only used in tests;
1080 // the purpose of this fuzz test is to establish confidence in SimpleLinearize, so that it can
1081 // be used to test the real Linearize function in the fuzz test below.
1082
1083 // Retrieve an iteration count and a depgraph from the fuzz input.
1084 SpanReader reader(buffer);
1085 uint64_t iter_count{0};
1086 DepGraph<TestBitSet> depgraph;
1087 try {
1088 reader >> VARINT(iter_count) >> Using<DepGraphFormatter>(depgraph);
1089 } catch (const std::ios_base::failure&) {}
1090 iter_count %= MAX_SIMPLE_ITERATIONS;
1091
1092 // Invoke SimpleLinearize().
1093 auto [linearization, optimal] = SimpleLinearize(depgraph, iter_count);
1094 SanityCheck(depgraph, linearization);
1095 auto simple_chunking = ChunkLinearization(depgraph, linearization);
1096
1097 // If the iteration count is sufficiently high, an optimal linearization must be found.
1098 // SimpleLinearize on k transactions can take up to 2^(k-1) iterations (one per non-empty
1099 // connected topologically valid subset), which sums over k=1..n to (2^n)-1.
1100 const uint64_t n = depgraph.TxCount();
1101 if (n <= 63 && (iter_count >> n)) {
1102 assert(optimal);
1103 }
1104
1105 // If SimpleLinearize claims optimal result, and the cluster is sufficiently small (there are
1106 // n! linearizations), test that the result is as good as every valid linearization.
1107 if (optimal && depgraph.TxCount() <= 8) {
1108 auto exh_linearization = ExhaustiveLinearize(depgraph);
1109 auto exh_chunking = ChunkLinearization(depgraph, exh_linearization);
1110 auto cmp = CompareChunks(simple_chunking, exh_chunking);
1111 assert(cmp == 0);
1112 assert(simple_chunking.size() == exh_chunking.size());
1113 }
1114
1115 if (optimal) {
1116 // Compare with a linearization read from the fuzz input.
1117 auto read = ReadLinearization(depgraph, reader);
1118 auto read_chunking = ChunkLinearization(depgraph, read);
1119 auto cmp = CompareChunks(simple_chunking, read_chunking);
1120 assert(cmp >= 0);
1121 }
1122}
1123
1124FUZZ_TARGET(clusterlin_linearize)
1125{
1126 // Verify the behavior of Linearize().
1127
1128 // Retrieve an RNG seed, an iteration count, a depgraph, and whether to make it connected from
1129 // the fuzz input.
1130 SpanReader reader(buffer);
1131 DepGraph<TestBitSet> depgraph;
1132 uint64_t rng_seed{0};
1133 uint64_t iter_count{0};
1134 uint8_t make_connected{1};
1135 try {
1136 reader >> VARINT(iter_count) >> Using<DepGraphFormatter>(depgraph) >> rng_seed >> make_connected;
1137 } catch (const std::ios_base::failure&) {}
1138 // The most complicated graphs are connected ones (other ones just split up). Optionally force
1139 // the graph to be connected.
1140 if (make_connected) MakeConnected(depgraph);
1141
1142 // Optionally construct an old linearization for it.
1143 std::vector<DepGraphIndex> old_linearization;
1144 {
1145 uint8_t have_old_linearization{0};
1146 try {
1147 reader >> have_old_linearization;
1148 } catch(const std::ios_base::failure&) {}
1149 if (have_old_linearization & 1) {
1150 old_linearization = ReadLinearization(depgraph, reader);
1151 SanityCheck(depgraph, old_linearization);
1152 }
1153 }
1154
1155 // Invoke Linearize().
1156 iter_count &= 0x7ffff;
1157 auto [linearization, optimal, cost] = Linearize(depgraph, iter_count, rng_seed, old_linearization);
1158 assert(cost <= iter_count);
1159 SanityCheck(depgraph, linearization);
1160 auto chunking = ChunkLinearization(depgraph, linearization);
1161
1162 // Linearization must always be as good as the old one, if provided.
1163 if (!old_linearization.empty()) {
1164 auto old_chunking = ChunkLinearization(depgraph, old_linearization);
1165 auto cmp = CompareChunks(chunking, old_chunking);
1166 assert(cmp >= 0);
1167 }
1168
1169 // If the iteration count is sufficiently high, an optimal linearization must be found.
1170 if (iter_count >= MaxOptimalLinearizationIters(depgraph.TxCount())) {
1171 assert(optimal);
1172 }
1173
1174 // If Linearize claims optimal result, run quality tests.
1175 if (optimal) {
1176 // It must be as good as SimpleLinearize.
1177 auto [simple_linearization, simple_optimal] = SimpleLinearize(depgraph, MAX_SIMPLE_ITERATIONS);
1178 SanityCheck(depgraph, simple_linearization);
1179 auto simple_chunking = ChunkLinearization(depgraph, simple_linearization);
1180 auto cmp = CompareChunks(chunking, simple_chunking);
1181 assert(cmp >= 0);
1182 // If SimpleLinearize finds the optimal result too, they must be equal (if not,
1183 // SimpleLinearize is broken).
1184 if (simple_optimal) assert(cmp == 0);
1185 // If simple_chunking is diagram-optimal, it cannot have more chunks than chunking (as
1186 // chunking is claimed to be optimal, which implies minimal chunks).
1187 if (cmp == 0) assert(chunking.size() >= simple_chunking.size());
1188
1189 // Compare with a linearization read from the fuzz input.
1190 auto read = ReadLinearization(depgraph, reader);
1191 auto read_chunking = ChunkLinearization(depgraph, read);
1192 auto cmp_read = CompareChunks(chunking, read_chunking);
1193 assert(cmp_read >= 0);
1194 }
1195}
1196
1197FUZZ_TARGET(clusterlin_postlinearize)
1198{
1199 // Verify expected properties of PostLinearize() on arbitrary linearizations.
1200
1201 // Retrieve a depgraph from the fuzz input.
1202 SpanReader reader(buffer);
1203 DepGraph<TestBitSet> depgraph;
1204 try {
1205 reader >> Using<DepGraphFormatter>(depgraph);
1206 } catch (const std::ios_base::failure&) {}
1207
1208 // Retrieve a linearization from the fuzz input.
1209 std::vector<DepGraphIndex> linearization;
1210 linearization = ReadLinearization(depgraph, reader);
1211 SanityCheck(depgraph, linearization);
1212
1213 // Produce a post-processed version.
1214 auto post_linearization = linearization;
1215 PostLinearize(depgraph, post_linearization);
1216 SanityCheck(depgraph, post_linearization);
1217
1218 // Compare diagrams: post-linearization cannot worsen anywhere.
1219 auto chunking = ChunkLinearization(depgraph, linearization);
1220 auto post_chunking = ChunkLinearization(depgraph, post_linearization);
1221 auto cmp = CompareChunks(post_chunking, chunking);
1222 assert(cmp >= 0);
1223
1224 // Run again, things can keep improving (and never get worse)
1225 auto post_post_linearization = post_linearization;
1226 PostLinearize(depgraph, post_post_linearization);
1227 SanityCheck(depgraph, post_post_linearization);
1228 auto post_post_chunking = ChunkLinearization(depgraph, post_post_linearization);
1229 cmp = CompareChunks(post_post_chunking, post_chunking);
1230 assert(cmp >= 0);
1231
1232 // The chunks that come out of postlinearizing are always connected.
1233 LinearizationChunking linchunking(depgraph, post_linearization);
1234 while (linchunking.NumChunksLeft()) {
1235 assert(depgraph.IsConnected(linchunking.GetChunk(0).transactions));
1236 linchunking.MarkDone(linchunking.GetChunk(0).transactions);
1237 }
1238}
1239
1240FUZZ_TARGET(clusterlin_postlinearize_tree)
1241{
1242 // Verify expected properties of PostLinearize() on linearizations of graphs that form either
1243 // an upright or reverse tree structure.
1244
1245 // Construct a direction, RNG seed, and an arbitrary graph from the fuzz input.
1246 SpanReader reader(buffer);
1247 uint64_t rng_seed{0};
1248 DepGraph<TestBitSet> depgraph_gen;
1249 uint8_t direction{0};
1250 try {
1251 reader >> direction >> rng_seed >> Using<DepGraphFormatter>(depgraph_gen);
1252 } catch (const std::ios_base::failure&) {}
1253
1254 // Now construct a new graph, copying the nodes, but leaving only the first parent (even
1255 // direction) or the first child (odd direction).
1256 DepGraph<TestBitSet> depgraph_tree;
1257 for (DepGraphIndex i = 0; i < depgraph_gen.PositionRange(); ++i) {
1258 if (depgraph_gen.Positions()[i]) {
1259 depgraph_tree.AddTransaction(depgraph_gen.FeeRate(i));
1260 } else {
1261 // For holes, add a dummy transaction which is deleted below, so that non-hole
1262 // transactions retain their position.
1263 depgraph_tree.AddTransaction(FeeFrac{});
1264 }
1265 }
1266 depgraph_tree.RemoveTransactions(TestBitSet::Fill(depgraph_gen.PositionRange()) - depgraph_gen.Positions());
1267
1268 if (direction & 1) {
1269 for (DepGraphIndex i = 0; i < depgraph_gen.TxCount(); ++i) {
1270 auto children = depgraph_gen.GetReducedChildren(i);
1271 if (children.Any()) {
1272 depgraph_tree.AddDependencies(TestBitSet::Singleton(i), children.First());
1273 }
1274 }
1275 } else {
1276 for (DepGraphIndex i = 0; i < depgraph_gen.TxCount(); ++i) {
1277 auto parents = depgraph_gen.GetReducedParents(i);
1278 if (parents.Any()) {
1279 depgraph_tree.AddDependencies(TestBitSet::Singleton(parents.First()), i);
1280 }
1281 }
1282 }
1283
1284 // Retrieve a linearization from the fuzz input.
1285 std::vector<DepGraphIndex> linearization;
1286 linearization = ReadLinearization(depgraph_tree, reader);
1287 SanityCheck(depgraph_tree, linearization);
1288
1289 // Produce a postlinearized version.
1290 auto post_linearization = linearization;
1291 PostLinearize(depgraph_tree, post_linearization);
1292 SanityCheck(depgraph_tree, post_linearization);
1293
1294 // Compare diagrams.
1295 auto chunking = ChunkLinearization(depgraph_tree, linearization);
1296 auto post_chunking = ChunkLinearization(depgraph_tree, post_linearization);
1297 auto cmp = CompareChunks(post_chunking, chunking);
1298 assert(cmp >= 0);
1299
1300 // Verify that post-linearizing again does not change the diagram. The result must be identical
1301 // as post_linearization ought to be optimal already with a tree-structured graph.
1302 auto post_post_linearization = post_linearization;
1303 PostLinearize(depgraph_tree, post_linearization);
1304 SanityCheck(depgraph_tree, post_linearization);
1305 auto post_post_chunking = ChunkLinearization(depgraph_tree, post_post_linearization);
1306 auto cmp_post = CompareChunks(post_post_chunking, post_chunking);
1307 assert(cmp_post == 0);
1308
1309 // Try to find an even better linearization directly. This must not change the diagram for the
1310 // same reason.
1311 auto [opt_linearization, _optimal, _cost] = Linearize(depgraph_tree, 100000, rng_seed, post_linearization);
1312 auto opt_chunking = ChunkLinearization(depgraph_tree, opt_linearization);
1313 auto cmp_opt = CompareChunks(opt_chunking, post_chunking);
1314 assert(cmp_opt == 0);
1315}
1316
1317FUZZ_TARGET(clusterlin_postlinearize_moved_leaf)
1318{
1319 // Verify that taking an existing linearization, and moving a leaf to the back, potentially
1320 // increasing its fee, and then post-linearizing, results in something as good as the
1321 // original. This guarantees that in an RBF that replaces a transaction with one of the same
1322 // size but higher fee, applying the "remove conflicts, append new transaction, postlinearize"
1323 // process will never worsen linearization quality.
1324
1325 // Construct an arbitrary graph and a fee from the fuzz input.
1326 SpanReader reader(buffer);
1327 DepGraph<TestBitSet> depgraph;
1328 int32_t fee_inc{0};
1329 try {
1330 uint64_t fee_inc_code;
1331 reader >> Using<DepGraphFormatter>(depgraph) >> VARINT(fee_inc_code);
1332 fee_inc = fee_inc_code & 0x3ffff;
1333 } catch (const std::ios_base::failure&) {}
1334 if (depgraph.TxCount() == 0) return;
1335
1336 // Retrieve two linearizations from the fuzz input.
1337 auto lin = ReadLinearization(depgraph, reader);
1338 auto lin_leaf = ReadLinearization(depgraph, reader);
1339
1340 // Construct a linearization identical to lin, but with the tail end of lin_leaf moved to the
1341 // back.
1342 std::vector<DepGraphIndex> lin_moved;
1343 for (auto i : lin) {
1344 if (i != lin_leaf.back()) lin_moved.push_back(i);
1345 }
1346 lin_moved.push_back(lin_leaf.back());
1347
1348 // Postlinearize lin_moved.
1349 PostLinearize(depgraph, lin_moved);
1350 SanityCheck(depgraph, lin_moved);
1351
1352 // Compare diagrams (applying the fee delta after computing the old one).
1353 auto old_chunking = ChunkLinearization(depgraph, lin);
1354 depgraph.FeeRate(lin_leaf.back()).fee += fee_inc;
1355 auto new_chunking = ChunkLinearization(depgraph, lin_moved);
1356 auto cmp = CompareChunks(new_chunking, old_chunking);
1357 assert(cmp >= 0);
1358}
1359
1360FUZZ_TARGET(clusterlin_merge)
1361{
1362 // Construct an arbitrary graph from the fuzz input.
1363 SpanReader reader(buffer);
1364 DepGraph<TestBitSet> depgraph;
1365 try {
1366 reader >> Using<DepGraphFormatter>(depgraph);
1367 } catch (const std::ios_base::failure&) {}
1368
1369 // Retrieve two linearizations from the fuzz input.
1370 auto lin1 = ReadLinearization(depgraph, reader);
1371 auto lin2 = ReadLinearization(depgraph, reader);
1372
1373 // Merge the two.
1374 auto lin_merged = MergeLinearizations(depgraph, lin1, lin2);
1375
1376 // Compute chunkings and compare.
1377 auto chunking1 = ChunkLinearization(depgraph, lin1);
1378 auto chunking2 = ChunkLinearization(depgraph, lin2);
1379 auto chunking_merged = ChunkLinearization(depgraph, lin_merged);
1380 auto cmp1 = CompareChunks(chunking_merged, chunking1);
1381 assert(cmp1 >= 0);
1382 auto cmp2 = CompareChunks(chunking_merged, chunking2);
1383 assert(cmp2 >= 0);
1384}
1385
1386FUZZ_TARGET(clusterlin_fix_linearization)
1387{
1388 // Verify expected properties of FixLinearization() on arbitrary linearizations.
1389
1390 // Retrieve a depgraph from the fuzz input.
1391 SpanReader reader(buffer);
1392 DepGraph<TestBitSet> depgraph;
1393 try {
1394 reader >> Using<DepGraphFormatter>(depgraph);
1395 } catch (const std::ios_base::failure&) {}
1396
1397 // Construct an arbitrary linearization (not necessarily topological for depgraph).
1398 std::vector<DepGraphIndex> linearization;
1400 TestBitSet todo = depgraph.Positions();
1401 while (todo.Any()) {
1402 // Read a number from the fuzz input in range [0, todo.Count()).
1403 uint64_t val{0};
1404 try {
1405 reader >> VARINT(val);
1406 } catch (const std::ios_base::failure&) {}
1407 val %= todo.Count();
1408 // Find the val'th element in todo, remove it from todo, and append it to linearization.
1409 for (auto idx : todo) {
1410 if (val == 0) {
1411 linearization.push_back(idx);
1412 todo.Reset(idx);
1413 break;
1414 }
1415 --val;
1416 }
1417 }
1418 assert(linearization.size() == depgraph.TxCount());
1419
1420 // Determine what prefix of linearization is topological, i.e., the position of the first entry
1421 // in linearization which corresponds to a transaction that is not preceded by all its
1422 // ancestors.
1423 size_t topo_prefix = 0;
1424 todo = depgraph.Positions();
1425 while (topo_prefix < linearization.size()) {
1426 DepGraphIndex idx = linearization[topo_prefix];
1427 todo.Reset(idx);
1428 if (todo.Overlaps(depgraph.Ancestors(idx))) break;
1429 ++topo_prefix;
1430 }
1431
1432 // Then make a fixed copy of linearization.
1433 auto linearization_fixed = linearization;
1434 FixLinearization(depgraph, linearization_fixed);
1435 // Sanity check it (which includes testing whether it is topological).
1436 SanityCheck(depgraph, linearization_fixed);
1437
1438 // FixLinearization does not modify the topological prefix of linearization.
1439 assert(std::equal(linearization.begin(), linearization.begin() + topo_prefix,
1440 linearization_fixed.begin()));
1441 // This also means that if linearization was entirely topological, FixLinearization cannot have
1442 // modified it. This is implied by the assertion above already, but repeat it explicitly.
1443 if (topo_prefix == linearization.size()) {
1444 assert(linearization == linearization_fixed);
1445 }
1446}
#define LIFETIMEBOUND
Definition: attributes.h:16
int ret
const auto command
#define Assume(val)
Assume is the identity function.
Definition: check.h:118
T ConsumeIntegralInRange(T min, T max)
Minimal stream for reading from an existing byte array by std::span.
Definition: streams.h:83
Class encapsulating the state needed to find the best remaining ancestor set.
void MarkDone(SetType select) noexcept
Remove a set of transactions from the set of to-be-linearized ones.
DepGraphIndex NumRemaining() const noexcept
Count the number of remaining unlinearized transactions.
SetInfo< SetType > FindCandidateSet() const noexcept
Find the best (highest-feerate, smallest among those in case of a tie) ancestor set among the remaini...
bool AllDone() const noexcept
Check whether any unlinearized transactions remain.
Data structure that holds a transaction graph's preprocessed data (fee, size, ancestors,...
const SetType & Ancestors(DepGraphIndex i) const noexcept
Get the ancestors of a given transaction i.
SetType GetReducedChildren(DepGraphIndex i) const noexcept
Compute the (reduced) set of children of node i in this graph.
SetType GetReducedParents(DepGraphIndex i) const noexcept
Compute the (reduced) set of parents of node i in this graph.
void AppendTopo(std::vector< DepGraphIndex > &list, const SetType &select) const noexcept
Append the entries of select to list in a topologically valid order.
const FeeFrac & FeeRate(DepGraphIndex i) const noexcept
Get the feerate of a given transaction i.
SetType GetConnectedComponent(const SetType &todo, DepGraphIndex tx) const noexcept
Get the connected component within the subset "todo" that contains tx (which must be in todo).
bool IsConnected(const SetType &subset) const noexcept
Determine if a subset is connected.
DepGraphIndex PositionRange() const noexcept
Get the range of positions in this DepGraph.
SetType FindConnectedComponent(const SetType &todo) const noexcept
Find some connected component within the subset "todo" of this graph.
DepGraphIndex AddTransaction(const FeeFrac &feefrac) noexcept
Add a new unconnected transaction to this transaction graph (in the first available position),...
void RemoveTransactions(const SetType &del) noexcept
Remove the specified positions from this DepGraph.
auto TxCount() const noexcept
Get the number of transactions in the graph.
const SetType & Descendants(DepGraphIndex i) const noexcept
Get the descendants of a given transaction i.
const SetType & Positions() const noexcept
Get the set of transactions positions in use.
void AddDependencies(const SetType &parents, DepGraphIndex child) noexcept
Modify this transaction graph, adding multiple parents to a specified child.
bool IsAcyclic() const noexcept
Check if this graph is acyclic.
Data structure encapsulating the chunking of a linearization, permitting removal of subsets.
DepGraphIndex NumChunksLeft() const noexcept
Determine how many chunks remain in the linearization.
const SetInfo< SetType > & GetChunk(DepGraphIndex n) const noexcept
Access a chunk.
SetInfo< SetType > IntersectPrefixes(const SetInfo< SetType > &subset) const noexcept
Find the shortest intersection between subset and the prefixes of remaining chunks of the linearizati...
void MarkDone(SetType subset) noexcept
Remove some subset of transactions from the linearization.
Class encapsulating the state needed to perform search for good candidate sets.
bool AllDone() const noexcept
Check whether any unlinearized transactions remain.
void MarkDone(const SetType &done) noexcept
Remove a subset of transactions from the cluster being linearized.
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.
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
uint64_t fee
std::vector< FeeFrac > ChunkLinearization(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the feerates of the chunks of linearization.
std::tuple< std::vector< DepGraphIndex >, bool, uint64_t > Linearize(const DepGraph< SetType > &depgraph, uint64_t max_iterations, uint64_t rng_seed, std::span< const DepGraphIndex > old_linearization={}) noexcept
Find or improve a linearization for a cluster.
void FixLinearization(const DepGraph< SetType > &depgraph, std::span< DepGraphIndex > linearization) noexcept
Make linearization topological, retaining its ordering where possible.
std::vector< DepGraphIndex > MergeLinearizations(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > lin1, std::span< const DepGraphIndex > lin2)
Merge two linearizations for the same cluster into one that is as good as both.
uint32_t DepGraphIndex
Data type to represent transaction indices in DepGraphs and the clusters they represent.
void PostLinearize(const DepGraph< SetType > &depgraph, std::span< DepGraphIndex > linearization)
Improve a given linearization.
static std::vector< std::string > split(const std::string &str, const std::string &delims=" \t")
Definition: subprocess.h:315
const char * prefix
Definition: rest.cpp:1117
#define VARINT(obj)
Definition: serialize.h:491
Data structure storing a fee and size, ordered by increasing fee/size.
Definition: feefrac.h:40
int64_t fee
Definition: feefrac.h:107
bool IsEmpty() const noexcept
Check if this is empty (size and fee are 0).
Definition: feefrac.h:120
A set of transactions together with their aggregate feerate.
FeeFrac feerate
Their combined fee and size.
void Set(const DepGraph< SetType > &depgraph, DepGraphIndex pos) noexcept
Add a transaction to this SetInfo (which must not yet be in it).
SetType transactions
The transactions in the set.
FUZZ_TARGET(clusterlin_depgraph_sim)
static constexpr auto MAX_SIMPLE_ITERATIONS
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
assert(!tx.IsCoinBase())