Bitcoin Core 30.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 * | 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
65using namespace cluster_linearize;
66
67namespace {
68
71template<typename SetType>
72class SimpleCandidateFinder
73{
75 const DepGraph<SetType>& m_depgraph;
77 SetType m_todo;
78
79public:
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
144template<typename SetType>
145class ExhaustiveCandidateFinder
146{
148 const DepGraph<SetType>& m_depgraph;
150 SetType m_todo;
151
152public:
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
196template<typename SetType>
197std::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
221template<typename SetType>
222std::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
267template<typename BS>
268void 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
284template<typename SetType>
285SetType 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
316template<typename BS>
317std::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
364template<typename BS>
365DepGraph<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
400FUZZ_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
562FUZZ_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
607FUZZ_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
699FUZZ_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
713FUZZ_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
764static constexpr auto MAX_SIMPLE_ITERATIONS = 300000;
765
766FUZZ_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
844FUZZ_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
891FUZZ_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(depgraph);
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();
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() <= MaxOptimalLinearizationIters(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
1010FUZZ_TARGET(clusterlin_linearize)
1011{
1012 // Verify the behavior of Linearize().
1013
1014 // Retrieve an RNG seed, an iteration count, a depgraph, and whether to make it connected from
1015 // the fuzz input.
1016 SpanReader reader(buffer);
1017 DepGraph<TestBitSet> depgraph;
1018 uint64_t rng_seed{0};
1019 uint64_t iter_count{0};
1020 uint8_t flags{7};
1021 try {
1022 reader >> VARINT(iter_count) >> Using<DepGraphFormatter>(depgraph) >> rng_seed >> flags;
1023 } catch (const std::ios_base::failure&) {}
1024 bool make_connected = flags & 1;
1025 // The following 3 booleans have 4 combinations:
1026 // - (flags & 6) == 0: do not provide input linearization.
1027 // - (flags & 6) == 2: provide potentially non-topological input.
1028 // - (flags & 6) == 4: provide topological input linearization, but do not claim it is
1029 // topological.
1030 // - (flags & 6) == 6: provide topological input linearization, and claim it is topological.
1031 bool provide_input = flags & 6;
1032 bool provide_topological_input = flags & 4;
1033 bool claim_topological_input = (flags & 6) == 6;
1034 // The most complicated graphs are connected ones (other ones just split up). Optionally force
1035 // the graph to be connected.
1036 if (make_connected) MakeConnected(depgraph);
1037
1038 // Optionally construct an old linearization for it.
1039 std::vector<DepGraphIndex> old_linearization;
1040 if (provide_input) {
1041 old_linearization = ReadLinearization(depgraph, reader, /*topological=*/provide_topological_input);
1042 if (provide_topological_input) SanityCheck(depgraph, old_linearization);
1043 }
1044
1045 // Invoke Linearize().
1046 iter_count &= 0x7ffff;
1047 auto [linearization, optimal, cost] = Linearize(depgraph, iter_count, rng_seed, old_linearization, /*is_topological=*/claim_topological_input);
1048 SanityCheck(depgraph, linearization);
1049 auto chunking = ChunkLinearization(depgraph, linearization);
1050
1051 // Linearization must always be as good as the old one, if provided and topological (even when
1052 // not claimed to be topological).
1053 if (provide_topological_input) {
1054 auto old_chunking = ChunkLinearization(depgraph, old_linearization);
1055 auto cmp = CompareChunks(chunking, old_chunking);
1056 assert(cmp >= 0);
1057 }
1058
1059 // If the iteration count is sufficiently high, an optimal linearization must be found.
1060 if (iter_count > MaxOptimalLinearizationIters(depgraph.TxCount())) {
1061 assert(optimal);
1062 }
1063
1064 // If Linearize claims optimal result, run quality tests.
1065 if (optimal) {
1066 // It must be as good as SimpleLinearize.
1067 auto [simple_linearization, simple_optimal] = SimpleLinearize(depgraph, MAX_SIMPLE_ITERATIONS);
1068 SanityCheck(depgraph, simple_linearization);
1069 auto simple_chunking = ChunkLinearization(depgraph, simple_linearization);
1070 auto cmp = CompareChunks(chunking, simple_chunking);
1071 assert(cmp >= 0);
1072 // If SimpleLinearize finds the optimal result too, they must be equal (if not,
1073 // SimpleLinearize is broken).
1074 if (simple_optimal) assert(cmp == 0);
1075
1076 // If simple_chunking is diagram-optimal, it cannot have more chunks than chunking (as
1077 // chunking is claimed to be optimal, which implies minimal chunks).
1078 if (cmp == 0) assert(chunking.size() >= simple_chunking.size());
1079
1080 // Compare with a linearization read from the fuzz input.
1081 auto read = ReadLinearization(depgraph, reader);
1082 auto read_chunking = ChunkLinearization(depgraph, read);
1083 auto cmp_read = CompareChunks(chunking, read_chunking);
1084 assert(cmp_read >= 0);
1085 }
1086}
1087
1088FUZZ_TARGET(clusterlin_postlinearize)
1089{
1090 // Verify expected properties of PostLinearize() on arbitrary linearizations.
1091
1092 // Retrieve a depgraph from the fuzz input.
1093 SpanReader reader(buffer);
1094 DepGraph<TestBitSet> depgraph;
1095 try {
1096 reader >> Using<DepGraphFormatter>(depgraph);
1097 } catch (const std::ios_base::failure&) {}
1098
1099 // Retrieve a linearization from the fuzz input.
1100 std::vector<DepGraphIndex> linearization;
1101 linearization = ReadLinearization(depgraph, reader);
1102 SanityCheck(depgraph, linearization);
1103
1104 // Produce a post-processed version.
1105 auto post_linearization = linearization;
1106 PostLinearize(depgraph, post_linearization);
1107 SanityCheck(depgraph, post_linearization);
1108
1109 // Compare diagrams: post-linearization cannot worsen anywhere.
1110 auto chunking = ChunkLinearization(depgraph, linearization);
1111 auto post_chunking = ChunkLinearization(depgraph, post_linearization);
1112 auto cmp = CompareChunks(post_chunking, chunking);
1113 assert(cmp >= 0);
1114
1115 // Run again, things can keep improving (and never get worse)
1116 auto post_post_linearization = post_linearization;
1117 PostLinearize(depgraph, post_post_linearization);
1118 SanityCheck(depgraph, post_post_linearization);
1119 auto post_post_chunking = ChunkLinearization(depgraph, post_post_linearization);
1120 cmp = CompareChunks(post_post_chunking, post_chunking);
1121 assert(cmp >= 0);
1122
1123 // The chunks that come out of postlinearizing are always connected.
1124 auto linchunking = ChunkLinearizationInfo(depgraph, post_linearization);
1125 for (const auto& [chunk_set, _chunk_feerate] : linchunking) {
1126 assert(depgraph.IsConnected(chunk_set));
1127 }
1128}
1129
1130FUZZ_TARGET(clusterlin_postlinearize_tree)
1131{
1132 // Verify expected properties of PostLinearize() on linearizations of graphs that form either
1133 // an upright or reverse tree structure.
1134
1135 // Construct a direction, RNG seed, and an arbitrary graph from the fuzz input.
1136 SpanReader reader(buffer);
1137 uint64_t rng_seed{0};
1138 DepGraph<TestBitSet> depgraph_gen;
1139 uint8_t direction{0};
1140 try {
1141 reader >> direction >> rng_seed >> Using<DepGraphFormatter>(depgraph_gen);
1142 } catch (const std::ios_base::failure&) {}
1143
1144 auto depgraph_tree = BuildTreeGraph(depgraph_gen, direction);
1145
1146 // Retrieve a linearization from the fuzz input.
1147 std::vector<DepGraphIndex> linearization;
1148 linearization = ReadLinearization(depgraph_tree, reader);
1149 SanityCheck(depgraph_tree, linearization);
1150
1151 // Produce a postlinearized version.
1152 auto post_linearization = linearization;
1153 PostLinearize(depgraph_tree, post_linearization);
1154 SanityCheck(depgraph_tree, post_linearization);
1155
1156 // Compare diagrams.
1157 auto chunking = ChunkLinearization(depgraph_tree, linearization);
1158 auto post_chunking = ChunkLinearization(depgraph_tree, post_linearization);
1159 auto cmp = CompareChunks(post_chunking, chunking);
1160 assert(cmp >= 0);
1161
1162 // Verify that post-linearizing again does not change the diagram. The result must be identical
1163 // as post_linearization ought to be optimal already with a tree-structured graph.
1164 auto post_post_linearization = post_linearization;
1165 PostLinearize(depgraph_tree, post_post_linearization);
1166 SanityCheck(depgraph_tree, post_post_linearization);
1167 auto post_post_chunking = ChunkLinearization(depgraph_tree, post_post_linearization);
1168 auto cmp_post = CompareChunks(post_post_chunking, post_chunking);
1169 assert(cmp_post == 0);
1170
1171 // Try to find an even better linearization directly. This must not change the diagram for the
1172 // same reason.
1173 auto [opt_linearization, _optimal, _cost] = Linearize(depgraph_tree, 100000, rng_seed, post_linearization);
1174 auto opt_chunking = ChunkLinearization(depgraph_tree, opt_linearization);
1175 auto cmp_opt = CompareChunks(opt_chunking, post_chunking);
1176 assert(cmp_opt == 0);
1177}
1178
1179FUZZ_TARGET(clusterlin_postlinearize_moved_leaf)
1180{
1181 // Verify that taking an existing linearization, and moving a leaf to the back, potentially
1182 // increasing its fee, and then post-linearizing, results in something as good as the
1183 // original. This guarantees that in an RBF that replaces a transaction with one of the same
1184 // size but higher fee, applying the "remove conflicts, append new transaction, postlinearize"
1185 // process will never worsen linearization quality.
1186
1187 // Construct an arbitrary graph and a fee from the fuzz input.
1188 SpanReader reader(buffer);
1189 DepGraph<TestBitSet> depgraph;
1190 int32_t fee_inc{0};
1191 try {
1192 uint64_t fee_inc_code;
1193 reader >> Using<DepGraphFormatter>(depgraph) >> VARINT(fee_inc_code);
1194 fee_inc = fee_inc_code & 0x3ffff;
1195 } catch (const std::ios_base::failure&) {}
1196 if (depgraph.TxCount() == 0) return;
1197
1198 // Retrieve two linearizations from the fuzz input.
1199 auto lin = ReadLinearization(depgraph, reader);
1200 auto lin_leaf = ReadLinearization(depgraph, reader);
1201
1202 // Construct a linearization identical to lin, but with the tail end of lin_leaf moved to the
1203 // back.
1204 std::vector<DepGraphIndex> lin_moved;
1205 for (auto i : lin) {
1206 if (i != lin_leaf.back()) lin_moved.push_back(i);
1207 }
1208 lin_moved.push_back(lin_leaf.back());
1209
1210 // Postlinearize lin_moved.
1211 PostLinearize(depgraph, lin_moved);
1212 SanityCheck(depgraph, lin_moved);
1213
1214 // Compare diagrams (applying the fee delta after computing the old one).
1215 auto old_chunking = ChunkLinearization(depgraph, lin);
1216 depgraph.FeeRate(lin_leaf.back()).fee += fee_inc;
1217 auto new_chunking = ChunkLinearization(depgraph, lin_moved);
1218 auto cmp = CompareChunks(new_chunking, old_chunking);
1219 assert(cmp >= 0);
1220}
#define LIFETIMEBOUND
Definition: attributes.h:16
int ret
int flags
Definition: bitcoin-tx.cpp:529
const auto command
#define Assume(val)
Assume is the identity function.
Definition: check.h:125
T ConsumeIntegralInRange(T min, T max)
xoroshiro128++ PRNG.
Definition: random.h:425
constexpr uint64_t rand64() noexcept
Definition: random.h:448
uint64_t randbits(int bits) noexcept
Generate a random (bits)-bit integer.
Definition: random.h:204
Minimal stream for reading from an existing byte array by std::span.
Definition: streams.h:83
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.
size_t DynamicMemoryUsage() const noexcept
const SetType & Positions() const noexcept
Get the set of transactions positions in use.
void Compact() noexcept
Reduce memory usage if possible.
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.
Class to represent the internal state of the spanning-forest linearization (SFL) algorithm.
bool OptimizeStep() noexcept
Try to improve the forest.
uint64_t GetCost() const noexcept
Determine how much work was performed so far.
std::vector< FeeFrac > GetDiagram() const noexcept
Get the diagram for the current state, which must be topological.
void MakeTopological() noexcept
Make state topological.
void StartMinimizing() noexcept
Initialize data structure for minimizing the chunks.
void SanityCheck(const DepGraph< SetType > &depgraph) const
Verify internal consistency of the data structure.
void StartOptimizing() noexcept
Initialize the data structure for optimization.
void LoadLinearization(std::span< const DepGraphIndex > old_linearization) noexcept
Load an existing linearization.
std::vector< DepGraphIndex > GetLinearization() noexcept
Construct a topologically-valid linearization from the current forest state.
bool MinimizeStep() noexcept
Try to reduce a chunk's size.
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
uint64_t fee
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={}, bool is_topological=true) noexcept
Find or improve a linearization for a cluster.
std::vector< FeeFrac > ChunkLinearization(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the feerates of the chunks of linearization.
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.
std::vector< SetInfo< SetType > > ChunkLinearizationInfo(const DepGraph< SetType > &depgraph, std::span< const DepGraphIndex > linearization) noexcept
Compute the chunks of linearization as SetInfos.
static std::vector< std::string > split(const std::string &str, const std::string &delims=" \t")
Definition: subprocess.h:315
#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
void(* test_fn)(void)
Definition: unit_test.h:49
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())