Bitcoin Core 29.99.0
P2P Digital Currency
txgraph.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 <txgraph.h>
6#include <cluster_linearize.h>
7#include <test/fuzz/fuzz.h>
9#include <test/util/random.h>
10#include <util/bitset.h>
11#include <util/feefrac.h>
12
13#include <algorithm>
14#include <map>
15#include <memory>
16#include <set>
17#include <stdint.h>
18#include <utility>
19
20using namespace cluster_linearize;
21
22namespace {
23
27struct SimTxGraph
28{
32 static constexpr unsigned MAX_TRANSACTIONS = MAX_CLUSTER_COUNT_LIMIT * 2;
34 using SetType = BitSet<MAX_TRANSACTIONS>;
36 using Pos = DepGraphIndex;
38 static constexpr auto MISSING = Pos(-1);
39
46 std::array<std::shared_ptr<TxGraph::Ref>, MAX_TRANSACTIONS> simmap;
48 std::map<const TxGraph::Ref*, Pos> simrevmap;
50 std::vector<std::shared_ptr<TxGraph::Ref>> removed;
52 std::optional<bool> oversized;
54 DepGraphIndex max_cluster_count;
55
57 explicit SimTxGraph(DepGraphIndex max_cluster) : max_cluster_count(max_cluster) {}
58
59 // Permit copying and moving.
60 SimTxGraph(const SimTxGraph&) noexcept = default;
61 SimTxGraph& operator=(const SimTxGraph&) noexcept = default;
62 SimTxGraph(SimTxGraph&&) noexcept = default;
63 SimTxGraph& operator=(SimTxGraph&&) noexcept = default;
64
67 bool IsOversized()
68 {
69 if (!oversized.has_value()) {
70 // Only recompute when oversized isn't already known.
71 oversized = false;
72 auto todo = graph.Positions();
73 // Iterate over all connected components of the graph.
74 while (todo.Any()) {
75 auto component = graph.FindConnectedComponent(todo);
76 if (component.Count() > max_cluster_count) oversized = true;
77 todo -= component;
78 }
79 }
80 return *oversized;
81 }
82
84 DepGraphIndex GetTransactionCount() const { return graph.TxCount(); }
85
87 Pos Find(const TxGraph::Ref* ref) const
88 {
89 auto it = simrevmap.find(ref);
90 if (it != simrevmap.end()) return it->second;
91 return MISSING;
92 }
93
95 TxGraph::Ref* GetRef(Pos pos)
96 {
97 assert(graph.Positions()[pos]);
98 assert(simmap[pos]);
99 return simmap[pos].get();
100 }
101
103 TxGraph::Ref* AddTransaction(const FeePerWeight& feerate)
104 {
105 assert(graph.TxCount() < MAX_TRANSACTIONS);
106 auto simpos = graph.AddTransaction(feerate);
107 assert(graph.Positions()[simpos]);
108 simmap[simpos] = std::make_shared<TxGraph::Ref>();
109 auto ptr = simmap[simpos].get();
110 simrevmap[ptr] = simpos;
111 return ptr;
112 }
113
115 void AddDependency(TxGraph::Ref* parent, TxGraph::Ref* child)
116 {
117 auto par_pos = Find(parent);
118 if (par_pos == MISSING) return;
119 auto chl_pos = Find(child);
120 if (chl_pos == MISSING) return;
121 graph.AddDependencies(SetType::Singleton(par_pos), chl_pos);
122 // This may invalidate our cached oversized value.
123 if (oversized.has_value() && !*oversized) oversized = std::nullopt;
124 }
125
127 void SetTransactionFee(TxGraph::Ref* ref, int64_t fee)
128 {
129 auto pos = Find(ref);
130 if (pos == MISSING) return;
131 graph.FeeRate(pos).fee = fee;
132 }
133
135 void RemoveTransaction(TxGraph::Ref* ref)
136 {
137 auto pos = Find(ref);
138 if (pos == MISSING) return;
139 graph.RemoveTransactions(SetType::Singleton(pos));
140 simrevmap.erase(simmap[pos].get());
141 // Retain the TxGraph::Ref corresponding to this position, so the Ref destruction isn't
142 // invoked until the simulation explicitly decided to do so.
143 removed.push_back(std::move(simmap[pos]));
144 simmap[pos].reset();
145 // This may invalidate our cached oversized value.
146 if (oversized.has_value() && *oversized) oversized = std::nullopt;
147 }
148
153 void DestroyTransaction(TxGraph::Ref* ref, bool reset_oversize)
154 {
155 auto pos = Find(ref);
156 if (pos == MISSING) {
157 // Wipe the ref, if it exists, from the removed vector. Use std::partition rather
158 // than std::erase because we don't care about the order of the entries that
159 // remain.
160 auto remove = std::partition(removed.begin(), removed.end(), [&](auto& arg) { return arg.get() != ref; });
161 removed.erase(remove, removed.end());
162 } else {
163 graph.RemoveTransactions(SetType::Singleton(pos));
164 simrevmap.erase(simmap[pos].get());
165 simmap[pos].reset();
166 // This may invalidate our cached oversized value.
167 if (reset_oversize && oversized.has_value() && *oversized) {
168 oversized = std::nullopt;
169 }
170 }
171 }
172
175 SetType MakeSet(std::span<TxGraph::Ref* const> arg)
176 {
177 SetType ret;
178 for (TxGraph::Ref* ptr : arg) {
179 auto pos = Find(ptr);
180 assert(pos != Pos(-1));
181 ret.Set(pos);
182 }
183 return ret;
184 }
185
187 SetType GetAncDesc(TxGraph::Ref* arg, bool desc)
188 {
189 auto pos = Find(arg);
190 if (pos == MISSING) return {};
191 return desc ? graph.Descendants(pos) : graph.Ancestors(pos);
192 }
193
196 void IncludeAncDesc(std::vector<TxGraph::Ref*>& arg, bool desc)
197 {
198 std::vector<TxGraph::Ref*> ret;
199 for (auto ptr : arg) {
200 auto simpos = Find(ptr);
201 if (simpos != MISSING) {
202 for (auto i : desc ? graph.Descendants(simpos) : graph.Ancestors(simpos)) {
203 ret.push_back(simmap[i].get());
204 }
205 } else {
206 ret.push_back(ptr);
207 }
208 }
209 // Deduplicate.
210 std::sort(ret.begin(), ret.end());
211 ret.erase(std::unique(ret.begin(), ret.end()), ret.end());
212 // Replace input.
213 arg = std::move(ret);
214 }
215};
216
217} // namespace
218
220{
221 // This is a big simulation test for TxGraph, which performs a fuzz-derived sequence of valid
222 // operations on a TxGraph instance, as well as on a simpler (mostly) reimplementation (see
223 // SimTxGraph above), comparing the outcome of functions that return a result, and finally
224 // performing a full comparison between the two.
225
227 FuzzedDataProvider provider(buffer.data(), buffer.size());
228
231 InsecureRandomContext rng(0xdecade2009added + buffer.size());
232
234 TxGraph::Ref empty_ref;
235
236 // Decide the maximum number of transactions per cluster we will use in this simulation.
237 auto max_count = provider.ConsumeIntegralInRange<DepGraphIndex>(1, MAX_CLUSTER_COUNT_LIMIT);
238
239 // Construct a real graph, and a vector of simulated graphs (main, and possibly staging).
240 auto real = MakeTxGraph(max_count);
241 std::vector<SimTxGraph> sims;
242 sims.reserve(2);
243 sims.emplace_back(max_count);
244
247 auto pick_fn = [&]() noexcept -> TxGraph::Ref* {
248 size_t tx_count[2] = {sims[0].GetTransactionCount(), 0};
250 size_t choices = tx_count[0] + sims[0].removed.size() + 1;
251 if (sims.size() == 2) {
252 tx_count[1] = sims[1].GetTransactionCount();
253 choices += tx_count[1] + sims[1].removed.size();
254 }
256 auto choice = provider.ConsumeIntegralInRange<size_t>(0, choices - 1);
257 // Consider both main and (if it exists) staging.
258 for (size_t level = 0; level < sims.size(); ++level) {
259 auto& sim = sims[level];
260 if (choice < tx_count[level]) {
261 // Return from graph.
262 for (auto i : sim.graph.Positions()) {
263 if (choice == 0) return sim.GetRef(i);
264 --choice;
265 }
266 assert(false);
267 } else {
268 choice -= tx_count[level];
269 }
270 if (choice < sim.removed.size()) {
271 // Return from removed.
272 return sim.removed[choice].get();
273 } else {
274 choice -= sim.removed.size();
275 }
276 }
277 // Return empty.
278 assert(choice == 0);
279 return &empty_ref;
280 };
281
282 LIMITED_WHILE(provider.remaining_bytes() > 0, 200) {
283 // Read a one-byte command.
284 int command = provider.ConsumeIntegral<uint8_t>();
285 // Treat the lowest bit of a command as a flag (which selects a variant of some of the
286 // operations), and the second-lowest bit as a way of selecting main vs. staging, and leave
287 // the rest of the bits in command.
288 bool alt = command & 1;
289 bool use_main = command & 2;
290 command >>= 2;
291
292 // Provide convenient aliases for the top simulated graph (main, or staging if it exists),
293 // one for the simulated graph selected based on use_main (for operations that can operate
294 // on both graphs), and one that always refers to the main graph.
295 auto& top_sim = sims.back();
296 auto& sel_sim = use_main ? sims[0] : top_sim;
297 auto& main_sim = sims[0];
298
299 // Keep decrementing command for each applicable operation, until one is hit. Multiple
300 // iterations may be necessary.
301 while (true) {
302 if (top_sim.GetTransactionCount() < SimTxGraph::MAX_TRANSACTIONS && command-- == 0) {
303 // AddTransaction.
304 int64_t fee;
305 int32_t size;
306 if (alt) {
307 // If alt is true, pick fee and size from the entire range.
308 fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
309 size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
310 } else {
311 // Otherwise, use smaller range which consume fewer fuzz input bytes, as just
312 // these are likely sufficient to trigger all interesting code paths already.
313 fee = provider.ConsumeIntegral<uint8_t>();
314 size = provider.ConsumeIntegral<uint8_t>() + 1;
315 }
316 FeePerWeight feerate{fee, size};
317 // Create a real TxGraph::Ref.
318 auto ref = real->AddTransaction(feerate);
319 // Create a shared_ptr place in the simulation to put the Ref in.
320 auto ref_loc = top_sim.AddTransaction(feerate);
321 // Move it in place.
322 *ref_loc = std::move(ref);
323 break;
324 } else if (top_sim.GetTransactionCount() + top_sim.removed.size() > 1 && command-- == 0) {
325 // AddDependency.
326 auto par = pick_fn();
327 auto chl = pick_fn();
328 auto pos_par = top_sim.Find(par);
329 auto pos_chl = top_sim.Find(chl);
330 if (pos_par != SimTxGraph::MISSING && pos_chl != SimTxGraph::MISSING) {
331 // Determine if adding this would introduce a cycle (not allowed by TxGraph),
332 // and if so, skip.
333 if (top_sim.graph.Ancestors(pos_par)[pos_chl]) break;
334 }
335 top_sim.AddDependency(par, chl);
336 real->AddDependency(*par, *chl);
337 break;
338 } else if (top_sim.removed.size() < 100 && command-- == 0) {
339 // RemoveTransaction. Either all its ancestors or all its descendants are also
340 // removed (if any), to make sure TxGraph's reordering of removals and dependencies
341 // has no effect.
342 std::vector<TxGraph::Ref*> to_remove;
343 to_remove.push_back(pick_fn());
344 top_sim.IncludeAncDesc(to_remove, alt);
345 // The order in which these ancestors/descendants are removed should not matter;
346 // randomly shuffle them.
347 std::shuffle(to_remove.begin(), to_remove.end(), rng);
348 for (TxGraph::Ref* ptr : to_remove) {
349 real->RemoveTransaction(*ptr);
350 top_sim.RemoveTransaction(ptr);
351 }
352 break;
353 } else if (sel_sim.removed.size() > 0 && command-- == 0) {
354 // ~Ref (of an already-removed transaction). Destroying a TxGraph::Ref has an
355 // observable effect on the TxGraph it refers to, so this simulation permits doing
356 // so separately from other actions on TxGraph.
357
358 // Pick a Ref of sel_sim.removed to destroy. Note that the same Ref may still occur
359 // in the other graph, and thus not actually trigger ~Ref yet (which is exactly
360 // what we want, as destroying Refs is only allowed when it does not refer to an
361 // existing transaction in either graph).
362 auto removed_pos = provider.ConsumeIntegralInRange<size_t>(0, sel_sim.removed.size() - 1);
363 if (removed_pos != sel_sim.removed.size() - 1) {
364 std::swap(sel_sim.removed[removed_pos], sel_sim.removed.back());
365 }
366 sel_sim.removed.pop_back();
367 break;
368 } else if (command-- == 0) {
369 // ~Ref (of any transaction).
370 std::vector<TxGraph::Ref*> to_destroy;
371 to_destroy.push_back(pick_fn());
372 while (true) {
373 // Keep adding either the ancestors or descendants the already picked
374 // transactions have in both graphs (main and staging) combined. Destroying
375 // will trigger deletions in both, so to have consistent TxGraph behavior, the
376 // set must be closed under ancestors, or descendants, in both graphs.
377 auto old_size = to_destroy.size();
378 for (auto& sim : sims) sim.IncludeAncDesc(to_destroy, alt);
379 if (to_destroy.size() == old_size) break;
380 }
381 // The order in which these ancestors/descendants are destroyed should not matter;
382 // randomly shuffle them.
383 std::shuffle(to_destroy.begin(), to_destroy.end(), rng);
384 for (TxGraph::Ref* ptr : to_destroy) {
385 for (size_t level = 0; level < sims.size(); ++level) {
386 sims[level].DestroyTransaction(ptr, level == sims.size() - 1);
387 }
388 }
389 break;
390 } else if (command-- == 0) {
391 // SetTransactionFee.
392 int64_t fee;
393 if (alt) {
394 fee = provider.ConsumeIntegralInRange<int64_t>(-0x8000000000000, 0x7ffffffffffff);
395 } else {
396 fee = provider.ConsumeIntegral<uint8_t>();
397 }
398 auto ref = pick_fn();
399 real->SetTransactionFee(*ref, fee);
400 for (auto& sim : sims) {
401 sim.SetTransactionFee(ref, fee);
402 }
403 break;
404 } else if (command-- == 0) {
405 // GetTransactionCount.
406 assert(real->GetTransactionCount(use_main) == sel_sim.GetTransactionCount());
407 break;
408 } else if (command-- == 0) {
409 // Exists.
410 auto ref = pick_fn();
411 bool exists = real->Exists(*ref, use_main);
412 bool should_exist = sel_sim.Find(ref) != SimTxGraph::MISSING;
413 assert(exists == should_exist);
414 break;
415 } else if (command-- == 0) {
416 // IsOversized.
417 assert(sel_sim.IsOversized() == real->IsOversized(use_main));
418 break;
419 } else if (command-- == 0) {
420 // GetIndividualFeerate.
421 auto ref = pick_fn();
422 auto feerate = real->GetIndividualFeerate(*ref);
423 bool found{false};
424 for (auto& sim : sims) {
425 auto simpos = sim.Find(ref);
426 if (simpos != SimTxGraph::MISSING) {
427 found = true;
428 assert(feerate == sim.graph.FeeRate(simpos));
429 }
430 }
431 if (!found) assert(feerate.IsEmpty());
432 break;
433 } else if (!main_sim.IsOversized() && command-- == 0) {
434 // GetMainChunkFeerate.
435 auto ref = pick_fn();
436 auto feerate = real->GetMainChunkFeerate(*ref);
437 auto simpos = main_sim.Find(ref);
438 if (simpos == SimTxGraph::MISSING) {
439 assert(feerate.IsEmpty());
440 } else {
441 // Just do some quick checks that the reported value is in range. A full
442 // recomputation of expected chunk feerates is done at the end.
443 assert(feerate.size >= main_sim.graph.FeeRate(simpos).size);
444 }
445 break;
446 } else if (!sel_sim.IsOversized() && command-- == 0) {
447 // GetAncestors/GetDescendants.
448 auto ref = pick_fn();
449 auto result = alt ? real->GetDescendants(*ref, use_main)
450 : real->GetAncestors(*ref, use_main);
451 assert(result.size() <= max_count);
452 auto result_set = sel_sim.MakeSet(result);
453 assert(result.size() == result_set.Count());
454 auto expect_set = sel_sim.GetAncDesc(ref, alt);
455 assert(result_set == expect_set);
456 break;
457 } else if (!sel_sim.IsOversized() && command-- == 0) {
458 // GetAncestorsUnion/GetDescendantsUnion.
459 std::vector<TxGraph::Ref*> refs;
460 // Gather a list of up to 15 Ref pointers.
461 auto count = provider.ConsumeIntegralInRange<size_t>(0, 15);
462 refs.resize(count);
463 for (size_t i = 0; i < count; ++i) {
464 refs[i] = pick_fn();
465 }
466 // Their order should not matter, shuffle them.
467 std::shuffle(refs.begin(), refs.end(), rng);
468 // Invoke the real function, and convert to SimPos set.
469 auto result = alt ? real->GetDescendantsUnion(refs, use_main)
470 : real->GetAncestorsUnion(refs, use_main);
471 auto result_set = sel_sim.MakeSet(result);
472 assert(result.size() == result_set.Count());
473 // Compute the expected result.
474 SimTxGraph::SetType expect_set;
475 for (TxGraph::Ref* ref : refs) expect_set |= sel_sim.GetAncDesc(ref, alt);
476 // Compare.
477 assert(result_set == expect_set);
478 break;
479 } else if (!sel_sim.IsOversized() && command-- == 0) {
480 // GetCluster.
481 auto ref = pick_fn();
482 auto result = real->GetCluster(*ref, use_main);
483 // Check cluster count limit.
484 assert(result.size() <= max_count);
485 // Require the result to be topologically valid and not contain duplicates.
486 auto left = sel_sim.graph.Positions();
487 for (auto refptr : result) {
488 auto simpos = sel_sim.Find(refptr);
489 assert(simpos != SimTxGraph::MISSING);
490 assert(left[simpos]);
491 left.Reset(simpos);
492 assert(!sel_sim.graph.Ancestors(simpos).Overlaps(left));
493 }
494 // Require the set to be connected.
495 auto result_set = sel_sim.MakeSet(result);
496 assert(sel_sim.graph.IsConnected(result_set));
497 // If ref exists, the result must contain it. If not, it must be empty.
498 auto simpos = sel_sim.Find(ref);
499 if (simpos != SimTxGraph::MISSING) {
500 assert(result_set[simpos]);
501 } else {
502 assert(result_set.None());
503 }
504 // Require the set not to have ancestors or descendants outside of it.
505 for (auto i : result_set) {
506 assert(sel_sim.graph.Ancestors(i).IsSubsetOf(result_set));
507 assert(sel_sim.graph.Descendants(i).IsSubsetOf(result_set));
508 }
509 break;
510 } else if (command-- == 0) {
511 // HaveStaging.
512 assert((sims.size() == 2) == real->HaveStaging());
513 break;
514 } else if (sims.size() < 2 && command-- == 0) {
515 // StartStaging.
516 sims.emplace_back(sims.back());
517 real->StartStaging();
518 break;
519 } else if (sims.size() > 1 && command-- == 0) {
520 // CommitStaging.
521 real->CommitStaging();
522 sims.erase(sims.begin());
523 break;
524 } else if (sims.size() > 1 && command-- == 0) {
525 // AbortStaging.
526 real->AbortStaging();
527 sims.pop_back();
528 // Reset the cached oversized value (if TxGraph::Ref destructions triggered
529 // removals of main transactions while staging was active, then aborting will
530 // cause it to be re-evaluated in TxGraph).
531 sims.back().oversized = std::nullopt;
532 break;
533 } else if (!main_sim.IsOversized() && command-- == 0) {
534 // CompareMainOrder.
535 auto ref_a = pick_fn();
536 auto ref_b = pick_fn();
537 auto sim_a = main_sim.Find(ref_a);
538 auto sim_b = main_sim.Find(ref_b);
539 // Both transactions must exist in the main graph.
540 if (sim_a == SimTxGraph::MISSING || sim_b == SimTxGraph::MISSING) break;
541 auto cmp = real->CompareMainOrder(*ref_a, *ref_b);
542 // Distinct transactions have distinct places.
543 if (sim_a != sim_b) assert(cmp != 0);
544 // Ancestors go before descendants.
545 if (main_sim.graph.Ancestors(sim_a)[sim_b]) assert(cmp >= 0);
546 if (main_sim.graph.Descendants(sim_a)[sim_b]) assert(cmp <= 0);
547 // Do not verify consistency with chunk feerates, as we cannot easily determine
548 // these here without making more calls to real, which could affect its internal
549 // state. A full comparison is done at the end.
550 break;
551 } else if (!sel_sim.IsOversized() && command-- == 0) {
552 // CountDistinctClusters.
553 std::vector<TxGraph::Ref*> refs;
554 // Gather a list of up to 15 (or up to 255) Ref pointers.
555 auto count = provider.ConsumeIntegralInRange<size_t>(0, alt ? 255 : 15);
556 refs.resize(count);
557 for (size_t i = 0; i < count; ++i) {
558 refs[i] = pick_fn();
559 }
560 // Their order should not matter, shuffle them.
561 std::shuffle(refs.begin(), refs.end(), rng);
562 // Invoke the real function.
563 auto result = real->CountDistinctClusters(refs, use_main);
564 // Build a set with representatives of the clusters the Refs occur in in the
565 // simulated graph. For each, remember the lowest-index transaction SimPos in the
566 // cluster.
567 SimTxGraph::SetType sim_reps;
568 for (auto ref : refs) {
569 // Skip Refs that do not occur in the simulated graph.
570 auto simpos = sel_sim.Find(ref);
571 if (simpos == SimTxGraph::MISSING) continue;
572 // Find the component that includes ref.
573 auto component = sel_sim.graph.GetConnectedComponent(sel_sim.graph.Positions(), simpos);
574 // Remember the lowest-index SimPos in component, as a representative for it.
575 assert(component.Any());
576 sim_reps.Set(component.First());
577 }
578 // Compare the number of deduplicated representatives with the value returned by
579 // the real function.
580 assert(result == sim_reps.Count());
581 break;
582 } else if (command-- == 0) {
583 // DoWork.
584 real->DoWork();
585 break;
586 }
587 }
588 }
589
590 // After running all modifications, perform an internal sanity check (before invoking
591 // inspectors that may modify the internal state).
592 real->SanityCheck();
593
594 if (!sims[0].IsOversized()) {
595 // If the main graph is not oversized, verify the total ordering implied by
596 // CompareMainOrder.
597 // First construct two distinct randomized permutations of the positions in sims[0].
598 std::vector<SimTxGraph::Pos> vec1;
599 for (auto i : sims[0].graph.Positions()) vec1.push_back(i);
600 std::shuffle(vec1.begin(), vec1.end(), rng);
601 auto vec2 = vec1;
602 std::shuffle(vec2.begin(), vec2.end(), rng);
603 if (vec1 == vec2) std::next_permutation(vec2.begin(), vec2.end());
604 // Sort both according to CompareMainOrder. By having randomized starting points, the order
605 // of CompareMainOrder invocations is somewhat randomized as well.
606 auto cmp = [&](SimTxGraph::Pos a, SimTxGraph::Pos b) noexcept {
607 return real->CompareMainOrder(*sims[0].GetRef(a), *sims[0].GetRef(b)) < 0;
608 };
609 std::sort(vec1.begin(), vec1.end(), cmp);
610 std::sort(vec2.begin(), vec2.end(), cmp);
611
612 // Verify the resulting orderings are identical. This could only fail if the ordering was
613 // not total.
614 assert(vec1 == vec2);
615
616 // Verify that the ordering is topological.
617 auto todo = sims[0].graph.Positions();
618 for (auto i : vec1) {
619 todo.Reset(i);
620 assert(!sims[0].graph.Ancestors(i).Overlaps(todo));
621 }
622 assert(todo.None());
623
624 // For every transaction in the total ordering, find a random one before it and after it,
625 // and compare their chunk feerates, which must be consistent with the ordering.
626 for (size_t pos = 0; pos < vec1.size(); ++pos) {
627 auto pos_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[pos]));
628 if (pos > 0) {
629 size_t before = rng.randrange<size_t>(pos);
630 auto before_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[before]));
631 assert(FeeRateCompare(before_feerate, pos_feerate) >= 0);
632 }
633 if (pos + 1 < vec1.size()) {
634 size_t after = pos + 1 + rng.randrange<size_t>(vec1.size() - 1 - pos);
635 auto after_feerate = real->GetMainChunkFeerate(*sims[0].GetRef(vec1[after]));
636 assert(FeeRateCompare(after_feerate, pos_feerate) <= 0);
637 }
638 }
639 }
640
641 assert(real->HaveStaging() == (sims.size() > 1));
642
643 // Try to run a full comparison, for both main_only=false and main_only=true in TxGraph
644 // inspector functions that support both.
645 for (int main_only = 0; main_only < 2; ++main_only) {
646 auto& sim = main_only ? sims[0] : sims.back();
647 // Compare simple properties of the graph with the simulation.
648 assert(real->IsOversized(main_only) == sim.IsOversized());
649 assert(real->GetTransactionCount(main_only) == sim.GetTransactionCount());
650 // If the graph (and the simulation) are not oversized, perform a full comparison.
651 if (!sim.IsOversized()) {
652 auto todo = sim.graph.Positions();
653 // Iterate over all connected components of the resulting (simulated) graph, each of which
654 // should correspond to a cluster in the real one.
655 while (todo.Any()) {
656 auto component = sim.graph.FindConnectedComponent(todo);
657 todo -= component;
658 // Iterate over the transactions in that component.
659 for (auto i : component) {
660 // Check its individual feerate against simulation.
661 assert(sim.graph.FeeRate(i) == real->GetIndividualFeerate(*sim.GetRef(i)));
662 // Check its ancestors against simulation.
663 auto expect_anc = sim.graph.Ancestors(i);
664 auto anc = sim.MakeSet(real->GetAncestors(*sim.GetRef(i), main_only));
665 assert(anc.Count() <= max_count);
666 assert(anc == expect_anc);
667 // Check its descendants against simulation.
668 auto expect_desc = sim.graph.Descendants(i);
669 auto desc = sim.MakeSet(real->GetDescendants(*sim.GetRef(i), main_only));
670 assert(desc.Count() <= max_count);
671 assert(desc == expect_desc);
672 // Check the cluster the transaction is part of.
673 auto cluster = real->GetCluster(*sim.GetRef(i), main_only);
674 assert(cluster.size() <= max_count);
675 assert(sim.MakeSet(cluster) == component);
676 // Check that the cluster is reported in a valid topological order (its
677 // linearization).
678 std::vector<DepGraphIndex> simlin;
679 SimTxGraph::SetType done;
680 for (TxGraph::Ref* ptr : cluster) {
681 auto simpos = sim.Find(ptr);
682 assert(sim.graph.Descendants(simpos).IsSubsetOf(component - done));
683 done.Set(simpos);
684 assert(sim.graph.Ancestors(simpos).IsSubsetOf(done));
685 simlin.push_back(simpos);
686 }
687 // Construct a chunking object for the simulated graph, using the reported cluster
688 // linearization as ordering, and compare it against the reported chunk feerates.
689 if (sims.size() == 1 || main_only) {
690 cluster_linearize::LinearizationChunking simlinchunk(sim.graph, simlin);
691 DepGraphIndex idx{0};
692 for (unsigned chunknum = 0; chunknum < simlinchunk.NumChunksLeft(); ++chunknum) {
693 auto chunk = simlinchunk.GetChunk(chunknum);
694 // Require that the chunks of cluster linearizations are connected (this must
695 // be the case as all linearizations inside are PostLinearized).
696 assert(sim.graph.IsConnected(chunk.transactions));
697 // Check the chunk feerates of all transactions in the cluster.
698 while (chunk.transactions.Any()) {
699 assert(chunk.transactions[simlin[idx]]);
700 chunk.transactions.Reset(simlin[idx]);
701 assert(chunk.feerate == real->GetMainChunkFeerate(*cluster[idx]));
702 ++idx;
703 }
704 }
705 }
706 }
707 }
708 }
709 }
710
711 // Sanity check again (because invoking inspectors may modify internal unobservable state).
712 real->SanityCheck();
713
714 // Kill the TxGraph object.
715 real.reset();
716 // Kill the simulated graphs, with all remaining Refs in it. If any, this verifies that Refs
717 // can outlive the TxGraph that created them.
718 sims.clear();
719}
int ret
const auto command
std::conditional_t<(BITS<=32), bitset_detail::IntBitSet< uint32_t >, std::conditional_t<(BITS<=std::numeric_limits< size_t >::digits), bitset_detail::IntBitSet< size_t >, bitset_detail::MultiIntBitSet< size_t,(BITS+std::numeric_limits< size_t >::digits - 1)/std::numeric_limits< size_t >::digits > > > BitSet
Definition: bitset.h:525
T ConsumeIntegralInRange(T min, T max)
xoroshiro128++ PRNG.
Definition: random.h:416
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
Definition: random.h:254
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.
const FeeFrac & FeeRate(DepGraphIndex i) const noexcept
Get the feerate of a given transaction i.
SetType FindConnectedComponent(const SetType &todo) const noexcept
Find some connected component within the subset "todo" of this graph.
DepGraphIndex AddTransaction(const FeeFrac &feefrac) noexcept
Add a new unconnected transaction to this transaction graph (in the first available position),...
void RemoveTransactions(const SetType &del) noexcept
Remove the specified positions from this DepGraph.
auto TxCount() const noexcept
Get the number of transactions in the graph.
const SetType & Descendants(DepGraphIndex i) const noexcept
Get the descendants of a given transaction i.
const SetType & Positions() const noexcept
Get the set of transactions positions in use.
void AddDependencies(const SetType &parents, DepGraphIndex child) noexcept
Modify this transaction graph, adding multiple parents to a specified child.
Data structure encapsulating the chunking of a linearization, permitting removal of subsets.
DepGraphIndex NumChunksLeft() const noexcept
Determine how many chunks remain in the linearization.
const SetInfo< SetType > & GetChunk(DepGraphIndex n) const noexcept
Access a chunk.
constexpr MaybeCoin MISSING
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
uint64_t fee
Value Find(const std::map< Key, Value > &map, const Key &key)
uint32_t DepGraphIndex
Data type to represent transaction indices in DepGraphs and the clusters they represent.
static bool exists(const path &p)
Definition: fs.h:89
int64_t fee
Definition: feefrac.h:63
int32_t size
Definition: feefrac.h:64
bool IsEmpty() const noexcept
Check if this is empty (size and fee are 0).
Definition: feefrac.h:76
Tagged wrapper around FeeFrac to avoid unit confusion.
Definition: feefrac.h:162
FUZZ_TARGET(txgraph)
Definition: txgraph.cpp:219
void SeedRandomStateForTest(SeedRand seedtype)
Seed the global RNG state for testing and log the seed value.
Definition: random.cpp:19
@ ZEROS
Seed with a compile time constant of zeros.
static int count
std::unique_ptr< TxGraph > MakeTxGraph(unsigned max_cluster_count) noexcept
Construct a new TxGraph with the specified limit on transactions within a cluster.
Definition: txgraph.cpp:2117
static constexpr unsigned MAX_CLUSTER_COUNT_LIMIT
Definition: txgraph.h:15
assert(!tx.IsCoinBase())