Skip to content

Commit a38c389

Browse files
committed
clusterlin tests: separate testing of Search- and SimpleCandidateFinder
This separates the existing fuzz test into: * clusterlin_search_finder: establishes SearchCandidateFinder's correctness using the simpler SimpleCandidateFinder. * clusterlin_simple_finder: establishes SimpleCandidateFinder's correctness using the (even) simpler ExhaustiveCandidateFinder. rather than trying to do both at once.
1 parent 77a432e commit a38c389

File tree

1 file changed

+90
-24
lines changed

1 file changed

+90
-24
lines changed

src/test/fuzz/cluster_linearize.cpp

Lines changed: 90 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
9998
template<typename SetType>
10099
class ExhaustiveCandidateFinder
@@ -640,11 +639,94 @@ FUZZ_TARGET(clusterlin_ancestor_finder)
640639

641640
static 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+
643725
FUZZ_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

Comments
 (0)