From a4d9f1a1b09409b2e07a25c9f921ca64cb2a06ea Mon Sep 17 00:00:00 2001 From: Reuben Yap Date: Sun, 14 Jun 2026 02:18:23 +0800 Subject: [PATCH] Spark: fail closed on deferred batch verification --- src/batchproof_container.cpp | 25 +++++++++++++++++-------- src/batchproof_container.h | 5 +++-- src/init.cpp | 11 ++++++++--- src/validation.cpp | 19 ++++++++++++++++++- src/validation.h | 2 ++ 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/batchproof_container.cpp b/src/batchproof_container.cpp index e2a2fe040e..45aeafb507 100644 --- a/src/batchproof_container.cpp +++ b/src/batchproof_container.cpp @@ -20,15 +20,23 @@ void BatchProofContainer::init() { void BatchProofContainer::finalize() { if (fCollectProofs) { sparkTransactions.insert(sparkTransactions.end(), tempSparkTransactions.begin(), tempSparkTransactions.end()); + tempSparkTransactions.clear(); } fCollectProofs = false; } -void BatchProofContainer::verify() { - if (!fCollectProofs) { - batch_spark(); +bool BatchProofContainer::verify() { + if (fCollectProofs) { + fCollectProofs = false; + return true; } - fCollectProofs = false; + + return batch_spark(); +} + +bool BatchProofContainer::verify_pending() { + finalize(); + return batch_spark(); } void BatchProofContainer::add(const spark::SpendTransaction& tx) { @@ -42,12 +50,12 @@ void BatchProofContainer::remove(const spark::SpendTransaction& tx) { sparkTransactions.end()); } -void BatchProofContainer::batch_spark() { +bool BatchProofContainer::batch_spark() { if (!sparkTransactions.empty()){ LogPrintf("Spark batch verification started.\n"); uiInterface.UpdateProgressBarLabel("Batch verifying Spark Proofs..."); } else { - return; + return true; } std::unordered_map> cover_sets; @@ -75,10 +83,11 @@ void BatchProofContainer::batch_spark() { if (!passed) { LogPrintf("Spark batch verification failed."); - throw std::invalid_argument("Spark batch verification failed, please run Firo with -reindex -batching=0"); + return false; } if (!sparkTransactions.empty()) LogPrintf("Spark batch verification finished successfully.\n"); sparkTransactions.clear(); -} \ No newline at end of file + return true; +} diff --git a/src/batchproof_container.h b/src/batchproof_container.h index dc87dcfce2..9a19e47b51 100644 --- a/src/batchproof_container.h +++ b/src/batchproof_container.h @@ -15,11 +15,12 @@ class BatchProofContainer { void finalize(); - void verify(); + bool verify(); + bool verify_pending(); void add(const spark::SpendTransaction& tx); void remove(const spark::SpendTransaction& tx); - void batch_spark(); + bool batch_spark(); public: bool fCollectProofs = 0; diff --git a/src/init.cpp b/src/init.cpp index f80bd18562..1dfd59f944 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -41,7 +41,6 @@ #include "validationinterface.h" #include "validation.h" #include "mtpstate.h" -#include "batchproof_container.h" #include #include "leveldb/env.h" @@ -264,8 +263,9 @@ void Shutdown() StopHTTPServer(); llmq::StopLLMQSystem(); - BatchProofContainer::get_instance()->finalize(); - BatchProofContainer::get_instance()->verify(); + CValidationState sparkBatchState; + if (!VerifyPendingSparkBatch(sparkBatchState, "shutdown")) + LogPrintf("Shutdown continuing after Spark batch verification failure: %s\n", FormatStateMessage(sparkBatchState)); #ifdef ENABLE_WALLET if (pwalletMain) @@ -761,6 +761,11 @@ void ThreadImport(std::vector vImportFiles) { LoadExternalBlockFile(chainparams, file, &pos); nFile++; } + CValidationState state; + if (!VerifyPendingSparkBatch(state, "clearing reindex flag")) { + LogPrintf("Reindexing stopped before clearing reindex flag: %s\n", FormatStateMessage(state)); + return; + } pblocktree->WriteReindexing(false); fReindex = false; LogPrintf("Reindexing finished\n"); diff --git a/src/validation.cpp b/src/validation.cpp index 616fbf6e39..b7983a3997 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2234,6 +2234,17 @@ bool AbortNode(CValidationState& state, const std::string& strMessage, const std return state.Error(strMessage); } +bool VerifyPendingSparkBatch(CValidationState& state, const std::string& reason) +{ + BatchProofContainer* batchProofContainer = BatchProofContainer::get_instance(); + if (!batchProofContainer->verify_pending()) { + return AbortNode(state, + strprintf("Spark batch verification failed before %s", reason), + _("Spark batch verification failed. Please restart with -reindex -batching=0 to identify the invalid Spark spend.")); + } + return true; +} + enum DisconnectResult { DISCONNECT_OK, // All good. @@ -3084,6 +3095,8 @@ bool static FlushStateToDisk(CValidationState &state, FlushStateMode mode, int n bool fPeriodicFlush = mode == FLUSH_STATE_PERIODIC && nNow > nLastFlush + (int64_t)DATABASE_FLUSH_INTERVAL * 1000000; // Combine all conditions that result in a full cache flush. bool fDoFullFlush = (mode == FLUSH_STATE_ALWAYS) || fCacheLarge || fCacheCritical || fPeriodicFlush || fFlushForPrune; + if ((fDoFullFlush || fPeriodicWrite) && !VerifyPendingSparkBatch(state, "flushing block index or chainstate")) + return false; // Write blocks and block index to disk. if (fDoFullFlush || fPeriodicWrite) { // Depend on nMinDiskSpace to ensure we can write block index @@ -3798,7 +3811,11 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, // Do batch verification if we reach 1 day old block, BatchProofContainer* batchProofContainer = BatchProofContainer::get_instance(); batchProofContainer->fCollectProofs = ((GetSystemTimeInSeconds() - pindexNewTip->GetBlockTime()) > 86400) && GetBoolArg("-batching", true); - batchProofContainer->verify(); + if (!batchProofContainer->verify()) { + return AbortNode(state, + "Spark batch verification failed", + _("Spark batch verification failed. Please restart with -reindex -batching=0 to identify the invalid Spark spend.")); + } // When we reach this point, we switched to a new tip (stored in pindexNewTip). diff --git a/src/validation.h b/src/validation.h index 48f1bd776d..6bf31d5427 100644 --- a/src/validation.h +++ b/src/validation.h @@ -303,6 +303,8 @@ bool IsInitialBlockDownload(); bool GetTransaction(const uint256 &hash, CTransactionRef &tx, const Consensus::Params& params, uint256 &hashBlock, bool fAllowSlow = false); /** Find the best known block, and make it the tip of the block chain */ bool ActivateBestChain(CValidationState& state, const CChainParams& chainparams, std::shared_ptr pblock = std::shared_ptr()); +/** Verify any pending deferred Spark proof batch before persisting validation state. */ +bool VerifyPendingSparkBatch(CValidationState& state, const std::string& reason); CAmount GetBlockSubsidyWithMTPFlag(int nHeight, const Consensus::Params& consensusParams, bool fMTP, bool fShorterBlockDistance); CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams, int nTime = 1475020800); CAmount GetMasternodePayment(int nHeight, int nTime, CAmount blockValue);