2024-05-08 20:52:56 -04:00
|
|
|
// 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 <cluster_linearize.h>
|
|
|
|
#include <serialize.h>
|
|
|
|
#include <streams.h>
|
|
|
|
#include <test/fuzz/fuzz.h>
|
|
|
|
#include <test/fuzz/FuzzedDataProvider.h>
|
|
|
|
#include <test/util/cluster_linearize.h>
|
|
|
|
#include <util/bitset.h>
|
|
|
|
#include <util/feefrac.h>
|
|
|
|
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <vector>
|
|
|
|
#include <utility>
|
|
|
|
|
2024-05-08 18:56:59 -04:00
|
|
|
using namespace cluster_linearize;
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
/** Given a dependency graph, and a todo set, read a topological subset of todo from reader. */
|
|
|
|
template<typename SetType>
|
|
|
|
SetType ReadTopologicalSet(const DepGraph<SetType>& 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
|
|
|
|
|
2024-05-08 20:52:56 -04:00
|
|
|
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<ClusterIndex>(2, 32);
|
|
|
|
Cluster<TestBitSet> 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<ClusterIndex>(0, num_tx - 1);
|
|
|
|
auto child = provider.ConsumeIntegralInRange<ClusterIndex>(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<TestBitSet> cluster;
|
|
|
|
auto num_tx = provider.ConsumeIntegralInRange<ClusterIndex>(1, 32);
|
|
|
|
cluster.resize(num_tx);
|
|
|
|
for (ClusterIndex i = 0; i < num_tx; ++i) {
|
|
|
|
cluster[i].first.size = provider.ConsumeIntegralInRange<int32_t>(1, 0x3fffff);
|
|
|
|
cluster[i].first.fee = provider.ConsumeIntegralInRange<int64_t>(-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<TestBitSet> depgraph;
|
|
|
|
try {
|
|
|
|
reader >> Using<DepGraphFormatter>(depgraph);
|
|
|
|
} catch (const std::ios_base::failure&) {}
|
|
|
|
SanityCheck(depgraph);
|
|
|
|
|
|
|
|
// Verify the graph is a DAG.
|
|
|
|
assert(IsAcyclic(depgraph));
|
|
|
|
}
|
2024-05-08 18:56:59 -04:00
|
|
|
|
|
|
|
FUZZ_TARGET(clusterlin_ancestor_finder)
|
|
|
|
{
|
|
|
|
// Verify that AncestorCandidateFinder works as expected.
|
|
|
|
|
|
|
|
// Retrieve a depgraph from the fuzz input.
|
|
|
|
SpanReader reader(buffer);
|
|
|
|
DepGraph<TestBitSet> depgraph;
|
|
|
|
try {
|
|
|
|
reader >> Using<DepGraphFormatter>(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<SetInfo<TestBitSet>> 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());
|
|
|
|
}
|