@@ -92,9 +92,8 @@ class SimpleCandidateFinder
9292
9393/* * A very simple finder class for optimal candidate sets, which tries every subset.
9494 *
95- * It is even simpler than SimpleCandidateFinder, and is primarily included here to test the
96- * correctness of SimpleCandidateFinder, which is then used to test the correctness of
97- * SearchCandidateFinder.
95+ * It is even simpler than SimpleCandidateFinder, and exists just to help test the correctness of
96+ * SimpleCandidateFinder, which is then used to test the correctness of SearchCandidateFinder.
9897 */
9998template <typename SetType>
10099class ExhaustiveCandidateFinder
@@ -640,11 +639,94 @@ FUZZ_TARGET(clusterlin_ancestor_finder)
640639
641640static constexpr auto MAX_SIMPLE_ITERATIONS = 300000 ;
642641
642+ FUZZ_TARGET (clusterlin_simple_finder)
643+ {
644+ // Verify that SimpleCandidateFinder works as expected by sanity checking the results
645+ // and comparing them (if claimed to be optimal) against the sets found by
646+ // ExhaustiveCandidateFinder and AncestorCandidateFinder.
647+ //
648+ // Note that SimpleCandidateFinder is only used in tests; the purpose of this fuzz test is to
649+ // establish confidence in SimpleCandidateFinder, so that it can be used to test
650+ // SearchCandidateFinder below.
651+
652+ // Retrieve a depgraph from the fuzz input.
653+ SpanReader reader (buffer);
654+ DepGraph<TestBitSet> depgraph;
655+ try {
656+ reader >> Using<DepGraphFormatter>(depgraph);
657+ } catch (const std::ios_base::failure&) {}
658+
659+ // Instantiate the SimpleCandidateFinder to be tested, and the ExhaustiveCandidateFinder and
660+ // AncestorCandidateFinder it is being tested against.
661+ SimpleCandidateFinder smp_finder (depgraph);
662+ ExhaustiveCandidateFinder exh_finder (depgraph);
663+ AncestorCandidateFinder anc_finder (depgraph);
664+
665+ auto todo = depgraph.Positions ();
666+ while (todo.Any ()) {
667+ assert (!smp_finder.AllDone ());
668+ assert (!exh_finder.AllDone ());
669+ assert (!anc_finder.AllDone ());
670+ assert (anc_finder.NumRemaining () == todo.Count ());
671+
672+ // Call SimpleCandidateFinder.
673+ auto [found, iterations_done] = smp_finder.FindCandidateSet (MAX_SIMPLE_ITERATIONS);
674+ bool optimal = (iterations_done != MAX_SIMPLE_ITERATIONS);
675+
676+ // Sanity check the result.
677+ assert (iterations_done <= MAX_SIMPLE_ITERATIONS);
678+ assert (found.transactions .Any ());
679+ assert (found.transactions .IsSubsetOf (todo));
680+ assert (depgraph.FeeRate (found.transactions ) == found.feerate );
681+ // Check that it is topologically valid.
682+ for (auto i : found.transactions ) {
683+ assert (found.transactions .IsSupersetOf (depgraph.Ancestors (i) & todo));
684+ }
685+
686+ // At most 2^(N-1) iterations can be required: the number of non-empty connected subsets a
687+ // graph with N transactions can have. If MAX_SIMPLE_ITERATIONS exceeds this number, the
688+ // result is necessarily optimal.
689+ assert (iterations_done <= (uint64_t {1 } << (todo.Count () - 1 )));
690+ if (MAX_SIMPLE_ITERATIONS > (uint64_t {1 } << (todo.Count () - 1 ))) assert (optimal);
691+
692+ // Perform quality checks only if SimpleCandidateFinder claims an optimal result.
693+ if (optimal) {
694+ // Optimal sets are always connected.
695+ assert (depgraph.IsConnected (found.transactions ));
696+
697+ // Compare with AncestorCandidateFinder.
698+ auto anc = anc_finder.FindCandidateSet ();
699+ assert (anc.feerate <= found.feerate );
700+
701+ if (todo.Count () <= 12 ) {
702+ // Compare with ExhaustiveCandidateFinder. This quickly gets computationally
703+ // expensive for large clusters (O(2^n)), so only do it for sufficiently small ones.
704+ auto exhaustive = exh_finder.FindCandidateSet ();
705+ assert (exhaustive.feerate == found.feerate );
706+ }
707+ }
708+
709+ // Find a topologically valid subset of transactions to remove from the graph.
710+ auto del_set = ReadTopologicalSet (depgraph, todo, reader);
711+ // If we did not find anything, use found itself, because we should remove something.
712+ if (del_set.None ()) del_set = found.transactions ;
713+ todo -= del_set;
714+ smp_finder.MarkDone (del_set);
715+ exh_finder.MarkDone (del_set);
716+ anc_finder.MarkDone (del_set);
717+ }
718+
719+ assert (smp_finder.AllDone ());
720+ assert (exh_finder.AllDone ());
721+ assert (anc_finder.AllDone ());
722+ assert (anc_finder.NumRemaining () == 0 );
723+ }
724+
643725FUZZ_TARGET (clusterlin_search_finder)
644726{
645727 // Verify that SearchCandidateFinder works as expected by sanity checking the results
646- // and comparing with the results from SimpleCandidateFinder, ExhaustiveCandidateFinder, and
647- // AncestorCandidateFinder .
728+ // and comparing with the results from SimpleCandidateFinder and AncestorCandidateFinder,
729+ // if the result is claimed to be optimal .
648730
649731 // Retrieve an RNG seed, a depgraph, and whether to make it connected, from the fuzz input.
650732 SpanReader reader (buffer);
@@ -658,17 +740,15 @@ FUZZ_TARGET(clusterlin_search_finder)
658740 // the graph to be connected.
659741 if (make_connected) MakeConnected (depgraph);
660742
661- // Instantiate ALL the candidate finders.
743+ // Instantiate the candidate finders.
662744 SearchCandidateFinder src_finder (depgraph, rng_seed);
663745 SimpleCandidateFinder smp_finder (depgraph);
664- ExhaustiveCandidateFinder exh_finder (depgraph);
665746 AncestorCandidateFinder anc_finder (depgraph);
666747
667748 auto todo = depgraph.Positions ();
668749 while (todo.Any ()) {
669750 assert (!src_finder.AllDone ());
670751 assert (!smp_finder.AllDone ());
671- assert (!exh_finder.AllDone ());
672752 assert (!anc_finder.AllDone ());
673753 assert (anc_finder.NumRemaining () == todo.Count ());
674754
@@ -684,6 +764,7 @@ FUZZ_TARGET(clusterlin_search_finder)
684764
685765 // Call the search finder's FindCandidateSet for what remains of the graph.
686766 auto [found, iterations_done] = src_finder.FindCandidateSet (max_iterations, init_best);
767+ bool optimal = iterations_done < max_iterations;
687768
688769 // Sanity check the result.
689770 assert (iterations_done <= max_iterations);
@@ -709,7 +790,7 @@ FUZZ_TARGET(clusterlin_search_finder)
709790 }
710791
711792 // Perform quality checks only if SearchCandidateFinder claims an optimal result.
712- if (iterations_done < max_iterations ) {
793+ if (optimal ) {
713794 // Optimal sets are always connected.
714795 assert (depgraph.IsConnected (found.transactions ));
715796
@@ -723,19 +804,6 @@ FUZZ_TARGET(clusterlin_search_finder)
723804 // Compare with AncestorCandidateFinder;
724805 auto anc = anc_finder.FindCandidateSet ();
725806 assert (found.feerate >= anc.feerate );
726-
727- // Compare with ExhaustiveCandidateFinder. This quickly gets computationally expensive
728- // for large clusters (O(2^n)), so only do it for sufficiently small ones.
729- if (todo.Count () <= 12 ) {
730- auto exhaustive = exh_finder.FindCandidateSet ();
731- assert (exhaustive.feerate == found.feerate );
732- // Also compare ExhaustiveCandidateFinder with SimpleCandidateFinder (this is
733- // primarily a test for SimpleCandidateFinder's correctness).
734- assert (exhaustive.feerate >= simple.feerate );
735- if (simple_iters < MAX_SIMPLE_ITERATIONS) {
736- assert (exhaustive.feerate == simple.feerate );
737- }
738- }
739807 }
740808
741809 // Find a topologically valid subset of transactions to remove from the graph.
@@ -745,13 +813,11 @@ FUZZ_TARGET(clusterlin_search_finder)
745813 todo -= del_set;
746814 src_finder.MarkDone (del_set);
747815 smp_finder.MarkDone (del_set);
748- exh_finder.MarkDone (del_set);
749816 anc_finder.MarkDone (del_set);
750817 }
751818
752819 assert (src_finder.AllDone ());
753820 assert (smp_finder.AllDone ());
754- assert (exh_finder.AllDone ());
755821 assert (anc_finder.AllDone ());
756822 assert (anc_finder.NumRemaining () == 0 );
757823}
0 commit comments