clusterlin tests: support non-empty ReadTopologicalSubset()

In several call sites for ReadTopologicalSubset, a non-empty result is
expected, necessitating a special case at the call site for empty results.

Fix this by adding a bool non_empty argument, which does this special
casing (more efficiently) inside ReadTopologicalSubset itself.
This commit is contained in:
Pieter Wuille 2024-10-04 10:59:32 -04:00
parent 50a18d7143
commit 1d27b5b63b

View file

@ -184,12 +184,16 @@ void MakeConnected(DepGraph<BS>& depgraph)
/** Given a dependency graph, and a todo set, read a topological subset of todo from reader. */ /** Given a dependency graph, and a todo set, read a topological subset of todo from reader. */
template<typename SetType> template<typename SetType>
SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& todo, SpanReader& reader) SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& todo, SpanReader& reader, bool non_empty)
{ {
// Read a bitmask from the fuzzing input. Add 1 if non_empty, so the mask is definitely not
// zero in that case.
uint64_t mask{0}; uint64_t mask{0};
try { try {
reader >> VARINT(mask); reader >> VARINT(mask);
} catch(const std::ios_base::failure&) {} } catch(const std::ios_base::failure&) {}
mask += non_empty;
SetType ret; SetType ret;
for (auto i : todo) { for (auto i : todo) {
if (!ret[i]) { if (!ret[i]) {
@ -197,7 +201,17 @@ SetType ReadTopologicalSet(const DepGraph<SetType>& depgraph, const SetType& tod
mask >>= 1; mask >>= 1;
} }
} }
return ret & todo; ret &= todo;
// While mask starts off non-zero if non_empty is true, it is still possible that all its low
// bits are 0, and ret ends up being empty. As a last resort, use the in-todo ancestry of the
// first todo position.
if (non_empty && ret.None()) {
Assume(todo.Any());
ret = depgraph.Ancestors(todo.First()) & todo;
Assume(ret.Any());
}
return ret;
} }
/** Given a dependency graph, construct any valid linearization for it, reading from a SpanReader. */ /** Given a dependency graph, construct any valid linearization for it, reading from a SpanReader. */
@ -609,10 +623,10 @@ FUZZ_TARGET(clusterlin_ancestor_finder)
assert(real_best_anc.has_value()); assert(real_best_anc.has_value());
assert(*real_best_anc == best_anc); assert(*real_best_anc == best_anc);
// Find a topologically valid subset of transactions to remove from the graph. // Find a non-empty topologically valid subset of transactions to remove from the graph.
auto del_set = ReadTopologicalSet(depgraph, todo, reader); // Using an empty set would mean the next iteration is identical to the current one, and
// If we did not find anything, use best_anc itself, because we should remove something. // could cause an infinite loop.
if (del_set.None()) del_set = best_anc.transactions; auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
todo -= del_set; todo -= del_set;
anc_finder.MarkDone(del_set); anc_finder.MarkDone(del_set);
} }
@ -688,15 +702,16 @@ FUZZ_TARGET(clusterlin_simple_finder)
assert(exhaustive.feerate == found.feerate); assert(exhaustive.feerate == found.feerate);
} }
// Compare with a topological set read from the fuzz input. // Compare with a non-empty topological set read from the fuzz input (comparing with an
auto read_topo = ReadTopologicalSet(depgraph, todo, reader); // empty set is not interesting).
if (read_topo.Any()) assert(found.feerate >= depgraph.FeeRate(read_topo)); auto read_topo = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
assert(found.feerate >= depgraph.FeeRate(read_topo));
} }
// Find a topologically valid subset of transactions to remove from the graph. // Find a non-empty topologically valid subset of transactions to remove from the graph.
auto del_set = ReadTopologicalSet(depgraph, todo, reader); // Using an empty set would mean the next iteration is identical to the current one, and
// If we did not find anything, use found itself, because we should remove something. // could cause an infinite loop.
if (del_set.None()) del_set = found.transactions; auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
todo -= del_set; todo -= del_set;
smp_finder.MarkDone(del_set); smp_finder.MarkDone(del_set);
exh_finder.MarkDone(del_set); exh_finder.MarkDone(del_set);
@ -746,8 +761,9 @@ FUZZ_TARGET(clusterlin_search_finder)
} catch (const std::ios_base::failure&) {} } catch (const std::ios_base::failure&) {}
max_iterations &= 0xfffff; max_iterations &= 0xfffff;
// Read an initial subset from the fuzz input. // Read an initial subset from the fuzz input (allowed to be empty).
SetInfo init_best(depgraph, ReadTopologicalSet(depgraph, todo, reader)); auto init_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/false);
SetInfo init_best(depgraph, init_set);
// Call the search finder's FindCandidateSet for what remains of the graph. // Call the search finder's FindCandidateSet for what remains of the graph.
auto [found, iterations_done] = src_finder.FindCandidateSet(max_iterations, init_best); auto [found, iterations_done] = src_finder.FindCandidateSet(max_iterations, init_best);
@ -792,15 +808,16 @@ FUZZ_TARGET(clusterlin_search_finder)
auto anc = anc_finder.FindCandidateSet(); auto anc = anc_finder.FindCandidateSet();
assert(found.feerate >= anc.feerate); assert(found.feerate >= anc.feerate);
// Compare with a topological set read from the fuzz input. // Compare with a non-empty topological set read from the fuzz input (comparing with an
auto read_topo = ReadTopologicalSet(depgraph, todo, reader); // empty set is not interesting).
if (read_topo.Any()) assert(found.feerate >= depgraph.FeeRate(read_topo)); auto read_topo = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
assert(found.feerate >= depgraph.FeeRate(read_topo));
} }
// Find a topologically valid subset of transactions to remove from the graph. // Find a non-empty topologically valid subset of transactions to remove from the graph.
auto del_set = ReadTopologicalSet(depgraph, todo, reader); // Using an empty set would mean the next iteration is identical to the current one, and
// If we did not find anything, use found itself, because we should remove something. // could cause an infinite loop.
if (del_set.None()) del_set = found.transactions; auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
todo -= del_set; todo -= del_set;
src_finder.MarkDone(del_set); src_finder.MarkDone(del_set);
smp_finder.MarkDone(del_set); smp_finder.MarkDone(del_set);
@ -824,9 +841,10 @@ FUZZ_TARGET(clusterlin_linearization_chunking)
reader >> Using<DepGraphFormatter>(depgraph); reader >> Using<DepGraphFormatter>(depgraph);
} catch (const std::ios_base::failure&) {} } catch (const std::ios_base::failure&) {}
// Retrieve a topologically-valid subset of depgraph. // Retrieve a topologically-valid subset of depgraph (allowed to be empty, because the argument
// to LinearizationChunking::Intersect is allowed to be empty).
auto todo = depgraph.Positions(); auto todo = depgraph.Positions();
auto subset = SetInfo(depgraph, ReadTopologicalSet(depgraph, todo, reader)); auto subset = SetInfo(depgraph, ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/false));
// Retrieve a valid linearization for depgraph. // Retrieve a valid linearization for depgraph.
auto linearization = ReadLinearization(depgraph, reader); auto linearization = ReadLinearization(depgraph, reader);
@ -915,13 +933,10 @@ FUZZ_TARGET(clusterlin_linearization_chunking)
} }
} }
// Find a subset to remove from linearization. // Find a non-empty topologically valid subset of transactions to remove from the graph.
auto done = ReadTopologicalSet(depgraph, todo, reader); // Using an empty set would mean the next iteration is identical to the current one, and
if (done.None()) { // could cause an infinite loop.
// We need to remove a non-empty subset, so fall back to the unlinearized ancestors of auto done = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true);
// the first transaction in todo if done is empty.
done = depgraph.Ancestors(todo.First()) & todo;
}
todo -= done; todo -= done;
chunking.MarkDone(done); chunking.MarkDone(done);
subset = SetInfo(depgraph, subset.transactions - done); subset = SetInfo(depgraph, subset.transactions - done);