// Copyright (c) The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include using namespace cluster_linearize; namespace { /** Given a dependency graph, and a todo set, read a topological subset of todo from reader. */ template SetType ReadTopologicalSet(const DepGraph& depgraph, const SetType& todo, SpanReader& reader) { uint64_t mask{0}; try { reader >> VARINT(mask); } catch(const std::ios_base::failure&) {} SetType ret; for (auto i : todo) { if (!ret[i]) { if (mask & 1) ret |= depgraph.Ancestors(i); mask >>= 1; } } return ret & todo; } } // namespace FUZZ_TARGET(clusterlin_add_dependency) { // Verify that computing a DepGraph from a cluster, or building it step by step using AddDependency // have the same effect. // Construct a cluster of a certain length, with no dependencies. FuzzedDataProvider provider(buffer.data(), buffer.size()); auto num_tx = provider.ConsumeIntegralInRange(2, 32); Cluster cluster(num_tx, std::pair{FeeFrac{0, 1}, TestBitSet{}}); // Construct the corresponding DepGraph object (also no dependencies). DepGraph depgraph(cluster); SanityCheck(depgraph); // Read (parent, child) pairs, and add them to the cluster and depgraph. LIMITED_WHILE(provider.remaining_bytes() > 0, TestBitSet::Size() * TestBitSet::Size()) { auto parent = provider.ConsumeIntegralInRange(0, num_tx - 1); auto child = provider.ConsumeIntegralInRange(0, num_tx - 2); child += (child >= parent); cluster[child].second.Set(parent); depgraph.AddDependency(parent, child); assert(depgraph.Ancestors(child)[parent]); assert(depgraph.Descendants(parent)[child]); } // Sanity check the result. SanityCheck(depgraph); // Verify that the resulting DepGraph matches one recomputed from the cluster. assert(DepGraph(cluster) == depgraph); } FUZZ_TARGET(clusterlin_cluster_serialization) { // Verify that any graph of transactions has its ancestry correctly computed by DepGraph, and // if it is a DAG, that it can be serialized as a DepGraph in a way that roundtrips. This // guarantees that any acyclic cluster has a corresponding DepGraph serialization. FuzzedDataProvider provider(buffer.data(), buffer.size()); // Construct a cluster in a naive way (using a FuzzedDataProvider-based serialization). Cluster cluster; auto num_tx = provider.ConsumeIntegralInRange(1, 32); cluster.resize(num_tx); for (ClusterIndex i = 0; i < num_tx; ++i) { cluster[i].first.size = provider.ConsumeIntegralInRange(1, 0x3fffff); cluster[i].first.fee = provider.ConsumeIntegralInRange(-0x8000000000000, 0x7ffffffffffff); for (ClusterIndex j = 0; j < num_tx; ++j) { if (i == j) continue; if (provider.ConsumeBool()) cluster[i].second.Set(j); } } // Construct dependency graph, and verify it matches the cluster (which includes a round-trip // check for the serialization). DepGraph depgraph(cluster); VerifyDepGraphFromCluster(cluster, depgraph); } FUZZ_TARGET(clusterlin_depgraph_serialization) { // Verify that any deserialized depgraph is acyclic and roundtrips to an identical depgraph. // Construct a graph by deserializing. SpanReader reader(buffer); DepGraph depgraph; try { reader >> Using(depgraph); } catch (const std::ios_base::failure&) {} SanityCheck(depgraph); // Verify the graph is a DAG. assert(IsAcyclic(depgraph)); } FUZZ_TARGET(clusterlin_ancestor_finder) { // Verify that AncestorCandidateFinder works as expected. // Retrieve a depgraph from the fuzz input. SpanReader reader(buffer); DepGraph depgraph; try { reader >> Using(depgraph); } catch (const std::ios_base::failure&) {} AncestorCandidateFinder anc_finder(depgraph); auto todo = TestBitSet::Fill(depgraph.TxCount()); while (todo.Any()) { // Call the ancestor finder's FindCandidateSet for what remains of the graph. assert(!anc_finder.AllDone()); auto best_anc = anc_finder.FindCandidateSet(); // Sanity check the result. assert(best_anc.transactions.Any()); assert(best_anc.transactions.IsSubsetOf(todo)); assert(depgraph.FeeRate(best_anc.transactions) == best_anc.feerate); // Check that it is topologically valid. for (auto i : best_anc.transactions) { assert((depgraph.Ancestors(i) & todo).IsSubsetOf(best_anc.transactions)); } // Compute all remaining ancestor sets. std::optional> real_best_anc; for (auto i : todo) { SetInfo info(depgraph, todo & depgraph.Ancestors(i)); if (!real_best_anc.has_value() || info.feerate > real_best_anc->feerate) { real_best_anc = info; } } // The set returned by anc_finder must equal the real best ancestor sets. assert(real_best_anc.has_value()); assert(*real_best_anc == best_anc); // Find a topologically valid subset of transactions to remove from the graph. auto del_set = ReadTopologicalSet(depgraph, todo, reader); // If we did not find anything, use best_anc itself, because we should remove something. if (del_set.None()) del_set = best_anc.transactions; todo -= del_set; anc_finder.MarkDone(del_set); } assert(anc_finder.AllDone()); }