Skip to content

Commit 62ed1f9

Browse files
committed
txgraph: check that DoWork finds optimal if given high budget (tests)
1 parent f3c2fc8 commit 62ed1f9

File tree

3 files changed

+55
-24
lines changed

3 files changed

+55
-24
lines changed

src/test/fuzz/cluster_linearize.cpp

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,24 +1167,9 @@ FUZZ_TARGET(clusterlin_linearize)
11671167
}
11681168

11691169
// If the iteration count is sufficiently high, an optimal linearization must be found.
1170-
// Each linearization step can use up to 2^(k-1) iterations, with steps k=1..n. That sum is
1171-
// 2^n - 1.
1172-
const uint64_t n = depgraph.TxCount();
1173-
if (n <= 19 && iter_count > (uint64_t{1} << n)) {
1170+
if (iter_count >= MaxOptimalLinearizationIters(depgraph.TxCount())) {
11741171
assert(optimal);
11751172
}
1176-
// Additionally, if the assumption of sqrt(2^k)+1 iterations per step holds, plus ceil(k/4)
1177-
// start-up cost per step, plus ceil(n^2/64) start-up cost overall, we can compute the upper
1178-
// bound for a whole linearization (summing for k=1..n) using the Python expression
1179-
// [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)]:
1180-
static constexpr uint64_t MAX_OPTIMAL_ITERS[] = {
1181-
0, 4, 8, 12, 18, 26, 37, 51, 70, 97, 133, 182, 251, 346, 480, 666, 927, 1296, 1815, 2545,
1182-
3576, 5031, 7087, 9991, 14094, 19895, 28096, 39690, 56083, 79263, 112041, 158391, 223936,
1183-
316629, 447712
1184-
};
1185-
if (n < std::size(MAX_OPTIMAL_ITERS) && iter_count >= MAX_OPTIMAL_ITERS[n]) {
1186-
Assume(optimal);
1187-
}
11881173

11891174
// If Linearize claims optimal result, run quality tests.
11901175
if (optimal) {

src/test/fuzz/txgraph.cpp

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <cluster_linearize.h>
66
#include <test/fuzz/FuzzedDataProvider.h>
77
#include <test/fuzz/fuzz.h>
8+
#include <test/util/cluster_linearize.h>
89
#include <test/util/random.h>
910
#include <txgraph.h>
1011
#include <util/bitset.h>
@@ -730,16 +731,38 @@ FUZZ_TARGET(txgraph)
730731
} else if (command-- == 0) {
731732
// DoWork.
732733
uint64_t iters = provider.ConsumeIntegralInRange<uint64_t>(0, alt ? 10000 : 255);
733-
if (real->DoWork(iters)) {
734-
for (unsigned level = 0; level < sims.size(); ++level) {
735-
// DoWork() will not optimize oversized levels.
736-
if (sims[level].IsOversized()) continue;
737-
// DoWork() will not touch the main level if a builder is present.
738-
if (level == 0 && !block_builders.empty()) continue;
739-
// If neither of the two above conditions holds, and DoWork() returned
740-
// then the level is optimal.
734+
bool ret = real->DoWork(iters);
735+
uint64_t iters_for_optimal{0};
736+
for (unsigned level = 0; level < sims.size(); ++level) {
737+
// DoWork() will not optimize oversized levels, or the main level if a builder
738+
// is present. Note that this impacts the DoWork() return value, as true means
739+
// that non-optimal clusters may remain within such oversized or builder-having
740+
// levels.
741+
if (sims[level].IsOversized()) continue;
742+
if (level == 0 && !block_builders.empty()) continue;
743+
// If neither of the two above conditions holds, and DoWork() returned true,
744+
// then the level is optimal.
745+
if (ret) {
741746
sims[level].real_is_optimal = true;
742747
}
748+
// Compute how many iterations would be needed to make everything optimal.
749+
for (auto component : sims[level].GetComponents()) {
750+
auto iters_opt_this_cluster = MaxOptimalLinearizationIters(component.Count());
751+
if (iters_opt_this_cluster > acceptable_iters) {
752+
// If the number of iterations required to linearize this cluster
753+
// optimally exceeds acceptable_iters, DoWork() may process it in two
754+
// stages: once to acceptable, and once to optimal.
755+
iters_for_optimal += iters_opt_this_cluster + acceptable_iters;
756+
} else {
757+
iters_for_optimal += iters_opt_this_cluster;
758+
}
759+
}
760+
}
761+
if (!ret) {
762+
// DoWork can only have more work left if the requested number of iterations
763+
// was insufficient to linearize everything optimally within the levels it is
764+
// allowed to touch.
765+
assert(iters <= iters_for_optimal);
743766
}
744767
break;
745768
} else if (sims.size() == 2 && !sims[0].IsOversized() && !sims[1].IsOversized() && command-- == 0) {

src/test/util/cluster_linearize.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,29 @@ void SanityCheck(const DepGraph<SetType>& depgraph, std::span<const DepGraphInde
394394
}
395395
}
396396

397+
inline uint64_t MaxOptimalLinearizationIters(DepGraphIndex cluster_count)
398+
{
399+
// We assume sqrt(2^k)+1 candidate-finding iterations per candidate to be found, plus ceil(k/4)
400+
// startup cost when up to k unlinearization transactions remain, plus ceil(n^2/64) overall
401+
// startup cost in Linearize. Thus, we can compute the upper bound for a whole linearization
402+
// (summing for k=1..n) using the Python expression:
403+
//
404+
// [sum((k+3)//4 + math.isqrt(2**k) + 1 for k in range(1, n + 1)) + (n**2 + 63) // 64 for n in range(0, 65)]
405+
//
406+
// Note that these are just assumptions, as the proven upper bound grows with 2^k, not
407+
// sqrt(2^k).
408+
static constexpr uint64_t MAX_OPTIMAL_ITERS[65] = {
409+
0, 4, 8, 12, 18, 26, 37, 51, 70, 97, 133, 182, 251, 346, 480, 666, 927, 1296, 1815, 2545,
410+
3576, 5031, 7087, 9991, 14094, 19895, 28096, 39690, 56083, 79263, 112041, 158391, 223936,
411+
316629, 447712, 633086, 895241, 1265980, 1790280, 2531747, 3580335, 5063259, 7160424,
412+
10126257, 14320575, 20252230, 28640853, 40504150, 57281380, 81007962, 114562410, 162015557,
413+
229124437, 324030718, 458248463, 648061011, 916496483, 1296121563, 1832992493, 2592242635,
414+
3665984477, 5184484745, 7331968412, 10368968930, 14663936244
415+
};
416+
assert(cluster_count < sizeof(MAX_OPTIMAL_ITERS) / sizeof(MAX_OPTIMAL_ITERS[0]));
417+
return MAX_OPTIMAL_ITERS[cluster_count];
418+
}
419+
397420
} // namespace
398421

399422
#endif // BITCOIN_TEST_UTIL_CLUSTER_LINEARIZE_H

0 commit comments

Comments
 (0)