Files

1787 lines
76 KiB
C++
Raw Permalink Normal View History

2018-01-15 15:26:12 +00:00
/*
* This file is part of the flowee project
2025-02-11 19:41:22 +01:00
* Copyright (C) 2017-2025 Tom Zander <tom@flowee.org>
2018-01-15 15:26:12 +00:00
*
* 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"
2018-02-12 14:15:24 +01:00
#include <SettingsDefaults.h>
2018-01-15 15:26:12 +00:00
#include "BlockValidation_p.h"
#include "TxValidation_p.h"
#include "BlockMetaData.h"
2019-09-02 23:28:14 +02:00
#include "DoubleSpendProofStorage.h"
2018-01-15 15:26:12 +00:00
#include "ValidationException.h"
#include <consensus/consensus.h>
#include <Application.h>
#include <checkpoints.h>
#include <init.h> // for StartShutdown
#include <main.h>
2019-08-24 13:20:37 +02:00
#include <util.h>
2018-01-15 15:26:12 +00:00
#include <txorphancache.h>
#include <policy/policy.h>
#include <timedata.h>
2018-02-10 22:48:07 +01:00
#include <UiInterface.h>
2018-01-15 15:26:12 +00:00
#include <validationinterface.h>
#include <chainparams.h>
#include <consensus/validation.h>
2026-05-13 17:55:03 +02:00
#include <server/BlocksDB.h>
#include <Logger.h>
2020-04-01 22:51:47 +02:00
#include <merkle.h>
2026-05-13 17:55:03 +02:00
#include <utiltime.h>
2018-02-15 18:06:38 +01:00
#include <streaming/BufferPool.h>
2025-02-11 19:41:22 +01:00
#include <streaming/MessageBuilder.h>
#include <utxo/UnspentOutputDatabase.h>
2023-07-14 21:50:44 +02:00
#include <utxo/UTXOInteralError.h>
#include <UnspentOutputData.h>
2026-05-13 17:55:03 +02:00
2025-02-11 19:41:22 +01:00
#include <boost/asio/post.hpp>
2018-01-15 15:26:12 +00:00
// #define DEBUG_BLOCK_VALIDATION
#ifdef DEBUG_BLOCK_VALIDATION
# define DEBUGBV logCritical(Log::BlockValidation)
#else
# define DEBUGBV BCH_NO_DEBUG_MACRO()
2018-01-15 15:26:12 +00:00
#endif
using Validation::Exception;
//---------------------------------------------------------
ValidationEnginePrivate::ValidationEnginePrivate(Validation::EngineType type)
2025-02-08 19:05:26 +01:00
: strand(Application::instance()->ioContext()),
2018-01-15 15:26:12 +00:00
shuttingDown(false),
headersInFlight(0),
blocksInFlight(0),
2018-06-14 20:42:19 +02:00
blockchain(nullptr),
mempool(nullptr),
2018-01-15 15:26:12 +00:00
recentTxRejects(120000, 0.000001),
engineType(type),
lastFullBlockScheduled(-1)
#ifdef ENABLE_BENCHMARKS
, m_headerCheckTime(0),
m_basicValidityChecks(0),
m_contextCheckTime(0),
m_utxoTime(0),
m_validationTime(0),
m_loadingTime(0),
m_mempoolTime(0),
m_walletTime(0)
#endif
{
}
/**
* @brief All blocks offered will get back here, where we check the output of the header checks.
* @param state the actual block and its parsing state.
*
* This method is called in a strand, which means we can avoid locking for our member vars
* additionally, the state object is guarenteed (by design, not by locks) to not change any of its
* members during this call. Except for the state atomic as further validation of transactions
* can be happening in another thread at the same time.
*/
void ValidationEnginePrivate::blockHeaderValidated(std::shared_ptr<BlockValidationState> state)
{
assert(strand.running_in_this_thread());
assert(state->m_blockIndex == nullptr);
if (state->m_block.size() < 80) {// malformed block, without header we can't report issues, just return
state->error = "Malformed block (too short)";
2018-01-15 15:26:12 +00:00
return;
}
2018-01-15 15:26:12 +00:00
struct RAII {
RAII(const std::shared_ptr<BlockValidationState> &state)
: m_state(state),
finished(true) { }
~RAII() {
std::shared_ptr<ValidationSettingsPrivate> settingsPriv = m_state->m_settings.lock();
if (settingsPriv) {
if (!error.empty())
settingsPriv->error = error;
if (m_state->m_ownsIndex) {
settingsPriv->blockHash = m_state->blockId;
if (!m_state->m_blockIndex->phashBlock) {
// make sure a non-global index still has a working blockhash pointer
m_state->m_blockIndex->phashBlock = &settingsPriv->blockHash;
}
} else {
// destructor of state will not delete index, so we can safely deref
settingsPriv->state.reset();
}
settingsPriv->setBlockIndex(m_state->m_blockIndex);
if (finished)
settingsPriv->markFinished();
2018-01-15 15:26:12 +00:00
}
}
std::shared_ptr<BlockValidationState> m_state;
bool finished;
std::string error;
};
RAII raii(state);
if (shuttingDown)
return;
const bool hasFailed = state->m_validationStatus.load() & BlockValidationState::BlockInvalid;
2021-11-02 10:18:24 +01:00
const Block &block = state->m_block;
2018-01-15 15:26:12 +00:00
assert(block.size() >= 80);
DEBUGBV << state->blockId << "Parent:" << state->m_block.previousBlockId() << (state->m_block.isFullBlock() ? "":"[header]");
2018-01-15 15:26:12 +00:00
if (!hasFailed) {
auto iter = blocksBeingValidated.find(state->blockId);
2018-01-15 15:26:12 +00:00
if (iter != blocksBeingValidated.end()) {
// we are already validating the block. And since the merkle root was found OK, this means
// that its a duplicate.
state->m_blockIndex = iter->second->m_blockIndex;
DEBUGBV << " + already in the blocksBeingValidated, ignoring";
return;
}
}
// check past work.
CBlockIndex *index = nullptr;
if (state->m_checkMerkleRoot) // We have no proper block-hash if we don't have a merkle-root.
index = Blocks::Index::get(state->blockId);
2018-01-15 15:26:12 +00:00
if (index) { // we already parsed it...
state->m_blockIndex = index;
state->m_ownsIndex = false;
DEBUGBV << " already known at height:" << index->nHeight;
DEBUGBV << " current height:" << blockchain->Height();
if (index->nStatus & BLOCK_FAILED_MASK) {
DEBUGBV << " Block failed previously. Ignoring";
raii.error = "Block failed previously, not rechecking";
return;
}
DEBUGBV << " pprev:" << index->pprev;
if (index->pprev)
DEBUGBV << " pprev has completed:" << index->pprev->IsValid(BLOCK_VALID_TRANSACTIONS);
assert(index);
assert(index->phashBlock);
if (blockchain->Contains(index)) { // its already in the chain...
DEBUGBV << " Already in the chain. Exiting";
return;
}
if (index->pprev) {
auto miPrev = blocksBeingValidated.find(block.previousBlockId());
if (miPrev != blocksBeingValidated.end()) {
miPrev->second->m_chainChildren.push_back(state);
DEBUGBV << " + adding me to parents chain-children" << miPrev->second->blockId;
2018-01-15 15:26:12 +00:00
} else if (index->pprev->IsValid(BLOCK_VALID_TRANSACTIONS)) {
state->m_validationStatus.fetch_or(BlockValidationState::BlockValidParent);
DEBUGBV << " Setting BlockValidParent";
}
}
if (index->nHeight == 0) {
state->m_validationStatus.fetch_or(BlockValidationState::BlockValidParent);
}
}
createBlockIndexFor(state);
index = state->m_blockIndex;
if (hasFailed || index->nStatus & BLOCK_FAILED_MASK) {
logInfo(Log::BlockValidation) << "Block" << index->nHeight << state->blockId << "rejected with error:" << state->error;
2020-02-27 13:35:56 +01:00
if (!state->m_checkValidityOnly && state->m_checkMerkleRoot)
2018-01-15 15:26:12 +00:00
handleFailedBlock(state);
return;
}
state->flags = tipFlags;
2021-03-15 12:54:58 +01:00
state->m_fetchFees = state->flags.enableValidation || fetchFeeForMetaBlocks;
2018-01-15 15:26:12 +00:00
if (index->nHeight == -1) { // is an orphan for now.
2024-03-20 19:08:32 +01:00
blockLanded(ValidationEnginePrivate::CheckingHeader);
2018-01-15 15:26:12 +00:00
if (state->m_checkValidityOnly) {
state->blockFailed(100, "Block is an orphan, can't check", Validation::RejectInternal);
2026-04-20 19:43:55 +02:00
return;
2024-03-20 19:08:32 +01:00
}
2026-04-20 19:43:55 +02:00
if (!Blocks::DB::instance()->isReindexing() && orphanBlocks.size() > 5000) {
logWarning(Log::BlockValidation) << "OrphanBlocks array shortened as it got too big";
2024-03-20 19:08:32 +01:00
// Since the orphanBlocks array takes space and more importantly, the
// block objects take a file-descriptor, lets just accept we don't
// have that block (header).
2026-04-20 19:43:55 +02:00
headersInFlight.fetch_add(100);
for (int i = 0; i < 100; ++i) {
auto iter = orphanBlocks.begin();
2024-03-20 19:08:32 +01:00
const std::shared_ptr<BlockValidationState> &orphan = *iter;
auto settings = orphan->m_settings.lock();
if (settings) {
settings->error = "orphan abandoned";
settings->markFinished();
}
2026-04-20 19:43:55 +02:00
orphanBlocks.erase(iter);
2024-03-20 19:08:32 +01:00
}
}
2026-04-20 19:43:55 +02:00
orphanBlocks.push_back(state);
DEBUGBV << " adding it to orphan blocks" << orphanBlocks.size() << "headers in flight now;" << headersInFlight.load();
raii.finished = false;
2018-01-15 15:26:12 +00:00
return;
}
2024-03-20 19:08:32 +01:00
// check all orphans to see if the addition of our new block attached them to the genesis.
2026-04-20 19:43:55 +02:00
std::vector<std::shared_ptr<BlockValidationState> > adoptees;
2018-01-15 15:26:12 +00:00
if (!state->m_checkValidityOnly)
startOrphanWithParent(adoptees, state);
DEBUGBV << " Found" << adoptees.size() << "adoptees";
if (adoptees.size())
2018-06-14 20:42:19 +02:00
headersInFlight.fetch_add(static_cast<int>(adoptees.size()));
2018-01-15 15:26:12 +00:00
#ifdef DEBUG_BLOCK_VALIDATION
2021-03-15 12:54:58 +01:00
for (auto &state : adoptees) {
2018-01-15 15:26:12 +00:00
assert(state->m_checkingHeader);
}
#endif
CBlockIndex *currentHeaderTip = Blocks::DB::instance()->headerChain().Tip();
adoptees.insert(adoptees.begin(), state);
2018-11-18 00:52:39 +01:00
const auto &cpMap = Params().Checkpoints().mapCheckpoints;
2026-04-23 14:51:25 +02:00
bool adoptedFullBlock = false;
2020-12-24 14:13:43 +01:00
for (const auto &item : adoptees) {
2018-01-15 15:26:12 +00:00
if (item->m_checkValidityOnly)
continue;
2020-02-27 13:35:56 +01:00
if (item->m_ownsIndex) {
item->m_ownsIndex = false; // the CBlockIndex is now owned by the Blocks::DB
item->m_blockIndex->RaiseValidity(BLOCK_VALID_TREE);
item->m_blockIndex->phashBlock = Blocks::Index::insert(item->blockId, item->m_blockIndex);
2020-02-27 13:35:56 +01:00
MarkIndexUnsaved(item->m_blockIndex); // Save it to the DB.
}
2018-11-18 00:52:39 +01:00
// check checkpoints. If we have the right height but not the hash, fail block
2026-04-20 14:06:33 +02:00
auto iter = cpMap.find(item->m_blockIndex->nHeight);
if (iter != cpMap.end() && iter->second != item->m_blockIndex->GetBlockHash()) {
logCritical(Log::BlockValidation) << "Failing block due to checkpoint" << item->m_blockIndex->nHeight
<< item->m_blockIndex->GetBlockHash();
item->m_blockIndex->nStatus |= BLOCK_FAILED_VALID;
raii.error = "Failed due to checkpoint";
2018-11-18 00:52:39 +01:00
}
2018-01-15 15:26:12 +00:00
assert(item->m_blockIndex->pprev || item->m_blockIndex->nHeight == 0);
Blocks::DB::instance()->appendHeader(item->m_blockIndex);
DEBUGBV << "appendHeader block at height:" << item->m_blockIndex->nHeight;
if (item->m_block.isFullBlock()) {
assert(!item->m_block.transactions().empty()); // assume we already had findTransactions called before here.
2018-06-14 20:42:19 +02:00
item->m_blockIndex->nTx = static_cast<std::uint32_t>(item->m_block.transactions().size());
2018-01-15 15:26:12 +00:00
try { // Write block to history file
2020-02-28 00:25:31 +01:00
if ((item->m_blockIndex->nStatus & BLOCK_HAVE_DATA) == 0 && item->m_onResultFlags & Validation::SaveGoodToDisk) {
2018-01-15 15:26:12 +00:00
item->m_block = Blocks::DB::instance()->writeBlock(item->m_block, item->m_blockPos);
}
if (!item->m_blockPos.IsNull()) {
item->m_blockIndex->nDataPos = item->m_blockPos.nPos;
item->m_blockIndex->nFile = item->m_blockPos.nFile;
item->m_blockIndex->nStatus |= BLOCK_HAVE_DATA;
}
} catch (const std::exception &e) {
fatal(e.what());
}
}
2026-04-23 14:51:25 +02:00
adoptedFullBlock |= item->m_blockIndex->nStatus & BLOCK_HAVE_DATA;
2018-01-15 15:26:12 +00:00
}
CBlockIndex *prevTip = Blocks::DB::instance()->headerChain().Tip();
assert(prevTip);
if (currentHeaderTip != prevTip) {
const CBlockIndex *chainTip = tip;
const bool farBehind = chainTip && chainTip->nHeight - 1008 < prevTip->nHeight;
if (!farBehind || previousPrintedHeaderHeight + 1000 < prevTip->nHeight) {
logCritical(Log::BlockValidation).nospace() << "new best header=" << *prevTip->phashBlock << " height=" << prevTip->nHeight << " orphans=" << orphanBlocks.size();
previousPrintedHeaderHeight = prevTip->nHeight;
}
}
2018-01-15 15:26:12 +00:00
if (currentHeaderTip && !Blocks::DB::instance()->headerChain().Contains(currentHeaderTip)) { // re-org happened in headers.
2019-04-28 22:58:36 +02:00
logInfo(Log::BlockValidation) << "Header-reorg detected. height=" << prevTip->nHeight <<
"Old-tip" << *currentHeaderTip->phashBlock << "@" << currentHeaderTip->nHeight;
2019-06-11 22:17:09 +02:00
int reorgSize = 0;
if (!Blocks::DB::instance()->headerChain().Contains(blockchain->Tip())) {
2019-05-26 13:30:27 +02:00
// the reorg removes blocks from our validated chain!
// Now see how big a reorg we are talking...
auto commonAncestor = Blocks::Index::lastCommonAncestor(currentHeaderTip, blockchain->Tip());
2019-06-11 22:17:09 +02:00
reorgSize = 1 + blockchain->Height() - commonAncestor->nHeight;
2019-05-26 13:30:27 +02:00
}
2019-06-11 22:17:09 +02:00
DEBUGBV << " + reorgSize" << reorgSize;
DEBUGBV << " + validation-tip" << blockchain->Height() << blockchain->Tip()->GetBlockHash();
2019-05-26 13:30:27 +02:00
2019-06-11 22:17:09 +02:00
if (reorgSize > 6 && Params().NetworkIDString() != CBaseChainParams::REGTEST) { // reorgs are fine on REGTEST
2020-02-27 14:43:30 +01:00
logCritical(Log::BlockValidation).nospace() << "Reorg larger than 6 blocks detected (" << reorgSize
<< "), this needs manual intervention.";
2019-05-26 13:30:27 +02:00
logCritical(Log::BlockValidation) << " Use invalidateblock and reconsiderblock methods to change chain.";
2019-06-11 22:17:09 +02:00
} else if (reorgSize > 0) {
2018-11-17 16:01:46 +01:00
prepareChain();
lastFullBlockScheduled = -1;
}
2018-01-15 15:26:12 +00:00
}
const int diff = index->nHeight - blockchain->Height();
if (diff <= blocksInFlightLimit()) { // if block is recent, then continue immediately.
2018-01-15 15:26:12 +00:00
bool forward = false;
bool first = true;
// adoptees are sorted
for (auto iter = adoptees.cbegin(); iter != adoptees.cend(); ++iter) {
const std::shared_ptr<BlockValidationState>&item = *iter;
if (!item->m_checkValidityOnly && !Blocks::DB::instance()->headerChain().Contains(item->m_blockIndex))
continue;
if (first) {
first = false;
// Check the first blocks BlockValidTree by checking its parents are all Ok.
assert(item->m_blockIndex);
if (item->m_blockIndex->nHeight <= 1 || blockchain->Contains(item->m_blockIndex->pprev)) {
forward = true;
} else {
auto iter = blocksBeingValidated.find(item->m_blockIndex->pprev->GetBlockHash());
if (iter != blocksBeingValidated.end()) {
if (iter->second->m_validationStatus.load() & BlockValidationState::BlockValidTree) {
forward = true;
}
}
}
}
forward = forward && item->m_block.isFullBlock();
if (forward) {
if (!item->m_checkValidityOnly) {
DEBUGBV << "moving block on to checks2. Block at height:" << item->m_blockIndex->nHeight;
blocksBeingValidated.insert(std::make_pair(item->blockId, item));
2018-01-15 15:26:12 +00:00
lastFullBlockScheduled = std::max(lastFullBlockScheduled, item->m_blockIndex->nHeight);
}
item->m_validationStatus.fetch_or(BlockValidationState::BlockValidTree);
2025-02-11 19:41:22 +01:00
boost::asio::post(Application::instance()->ioContext(),
std::bind(&BlockValidationState::checks2HaveParentHeaders, item));
2018-01-15 15:26:12 +00:00
}
}
raii.finished = !forward;
}
2026-04-23 14:51:25 +02:00
// If a full block was adopted from the orphans pool, there may be new jobs available for validation.
if (adoptedFullBlock && !shuttingDown)
strand.post(std::bind(&ValidationEnginePrivate::findMoreJobs, me.lock()), std::allocator<void>());
2018-01-15 15:26:12 +00:00
}
void ValidationEnginePrivate::createBlockIndexFor(const std::shared_ptr<BlockValidationState> &state)
{
DEBUGBV << state->blockId;
2018-01-15 15:26:12 +00:00
if (state->m_blockIndex)
return;
2021-11-02 10:18:24 +01:00
const Block &block = state->m_block;
2018-01-15 15:26:12 +00:00
CBlockIndex *index = new CBlockIndex();
state->m_blockIndex = index;
state->m_ownsIndex = true;
index->nHeight = -1;
index->nVersion = block.blockVersion();
index->hashMerkleRoot = block.merkleRoot();
index->nBits = block.bits();
index->nTime = block.timestamp();
index->nNonce = block.nonce();
index->nFile = state->m_blockPos.nFile;
index->nStatus = BLOCK_VALID_HEADER;
if (!state->m_blockPos.IsNull()) // likely found during reindex
index->nStatus |= BLOCK_HAVE_DATA;
auto miPrev = blocksBeingValidated.find(block.previousBlockId());
bool hasKnownParent = miPrev != blocksBeingValidated.end();
if (hasKnownParent) {
index->pprev = miPrev->second->m_blockIndex;
miPrev->second->m_chainChildren.push_back(state);
} else {
index->pprev = Blocks::Index::get(block.previousBlockId());
hasKnownParent = index->pprev;
if (hasKnownParent) {
if (index->pprev->IsValid(BLOCK_VALID_TRANSACTIONS))
state->m_validationStatus.fetch_or(BlockValidationState::BlockValidParent);
} else {
for (auto *headersTip : Blocks::DB::instance()->headerChainTips()) {
if (headersTip->GetBlockHash() == block.previousBlockId()) {
index->pprev = headersTip;
break;
}
}
}
}
if (index->pprev && index->pprev->nHeight != -1) {
index->nHeight = index->pprev->nHeight + 1;
index->nChainWork = index->pprev->nChainWork + GetBlockProof(*index);
index->BuildSkip();
DEBUGBV << " block" << index->nHeight << "has pprev";
if (index->pprev->nStatus & BLOCK_FAILED_MASK) {
DEBUGBV << " + Which failed!";
index->nStatus |= BLOCK_FAILED_CHILD;
state->blockFailed(10, "bad-parent", Validation::RejectInvalid);
2018-01-15 15:26:12 +00:00
}
2020-10-27 15:17:22 +01:00
else if (index->pprev->nStatus & BLOCK_VALID_TREE) {
// inherit this
state->m_validationStatus.fetch_or(BlockValidationState::BlockValidTree);
index->RaiseValidity(BLOCK_VALID_TREE);
}
2018-01-15 15:26:12 +00:00
}
else if (index->pprev == nullptr && state->blockId == Params().GetConsensus().hashGenesisBlock) {
2018-01-15 15:26:12 +00:00
index->nHeight = 0;
state->m_validationStatus.fetch_or(BlockValidationState::BlockValidParent);
DEBUGBV.nospace() << " is genesis block (height=" << index->nHeight << ")";
}
}
/**
* This method is called when the validation engine is in the process of shutting down.
* The validation engine has shared pointers to the State objects that are being validated,
* clearing those will causse the validation to stop, which is the wanted effect.
* The settings objects also have a shared pointer to the State objects, so we make all
* of those error out in order to entice other parts of the app to also delete those Settings
* objects, which will delete the State objects and then when all State objects are deleted
* will the BlockValidationPrivate finally be deleted too.
*/
void ValidationEnginePrivate::cleanup()
{
assert(strand.running_in_this_thread());
assert(shuttingDown);
auto iter = orphanBlocks.begin();
while (iter != orphanBlocks.end()) {
const std::shared_ptr<BlockValidationState> &orphan = *iter;
auto settings = orphan->m_settings.lock();
if (settings) {
settings->error = "shutdown";
settings->markFinished();
}
++iter;
}
orphanBlocks.clear();
auto iter2 = blocksBeingValidated.begin();
while (iter2 != blocksBeingValidated.end()) {
const std::shared_ptr<BlockValidationState> &block = iter2->second;
auto settings = block->m_settings.lock();
if (settings) {
settings->error = "shutdown";
settings->markFinished();
}
++iter2;
}
blocksBeingValidated.clear();
std::unique_lock<decltype(lock)> waitLock(lock);
waitVariable.notify_all();
}
/**
* @brief We have a block that has tracable ancestry to our genesis. We start processing it.
* This first starts by finding all the orphans that now can be de-orphaned because the block
* might be their parent.
*
* Additionally, we now can look at the POW to see how this block relates to the main-chain.
2024-03-20 19:08:32 +01:00
* @param adoptedItems orphans we managed to pair to their parent block.
2018-01-15 15:26:12 +00:00
* @param state the block.
*/
2026-04-20 19:43:55 +02:00
void ValidationEnginePrivate::startOrphanWithParent(std::vector<std::shared_ptr<BlockValidationState> > &adoptedItems, const std::shared_ptr<BlockValidationState> &state)
2018-01-15 15:26:12 +00:00
{
assert(strand.running_in_this_thread());
2026-04-20 19:43:55 +02:00
std::vector<std::shared_ptr<BlockValidationState> > parents;
2020-02-27 23:10:25 +01:00
parents.push_back(state); // we start with the method-argument, we replace it in each loop in the do{}while with new parents.
do {
2026-04-20 19:43:55 +02:00
std::vector<std::shared_ptr<BlockValidationState> > younglings; // adoptees from the newest generation
2020-02-27 23:10:25 +01:00
auto iter = orphanBlocks.begin();
while (iter != orphanBlocks.end()) {
std::shared_ptr<BlockValidationState> orphan = *iter;
bool match = false;
for (const auto &parent : parents) {
if (parent->blockId == orphan->m_block.previousBlockId()) {
2020-02-27 23:10:25 +01:00
// we found a new child of one of the recently found parents.
match = true;
2018-01-15 15:26:12 +00:00
2020-02-27 23:10:25 +01:00
bool alreadyThere = false;
2020-12-24 14:13:43 +01:00
for (const auto &child : parent->m_chainChildren) {
2020-02-27 23:10:25 +01:00
if (child.lock() == orphan) {
alreadyThere = true;
break;
}
}
if (!alreadyThere)
parent->m_chainChildren.push_back(std::weak_ptr<BlockValidationState>(orphan));
2018-01-15 15:26:12 +00:00
2020-02-27 23:10:25 +01:00
CBlockIndex *index = orphan->m_blockIndex;
index->pprev = parent->m_blockIndex;
index->nHeight = parent->m_blockIndex->nHeight + 1;
index->nChainWork = parent->m_blockIndex->nChainWork + GetBlockProof(*index);
index->BuildSkip();
adoptedItems.push_back(orphan);
younglings.push_back(orphan);
break;
}
2018-01-15 15:26:12 +00:00
}
2020-02-27 23:10:25 +01:00
if (match)
iter = orphanBlocks.erase(iter);
else
++iter;
2018-01-15 15:26:12 +00:00
}
2020-02-27 23:10:25 +01:00
parents = younglings;
} while (!parents.empty());
2018-01-15 15:26:12 +00:00
}
/*
* When a block gets passed to this method we know the block is fully validated for
2018-01-15 15:26:12 +00:00
* correctness, and so are all of the parent blocks.
*/
void ValidationEnginePrivate::processNewBlock(std::shared_ptr<BlockValidationState> state)
{
assert(strand.running_in_this_thread());
if (shuttingDown)
return;
2018-06-14 20:42:19 +02:00
if (state->m_blockIndex == nullptr) // already handled.
2018-01-15 15:26:12 +00:00
return;
struct RAII {
uint256 m_hash;
ValidationEnginePrivate *m_parent;
std::shared_ptr<ValidationSettingsPrivate> m_priv;
RAII(ValidationEnginePrivate *parent, const std::shared_ptr<BlockValidationState> &state)
: m_hash(state->blockId), m_parent(parent), m_priv(state->m_settings.lock()) { }
2018-01-15 15:26:12 +00:00
~RAII() {
m_parent->blocksBeingValidated.erase(m_hash);
if (m_priv)
m_priv->markFinished();
}
};
RAII raii(this, state);
if (state->m_checkValidityOnly)
return;
CBlockIndex *index = state->m_blockIndex;
DEBUGBV << state->blockId << state->m_blockIndex->nHeight;
2018-01-15 15:26:12 +00:00
DEBUGBV << " chain:" << blockchain->Height();
assert(blockchain->Height() == -1 || index->nChainWork >= blockchain->Tip()->nChainWork); // the new block has more POW.
2018-07-29 13:55:30 +02:00
const bool blockValid = (state->m_validationStatus.load() & BlockValidationState::BlockInvalid) == 0;
2019-04-28 22:58:36 +02:00
if (!blockValid) {
2018-07-29 14:13:18 +02:00
mempool->utxo()->rollback();
logInfo(Log::BlockValidation) << " block not valid" << index->nHeight << state->blockId << "chain-height:" << blockchain->Height();
2019-04-28 22:58:36 +02:00
}
2020-02-27 13:45:42 +01:00
const bool farBehind = Blocks::DB::instance()->headerChain().Height() - blockchain->Height() > 144; // catching up
2018-01-15 15:26:12 +00:00
2019-04-28 22:58:36 +02:00
const bool isNextChainTip = index->nHeight == blockchain->Height() + 1; // If a parent was rejected for some reason, this is false
bool addToChain = isNextChainTip && blockValid && Blocks::DB::instance()->headerChain().Contains(index);
2018-01-15 15:26:12 +00:00
try {
if (!isNextChainTip)
index->nStatus |= BLOCK_FAILED_CHILD;
if (addToChain) {
DEBUGBV << "UTXO best block is" << mempool->utxo()->blockId() << "my parent is" << state->m_block.previousBlockId();
if (mempool->utxo()->blockId() != state->m_block.previousBlockId())
throw Exception("UnspentOutput DB inconsistent!");
2018-01-15 15:26:12 +00:00
index->nChainTx = index->nTx + (index->pprev ? index->pprev->nChainTx : 0); // pprev is only null if this is the genesisblock.
index->RaiseValidity(BLOCK_VALID_CHAIN);
if (index->nHeight == 0) { // is genesis block
mempool->utxo()->blockFinished(index->nHeight, state->blockId);
2018-01-15 15:26:12 +00:00
blockchain->SetTip(index);
index->RaiseValidity(BLOCK_VALID_SCRIPTS); // done
state->signalChildren();
} else {
2020-04-12 23:48:32 +02:00
static const uint64_t maxSigChecks = Policy::blockSigCheckAcceptLimit();
if (state->m_sigChecksCounted > maxSigChecks)
throw Exception("bad-blk-sigcheck");
2018-01-15 15:26:12 +00:00
2021-11-02 09:36:09 +01:00
MutableBlock block = state->m_block.createOldBlock();
2021-03-15 12:54:58 +01:00
if (state->m_fetchFees) {
2021-01-20 19:21:53 +01:00
int64_t blockReward = state->m_blockFees.load() + GetBlockSubsidy(index->nHeight, Params().GetConsensus());
2018-01-15 15:26:12 +00:00
if (block.vtx[0].GetValueOut() > blockReward)
throw Exception("bad-cb-amount");
}
bool createMeta = Blocks::DB::instance()->reindexing() != Blocks::ScanningFiles;
if (createMeta && index->nStatus & BLOCK_HAVE_METADATA) {
2021-03-11 10:22:45 +01:00
// there already is one.
try {
BlockMetaData meta = Blocks::DB::instance()->loadBlockMetaData(index->GetMetaDataPos());
2021-03-15 12:54:58 +01:00
createMeta = state->m_fetchFees && !meta.hasFeesData(); // we have fees now, replace.
2021-03-11 10:22:45 +01:00
} catch (const std::exception &e) {} // loading may throw
}
2023-12-21 15:13:56 +01:00
auto pool = std::make_shared<Streaming::BufferPool>();
2021-03-11 10:22:45 +01:00
if (createMeta) {
auto metaData = BlockMetaData::parseBlock(index->nHeight, state->m_block, state->m_perTxFees, pool);
2021-03-12 14:08:55 +01:00
try {
CDiskBlockPos metaDataPos = Blocks::DB::instance()->writeMetaBlock(metaData);
if (!metaDataPos.IsNull()) {
index->nMetaDataPos = metaDataPos.nPos;
index->nMetaDataFile = metaDataPos.nFile;
index->nStatus |= BLOCK_HAVE_METADATA;
}
} catch (const std::exception &e) {
logWarning(Log::BlockValidation) << "Failed to write meta block due to:" << e;
2021-03-11 10:22:45 +01:00
}
2021-03-08 18:14:47 +01:00
}
assert(index->nFile >= 0); // we need the block to have been saved
2023-12-21 15:13:56 +01:00
UndoBlockBuilder undoBlock(state->blockId, pool);
for (auto &chunk : state->m_undoItems) {
2018-07-29 14:13:18 +02:00
if (chunk) undoBlock.append(*chunk);
}
2021-03-08 18:14:47 +01:00
Blocks::DB::instance()->writeUndoBlock(undoBlock, index->nFile, index->nUndoPos);
index->nStatus |= BLOCK_HAVE_UNDO;
index->RaiseValidity(BLOCK_VALID_SCRIPTS); // done
MarkIndexUnsaved(index);
2018-09-23 16:05:55 +02:00
#ifdef ENABLE_BENCHMARKS
int64_t end, start = GetTimeMicros();
#endif
bool savedState = mempool->utxo()->blockFinished(index->nHeight, state->blockId);
2018-09-23 16:05:55 +02:00
#ifdef ENABLE_BENCHMARKS
end = GetTimeMicros();
m_utxoTime.fetch_add(end - start);
start = end;
#endif
CValidationState val;
// if savedState is true then the UTXO just wrote a checkpoint, use this opportunity to also
// write all block-index state.
if (!FlushStateToDisk(val, savedState ? FLUSH_STATE_ALWAYS : FLUSH_STATE_IF_NEEDED))
fatal(val.GetRejectReason().c_str());
2018-01-15 15:26:12 +00:00
std::list<CTransaction> txConflicted;
2019-06-28 12:38:46 +02:00
mempool->removeForBlock(block.vtx, txConflicted);
2018-01-15 15:26:12 +00:00
state->signalChildren(); // start tx-validation of next one.
blockchain->SetTip(index);
tip.store(index);
mempool->AddTransactionsUpdated(1);
2019-09-02 23:28:14 +02:00
mempool->doubleSpendProofStorage()->newBlockFound();
2018-01-15 15:26:12 +00:00
cvBlockChange.notify_all();
#ifdef ENABLE_BENCHMARKS
end = GetTimeMicros();
2018-09-23 16:05:55 +02:00
m_mempoolTime.fetch_add(end - start);
2018-01-15 15:26:12 +00:00
start = end;
#endif
2020-02-27 13:45:42 +01:00
if (!farBehind) {
// ^ The Hub doesn't accept transactions on IBD, so avoid doing unneeded work.
2018-01-15 15:26:12 +00:00
std::lock_guard<std::mutex> rejects(recentRejectsLock);
2020-02-27 13:45:42 +01:00
recentTxRejects.clear();
2018-01-15 15:26:12 +00:00
}
// Tell wallet about transactions that went from mempool to conflicted:
2020-02-27 14:43:30 +01:00
for (const CTransaction &tx : txConflicted) {
2021-02-18 16:03:01 +01:00
ValidationNotifier().syncTransaction(tx);
2023-12-21 15:13:56 +01:00
ValidationNotifier().syncTx(Tx::fromOldTransaction(tx, pool));
2018-01-15 15:26:12 +00:00
}
2021-02-18 16:03:01 +01:00
ValidationNotifier().syncAllTransactionsInBlock(state->m_block, index); // ... and about transactions that got confirmed:
ValidationNotifier().syncAllTransactionsInBlock(&block);
2018-02-15 18:06:38 +01:00
2018-01-15 15:26:12 +00:00
#ifdef ENABLE_BENCHMARKS
end = GetTimeMicros();
m_walletTime.fetch_add(end - start);
#endif
}
} else {
logDebug(Log::BlockValidation) << "Not appending: isNextChainTip" << isNextChainTip << "blockValid:" << blockValid << "addToChain" << addToChain;
}
} catch (const Exception &e) {
2021-03-15 12:54:58 +01:00
logWarning() << "Block failed validation with" << e;
2018-01-15 15:26:12 +00:00
state->blockFailed(100, e.what(), e.rejectCode(), e.corruptionPossible());
addToChain = false;
}
2019-04-28 22:58:36 +02:00
if (!blockValid) {
logCritical(Log::BlockValidation) << "block failed validation" << state->error << index->nHeight << state->blockId;
2018-01-15 15:26:12 +00:00
if (index->pprev == nullptr) // genesis block, all bets are off after this
return;
handleFailedBlock(state);
if (state->m_blockIndex->nHeight == lastFullBlockScheduled)
--lastFullBlockScheduled;
}
2018-06-14 20:42:19 +02:00
state->m_blockIndex = nullptr;
2018-01-15 15:26:12 +00:00
if (!addToChain)
return;
tipFlags = state->flags;
2021-03-15 12:54:58 +01:00
#ifndef DEBUG_BLOCK_VALIDATION
const bool isRecentBlock = index->nHeight + 1008 > Blocks::DB::instance()->headerChain().Height();
if (isRecentBlock || index->nHeight % 500 == 0)
2021-03-15 12:54:58 +01:00
#endif
logCritical(Log::BlockValidation).nospace() << "new best=" << state->blockId << " height=" << index->nHeight
2018-01-15 15:26:12 +00:00
<< " tx=" << blockchain->Tip()->nChainTx
<< " date=" << DateTimeStrFormat("%Y-%m-%d %H:%M:%S", index->GetBlockTime()).c_str()
<< Log::Fixed << Log::precision(1);
2018-01-15 15:26:12 +00:00
#ifdef ENABLE_BENCHMARKS
if ((index->nHeight % 1000) == 0) {
logCritical(Log::Bench) << "Times. Header:" << m_headerCheckTime
<< "Structure:" << m_basicValidityChecks
<< "Context:" << m_contextCheckTime
2018-10-08 22:29:08 +02:00
<< "UTXO:" << m_utxoTime
2018-01-15 15:26:12 +00:00
<< "validation:" << m_validationTime
<< "loading:" << m_loadingTime
<< "mempool:" << m_mempoolTime
<< "wallet:" << m_walletTime;
}
int64_t start = GetTimeMicros();
#endif
2020-02-27 13:35:56 +01:00
uiInterface.NotifyBlockTip(farBehind, index);
2018-01-15 15:26:12 +00:00
{
LOCK(cs_main);
2021-02-18 16:03:01 +01:00
ValidationNotifier().updatedTransaction(hashPrevBestCoinBase);
2018-01-15 15:26:12 +00:00
}
hashPrevBestCoinBase = state->m_block.transactions().at(0).createHash();
#ifdef ENABLE_BENCHMARKS
m_mempoolTime.fetch_add(GetTimeMicros() - start);
#endif
if (state->m_onResultFlags & Validation::ForwardGoodToPeers) {
2019-08-05 11:55:59 +02:00
int totalBlocks = Blocks::DB::instance()->headerChain().Height();
2018-01-15 15:26:12 +00:00
LOCK(cs_vNodes);
for (CNode* pnode : vNodes) {
2019-08-05 11:55:59 +02:00
if (blockchain->Height() > totalBlocks - 10)
pnode->PushBlockHash(state->blockId);
2018-01-15 15:26:12 +00:00
}
}
}
void ValidationEnginePrivate::handleFailedBlock(const std::shared_ptr<BlockValidationState> &state)
{
assert(strand.running_in_this_thread());
2019-04-28 22:58:36 +02:00
assert(state->m_blockIndex);
assert(state->m_blockIndex != blockchain->Tip());
2018-01-15 15:26:12 +00:00
state->recursivelyMark(BlockValidationState::BlockInvalid);
if (!state->isCorruptionPossible && state->m_blockIndex && state->m_checkMerkleRoot) {
auto index = state->m_blockIndex;
state->m_blockIndex->nStatus |= BLOCK_FAILED_VALID;
// Mark all children as failed too
for (auto tip : Blocks::DB::instance()->headerChainTips()) {
if (tip->GetAncestor(index->nHeight) == index) {
while (tip != index) {
tip->nStatus |= BLOCK_FAILED_CHILD;
tip = tip->pprev;
}
}
}
2020-02-27 13:35:56 +01:00
// remember this failed block-id
mempool->utxo()->setFailedBlockId(state->blockId);
2018-01-15 15:26:12 +00:00
2020-09-11 13:09:28 +02:00
// no need to update the header chain if the index is never moved to be owned by the Blocks::DB
if (!state->m_ownsIndex) {
// check if this invalidation has an effect on the validated chain.
auto currentHeaderTip = Blocks::DB::instance()->headerChain().Tip();
const bool changed = Blocks::DB::instance()->appendHeader(index); // Processes that the block actually is invalid.
auto tip = Blocks::DB::instance()->headerChain().Tip();
if (changed && currentHeaderTip != tip) {
logCritical(Log::BlockValidation).nospace() << "new best header=" << *tip->phashBlock << " height=" << tip->nHeight;
logInfo(Log::BlockValidation) << "Header-reorg detected. Old-tip" << *currentHeaderTip->phashBlock << "@" << currentHeaderTip->nHeight;
prepareChain();
}
2018-01-15 15:26:12 +00:00
}
}
if (state->m_originatingNodeId >= 0) {
LOCK(cs_main);
if (state->errorCode < 0xFF)
queueRejectMessage(state->m_originatingNodeId, state->blockId,
2018-01-15 15:26:12 +00:00
static_cast<std::uint8_t>(state->errorCode), state->error);
if (state->m_onResultFlags & Validation::PunishBadNode)
Misbehaving(state->m_originatingNodeId, state->punishment);
}
// TODO rememeber to ignore this blockhash in the 'recently failed' list
}
2026-05-18 11:55:31 +02:00
void ValidationEnginePrivate::nodeBanned(int nodeId)
{
assert(strand.running_in_this_thread());
auto i = orphanBlocks.begin();
int count = 0;
while (i != orphanBlocks.end()) {
auto orphan = *i;
if (orphan->m_originatingNodeId == nodeId) {
i = orphanBlocks.erase(i);
++count;
}
else {
++i;
}
}
if (count)
logCritical(Log::BlockValidation) << "cleared" << count << "orphans that originated from peer:" << nodeId;
}
2018-01-15 15:26:12 +00:00
/*
* The 'main chain' is determined by the Blocks::DB::headersChain()
* This method does nothing more than update the real chain to remove blocks that are
* no longer on the headersChain (due to reorgs, mostly).
*/
void ValidationEnginePrivate::prepareChain()
{
if (blockchain->Height() <= 0)
return;
if (Blocks::DB::instance()->headerChain().Contains(blockchain->Tip()))
return;
2019-06-11 22:17:09 +02:00
DEBUGBV << "PrepareChain actually has work to do!";
2018-01-15 15:26:12 +00:00
2021-11-02 10:18:24 +01:00
std::vector<Block> revertedBlocks;
2018-01-15 15:26:12 +00:00
CBlockIndex *oldTip = blockchain->Tip();
2018-01-15 15:26:12 +00:00
LOCK(mempool->cs);
while (!Blocks::DB::instance()->headerChain().Contains(blockchain->Tip())) {
CBlockIndex *index = blockchain->Tip();
2018-10-13 21:28:53 +02:00
logInfo(Log::BlockValidation) << "Removing (rollback) chain tip at" << index->nHeight << index->GetBlockHash();
2021-11-02 10:18:24 +01:00
Block block;
2018-01-15 15:26:12 +00:00
try {
block = Blocks::DB::instance()->loadBlock(index->GetBlockPos());
revertedBlocks.push_back(block);
block.findTransactions();
2018-01-15 15:26:12 +00:00
} catch (const std::runtime_error &error) {
logFatal(Log::BlockValidation) << "ERROR: Can't undo the tip because I can't find it on disk";
fatal(error.what());
}
if (block.size() == 0)
fatal("BlockValidationPrivate::prepareChainForBlock: got no block, can't continue.");
2020-11-18 22:08:00 +01:00
if (!disconnectTip(index))
2018-01-15 15:26:12 +00:00
fatal("Failed to disconnect block");
tip.store(index->pprev);
}
mempool->removeForReorg(blockchain->Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS);
if (!revertedBlocks.empty())
ValidationNotifier().chainReorged(oldTip, revertedBlocks);
2018-01-15 15:26:12 +00:00
if (revertedBlocks.size() > 3)
return;
2018-01-15 15:26:12 +00:00
// Add transactions. Only after we have flushed our removal of transactions from the UTXO view.
// Otherwise the mempool would object because they would be in conflict with themselves.
2023-12-21 15:13:56 +01:00
auto pool = std::make_shared<Streaming::BufferPool>();
2018-01-15 15:26:12 +00:00
for (int index = revertedBlocks.size() - 1; index >= 0; --index) {
2021-11-02 10:18:24 +01:00
Block block = revertedBlocks.at(index);
2018-01-15 15:26:12 +00:00
block.findTransactions();
for (size_t txIndex = 1; txIndex < block.transactions().size(); txIndex++) {
Tx tx = block.transactions().at(txIndex);
std::list<CTransaction> deps;
mempool->remove(tx.createOldTransaction(), deps, true);
std::shared_ptr<TxValidationState> state(new TxValidationState(me, tx, TxValidationState::FromMempool));
state->checkTransaction();
2020-12-24 14:13:43 +01:00
for (const CTransaction &tx2 : deps) {// dependent transactions
2023-12-21 15:13:56 +01:00
state.reset(new TxValidationState(me, Tx::fromOldTransaction(tx2, pool), TxValidationState::FromMempool));
2018-01-15 15:26:12 +00:00
state->checkTransaction();
}
// Let wallets know transactions went from 1-confirmed to
// 0-confirmed or conflicted:
2021-02-18 16:03:01 +01:00
ValidationNotifier().syncTransaction(tx.createOldTransaction());
ValidationNotifier().syncTx(tx);
2018-01-15 15:26:12 +00:00
}
}
mempool->AddTransactionsUpdated(1);
2018-02-12 14:15:24 +01:00
LimitMempoolSize(*mempool, GetArg("-maxmempool", Settings::DefaultMaxMempoolSize) * 1000000, GetArg("-mempoolexpiry", Settings::DefaultMempoolExpiry) * 60 * 60);
2018-01-15 15:26:12 +00:00
}
void ValidationEnginePrivate::fatal(const char *error)
{
logFatal(Log::Bitcoin) << "***" << error;
StartShutdown();
throw std::runtime_error("App stopping, killing task");
}
void ValidationEnginePrivate::blockLanded(ProcessingType type)
{
std::unique_lock<decltype(lock)> waitLock(lock);
if (type == CheckingHeader)
headersInFlight.fetch_sub(1);
2018-01-15 15:26:12 +00:00
else
blocksInFlight.fetch_sub(1);
2018-01-15 15:26:12 +00:00
DEBUGBV << "headers:" << headersInFlight << "blocks:" << blocksInFlight << "orphans" << orphanBlocks.size();
waitVariable.notify_all();
if (!shuttingDown)
strand.post(std::bind(&ValidationEnginePrivate::findMoreJobs, me.lock()), std::allocator<void>());
2018-01-15 15:26:12 +00:00
}
void ValidationEnginePrivate::findMoreJobs()
{
assert(strand.running_in_this_thread());
DEBUGBV << "last scheduled:" << lastFullBlockScheduled;
if (shuttingDown || engineType == Validation::SkipAutoBlockProcessing)
return;
if (lastFullBlockScheduled == -1)
2018-10-21 21:31:56 +02:00
lastFullBlockScheduled = std::max(0, blockchain->Height());
2018-01-15 15:26:12 +00:00
while (true) {
CBlockIndex *index = Blocks::DB::instance()->headerChain()[lastFullBlockScheduled + 1];
DEBUGBV << " next:" << index;
2026-04-11 14:38:33 +02:00
if (index) DEBUGBV << " \\= " << index->GetBlockHash() << "has data:" << (bool) (index->nStatus & BLOCK_HAVE_DATA);
2018-01-15 15:26:12 +00:00
if (!(index && (index->nStatus & BLOCK_HAVE_DATA)))
return;
assert(index->pprev);
assert(index->nHeight == lastFullBlockScheduled + 1);
int currentCount = blocksInFlight.load();
if (currentCount >= blocksInFlightLimit())
return;
int newCount = currentCount + 1;
if (!blocksInFlight.compare_exchange_weak(currentCount, newCount, std::memory_order_relaxed, std::memory_order_relaxed))
continue;
// If we have 1008 validated headers on top of the block, turn off loads of validation of the actual block.
2021-03-09 16:45:16 +01:00
const bool isRecentBlock = index->nHeight + 1008 > Blocks::DB::instance()->headerChain().Height();
int onResultFlags = isRecentBlock ? Validation::ForwardGoodToPeers : 0;
2018-01-15 15:26:12 +00:00
if ((index->nStatus & BLOCK_HAVE_UNDO) == 0)
onResultFlags |= Validation::SaveGoodToDisk;
2021-11-02 10:18:24 +01:00
std::shared_ptr<BlockValidationState> state = std::make_shared<BlockValidationState>(me, Block(), onResultFlags);
2018-01-15 15:26:12 +00:00
state->m_blockIndex = index;
state->flags = tipFlags;
2018-01-15 15:26:12 +00:00
state->m_blockPos = index->GetBlockPos();
try {
state->load();
if (state->m_block.size() <= 90)
throw std::runtime_error("Expected full block");
2018-01-15 15:26:12 +00:00
} catch (const std::runtime_error &e) {
logWarning(Log::BlockValidation) << "Failed to load block" << state->m_blockPos << "got exception:" << e;
index->nStatus ^= BLOCK_HAVE_DATA; // obviously not...
return;
}
2021-03-15 12:54:58 +01:00
state->m_fetchFees = isRecentBlock || fetchFeeForMetaBlocks;
state->flags.enableValidation = isRecentBlock && !spvValidationMode;
2018-01-15 15:26:12 +00:00
state->m_validationStatus = BlockValidationState::BlockValidHeader | BlockValidationState::BlockValidTree;
state->m_checkingHeader = false;
blocksBeingValidated.insert(std::make_pair(state->blockId, state));
2018-01-15 15:26:12 +00:00
auto iter = blocksBeingValidated.find(state->m_block.previousBlockId());
if (iter != blocksBeingValidated.end()) {
iter->second->m_chainChildren.push_back(state);
} else if (index->pprev->nChainTx != 0) {
state->m_validationStatus |= BlockValidationState::BlockValidParent;
}
DEBUGBV << "scheduling" << lastFullBlockScheduled + 1 << "for validation"
<< (state->flags.enableValidation ? "(full)" : "(shallow)") << "Blocks in flight:" << newCount;
2025-02-11 19:41:22 +01:00
boost::asio::post(Application::instance()->ioContext(),
std::bind(&BlockValidationState::checks2HaveParentHeaders, state));
2018-01-15 15:26:12 +00:00
++lastFullBlockScheduled;
}
}
2020-11-18 22:08:00 +01:00
bool ValidationEnginePrivate::disconnectTip(CBlockIndex *index, bool *userClean, bool *error)
2018-01-15 15:26:12 +00:00
{
assert(index);
assert(index->pprev);
2018-10-22 13:08:35 +02:00
assert(strand.running_in_this_thread());
2018-01-15 15:26:12 +00:00
CDiskBlockPos pos = index->GetUndoPos();
if (pos.IsNull()) {
logFatal(Log::BlockValidation) << "No undo data available to disconnectBlock";
if (error) *error = true;
return false;
}
FastUndoBlock blockUndoFast = Blocks::DB::instance()->loadUndoBlock(pos);
2018-01-15 15:26:12 +00:00
if (blockUndoFast.size() == 0) {
logFatal(Log::BlockValidation) << "Failed reading undo data";
if (error) *error = true;
return false;
}
UnspentOutputDatabase *utxo = mempool->utxo();
2019-04-21 21:42:01 +02:00
while (true) {
FastUndoBlock::Item item = blockUndoFast.nextItem();
if (!item.isValid())
break;
if (!item.isInsert())
utxo->insert(item.prevTxId, item.outputIndex, item.blockHeight, item.offsetInBlock);
}
blockUndoFast.restartStream();
2018-01-15 15:26:12 +00:00
bool clean = true;
while (true) {
FastUndoBlock::Item item = blockUndoFast.nextItem();
if (!item.isValid())
break;
if (item.isInsert()) {
if (!utxo->remove(item.prevTxId, item.outputIndex).isValid())
2018-01-15 15:26:12 +00:00
clean = false;
}
2018-01-15 15:26:12 +00:00
}
// move best block pointer to prevout block
utxo->blockFinished(index->pprev->nHeight, index->pprev->GetBlockHash());
2019-04-21 21:42:01 +02:00
blockchain->SetTip(index->pprev);
2018-01-15 15:26:12 +00:00
if (userClean) {
*userClean = clean;
return true;
}
return clean;
}
void ValidationEnginePrivate::setSpvValidationMode(bool spv)
{
spvValidationMode = spv;
if (spvValidationMode)
tipFlags.enableValidation = false;
}
2018-01-15 15:26:12 +00:00
//---------------------------------------------------------
ValidationFlags::ValidationFlags()
: strictPayToScriptHash(false),
enforceBIP34(false),
enableValidation(true),
2018-01-15 15:26:12 +00:00
scriptVerifyDerSig(false),
scriptVerifyLockTimeVerify(false),
scriptVerifySequenceVerify(false),
nLocktimeVerifySequence(false),
2018-11-02 22:28:50 +01:00
hf201708Active(false),
2018-11-08 13:57:22 +01:00
hf201805Active(false),
2019-04-17 16:00:22 +02:00
hf201811Active(false),
2019-10-08 13:51:01 +02:00
hf201905Active(false),
2020-04-10 17:28:02 +02:00
hf201911Active(false),
2025-04-12 12:11:01 +02:00
hf202005Active(false),
hf202205Active(false), // introspection opcodes, bigint
2026-05-09 08:29:15 +02:00
hf202305Active(false), // cashtokens, p2sh32, txversion, sighash_utxos
2025-04-12 12:11:01 +02:00
hf202405Active(false),
hf202505Active(false),
hf202605Active(false)
2018-01-15 15:26:12 +00:00
{
}
2019-04-17 23:21:14 +02:00
uint32_t ValidationFlags::scriptValidationFlags(bool requireStandard) const
2018-01-15 15:26:12 +00:00
{
uint32_t flags = strictPayToScriptHash ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE;
if (scriptVerifyDerSig)
flags |= SCRIPT_VERIFY_DERSIG;
if (scriptVerifyLockTimeVerify)
flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
if (scriptVerifySequenceVerify)
flags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
2018-11-02 22:28:50 +01:00
if (hf201708Active) {
2018-01-15 15:26:12 +00:00
flags |= SCRIPT_VERIFY_STRICTENC;
flags |= SCRIPT_ENABLE_SIGHASH_FORKID;
}
2018-11-08 13:57:22 +01:00
if (hf201811Active) {
flags |= SCRIPT_ENABLE_CHECKDATASIG;
flags |= SCRIPT_VERIFY_SIGPUSHONLY;
flags |= SCRIPT_VERIFY_CLEANSTACK;
2018-11-13 15:03:04 +01:00
flags |= SCRIPT_VERIFY_P2SH; // implied requirement by CLEANSTACK (normally present, but not in unit tests)
2018-11-08 13:57:22 +01:00
}
2019-04-17 16:20:05 +02:00
if (hf201905Active) {
2019-04-17 23:21:14 +02:00
if (!requireStandard)
flags |= SCRIPT_ALLOW_SEGWIT_RECOVERY;
flags |= SCRIPT_ENABLE_SCHNORR;
2019-04-17 16:20:05 +02:00
}
2019-10-10 20:51:34 +02:00
if (hf201911Active) {
2019-10-08 13:51:01 +02:00
flags |= SCRIPT_VERIFY_MINIMALDATA;
2019-10-10 20:51:34 +02:00
flags |= SCRIPT_ENABLE_SCHNORR_MULTISIG;
}
2020-04-10 17:28:02 +02:00
if (hf202005Active) {
flags |= SCRIPT_ENABLE_OP_REVERSEBYTES;
}
if (hf202205Active) {
2026-05-14 22:43:01 +02:00
flags |= SCRIPT_ENABLE_64_BIT_INTEGERS;
flags |= SCRIPT_ENABLE_NATIVE_INTROSPECTION;
}
2026-05-07 13:50:12 +02:00
if (hf202305Active) {
flags |= SCRIPT_ENABLE_P2SH_32;
2026-05-09 08:29:15 +02:00
flags |= SCRIPT_ENABLE_SIGHASH_UTXOS;
2026-05-14 08:15:38 +02:00
flags |= SCRIPT_ENABLE_CASHTOKENS;
2026-05-07 13:50:12 +02:00
}
if (hf202505Active) {
flags |= SCRIPT_ENABLE_MAY2025;
if (requireStandard)
flags |= SCRIPT_VM_LIMITS_STANDARD;
}
if (hf202605Active) {
flags |= SCRIPT_ENABLE_MAY2026;
}
2018-01-15 15:26:12 +00:00
return flags;
}
void ValidationFlags::updateForBlock(CBlockIndex *index)
2018-01-15 15:26:12 +00:00
{
if (index->pprev == nullptr) // skip for genesis block
return;
// BIP16 didn't become active until Apr 1 2012
const int64_t BIP16SwitchTime = 1333238400;
if (!strictPayToScriptHash && index->nTime >= BIP16SwitchTime)
strictPayToScriptHash = true; // mainnet: activates on block 173805
2018-01-15 15:26:12 +00:00
const auto &consensus = Params().GetConsensus();
if (!enforceBIP34 && index->nHeight >= consensus.BIP34Height && consensus.BIP34Height > 0)
enforceBIP34 = true;
2018-01-15 15:26:12 +00:00
// Start enforcing the DERSIG (BIP66) rules
// Originally this was for block.nVersion=3 blocks, when 75% of the network has upgraded
// now we just hardcode the height
if (!scriptVerifyDerSig && index->nHeight >= consensus.BIP66Height)
2018-01-15 15:26:12 +00:00
scriptVerifyDerSig = true;
// Start enforcing CHECKLOCKTIMEVERIFY (BIP65)
// Originally this was for block.nVersion=4 blocks, when 75% of the network has upgraded
// now we just hardcode the height
if (!scriptVerifyLockTimeVerify && index->nHeight >= consensus.BIP65Height)
2018-01-15 15:26:12 +00:00
scriptVerifyLockTimeVerify = true;
// Start enforcing BIP68 (sequence locks) and BIP112 (CHECKSEQUENCEVERIFY)
// This was originally using versionbits logic (bip9).
// now we just hardcode the height
if (!scriptVerifySequenceVerify && index->nHeight >= consensus.BIP68Height) {
scriptVerifySequenceVerify = true;
nLocktimeVerifySequence = true;
2018-01-15 15:26:12 +00:00
}
if (!hf201708Active && index->nHeight >= consensus.hf201708Height)
2018-11-02 22:28:50 +01:00
hf201708Active = true;
if (!hf201805Active && index->nHeight >= consensus.hf201805Height)
2018-11-02 22:28:50 +01:00
hf201805Active = true;
if (!hf201811Active && index->nHeight >= consensus.hf201811Height)
2018-11-08 13:57:22 +01:00
hf201811Active = true;
if (!hf201905Active && index->nHeight >= consensus.hf201905Height)
2019-04-17 16:00:22 +02:00
hf201905Active = true;
if (!hf201911Active && index->nHeight >= consensus.hf201911Height)
2019-10-08 13:51:01 +02:00
hf201911Active = true;
if (!hf202005Active && index->nHeight >= consensus.hf202005Height)
2020-04-10 17:28:02 +02:00
hf202005Active = true;
if (!hf202205Active && index->nHeight >= consensus.hf202205Height)
hf202205Active = true;
2025-04-12 12:11:01 +02:00
if (!hf202305Active && index->nHeight >= consensus.hf202305Height)
hf202305Active = true;
if (!hf202405Active && index->nHeight >= consensus.hf202405Height)
hf202405Active = true;
if (!hf202505Active && index->nHeight >= consensus.hf202505Height)
hf202505Active = true;
if (!hf202605Active && hf202505Active && index->GetMedianTimePast() >= consensus.hf202605ActivationTime)
hf202605Active = true;
2018-01-15 15:26:12 +00:00
}
/* TODO Expire orphans.
* Have a bool in the State object.
* Have a once-an-hour timer which walks over all orphans. Either set the bool, or delete the orphan if its bool is already set.
* Unset issuedWarningForVersion every so many hours.
*/
//---------------------------------------------------------
2021-11-02 10:18:24 +01:00
BlockValidationState::BlockValidationState(const std::weak_ptr<ValidationEnginePrivate> &parent, const Block &block, uint32_t onResultFlags, int originatingNodeId)
2018-01-15 15:26:12 +00:00
: m_block(block),
m_blockIndex(nullptr),
m_onResultFlags(static_cast<uint8_t>(onResultFlags)),
2018-01-15 15:26:12 +00:00
m_originatingNodeId(originatingNodeId),
m_txChunkLeftToStart(-1),
m_txChunkLeftToFinish(-1),
m_validationStatus(BlockValidityUnknown),
m_blockFees(0),
2020-04-12 23:48:32 +02:00
m_sigChecksCounted(0),
2018-01-15 15:26:12 +00:00
m_parent(parent)
{
auto p = parent.lock();
assert(p);
flags.enableValidation = !p->spvValidationMode;
if (block.hasHeader())
blockId = block.createHash();
assert(onResultFlags < 0x100);
2018-01-15 15:26:12 +00:00
}
BlockValidationState::~BlockValidationState()
{
std::shared_ptr<ValidationEnginePrivate> parent = m_parent.lock();
if (parent)
parent->blockLanded(m_checkingHeader ? ValidationEnginePrivate::CheckingHeader : ValidationEnginePrivate::CheckingBlock);
if (m_originatingNodeId != -1 && m_block.isFullBlock()) {
LOCK(cs_main);
MarkBlockAsReceived(m_block.createHash());
}
if (m_block.size() >= 80)
DEBUGBV << "Finished" << blockId;
2026-04-20 19:43:55 +02:00
if (m_blockIndex)
DEBUGBV << " + is valid:" << m_blockIndex->nStatus << m_blockIndex->IsValid(BLOCK_VALID_HEADER);
if (m_ownsIndex)
delete m_blockIndex;
2018-01-15 15:26:12 +00:00
}
void BlockValidationState::load()
{
#ifdef ENABLE_BENCHMARKS
int64_t start = GetTimeMicros();
#endif
m_block = Blocks::DB::instance()->loadBlock(m_blockPos);
#ifdef ENABLE_BENCHMARKS
int64_t end = GetTimeMicros();
auto parent = m_parent.lock();
if (parent)
parent->m_loadingTime.fetch_add(end - start);
#endif
if (m_block.hasHeader())
blockId = m_block.createHash();
2026-04-14 23:43:29 +02:00
DEBUGBV << "from" << m_blockPos.nFile << m_blockPos.nPos << "Load succeeded:" << blockId << '/' << m_block.size();
2018-01-15 15:26:12 +00:00
}
void BlockValidationState::blockFailed(int punishment, const std::string &error, Validation::RejectCodes code, bool corruptionPossible)
{
assert(punishment < 0x100);
assert(punishment >= 0);
this->punishment = static_cast<uint8_t>(punishment);
2018-01-15 15:26:12 +00:00
this->error = error;
errorCode = code;
isCorruptionPossible = corruptionPossible;
m_validationStatus.fetch_or(BlockInvalid);
auto validationSettings = m_settings.lock();
2018-10-22 13:08:35 +02:00
if (validationSettings)
2018-01-15 15:26:12 +00:00
validationSettings->error = error;
}
void BlockValidationState::signalChildren() const
{
2020-12-24 14:13:43 +01:00
for (const auto &child_weak : m_chainChildren) {
2018-01-15 15:26:12 +00:00
std::shared_ptr<BlockValidationState> child = child_weak.lock();
if (child.get()) {
2019-04-28 22:58:36 +02:00
assert(child->m_blockIndex->nHeight == m_blockIndex->nHeight + 1);
2018-01-15 15:26:12 +00:00
int status = child->m_validationStatus.load();
while (true) {
int newStatus = status | BlockValidationState::BlockValidParent;
assert(newStatus != status);
if (child->m_validationStatus.compare_exchange_weak(status, newStatus, std::memory_order_relaxed, std::memory_order_relaxed)) {
if (status & BlockValidationState::BlockValidChainHeaders)
2025-02-11 19:41:22 +01:00
boost::asio::post(Application::instance()->ioContext(),
std::bind(&BlockValidationState::updateUtxoAndStartValidation, child));
2018-01-15 15:26:12 +00:00
break;
}
}
}
}
}
void BlockValidationState::recursivelyMark(BlockValidationStatus value, RecursiveOption option)
{
if (option == AddFlag)
m_validationStatus.fetch_or(value);
else
m_validationStatus.fetch_and(0xFF^value);
2020-12-24 14:13:43 +01:00
for (const auto &child : m_chainChildren) {
2018-01-15 15:26:12 +00:00
std::shared_ptr<BlockValidationState> state = child.lock();
if (state)
state->recursivelyMark(value, option);
}
}
void BlockValidationState::finishUp()
{
std::shared_ptr<ValidationEnginePrivate> parent = m_parent.lock();
if (parent)
2025-02-11 19:41:22 +01:00
parent->strand.post(std::bind(&ValidationEnginePrivate::processNewBlock, parent, shared_from_this()), std::allocator<void>());
2018-01-15 15:26:12 +00:00
}
void BlockValidationState::checks1NoContext()
{
try {
if (m_block.size() == 0)
load();
} catch (const std::exception &e) {
2019-02-22 22:07:30 +01:00
logInfo(Log::BlockValidation) << "BlockValidationState: Failed to load block, ignoring. Error:" << e.what()
<< "File idx:" << m_blockPos.nFile << "pos:" << m_blockPos.nPos;
auto validationSettings = m_settings.lock();
if (validationSettings) {
validationSettings->error = std::string("Failed to load block. Error: ") + e.what();
validationSettings->markFinished();
}
2018-01-15 15:26:12 +00:00
return;
}
#ifdef ENABLE_BENCHMARKS
int64_t start2, end2, end, start; start2 = end2 = start = end = GetTimeMicros();
#endif
DEBUGBV << "Starting" << blockId << "CheckPOW:" << m_checkPow << "CheckMerkleRoot:" << m_checkMerkleRoot << "ValidityOnly:" << m_checkValidityOnly;
2018-01-15 15:26:12 +00:00
try { // These are checks that are independent of context.
// Check proof of work matches claimed amount
if (m_checkPow && !CheckProofOfWork(blockId, m_block.bits(), Params().GetConsensus()))
2018-01-15 15:26:12 +00:00
throw Exception("high-hash", 50);
// Check timestamp
if (m_block.timestamp() > GetAdjustedTime() + 2 * 60 * 60)
throw Exception("time-too-new");
#ifdef ENABLE_BENCHMARKS
start2 = end = GetTimeMicros();
#endif
// if this is a full block, test the transactions too.
if (m_block.isFullBlock() && m_checkTransactionValidity) {
m_block.findTransactions(); // find out if the block and its transactions are well formed and parsable.
if (m_checkMerkleRoot) { // Check the merkle root.
bool mutated;
2026-04-14 23:43:29 +02:00
uint256 hashMerkleRoot2 = BlockMerkleRoot(m_block, &mutated);
if (m_block.merkleRoot() != hashMerkleRoot2)
2018-01-15 15:26:12 +00:00
throw Exception("bad-txnmrklroot", Validation::InvalidNotFatal);
// Check for merkle tree malleability (CVE-2012-2459): repeating sequences
// of transactions in a block without affecting the merkle root of a block,
// while still invalidating it.
if (mutated)
throw Exception("bad-txns-duplicate", Validation::InvalidNotFatal);
}
// Size limits
2026-04-14 23:43:29 +02:00
if (m_block.transactions().empty()) {
2019-03-23 18:43:10 +01:00
logCritical(Log::BlockValidation) << "Block has no transactions, not even a coinbase. Rejecting";
2018-01-15 15:26:12 +00:00
throw Exception("bad-blk-length");
2019-03-23 18:43:10 +01:00
}
2018-01-15 15:26:12 +00:00
const std::int32_t blockSizeAcceptLimit = Policy::blockSizeAcceptLimit();
const std::int32_t blockSize = m_block.size();
if (blockSize > blockSizeAcceptLimit) {
const float punishment = (blockSize - blockSizeAcceptLimit) / float(blockSizeAcceptLimit);
2019-03-23 18:43:10 +01:00
logCritical(Log::BlockValidation) << "Block too large" << blockSize << ">" << blockSizeAcceptLimit;
throw Exception("bad-blk-length", Validation::RejectExceedsLimit, int(10 * punishment + 0.5f));
2018-01-15 15:26:12 +00:00
}
// All potential-corruption validation must be done before we do any
// transaction validation, as otherwise we may mark the header as invalid
// because we receive the wrong transactions for it.
2026-04-14 23:43:29 +02:00
assert(!m_block.transactions().empty());
2018-01-15 15:26:12 +00:00
// First transaction must be coinbase, the rest must not be
2026-04-14 23:43:29 +02:00
bool isCoinbase = true;
for (const Tx &tx : m_block.transactions()) {
Tx::Iterator iter(tx);
if (iter.next(Tx::PrevTxHash) == Tx::PrevTxHash) {
if (isCoinbase && !iter.uint256Data().IsNull())
throw Exception("bad-cb-missing");
if (!isCoinbase && iter.uint256Data().IsNull())
throw Exception("bad-cb-multiple");
}
// only one input allowed for the real coinbase.
if (isCoinbase && iter.next(Tx::PrevTxHash) == Tx::PrevTxHash)
throw Exception("bad-cb-missing");
isCoinbase = false;
2018-01-15 15:26:12 +00:00
}
// Check transactions
2026-04-14 23:43:29 +02:00
for (const Tx &tx : m_block.transactions()) {
2018-01-15 15:26:12 +00:00
Validation::checkTransaction(tx);
}
}
m_validationStatus.fetch_or(BlockValidHeader);
#ifdef ENABLE_BENCHMARKS
end2 = GetTimeMicros();
#endif
} catch (const Exception &ex) {
blockFailed(ex.punishment(), ex.what(), ex.rejectCode(), ex.corruptionPossible());
} catch (const std::runtime_error &ex) {
assert(false);
blockFailed(100, ex.what(), Validation::RejectInternal);
}
std::shared_ptr<ValidationEnginePrivate> parent = m_parent.lock();
if (parent) {
#ifdef ENABLE_BENCHMARKS
parent->m_headerCheckTime.fetch_add(end - start);
parent->m_basicValidityChecks.fetch_add(end2 - start2);
#endif
2025-02-11 19:41:22 +01:00
parent->strand.dispatch(std::bind(&ValidationEnginePrivate::blockHeaderValidated, parent, shared_from_this()), std::allocator<void>());
2018-01-15 15:26:12 +00:00
}
}
void BlockValidationState::checks2HaveParentHeaders()
{
assert(m_blockIndex);
assert(m_blockIndex->nHeight >= 0);
assert(m_block.isFullBlock());
DEBUGBV << m_blockIndex->nHeight << blockId;
2018-01-15 15:26:12 +00:00
#ifdef ENABLE_BENCHMARKS
int64_t start = GetTimeMicros();
#endif
2026-05-15 09:19:13 +02:00
flags.updateForBlock(m_blockIndex);
2018-01-15 15:26:12 +00:00
try {
m_block.findTransactions();
auto header = m_block.header();
// MutableBlock block = m_block.createOldBlock();
2018-01-15 15:26:12 +00:00
if (m_blockIndex->pprev) { // not genesis
const auto consensusParams = Params().GetConsensus();
// Check proof of work
if (header.nBits != CalculateNextWorkRequired(m_blockIndex->pprev, header, consensusParams))
2018-01-15 15:26:12 +00:00
throw Exception("bad-diffbits");
// Check timestamp against prev
if (header.blockTime() <= m_blockIndex->pprev->GetMedianTimePast())
2018-01-15 15:26:12 +00:00
throw Exception("time-too-old");
if (header.nVersion < 4 && flags.scriptVerifyLockTimeVerify) // reject incorrect block version.
2018-01-15 15:26:12 +00:00
throw Exception("bad-version", Validation::RejectObsolete);
}
else {
// This usecase can't happen, lets put this in code so we can avoid additional checks below.
// This silences static-code checkers.
assert(!flags.scriptVerifySequenceVerify);
}
2018-01-15 15:26:12 +00:00
// Check that all transactions are finalized
const int64_t nLockTimeCutoff = flags.scriptVerifySequenceVerify ? m_blockIndex->pprev->GetMedianTimePast() : header.blockTime();
for (const auto &tx : m_block.transactions()) {
if (!IsFinalTx(tx, m_blockIndex->nHeight, nLockTimeCutoff))
throw Exception("bad-txns-nonfinal");
2018-01-15 15:26:12 +00:00
}
// Enforce rule that the coinbase starts with serialized block height
if (flags.enforceBIP34) {
2018-01-15 15:26:12 +00:00
CScript expect = CScript() << m_blockIndex->nHeight;
Tx::Iterator iter(m_block);
auto type = iter.next(Tx::TxInScript);
assert(type == Tx::TxInScript); // should have been rejected ages ago if this fails
auto inScript = iter.byteData();
2026-04-21 00:39:52 +02:00
bool ok = inScript.size() >= (int) expect.size();
// this is typically 4 bytes, lets keep the code simple and readable
for (size_t i = 0; ok && i < expect.size(); ++i)
ok &= expect[i] == static_cast<uint8_t>(inScript[i]);
if (!ok)
2018-01-15 15:26:12 +00:00
throw Exception("bad-cb-height");
}
for (const auto &tx : m_block.transactions())
ValidationPrivate::checkContextualTransaction(tx, flags);
2018-01-15 15:26:12 +00:00
} catch (const Exception &e) {
blockFailed(e.punishment(), e.what(), e.rejectCode(), e.corruptionPossible());
finishUp();
return;
} catch (std::runtime_error &e) {
assert(false);
blockFailed(100, e.what(), Validation::RejectInternal);
finishUp();
return;
}
#ifdef ENABLE_BENCHMARKS
int64_t end = GetTimeMicros();
#endif
int status = m_validationStatus.load();
auto parent = m_parent.lock();
while (parent) {
#ifdef ENABLE_BENCHMARKS
parent->m_contextCheckTime.fetch_add(end - start);
2026-04-15 14:15:57 +02:00
start = end;
2018-01-15 15:26:12 +00:00
#endif
int newStatus = status | BlockValidChainHeaders;
if (m_validationStatus.compare_exchange_weak(status, newStatus, std::memory_order_relaxed, std::memory_order_relaxed)) {
if ((status & BlockValidParent) || (status & BlockInvalid)) { // we just added the last bit.
2025-02-11 19:41:22 +01:00
boost::asio::post(Application::instance()->ioContext(),
std::bind(&BlockValidationState::updateUtxoAndStartValidation, shared_from_this()));
2018-01-15 15:26:12 +00:00
} else {
2020-10-27 15:17:22 +01:00
assert(!m_checkValidityOnly); // why did we get here if there is no known parent...
DEBUGBV << " saving block for later, no parent yet" << blockId
2019-06-11 22:17:09 +02:00
<< '@' << m_blockIndex->nHeight << "parent:" << m_blockIndex->pprev->GetBlockHash();
2018-01-15 15:26:12 +00:00
}
return;
}
}
}
void BlockValidationState::updateUtxoAndStartValidation()
{
DEBUGBV << blockId;
2018-01-15 15:26:12 +00:00
assert(m_txChunkLeftToStart.load() < 0); // this method should get called only once
2018-11-13 14:59:31 +01:00
auto parent = m_parent.lock();
if (!parent)
return;
2018-01-15 15:26:12 +00:00
2019-04-28 22:58:36 +02:00
assert(parent->blockchain->Tip() == nullptr || parent->blockchain->Tip()->nHeight <= m_blockIndex->nHeight);
2018-01-15 15:26:12 +00:00
if (m_blockIndex->pprev == nullptr) { // genesis
finishUp();
return;
}
2018-01-15 15:26:12 +00:00
try {
2019-02-16 13:24:20 +01:00
assert (m_block.transactions().size() > 0);
2019-03-15 19:10:47 +01:00
// inserting all outputs that are created in this block first.
// we do this in a single thread since inserting massively parallel will just cause a huge overhead
// and we'd end up being no faster while competing for the scarce resources that are the UTXO DB
UnspentOutputDatabase::BlockData data;
data.blockHeight = m_blockIndex->nHeight;
data.outputs.reserve(m_block.transactions().size());
Tx::Iterator iter = Tx::Iterator(m_block);
int outputCount = 0, txIndex = 0;
uint256 prevTxHash;
while (true) {
const auto type = iter.next();
if (type == Tx::End) {
Tx tx = iter.prevTx();
const int offsetInBlock = tx.offsetInBlock(m_block);
assert(tx.isValid());
const uint256 txHash = tx.createHash();
if (flags.hf201811Active && txIndex > 1 && txHash.Compare(prevTxHash) <= 0)
throw Exception("tx-ordering-not-CTOR");
data.outputs.push_back(UnspentOutputDatabase::BlockData::TxOutputs(txHash, offsetInBlock, 0, outputCount - 1));
outputCount = 0;
if (flags.hf201811Active)
prevTxHash = txHash;
++txIndex;
if (iter.next() == Tx::End) // double end: last tx in block
break;
}
else if (iter.tag() == Tx::OutputValue) { // next output!
2019-05-15 19:55:18 +02:00
// if (iter.longData() == 0) logDebug(Log::BlockValidation) << "Output with zero value";
2019-03-15 19:10:47 +01:00
outputCount++;
}
}
2018-11-13 14:59:31 +01:00
2020-12-24 14:13:43 +01:00
int chunks;
2019-03-15 19:10:47 +01:00
if (m_checkValidityOnly) { // no UTXO interaction allowed.
chunks = 1;
for (auto tx : data.outputs) {
assert(tx.firstOutput == 0);
2019-03-17 13:20:09 +01:00
std::deque<std::pair<int, int> > outputs;
for (int i = 0; i <= tx.lastOutput; ++i) {
outputs.push_back(std::make_pair(i, tx.offsetInBlock));
2018-11-13 14:59:31 +01:00
}
2019-03-17 13:20:09 +01:00
DEBUGBV << "available in this block: " << tx.txid << tx.lastOutput;
m_txMap.insert(std::make_pair(tx.txid, outputs));
2018-11-13 14:59:31 +01:00
}
2019-03-15 19:10:47 +01:00
}
else {
2020-12-24 14:13:43 +01:00
int itemsPerChunk; // not used here.
2019-03-15 19:10:47 +01:00
calculateTxCheckChunks(chunks, itemsPerChunk);
2018-11-20 15:27:38 +01:00
#ifdef ENABLE_BENCHMARKS
int64_t start = GetTimeMicros();
#endif
parent->mempool->utxo()->insertAll(data);
#ifdef ENABLE_BENCHMARKS
int64_t end = GetTimeMicros();
parent->m_utxoTime.fetch_add(end - start);
#endif
2018-11-13 14:59:31 +01:00
}
2026-04-11 14:38:33 +02:00
DEBUGBV << "Starting block validation of" << txIndex << "transactions in" << chunks << "chunks";
2019-03-15 19:10:47 +01:00
m_txChunkLeftToFinish.store(chunks);
m_txChunkLeftToStart.store(chunks);
m_undoItems.resize(static_cast<size_t>(chunks));
m_perTxFees.resize(static_cast<size_t>(chunks));
2018-11-13 14:59:31 +01:00
for (int i = 0; i < chunks; ++i) {
2025-02-11 19:41:22 +01:00
boost::asio::post(Application::instance()->ioContext(),
std::bind(&BlockValidationState::checkSignaturesChunk, shared_from_this()));
2018-11-13 14:59:31 +01:00
}
} catch(const UTXOInternalError &ex) {
parent->fatal(ex.what());
2018-01-15 15:26:12 +00:00
} catch(const Exception &ex) {
blockFailed(ex.punishment(), ex.what(), ex.rejectCode(), ex.corruptionPossible());
finishUp();
} catch(const std::exception &ex) {
assert(false);
blockFailed(100, ex.what(), Validation::RejectInternal);
finishUp();
}
}
2019-03-15 19:10:47 +01:00
void BlockValidationState::checkSignaturesChunk()
2018-01-15 15:26:12 +00:00
{
#ifdef ENABLE_BENCHMARKS
int64_t start = GetTimeMicros();
2018-09-23 16:05:55 +02:00
int64_t utxoStart, utxoDuration = 0;
2018-01-15 15:26:12 +00:00
#endif
2018-11-08 18:26:22 +01:00
auto parent = m_parent.lock();
if (!parent)
return;
2018-11-08 18:26:22 +01:00
assert(parent->mempool);
UnspentOutputDatabase *utxo = parent->mempool->utxo();
assert(utxo);
const int totalTxCount = static_cast<int>(m_block.transactions().size());
2018-01-15 15:26:12 +00:00
2026-04-11 14:38:33 +02:00
const int chunkToStart = m_txChunkLeftToStart.fetch_sub(1) - 1;
2018-01-15 15:26:12 +00:00
assert(chunkToStart >= 0);
DEBUGBV << chunkToStart << blockId;
2018-01-15 15:26:12 +00:00
2020-12-24 14:13:43 +01:00
int itemsPerChunk;
2019-03-15 19:10:47 +01:00
if (m_checkValidityOnly) {
2020-12-24 14:13:43 +01:00
itemsPerChunk = totalTxCount;
2019-03-15 19:10:47 +01:00
} else {
2020-12-24 14:13:43 +01:00
int chunks = 0; // unused
2019-03-15 19:10:47 +01:00
calculateTxCheckChunks(chunks, itemsPerChunk);
}
2018-01-15 15:26:12 +00:00
bool blockValid = (m_validationStatus.load() & BlockInvalid) == 0;
int txIndex = itemsPerChunk * chunkToStart;
const int txMax = std::min(txIndex + itemsPerChunk, totalTxCount);
2020-04-12 23:48:32 +02:00
uint32_t chunkSigChecks = 0;
2021-01-20 19:21:53 +01:00
int64_t chunkFees = 0;
m_undoItems[static_cast<size_t>(chunkToStart)].reset(new std::deque<FastUndoBlock::Item>());
auto undoItems = m_undoItems[static_cast<size_t>(chunkToStart)].get();
std::deque<std::int32_t> *perTxFees = nullptr;
2021-03-15 12:54:58 +01:00
if (m_fetchFees) {
m_perTxFees[static_cast<size_t>(chunkToStart)].reset(new std::deque<std::int32_t>());
perTxFees = m_perTxFees[static_cast<size_t>(chunkToStart)].get();
}
2018-01-15 15:26:12 +00:00
try {
for (;blockValid && txIndex < txMax; ++txIndex) {
2021-01-20 19:21:53 +01:00
int64_t fees = 0;
2018-11-13 14:59:31 +01:00
Tx tx = m_block.transactions().at(static_cast<size_t>(txIndex));
const uint256 hash = tx.createHash();
2026-05-13 17:55:43 +02:00
std::vector<UnspentOutputData> unspents; // list of prev outputs
2018-11-13 14:59:31 +01:00
auto txIter = Tx::Iterator(tx);
auto inputs = Tx::findInputs(txIter);
if (txIndex == 0)
inputs.clear(); // skip inputs check for coinbase
std::vector<int> prevheights; // the height of each input
for (auto input : inputs) { // find inputs
2019-06-12 18:13:44 +02:00
#ifdef ENABLE_BENCHMARKS
utxoStart = GetTimeMicros();
#endif
UnspentOutput unspentOutput = utxo->find(input.txid, input.index);
#ifdef ENABLE_BENCHMARKS
utxoDuration += GetTimeMicros() - utxoStart;
#endif
bool validUtxo = unspentOutput.isValid();
bool validInterBlockSpent = validUtxo; // ONLY used when m_validityOnly is true!
if (!validUtxo && m_checkValidityOnly) {
// in the checkValidity case we don't touch the UTXO and as such some inter-block spending may
// give a false-positive. Check that using the m_txMap structure
auto ti = m_txMap.find(input.txid);
if (ti != m_txMap.end()) {
for (auto iter = ti->second.begin(); iter != ti->second.end(); ++iter) {
if (iter->first == input.index) {
// found index.
prevheights.push_back(m_blockIndex->nHeight);
2021-03-15 12:54:58 +01:00
if (m_fetchFees) { // prepare the unspents
2026-05-13 16:43:23 +02:00
assert(input.index >= 0);
2019-06-12 18:13:44 +02:00
Tx::Iterator prevTxIter(m_block, iter->second);
2026-05-14 08:09:17 +02:00
UnspentOutputData prevOut(prevTxIter.nextOutput(input.index));
2019-06-12 18:13:44 +02:00
unspents.push_back(prevOut);
}
validInterBlockSpent = true;
ti->second.erase(iter);
break;
}
}
if (!validInterBlockSpent)
DEBUGBV << "Found txid in m_txMap, but not the wanted output...";
}
}
if (!validUtxo && !validInterBlockSpent) {
logCritical(Log::BlockValidation) << "Rejecting block" << blockId << "due to missing inputs";
2019-06-12 18:13:44 +02:00
logInfo(Log::BlockValidation) << " + txid:" << tx.createHash() << "needs input:" << input.txid << input.index;
throw Exception("missing-inputs", 0);
}
if (m_checkValidityOnly && validUtxo) {
// we just checked the UTXO, but when m_checkValidityOnly is true
// the output is not removed from the UTXO, and as such we need a bit of extra code
// to detect double-spends.
auto ti = m_spentMap.find(input.txid);
if (ti != m_spentMap.end()) {
for (int index : ti->second) {
if (index == input.index) // already spent the UTXO!
throw Exception("missing-inputs", 0);
}
ti->second.push_back(input.index);
} else {
std::deque<int> spentIndex = { input.index };
m_spentMap.insert(std::make_pair(input.txid, spentIndex));
}
}
if (validUtxo) { // fill prevHeight and unspents from the UTXO
prevheights.push_back(unspentOutput.blockHeight());
2021-03-15 12:54:58 +01:00
if (flags.enableValidation || m_fetchFees) {
2026-05-14 22:10:35 +02:00
unspents.push_back(UnspentOutputData::fromUtxoDB(unspentOutput));
2019-06-12 18:13:44 +02:00
}
}
if (!m_checkValidityOnly) {
2018-11-13 14:59:31 +01:00
#ifdef ENABLE_BENCHMARKS
utxoStart = GetTimeMicros();
#endif
2019-06-12 18:13:44 +02:00
SpentOutput removed = utxo->remove(input.txid, input.index, unspentOutput.rmHint());
2018-11-13 14:59:31 +01:00
#ifdef ENABLE_BENCHMARKS
utxoDuration += GetTimeMicros() - utxoStart;
#endif
2019-06-12 18:13:44 +02:00
if (!removed.isValid()) {
logCritical(Log::BlockValidation) << "Rejecting block" << blockId << "due to deleted input";
2019-02-22 22:07:30 +01:00
logInfo(Log::BlockValidation) << " + txid:" << tx.createHash() << "needs input:" << input.txid << input.index;
2018-11-13 14:59:31 +01:00
throw Exception("missing-inputs", 0);
}
2019-06-12 18:13:44 +02:00
assert(input.index >= 0);
assert(removed.blockHeight > 0);
assert(removed.offsetInBlock > 80);
undoItems->push_back(FastUndoBlock::Item(input.txid, input.index,
removed.blockHeight, removed.offsetInBlock));
}
2018-01-15 15:26:12 +00:00
}
2018-11-13 14:59:31 +01:00
if (flags.enableValidation && txIndex > 0) {
CTransaction old = tx.createOldTransaction();
// Check that transaction is BIP68 final
int nLockTimeFlags = 0;
if (flags.nLocktimeVerifySequence)
nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE;
if (!SequenceLocks(old, nLockTimeFlags, &prevheights, *m_blockIndex))
throw Exception("bad-txns-nonfinal");
bool spendsCoinBase;
2020-04-12 23:48:32 +02:00
uint32_t sigChecks = 0;
2026-05-14 12:07:30 +02:00
ValidationPrivate::validateTransactionInputs(old, tx, unspents, m_blockIndex->nHeight, flags, fees,
2020-04-12 23:48:32 +02:00
sigChecks, spendsCoinBase, /* requireStandard */ false);
2026-05-14 15:21:25 +02:00
if (m_fetchFees)
perTxFees->push_back(fees);
2020-04-12 23:48:32 +02:00
chunkSigChecks += sigChecks;
2018-11-13 14:59:31 +01:00
chunkFees += fees;
}
2021-03-15 12:54:58 +01:00
else if (m_fetchFees && txIndex > 0) {
// calculate fees faster if we don't need to validate the script.
int64_t valueIn = 0;
for (const auto &unspent : unspents) {
2026-05-13 16:43:23 +02:00
assert(unspent.outputValue >= 0);
valueIn += unspent.outputValue;
2021-03-15 12:54:58 +01:00
}
int64_t valueOut = 0;
Tx::Iterator iter(tx);
while (iter.next(Tx::OutputValue) != Tx::End) {
assert(iter.longData() >= 0);
valueOut += iter.longData();
}
int64_t fee = valueIn - valueOut;
perTxFees->push_back(static_cast<int32_t>(fee));
chunkFees += fee;
}
2018-11-13 14:59:31 +01:00
if (!m_checkValidityOnly) {
2026-04-11 14:38:33 +02:00
// DEBUGBV << "add outputs from TX" << txIndex;
2019-04-21 21:42:01 +02:00
// Find the outputs added to the unspentOutputDB
2018-11-13 14:59:31 +01:00
int outputCount = 0;
auto content = txIter.tag();
while (content != Tx::End) {
if (content == Tx::OutputValue) {
2019-05-15 19:55:18 +02:00
// if (txIter.longData() == 0) logDebug(Log::BlockValidation) << "Output with zero value";
2019-04-21 21:42:01 +02:00
undoItems->push_back(FastUndoBlock::Item(hash, outputCount));
2018-11-13 14:59:31 +01:00
outputCount++;
}
2019-04-21 21:42:01 +02:00
content = txIter.next(Tx::OutputValue + Tx::End);
2018-11-13 14:59:31 +01:00
}
}
2018-01-15 15:26:12 +00:00
}
} catch(const UTXOInternalError &ex) {
parent->fatal(ex.what());
} catch (const Exception &e) {
2018-01-15 15:26:12 +00:00
DEBUGBV << "Failed validation due to" << e.what();
blockFailed(e.punishment(), e.what(), e.rejectCode(), e.corruptionPossible());
blockValid = false;
} catch (const std::runtime_error &e) {
2018-01-15 15:26:12 +00:00
DEBUGBV << "Failed validation due to" << e.what();
blockFailed(100, e.what(), Validation::RejectMalformed);
2018-01-15 15:26:12 +00:00
blockValid = false;
}
m_blockFees.fetch_add(chunkFees);
2020-04-12 23:48:32 +02:00
m_sigChecksCounted.fetch_add(chunkSigChecks );
2018-01-15 15:26:12 +00:00
#ifdef ENABLE_BENCHMARKS
int64_t end = GetTimeMicros();
2018-09-23 16:05:55 +02:00
if (blockValid) {
2020-04-13 15:10:13 +02:00
parent->m_validationTime.fetch_add(end - start - utxoDuration);
parent->m_utxoTime.fetch_add(utxoDuration);
2018-09-23 16:05:55 +02:00
}
2021-03-06 13:00:22 +01:00
logDebug(Log::BlockValidation) << "batch:" << chunkToStart << (end - start)/1000. << "ms" << "success so far:" << blockValid;
2018-01-15 15:26:12 +00:00
#endif
2026-04-11 14:38:33 +02:00
DEBUGBV.nospace() << "Finished my chunk. " << itemsPerChunk << "/" << totalTxCount << "txs. Valid:" << blockValid;
2018-01-15 15:26:12 +00:00
const int chunksLeft = m_txChunkLeftToFinish.fetch_sub(1) - 1;
if (chunksLeft <= 0) // I'm the last one to finish
2018-01-15 15:26:12 +00:00
finishUp();
}