Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
751best = accumulator;752}753}754}755assert(chunk_feerate == best.feerate);756assert(chunk_set == best.transactions);757assert(best.transactions.IsSubsetOf(todo));758todo -= best.transactions;759}760assert(todo.None());761}763static constexpr auto MAX_SIMPLE_ITERATIONS = 300000;765FUZZ_TARGET(clusterlin_simple_finder)766{767// Verify that SimpleCandidateFinder works as expected by sanity checking the results768// and comparing them (if claimed to be optimal) against the sets found by769// ExhaustiveCandidateFinder.770//771// Note that SimpleCandidateFinder is only used in tests; the purpose of this fuzz test is to772// establish confidence in SimpleCandidateFinder, so that it can be used in SimpleLinearize,773// which is then used to test Linearize below.775// Retrieve a depgraph from the fuzz input.776SpanReader reader(buffer);777DepGraph<TestBitSet> depgraph;778try {779reader >> Using<DepGraphFormatter>(depgraph);780} catch (const std::ios_base::failure&) {}782// Instantiate the SimpleCandidateFinder to be tested, and the ExhaustiveCandidateFinder it is783// being tested against.784SimpleCandidateFinder smp_finder(depgraph);785ExhaustiveCandidateFinder exh_finder(depgraph);787auto todo = depgraph.Positions();788while (todo.Any()) {789assert(!smp_finder.AllDone());790assert(!exh_finder.AllDone());792// Call SimpleCandidateFinder.793auto [found, iterations_done] = smp_finder.FindCandidateSet(MAX_SIMPLE_ITERATIONS);794bool optimal = (iterations_done != MAX_SIMPLE_ITERATIONS);796// Sanity check the result.797assert(iterations_done <= MAX_SIMPLE_ITERATIONS);798assert(found.transactions.Any());799assert(found.transactions.IsSubsetOf(todo));800assert(depgraph.FeeRate(found.transactions) == found.feerate);801// Check that it is topologically valid.802for (auto i : found.transactions) {803assert(found.transactions.IsSupersetOf(depgraph.Ancestors(i) & todo));804}806// At most 2^(N-1) iterations can be required: the number of non-empty connected subsets a807// graph with N transactions can have. If MAX_SIMPLE_ITERATIONS exceeds this number, the808// result is necessarily optimal.809assert(iterations_done <= (uint64_t{1} << (todo.Count() - 1)));810if (MAX_SIMPLE_ITERATIONS > (uint64_t{1} << (todo.Count() - 1))) assert(optimal);812// SimpleCandidateFinder only finds connected sets.813assert(depgraph.IsConnected(found.transactions));815// Perform further quality checks only if SimpleCandidateFinder claims an optimal result.816if (optimal) {817if (todo.Count() <= 12) {818// Compare with ExhaustiveCandidateFinder. This quickly gets computationally819// expensive for large clusters (O(2^n)), so only do it for sufficiently small ones.820auto exhaustive = exh_finder.FindCandidateSet();821assert(exhaustive.feerate == found.feerate);822}824// Compare with a non-empty topological set read from the fuzz input (comparing with an825// empty set is not interesting).826auto read_topo = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);827assert(ByRatioNegSize{found.feerate} >= ByRatioNegSize{depgraph.FeeRate(read_topo)});828}830// Find a non-empty topologically valid subset of transactions to remove from the graph.831// Using an empty set would mean the next iteration is identical to the current one, and832// could cause an infinite loop.833auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);834todo -= del_set;835smp_finder.MarkDone(del_set);836exh_finder.MarkDone(del_set);837}839assert(smp_finder.AllDone());840assert(exh_finder.AllDone());841}843FUZZ_TARGET(clusterlin_simple_linearize)844{845// Verify the behavior of SimpleLinearize(). Note that SimpleLinearize is only used in tests;846// the purpose of this fuzz test is to establish confidence in SimpleLinearize, so that it can847// be used to test the real Linearize function in the fuzz test below.849// Retrieve an iteration count and a depgraph from the fuzz input.850SpanReader reader(buffer);851uint64_t iter_count{0};852DepGraph<TestBitSet> depgraph;853try {854reader >> VARINT(iter_count) >> Using<DepGraphFormatter>(depgraph);855} catch (const std::ios_base::failure&) {}856iter_count %= MAX_SIMPLE_ITERATIONS;858// Invoke SimpleLinearize().859auto [linearization, optimal] = SimpleLinearize(depgraph, iter_count);860SanityCheck(depgraph, linearization);861auto simple_chunking = ChunkLinearization(depgraph, linearization);863// If the iteration count is sufficiently high, an optimal linearization must be found.864// SimpleLinearize on k transactions can take up to 2^(k-1) iterations (one per non-empty865// connected topologically valid subset), which sums over k=1..n to (2^n)-1.866const uint64_t n = depgraph.TxCount();867if (n <= 63 && (iter_count >> n)) {868assert(optimal);869}871// If SimpleLinearize claims optimal result, and the cluster is sufficiently small (there are872// n! linearizations), test that the result is as good as every valid linearization.873if (optimal && depgraph.TxCount() <= 8) {874auto exh_linearization = ExhaustiveLinearize(depgraph);875auto exh_chunking = ChunkLinearization(depgraph, exh_linearization);876auto cmp = CompareChunks(simple_chunking, exh_chunking);877assert(cmp == 0);878assert(simple_chunking.size() == exh_chunking.size());879}881if (optimal) {882// Compare with a linearization read from the fuzz input.883auto read = ReadLinearization(depgraph, reader);884auto read_chunking = ChunkLinearization(depgraph, read);885auto cmp = CompareChunks(simple_chunking, read_chunking);886assert(cmp >= 0);887}888}890FUZZ_TARGET(clusterlin_sfl)891{892// Verify the individual steps of the SFL algorithm.894SpanReader reader(buffer);895DepGraph<TestBitSet> depgraph;896uint8_t flags{1};897uint64_t rng_seed{0};898try {899reader >> rng_seed >> flags >> Using<DepGraphFormatter>(depgraph);900} catch (const std::ios_base::failure&) {}901if (depgraph.TxCount() <= 1) return;902InsecureRandomContext rng(rng_seed);903/** Whether to make the depgraph connected. */904const bool make_connected = flags & 1;905/** Whether to load some input linearization into the state. */906const bool load_linearization = flags & 2;907/** Whether that input linearization is topological. */908const bool load_topological = load_linearization && (flags & 4);910// Initialize SFL state.911if (make_connected) MakeConnected(depgraph);912SpanningForestState sfl(depgraph, rng.rand64());914// Function to test the state.915std::vector<FeeFrac> last_diagram;916bool was_optimal{false};917auto test_fn = [&](bool is_optimal = false, bool is_minimal = false) {918if (rng.randbits(4) == 0) {919// Perform sanity checks from time to time (too computationally expensive to do after920// every step).921sfl.SanityCheck();922}923auto diagram = sfl.GetDiagram();924if (rng.randbits(4) == 0) {925// Verify that the diagram of GetLinearization() is at least as good as GetDiagram(),926// from time to time.927auto lin = sfl.GetLinearization(IndexTxOrder{});928auto lin_diagram = ChunkLinearization(depgraph, lin);929auto cmp_lin = CompareChunks(lin_diagram, diagram);930assert(cmp_lin >= 0);931// If we're in an allegedly optimal state, they must match.932if (is_optimal) assert(cmp_lin == 0);933// If we're in an allegedly minimal state, they must also have the same number of934// segments.935if (is_minimal) assert(diagram.size() == lin_diagram.size());936}937// Verify that subsequent calls to GetDiagram() never get worse/incomparable.938if (!last_diagram.empty()) {939auto cmp = CompareChunks(diagram, last_diagram);940assert(cmp >= 0);941// If the last diagram was already optimal, the new one cannot be better.942if (was_optimal) assert(cmp == 0);943// Also, if the diagram was already optimal, the number of segments can only increase.944if (was_optimal) assert(diagram.size() >= last_diagram.size());945}946last_diagram = std::move(diagram);947was_optimal = is_optimal;948};950if (load_linearization) {951auto input_lin = ReadLinearization(depgraph, reader, load_topological);952sfl.LoadLinearization(input_lin);953if (load_topological) {954// The diagram of the loaded linearization forms an initial lower bound on future955// diagrams.956last_diagram = ChunkLinearization(depgraph, input_lin);957} else {958// The input linearization may have been non-topological, so invoke MakeTopological to959// fix it still.960sfl.MakeTopological();961}962} else {963// Invoke MakeTopological to create an initial from-scratch topological state.964sfl.MakeTopological();965}967// Loop until optimal.968test_fn();969sfl.StartOptimizing();970while (true) {971test_fn();972if (!sfl.OptimizeStep()) break;973}975// Loop until minimal.976test_fn(/*is_optimal=*/true);977sfl.StartMinimizing();978while (true) {979test_fn(/*is_optimal=*/true);980if (!sfl.MinimizeStep()) break;981}982test_fn(/*is_optimal=*/true, /*is_minimal=*/true);984// Verify that optimality is reached within an expected amount of work. This protects against985// hypothetical bugs that hugely increase the amount of work needed to reach optimality.986assert(sfl.GetCost() <= MaxOptimalLinearizationCost(depgraph.TxCount()));988// The result must be as good as SimpleLinearize.989auto [simple_linearization, simple_optimal] = SimpleLinearize(depgraph, MAX_SIMPLE_ITERATIONS / 10);990auto simple_diagram = ChunkLinearization(depgraph, simple_linearization);991auto simple_cmp = CompareChunks(last_diagram, simple_diagram);992assert(simple_cmp >= 0);993if (simple_optimal) assert(simple_cmp == 0);994// If the diagram matches, we must also have at least as many segments (because the SFL state995// and its produced diagram are minimal);996if (simple_cmp == 0) assert(last_diagram.size() >= simple_diagram.size());998// We can compare with any arbitrary linearization, and the diagram must be at least as good as999// each.1000for (int i = 0; i < 10; ++i) {
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.