From 62ed1f92efff42bc79c50935e6dbd9da4e072020 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 13 Jul 2025 13:32:35 -0400 Subject: [PATCH] txgraph: check that DoWork finds optimal if given high budget (tests) --- src/test/fuzz/cluster_linearize.cpp | 17 +------------ src/test/fuzz/txgraph.cpp | 39 +++++++++++++++++++++++------ src/test/util/cluster_linearize.h | 23 +++++++++++++++++ 3 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/test/fuzz/cluster_linearize.cpp b/src/test/fuzz/cluster_linearize.cpp index 4cd93e20793..9e8dc2f43dc 100644 --- a/src/test/fuzz/cluster_linearize.cpp +++ b/src/test/fuzz/cluster_linearize.cpp @@ -1167,24 +1167,9 @@ FUZZ_TARGET(clusterlin_linearize) } // If the iteration count is sufficiently high, an optimal linearization must be found. - // Each linearization step can use up to 2^(k-1) iterations, with steps k=1..n. That sum is - // 2^n - 1. - const uint64_t n = depgraph.TxCount(); - if (n <= 19 && iter_count > (uint64_t{1} << n)) { + if (iter_count >= MaxOptimalLinearizationIters(depgraph.TxCount())) { assert(optimal); } - // Additionally, if the assumption of sqrt(2^k)+1 iterations per step holds, plus ceil(k/4) - // start-up cost per step, plus ceil(n^2/64) start-up cost overall, we can compute the upper - // bound for a whole linearization (summing for k=1..n) using the Python expression - // [sum((k+3)//4 + int(math.sqrt(2**k)) + 1 for k in range(1, n + 1)) + (n**2 + 63) // 64 for n in range(0, 35)]: - static constexpr uint64_t MAX_OPTIMAL_ITERS[] = { - 0, 4, 8, 12, 18, 26, 37, 51, 70, 97, 133, 182, 251, 346, 480, 666, 927, 1296, 1815, 2545, - 3576, 5031, 7087, 9991, 14094, 19895, 28096, 39690, 56083, 79263, 112041, 158391, 223936, - 316629, 447712 - }; - if (n < std::size(MAX_OPTIMAL_ITERS) && iter_count >= MAX_OPTIMAL_ITERS[n]) { - Assume(optimal); - } // If Linearize claims optimal result, run quality tests. if (optimal) { diff --git a/src/test/fuzz/txgraph.cpp b/src/test/fuzz/txgraph.cpp index d9629b11a45..e48d9852942 100644 --- a/src/test/fuzz/txgraph.cpp +++ b/src/test/fuzz/txgraph.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -730,16 +731,38 @@ FUZZ_TARGET(txgraph) } else if (command-- == 0) { // DoWork. uint64_t iters = provider.ConsumeIntegralInRange(0, alt ? 10000 : 255); - if (real->DoWork(iters)) { - for (unsigned level = 0; level < sims.size(); ++level) { - // DoWork() will not optimize oversized levels. - if (sims[level].IsOversized()) continue; - // DoWork() will not touch the main level if a builder is present. - if (level == 0 && !block_builders.empty()) continue; - // If neither of the two above conditions holds, and DoWork() returned - // then the level is optimal. + bool ret = real->DoWork(iters); + uint64_t iters_for_optimal{0}; + for (unsigned level = 0; level < sims.size(); ++level) { + // DoWork() will not optimize oversized levels, or the main level if a builder + // is present. Note that this impacts the DoWork() return value, as true means + // that non-optimal clusters may remain within such oversized or builder-having + // levels. + if (sims[level].IsOversized()) continue; + if (level == 0 && !block_builders.empty()) continue; + // If neither of the two above conditions holds, and DoWork() returned true, + // then the level is optimal. + if (ret) { sims[level].real_is_optimal = true; } + // Compute how many iterations would be needed to make everything optimal. + for (auto component : sims[level].GetComponents()) { + auto iters_opt_this_cluster = MaxOptimalLinearizationIters(component.Count()); + if (iters_opt_this_cluster > acceptable_iters) { + // If the number of iterations required to linearize this cluster + // optimally exceeds acceptable_iters, DoWork() may process it in two + // stages: once to acceptable, and once to optimal. + iters_for_optimal += iters_opt_this_cluster + acceptable_iters; + } else { + iters_for_optimal += iters_opt_this_cluster; + } + } + } + if (!ret) { + // DoWork can only have more work left if the requested number of iterations + // was insufficient to linearize everything optimally within the levels it is + // allowed to touch. + assert(iters <= iters_for_optimal); } break; } else if (sims.size() == 2 && !sims[0].IsOversized() && !sims[1].IsOversized() && command-- == 0) { diff --git a/src/test/util/cluster_linearize.h b/src/test/util/cluster_linearize.h index a89a46552b6..c79f414a0e4 100644 --- a/src/test/util/cluster_linearize.h +++ b/src/test/util/cluster_linearize.h @@ -394,6 +394,29 @@ void SanityCheck(const DepGraph& depgraph, std::span