diff --git a/src/pow.cpp b/src/pow.cpp index 0f54aaec0f7..907d378741d 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -139,7 +139,7 @@ bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t heig // the most significant bit of the last byte of the hash is set. bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params) { - if constexpr (G_FUZZING) return (hash.data()[31] & 0x80) == 0; + if (EnableFuzzDeterminism()) return (hash.data()[31] & 0x80) == 0; return CheckProofOfWorkImpl(hash, nBits, params); } diff --git a/src/test/fuzz/fuzz.cpp b/src/test/fuzz/fuzz.cpp index 30766700b9d..77341898536 100644 --- a/src/test/fuzz/fuzz.cpp +++ b/src/test/fuzz/fuzz.cpp @@ -147,10 +147,18 @@ static void initialize() std::cerr << "No fuzz target compiled for " << g_fuzz_target << "." << std::endl; std::exit(EXIT_FAILURE); } - if constexpr (!G_FUZZING) { - std::cerr << "Must compile with -DBUILD_FOR_FUZZING=ON to execute a fuzz target." << std::endl; + if constexpr (!G_FUZZING_BUILD && !G_ABORT_ON_FAILED_ASSUME) { + std::cerr << "Must compile with -DBUILD_FOR_FUZZING=ON or in Debug mode to execute a fuzz target." << std::endl; std::exit(EXIT_FAILURE); } + if (!EnableFuzzDeterminism()) { + if (std::getenv("FUZZ_NONDETERMINISM")) { + std::cerr << "Warning: FUZZ_NONDETERMINISM env var set, results may be inconsistent with fuzz build" << std::endl; + } else { + g_enable_dynamic_fuzz_determinism = true; + assert(EnableFuzzDeterminism()); + } + } Assert(!g_test_one_input); g_test_one_input = &it->second.test_one_input; it->second.opts.init(); diff --git a/src/test/util/random.cpp b/src/test/util/random.cpp index d75f1ef8a6b..90ddf66ca41 100644 --- a/src/test/util/random.cpp +++ b/src/test/util/random.cpp @@ -25,7 +25,7 @@ void SeedRandomStateForTest(SeedRand seedtype) // no longer truly random. It should be enough to get the seed once for the // process. static const auto g_ctx_seed = []() -> std::optional { - if constexpr (G_FUZZING) return {}; + if (EnableFuzzDeterminism()) return {}; // If RANDOM_CTX_SEED is set, use that as seed. if (const char* num{std::getenv(RANDOM_CTX_SEED)}) { if (auto num_parsed{uint256::FromUserHex(num)}) { @@ -40,7 +40,7 @@ void SeedRandomStateForTest(SeedRand seedtype) }(); g_seeded_g_prng_zero = seedtype == SeedRand::ZEROS; - if constexpr (G_FUZZING) { + if (EnableFuzzDeterminism()) { Assert(g_seeded_g_prng_zero); // Only SeedRandomStateForTest(SeedRand::ZEROS) is allowed in fuzz tests Assert(!g_used_g_prng); // The global PRNG must not have been used before SeedRandomStateForTest(SeedRand::ZEROS) } diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 87f0b31cad8..82bbc4adcba 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -112,7 +112,7 @@ static void ExitFailure(std::string_view str_err) BasicTestingSetup::BasicTestingSetup(const ChainType chainType, TestOpts opts) : m_args{} { - if constexpr (!G_FUZZING) { + if (!EnableFuzzDeterminism()) { SeedRandomForTest(SeedRand::FIXED_SEED); } m_node.shutdown_signal = &m_interrupt; @@ -203,7 +203,7 @@ BasicTestingSetup::~BasicTestingSetup() { m_node.ecc_context.reset(); m_node.kernel.reset(); - if constexpr (!G_FUZZING) { + if (!EnableFuzzDeterminism()) { SetMockTime(0s); // Reset mocktime for following tests } LogInstance().DisconnectTestLogger(); @@ -229,8 +229,9 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts) m_node.scheduler->m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { m_node.scheduler->serviceQueue(); }); m_node.validation_signals = // Use synchronous task runner while fuzzing to avoid non-determinism - G_FUZZING ? std::make_unique(std::make_unique()) : - std::make_unique(std::make_unique(*m_node.scheduler)); + EnableFuzzDeterminism() ? + std::make_unique(std::make_unique()) : + std::make_unique(std::make_unique(*m_node.scheduler)); { // Ensure deterministic coverage by waiting for m_service_thread to be running std::promise promise; @@ -255,7 +256,7 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts) .notifications = *m_node.notifications, .signals = m_node.validation_signals.get(), // Use no worker threads while fuzzing to avoid non-determinism - .worker_threads_num = G_FUZZING ? 0 : 2, + .worker_threads_num = EnableFuzzDeterminism() ? 0 : 2, }; if (opts.min_validation_cache) { chainman_opts.script_execution_cache_bytes = 0; diff --git a/src/util/check.cpp b/src/util/check.cpp index 1430c0e8e29..9f07fbe4780 100644 --- a/src/util/check.cpp +++ b/src/util/check.cpp @@ -33,3 +33,5 @@ void assertion_fail(std::string_view file, int line, std::string_view func, std: fwrite(str.data(), 1, str.size(), stderr); std::abort(); } + +std::atomic g_enable_dynamic_fuzz_determinism{false}; diff --git a/src/util/check.h b/src/util/check.h index efc78915a9e..f7dea5a3c2e 100644 --- a/src/util/check.h +++ b/src/util/check.h @@ -7,19 +7,44 @@ #include +#include #include // IWYU pragma: export #include #include #include #include -constexpr bool G_FUZZING{ +constexpr bool G_FUZZING_BUILD{ #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION true #else false #endif }; +constexpr bool G_ABORT_ON_FAILED_ASSUME{ +#ifdef ABORT_ON_FAILED_ASSUME + true +#else + false +#endif +}; + +extern std::atomic g_enable_dynamic_fuzz_determinism; + +inline bool EnableFuzzDeterminism() +{ + if constexpr (G_FUZZING_BUILD) { + return true; + } else if constexpr (!G_ABORT_ON_FAILED_ASSUME) { + // Running fuzz tests is always disabled if Assume() doesn't abort + // (ie, non-fuzz non-debug builds), as otherwise tests which + // should fail due to a failing Assume may still pass. As such, + // we also statically disable fuzz determinism in that case. + return false; + } else { + return g_enable_dynamic_fuzz_determinism; + } +} std::string StrFormatInternalBug(std::string_view msg, std::string_view file, int line, std::string_view func); @@ -50,11 +75,7 @@ void assertion_fail(std::string_view file, int line, std::string_view func, std: template constexpr T&& inline_assertion_check(LIFETIMEBOUND T&& val, [[maybe_unused]] const char* file, [[maybe_unused]] int line, [[maybe_unused]] const char* func, [[maybe_unused]] const char* assertion) { - if (IS_ASSERT || std::is_constant_evaluated() || G_FUZZING -#ifdef ABORT_ON_FAILED_ASSUME - || true -#endif - ) { + if (IS_ASSERT || std::is_constant_evaluated() || G_FUZZING_BUILD || G_ABORT_ON_FAILED_ASSUME) { if (!val) { assertion_fail(file, line, func, assertion); }