clusterlin: Add FixLinearization function + fuzz test

This function takes an existing ordering for transactions in a DepGraph, and
makes it a valid linearization for it (i.e., topological). Any topological
prefix of the input remains untouched.
This commit is contained in:
Pieter Wuille 2024-10-25 14:11:50 -04:00
parent 770d39a376
commit 0aa874a357
2 changed files with 94 additions and 0 deletions

View file

@ -1336,6 +1336,38 @@ std::vector<ClusterIndex> MergeLinearizations(const DepGraph<SetType>& depgraph,
return ret; return ret;
} }
/** Make linearization topological, retaining its ordering where possible. */
template<typename SetType>
void FixLinearization(const DepGraph<SetType>& depgraph, std::span<ClusterIndex> linearization) noexcept
{
// This algorithm can be summarized as moving every element in the linearization backwards
// until it is placed after all its ancestors.
SetType done;
const auto len = linearization.size();
// Iterate over the elements of linearization from back to front (i is distance from back).
for (ClusterIndex i = 0; i < len; ++i) {
/** The element at that position. */
ClusterIndex elem = linearization[len - 1 - i];
/** j represents how far from the back of the linearization elem should be placed. */
ClusterIndex j = i;
// Figure out which elements need to be moved before elem.
SetType place_before = done & depgraph.Ancestors(elem);
// Find which position to place elem in (updating j), continuously moving the elements
// in between forward.
while (place_before.Any()) {
// j cannot be 0 here; if it was, then there was necessarily nothing earlier which
// elem needs to be place before anymore, and place_before would be empty.
Assume(j > 0);
auto to_swap = linearization[len - 1 - (j - 1)];
place_before.Reset(to_swap);
linearization[len - 1 - (j--)] = to_swap;
}
// Put elem in its final position and mark it as done.
linearization[len - 1 - j] = elem;
done.Set(elem);
}
}
} // namespace cluster_linearize } // namespace cluster_linearize
#endif // BITCOIN_CLUSTER_LINEARIZE_H #endif // BITCOIN_CLUSTER_LINEARIZE_H

View file

@ -1118,3 +1118,65 @@ FUZZ_TARGET(clusterlin_merge)
auto cmp2 = CompareChunks(chunking_merged, chunking2); auto cmp2 = CompareChunks(chunking_merged, chunking2);
assert(cmp2 >= 0); assert(cmp2 >= 0);
} }
FUZZ_TARGET(clusterlin_fix_linearization)
{
// Verify expected properties of FixLinearization() on arbitrary linearizations.
// Retrieve a depgraph from the fuzz input.
SpanReader reader(buffer);
DepGraph<TestBitSet> depgraph;
try {
reader >> Using<DepGraphFormatter>(depgraph);
} catch (const std::ios_base::failure&) {}
// Construct an arbitrary linearization (not necessarily topological for depgraph).
std::vector<ClusterIndex> linearization;
/** Which transactions of depgraph are yet to be included in linearization. */
TestBitSet todo = depgraph.Positions();
while (todo.Any()) {
// Read a number from the fuzz input in range [0, todo.Count()).
uint64_t val{0};
try {
reader >> VARINT(val);
} catch (const std::ios_base::failure&) {}
val %= todo.Count();
// Find the val'th element in todo, remove it from todo, and append it to linearization.
for (auto idx : todo) {
if (val == 0) {
linearization.push_back(idx);
todo.Reset(idx);
break;
}
--val;
}
}
assert(linearization.size() == depgraph.TxCount());
// Determine what prefix of linearization is topological, i.e., the position of the first entry
// in linearization which corresponds to a transaction that is not preceded by all its
// ancestors.
size_t topo_prefix = 0;
todo = depgraph.Positions();
while (topo_prefix < linearization.size()) {
ClusterIndex idx = linearization[topo_prefix];
todo.Reset(idx);
if (todo.Overlaps(depgraph.Ancestors(idx))) break;
++topo_prefix;
}
// Then make a fixed copy of linearization.
auto linearization_fixed = linearization;
FixLinearization(depgraph, linearization_fixed);
// Sanity check it (which includes testing whether it is topological).
SanityCheck(depgraph, linearization_fixed);
// FixLinearization does not modify the topological prefix of linearization.
assert(std::equal(linearization.begin(), linearization.begin() + topo_prefix,
linearization_fixed.begin()));
// This also means that if linearization was entirely topological, FixLinearization cannot have
// modified it. This is implied by the assertion above already, but repeat it explicitly.
if (topo_prefix == linearization.size()) {
assert(linearization == linearization_fixed);
}
}