281 lines
8.5 KiB
C++
281 lines
8.5 KiB
C++
/*
|
|
* This file is part of the flowee project
|
|
* Copyright (C) 2017-2025 Tom Zander <tom@flowee.org>
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "Engine.h"
|
|
#include "BlockValidation_p.h"
|
|
#include "TxValidation_p.h"
|
|
#include "ValidationException.h"
|
|
#include <Application.h>
|
|
#include <main.h>
|
|
#include <net.h>
|
|
#include <Logger.h>
|
|
#include <txorphancache.h>
|
|
#include <utxo/UnspentOutputDatabase.h>
|
|
#include <server/BlocksDB.h>
|
|
#include <WaitUntilFinishedHelper.h>
|
|
|
|
#include <boost/asio/post.hpp>
|
|
|
|
// #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<BlockValidationState> 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<BlockValidationState> 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<std::string> 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<std::string> promise;
|
|
promise.set_value(std::string());
|
|
return promise.get_future();
|
|
}
|
|
const uint256 hash = tx.createHash();
|
|
std::shared_ptr<TxValidationState> state(new TxValidationState(priv(), tx, onResultFlags));
|
|
bool start = true;
|
|
{
|
|
std::lock_guard<std::mutex> 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<ValidationEnginePrivate> dd(d); // Make sure this method is re-entrant
|
|
if (!dd.get() || dd->shuttingDown)
|
|
return;
|
|
std::unique_lock<decltype(dd->lock)> lock(dd->lock);
|
|
while (!dd->shuttingDown && dd->headersInFlight >= dd->blocksInFlightLimit())
|
|
dd->waitVariable.wait(lock);
|
|
}
|
|
|
|
void Validation::Engine::waitValidationFinished()
|
|
{
|
|
std::shared_ptr<ValidationEnginePrivate> dd(d); // Make sure this method is re-entrant
|
|
if (!dd.get())
|
|
return;
|
|
std::unique_lock<decltype(dd->lock)> lock(dd->lock);
|
|
while (!dd->shuttingDown && (dd->headersInFlight > 0 || dd->blocksInFlight > 0))
|
|
dd->waitVariable.wait(lock);
|
|
}
|
|
|
|
std::weak_ptr<ValidationEnginePrivate> 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<std::mutex> 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<void>());
|
|
}
|
|
|
|
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<void>());
|
|
}
|