From 0aa874a357865dd4768091f26dff238e66fb8d83 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Fri, 25 Oct 2024 14:11:50 -0400 Subject: [PATCH] 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. --- src/cluster_linearize.h | 32 +++++++++++++++ src/test/fuzz/cluster_linearize.cpp | 62 +++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h index 7c7401706f6..32cedf9f840 100644 --- a/src/cluster_linearize.h +++ b/src/cluster_linearize.h @@ -1336,6 +1336,38 @@ std::vector MergeLinearizations(const DepGraph& depgraph, return ret; } +/** Make linearization topological, retaining its ordering where possible. */ +template +void FixLinearization(const DepGraph& depgraph, std::span 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 #endif // BITCOIN_CLUSTER_LINEARIZE_H diff --git a/src/test/fuzz/cluster_linearize.cpp b/src/test/fuzz/cluster_linearize.cpp index 5b3770636ab..de066237b2a 100644 --- a/src/test/fuzz/cluster_linearize.cpp +++ b/src/test/fuzz/cluster_linearize.cpp @@ -1118,3 +1118,65 @@ FUZZ_TARGET(clusterlin_merge) auto cmp2 = CompareChunks(chunking_merged, chunking2); 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 depgraph; + try { + reader >> Using(depgraph); + } catch (const std::ios_base::failure&) {} + + // Construct an arbitrary linearization (not necessarily topological for depgraph). + std::vector 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); + } +}