From a0eaa4749fe0f755e113eee70dee1989bdc07ad5 Mon Sep 17 00:00:00 2001
From: marcofleon <marleo23@proton.me>
Date: Tue, 13 Aug 2024 11:42:59 +0100
Subject: [PATCH] Add FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION in PoW check

To avoid PoW being a blocker for fuzz tests,
`FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION` is used in fuzz builds to
bypass the actual PoW validation in `CheckProofOfWork`. It's
replaced with a check on the last byte of the hash, which allows the
fuzzer to quickly generate (in)valid blocks by checking a single bit,
rather than performing the full PoW computation.

If PoW is the target of a fuzz test, then it should call
`CheckProofOfWorkImpl`.
---
 src/pow.cpp               | 11 +++++++++++
 src/pow.h                 |  1 +
 src/test/fuzz/integer.cpp |  2 +-
 src/test/fuzz/pow.cpp     |  2 +-
 4 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/pow.cpp b/src/pow.cpp
index 50de8946be6..6c8e7e5d98a 100644
--- a/src/pow.cpp
+++ b/src/pow.cpp
@@ -134,7 +134,18 @@ bool PermittedDifficultyTransition(const Consensus::Params& params, int64_t heig
     return true;
 }
 
+// Bypasses the actual proof of work check during fuzz testing with a simplified validation checking whether
+// the most signficant bit of the last byte of the hash is set.
 bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params& params)
+{
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+    return (hash.data()[31] & 0x80) == 0;
+#else
+    return CheckProofOfWorkImpl(hash, nBits, params);
+#endif
+}
+
+bool CheckProofOfWorkImpl(uint256 hash, unsigned int nBits, const Consensus::Params& params)
 {
     bool fNegative;
     bool fOverflow;
diff --git a/src/pow.h b/src/pow.h
index ec03f318a42..2b28ade273c 100644
--- a/src/pow.h
+++ b/src/pow.h
@@ -19,6 +19,7 @@ unsigned int CalculateNextWorkRequired(const CBlockIndex* pindexLast, int64_t nF
 
 /** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */
 bool CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&);
+bool CheckProofOfWorkImpl(uint256 hash, unsigned int nBits, const Consensus::Params&);
 
 /**
  * Return false if the proof-of-work requirement specified by new_nbits at a
diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp
index 02c6796d11e..87838b7b39f 100644
--- a/src/test/fuzz/integer.cpp
+++ b/src/test/fuzz/integer.cpp
@@ -69,7 +69,7 @@ FUZZ_TARGET(integer, .init = initialize_integer)
     const bool b = fuzzed_data_provider.ConsumeBool();
 
     const Consensus::Params& consensus_params = Params().GetConsensus();
-    (void)CheckProofOfWork(u256, u32, consensus_params);
+    (void)CheckProofOfWorkImpl(u256, u32, consensus_params);
     if (u64 <= MAX_MONEY) {
         const uint64_t compressed_money_amount = CompressAmount(u64);
         assert(u64 == DecompressAmount(compressed_money_amount));
diff --git a/src/test/fuzz/pow.cpp b/src/test/fuzz/pow.cpp
index 05cdb740e48..dba999ce4fa 100644
--- a/src/test/fuzz/pow.cpp
+++ b/src/test/fuzz/pow.cpp
@@ -80,7 +80,7 @@ FUZZ_TARGET(pow, .init = initialize_pow)
         {
             const std::optional<uint256> hash = ConsumeDeserializable<uint256>(fuzzed_data_provider);
             if (hash) {
-                (void)CheckProofOfWork(*hash, fuzzed_data_provider.ConsumeIntegral<unsigned int>(), consensus_params);
+                (void)CheckProofOfWorkImpl(*hash, fuzzed_data_provider.ConsumeIntegral<unsigned int>(), consensus_params);
             }
         }
     }