From b9d573e4a9594e460e100042f195392f3a59480b Mon Sep 17 00:00:00 2001 From: Hao Xu Date: Tue, 28 Jul 2026 22:17:01 +0800 Subject: [PATCH] fees: Return false for incompatible fee estimates policy_estimator_io deliberately reuses a CBlockPolicyEstimator because constructing one for every fuzz input severely reduces throughput. However, Read() returns true for an incompatible old fee estimates file without replacing the estimator state. The target then calls Write() with state loaded by a previous input, making coverage depend on corpus order. Return false for incompatible files so the target skips Write() when no state was loaded. This keeps the estimator reuse optimization instead of resetting the expensive object before every fuzz input. For the in-tree production caller, incompatible files remain non-fatal and the estimator still starts from its default state. Read() now reports failure, so startup emits one additional non-fatal warning. Node startup and estimator state are unchanged, as is RPC behavior. Co-authored-by: maflcko <6399679+maflcko@users.noreply.github.com> --- src/policy/fees/block_policy_estimator.cpp | 76 +++++++++++----------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/policy/fees/block_policy_estimator.cpp b/src/policy/fees/block_policy_estimator.cpp index 748c49bcdc6..1434c5de070 100644 --- a/src/policy/fees/block_policy_estimator.cpp +++ b/src/policy/fees/block_policy_estimator.cpp @@ -1008,51 +1008,51 @@ bool CBlockPolicyEstimator::Read(AutoFile& filein) if (nVersionRequired > CURRENT_FEES_FILE_VERSION) { throw std::runtime_error{strprintf("File version (%d) too high to be read.", nVersionRequired)}; } + if (nVersionRequired < CURRENT_FEES_FILE_VERSION) { + throw std::runtime_error{strprintf("File version (%d) incompatible: Too old to be read", nVersionRequired)}; + } // Read fee estimates file into temporary variables so existing data // structures aren't corrupted if there is an exception. unsigned int nFileBestSeenHeight; filein >> nFileBestSeenHeight; - if (nVersionRequired < CURRENT_FEES_FILE_VERSION) { - LogWarning("Incompatible old fee estimation data (non-fatal). Version: %d", nVersionRequired); - } else { // nVersionRequired == CURRENT_FEES_FILE_VERSION - unsigned int nFileHistoricalFirst, nFileHistoricalBest; - filein >> nFileHistoricalFirst >> nFileHistoricalBest; - if (nFileHistoricalFirst > nFileHistoricalBest || nFileHistoricalBest > nFileBestSeenHeight) { - throw std::runtime_error("Corrupt estimates file. Historical block range for estimates is invalid"); - } - std::vector fileBuckets; - filein >> Using>(fileBuckets); - size_t numBuckets = fileBuckets.size(); - if (numBuckets <= 1 || numBuckets > 1000) { - throw std::runtime_error("Corrupt estimates file. Must have between 2 and 1000 feerate buckets"); - } - - std::unique_ptr fileFeeStats(new TxConfirmStats(buckets, bucketMap, MED_BLOCK_PERIODS, MED_DECAY, MED_SCALE)); - std::unique_ptr fileShortStats(new TxConfirmStats(buckets, bucketMap, SHORT_BLOCK_PERIODS, SHORT_DECAY, SHORT_SCALE)); - std::unique_ptr fileLongStats(new TxConfirmStats(buckets, bucketMap, LONG_BLOCK_PERIODS, LONG_DECAY, LONG_SCALE)); - fileFeeStats->Read(filein, numBuckets); - fileShortStats->Read(filein, numBuckets); - fileLongStats->Read(filein, numBuckets); - - // Fee estimates file parsed correctly - // Copy buckets from file and refresh our bucketmap - buckets = fileBuckets; - bucketMap.clear(); - for (unsigned int i = 0; i < buckets.size(); i++) { - bucketMap[buckets[i]] = i; - } - - // Destroy old TxConfirmStats and point to new ones that already reference buckets and bucketMap - feeStats = std::move(fileFeeStats); - shortStats = std::move(fileShortStats); - longStats = std::move(fileLongStats); - - nBestSeenHeight = nFileBestSeenHeight; - historicalFirst = nFileHistoricalFirst; - historicalBest = nFileHistoricalBest; + // nVersionRequired == CURRENT_FEES_FILE_VERSION + unsigned int nFileHistoricalFirst, nFileHistoricalBest; + filein >> nFileHistoricalFirst >> nFileHistoricalBest; + if (nFileHistoricalFirst > nFileHistoricalBest || nFileHistoricalBest > nFileBestSeenHeight) { + throw std::runtime_error("Corrupt estimates file. Historical block range for estimates is invalid"); } + std::vector fileBuckets; + filein >> Using>(fileBuckets); + size_t numBuckets = fileBuckets.size(); + if (numBuckets <= 1 || numBuckets > 1000) { + throw std::runtime_error("Corrupt estimates file. Must have between 2 and 1000 feerate buckets"); + } + + std::unique_ptr fileFeeStats(new TxConfirmStats(buckets, bucketMap, MED_BLOCK_PERIODS, MED_DECAY, MED_SCALE)); + std::unique_ptr fileShortStats(new TxConfirmStats(buckets, bucketMap, SHORT_BLOCK_PERIODS, SHORT_DECAY, SHORT_SCALE)); + std::unique_ptr fileLongStats(new TxConfirmStats(buckets, bucketMap, LONG_BLOCK_PERIODS, LONG_DECAY, LONG_SCALE)); + fileFeeStats->Read(filein, numBuckets); + fileShortStats->Read(filein, numBuckets); + fileLongStats->Read(filein, numBuckets); + + // Fee estimates file parsed correctly + // Copy buckets from file and refresh our bucketmap + buckets = fileBuckets; + bucketMap.clear(); + for (unsigned int i = 0; i < buckets.size(); i++) { + bucketMap[buckets[i]] = i; + } + + // Destroy old TxConfirmStats and point to new ones that already reference buckets and bucketMap + feeStats = std::move(fileFeeStats); + shortStats = std::move(fileShortStats); + longStats = std::move(fileLongStats); + + nBestSeenHeight = nFileBestSeenHeight; + historicalFirst = nFileHistoricalFirst; + historicalBest = nFileHistoricalBest; } catch (const std::exception& e) { LogWarning("Unable to read policy estimator data (non-fatal): %s", e.what());