/* * This file is part of the flowee project * Copyright (C) 2017-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "Engine.h" #include "BlockValidation_p.h" #include "TxValidation_p.h" #include "ValidationException.h" #include #include #include #include #include #include #include #include #include // #define DEBUG_BLOCK_VALIDATION #ifdef DEBUG_BLOCK_VALIDATION # define DEBUGBV logCritical(Log::BlockValidation) #else # define DEBUGBV BCH_NO_DEBUG_MACRO() #endif // #define DEBUG_TRANSACTION_VALIDATION #ifdef DEBUG_TRANSACTION_VALIDATION # define DEBUGTX logCritical(Log::TxValidation) #else # define DEBUGTX BCH_NO_DEBUG_MACRO() #endif using Validation::Exception; Validation::Engine::Engine(EngineType type) : d(new ValidationEnginePrivate(type)) { d->me = priv(); } Validation::Engine::~Engine() { // shutdown is a little odd. We don't delete our 'BlockValidationPrivate' instance! // instead we tell it to shutdown and we reset the shared pointer. // any jobs that are still running will stop eventually and they will then also // stop referencing our 'BlockValidationPrivate' instance and when that count hits zero, // then our BlockValidationPrivate will be deleted. shutdown(); } void Validation::Engine::setSpvValidationMode(bool spv) { d->setSpvValidationMode(spv); } Validation::Settings Validation::Engine::addBlock(const Block &block, std::uint32_t onResultFlags, CNode *pFrom) { assert(onResultFlags < 8); if (!d.get() || d->shuttingDown) return Validation::Settings(); #ifdef DEBUG_BLOCK_VALIDATION if (block.size() < 80) DEBUGBV << "Block too small" << block.size() << '/' << block.previousBlockId() << "Headers in flight:" << d->headersInFlight.load() << (block.isFullBlock() ? "":"[header]"); else DEBUGBV << block.createHash() << '/' << block.previousBlockId() << "Headers in flight:" << d->headersInFlight.load() << (block.isFullBlock() ? "":"[header]"); #endif d->headersInFlight.fetch_add(1); std::shared_ptr state(new BlockValidationState(priv(), block, onResultFlags, pFrom ? pFrom->id : -1)); Validation::Settings settings; settings.d->state = state; state->m_settings = settings.d; return settings; } Validation::Settings Validation::Engine::addBlock(const CDiskBlockPos &pos, std::uint32_t onResultFlags) { assert(onResultFlags < 8); if (!d.get() || d->shuttingDown) return Validation::Settings(); d->headersInFlight.fetch_add(1); DEBUGBV << d->headersInFlight.load(); Block dummy; std::shared_ptr state(new BlockValidationState(priv(), dummy, onResultFlags)); state->m_blockPos = pos; Validation::Settings settings; settings.d->state = state; state->m_settings = settings.d; return settings; } std::future Validation::Engine::addTransaction(const Tx &tx, uint32_t onResultFlags, CNode *pFrom) { assert(onResultFlags < 0x40); assert((onResultFlags & SaveGoodToDisk) == 0); if (!d.get() || d->shuttingDown) { std::promise promise; promise.set_value(std::string()); return promise.get_future(); } const uint256 hash = tx.createHash(); std::shared_ptr state(new TxValidationState(priv(), tx, onResultFlags)); bool start = true; { std::lock_guard rejects(d->recentRejectsLock); if (d->recentTxRejects.contains(hash)) { state->m_promise.set_value("recently rejected"); start = false; } } if (start && CTxOrphanCache::contains(hash)) start = false; DEBUGTX << tx.createHash() << tx.size() << "will start:" << start; if (pFrom) state->m_originatingNodeId = pFrom->id; if (start) boost::asio::post(Application::instance()->ioContext(), std::bind(&TxValidationState::checkTransaction, state)); return state->m_promise.get_future(); } void Validation::Engine::waitForSpace() { std::shared_ptr dd(d); // Make sure this method is re-entrant if (!dd.get() || dd->shuttingDown) return; std::unique_locklock)> lock(dd->lock); while (!dd->shuttingDown && dd->headersInFlight >= dd->blocksInFlightLimit()) dd->waitVariable.wait(lock); } void Validation::Engine::waitValidationFinished() { std::shared_ptr dd(d); // Make sure this method is re-entrant if (!dd.get()) return; std::unique_locklock)> lock(dd->lock); while (!dd->shuttingDown && (dd->headersInFlight > 0 || dd->blocksInFlight > 0)) dd->waitVariable.wait(lock); } std::weak_ptr Validation::Engine::priv() const { return d; } void Validation::Engine::setBlockchain(CChain *chain) { assert(chain); if (!d.get() || d->shuttingDown) return; d->blockchain = chain; d->tip = chain->Tip(); if (chain->Height() >= 0) { d->tipFlags.updateForBlock(chain->Tip()); chain->Tip()->nStatus |= BLOCK_VALID_SCRIPTS; } } bool Validation::Engine::isRecentlyRejectedTransaction(const uint256 &txHash) const { if (!d.get() || d->shuttingDown) return false; std::lock_guard lock(d->recentRejectsLock); return d->recentTxRejects.contains(txHash); } CChain *Validation::Engine::blockchain() const { if (!d.get() || d->shuttingDown) return nullptr; return d->blockchain; } void Validation::Engine::setMempool(CTxMemPool *mempool) { if (!d.get() || d->shuttingDown) return; d->mempool = mempool; } CTxMemPool *Validation::Engine::mempool() const { if (!d.get() || d->shuttingDown) return nullptr; return d->mempool; } void Validation::Engine::invalidateBlock(CBlockIndex *index) { assert(index); if (!d.get() || d->shuttingDown) return; // Tell the UTXO that the block is invalid. mempool()->utxo()->setFailedBlockId(index->GetBlockHash()); index->nStatus |= BLOCK_FAILED_VALID; Blocks::DB::instance()->appendHeader(index); WaitUntilFinishedHelper helper(std::bind(&ValidationEnginePrivate::prepareChain_priv, d), &d->strand); helper.run(); } void Validation::Engine::enableFeeResolveForMetaData(bool on) { if (!d.get() || d->shuttingDown) return; d->fetchFeeForMetaBlocks = on; } void ValidationEnginePrivate::prepareChain_priv() { prepareChain(); lastFullBlockScheduled = -1; findMoreJobs(); } bool Validation::Engine::disconnectTip(CBlockIndex *index, bool *userClean) { assert(index); if (!d.get() || d->shuttingDown) return true; assert(d->mempool); assert(d->mempool->utxo()); bool clean = true; bool error = false; // essentially our return-value, since our helper doesn't remember that. WaitUntilFinishedHelper helper(std::bind(&ValidationEnginePrivate::disconnectTip, d, index, &clean, &error), &d->strand); helper.run(); if (userClean) { *userClean = clean; return !error; } return clean && !error; } void Validation::Engine::shutdown() { if (!d.get()) return; d->shuttingDown = true; WaitUntilFinishedHelper helper(std::bind(&ValidationEnginePrivate::cleanup, d), &d->strand); d.reset(); helper.run(); } void Validation::Engine::start() { d->strand.post(std::bind(&ValidationEnginePrivate::findMoreJobs, d), std::allocator()); } uint32_t Validation::Engine::tipValidationFlags(bool requireStandard) const { return priv().lock()->tipFlags.scriptValidationFlags(requireStandard); } void Validation::Engine::nodeBanned(int nodeId) { if (d.get() && !d->shuttingDown) d->strand.post(std::bind(&ValidationEnginePrivate::nodeBanned, d, nodeId), std::allocator()); }