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