Page MenuHomePhabricator

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index a25ea7012..c1f88086d 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1,2999 +1,3035 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <rpc/blockchain.h>
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <clientversion.h>
#include <coins.h>
#include <common/args.h>
#include <config.h>
#include <consensus/amount.h>
#include <consensus/params.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <hash.h>
#include <index/blockfilterindex.h>
#include <index/coinstatsindex.h>
#include <logging/timer.h>
#include <net.h>
#include <net_processing.h>
#include <node/blockstorage.h>
#include <node/coinstats.h>
#include <node/context.h>
#include <node/utxo_snapshot.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
#include <rpc/server_util.h>
#include <rpc/util.h>
#include <script/descriptor.h>
+#include <serialize.h>
#include <streams.h>
#include <txdb.h>
#include <txmempool.h>
#include <undo.h>
#include <util/check.h>
#include <util/fs.h>
#include <util/strencodings.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
#include <warnings.h>
#include <condition_variable>
#include <cstdint>
#include <memory>
#include <mutex>
using kernel::CCoinsStats;
using kernel::CoinStatsHashType;
using node::BlockManager;
using node::GetUTXOStats;
using node::NodeContext;
using node::SnapshotMetadata;
struct CUpdatedBlock {
BlockHash hash;
int height;
};
static GlobalMutex cs_blockchange;
static std::condition_variable cond_blockchange;
static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
/**
* Calculate the difficulty for a given block index.
*/
double GetDifficulty(const CBlockIndex *blockindex) {
CHECK_NONFATAL(blockindex);
int nShift = (blockindex->nBits >> 24) & 0xff;
double dDiff = double(0x0000ffff) / double(blockindex->nBits & 0x00ffffff);
while (nShift < 29) {
dDiff *= 256.0;
nShift++;
}
while (nShift > 29) {
dDiff /= 256.0;
nShift--;
}
return dDiff;
}
static int ComputeNextBlockAndDepth(const CBlockIndex *tip,
const CBlockIndex *blockindex,
const CBlockIndex *&next) {
next = tip->GetAncestor(blockindex->nHeight + 1);
if (next && next->pprev == blockindex) {
return tip->nHeight - blockindex->nHeight + 1;
}
next = nullptr;
return blockindex == tip ? 1 : -1;
}
static const CBlockIndex *ParseHashOrHeight(const UniValue &param,
ChainstateManager &chainman) {
LOCK(::cs_main);
CChain &active_chain = chainman.ActiveChain();
if (param.isNum()) {
const int height{param.getInt<int>()};
if (height < 0) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Target block height %d is negative", height));
}
const int current_tip{active_chain.Height()};
if (height > current_tip) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Target block height %d after current tip %d", height,
current_tip));
}
return active_chain[height];
} else {
const BlockHash hash{ParseHashV(param, "hash_or_height")};
const CBlockIndex *pindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
return pindex;
}
}
UniValue blockheaderToJSON(const CBlockIndex *tip,
const CBlockIndex *blockindex) {
// Serialize passed information without accessing chain state of the active
// chain!
// For performance reasons
AssertLockNotHeld(cs_main);
UniValue result(UniValue::VOBJ);
result.pushKV("hash", blockindex->GetBlockHash().GetHex());
const CBlockIndex *pnext;
int confirmations = ComputeNextBlockAndDepth(tip, blockindex, pnext);
result.pushKV("confirmations", confirmations);
result.pushKV("height", blockindex->nHeight);
result.pushKV("version", blockindex->nVersion);
result.pushKV("versionHex", strprintf("%08x", blockindex->nVersion));
result.pushKV("merkleroot", blockindex->hashMerkleRoot.GetHex());
result.pushKV("time", int64_t(blockindex->nTime));
result.pushKV("mediantime", int64_t(blockindex->GetMedianTimePast()));
result.pushKV("nonce", uint64_t(blockindex->nNonce));
result.pushKV("bits", strprintf("%08x", blockindex->nBits));
result.pushKV("difficulty", GetDifficulty(blockindex));
result.pushKV("chainwork", blockindex->nChainWork.GetHex());
result.pushKV("nTx", uint64_t(blockindex->nTx));
if (blockindex->pprev) {
result.pushKV("previousblockhash",
blockindex->pprev->GetBlockHash().GetHex());
}
if (pnext) {
result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex());
}
return result;
}
UniValue blockToJSON(BlockManager &blockman, const CBlock &block,
const CBlockIndex *tip, const CBlockIndex *blockindex,
bool txDetails) {
UniValue result = blockheaderToJSON(tip, blockindex);
result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION));
UniValue txs(UniValue::VARR);
if (txDetails) {
CBlockUndo blockUndo;
const bool is_not_pruned{
WITH_LOCK(::cs_main, return !blockman.IsBlockPruned(blockindex))};
const bool have_undo{is_not_pruned &&
blockman.UndoReadFromDisk(blockUndo, *blockindex)};
for (size_t i = 0; i < block.vtx.size(); ++i) {
const CTransactionRef &tx = block.vtx.at(i);
// coinbase transaction (i == 0) doesn't have undo data
const CTxUndo *txundo =
(have_undo && i) ? &blockUndo.vtxundo.at(i - 1) : nullptr;
UniValue objTx(UniValue::VOBJ);
TxToUniv(*tx, BlockHash(), objTx, true, RPCSerializationFlags(),
txundo);
txs.push_back(objTx);
}
} else {
for (const CTransactionRef &tx : block.vtx) {
txs.push_back(tx->GetId().GetHex());
}
}
result.pushKV("tx", txs);
return result;
}
static RPCHelpMan getblockcount() {
return RPCHelpMan{
"getblockcount",
"Returns the height of the most-work fully-validated chain.\n"
"The genesis block has height 0.\n",
{},
RPCResult{RPCResult::Type::NUM, "", "The current block count"},
RPCExamples{HelpExampleCli("getblockcount", "") +
HelpExampleRpc("getblockcount", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
return chainman.ActiveHeight();
},
};
}
static RPCHelpMan getbestblockhash() {
return RPCHelpMan{
"getbestblockhash",
"Returns the hash of the best (tip) block in the "
"most-work fully-validated chain.\n",
{},
RPCResult{RPCResult::Type::STR_HEX, "", "the block hash, hex-encoded"},
RPCExamples{HelpExampleCli("getbestblockhash", "") +
HelpExampleRpc("getbestblockhash", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
return chainman.ActiveTip()->GetBlockHash().GetHex();
},
};
}
void RPCNotifyBlockChange(const CBlockIndex *pindex) {
if (pindex) {
LOCK(cs_blockchange);
latestblock.hash = pindex->GetBlockHash();
latestblock.height = pindex->nHeight;
}
cond_blockchange.notify_all();
}
static RPCHelpMan waitfornewblock() {
return RPCHelpMan{
"waitfornewblock",
"Waits for a specific new block and returns useful info about it.\n"
"\nReturns the current block on timeout or exit.\n",
{
{"timeout", RPCArg::Type::NUM, RPCArg::Default{0},
"Time in milliseconds to wait for a response. 0 indicates no "
"timeout."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "The blockhash"},
{RPCResult::Type::NUM, "height", "Block height"},
}},
RPCExamples{HelpExampleCli("waitfornewblock", "1000") +
HelpExampleRpc("waitfornewblock", "1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
int timeout = 0;
if (!request.params[0].isNull()) {
timeout = request.params[0].getInt<int>();
}
CUpdatedBlock block;
{
WAIT_LOCK(cs_blockchange, lock);
block = latestblock;
if (timeout) {
cond_blockchange.wait_for(
lock, std::chrono::milliseconds(timeout),
[&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height != block.height ||
latestblock.hash != block.hash ||
!IsRPCRunning();
});
} else {
cond_blockchange.wait(
lock,
[&block]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height != block.height ||
latestblock.hash != block.hash ||
!IsRPCRunning();
});
}
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);
return ret;
},
};
}
static RPCHelpMan waitforblock() {
return RPCHelpMan{
"waitforblock",
"Waits for a specific new block and returns useful info about it.\n"
"\nReturns the current block on timeout or exit.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"Block hash to wait for."},
{"timeout", RPCArg::Type::NUM, RPCArg::Default{0},
"Time in milliseconds to wait for a response. 0 indicates no "
"timeout."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "The blockhash"},
{RPCResult::Type::NUM, "height", "Block height"},
}},
RPCExamples{HelpExampleCli("waitforblock",
"\"0000000000079f8ef3d2c688c244eb7a4570b24c9"
"ed7b4a8c619eb02596f8862\" 1000") +
HelpExampleRpc("waitforblock",
"\"0000000000079f8ef3d2c688c244eb7a4570b24c9"
"ed7b4a8c619eb02596f8862\", 1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
int timeout = 0;
BlockHash hash(ParseHashV(request.params[0], "blockhash"));
if (!request.params[1].isNull()) {
timeout = request.params[1].getInt<int>();
}
CUpdatedBlock block;
{
WAIT_LOCK(cs_blockchange, lock);
if (timeout) {
cond_blockchange.wait_for(
lock, std::chrono::milliseconds(timeout),
[&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.hash == hash || !IsRPCRunning();
});
} else {
cond_blockchange.wait(
lock,
[&hash]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.hash == hash || !IsRPCRunning();
});
}
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);
return ret;
},
};
}
static RPCHelpMan waitforblockheight() {
return RPCHelpMan{
"waitforblockheight",
"Waits for (at least) block height and returns the height and "
"hash\nof the current tip.\n"
"\nReturns the current block on timeout or exit.\n",
{
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"Block height to wait for."},
{"timeout", RPCArg::Type::NUM, RPCArg::Default{0},
"Time in milliseconds to wait for a response. 0 indicates no "
"timeout."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash", "The blockhash"},
{RPCResult::Type::NUM, "height", "Block height"},
}},
RPCExamples{HelpExampleCli("waitforblockheight", "100 1000") +
HelpExampleRpc("waitforblockheight", "100, 1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
int timeout = 0;
int height = request.params[0].getInt<int>();
if (!request.params[1].isNull()) {
timeout = request.params[1].getInt<int>();
}
CUpdatedBlock block;
{
WAIT_LOCK(cs_blockchange, lock);
if (timeout) {
cond_blockchange.wait_for(
lock, std::chrono::milliseconds(timeout),
[&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height >= height ||
!IsRPCRunning();
});
} else {
cond_blockchange.wait(
lock,
[&height]() EXCLUSIVE_LOCKS_REQUIRED(cs_blockchange) {
return latestblock.height >= height ||
!IsRPCRunning();
});
}
block = latestblock;
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("hash", block.hash.GetHex());
ret.pushKV("height", block.height);
return ret;
},
};
}
static RPCHelpMan syncwithvalidationinterfacequeue() {
return RPCHelpMan{
"syncwithvalidationinterfacequeue",
"Waits for the validation interface queue to catch up on everything "
"that was there when we entered this function.\n",
{},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("syncwithvalidationinterfacequeue", "") +
HelpExampleRpc("syncwithvalidationinterfacequeue", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
static RPCHelpMan getdifficulty() {
return RPCHelpMan{
"getdifficulty",
"Returns the proof-of-work difficulty as a multiple of the minimum "
"difficulty.\n",
{},
RPCResult{RPCResult::Type::NUM, "",
"the proof-of-work difficulty as a multiple of the minimum "
"difficulty."},
RPCExamples{HelpExampleCli("getdifficulty", "") +
HelpExampleRpc("getdifficulty", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
return GetDifficulty(chainman.ActiveTip());
},
};
}
static RPCHelpMan getblockfrompeer() {
return RPCHelpMan{
"getblockfrompeer",
"Attempt to fetch block from a given peer.\n"
"\nWe must have the header for this block, e.g. using submitheader.\n"
"The block will not have any undo data which can limit the usage of "
"the block data in a context where the undo data is needed.\n"
"Subsequent calls for the same block may cause the response from the "
"previous peer to be ignored.\n"
"\nReturns an empty JSON object if the request was successfully "
"scheduled.",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The block hash to try to fetch"},
{"peer_id", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The peer to fetch it from (see getpeerinfo for peer IDs)"},
},
RPCResult{RPCResult::Type::OBJ, "", /*optional=*/false, "", {}},
RPCExamples{HelpExampleCli("getblockfrompeer",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\" 0") +
HelpExampleRpc("getblockfrompeer",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\" 0")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
PeerManager &peerman = EnsurePeerman(node);
const BlockHash block_hash{
ParseHashV(request.params[0], "blockhash")};
const NodeId peer_id{request.params[1].getInt<int64_t>()};
const CBlockIndex *const index = WITH_LOCK(
cs_main,
return chainman.m_blockman.LookupBlockIndex(block_hash););
if (!index) {
throw JSONRPCError(RPC_MISC_ERROR, "Block header missing");
}
if (WITH_LOCK(::cs_main, return index->nStatus.hasData())) {
throw JSONRPCError(RPC_MISC_ERROR, "Block already downloaded");
}
if (const auto err{peerman.FetchBlock(config, peer_id, *index)}) {
throw JSONRPCError(RPC_MISC_ERROR, err.value());
}
return UniValue::VOBJ;
},
};
}
static RPCHelpMan getblockhash() {
return RPCHelpMan{
"getblockhash",
"Returns hash of block in best-block-chain at height provided.\n",
{
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The height index"},
},
RPCResult{RPCResult::Type::STR_HEX, "", "The block hash"},
RPCExamples{HelpExampleCli("getblockhash", "1000") +
HelpExampleRpc("getblockhash", "1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
const CChain &active_chain = chainman.ActiveChain();
int nHeight = request.params[0].getInt<int>();
if (nHeight < 0 || nHeight > active_chain.Height()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Block height out of range");
}
const CBlockIndex *pblockindex = active_chain[nHeight];
return pblockindex->GetBlockHash().GetHex();
},
};
}
static RPCHelpMan getblockheader() {
return RPCHelpMan{
"getblockheader",
"If verbose is false, returns a string that is serialized, hex-encoded "
"data for blockheader 'hash'.\n"
"If verbose is true, returns an Object with information about "
"blockheader <hash>.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The block hash"},
{"verbose", RPCArg::Type::BOOL, RPCArg::Default{true},
"true for a json object, false for the hex-encoded data"},
},
{
RPCResult{
"for verbose = true",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash",
"the block hash (same as provided)"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations, or -1 if the block is not "
"on the main chain"},
{RPCResult::Type::NUM, "height",
"The block height or index"},
{RPCResult::Type::NUM, "version", "The block version"},
{RPCResult::Type::STR_HEX, "versionHex",
"The block version formatted in hexadecimal"},
{RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
{RPCResult::Type::NUM_TIME, "time",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime",
"The median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "nonce", "The nonce"},
{RPCResult::Type::STR_HEX, "bits", "The bits"},
{RPCResult::Type::NUM, "difficulty", "The difficulty"},
{RPCResult::Type::STR_HEX, "chainwork",
"Expected number of hashes required to produce the "
"current chain"},
{RPCResult::Type::NUM, "nTx",
"The number of transactions in the block"},
{RPCResult::Type::STR_HEX, "previousblockhash",
/* optional */ true,
"The hash of the previous block (if available)"},
{RPCResult::Type::STR_HEX, "nextblockhash",
/* optional */ true,
"The hash of the next block (if available)"},
}},
RPCResult{"for verbose=false", RPCResult::Type::STR_HEX, "",
"A string that is serialized, hex-encoded data for block "
"'hash'"},
},
RPCExamples{HelpExampleCli("getblockheader",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\"") +
HelpExampleRpc("getblockheader",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
BlockHash hash(ParseHashV(request.params[0], "hash"));
bool fVerbose = true;
if (!request.params[1].isNull()) {
fVerbose = request.params[1].get_bool();
}
const CBlockIndex *pblockindex;
const CBlockIndex *tip;
{
ChainstateManager &chainman =
EnsureAnyChainman(request.context);
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
tip = chainman.ActiveTip();
}
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
if (!fVerbose) {
CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
ssBlock << pblockindex->GetBlockHeader();
std::string strHex = HexStr(ssBlock);
return strHex;
}
return blockheaderToJSON(tip, pblockindex);
},
};
}
static CBlock GetBlockChecked(BlockManager &blockman,
const CBlockIndex *pblockindex) {
CBlock block;
{
LOCK(cs_main);
if (blockman.IsBlockPruned(pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR,
"Block not available (pruned data)");
}
}
if (!blockman.ReadBlockFromDisk(block, *pblockindex)) {
// Block not found on disk. This could be because we have the block
// header in our index but not yet have the block or did not accept the
// block. Or if the block was pruned right after we released the lock
// above.
throw JSONRPCError(RPC_MISC_ERROR, "Block not found on disk");
}
return block;
}
static CBlockUndo GetUndoChecked(BlockManager &blockman,
const CBlockIndex *pblockindex) {
CBlockUndo blockUndo;
{
LOCK(cs_main);
if (blockman.IsBlockPruned(pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR,
"Undo data not available (pruned data)");
}
}
if (!blockman.UndoReadFromDisk(blockUndo, *pblockindex)) {
throw JSONRPCError(RPC_MISC_ERROR, "Can't read undo data from disk");
}
return blockUndo;
}
static RPCHelpMan getblock() {
return RPCHelpMan{
"getblock",
"If verbosity is 0 or false, returns a string that is serialized, "
"hex-encoded data for block 'hash'.\n"
"If verbosity is 1 or true, returns an Object with information about "
"block <hash>.\n"
"If verbosity is 2, returns an Object with information about block "
"<hash> and information about each transaction.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The block hash"},
{"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1},
"0 for hex-encoded data, 1 for a json object, and 2 for json "
"object with transaction data",
RPCArgOptions{.skip_type_check = true}},
},
{
RPCResult{"for verbosity = 0", RPCResult::Type::STR_HEX, "",
"A string that is serialized, hex-encoded data for block "
"'hash'"},
RPCResult{
"for verbosity = 1",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "hash",
"the block hash (same as provided)"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations, or -1 if the block is not "
"on the main chain"},
{RPCResult::Type::NUM, "size", "The block size"},
{RPCResult::Type::NUM, "height",
"The block height or index"},
{RPCResult::Type::NUM, "version", "The block version"},
{RPCResult::Type::STR_HEX, "versionHex",
"The block version formatted in hexadecimal"},
{RPCResult::Type::STR_HEX, "merkleroot", "The merkle root"},
{RPCResult::Type::ARR,
"tx",
"The transaction ids",
{{RPCResult::Type::STR_HEX, "", "The transaction id"}}},
{RPCResult::Type::NUM_TIME, "time",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime",
"The median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "nonce", "The nonce"},
{RPCResult::Type::STR_HEX, "bits", "The bits"},
{RPCResult::Type::NUM, "difficulty", "The difficulty"},
{RPCResult::Type::STR_HEX, "chainwork",
"Expected number of hashes required to produce the chain "
"up to this block (in hex)"},
{RPCResult::Type::NUM, "nTx",
"The number of transactions in the block"},
{RPCResult::Type::STR_HEX, "previousblockhash",
/* optional */ true,
"The hash of the previous block (if available)"},
{RPCResult::Type::STR_HEX, "nextblockhash",
/* optional */ true,
"The hash of the next block (if available)"},
}},
RPCResult{"for verbosity = 2",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ELISION, "",
"Same output as verbosity = 1"},
{RPCResult::Type::ARR,
"tx",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::ELISION, "",
"The transactions in the format of the "
"getrawtransaction RPC. Different from "
"verbosity = 1 \"tx\" result"},
{RPCResult::Type::STR_AMOUNT, "fee",
"The transaction fee in " +
Currency::get().ticker +
", omitted if block undo data is not "
"available"},
}},
}},
}},
},
RPCExamples{
HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d"
"214adbda81d7e2a3dd146f6ed09\"") +
HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d"
"214adbda81d7e2a3dd146f6ed09\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
BlockHash hash(ParseHashV(request.params[0], "blockhash"));
int verbosity = 1;
if (!request.params[1].isNull()) {
if (request.params[1].isNum()) {
verbosity = request.params[1].getInt<int>();
} else {
verbosity = request.params[1].get_bool() ? 1 : 0;
}
}
const CBlockIndex *pblockindex;
const CBlockIndex *tip;
ChainstateManager &chainman = EnsureAnyChainman(request.context);
{
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
tip = chainman.ActiveTip();
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
}
const CBlock block =
GetBlockChecked(chainman.m_blockman, pblockindex);
if (verbosity <= 0) {
CDataStream ssBlock(SER_NETWORK,
PROTOCOL_VERSION | RPCSerializationFlags());
ssBlock << block;
std::string strHex = HexStr(ssBlock);
return strHex;
}
return blockToJSON(chainman.m_blockman, block, tip, pblockindex,
verbosity >= 2);
},
};
}
std::optional<int> GetPruneHeight(const BlockManager &blockman,
const CChain &chain) {
AssertLockHeld(::cs_main);
// Search for the last block missing block data or undo data. Don't let the
// search consider the genesis block, because the genesis block does not
// have undo data, but should not be considered pruned.
const CBlockIndex *first_block{chain[1]};
const CBlockIndex *chain_tip{chain.Tip()};
// If there are no blocks after the genesis block, or no blocks at all,
// nothing is pruned.
if (!first_block || !chain_tip) {
return std::nullopt;
}
// If the chain tip is pruned, everything is pruned.
if (!(chain_tip->nStatus.hasData() && chain_tip->nStatus.hasUndo())) {
return chain_tip->nHeight;
}
// Get first block with data, after the last block without data.
// This is the start of the unpruned range of blocks.
const CBlockIndex *first_unpruned{CHECK_NONFATAL(
blockman.GetFirstBlock(*chain_tip,
/*status_test=*/[](const BlockStatus &status) {
return status.hasData() && status.hasUndo();
}))};
if (first_unpruned == first_block) {
// All blocks between first_block and chain_tip have data, so nothing is
// pruned.
return std::nullopt;
}
// Block before the first unpruned block is the last pruned block.
return CHECK_NONFATAL(first_unpruned->pprev)->nHeight;
}
static RPCHelpMan pruneblockchain() {
return RPCHelpMan{
"pruneblockchain",
"",
{
{"height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The block height to prune up to. May be set to a discrete "
"height, or to a " +
UNIX_EPOCH_TIME +
"\n"
" to prune blocks whose block time is at "
"least 2 hours older than the provided timestamp."},
},
RPCResult{RPCResult::Type::NUM, "", "Height of the last block pruned"},
RPCExamples{HelpExampleCli("pruneblockchain", "1000") +
HelpExampleRpc("pruneblockchain", "1000")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
if (!chainman.m_blockman.IsPruneMode()) {
throw JSONRPCError(
RPC_MISC_ERROR,
"Cannot prune blocks because node is not in prune mode.");
}
LOCK(cs_main);
Chainstate &active_chainstate = chainman.ActiveChainstate();
CChain &active_chain = active_chainstate.m_chain;
int heightParam = request.params[0].getInt<int>();
if (heightParam < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Negative block height.");
}
// Height value more than a billion is too high to be a block
// height, and too low to be a block time (corresponds to timestamp
// from Sep 2001).
if (heightParam > 1000000000) {
// Add a 2 hour buffer to include blocks which might have had
// old timestamps
const CBlockIndex *pindex = active_chain.FindEarliestAtLeast(
heightParam - TIMESTAMP_WINDOW, 0);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Could not find block with at least the "
"specified timestamp.");
}
heightParam = pindex->nHeight;
}
unsigned int height = (unsigned int)heightParam;
unsigned int chainHeight = (unsigned int)active_chain.Height();
if (chainHeight < config.GetChainParams().PruneAfterHeight()) {
throw JSONRPCError(RPC_MISC_ERROR,
"Blockchain is too short for pruning.");
} else if (height > chainHeight) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Blockchain is shorter than the attempted prune height.");
} else if (height > chainHeight - MIN_BLOCKS_TO_KEEP) {
LogPrint(BCLog::RPC,
"Attempt to prune blocks close to the tip. "
"Retaining the minimum number of blocks.\n");
height = chainHeight - MIN_BLOCKS_TO_KEEP;
}
PruneBlockFilesManual(active_chainstate, height);
return GetPruneHeight(chainman.m_blockman, active_chain)
.value_or(-1);
},
};
}
static CoinStatsHashType ParseHashType(const std::string &hash_type_input) {
if (hash_type_input == "hash_serialized") {
return CoinStatsHashType::HASH_SERIALIZED;
} else if (hash_type_input == "muhash") {
return CoinStatsHashType::MUHASH;
} else if (hash_type_input == "none") {
return CoinStatsHashType::NONE;
} else {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("%s is not a valid hash_type", hash_type_input));
}
}
static RPCHelpMan gettxoutsetinfo() {
return RPCHelpMan{
"gettxoutsetinfo",
"Returns statistics about the unspent transaction output set.\n"
"Note this call may take some time if you are not using "
"coinstatsindex.\n",
{
{"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized"},
"Which UTXO set hash should be calculated. Options: "
"'hash_serialized' (the legacy algorithm), 'muhash', 'none'."},
{"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED,
"The block hash or height of the target height (only available "
"with coinstatsindex).",
RPCArgOptions{.skip_type_check = true,
.type_str = {"", "string or numeric"}}},
{"use_index", RPCArg::Type::BOOL, RPCArg::Default{true},
"Use coinstatsindex, if available."},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "height",
"The current block height (index)"},
{RPCResult::Type::STR_HEX, "bestblock",
"The hash of the block at the tip of the chain"},
{RPCResult::Type::NUM, "txouts",
"The number of unspent transaction outputs"},
{RPCResult::Type::NUM, "bogosize",
"Database-independent, meaningless metric indicating "
"the UTXO set size"},
{RPCResult::Type::STR_HEX, "hash_serialized",
/* optional */ true,
"The serialized hash (only present if 'hash_serialized' "
"hash_type is chosen)"},
{RPCResult::Type::STR_HEX, "muhash", /* optional */ true,
"The serialized hash (only present if 'muhash' "
"hash_type is chosen)"},
{RPCResult::Type::NUM, "transactions",
"The number of transactions with unspent outputs (not "
"available when coinstatsindex is used)"},
{RPCResult::Type::NUM, "disk_size",
"The estimated size of the chainstate on disk (not "
"available when coinstatsindex is used)"},
{RPCResult::Type::STR_AMOUNT, "total_amount",
"The total amount"},
{RPCResult::Type::STR_AMOUNT, "total_unspendable_amount",
"The total amount of coins permanently excluded from the UTXO "
"set (only available if coinstatsindex is used)"},
{RPCResult::Type::OBJ,
"block_info",
"Info on amounts in the block at this block height (only "
"available if coinstatsindex is used)",
{{RPCResult::Type::STR_AMOUNT, "prevout_spent",
"Total amount of all prevouts spent in this block"},
{RPCResult::Type::STR_AMOUNT, "coinbase",
"Coinbase subsidy amount of this block"},
{RPCResult::Type::STR_AMOUNT, "new_outputs_ex_coinbase",
"Total amount of new outputs created by this block"},
{RPCResult::Type::STR_AMOUNT, "unspendable",
"Total amount of unspendable outputs created in this block"},
{RPCResult::Type::OBJ,
"unspendables",
"Detailed view of the unspendable categories",
{
{RPCResult::Type::STR_AMOUNT, "genesis_block",
"The unspendable amount of the Genesis block subsidy"},
{RPCResult::Type::STR_AMOUNT, "bip30",
"Transactions overridden by duplicates (no longer "
"possible with BIP30)"},
{RPCResult::Type::STR_AMOUNT, "scripts",
"Amounts sent to scripts that are unspendable (for "
"example OP_RETURN outputs)"},
{RPCResult::Type::STR_AMOUNT, "unclaimed_rewards",
"Fee rewards that miners did not claim in their "
"coinbase transaction"},
}}}},
}},
RPCExamples{
HelpExampleCli("gettxoutsetinfo", "") +
HelpExampleCli("gettxoutsetinfo", R"("none")") +
HelpExampleCli("gettxoutsetinfo", R"("none" 1000)") +
HelpExampleCli(
"gettxoutsetinfo",
R"("none" '"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"')") +
HelpExampleRpc("gettxoutsetinfo", "") +
HelpExampleRpc("gettxoutsetinfo", R"("none")") +
HelpExampleRpc("gettxoutsetinfo", R"("none", 1000)") +
HelpExampleRpc(
"gettxoutsetinfo",
R"("none", "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09")")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
UniValue ret(UniValue::VOBJ);
const CBlockIndex *pindex{nullptr};
const CoinStatsHashType hash_type{
request.params[0].isNull()
? CoinStatsHashType::HASH_SERIALIZED
: ParseHashType(request.params[0].get_str())};
bool index_requested =
request.params[2].isNull() || request.params[2].get_bool();
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
Chainstate &active_chainstate = chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk();
CCoinsView *coins_view;
BlockManager *blockman;
{
LOCK(::cs_main);
coins_view = &active_chainstate.CoinsDB();
blockman = &active_chainstate.m_blockman;
pindex = blockman->LookupBlockIndex(coins_view->GetBestBlock());
}
if (!request.params[1].isNull()) {
if (!g_coin_stats_index) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Querying specific block heights "
"requires coinstatsindex");
}
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"hash_serialized hash type cannot be "
"queried for a specific block");
}
pindex = ParseHashOrHeight(request.params[1], chainman);
}
if (index_requested && g_coin_stats_index) {
if (!g_coin_stats_index->BlockUntilSyncedToCurrentChain()) {
const IndexSummary summary{
g_coin_stats_index->GetSummary()};
// If a specific block was requested and the index has
// already synced past that height, we can return the data
// already even though the index is not fully synced yet.
if (pindex->nHeight > summary.best_block_height) {
throw JSONRPCError(
RPC_INTERNAL_ERROR,
strprintf(
"Unable to get data because coinstatsindex is "
"still syncing. Current height: %d",
summary.best_block_height));
}
}
}
const std::optional<CCoinsStats> maybe_stats = GetUTXOStats(
coins_view, *blockman, hash_type, node.rpc_interruption_point,
pindex, index_requested);
if (maybe_stats.has_value()) {
const CCoinsStats &stats = maybe_stats.value();
ret.pushKV("height", int64_t(stats.nHeight));
ret.pushKV("bestblock", stats.hashBlock.GetHex());
ret.pushKV("txouts", int64_t(stats.nTransactionOutputs));
ret.pushKV("bogosize", int64_t(stats.nBogoSize));
if (hash_type == CoinStatsHashType::HASH_SERIALIZED) {
ret.pushKV("hash_serialized",
stats.hashSerialized.GetHex());
}
if (hash_type == CoinStatsHashType::MUHASH) {
ret.pushKV("muhash", stats.hashSerialized.GetHex());
}
ret.pushKV("total_amount", stats.nTotalAmount);
if (!stats.index_used) {
ret.pushKV("transactions",
static_cast<int64_t>(stats.nTransactions));
ret.pushKV("disk_size", stats.nDiskSize);
} else {
ret.pushKV("total_unspendable_amount",
stats.total_unspendable_amount);
CCoinsStats prev_stats{};
if (pindex->nHeight > 0) {
const std::optional<CCoinsStats> maybe_prev_stats =
GetUTXOStats(coins_view, *blockman, hash_type,
node.rpc_interruption_point,
pindex->pprev, index_requested);
if (!maybe_prev_stats) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Unable to read UTXO set");
}
prev_stats = maybe_prev_stats.value();
}
UniValue block_info(UniValue::VOBJ);
block_info.pushKV(
"prevout_spent",
stats.total_prevout_spent_amount -
prev_stats.total_prevout_spent_amount);
block_info.pushKV("coinbase",
stats.total_coinbase_amount -
prev_stats.total_coinbase_amount);
block_info.pushKV(
"new_outputs_ex_coinbase",
stats.total_new_outputs_ex_coinbase_amount -
prev_stats.total_new_outputs_ex_coinbase_amount);
block_info.pushKV("unspendable",
stats.total_unspendable_amount -
prev_stats.total_unspendable_amount);
UniValue unspendables(UniValue::VOBJ);
unspendables.pushKV(
"genesis_block",
stats.total_unspendables_genesis_block -
prev_stats.total_unspendables_genesis_block);
unspendables.pushKV(
"bip30", stats.total_unspendables_bip30 -
prev_stats.total_unspendables_bip30);
unspendables.pushKV(
"scripts", stats.total_unspendables_scripts -
prev_stats.total_unspendables_scripts);
unspendables.pushKV(
"unclaimed_rewards",
stats.total_unspendables_unclaimed_rewards -
prev_stats.total_unspendables_unclaimed_rewards);
block_info.pushKV("unspendables", unspendables);
ret.pushKV("block_info", block_info);
}
} else {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Unable to read UTXO set");
}
return ret;
},
};
}
RPCHelpMan gettxout() {
return RPCHelpMan{
"gettxout",
"Returns details about an unspent transaction output.\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The transaction id"},
{"n", RPCArg::Type::NUM, RPCArg::Optional::NO, "vout number"},
{"include_mempool", RPCArg::Type::BOOL, RPCArg::Default{true},
"Whether to include the mempool. Note that an unspent output that "
"is spent in the mempool won't appear."},
},
{
RPCResult{"If the UTXO was not found", RPCResult::Type::NONE, "",
""},
RPCResult{
"Otherwise",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "bestblock",
"The hash of the block at the tip of the chain"},
{RPCResult::Type::NUM, "confirmations",
"The number of confirmations"},
{RPCResult::Type::STR_AMOUNT, "value",
"The transaction value in " + Currency::get().ticker},
{RPCResult::Type::OBJ,
"scriptPubKey",
"",
{
{RPCResult::Type::STR_HEX, "asm", ""},
{RPCResult::Type::STR_HEX, "hex", ""},
{RPCResult::Type::NUM, "reqSigs",
"Number of required signatures"},
{RPCResult::Type::STR_HEX, "type",
"The type, eg pubkeyhash"},
{RPCResult::Type::ARR,
"addresses",
"array of eCash addresses",
{{RPCResult::Type::STR, "address", "eCash address"}}},
}},
{RPCResult::Type::BOOL, "coinbase", "Coinbase or not"},
}},
},
RPCExamples{"\nGet unspent transactions\n" +
HelpExampleCli("listunspent", "") + "\nView the details\n" +
HelpExampleCli("gettxout", "\"txid\" 1") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("gettxout", "\"txid\", 1")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
LOCK(cs_main);
UniValue ret(UniValue::VOBJ);
TxId txid(ParseHashV(request.params[0], "txid"));
int n = request.params[1].getInt<int>();
COutPoint out(txid, n);
bool fMempool = true;
if (!request.params[2].isNull()) {
fMempool = request.params[2].get_bool();
}
Coin coin;
Chainstate &active_chainstate = chainman.ActiveChainstate();
CCoinsViewCache *coins_view = &active_chainstate.CoinsTip();
if (fMempool) {
const CTxMemPool &mempool = EnsureMemPool(node);
LOCK(mempool.cs);
CCoinsViewMemPool view(coins_view, mempool);
if (!view.GetCoin(out, coin) || mempool.isSpent(out)) {
return NullUniValue;
}
} else {
if (!coins_view->GetCoin(out, coin)) {
return NullUniValue;
}
}
const CBlockIndex *pindex =
active_chainstate.m_blockman.LookupBlockIndex(
coins_view->GetBestBlock());
ret.pushKV("bestblock", pindex->GetBlockHash().GetHex());
if (coin.GetHeight() == MEMPOOL_HEIGHT) {
ret.pushKV("confirmations", 0);
} else {
ret.pushKV("confirmations",
int64_t(pindex->nHeight - coin.GetHeight() + 1));
}
ret.pushKV("value", coin.GetTxOut().nValue);
UniValue o(UniValue::VOBJ);
ScriptPubKeyToUniv(coin.GetTxOut().scriptPubKey, o, true);
ret.pushKV("scriptPubKey", o);
ret.pushKV("coinbase", coin.IsCoinBase());
return ret;
},
};
}
static RPCHelpMan verifychain() {
return RPCHelpMan{
"verifychain",
"Verifies blockchain database.\n",
{
{"checklevel", RPCArg::Type::NUM,
RPCArg::DefaultHint{
strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL)},
strprintf("How thorough the block verification is:\n - %s",
Join(CHECKLEVEL_DOC, "\n- "))},
{"nblocks", RPCArg::Type::NUM,
RPCArg::DefaultHint{strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS)},
"The number of blocks to check."},
},
RPCResult{RPCResult::Type::BOOL, "",
"Verification finished successfully. If false, check "
"debug.log for reason."},
RPCExamples{HelpExampleCli("verifychain", "") +
HelpExampleRpc("verifychain", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const int check_level{request.params[0].isNull()
? DEFAULT_CHECKLEVEL
: request.params[0].getInt<int>()};
const int check_depth{request.params[1].isNull()
? DEFAULT_CHECKBLOCKS
: request.params[1].getInt<int>()};
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
Chainstate &active_chainstate = chainman.ActiveChainstate();
return CVerifyDB(chainman.GetNotifications())
.VerifyDB(active_chainstate,
active_chainstate.CoinsTip(), check_level,
check_depth) == VerifyDBResult::SUCCESS;
},
};
}
RPCHelpMan getblockchaininfo() {
return RPCHelpMan{
"getblockchaininfo",
"Returns an object containing various state info regarding blockchain "
"processing.\n",
{},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR, "chain",
"current network name (main, test, regtest)"},
{RPCResult::Type::NUM, "blocks",
"the height of the most-work fully-validated chain. The "
"genesis block has height 0"},
{RPCResult::Type::NUM, "headers",
"the current number of headers we have validated"},
{RPCResult::Type::STR, "bestblockhash",
"the hash of the currently best block"},
{RPCResult::Type::NUM, "difficulty", "the current difficulty"},
{RPCResult::Type::NUM_TIME, "time",
"The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime",
"The median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "verificationprogress",
"estimate of verification progress [0..1]"},
{RPCResult::Type::BOOL, "initialblockdownload",
"(debug information) estimate of whether this node is in "
"Initial Block Download mode"},
{RPCResult::Type::STR_HEX, "chainwork",
"total amount of work in active chain, in hexadecimal"},
{RPCResult::Type::NUM, "size_on_disk",
"the estimated size of the block and undo files on disk"},
{RPCResult::Type::BOOL, "pruned",
"if the blocks are subject to pruning"},
{RPCResult::Type::NUM, "pruneheight",
"lowest-height complete block stored (only present if pruning "
"is enabled)"},
{RPCResult::Type::BOOL, "automatic_pruning",
"whether automatic pruning is enabled (only present if "
"pruning is enabled)"},
{RPCResult::Type::NUM, "prune_target_size",
"the target size used by pruning (only present if automatic "
"pruning is enabled)"},
{RPCResult::Type::STR, "warnings",
"any network and blockchain warnings"},
}},
RPCExamples{HelpExampleCli("getblockchaininfo", "") +
HelpExampleRpc("getblockchaininfo", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const CChainParams &chainparams = config.GetChainParams();
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
Chainstate &active_chainstate = chainman.ActiveChainstate();
const CBlockIndex &tip{
*CHECK_NONFATAL(active_chainstate.m_chain.Tip())};
const int height{tip.nHeight};
UniValue obj(UniValue::VOBJ);
obj.pushKV("chain", chainparams.NetworkIDString());
obj.pushKV("blocks", height);
obj.pushKV("headers", chainman.m_best_header
? chainman.m_best_header->nHeight
: -1);
obj.pushKV("bestblockhash", tip.GetBlockHash().GetHex());
obj.pushKV("difficulty", GetDifficulty(&tip));
obj.pushKV("time", tip.GetBlockTime());
obj.pushKV("mediantime", tip.GetMedianTimePast());
obj.pushKV(
"verificationprogress",
GuessVerificationProgress(chainman.GetParams().TxData(), &tip));
obj.pushKV("initialblockdownload",
chainman.IsInitialBlockDownload());
obj.pushKV("chainwork", tip.nChainWork.GetHex());
obj.pushKV("size_on_disk",
chainman.m_blockman.CalculateCurrentUsage());
obj.pushKV("pruned", chainman.m_blockman.IsPruneMode());
if (chainman.m_blockman.IsPruneMode()) {
const auto prune_height{GetPruneHeight(
chainman.m_blockman, active_chainstate.m_chain)};
obj.pushKV("pruneheight",
prune_height ? prune_height.value() + 1 : 0);
const bool automatic_pruning{
chainman.m_blockman.GetPruneTarget() !=
BlockManager::PRUNE_TARGET_MANUAL};
obj.pushKV("automatic_pruning", automatic_pruning);
if (automatic_pruning) {
obj.pushKV("prune_target_size",
chainman.m_blockman.GetPruneTarget());
}
}
obj.pushKV("warnings", GetWarnings(false).original);
return obj;
},
};
}
/** Comparison function for sorting the getchaintips heads. */
struct CompareBlocksByHeight {
bool operator()(const CBlockIndex *a, const CBlockIndex *b) const {
// Make sure that unequal blocks with the same height do not compare
// equal. Use the pointers themselves to make a distinction.
if (a->nHeight != b->nHeight) {
return (a->nHeight > b->nHeight);
}
return a < b;
}
};
static RPCHelpMan getchaintips() {
return RPCHelpMan{
"getchaintips",
"Return information about all known tips in the block tree, including "
"the main chain as well as orphaned branches.\n",
{},
RPCResult{
RPCResult::Type::ARR,
"",
"",
{{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "height", "height of the chain tip"},
{RPCResult::Type::STR_HEX, "hash", "block hash of the tip"},
{RPCResult::Type::NUM, "branchlen",
"zero for main chain, otherwise length of branch connecting "
"the tip to the main chain"},
{RPCResult::Type::STR, "status",
"status of the chain, \"active\" for the main chain\n"
"Possible values for status:\n"
"1. \"invalid\" This branch contains at "
"least one invalid block\n"
"2. \"parked\" This branch contains at "
"least one parked block\n"
"3. \"headers-only\" Not all blocks for this "
"branch are available, but the headers are valid\n"
"4. \"valid-headers\" All blocks are available for "
"this branch, but they were never fully validated\n"
"5. \"valid-fork\" This branch is not part of "
"the active chain, but is fully validated\n"
"6. \"active\" This is the tip of the "
"active main chain, which is certainly valid"},
}}}},
RPCExamples{HelpExampleCli("getchaintips", "") +
HelpExampleRpc("getchaintips", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
CChain &active_chain = chainman.ActiveChain();
/**
* Idea: The set of chain tips is the active chain tip, plus orphan
* blocks which do not have another orphan building off of them.
* Algorithm:
* - Make one pass through BlockIndex(), picking out the orphan
* blocks, and also storing a set of the orphan block's pprev
* pointers.
* - Iterate through the orphan blocks. If the block isn't pointed
* to by another orphan, it is a chain tip.
* - Add the active chain tip
*/
std::set<const CBlockIndex *, CompareBlocksByHeight> setTips;
std::set<const CBlockIndex *> setOrphans;
std::set<const CBlockIndex *> setPrevs;
for (const auto &[_, block_index] : chainman.BlockIndex()) {
if (!active_chain.Contains(&block_index)) {
setOrphans.insert(&block_index);
setPrevs.insert(block_index.pprev);
}
}
for (std::set<const CBlockIndex *>::iterator it =
setOrphans.begin();
it != setOrphans.end(); ++it) {
if (setPrevs.erase(*it) == 0) {
setTips.insert(*it);
}
}
// Always report the currently active tip.
setTips.insert(active_chain.Tip());
/* Construct the output array. */
UniValue res(UniValue::VARR);
for (const CBlockIndex *block : setTips) {
UniValue obj(UniValue::VOBJ);
obj.pushKV("height", block->nHeight);
obj.pushKV("hash", block->phashBlock->GetHex());
const int branchLen =
block->nHeight - active_chain.FindFork(block)->nHeight;
obj.pushKV("branchlen", branchLen);
std::string status;
if (active_chain.Contains(block)) {
// This block is part of the currently active chain.
status = "active";
} else if (block->nStatus.isInvalid()) {
// This block or one of its ancestors is invalid.
status = "invalid";
} else if (block->nStatus.isOnParkedChain()) {
// This block or one of its ancestors is parked.
status = "parked";
} else if (!block->HaveNumChainTxs()) {
// This block cannot be connected because full block data
// for it or one of its parents is missing.
status = "headers-only";
} else if (block->IsValid(BlockValidity::SCRIPTS)) {
// This block is fully validated, but no longer part of the
// active chain. It was probably the active block once, but
// was reorganized.
status = "valid-fork";
} else if (block->IsValid(BlockValidity::TREE)) {
// The headers for this block are valid, but it has not been
// validated. It was probably never part of the most-work
// chain.
status = "valid-headers";
} else {
// No clue.
status = "unknown";
}
obj.pushKV("status", status);
res.push_back(obj);
}
return res;
},
};
}
static RPCHelpMan preciousblock() {
return RPCHelpMan{
"preciousblock",
"Treats a block as if it were received before others with the same "
"work.\n"
"\nA later preciousblock call can override the effect of an earlier "
"one.\n"
"\nThe effects of preciousblock are not retained across restarts.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to mark as precious"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("preciousblock", "\"blockhash\"") +
HelpExampleRpc("preciousblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
BlockHash hash(ParseHashV(request.params[0], "blockhash"));
CBlockIndex *pblockindex;
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
{
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
}
BlockValidationState state;
chainman.ActiveChainstate().PreciousBlock(state, pblockindex,
node.avalanche.get());
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
}
// Block to make sure wallet/indexers sync before returning
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
static RPCHelpMan invalidateblock() {
return RPCHelpMan{
"invalidateblock",
"Permanently marks a block as invalid, as if it violated a consensus "
"rule.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to mark as invalid"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("invalidateblock", "\"blockhash\"") +
HelpExampleRpc("invalidateblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const BlockHash hash(ParseHashV(request.params[0], "blockhash"));
BlockValidationState state;
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
CBlockIndex *pblockindex;
{
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
}
chainman.ActiveChainstate().InvalidateBlock(state, pblockindex);
if (state.IsValid()) {
chainman.ActiveChainstate().ActivateBestChain(
state, /*pblock=*/nullptr, node.avalanche.get());
}
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
// Block to make sure wallet/indexers sync before returning
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
RPCHelpMan parkblock() {
return RPCHelpMan{
"parkblock",
"Marks a block as parked.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to park"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("parkblock", "\"blockhash\"") +
HelpExampleRpc("parkblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const std::string strHash = request.params[0].get_str();
const BlockHash hash(uint256S(strHash));
BlockValidationState state;
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
Chainstate &active_chainstate = chainman.ActiveChainstate();
CBlockIndex *pblockindex = nullptr;
{
LOCK(cs_main);
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
if (active_chainstate.IsBlockAvalancheFinalized(pblockindex)) {
// Reset avalanche finalization if we park a finalized
// block.
active_chainstate.ClearAvalancheFinalizedBlock();
}
}
active_chainstate.ParkBlock(state, pblockindex);
if (state.IsValid()) {
active_chainstate.ActivateBestChain(state, /*pblock=*/nullptr,
node.avalanche.get());
}
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
}
// Block to make sure wallet/indexers sync before returning
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
static RPCHelpMan reconsiderblock() {
return RPCHelpMan{
"reconsiderblock",
"Removes invalidity status of a block, its ancestors and its"
"descendants, reconsider them for activation.\n"
"This can be used to undo the effects of invalidateblock.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to reconsider"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("reconsiderblock", "\"blockhash\"") +
HelpExampleRpc("reconsiderblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
const BlockHash hash(ParseHashV(request.params[0], "blockhash"));
{
LOCK(cs_main);
CBlockIndex *pblockindex =
chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
chainman.ActiveChainstate().ResetBlockFailureFlags(pblockindex);
}
BlockValidationState state;
chainman.ActiveChainstate().ActivateBestChain(
state, /*pblock=*/nullptr, node.avalanche.get());
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
}
// Block to make sure wallet/indexers sync before returning
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
RPCHelpMan unparkblock() {
return RPCHelpMan{
"unparkblock",
"Removes parked status of a block and its descendants, reconsider "
"them for activation.\n"
"This can be used to undo the effects of parkblock.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"the hash of the block to unpark"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{HelpExampleCli("unparkblock", "\"blockhash\"") +
HelpExampleRpc("unparkblock", "\"blockhash\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const std::string strHash = request.params[0].get_str();
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
const BlockHash hash(uint256S(strHash));
Chainstate &active_chainstate = chainman.ActiveChainstate();
{
LOCK(cs_main);
CBlockIndex *pblockindex =
chainman.m_blockman.LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
if (!pblockindex->nStatus.isOnParkedChain()) {
// Block to unpark is not parked so there is nothing to do.
return NullUniValue;
}
const CBlockIndex *tip = active_chainstate.m_chain.Tip();
if (tip) {
const CBlockIndex *ancestor =
LastCommonAncestor(tip, pblockindex);
if (active_chainstate.IsBlockAvalancheFinalized(ancestor)) {
// Only reset avalanche finalization if we unpark a
// block that might conflict with avalanche finalized
// blocks.
active_chainstate.ClearAvalancheFinalizedBlock();
}
}
active_chainstate.UnparkBlockAndChildren(pblockindex);
}
BlockValidationState state;
active_chainstate.ActivateBestChain(state, /*pblock=*/nullptr,
node.avalanche.get());
if (!state.IsValid()) {
throw JSONRPCError(RPC_DATABASE_ERROR, state.GetRejectReason());
}
// Block to make sure wallet/indexers sync before returning
SyncWithValidationInterfaceQueue();
return NullUniValue;
},
};
}
static RPCHelpMan getchaintxstats() {
return RPCHelpMan{
"getchaintxstats",
"Compute statistics about the total number and rate of transactions "
"in the chain.\n",
{
{"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{"one month"},
"Size of the window in number of blocks"},
{"blockhash", RPCArg::Type::STR_HEX,
RPCArg::DefaultHint{"chain tip"},
"The hash of the block that ends the window."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM_TIME, "time",
"The timestamp for the final block in the window, "
"expressed in " +
UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "txcount",
"The total number of transactions in the chain up to "
"that point"},
{RPCResult::Type::STR_HEX, "window_final_block_hash",
"The hash of the final block in the window"},
{RPCResult::Type::NUM, "window_final_block_height",
"The height of the final block in the window."},
{RPCResult::Type::NUM, "window_block_count",
"Size of the window in number of blocks"},
{RPCResult::Type::NUM, "window_tx_count",
"The number of transactions in the window. Only "
"returned if \"window_block_count\" is > 0"},
{RPCResult::Type::NUM, "window_interval",
"The elapsed time in the window in seconds. Only "
"returned if \"window_block_count\" is > 0"},
{RPCResult::Type::NUM, "txrate",
"The average rate of transactions per second in the "
"window. Only returned if \"window_interval\" is > 0"},
}},
RPCExamples{HelpExampleCli("getchaintxstats", "") +
HelpExampleRpc("getchaintxstats", "2016")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
const CBlockIndex *pindex;
// By default: 1 month
int blockcount =
30 * 24 * 60 * 60 /
config.GetChainParams().GetConsensus().nPowTargetSpacing;
if (request.params[1].isNull()) {
LOCK(cs_main);
pindex = chainman.ActiveTip();
} else {
BlockHash hash(ParseHashV(request.params[1], "blockhash"));
LOCK(cs_main);
pindex = chainman.m_blockman.LookupBlockIndex(hash);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
if (!chainman.ActiveChain().Contains(pindex)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Block is not in main chain");
}
}
CHECK_NONFATAL(pindex != nullptr);
if (request.params[0].isNull()) {
blockcount =
std::max(0, std::min(blockcount, pindex->nHeight - 1));
} else {
blockcount = request.params[0].getInt<int>();
if (blockcount < 0 ||
(blockcount > 0 && blockcount >= pindex->nHeight)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid block count: "
"should be between 0 and "
"the block's height - 1");
}
}
const CBlockIndex &past_block{*CHECK_NONFATAL(
pindex->GetAncestor(pindex->nHeight - blockcount))};
const int64_t nTimeDiff{pindex->GetMedianTimePast() -
past_block.GetMedianTimePast()};
const int nTxDiff =
pindex->GetChainTxCount() - past_block.GetChainTxCount();
UniValue ret(UniValue::VOBJ);
ret.pushKV("time", pindex->GetBlockTime());
ret.pushKV("txcount", pindex->GetChainTxCount());
ret.pushKV("window_final_block_hash",
pindex->GetBlockHash().GetHex());
ret.pushKV("window_final_block_height", pindex->nHeight);
ret.pushKV("window_block_count", blockcount);
if (blockcount > 0) {
ret.pushKV("window_tx_count", nTxDiff);
ret.pushKV("window_interval", nTimeDiff);
if (nTimeDiff > 0) {
ret.pushKV("txrate", double(nTxDiff) / nTimeDiff);
}
}
return ret;
},
};
}
template <typename T>
static T CalculateTruncatedMedian(std::vector<T> &scores) {
size_t size = scores.size();
if (size == 0) {
return T();
}
std::sort(scores.begin(), scores.end());
if (size % 2 == 0) {
return (scores[size / 2 - 1] + scores[size / 2]) / 2;
} else {
return scores[size / 2];
}
}
template <typename T> static inline bool SetHasKeys(const std::set<T> &set) {
return false;
}
template <typename T, typename Tk, typename... Args>
static inline bool SetHasKeys(const std::set<T> &set, const Tk &key,
const Args &...args) {
return (set.count(key) != 0) || SetHasKeys(set, args...);
}
// outpoint (needed for the utxo index) + nHeight + fCoinBase
static constexpr size_t PER_UTXO_OVERHEAD =
sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool);
static RPCHelpMan getblockstats() {
const auto &ticker = Currency::get().ticker;
return RPCHelpMan{
"getblockstats",
"Compute per block statistics for a given window. All amounts are "
"in " +
ticker +
".\n"
"It won't work for some heights with pruning.\n",
{
{"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO,
"The block hash or height of the target block",
RPCArgOptions{.skip_type_check = true,
.type_str = {"", "string or numeric"}}},
{"stats",
RPCArg::Type::ARR,
RPCArg::DefaultHint{"all values"},
"Values to plot (see result below)",
{
{"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"Selected statistic"},
{"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"Selected statistic"},
},
RPCArgOptions{.oneline_description = "stats"}},
},
RPCResult{
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "avgfee", "Average fee in the block"},
{RPCResult::Type::NUM, "avgfeerate",
"Average feerate (in satoshis per virtual byte)"},
{RPCResult::Type::NUM, "avgtxsize", "Average transaction size"},
{RPCResult::Type::STR_HEX, "blockhash",
"The block hash (to check for potential reorgs)"},
{RPCResult::Type::NUM, "height", "The height of the block"},
{RPCResult::Type::NUM, "ins",
"The number of inputs (excluding coinbase)"},
{RPCResult::Type::NUM, "maxfee", "Maximum fee in the block"},
{RPCResult::Type::NUM, "maxfeerate",
"Maximum feerate (in satoshis per virtual byte)"},
{RPCResult::Type::NUM, "maxtxsize", "Maximum transaction size"},
{RPCResult::Type::NUM, "medianfee",
"Truncated median fee in the block"},
{RPCResult::Type::NUM, "medianfeerate",
"Truncated median feerate (in " + ticker + " per byte)"},
{RPCResult::Type::NUM, "mediantime",
"The block median time past"},
{RPCResult::Type::NUM, "mediantxsize",
"Truncated median transaction size"},
{RPCResult::Type::NUM, "minfee", "Minimum fee in the block"},
{RPCResult::Type::NUM, "minfeerate",
"Minimum feerate (in satoshis per virtual byte)"},
{RPCResult::Type::NUM, "mintxsize", "Minimum transaction size"},
{RPCResult::Type::NUM, "outs", "The number of outputs"},
{RPCResult::Type::NUM, "subsidy", "The block subsidy"},
{RPCResult::Type::NUM, "time", "The block time"},
{RPCResult::Type::NUM, "total_out",
"Total amount in all outputs (excluding coinbase and thus "
"reward [ie subsidy + totalfee])"},
{RPCResult::Type::NUM, "total_size",
"Total size of all non-coinbase transactions"},
{RPCResult::Type::NUM, "totalfee", "The fee total"},
{RPCResult::Type::NUM, "txs",
"The number of transactions (including coinbase)"},
{RPCResult::Type::NUM, "utxo_increase",
"The increase/decrease in the number of unspent outputs"},
{RPCResult::Type::NUM, "utxo_size_inc",
"The increase/decrease in size for the utxo index (not "
"discounting op_return and similar)"},
}},
RPCExamples{
HelpExampleCli(
"getblockstats",
R"('"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"' '["minfeerate","avgfeerate"]')") +
HelpExampleCli("getblockstats",
R"(1000 '["minfeerate","avgfeerate"]')") +
HelpExampleRpc(
"getblockstats",
R"("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", ["minfeerate","avgfeerate"])") +
HelpExampleRpc("getblockstats",
R"(1000, ["minfeerate","avgfeerate"])")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
ChainstateManager &chainman = EnsureAnyChainman(request.context);
const CBlockIndex &pindex{*CHECK_NONFATAL(
ParseHashOrHeight(request.params[0], chainman))};
std::set<std::string> stats;
if (!request.params[1].isNull()) {
const UniValue stats_univalue = request.params[1].get_array();
for (unsigned int i = 0; i < stats_univalue.size(); i++) {
const std::string stat = stats_univalue[i].get_str();
stats.insert(stat);
}
}
const CBlock &block = GetBlockChecked(chainman.m_blockman, &pindex);
const CBlockUndo &blockUndo =
GetUndoChecked(chainman.m_blockman, &pindex);
// Calculate everything if nothing selected (default)
const bool do_all = stats.size() == 0;
const bool do_mediantxsize =
do_all || stats.count("mediantxsize") != 0;
const bool do_medianfee = do_all || stats.count("medianfee") != 0;
const bool do_medianfeerate =
do_all || stats.count("medianfeerate") != 0;
const bool loop_inputs =
do_all || do_medianfee || do_medianfeerate ||
SetHasKeys(stats, "utxo_size_inc", "totalfee", "avgfee",
"avgfeerate", "minfee", "maxfee", "minfeerate",
"maxfeerate");
const bool loop_outputs =
do_all || loop_inputs || stats.count("total_out");
const bool do_calculate_size =
do_mediantxsize || loop_inputs ||
SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize",
"maxtxsize");
const int64_t blockMaxSize = config.GetMaxBlockSize();
Amount maxfee = Amount::zero();
Amount maxfeerate = Amount::zero();
Amount minfee = MAX_MONEY;
Amount minfeerate = MAX_MONEY;
Amount total_out = Amount::zero();
Amount totalfee = Amount::zero();
int64_t inputs = 0;
int64_t maxtxsize = 0;
int64_t mintxsize = blockMaxSize;
int64_t outputs = 0;
int64_t total_size = 0;
int64_t utxo_size_inc = 0;
std::vector<Amount> fee_array;
std::vector<Amount> feerate_array;
std::vector<int64_t> txsize_array;
for (size_t i = 0; i < block.vtx.size(); ++i) {
const auto &tx = block.vtx.at(i);
outputs += tx->vout.size();
Amount tx_total_out = Amount::zero();
if (loop_outputs) {
for (const CTxOut &out : tx->vout) {
tx_total_out += out.nValue;
utxo_size_inc +=
GetSerializeSize(out, PROTOCOL_VERSION) +
PER_UTXO_OVERHEAD;
}
}
if (tx->IsCoinBase()) {
continue;
}
// Don't count coinbase's fake input
inputs += tx->vin.size();
// Don't count coinbase reward
total_out += tx_total_out;
int64_t tx_size = 0;
if (do_calculate_size) {
tx_size = tx->GetTotalSize();
if (do_mediantxsize) {
txsize_array.push_back(tx_size);
}
maxtxsize = std::max(maxtxsize, tx_size);
mintxsize = std::min(mintxsize, tx_size);
total_size += tx_size;
}
if (loop_inputs) {
Amount tx_total_in = Amount::zero();
const auto &txundo = blockUndo.vtxundo.at(i - 1);
for (const Coin &coin : txundo.vprevout) {
const CTxOut &prevoutput = coin.GetTxOut();
tx_total_in += prevoutput.nValue;
utxo_size_inc -=
GetSerializeSize(prevoutput, PROTOCOL_VERSION) +
PER_UTXO_OVERHEAD;
}
Amount txfee = tx_total_in - tx_total_out;
CHECK_NONFATAL(MoneyRange(txfee));
if (do_medianfee) {
fee_array.push_back(txfee);
}
maxfee = std::max(maxfee, txfee);
minfee = std::min(minfee, txfee);
totalfee += txfee;
Amount feerate = txfee / tx_size;
if (do_medianfeerate) {
feerate_array.push_back(feerate);
}
maxfeerate = std::max(maxfeerate, feerate);
minfeerate = std::min(minfeerate, feerate);
}
}
UniValue ret_all(UniValue::VOBJ);
ret_all.pushKV("avgfee",
block.vtx.size() > 1
? (totalfee / int((block.vtx.size() - 1)))
: Amount::zero());
ret_all.pushKV("avgfeerate", total_size > 0
? (totalfee / total_size)
: Amount::zero());
ret_all.pushKV("avgtxsize",
(block.vtx.size() > 1)
? total_size / (block.vtx.size() - 1)
: 0);
ret_all.pushKV("blockhash", pindex.GetBlockHash().GetHex());
ret_all.pushKV("height", (int64_t)pindex.nHeight);
ret_all.pushKV("ins", inputs);
ret_all.pushKV("maxfee", maxfee);
ret_all.pushKV("maxfeerate", maxfeerate);
ret_all.pushKV("maxtxsize", maxtxsize);
ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array));
ret_all.pushKV("medianfeerate",
CalculateTruncatedMedian(feerate_array));
ret_all.pushKV("mediantime", pindex.GetMedianTimePast());
ret_all.pushKV("mediantxsize",
CalculateTruncatedMedian(txsize_array));
ret_all.pushKV("minfee",
minfee == MAX_MONEY ? Amount::zero() : minfee);
ret_all.pushKV("minfeerate", minfeerate == MAX_MONEY
? Amount::zero()
: minfeerate);
ret_all.pushKV("mintxsize",
mintxsize == blockMaxSize ? 0 : mintxsize);
ret_all.pushKV("outs", outputs);
ret_all.pushKV("subsidy", GetBlockSubsidy(pindex.nHeight,
chainman.GetConsensus()));
ret_all.pushKV("time", pindex.GetBlockTime());
ret_all.pushKV("total_out", total_out);
ret_all.pushKV("total_size", total_size);
ret_all.pushKV("totalfee", totalfee);
ret_all.pushKV("txs", (int64_t)block.vtx.size());
ret_all.pushKV("utxo_increase", outputs - inputs);
ret_all.pushKV("utxo_size_inc", utxo_size_inc);
if (do_all) {
return ret_all;
}
UniValue ret(UniValue::VOBJ);
for (const std::string &stat : stats) {
const UniValue &value = ret_all[stat];
if (value.isNull()) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Invalid selected statistic %s", stat));
}
ret.pushKV(stat, value);
}
return ret;
},
};
}
namespace {
//! Search for a given set of pubkey scripts
static bool FindScriptPubKey(std::atomic<int> &scan_progress,
const std::atomic<bool> &should_abort,
int64_t &count, CCoinsViewCursor *cursor,
const std::set<CScript> &needles,
std::map<COutPoint, Coin> &out_results,
std::function<void()> &interruption_point) {
scan_progress = 0;
count = 0;
while (cursor->Valid()) {
COutPoint key;
Coin coin;
if (!cursor->GetKey(key) || !cursor->GetValue(coin)) {
return false;
}
if (++count % 8192 == 0) {
interruption_point();
if (should_abort) {
// allow to abort the scan via the abort reference
return false;
}
}
if (count % 256 == 0) {
// update progress reference every 256 item
const TxId &txid = key.GetTxId();
uint32_t high = 0x100 * *txid.begin() + *(txid.begin() + 1);
scan_progress = int(high * 100.0 / 65536.0 + 0.5);
}
if (needles.count(coin.GetTxOut().scriptPubKey)) {
out_results.emplace(key, coin);
}
cursor->Next();
}
scan_progress = 100;
return true;
}
} // namespace
/** RAII object to prevent concurrency issue when scanning the txout set */
static std::atomic<int> g_scan_progress;
static std::atomic<bool> g_scan_in_progress;
static std::atomic<bool> g_should_abort_scan;
class CoinsViewScanReserver {
private:
bool m_could_reserve;
public:
explicit CoinsViewScanReserver() : m_could_reserve(false) {}
bool reserve() {
CHECK_NONFATAL(!m_could_reserve);
if (g_scan_in_progress.exchange(true)) {
return false;
}
m_could_reserve = true;
return true;
}
~CoinsViewScanReserver() {
if (m_could_reserve) {
g_scan_in_progress = false;
}
}
};
static RPCHelpMan scantxoutset() {
const auto &ticker = Currency::get().ticker;
return RPCHelpMan{
"scantxoutset",
"Scans the unspent transaction output set for entries that match "
"certain output descriptors.\n"
"Examples of output descriptors are:\n"
" addr(<address>) Outputs whose scriptPubKey "
"corresponds to the specified address (does not include P2PK)\n"
" raw(<hex script>) Outputs whose scriptPubKey "
"equals the specified hex scripts\n"
" combo(<pubkey>) P2PK and P2PKH outputs for "
"the given pubkey\n"
" pkh(<pubkey>) P2PKH outputs for the given "
"pubkey\n"
" sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for "
"the given threshold and pubkeys\n"
"\nIn the above, <pubkey> either refers to a fixed public key in "
"hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
"or more path elements separated by \"/\", and optionally ending in "
"\"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"
"unhardened or hardened child keys.\n"
"In the latter case, a range needs to be specified by below if "
"different from 1000.\n"
"For more information on output descriptors, see the documentation in "
"the doc/descriptors.md file.\n",
{
{"action", RPCArg::Type::STR, RPCArg::Optional::NO,
"The action to execute\n"
" \"start\" for starting a "
"scan\n"
" \"abort\" for aborting the "
"current scan (returns true when abort was successful)\n"
" \"status\" for "
"progress report (in %) of the current scan"},
{"scanobjects",
RPCArg::Type::ARR,
RPCArg::Optional::OMITTED,
"Array of scan objects. Required for \"start\" action\n"
" Every scan object is either a "
"string descriptor or an object:",
{
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::OMITTED,
"An output descriptor"},
{
"",
RPCArg::Type::OBJ,
RPCArg::Optional::OMITTED,
"An object with output descriptor and metadata",
{
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO,
"An output descriptor"},
{"range", RPCArg::Type::RANGE, RPCArg::Default{1000},
"The range of HD chain indexes to explore (either "
"end or [begin,end])"},
},
},
},
RPCArgOptions{.oneline_description = "[scanobjects,...]"}},
},
{
RPCResult{"When action=='abort'", RPCResult::Type::BOOL, "", ""},
RPCResult{"When action=='status' and no scan is in progress",
RPCResult::Type::NONE, "", ""},
RPCResult{
"When action=='status' and scan is in progress",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "progress", "The scan progress"},
}},
RPCResult{
"When action=='start'",
RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::BOOL, "success",
"Whether the scan was completed"},
{RPCResult::Type::NUM, "txouts",
"The number of unspent transaction outputs scanned"},
{RPCResult::Type::NUM, "height",
"The current block height (index)"},
{RPCResult::Type::STR_HEX, "bestblock",
"The hash of the block at the tip of the chain"},
{RPCResult::Type::ARR,
"unspents",
"",
{
{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "txid",
"The transaction id"},
{RPCResult::Type::NUM, "vout", "The vout value"},
{RPCResult::Type::STR_HEX, "scriptPubKey",
"The script key"},
{RPCResult::Type::STR, "desc",
"A specialized descriptor for the matched "
"scriptPubKey"},
{RPCResult::Type::STR_AMOUNT, "amount",
"The total amount in " + ticker +
" of the unspent output"},
{RPCResult::Type::BOOL, "coinbase",
"Whether this is a coinbase output"},
{RPCResult::Type::NUM, "height",
"Height of the unspent transaction output"},
}},
}},
{RPCResult::Type::STR_AMOUNT, "total_amount",
"The total amount of all found unspent outputs in " +
ticker},
}},
},
RPCExamples{""},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
UniValue result(UniValue::VOBJ);
const auto action{self.Arg<std::string>("action")};
if (action == "status") {
CoinsViewScanReserver reserver;
if (reserver.reserve()) {
// no scan in progress
return NullUniValue;
}
result.pushKV("progress", g_scan_progress.load());
return result;
} else if (action == "abort") {
CoinsViewScanReserver reserver;
if (reserver.reserve()) {
// reserve was possible which means no scan was running
return false;
}
// set the abort flag
g_should_abort_scan = true;
return true;
} else if (action == "start") {
CoinsViewScanReserver reserver;
if (!reserver.reserve()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Scan already in progress, use action "
"\"abort\" or \"status\"");
}
if (request.params.size() < 2) {
throw JSONRPCError(RPC_MISC_ERROR,
"scanobjects argument is required for "
"the start action");
}
std::set<CScript> needles;
std::map<CScript, std::string> descriptors;
Amount total_in = Amount::zero();
// loop through the scan objects
for (const UniValue &scanobject :
request.params[1].get_array().getValues()) {
FlatSigningProvider provider;
auto scripts =
EvalDescriptorStringOrObject(scanobject, provider);
for (CScript &script : scripts) {
std::string inferred =
InferDescriptor(script, provider)->ToString();
needles.emplace(script);
descriptors.emplace(std::move(script),
std::move(inferred));
}
}
// Scan the unspent transaction output set for inputs
UniValue unspents(UniValue::VARR);
std::vector<CTxOut> input_txos;
std::map<COutPoint, Coin> coins;
g_should_abort_scan = false;
g_scan_progress = 0;
int64_t count = 0;
std::unique_ptr<CCoinsViewCursor> pcursor;
const CBlockIndex *tip;
NodeContext &node = EnsureAnyNodeContext(request.context);
{
ChainstateManager &chainman = EnsureChainman(node);
LOCK(cs_main);
Chainstate &active_chainstate = chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk();
pcursor = CHECK_NONFATAL(std::unique_ptr<CCoinsViewCursor>(
active_chainstate.CoinsDB().Cursor()));
tip = CHECK_NONFATAL(active_chainstate.m_chain.Tip());
}
bool res = FindScriptPubKey(
g_scan_progress, g_should_abort_scan, count, pcursor.get(),
needles, coins, node.rpc_interruption_point);
result.pushKV("success", res);
result.pushKV("txouts", count);
result.pushKV("height", tip->nHeight);
result.pushKV("bestblock", tip->GetBlockHash().GetHex());
for (const auto &it : coins) {
const COutPoint &outpoint = it.first;
const Coin &coin = it.second;
const CTxOut &txo = coin.GetTxOut();
input_txos.push_back(txo);
total_in += txo.nValue;
UniValue unspent(UniValue::VOBJ);
unspent.pushKV("txid", outpoint.GetTxId().GetHex());
unspent.pushKV("vout", int32_t(outpoint.GetN()));
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
unspent.pushKV("desc", descriptors[txo.scriptPubKey]);
unspent.pushKV("amount", txo.nValue);
unspent.pushKV("coinbase", coin.IsCoinBase());
unspent.pushKV("height", int32_t(coin.GetHeight()));
unspents.push_back(unspent);
}
result.pushKV("unspents", unspents);
result.pushKV("total_amount", total_in);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER,
strprintf("Invalid action '%s'", action));
}
return result;
},
};
}
static RPCHelpMan getblockfilter() {
return RPCHelpMan{
"getblockfilter",
"Retrieve a BIP 157 content filter for a particular block.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO,
"The hash of the block"},
{"filtertype", RPCArg::Type::STR, RPCArg::Default{"basic"},
"The type name of the filter"},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::STR_HEX, "filter",
"the hex-encoded filter data"},
{RPCResult::Type::STR_HEX, "header",
"the hex-encoded filter header"},
}},
RPCExamples{
HelpExampleCli("getblockfilter",
"\"00000000c937983704a73af28acdec37b049d214a"
"dbda81d7e2a3dd146f6ed09\" \"basic\"") +
HelpExampleRpc("getblockfilter",
"\"00000000c937983704a73af28acdec37b049d214adbda81d7"
"e2a3dd146f6ed09\", \"basic\"")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const BlockHash block_hash(
ParseHashV(request.params[0], "blockhash"));
std::string filtertype_name = "basic";
if (!request.params[1].isNull()) {
filtertype_name = request.params[1].get_str();
}
BlockFilterType filtertype;
if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Unknown filtertype");
}
BlockFilterIndex *index = GetBlockFilterIndex(filtertype);
if (!index) {
throw JSONRPCError(RPC_MISC_ERROR,
"Index is not enabled for filtertype " +
filtertype_name);
}
const CBlockIndex *block_index;
bool block_was_connected;
{
ChainstateManager &chainman =
EnsureAnyChainman(request.context);
LOCK(cs_main);
block_index = chainman.m_blockman.LookupBlockIndex(block_hash);
if (!block_index) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Block not found");
}
block_was_connected =
block_index->IsValid(BlockValidity::SCRIPTS);
}
bool index_ready = index->BlockUntilSyncedToCurrentChain();
BlockFilter filter;
uint256 filter_header;
if (!index->LookupFilter(block_index, filter) ||
!index->LookupFilterHeader(block_index, filter_header)) {
int err_code;
std::string errmsg = "Filter not found.";
if (!block_was_connected) {
err_code = RPC_INVALID_ADDRESS_OR_KEY;
errmsg += " Block was not connected to active chain.";
} else if (!index_ready) {
err_code = RPC_MISC_ERROR;
errmsg += " Block filters are still in the process of "
"being indexed.";
} else {
err_code = RPC_INTERNAL_ERROR;
errmsg += " This error is unexpected and indicates index "
"corruption.";
}
throw JSONRPCError(err_code, errmsg);
}
UniValue ret(UniValue::VOBJ);
ret.pushKV("filter", HexStr(filter.GetEncodedFilter()));
ret.pushKV("header", filter_header.GetHex());
return ret;
},
};
}
/**
* Serialize the UTXO set to a file for loading elsewhere.
*
* @see SnapshotMetadata
*/
static RPCHelpMan dumptxoutset() {
return RPCHelpMan{
"dumptxoutset",
"Write the serialized UTXO set to a file.\n",
{
{"path", RPCArg::Type::STR, RPCArg::Optional::NO,
"path to the output file. If relative, will be prefixed by "
"datadir."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "coins_written",
"the number of coins written in the snapshot"},
{RPCResult::Type::STR_HEX, "base_hash",
"the hash of the base of the snapshot"},
{RPCResult::Type::NUM, "base_height",
"the height of the base of the snapshot"},
{RPCResult::Type::STR, "path",
"the absolute path that the snapshot was written to"},
{RPCResult::Type::STR_HEX, "txoutset_hash",
"the hash of the UTXO set contents"},
{RPCResult::Type::NUM, "nchaintx",
"the number of transactions in the chain up to and "
"including the base block"},
}},
RPCExamples{HelpExampleCli("dumptxoutset", "utxo.dat")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
const ArgsManager &args{EnsureAnyArgsman(request.context)};
const fs::path path = fsbridge::AbsPathJoin(
args.GetDataDirNet(), fs::u8path(request.params[0].get_str()));
// Write to a temporary path and then move into `path` on completion
// to avoid confusion due to an interruption.
const fs::path temppath = fsbridge::AbsPathJoin(
args.GetDataDirNet(),
fs::u8path(request.params[0].get_str() + ".incomplete"));
if (fs::exists(path)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
path.u8string() +
" already exists. If you are sure this "
"is what you want, "
"move it out of the way first");
}
FILE *file{fsbridge::fopen(temppath, "wb")};
AutoFile afile{file};
NodeContext &node = EnsureAnyNodeContext(request.context);
UniValue result = CreateUTXOSnapshot(
node, node.chainman->ActiveChainstate(), afile, path, temppath);
fs::rename(temppath, path);
return result;
},
};
}
UniValue CreateUTXOSnapshot(NodeContext &node, Chainstate &chainstate,
AutoFile &afile, const fs::path &path,
const fs::path &temppath) {
std::unique_ptr<CCoinsViewCursor> pcursor;
std::optional<CCoinsStats> maybe_stats;
const CBlockIndex *tip;
{
// We need to lock cs_main to ensure that the coinsdb isn't
// written to between (i) flushing coins cache to disk
// (coinsdb), (ii) getting stats based upon the coinsdb, and
// (iii) constructing a cursor to the coinsdb for use below this
// block.
//
// Cursors returned by leveldb iterate over snapshots, so the
// contents of the pcursor will not be affected by simultaneous
// writes during use below this block.
//
// See discussion here:
// https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369
//
LOCK(::cs_main);
chainstate.ForceFlushStateToDisk();
maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman,
CoinStatsHashType::HASH_SERIALIZED,
node.rpc_interruption_point);
if (!maybe_stats) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
pcursor =
std::unique_ptr<CCoinsViewCursor>(chainstate.CoinsDB().Cursor());
tip = CHECK_NONFATAL(
chainstate.m_blockman.LookupBlockIndex(maybe_stats->hashBlock));
}
LOG_TIME_SECONDS(
strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)",
tip->nHeight, tip->GetBlockHash().ToString(),
fs::PathToString(path), fs::PathToString(temppath)));
SnapshotMetadata metadata{tip->GetBlockHash(), maybe_stats->coins_count};
afile << metadata;
COutPoint key;
+ TxId last_txid;
Coin coin;
unsigned int iter{0};
-
+ size_t written_coins_count{0};
+ std::vector<std::pair<uint32_t, Coin>> coins;
+
+ // To reduce space the serialization format of the snapshot avoids
+ // duplication of tx hashes. The code takes advantage of the guarantee by
+ // leveldb that keys are lexicographically sorted.
+ // In the coins vector we collect all coins that belong to a certain tx hash
+ // (key.hash) and when we have them all (key.hash != last_hash) we write
+ // them to file using the below lambda function.
+ // See also https://github.com/bitcoin/bitcoin/issues/25675
+ auto write_coins_to_file =
+ [&](AutoFile &afile, const TxId &last_txid,
+ const std::vector<std::pair<uint32_t, Coin>> &coins,
+ size_t &written_coins_count) {
+ afile << last_txid;
+ WriteCompactSize(afile, coins.size());
+ for (const auto &[n, coin_] : coins) {
+ WriteCompactSize(afile, n);
+ afile << coin_;
+ ++written_coins_count;
+ }
+ };
+
+ pcursor->GetKey(key);
+ last_txid = key.GetTxId();
while (pcursor->Valid()) {
if (iter % 5000 == 0) {
node.rpc_interruption_point();
}
++iter;
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
- afile << key;
- afile << coin;
+ if (key.GetTxId() != last_txid) {
+ write_coins_to_file(afile, last_txid, coins,
+ written_coins_count);
+ last_txid = key.GetTxId();
+ coins.clear();
+ }
+ coins.emplace_back(key.GetN(), coin);
}
-
pcursor->Next();
}
+ if (!coins.empty()) {
+ write_coins_to_file(afile, last_txid, coins, written_coins_count);
+ }
+
+ CHECK_NONFATAL(written_coins_count == maybe_stats->coins_count);
+
afile.fclose();
UniValue result(UniValue::VOBJ);
- result.pushKV("coins_written", maybe_stats->coins_count);
+ result.pushKV("coins_written", written_coins_count);
result.pushKV("base_hash", tip->GetBlockHash().ToString());
result.pushKV("base_height", tip->nHeight);
result.pushKV("path", path.u8string());
result.pushKV("txoutset_hash", maybe_stats->hashSerialized.ToString());
result.pushKV("nchaintx", tip->nChainTx);
return result;
}
static RPCHelpMan loadtxoutset() {
return RPCHelpMan{
"loadtxoutset",
"Load the serialized UTXO set from a file.\n"
"Once this snapshot is loaded, its contents will be deserialized into "
"a second chainstate data structure, which is then used to sync to the "
"network's tip. "
"Meanwhile, the original chainstate will complete the initial block "
"download process in the background, eventually validating up to the "
"block that the snapshot is based upon.\n\n"
"The result is a usable bitcoind instance that is current with the "
"network tip in a matter of minutes rather than hours. UTXO snapshot "
"are typically obtained from third-party sources (HTTP, torrent, etc.) "
"which is reasonable since their contents are always checked by "
"hash.\n\n"
"You can find more information on this process in the `assumeutxo` "
"design document (https://www.bitcoinabc.org/doc/assumeutxo.html).",
{
{"path", RPCArg::Type::STR, RPCArg::Optional::NO,
"path to the snapshot file. If relative, will be prefixed by "
"datadir."},
},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "coins_loaded",
"the number of coins loaded from the snapshot"},
{RPCResult::Type::STR_HEX, "tip_hash",
"the hash of the base of the snapshot"},
{RPCResult::Type::NUM, "base_height",
"the height of the base of the snapshot"},
{RPCResult::Type::STR, "path",
"the absolute path that the snapshot was loaded from"},
}},
RPCExamples{HelpExampleCli("loadtxoutset", "utxo.dat")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
NodeContext &node = EnsureAnyNodeContext(request.context);
ChainstateManager &chainman = EnsureChainman(node);
fs::path path{AbsPathForConfigVal(
EnsureArgsman(node), fs::u8path(request.params[0].get_str()))};
FILE *file{fsbridge::fopen(path, "rb")};
AutoFile afile{file};
if (afile.IsNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Couldn't open file " + path.u8string() +
" for reading.");
}
SnapshotMetadata metadata;
afile >> metadata;
BlockHash base_blockhash = metadata.m_base_blockhash;
if (!chainman.GetParams()
.AssumeutxoForBlockhash(base_blockhash)
.has_value()) {
throw JSONRPCError(
RPC_INTERNAL_ERROR,
strprintf("Unable to load UTXO snapshot, "
"assumeutxo block hash in snapshot metadata not "
"recognized (%s)",
base_blockhash.ToString()));
}
CBlockIndex *snapshot_start_block = WITH_LOCK(
::cs_main,
return chainman.m_blockman.LookupBlockIndex(base_blockhash));
if (!snapshot_start_block) {
throw JSONRPCError(
RPC_INTERNAL_ERROR,
strprintf("The base block header (%s) must appear in the "
"headers chain. Make sure all headers are "
"synced, and call this RPC again.",
base_blockhash.ToString()));
}
if (!chainman.ActivateSnapshot(afile, metadata, false)) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Unable to load UTXO snapshot " +
fs::PathToString(path));
}
// Because we can't provide historical blocks during tip or
// background sync. Update local services to reflect we are a
// limited peer until we are fully sync.
node.connman->RemoveLocalServices(NODE_NETWORK);
// Setting the limited state is usually redundant because the node
// can always provide the last 288 blocks, but it doesn't hurt to
// set it.
node.connman->AddLocalServices(NODE_NETWORK_LIMITED);
CBlockIndex *new_tip{
WITH_LOCK(::cs_main, return chainman.ActiveTip())};
UniValue result(UniValue::VOBJ);
result.pushKV("coins_loaded", metadata.m_coins_count);
result.pushKV("tip_hash", new_tip->GetBlockHash().ToString());
result.pushKV("base_height", new_tip->nHeight);
result.pushKV("path", fs::PathToString(path));
return result;
},
};
}
const std::vector<RPCResult> RPCHelpForChainstate{
{RPCResult::Type::NUM, "blocks", "number of blocks in this chainstate"},
{RPCResult::Type::STR_HEX, "bestblockhash", "blockhash of the tip"},
{RPCResult::Type::NUM, "difficulty", "difficulty of the tip"},
{RPCResult::Type::NUM, "verificationprogress",
"progress towards the network tip"},
{RPCResult::Type::STR_HEX, "snapshot_blockhash", /*optional=*/true,
"the base block of the snapshot this chainstate is based on, if any"},
{RPCResult::Type::NUM, "coins_db_cache_bytes", "size of the coinsdb cache"},
{RPCResult::Type::NUM, "coins_tip_cache_bytes",
"size of the coinstip cache"},
{RPCResult::Type::BOOL, "validated",
"whether the chainstate is fully validated. True if all blocks in the "
"chainstate were validated, false if the chain is based on a snapshot and "
"the snapshot has not yet been validated."},
};
static RPCHelpMan getchainstates() {
return RPCHelpMan{
"getchainstates",
"\nReturn information about chainstates.\n",
{},
RPCResult{RPCResult::Type::OBJ,
"",
"",
{
{RPCResult::Type::NUM, "headers",
"the number of headers seen so far"},
{RPCResult::Type::ARR,
"chainstates",
"list of the chainstates ordered by work, with the "
"most-work (active) chainstate last",
{
{RPCResult::Type::OBJ, "", "", RPCHelpForChainstate},
}},
}},
RPCExamples{HelpExampleCli("getchainstates", "") +
HelpExampleRpc("getchainstates", "")},
[&](const RPCHelpMan &self, const Config &config,
const JSONRPCRequest &request) -> UniValue {
LOCK(cs_main);
UniValue obj(UniValue::VOBJ);
ChainstateManager &chainman = EnsureAnyChainman(request.context);
auto make_chain_data =
[&](const Chainstate &chainstate,
bool validated) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
UniValue data(UniValue::VOBJ);
if (!chainstate.m_chain.Tip()) {
return data;
}
const CChain &chain = chainstate.m_chain;
const CBlockIndex *tip = chain.Tip();
data.pushKV("blocks", chain.Height());
data.pushKV("bestblockhash", tip->GetBlockHash().GetHex());
data.pushKV("difficulty", GetDifficulty(tip));
data.pushKV(
"verificationprogress",
GuessVerificationProgress(Params().TxData(), tip));
data.pushKV("coins_db_cache_bytes",
chainstate.m_coinsdb_cache_size_bytes);
data.pushKV("coins_tip_cache_bytes",
chainstate.m_coinstip_cache_size_bytes);
if (chainstate.m_from_snapshot_blockhash) {
data.pushKV(
"snapshot_blockhash",
chainstate.m_from_snapshot_blockhash->ToString());
}
data.pushKV("validated", validated);
return data;
};
obj.pushKV("headers", chainman.m_best_header
? chainman.m_best_header->nHeight
: -1);
const auto &chainstates = chainman.GetAll();
UniValue obj_chainstates{UniValue::VARR};
for (Chainstate *cs : chainstates) {
obj_chainstates.push_back(
make_chain_data(*cs, !cs->m_from_snapshot_blockhash ||
chainstates.size() == 1));
}
obj.pushKV("chainstates", std::move(obj_chainstates));
return obj;
}};
}
void RegisterBlockchainRPCCommands(CRPCTable &t) {
// clang-format off
static const CRPCCommand commands[] = {
// category actor (function)
// ------------------ ----------------------
{ "blockchain", getbestblockhash, },
{ "blockchain", getblock, },
{ "blockchain", getblockfrompeer, },
{ "blockchain", getblockchaininfo, },
{ "blockchain", getblockcount, },
{ "blockchain", getblockhash, },
{ "blockchain", getblockheader, },
{ "blockchain", getblockstats, },
{ "blockchain", getchaintips, },
{ "blockchain", getchaintxstats, },
{ "blockchain", getdifficulty, },
{ "blockchain", gettxout, },
{ "blockchain", gettxoutsetinfo, },
{ "blockchain", pruneblockchain, },
{ "blockchain", verifychain, },
{ "blockchain", preciousblock, },
{ "blockchain", scantxoutset, },
{ "blockchain", getblockfilter, },
{ "blockchain", dumptxoutset, },
{ "blockchain", loadtxoutset, },
{ "blockchain", getchainstates, },
/* Not shown in help */
{ "hidden", invalidateblock, },
{ "hidden", parkblock, },
{ "hidden", reconsiderblock, },
{ "hidden", syncwithvalidationinterfacequeue, },
{ "hidden", unparkblock, },
{ "hidden", waitfornewblock, },
{ "hidden", waitforblock, },
{ "hidden", waitforblockheight, },
};
// clang-format on
for (const auto &c : commands) {
t.appendCommand(c.name, &c);
}
}
diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp
index 5d111fca3..acc1bcaa2 100644
--- a/src/test/validation_chainstatemanager_tests.cpp
+++ b/src/test/validation_chainstatemanager_tests.cpp
@@ -1,920 +1,923 @@
// Copyright (c) 2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
#include <chainparams.h>
#include <config.h>
#include <consensus/validation.h>
#include <kernel/disconnected_transactions.h>
#include <node/chainstatemanager_args.h>
#include <node/kernel_notifications.h>
#include <node/utxo_snapshot.h>
#include <random.h>
#include <rpc/blockchain.h>
#include <sync.h>
#include <test/util/chainstate.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <test/util/validation.h>
#include <timedata.h>
#include <validation.h>
#include <validationinterface.h>
#include <tinyformat.h>
#include <vector>
#include <boost/test/unit_test.hpp>
using node::BlockManager;
using node::KernelNotifications;
using node::SnapshotMetadata;
BOOST_FIXTURE_TEST_SUITE(validation_chainstatemanager_tests, TestingSetup)
//! Basic tests for ChainstateManager.
//!
//! First create a legacy (IBD) chainstate, then create a snapshot chainstate.
BOOST_FIXTURE_TEST_CASE(chainstatemanager, TestChain100Setup) {
ChainstateManager &manager = *m_node.chainman;
std::vector<Chainstate *> chainstates;
BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
// Create a legacy (IBD) chainstate.
//
Chainstate &c1 = manager.ActiveChainstate();
chainstates.push_back(&c1);
BOOST_CHECK(!manager.IsSnapshotActive());
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
auto all = manager.GetAll();
BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(),
chainstates.end());
auto &active_chain =
WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain);
// Get to a valid assumeutxo tip (per chainparams);
mineBlocks(10);
BOOST_CHECK_EQUAL(
WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 110);
auto active_tip = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip());
auto exp_tip = c1.m_chain.Tip();
BOOST_CHECK_EQUAL(active_tip, exp_tip);
BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
// Create a snapshot-based chainstate.
//
const BlockHash snapshot_blockhash{active_tip->GetBlockHash()};
Chainstate &c2 = WITH_LOCK(
::cs_main, return manager.ActivateExistingSnapshot(snapshot_blockhash));
chainstates.push_back(&c2);
c2.InitCoinsDB(
/* cache_size_bytes */ 1 << 23, /* in_memory */ true,
/* should_wipe */ false);
{
LOCK(::cs_main);
c2.InitCoinsCache(1 << 23);
c2.CoinsTip().SetBestBlock(active_tip->GetBlockHash());
c2.setBlockIndexCandidates.insert(
manager.m_blockman.LookupBlockIndex(active_tip->GetBlockHash()));
c2.LoadChainTip();
}
BlockValidationState _;
BOOST_CHECK(c2.ActivateBestChain(_, nullptr));
BOOST_CHECK_EQUAL(manager.SnapshotBlockhash().value(), snapshot_blockhash);
BOOST_CHECK(manager.IsSnapshotActive());
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate());
BOOST_CHECK(&c1 != &manager.ActiveChainstate());
auto all2 = manager.GetAll();
BOOST_CHECK_EQUAL_COLLECTIONS(all2.begin(), all2.end(), chainstates.begin(),
chainstates.end());
auto &active_chain2 =
WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain);
BOOST_CHECK_EQUAL(
WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 110);
mineBlocks(1);
BOOST_CHECK_EQUAL(
WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 111);
BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return c1.m_chain.Height()),
110);
auto active_tip2 =
WITH_LOCK(manager.GetMutex(), return manager.ActiveTip());
BOOST_CHECK_EQUAL(active_tip, active_tip2->pprev);
BOOST_CHECK_EQUAL(active_tip, c1.m_chain.Tip());
BOOST_CHECK_EQUAL(active_tip2, c2.m_chain.Tip());
// Let scheduler events finish running to avoid accessing memory that is
// going to be unloaded
SyncWithValidationInterfaceQueue();
}
//! Test rebalancing the caches associated with each chainstate.
BOOST_FIXTURE_TEST_CASE(chainstatemanager_rebalance_caches, TestChain100Setup) {
ChainstateManager &manager = *m_node.chainman;
size_t max_cache = 10000;
manager.m_total_coinsdb_cache = max_cache;
manager.m_total_coinstip_cache = max_cache;
std::vector<Chainstate *> chainstates;
// Create a legacy (IBD) chainstate.
//
Chainstate &c1 = manager.ActiveChainstate();
chainstates.push_back(&c1);
{
LOCK(::cs_main);
c1.InitCoinsCache(1 << 23);
manager.MaybeRebalanceCaches();
}
BOOST_CHECK_EQUAL(c1.m_coinstip_cache_size_bytes, max_cache);
BOOST_CHECK_EQUAL(c1.m_coinsdb_cache_size_bytes, max_cache);
// Create a snapshot-based chainstate.
//
CBlockIndex *snapshot_base{WITH_LOCK(
manager.GetMutex(),
return manager.ActiveChain()[manager.ActiveChain().Height() / 2])};
Chainstate &c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(
*snapshot_base->phashBlock));
chainstates.push_back(&c2);
c2.InitCoinsDB(
/* cache_size_bytes */ 1 << 23, /* in_memory */ true,
/* should_wipe */ false);
// Reset IBD state so IsInitialBlockDownload() returns true and causes
// MaybeRebalancesCaches() to prioritize the snapshot chainstate, giving it
// more cache space than the snapshot chainstate. Calling ResetIbd() is
// necessary because m_cached_finished_ibd is already latched to true before
// the test starts due to the test setup. After ResetIbd() is called.
// IsInitialBlockDownload will return true because at this point the active
// chainstate has a null chain tip.
static_cast<TestChainstateManager &>(manager).ResetIbd();
{
LOCK(::cs_main);
c2.InitCoinsCache(1 << 23);
manager.MaybeRebalanceCaches();
}
BOOST_CHECK_CLOSE(c1.m_coinstip_cache_size_bytes, max_cache * 0.05, 1);
BOOST_CHECK_CLOSE(c1.m_coinsdb_cache_size_bytes, max_cache * 0.05, 1);
BOOST_CHECK_CLOSE(c2.m_coinstip_cache_size_bytes, max_cache * 0.95, 1);
BOOST_CHECK_CLOSE(c2.m_coinsdb_cache_size_bytes, max_cache * 0.95, 1);
}
struct SnapshotTestSetup : TestChain100Setup {
// Run with coinsdb on the filesystem to support, e.g., moving invalidated
// chainstate dirs to "*_invalid".
//
// Note that this means the tests run considerably slower than in-memory DB
// tests, but we can't otherwise test this functionality since it relies on
// destructive filesystem operations.
SnapshotTestSetup()
: TestChain100Setup{
{},
{},
/*coins_db_in_memory=*/false,
/*block_tree_db_in_memory=*/false,
} {}
std::tuple<Chainstate *, Chainstate *> SetupSnapshot() {
ChainstateManager &chainman = *Assert(m_node.chainman);
BOOST_CHECK(!chainman.IsSnapshotActive());
{
LOCK(::cs_main);
BOOST_CHECK(!chainman.IsSnapshotValidated());
BOOST_CHECK(!node::FindSnapshotChainstateDir());
}
size_t initial_size;
size_t initial_total_coins{100};
// Make some initial assertions about the contents of the chainstate.
{
LOCK(::cs_main);
CCoinsViewCache &ibd_coinscache =
chainman.ActiveChainstate().CoinsTip();
initial_size = ibd_coinscache.GetCacheSize();
size_t total_coins{0};
for (CTransactionRef &txn : m_coinbase_txns) {
COutPoint op{txn->GetId(), 0};
BOOST_CHECK(ibd_coinscache.HaveCoin(op));
total_coins++;
}
BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
BOOST_CHECK_EQUAL(initial_size, initial_total_coins);
}
Chainstate &validation_chainstate = chainman.ActiveChainstate();
// Snapshot should refuse to load at this height.
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
BOOST_CHECK(!chainman.SnapshotBlockhash());
// Mine 10 more blocks, putting at us height 110 where a valid
// assumeutxo value can be found.
constexpr int snapshot_height = 110;
mineBlocks(10);
initial_size += 10;
initial_total_coins += 10;
// Should not load malleated snapshots
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
this, [](AutoFile &auto_infile, SnapshotMetadata &metadata) {
// A UTXO is missing but count is correct
metadata.m_coins_count -= 1;
- COutPoint outpoint;
+ TxId txid;
+ auto_infile >> txid;
+ // coins size
+ (void)ReadCompactSize(auto_infile);
+ // vout index
+ (void)ReadCompactSize(auto_infile);
Coin coin;
-
- auto_infile >> outpoint;
auto_infile >> coin;
}));
BOOST_CHECK(!node::FindSnapshotChainstateDir());
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
this, [](AutoFile &auto_infile, SnapshotMetadata &metadata) {
// Coins count is larger than coins in file
metadata.m_coins_count += 1;
}));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
this, [](AutoFile &auto_infile, SnapshotMetadata &metadata) {
// Coins count is smaller than coins in file
metadata.m_coins_count -= 1;
}));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
this, [](AutoFile &auto_infile, SnapshotMetadata &metadata) {
// Wrong hash
metadata.m_base_blockhash = BlockHash{uint256::ZERO};
}));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
this, [](AutoFile &auto_infile, SnapshotMetadata &metadata) {
// Wrong hash
metadata.m_base_blockhash = BlockHash{uint256::ONE};
}));
BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(this));
BOOST_CHECK(fs::exists(*node::FindSnapshotChainstateDir()));
// Ensure our active chain is the snapshot chainstate.
BOOST_CHECK(
!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
BOOST_CHECK_EQUAL(
*chainman.ActiveChainstate().m_from_snapshot_blockhash,
*chainman.SnapshotBlockhash());
Chainstate &snapshot_chainstate = chainman.ActiveChainstate();
{
LOCK(::cs_main);
fs::path found = *node::FindSnapshotChainstateDir();
// Note: WriteSnapshotBaseBlockhash() is implicitly tested above.
BOOST_CHECK_EQUAL(*node::ReadSnapshotBaseBlockhash(found),
*chainman.SnapshotBlockhash());
}
const auto &au_data =
chainman.GetParams().AssumeutxoForHeight(snapshot_height);
const CBlockIndex *tip =
WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());
BOOST_CHECK_EQUAL(tip->nChainTx, au_data->nChainTx);
// To be checked against later when we try loading a subsequent
// snapshot.
BlockHash loaded_snapshot_blockhash{*chainman.SnapshotBlockhash()};
// Make some assertions about the both chainstates. These checks ensure
// the legacy chainstate hasn't changed and that the newly created
// chainstate reflects the expected content.
{
LOCK(::cs_main);
int chains_tested{0};
for (Chainstate *chainstate : chainman.GetAll()) {
BOOST_TEST_MESSAGE("Checking coins in "
<< chainstate->ToString());
CCoinsViewCache &coinscache = chainstate->CoinsTip();
// Both caches will be empty initially.
BOOST_CHECK_EQUAL((unsigned int)0, coinscache.GetCacheSize());
size_t total_coins{0};
for (CTransactionRef &txn : m_coinbase_txns) {
COutPoint op{txn->GetId(), 0};
BOOST_CHECK(coinscache.HaveCoin(op));
total_coins++;
}
BOOST_CHECK_EQUAL(initial_size, coinscache.GetCacheSize());
BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
chains_tested++;
}
BOOST_CHECK_EQUAL(chains_tested, 2);
}
// Mine some new blocks on top of the activated snapshot chainstate.
constexpr size_t new_coins{100};
mineBlocks(new_coins); // Defined in TestChain100Setup.
{
LOCK(::cs_main);
size_t coins_in_active{0};
size_t coins_in_background{0};
size_t coins_missing_from_background{0};
for (Chainstate *chainstate : chainman.GetAll()) {
BOOST_TEST_MESSAGE("Checking coins in "
<< chainstate->ToString());
CCoinsViewCache &coinscache = chainstate->CoinsTip();
bool is_background = chainstate != &chainman.ActiveChainstate();
for (CTransactionRef &txn : m_coinbase_txns) {
COutPoint op{txn->GetId(), 0};
if (coinscache.HaveCoin(op)) {
(is_background ? coins_in_background
: coins_in_active)++;
} else if (is_background) {
coins_missing_from_background++;
}
}
}
BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins);
BOOST_CHECK_EQUAL(coins_in_background, initial_total_coins);
BOOST_CHECK_EQUAL(coins_missing_from_background, new_coins);
}
// Snapshot should refuse to load after one has already loaded.
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
// Snapshot blockhash should be unchanged.
BOOST_CHECK_EQUAL(
*chainman.ActiveChainstate().m_from_snapshot_blockhash,
loaded_snapshot_blockhash);
return std::make_tuple(&validation_chainstate, &snapshot_chainstate);
}
//! Simulate a restart of the node by flushing all state to disk, clearing
//! the existing ChainstateManager, and unloading the block index.
//!
//! @returns a reference to the "restarted" ChainstateManager
ChainstateManager &SimulateNodeRestart() {
ChainstateManager &chainman = *Assert(m_node.chainman);
BOOST_TEST_MESSAGE("Simulating node restart");
{
for (Chainstate *cs : chainman.GetAll()) {
LOCK(::cs_main);
cs->ForceFlushStateToDisk();
}
// Process all callbacks referring to the old manager before wiping
// it.
SyncWithValidationInterfaceQueue();
LOCK(::cs_main);
chainman.ResetChainstates();
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 0);
m_node.notifications = std::make_unique<KernelNotifications>();
ChainstateManager::Options chainman_opts{
.config = chainman.GetConfig(),
.datadir = m_args.GetDataDirNet(),
.adjusted_time_callback = GetAdjustedTime,
.notifications = *m_node.notifications,
};
node::ApplyArgsManOptions(*m_node.args, chainman_opts);
const BlockManager::Options blockman_opts{
.chainparams = chainman_opts.config.GetChainParams(),
.blocks_dir = m_args.GetBlocksDirPath(),
};
// For robustness, ensure the old manager is destroyed before
// creating a new one.
m_node.chainman.reset();
m_node.chainman = std::make_unique<ChainstateManager>(
chainman_opts, blockman_opts);
}
return *Assert(m_node.chainman);
}
};
//! Test basic snapshot activation.
BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot,
SnapshotTestSetup) {
this->SetupSnapshot();
}
//! Test LoadBlockIndex behavior when multiple chainstates are in use.
//!
//! - First, verfiy that setBlockIndexCandidates is as expected when using a
//! single, fully-validating chainstate.
//!
//! - Then mark a region of the chain as missing data and introduce a second
//! chainstate that will tolerate assumed-valid blocks. Run LoadBlockIndex()
//! and ensure that the first chainstate only contains fully validated blocks
//! and the other chainstate contains all blocks, except those marked
//! assume-valid, because those entries don't HAVE_DATA.
//!
BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup) {
ChainstateManager &chainman = *Assert(m_node.chainman);
// Mine to height 120, past the hardcoded regtest assumeutxo snapshot at
// height 110
mineBlocks(20);
CBlockIndex *assumed_tip{
WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip())};
BOOST_CHECK_EQUAL(assumed_tip->nHeight, 120);
auto reload_all_block_indexes = [&]() {
// For completeness, we also reset the block sequence counters to
// ensure that no state which affects the ranking of tip-candidates is
// retained (even though this isn't strictly necessary).
WITH_LOCK(::cs_main, return chainman.ResetBlockSequenceCounters());
for (Chainstate *cs : chainman.GetAll()) {
LOCK(::cs_main);
cs->ClearBlockIndexCandidates();
BOOST_CHECK(cs->setBlockIndexCandidates.empty());
}
WITH_LOCK(::cs_main, chainman.LoadBlockIndex());
};
// Ensure that without any assumed-valid BlockIndex entries, only the
// current tip is considered as a candidate.
reload_all_block_indexes();
Chainstate &cs1 = chainman.ActiveChainstate();
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 1);
int num_indexes{0};
// Blocks in range [assumed_valid_start_idx, last_assumed_valid_idx) will be
// marked as assumed-valid and not having data.
const int expected_assumed_valid{20};
const int last_assumed_valid_idx{111};
const int assumed_valid_start_idx =
last_assumed_valid_idx - expected_assumed_valid;
CBlockIndex *validated_tip{nullptr};
CBlockIndex *assumed_base{nullptr};
// Reset some region of the chain's nStatus, removing the HAVE_DATA flag.
for (int i = 0; i <= cs1.m_chain.Height(); ++i) {
LOCK(::cs_main);
auto index = cs1.m_chain[i];
// Blocks with heights in range [91, 110] are marked as missing data.
if (i < last_assumed_valid_idx && i >= assumed_valid_start_idx) {
index->nStatus = BlockStatus().withValidity(BlockValidity::TREE);
index->nTx = 0;
index->nChainTx = 0;
}
++num_indexes;
// Note the last fully-validated block as the expected validated tip.
if (i == (assumed_valid_start_idx - 1)) {
validated_tip = index;
}
// Note the last assumed valid block as the snapshot base
if (i == last_assumed_valid_idx - 1) {
assumed_base = index;
}
}
// Note: cs2's tip is not set when ActivateExistingSnapshot is called.
Chainstate &cs2 =
*WITH_LOCK(::cs_main, return &chainman.ActivateExistingSnapshot(
assumed_base->GetBlockHash()));
// Set tip of the fully validated chain to be the validated tip
cs1.m_chain.SetTip(*validated_tip);
// Set tip of the assume-valid-based chain to the assume-valid block
cs2.m_chain.SetTip(*assumed_base);
// Sanity check test variables.
// 121 total blocks, including genesis
BOOST_CHECK_EQUAL(num_indexes, 121);
// original chain has height 120
BOOST_CHECK_EQUAL(assumed_tip->nHeight, 120);
// current cs1 chain has height 90
BOOST_CHECK_EQUAL(validated_tip->nHeight, 90);
// current cs2 chain has height 110
BOOST_CHECK_EQUAL(assumed_base->nHeight, 110);
// Regenerate cs1.setBlockIndexCandidates and cs2.setBlockIndexCandidate
// and check contents below.
reload_all_block_indexes();
// The fully validated chain should only have the current validated tip and
// the assumed valid base as candidates, blocks 90 and 110. Specifically:
//
// - It does not have blocks 0-89 because they contain less work than the
// chain tip.
//
// - It has block 90 because it has data and equal work to the chain tip,
// (since it is the chain tip).
//
// - It does not have blocks 91-109 because they do not contain data.
//
// - It has block 110 even though it does not have data, because
// LoadBlockIndex has a special case to always add the snapshot block as
// a candidate. The special case is only actually intended to apply to
// the snapshot chainstate cs2, not the background chainstate cs1, but it
// is written broadly and applies to both.
//
// - It does not have any blocks after height 110 because cs1 is a
// background chainstate, and only blocks where are ancestors of the
// snapshot block are added as candidates for the background chainstate.
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 2);
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(validated_tip), 1);
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(assumed_base), 1);
// The assumed-valid tolerant chain has the assumed valid base as a
// candidate, but otherwise has none of the assumed-valid (which do not
// HAVE_DATA) blocks as candidates.
//
// Specifically:
// - All blocks below height 110 are not candidates, because cs2 chain tip
// has height 110 and they have less work than it does.
//
// - Block 110 is a candidate even though it does not have data, because it
// is the snapshot block, which is assumed valid.
//
// - Blocks 111-120 are added because they have data.
// Check that block 90 is absent
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 0);
// Check that block 109 is absent
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_base->pprev),
0);
// Check that block 110 is present
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_base), 1);
// Check that block 120 is present
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_tip), 1);
// Check that 11 blocks total are present.
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(),
num_indexes - last_assumed_valid_idx + 1);
}
//! Ensure that snapshot chainstates initialize properly when found on disk.
BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup) {
ChainstateManager &chainman = *Assert(m_node.chainman);
Chainstate &bg_chainstate = chainman.ActiveChainstate();
this->SetupSnapshot();
fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir();
BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
BOOST_CHECK_EQUAL(snapshot_chainstate_dir,
gArgs.GetDataDirNet() / "chainstate_snapshot");
BOOST_CHECK(chainman.IsSnapshotActive());
const BlockHash snapshot_tip_hash = WITH_LOCK(
chainman.GetMutex(), return chainman.ActiveTip()->GetBlockHash());
auto all_chainstates = chainman.GetAll();
BOOST_CHECK_EQUAL(all_chainstates.size(), 2);
// "Rewind" the background chainstate so that its tip is not at the
// base block of the snapshot - this is so after simulating a node restart,
// it will initialize instead of attempting to complete validation.
//
// Note that this is not a realistic use of DisconnectTip().
DisconnectedBlockTransactions unused_pool;
BlockValidationState unused_state;
{
LOCK2(::cs_main, bg_chainstate.MempoolMutex());
BOOST_CHECK(bg_chainstate.DisconnectTip(unused_state, &unused_pool));
// to avoid queuedTx assertion errors on teardown
unused_pool.clear();
}
BOOST_CHECK_EQUAL(bg_chainstate.m_chain.Height(), 109);
// Test that simulating a shutdown (resetting ChainstateManager) and then
// performing chainstate reinitializing successfully cleans up the
// background-validation chainstate data, and we end up with a single
// chainstate that is at tip.
ChainstateManager &chainman_restarted = this->SimulateNodeRestart();
BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate");
// This call reinitializes the chainstates.
this->LoadVerifyActivateChainstate();
{
LOCK(chainman_restarted.GetMutex());
BOOST_CHECK_EQUAL(chainman_restarted.GetAll().size(), 2);
BOOST_CHECK(chainman_restarted.IsSnapshotActive());
BOOST_CHECK(!chainman_restarted.IsSnapshotValidated());
BOOST_CHECK_EQUAL(chainman_restarted.ActiveTip()->GetBlockHash(),
snapshot_tip_hash);
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210);
}
BOOST_TEST_MESSAGE("Ensure we can mine blocks on top of the initialized "
"snapshot chainstate");
mineBlocks(10);
{
LOCK(chainman_restarted.GetMutex());
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220);
// Background chainstate should be unaware of new blocks on the snapshot
// chainstate.
for (const Chainstate *cs : chainman_restarted.GetAll()) {
if (cs != &chainman_restarted.ActiveChainstate()) {
BOOST_CHECK_EQUAL(cs->m_chain.Height(), 109);
}
}
}
}
BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion,
SnapshotTestSetup) {
this->SetupSnapshot();
ChainstateManager &chainman = *Assert(m_node.chainman);
Chainstate &active_cs = chainman.ActiveChainstate();
auto tip_cache_before_complete = active_cs.m_coinstip_cache_size_bytes;
auto db_cache_before_complete = active_cs.m_coinsdb_cache_size_bytes;
SnapshotCompletionResult res;
auto mock_shutdown = [](bilingual_str msg) {};
fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir();
BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
BOOST_CHECK_EQUAL(snapshot_chainstate_dir,
gArgs.GetDataDirNet() / "chainstate_snapshot");
BOOST_CHECK(chainman.IsSnapshotActive());
const BlockHash snapshot_tip_hash = WITH_LOCK(
chainman.GetMutex(), return chainman.ActiveTip()->GetBlockHash());
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation(
mock_shutdown));
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SUCCESS);
WITH_LOCK(::cs_main, BOOST_CHECK(chainman.IsSnapshotValidated()));
BOOST_CHECK(chainman.IsSnapshotActive());
// Cache should have been rebalanced and reallocated to the "only" remaining
// chainstate.
BOOST_CHECK(active_cs.m_coinstip_cache_size_bytes >
tip_cache_before_complete);
BOOST_CHECK(active_cs.m_coinsdb_cache_size_bytes >
db_cache_before_complete);
auto all_chainstates = chainman.GetAll();
BOOST_CHECK_EQUAL(all_chainstates.size(), 1);
BOOST_CHECK_EQUAL(all_chainstates[0], &active_cs);
// Trying completion again should return false.
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation(
mock_shutdown));
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SKIPPED);
// The invalid snapshot path should not have been used.
fs::path snapshot_invalid_dir =
gArgs.GetDataDirNet() / "chainstate_snapshot_INVALID";
BOOST_CHECK(!fs::exists(snapshot_invalid_dir));
// chainstate_snapshot should still exist.
BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
// Test that simulating a shutdown (resetting ChainstateManager) and then
// performing chainstate reinitializing successfully cleans up the
// background-validation chainstate data, and we end up with a single
// chainstate that is at tip.
ChainstateManager &chainman_restarted = this->SimulateNodeRestart();
BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate");
// This call reinitializes the chainstates, and should clean up the now
// unnecessary background-validation leveldb contents.
this->LoadVerifyActivateChainstate();
BOOST_CHECK(!fs::exists(snapshot_invalid_dir));
// chainstate_snapshot should now *not* exist.
BOOST_CHECK(!fs::exists(snapshot_chainstate_dir));
const Chainstate &active_cs2 = chainman_restarted.ActiveChainstate();
{
LOCK(chainman_restarted.GetMutex());
BOOST_CHECK_EQUAL(chainman_restarted.GetAll().size(), 1);
BOOST_CHECK(!chainman_restarted.IsSnapshotActive());
BOOST_CHECK(!chainman_restarted.IsSnapshotValidated());
BOOST_CHECK(active_cs2.m_coinstip_cache_size_bytes >
tip_cache_before_complete);
BOOST_CHECK(active_cs2.m_coinsdb_cache_size_bytes >
db_cache_before_complete);
BOOST_CHECK_EQUAL(chainman_restarted.ActiveTip()->GetBlockHash(),
snapshot_tip_hash);
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210);
}
BOOST_TEST_MESSAGE(
"Ensure we can mine blocks on top of the \"new\" IBD chainstate");
mineBlocks(10);
{
LOCK(chainman_restarted.GetMutex());
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220);
}
}
BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion_hash_mismatch,
SnapshotTestSetup) {
auto chainstates = this->SetupSnapshot();
Chainstate &validation_chainstate = *std::get<0>(chainstates);
ChainstateManager &chainman = *Assert(m_node.chainman);
SnapshotCompletionResult res;
auto mock_shutdown = [](bilingual_str msg) {};
// Test tampering with the IBD UTXO set with an extra coin to ensure it
// causes snapshot completion to fail.
CCoinsViewCache &ibd_coins =
WITH_LOCK(::cs_main, return validation_chainstate.CoinsTip());
CScript script;
script.assign(InsecureRandBits(6), 0);
Coin badcoin{CTxOut{int64_t(InsecureRand32()) * SATOSHI, script},
/*nHeightIn=*/1, /*IsCoinbase=*/false};
TxId txid{InsecureRand256()};
ibd_coins.AddCoin(COutPoint(txid, 0), std::move(badcoin), false);
fs::path snapshot_chainstate_dir =
gArgs.GetDataDirNet() / "chainstate_snapshot";
BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation(
mock_shutdown));
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::HASH_MISMATCH);
auto all_chainstates = chainman.GetAll();
BOOST_CHECK_EQUAL(all_chainstates.size(), 1);
BOOST_CHECK_EQUAL(all_chainstates[0], &validation_chainstate);
BOOST_CHECK_EQUAL(&chainman.ActiveChainstate(), &validation_chainstate);
fs::path snapshot_invalid_dir =
gArgs.GetDataDirNet() / "chainstate_snapshot_INVALID";
BOOST_CHECK(fs::exists(snapshot_invalid_dir));
// Test that simulating a shutdown (resetting ChainstateManager) and then
// performing chainstate reinitializing successfully loads only the
// fully-validated chainstate data, and we end up with a single chainstate
// that is at tip.
ChainstateManager &chainman_restarted = this->SimulateNodeRestart();
BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate");
// This call reinitializes the chainstates, and should clean up the now
// unnecessary background-validation leveldb contents.
this->LoadVerifyActivateChainstate();
BOOST_CHECK(fs::exists(snapshot_invalid_dir));
BOOST_CHECK(!fs::exists(snapshot_chainstate_dir));
{
LOCK(::cs_main);
BOOST_CHECK_EQUAL(chainman_restarted.GetAll().size(), 1);
BOOST_CHECK(!chainman_restarted.IsSnapshotActive());
BOOST_CHECK(!chainman_restarted.IsSnapshotValidated());
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210);
}
BOOST_TEST_MESSAGE(
"Ensure we can mine blocks on top of the \"new\" IBD chainstate");
mineBlocks(10);
{
LOCK(::cs_main);
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220);
}
}
BOOST_FIXTURE_TEST_CASE(chainstatemanager_recent_headers_time,
SnapshotTestSetup) {
auto &initial_chainman = *Assert(m_node.chainman);
// Make the path unique
fs::path dumpPath = "headerstime.test.";
dumpPath += ToString(FastRandomContext().rand64()) + ".dat";
BOOST_CHECK(!initial_chainman.m_options.store_recent_headers_time);
// The dump fails if the feature is not enabled
BOOST_CHECK(!WITH_LOCK(
cs_main, return initial_chainman.DumpRecentHeadersTime(dumpPath)));
// Restart with the appropriate option
m_node.args->ForceSetArg("-persistrecentheaderstime", "1");
auto &chainman = this->SimulateNodeRestart();
this->LoadVerifyActivateChainstate();
// Now it's enabled
BOOST_CHECK(chainman.m_options.store_recent_headers_time);
auto now = GetTime();
SetMockTime(now);
mineBlocks(30);
{
const CBlockIndex *index =
WITH_LOCK(cs_main, return chainman.ActiveTip());
for (size_t i = 30; i > 0; i--) {
BOOST_CHECK(index);
// mineBlocks increment the mocktime by 1 after each block
BOOST_CHECK_EQUAL(index->GetHeaderReceivedTime(), now + i - 1);
index = index->pprev;
}
}
// Dump the headers time
BOOST_CHECK(
WITH_LOCK(cs_main, return chainman.DumpRecentHeadersTime(dumpPath)));
// Restart the chainstate manager
BlockHash prevTipHash =
WITH_LOCK(cs_main, return chainman.ActiveTip()->GetBlockHash());
auto &restartedChainman = this->SimulateNodeRestart();
this->LoadVerifyActivateChainstate();
BlockHash restartedTip = WITH_LOCK(
cs_main, return restartedChainman.ActiveTip()->GetBlockHash());
BOOST_CHECK_EQUAL(restartedTip, prevTipHash);
{
const CBlockIndex *index =
WITH_LOCK(cs_main, return restartedChainman.ActiveTip());
// Without loading the file, all the headers time are lost and set to
// the default as loaded from disk, aka zero
for (size_t i = 30; i > 0; i--) {
BOOST_CHECK(index);
BOOST_CHECK_EQUAL(index->GetHeaderReceivedTime(), 0);
index = index->pprev;
}
}
// Load the headers time
BOOST_CHECK(WITH_LOCK(
cs_main, return restartedChainman.LoadRecentHeadersTime(dumpPath)));
{
const CBlockIndex *index =
WITH_LOCK(cs_main, return restartedChainman.ActiveTip());
// After the file is loaded, the headers time is recovered for the last
// 20 headers and lost for the others
for (size_t i = 30; i > 10; i--) {
BOOST_CHECK(index);
BOOST_CHECK_EQUAL(index->GetHeaderReceivedTime(), now + i - 1);
index = index->pprev;
}
for (size_t i = 10; i > 0; i--) {
BOOST_CHECK(index);
BOOST_CHECK_EQUAL(index->GetHeaderReceivedTime(), 0);
index = index->pprev;
}
}
// Failure cases
{
LOCK(cs_main);
// Cannot load a non existent file
BOOST_CHECK(!restartedChainman.LoadRecentHeadersTime(
"does_not_exist.test.dat"));
// Cannot dump if we don't have all the block indexes
CBlockIndex *index = restartedChainman.ActiveTip();
for (size_t i = 0; i < 19; i++) {
BOOST_CHECK(index);
CBlockIndex *savedIndex = index->pprev;
index->pprev = nullptr;
BOOST_CHECK(!restartedChainman.DumpRecentHeadersTime(dumpPath));
index->pprev = savedIndex;
index = index->pprev;
}
}
m_node.args->ClearForcedArg("-persistrecentheaderstime");
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/validation.cpp b/src/validation.cpp
index 840547667..d244971c4 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1,7350 +1,7367 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2018 The Bitcoin Core developers
// Copyright (c) 2017-2020 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <validation.h>
#include <kernel/chain.h>
#include <kernel/chainparams.h>
#include <kernel/coinstats.h>
#include <kernel/disconnected_transactions.h>
#include <kernel/mempool_entry.h>
#include <kernel/mempool_persist.h>
#include <arith_uint256.h>
#include <avalanche/avalanche.h>
#include <avalanche/processor.h>
#include <blockvalidity.h>
#include <chainparams.h>
#include <checkpoints.h>
#include <checkqueue.h>
#include <common/args.h>
#include <config.h>
#include <consensus/activation.h>
#include <consensus/amount.h>
#include <consensus/merkle.h>
#include <consensus/tx_check.h>
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
#include <hash.h>
#include <kernel/notifications_interface.h>
#include <logging.h>
#include <logging/timer.h>
#include <minerfund.h>
#include <node/blockstorage.h>
#include <node/utxo_snapshot.h>
#include <policy/block/minerfund.h>
#include <policy/block/preconsensus.h>
#include <policy/block/rtt.h>
#include <policy/block/stakingrewards.h>
#include <policy/policy.h>
#include <policy/settings.h>
#include <pow/pow.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <random.h>
#include <reverse_iterator.h>
#include <script/script.h>
#include <script/scriptcache.h>
#include <script/sigcache.h>
#include <shutdown.h>
#include <tinyformat.h>
#include <txdb.h>
#include <txmempool.h>
#include <undo.h>
#include <util/check.h> // For NDEBUG compile time check
#include <util/fs.h>
#include <util/fs_helpers.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/time.h>
#include <util/trace.h>
#include <util/translation.h>
#include <validationinterface.h>
#include <warnings.h>
#include <algorithm>
#include <atomic>
#include <cassert>
#include <chrono>
#include <deque>
#include <numeric>
#include <optional>
#include <string>
#include <thread>
#include <tuple>
using kernel::CCoinsStats;
using kernel::CoinStatsHashType;
using kernel::ComputeUTXOStats;
using kernel::LoadMempool;
using kernel::Notifications;
using fsbridge::FopenFn;
using node::BLOCKFILE_CHUNK_SIZE;
using node::BlockManager;
using node::BlockMap;
using node::fReindex;
using node::SnapshotMetadata;
using node::UNDOFILE_CHUNK_SIZE;
#define MICRO 0.000001
#define MILLI 0.001
/** Time to wait between writing blocks/block index to disk. */
static constexpr std::chrono::hours DATABASE_WRITE_INTERVAL{1};
/** Time to wait between flushing chainstate to disk. */
static constexpr std::chrono::hours DATABASE_FLUSH_INTERVAL{24};
const std::vector<std::string> CHECKLEVEL_DOC{
"level 0 reads the blocks from disk",
"level 1 verifies block validity",
"level 2 verifies undo data",
"level 3 checks disconnection of tip blocks",
"level 4 tries to reconnect the blocks",
"each level includes the checks of the previous levels",
};
/**
* The number of blocks to keep below the deepest prune lock.
* There is nothing special about this number. It is higher than what we
* expect to see in regular mainnet reorgs, but not so high that it would
* noticeably interfere with the pruning mechanism.
* */
static constexpr int PRUNE_LOCK_BUFFER{10};
static constexpr uint64_t HEADERS_TIME_VERSION{1};
GlobalMutex g_best_block_mutex;
std::condition_variable g_best_block_cv;
const CBlockIndex *g_best_block;
BlockValidationOptions::BlockValidationOptions(const Config &config)
: excessiveBlockSize(config.GetMaxBlockSize()), checkPoW(true),
checkMerkleRoot(true) {}
const CBlockIndex *
Chainstate::FindForkInGlobalIndex(const CBlockLocator &locator) const {
AssertLockHeld(cs_main);
// Find the latest block common to locator and chain - we expect that
// locator.vHave is sorted descending by height.
for (const BlockHash &hash : locator.vHave) {
const CBlockIndex *pindex{m_blockman.LookupBlockIndex(hash)};
if (pindex) {
if (m_chain.Contains(pindex)) {
return pindex;
}
if (pindex->GetAncestor(m_chain.Height()) == m_chain.Tip()) {
return m_chain.Tip();
}
}
}
return m_chain.Genesis();
}
static uint32_t GetNextBlockScriptFlags(const CBlockIndex *pindex,
const ChainstateManager &chainman);
namespace {
/**
* A helper which calculates heights of inputs of a given transaction.
*
* @param[in] tip The current chain tip. If an input belongs to a mempool
* transaction, we assume it will be confirmed in the next
* block.
* @param[in] coins Any CCoinsView that provides access to the relevant coins.
* @param[in] tx The transaction being evaluated.
*
* @returns A vector of input heights or nullopt, in case of an error.
*/
std::optional<std::vector<int>> CalculatePrevHeights(const CBlockIndex &tip,
const CCoinsView &coins,
const CTransaction &tx) {
std::vector<int> prev_heights;
prev_heights.resize(tx.vin.size());
for (size_t i = 0; i < tx.vin.size(); ++i) {
const CTxIn &txin = tx.vin[i];
Coin coin;
if (!coins.GetCoin(txin.prevout, coin)) {
LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n",
__func__, i, tx.GetId().GetHex());
return std::nullopt;
}
if (coin.GetHeight() == MEMPOOL_HEIGHT) {
// Assume all mempool transaction confirm in the next block.
prev_heights[i] = tip.nHeight + 1;
} else {
prev_heights[i] = coin.GetHeight();
}
}
return prev_heights;
}
} // namespace
std::optional<LockPoints> CalculateLockPointsAtTip(CBlockIndex *tip,
const CCoinsView &coins_view,
const CTransaction &tx) {
assert(tip);
auto prev_heights{CalculatePrevHeights(*tip, coins_view, tx)};
if (!prev_heights.has_value()) {
return std::nullopt;
}
CBlockIndex next_tip;
next_tip.pprev = tip;
// When SequenceLocks() is called within ConnectBlock(), the height
// of the block *being* evaluated is what is used.
// Thus if we want to know if a transaction can be part of the
// *next* block, we need to use one more than
// active_chainstate.m_chain.Height()
next_tip.nHeight = tip->nHeight + 1;
const auto [min_height, min_time] = CalculateSequenceLocks(
tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prev_heights.value(), next_tip);
return LockPoints{min_height, min_time};
}
bool CheckSequenceLocksAtTip(CBlockIndex *tip, const LockPoints &lock_points) {
assert(tip != nullptr);
CBlockIndex index;
index.pprev = tip;
// CheckSequenceLocksAtTip() uses active_chainstate.m_chain.Height()+1 to
// evaluate height based locks because when SequenceLocks() is called within
// ConnectBlock(), the height of the block *being* evaluated is what is
// used. Thus if we want to know if a transaction can be part of the *next*
// block, we need to use one more than active_chainstate.m_chain.Height()
index.nHeight = tip->nHeight + 1;
return EvaluateSequenceLocks(index, {lock_points.height, lock_points.time});
}
// Command-line argument "-replayprotectionactivationtime=<timestamp>" will
// cause the node to switch to replay protected SigHash ForkID value when the
// median timestamp of the previous 11 blocks is greater than or equal to
// <timestamp>. Defaults to the pre-defined timestamp when not set.
static bool IsReplayProtectionEnabled(const Consensus::Params &params,
int64_t nMedianTimePast) {
return nMedianTimePast >= gArgs.GetIntArg("-replayprotectionactivationtime",
params.shibusawaActivationTime);
}
static bool IsReplayProtectionEnabled(const Consensus::Params &params,
const CBlockIndex *pindexPrev) {
if (pindexPrev == nullptr) {
return false;
}
return IsReplayProtectionEnabled(params, pindexPrev->GetMedianTimePast());
}
/**
* Checks to avoid mempool polluting consensus critical paths since cached
* signature and script validity results will be reused if we validate this
* transaction again during block validation.
*/
static bool CheckInputsFromMempoolAndCache(
const CTransaction &tx, TxValidationState &state,
const CCoinsViewCache &view, const CTxMemPool &pool, const uint32_t flags,
PrecomputedTransactionData &txdata, int &nSigChecksOut,
CCoinsViewCache &coins_tip) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) {
AssertLockHeld(cs_main);
AssertLockHeld(pool.cs);
assert(!tx.IsCoinBase());
for (const CTxIn &txin : tx.vin) {
const Coin &coin = view.AccessCoin(txin.prevout);
// This coin was checked in PreChecks and MemPoolAccept
// has been holding cs_main since then.
Assume(!coin.IsSpent());
if (coin.IsSpent()) {
return false;
}
// If the Coin is available, there are 2 possibilities:
// it is available in our current ChainstateActive UTXO set,
// or it's a UTXO provided by a transaction in our mempool.
// Ensure the scriptPubKeys in Coins from CoinsView are correct.
const CTransactionRef &txFrom = pool.get(txin.prevout.GetTxId());
if (txFrom) {
assert(txFrom->GetId() == txin.prevout.GetTxId());
assert(txFrom->vout.size() > txin.prevout.GetN());
assert(txFrom->vout[txin.prevout.GetN()] == coin.GetTxOut());
} else {
const Coin &coinFromUTXOSet = coins_tip.AccessCoin(txin.prevout);
assert(!coinFromUTXOSet.IsSpent());
assert(coinFromUTXOSet.GetTxOut() == coin.GetTxOut());
}
}
// Call CheckInputScripts() to cache signature and script validity against
// current tip consensus rules.
return CheckInputScripts(tx, state, view, flags, /*sigCacheStore=*/true,
/*scriptCacheStore=*/true, txdata, nSigChecksOut);
}
namespace {
class MemPoolAccept {
public:
MemPoolAccept(CTxMemPool &mempool, Chainstate &active_chainstate)
: m_pool(mempool), m_view(&m_dummy),
m_viewmempool(&active_chainstate.CoinsTip(), m_pool),
m_active_chainstate(active_chainstate) {}
// We put the arguments we're handed into a struct, so we can pass them
// around easier.
struct ATMPArgs {
const Config &m_config;
const int64_t m_accept_time;
const bool m_bypass_limits;
/*
* Return any outpoints which were not previously present in the coins
* cache, but were added as a result of validating the tx for mempool
* acceptance. This allows the caller to optionally remove the cache
* additions if the associated transaction ends up being rejected by
* the mempool.
*/
std::vector<COutPoint> &m_coins_to_uncache;
const bool m_test_accept;
const unsigned int m_heightOverride;
/**
* When true, the mempool will not be trimmed when any transactions are
* submitted in Finalize(). Instead, limits should be enforced at the
* end to ensure the package is not partially submitted.
*/
const bool m_package_submission;
/**
* When true, use package feerates instead of individual transaction
* feerates for fee-based policies such as mempool min fee and min relay
* fee.
*/
const bool m_package_feerates;
/** Parameters for single transaction mempool validation. */
static ATMPArgs SingleAccept(const Config &config, int64_t accept_time,
bool bypass_limits,
std::vector<COutPoint> &coins_to_uncache,
bool test_accept,
unsigned int heightOverride) {
return ATMPArgs{
config,
accept_time,
bypass_limits,
coins_to_uncache,
test_accept,
heightOverride,
/*package_submission=*/false,
/*package_feerates=*/false,
};
}
/**
* Parameters for test package mempool validation through
* testmempoolaccept.
*/
static ATMPArgs
PackageTestAccept(const Config &config, int64_t accept_time,
std::vector<COutPoint> &coins_to_uncache) {
return ATMPArgs{
config,
accept_time,
/*bypass_limits=*/false,
coins_to_uncache,
/*test_accept=*/true,
/*height_override=*/0,
// not submitting to mempool
/*package_submission=*/false,
/*package_feerates=*/false,
};
}
/** Parameters for child-with-unconfirmed-parents package validation. */
static ATMPArgs
PackageChildWithParents(const Config &config, int64_t accept_time,
std::vector<COutPoint> &coins_to_uncache) {
return ATMPArgs{
config,
accept_time,
/*bypass_limits=*/false,
coins_to_uncache,
/*test_accept=*/false,
/*height_override=*/0,
/*package_submission=*/true,
/*package_feerates=*/true,
};
}
/** Parameters for a single transaction within a package. */
static ATMPArgs SingleInPackageAccept(const ATMPArgs &package_args) {
return ATMPArgs{
/*config=*/package_args.m_config,
/*accept_time=*/package_args.m_accept_time,
/*bypass_limits=*/false,
/*coins_to_uncache=*/package_args.m_coins_to_uncache,
/*test_accept=*/package_args.m_test_accept,
/*height_override=*/package_args.m_heightOverride,
// do not LimitMempoolSize in Finalize()
/*package_submission=*/true,
// only 1 transaction
/*package_feerates=*/false,
};
}
private:
// Private ctor to avoid exposing details to clients and allowing the
// possibility of mixing up the order of the arguments. Use static
// functions above instead.
ATMPArgs(const Config &config, int64_t accept_time, bool bypass_limits,
std::vector<COutPoint> &coins_to_uncache, bool test_accept,
unsigned int height_override, bool package_submission,
bool package_feerates)
: m_config{config}, m_accept_time{accept_time},
m_bypass_limits{bypass_limits},
m_coins_to_uncache{coins_to_uncache}, m_test_accept{test_accept},
m_heightOverride{height_override},
m_package_submission{package_submission},
m_package_feerates(package_feerates) {}
};
// Single transaction acceptance
MempoolAcceptResult AcceptSingleTransaction(const CTransactionRef &ptx,
ATMPArgs &args)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Multiple transaction acceptance. Transactions may or may not be
* interdependent, but must not conflict with each other, and the
* transactions cannot already be in the mempool. Parents must come
* before children if any dependencies exist.
*/
PackageMempoolAcceptResult
AcceptMultipleTransactions(const std::vector<CTransactionRef> &txns,
ATMPArgs &args)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Submission of a subpackage.
* If subpackage size == 1, calls AcceptSingleTransaction() with adjusted
* ATMPArgs to avoid package policy restrictions like no CPFP carve out
* (PackageMempoolChecks), and creates a PackageMempoolAcceptResult wrapping
* the result.
*
* If subpackage size > 1, calls AcceptMultipleTransactions() with the
* provided ATMPArgs.
*
* Also cleans up all non-chainstate coins from m_view at the end.
*/
PackageMempoolAcceptResult
AcceptSubPackage(const std::vector<CTransactionRef> &subpackage,
ATMPArgs &args)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
/**
* Package (more specific than just multiple transactions) acceptance.
* Package must be a child with all of its unconfirmed parents, and
* topologically sorted.
*/
PackageMempoolAcceptResult AcceptPackage(const Package &package,
ATMPArgs &args)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
private:
// All the intermediate state that gets passed between the various levels
// of checking a given transaction.
struct Workspace {
Workspace(const CTransactionRef &ptx,
const uint32_t next_block_script_verify_flags)
: m_ptx(ptx),
m_next_block_script_verify_flags(next_block_script_verify_flags) {
}
/**
* Mempool entry constructed for this transaction.
* Constructed in PreChecks() but not inserted into the mempool until
* Finalize().
*/
std::unique_ptr<CTxMemPoolEntry> m_entry;
/**
* Virtual size of the transaction as used by the mempool, calculated
* using serialized size of the transaction and sigchecks.
*/
int64_t m_vsize;
/**
* Fees paid by this transaction: total input amounts subtracted by
* total output amounts.
*/
Amount m_base_fees;
/**
* Base fees + any fee delta set by the user with
* prioritisetransaction.
*/
Amount m_modified_fees;
/**
* If we're doing package validation (i.e. m_package_feerates=true), the
* "effective" package feerate of this transaction is the total fees
* divided by the total size of transactions (which may include its
* ancestors and/or descendants).
*/
CFeeRate m_package_feerate{Amount::zero()};
const CTransactionRef &m_ptx;
TxValidationState m_state;
/**
* A temporary cache containing serialized transaction data for
* signature verification.
* Reused across PreChecks and ConsensusScriptChecks.
*/
PrecomputedTransactionData m_precomputed_txdata;
// ABC specific flags that are used in both PreChecks and
// ConsensusScriptChecks
const uint32_t m_next_block_script_verify_flags;
int m_sig_checks_standard;
};
// Run the policy checks on a given transaction, excluding any script
// checks. Looks up inputs, calculates feerate, considers replacement,
// evaluates package limits, etc. As this function can be invoked for "free"
// by a peer, only tests that are fast should be done here (to avoid CPU
// DoS).
bool PreChecks(ATMPArgs &args, Workspace &ws)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
// Re-run the script checks, using consensus flags, and try to cache the
// result in the scriptcache. This should be done after
// PolicyScriptChecks(). This requires that all inputs either be in our
// utxo set or in the mempool.
bool ConsensusScriptChecks(const ATMPArgs &args, Workspace &ws)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
// Try to add the transaction to the mempool, removing any conflicts first.
// Returns true if the transaction is in the mempool after any size
// limiting is performed, false otherwise.
bool Finalize(const ATMPArgs &args, Workspace &ws)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
// Submit all transactions to the mempool and call ConsensusScriptChecks to
// add to the script cache - should only be called after successful
// validation of all transactions in the package.
// Does not call LimitMempoolSize(), so mempool max_size_bytes may be
// temporarily exceeded.
bool SubmitPackage(const ATMPArgs &args, std::vector<Workspace> &workspaces,
PackageValidationState &package_state,
std::map<TxId, MempoolAcceptResult> &results)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
// Compare a package's feerate against minimum allowed.
bool CheckFeeRate(size_t package_size, size_t package_vsize,
Amount package_fee, TxValidationState &state)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_pool.cs) {
AssertLockHeld(::cs_main);
AssertLockHeld(m_pool.cs);
const Amount mempoolRejectFee =
m_pool.GetMinFee().GetFee(package_vsize);
if (mempoolRejectFee > Amount::zero() &&
package_fee < mempoolRejectFee) {
return state.Invalid(
TxValidationResult::TX_PACKAGE_RECONSIDERABLE,
"mempool min fee not met",
strprintf("%d < %d", package_fee, mempoolRejectFee));
}
// Do not change this to use virtualsize without coordinating a network
// policy upgrade.
if (package_fee < m_pool.m_min_relay_feerate.GetFee(package_size)) {
return state.Invalid(
TxValidationResult::TX_PACKAGE_RECONSIDERABLE,
"min relay fee not met",
strprintf("%d < %d", package_fee,
m_pool.m_min_relay_feerate.GetFee(package_size)));
}
return true;
}
private:
CTxMemPool &m_pool;
CCoinsViewCache m_view;
CCoinsViewMemPool m_viewmempool;
CCoinsView m_dummy;
Chainstate &m_active_chainstate;
};
bool MemPoolAccept::PreChecks(ATMPArgs &args, Workspace &ws) {
AssertLockHeld(cs_main);
AssertLockHeld(m_pool.cs);
const CTransactionRef &ptx = ws.m_ptx;
const CTransaction &tx = *ws.m_ptx;
const TxId &txid = ws.m_ptx->GetId();
// Copy/alias what we need out of args
const int64_t nAcceptTime = args.m_accept_time;
const bool bypass_limits = args.m_bypass_limits;
std::vector<COutPoint> &coins_to_uncache = args.m_coins_to_uncache;
const unsigned int heightOverride = args.m_heightOverride;
// Alias what we need out of ws
TxValidationState &state = ws.m_state;
// Coinbase is only valid in a block, not as a loose transaction.
if (!CheckRegularTransaction(tx, state)) {
// state filled in by CheckRegularTransaction.
return false;
}
// Rather not work on nonstandard transactions (unless -testnet)
std::string reason;
if (m_pool.m_require_standard &&
!IsStandardTx(tx, m_pool.m_max_datacarrier_bytes,
m_pool.m_permit_bare_multisig,
m_pool.m_dust_relay_feerate, reason)) {
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason);
}
// Only accept nLockTime-using transactions that can be mined in the next
// block; we don't want our mempool filled up with transactions that can't
// be mined yet.
TxValidationState ctxState;
if (!ContextualCheckTransactionForCurrentBlock(
*Assert(m_active_chainstate.m_chain.Tip()),
args.m_config.GetChainParams().GetConsensus(), tx, ctxState)) {
// We copy the state from a dummy to ensure we don't increase the
// ban score of peer for transaction that could be valid in the future.
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND,
ctxState.GetRejectReason(),
ctxState.GetDebugMessage());
}
// Is it already in the memory pool?
if (m_pool.exists(txid)) {
return state.Invalid(TxValidationResult::TX_DUPLICATE,
"txn-already-in-mempool");
}
// Check for conflicts with in-memory transactions
for (const CTxIn &txin : tx.vin) {
if (const auto ptxConflicting = m_pool.GetConflictTx(txin.prevout)) {
if (m_pool.isAvalancheFinalized(ptxConflicting->GetId())) {
return state.Invalid(TxValidationResult::TX_CONFLICT,
"finalized-tx-conflict");
}
return state.Invalid(
TxValidationResult::TX_AVALANCHE_RECONSIDERABLE,
"txn-mempool-conflict");
}
}
m_view.SetBackend(m_viewmempool);
const CCoinsViewCache &coins_cache = m_active_chainstate.CoinsTip();
// Do all inputs exist?
for (const CTxIn &txin : tx.vin) {
if (!coins_cache.HaveCoinInCache(txin.prevout)) {
coins_to_uncache.push_back(txin.prevout);
}
// Note: this call may add txin.prevout to the coins cache
// (coins_cache.cacheCoins) by way of FetchCoin(). It should be
// removed later (via coins_to_uncache) if this tx turns out to be
// invalid.
if (!m_view.HaveCoin(txin.prevout)) {
// Are inputs missing because we already have the tx?
for (size_t out = 0; out < tx.vout.size(); out++) {
// Optimistically just do efficient check of cache for
// outputs.
if (coins_cache.HaveCoinInCache(COutPoint(txid, out))) {
return state.Invalid(TxValidationResult::TX_DUPLICATE,
"txn-already-known");
}
}
// Otherwise assume this might be an orphan tx for which we just
// haven't seen parents yet.
return state.Invalid(TxValidationResult::TX_MISSING_INPUTS,
"bad-txns-inputs-missingorspent");
}
}
// Are the actual inputs available?
if (!m_view.HaveInputs(tx)) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
"bad-txns-inputs-spent");
}
// Bring the best block into scope.
m_view.GetBestBlock();
// we have all inputs cached now, so switch back to dummy (to protect
// against bugs where we pull more inputs from disk that miss being
// added to coins_to_uncache)
m_view.SetBackend(m_dummy);
assert(m_active_chainstate.m_blockman.LookupBlockIndex(
m_view.GetBestBlock()) == m_active_chainstate.m_chain.Tip());
// Only accept BIP68 sequence locked transactions that can be mined in
// the next block; we don't want our mempool filled up with transactions
// that can't be mined yet.
// Pass in m_view which has all of the relevant inputs cached. Note that,
// since m_view's backend was removed, it no longer pulls coins from the
// mempool.
const std::optional<LockPoints> lock_points{CalculateLockPointsAtTip(
m_active_chainstate.m_chain.Tip(), m_view, tx)};
if (!lock_points.has_value() ||
!CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(),
*lock_points)) {
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND,
"non-BIP68-final");
}
// The mempool holds txs for the next block, so pass height+1 to
// CheckTxInputs
if (!Consensus::CheckTxInputs(tx, state, m_view,
m_active_chainstate.m_chain.Height() + 1,
ws.m_base_fees)) {
// state filled in by CheckTxInputs
return false;
}
// Check for non-standard pay-to-script-hash in inputs
if (m_pool.m_require_standard &&
!AreInputsStandard(tx, m_view, ws.m_next_block_script_verify_flags)) {
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD,
"bad-txns-nonstandard-inputs");
}
// ws.m_modified_fess includes any fee deltas from PrioritiseTransaction
ws.m_modified_fees = ws.m_base_fees;
m_pool.ApplyDelta(txid, ws.m_modified_fees);
unsigned int nSize = tx.GetTotalSize();
// Validate input scripts against standard script flags.
const uint32_t scriptVerifyFlags =
ws.m_next_block_script_verify_flags | STANDARD_SCRIPT_VERIFY_FLAGS;
ws.m_precomputed_txdata = PrecomputedTransactionData{tx};
if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, true, false,
ws.m_precomputed_txdata, ws.m_sig_checks_standard)) {
// State filled in by CheckInputScripts
return false;
}
ws.m_entry = std::make_unique<CTxMemPoolEntry>(
ptx, ws.m_base_fees, nAcceptTime,
heightOverride ? heightOverride : m_active_chainstate.m_chain.Height(),
ws.m_sig_checks_standard, lock_points.value());
ws.m_vsize = ws.m_entry->GetTxVirtualSize();
// No individual transactions are allowed below the min relay feerate except
// from disconnected blocks. This requirement, unlike CheckFeeRate, cannot
// be bypassed using m_package_feerates because, while a tx could be package
// CPFP'd when entering the mempool, we do not have a DoS-resistant method
// of ensuring the tx remains bumped. For example, the fee-bumping child
// could disappear due to a replacement.
if (!bypass_limits &&
ws.m_modified_fees <
m_pool.m_min_relay_feerate.GetFee(ws.m_ptx->GetTotalSize())) {
// Even though this is a fee-related failure, this result is
// TX_MEMPOOL_POLICY, not TX_PACKAGE_RECONSIDERABLE, because it cannot
// be bypassed using package validation.
return state.Invalid(
TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met",
strprintf("%d < %d", ws.m_modified_fees,
m_pool.m_min_relay_feerate.GetFee(nSize)));
}
// No individual transactions are allowed below the mempool min feerate
// except from disconnected blocks and transactions in a package. Package
// transactions will be checked using package feerate later.
if (!bypass_limits && !args.m_package_feerates &&
!CheckFeeRate(nSize, ws.m_vsize, ws.m_modified_fees, state)) {
return false;
}
return true;
}
bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs &args, Workspace &ws) {
AssertLockHeld(cs_main);
AssertLockHeld(m_pool.cs);
const CTransaction &tx = *ws.m_ptx;
const TxId &txid = tx.GetId();
TxValidationState &state = ws.m_state;
// Check again against the next block's script verification flags
// to cache our script execution flags.
//
// This is also useful in case of bugs in the standard flags that cause
// transactions to pass as valid when they're actually invalid. For
// instance the STRICTENC flag was incorrectly allowing certain CHECKSIG
// NOT scripts to pass, even though they were invalid.
//
// There is a similar check in CreateNewBlock() to prevent creating
// invalid blocks (using TestBlockValidity), however allowing such
// transactions into the mempool can be exploited as a DoS attack.
int nSigChecksConsensus;
if (!CheckInputsFromMempoolAndCache(
tx, state, m_view, m_pool, ws.m_next_block_script_verify_flags,
ws.m_precomputed_txdata, nSigChecksConsensus,
m_active_chainstate.CoinsTip())) {
// This can occur under some circumstances, if the node receives an
// unrequested tx which is invalid due to new consensus rules not
// being activated yet (during IBD).
LogPrintf("BUG! PLEASE REPORT THIS! CheckInputScripts failed against "
"latest-block but not STANDARD flags %s, %s\n",
txid.ToString(), state.ToString());
return Assume(false);
}
if (ws.m_sig_checks_standard != nSigChecksConsensus) {
// We can't accept this transaction as we've used the standard count
// for the mempool/mining, but the consensus count will be enforced
// in validation (we don't want to produce bad block templates).
return error(
"%s: BUG! PLEASE REPORT THIS! SigChecks count differed between "
"standard and consensus flags in %s",
__func__, txid.ToString());
}
return true;
}
// Get the coins spent by ptx from the coins_view. Assumes coins are present.
static std::vector<Coin> getSpentCoins(const CTransactionRef &ptx,
const CCoinsViewCache &coins_view) {
std::vector<Coin> spent_coins;
spent_coins.reserve(ptx->vin.size());
for (const CTxIn &input : ptx->vin) {
Coin coin;
const bool coinFound = coins_view.GetCoin(input.prevout, coin);
Assume(coinFound);
spent_coins.push_back(std::move(coin));
}
return spent_coins;
}
bool MemPoolAccept::Finalize(const ATMPArgs &args, Workspace &ws) {
AssertLockHeld(cs_main);
AssertLockHeld(m_pool.cs);
const TxId &txid = ws.m_ptx->GetId();
TxValidationState &state = ws.m_state;
const bool bypass_limits = args.m_bypass_limits;
// Store transaction in memory
CTxMemPoolEntry *pentry = ws.m_entry.release();
auto entry = CTxMemPoolEntryRef::acquire(pentry);
m_pool.addUnchecked(entry);
GetMainSignals().TransactionAddedToMempool(
ws.m_ptx,
std::make_shared<const std::vector<Coin>>(
getSpentCoins(ws.m_ptx, m_view)),
m_pool.GetAndIncrementSequence());
// Trim mempool and check if tx was trimmed.
// If we are validating a package, don't trim here because we could evict a
// previous transaction in the package. LimitMempoolSize() should be called
// at the very end to make sure the mempool is still within limits and
// package submission happens atomically.
if (!args.m_package_submission && !bypass_limits) {
m_pool.LimitSize(m_active_chainstate.CoinsTip());
if (!m_pool.exists(txid)) {
// The tx no longer meets our (new) mempool minimum feerate but
// could be reconsidered in a package.
return state.Invalid(TxValidationResult::TX_PACKAGE_RECONSIDERABLE,
"mempool full");
}
}
return true;
}
bool MemPoolAccept::SubmitPackage(
const ATMPArgs &args, std::vector<Workspace> &workspaces,
PackageValidationState &package_state,
std::map<TxId, MempoolAcceptResult> &results) {
AssertLockHeld(cs_main);
AssertLockHeld(m_pool.cs);
// Sanity check: none of the transactions should be in the mempool.
assert(std::all_of(
workspaces.cbegin(), workspaces.cend(),
[this](const auto &ws) { return !m_pool.exists(ws.m_ptx->GetId()); }));
bool all_submitted = true;
// ConsensusScriptChecks adds to the script cache and is therefore
// consensus-critical; CheckInputsFromMempoolAndCache asserts that
// transactions only spend coins available from the mempool or UTXO set.
// Submit each transaction to the mempool immediately after calling
// ConsensusScriptChecks to make the outputs available for subsequent
// transactions.
for (Workspace &ws : workspaces) {
if (!ConsensusScriptChecks(args, ws)) {
results.emplace(ws.m_ptx->GetId(),
MempoolAcceptResult::Failure(ws.m_state));
// Since PreChecks() passed, this should never fail.
all_submitted = false;
package_state.Invalid(
PackageValidationResult::PCKG_MEMPOOL_ERROR,
strprintf("BUG! PolicyScriptChecks succeeded but "
"ConsensusScriptChecks failed: %s",
ws.m_ptx->GetId().ToString()));
}
// If we call LimitMempoolSize() for each individual Finalize(), the
// mempool will not take the transaction's descendant feerate into
// account because it hasn't seen them yet. Also, we risk evicting a
// transaction that a subsequent package transaction depends on.
// Instead, allow the mempool to temporarily bypass limits, the maximum
// package size) while submitting transactions individually and then
// trim at the very end.
if (!Finalize(args, ws)) {
results.emplace(ws.m_ptx->GetId(),
MempoolAcceptResult::Failure(ws.m_state));
// Since LimitMempoolSize() won't be called, this should never fail.
all_submitted = false;
package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR,
strprintf("BUG! Adding to mempool failed: %s",
ws.m_ptx->GetId().ToString()));
}
}
// It may or may not be the case that all the transactions made it into the
// mempool. Regardless, make sure we haven't exceeded max mempool size.
m_pool.LimitSize(m_active_chainstate.CoinsTip());
std::vector<TxId> all_package_txids;
all_package_txids.reserve(workspaces.size());
std::transform(workspaces.cbegin(), workspaces.cend(),
std::back_inserter(all_package_txids),
[](const auto &ws) { return ws.m_ptx->GetId(); });
// Add successful results. The returned results may change later if
// LimitMempoolSize() evicts them.
for (Workspace &ws : workspaces) {
const auto effective_feerate =
args.m_package_feerates
? ws.m_package_feerate
: CFeeRate{ws.m_modified_fees,
static_cast<uint32_t>(ws.m_vsize)};
const auto effective_feerate_txids =
args.m_package_feerates ? all_package_txids
: std::vector<TxId>({ws.m_ptx->GetId()});
results.emplace(ws.m_ptx->GetId(),
MempoolAcceptResult::Success(ws.m_vsize, ws.m_base_fees,
effective_feerate,
effective_feerate_txids));
}
return all_submitted;
}
MempoolAcceptResult
MemPoolAccept::AcceptSingleTransaction(const CTransactionRef &ptx,
ATMPArgs &args) {
AssertLockHeld(cs_main);
// mempool "read lock" (held through
// GetMainSignals().TransactionAddedToMempool())
LOCK(m_pool.cs);
const CBlockIndex *tip = m_active_chainstate.m_chain.Tip();
Workspace ws(ptx,
GetNextBlockScriptFlags(tip, m_active_chainstate.m_chainman));
const std::vector<TxId> single_txid{ws.m_ptx->GetId()};
// Perform the inexpensive checks first and avoid hashing and signature
// verification unless those checks pass, to mitigate CPU exhaustion
// denial-of-service attacks.
if (!PreChecks(args, ws)) {
if (ws.m_state.GetResult() ==
TxValidationResult::TX_PACKAGE_RECONSIDERABLE) {
// Failed for fee reasons. Provide the effective feerate and which
// tx was included.
return MempoolAcceptResult::FeeFailure(
ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize),
single_txid);
}
return MempoolAcceptResult::Failure(ws.m_state);
}
if (!ConsensusScriptChecks(args, ws)) {
return MempoolAcceptResult::Failure(ws.m_state);
}
const TxId txid = ptx->GetId();
// Mempool sanity check -- in our new mempool no tx can be added if its
// outputs are already spent in the mempool (that is, no children before
// parents allowed; the mempool must be consistent at all times).
//
// This means that on reorg, the disconnectpool *must* always import
// the existing mempool tx's, clear the mempool, and then re-add
// remaining tx's in topological order via this function. Our new mempool
// has fast adds, so this is ok.
if (auto it = m_pool.mapNextTx.lower_bound(COutPoint{txid, 0});
it != m_pool.mapNextTx.end() && it->first->GetTxId() == txid) {
LogPrintf("%s: BUG! PLEASE REPORT THIS! Attempt to add txid %s, but "
"its outputs are already spent in the "
"mempool\n",
__func__, txid.ToString());
ws.m_state.Invalid(TxValidationResult::TX_CHILD_BEFORE_PARENT,
"txn-child-before-parent");
return MempoolAcceptResult::Failure(ws.m_state);
}
const CFeeRate effective_feerate{ws.m_modified_fees,
static_cast<uint32_t>(ws.m_vsize)};
// Tx was accepted, but not added
if (args.m_test_accept) {
return MempoolAcceptResult::Success(ws.m_vsize, ws.m_base_fees,
effective_feerate, single_txid);
}
if (!Finalize(args, ws)) {
// The only possible failure reason is fee-related (mempool full).
// Failed for fee reasons. Provide the effective feerate and which txns
// were included.
Assume(ws.m_state.GetResult() ==
TxValidationResult::TX_PACKAGE_RECONSIDERABLE);
return MempoolAcceptResult::FeeFailure(
ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), single_txid);
}
return MempoolAcceptResult::Success(ws.m_vsize, ws.m_base_fees,
effective_feerate, single_txid);
}
PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(
const std::vector<CTransactionRef> &txns, ATMPArgs &args) {
AssertLockHeld(cs_main);
// These context-free package limits can be done before taking the mempool
// lock.
PackageValidationState package_state;
if (!CheckPackage(txns, package_state)) {
return PackageMempoolAcceptResult(package_state, {});
}
std::vector<Workspace> workspaces{};
workspaces.reserve(txns.size());
std::transform(
txns.cbegin(), txns.cend(), std::back_inserter(workspaces),
[this](const auto &tx) {
return Workspace(
tx, GetNextBlockScriptFlags(m_active_chainstate.m_chain.Tip(),
m_active_chainstate.m_chainman));
});
std::map<TxId, MempoolAcceptResult> results;
LOCK(m_pool.cs);
// Do all PreChecks first and fail fast to avoid running expensive script
// checks when unnecessary.
std::vector<TxId> valid_txids;
for (Workspace &ws : workspaces) {
if (!PreChecks(args, ws)) {
package_state.Invalid(PackageValidationResult::PCKG_TX,
"transaction failed");
// Exit early to avoid doing pointless work. Update the failed tx
// result; the rest are unfinished.
results.emplace(ws.m_ptx->GetId(),
MempoolAcceptResult::Failure(ws.m_state));
return PackageMempoolAcceptResult(package_state,
std::move(results));
}
// Make the coins created by this transaction available for subsequent
// transactions in the package to spend.
m_viewmempool.PackageAddTransaction(ws.m_ptx);
valid_txids.push_back(ws.m_ptx->GetId());
}
// Transactions must meet two minimum feerates: the mempool minimum fee and
// min relay fee. For transactions consisting of exactly one child and its
// parents, it suffices to use the package feerate
// (total modified fees / total size or vsize) to check this requirement.
// Note that this is an aggregate feerate; this function has not checked
// that there are transactions too low feerate to pay for themselves, or
// that the child transactions are higher feerate than their parents. Using
// aggregate feerate may allow "parents pay for child" behavior and permit
// a child that is below mempool minimum feerate. To avoid these behaviors,
// callers of AcceptMultipleTransactions need to restrict txns topology
// (e.g. to ancestor sets) and check the feerates of individuals and
// subsets.
const auto m_total_size = std::accumulate(
workspaces.cbegin(), workspaces.cend(), int64_t{0},
[](int64_t sum, auto &ws) { return sum + ws.m_ptx->GetTotalSize(); });
const auto m_total_vsize =
std::accumulate(workspaces.cbegin(), workspaces.cend(), int64_t{0},
[](int64_t sum, auto &ws) { return sum + ws.m_vsize; });
const auto m_total_modified_fees = std::accumulate(
workspaces.cbegin(), workspaces.cend(), Amount::zero(),
[](Amount sum, auto &ws) { return sum + ws.m_modified_fees; });
const CFeeRate package_feerate(m_total_modified_fees, m_total_vsize);
std::vector<TxId> all_package_txids;
all_package_txids.reserve(workspaces.size());
std::transform(workspaces.cbegin(), workspaces.cend(),
std::back_inserter(all_package_txids),
[](const auto &ws) { return ws.m_ptx->GetId(); });
TxValidationState placeholder_state;
if (args.m_package_feerates &&
!CheckFeeRate(m_total_size, m_total_vsize, m_total_modified_fees,
placeholder_state)) {
package_state.Invalid(PackageValidationResult::PCKG_TX,
"transaction failed");
return PackageMempoolAcceptResult(
package_state, {{workspaces.back().m_ptx->GetId(),
MempoolAcceptResult::FeeFailure(
placeholder_state,
CFeeRate(m_total_modified_fees, m_total_vsize),
all_package_txids)}});
}
for (Workspace &ws : workspaces) {
ws.m_package_feerate = package_feerate;
const TxId &ws_txid = ws.m_ptx->GetId();
if (args.m_test_accept &&
std::find(valid_txids.begin(), valid_txids.end(), ws_txid) !=
valid_txids.end()) {
const auto effective_feerate =
args.m_package_feerates
? ws.m_package_feerate
: CFeeRate{ws.m_modified_fees,
static_cast<uint32_t>(ws.m_vsize)};
const auto effective_feerate_txids =
args.m_package_feerates ? all_package_txids
: std::vector<TxId>{ws.m_ptx->GetId()};
// When test_accept=true, transactions that pass PreChecks
// are valid because there are no further mempool checks (passing
// PreChecks implies passing ConsensusScriptChecks).
results.emplace(ws_txid,
MempoolAcceptResult::Success(
ws.m_vsize, ws.m_base_fees, effective_feerate,
effective_feerate_txids));
}
}
if (args.m_test_accept) {
return PackageMempoolAcceptResult(package_state, std::move(results));
}
if (!SubmitPackage(args, workspaces, package_state, results)) {
// PackageValidationState filled in by SubmitPackage().
return PackageMempoolAcceptResult(package_state, std::move(results));
}
return PackageMempoolAcceptResult(package_state, std::move(results));
}
PackageMempoolAcceptResult
MemPoolAccept::AcceptSubPackage(const std::vector<CTransactionRef> &subpackage,
ATMPArgs &args) {
AssertLockHeld(::cs_main);
AssertLockHeld(m_pool.cs);
auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_pool.cs) {
if (subpackage.size() > 1) {
return AcceptMultipleTransactions(subpackage, args);
}
const auto &tx = subpackage.front();
ATMPArgs single_args = ATMPArgs::SingleInPackageAccept(args);
const auto single_res = AcceptSingleTransaction(tx, single_args);
PackageValidationState package_state_wrapped;
if (single_res.m_result_type !=
MempoolAcceptResult::ResultType::VALID) {
package_state_wrapped.Invalid(PackageValidationResult::PCKG_TX,
"transaction failed");
}
return PackageMempoolAcceptResult(package_state_wrapped,
{{tx->GetId(), single_res}});
}();
// Clean up m_view and m_viewmempool so that other subpackage evaluations
// don't have access to coins they shouldn't. Keep some coins in order to
// minimize re-fetching coins from the UTXO set.
//
// There are 3 kinds of coins in m_view:
// (1) Temporary coins from the transactions in subpackage, constructed by
// m_viewmempool.
// (2) Mempool coins from transactions in the mempool, constructed by
// m_viewmempool.
// (3) Confirmed coins fetched from our current UTXO set.
//
// (1) Temporary coins need to be removed, regardless of whether the
// transaction was submitted. If the transaction was submitted to the
// mempool, m_viewmempool will be able to fetch them from there. If it
// wasn't submitted to mempool, it is incorrect to keep them - future calls
// may try to spend those coins that don't actually exist.
// (2) Mempool coins also need to be removed. If the mempool contents have
// changed as a result of submitting or replacing transactions, coins
// previously fetched from mempool may now be spent or nonexistent. Those
// coins need to be deleted from m_view.
// (3) Confirmed coins don't need to be removed. The chainstate has not
// changed (we are holding cs_main and no blocks have been processed) so the
// confirmed tx cannot disappear like a mempool tx can. The coin may now be
// spent after we submitted a tx to mempool, but we have already checked
// that the package does not have 2 transactions spending the same coin.
// Keeping them in m_view is an optimization to not re-fetch confirmed coins
// if we later look up inputs for this transaction again.
for (const auto &outpoint : m_viewmempool.GetNonBaseCoins()) {
// In addition to resetting m_viewmempool, we also need to manually
// delete these coins from m_view because it caches copies of the coins
// it fetched from m_viewmempool previously.
m_view.Uncache(outpoint);
}
// This deletes the temporary and mempool coins.
m_viewmempool.Reset();
return result;
}
PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package &package,
ATMPArgs &args) {
AssertLockHeld(cs_main);
// Used if returning a PackageMempoolAcceptResult directly from this
// function.
PackageValidationState package_state_quit_early;
// Check that the package is well-formed. If it isn't, we won't try to
// validate any of the transactions and thus won't return any
// MempoolAcceptResults, just a package-wide error.
// Context-free package checks.
if (!CheckPackage(package, package_state_quit_early)) {
return PackageMempoolAcceptResult(package_state_quit_early, {});
}
// All transactions in the package must be a parent of the last transaction.
// This is just an opportunity for us to fail fast on a context-free check
// without taking the mempool lock.
if (!IsChildWithParents(package)) {
package_state_quit_early.Invalid(PackageValidationResult::PCKG_POLICY,
"package-not-child-with-parents");
return PackageMempoolAcceptResult(package_state_quit_early, {});
}
// IsChildWithParents() guarantees the package is > 1 transactions.
assert(package.size() > 1);
// The package must be 1 child with all of its unconfirmed parents. The
// package is expected to be sorted, so the last transaction is the child.
const auto &child = package.back();
std::unordered_set<TxId, SaltedTxIdHasher> unconfirmed_parent_txids;
std::transform(
package.cbegin(), package.cend() - 1,
std::inserter(unconfirmed_parent_txids, unconfirmed_parent_txids.end()),
[](const auto &tx) { return tx->GetId(); });
// All child inputs must refer to a preceding package transaction or a
// confirmed UTXO. The only way to verify this is to look up the child's
// inputs in our current coins view (not including mempool), and enforce
// that all parents not present in the package be available at chain tip.
// Since this check can bring new coins into the coins cache, keep track of
// these coins and uncache them if we don't end up submitting this package
// to the mempool.
const CCoinsViewCache &coins_tip_cache = m_active_chainstate.CoinsTip();
for (const auto &input : child->vin) {
if (!coins_tip_cache.HaveCoinInCache(input.prevout)) {
args.m_coins_to_uncache.push_back(input.prevout);
}
}
// Using the MemPoolAccept m_view cache allows us to look up these same
// coins faster later. This should be connecting directly to CoinsTip, not
// to m_viewmempool, because we specifically require inputs to be confirmed
// if they aren't in the package.
m_view.SetBackend(m_active_chainstate.CoinsTip());
const auto package_or_confirmed = [this, &unconfirmed_parent_txids](
const auto &input) {
return unconfirmed_parent_txids.count(input.prevout.GetTxId()) > 0 ||
m_view.HaveCoin(input.prevout);
};
if (!std::all_of(child->vin.cbegin(), child->vin.cend(),
package_or_confirmed)) {
package_state_quit_early.Invalid(
PackageValidationResult::PCKG_POLICY,
"package-not-child-with-unconfirmed-parents");
return PackageMempoolAcceptResult(package_state_quit_early, {});
}
// Protect against bugs where we pull more inputs from disk that miss being
// added to coins_to_uncache. The backend will be connected again when
// needed in PreChecks.
m_view.SetBackend(m_dummy);
LOCK(m_pool.cs);
// Stores results from which we will create the returned
// PackageMempoolAcceptResult. A result may be changed if a mempool
// transaction is evicted later due to LimitMempoolSize().
std::map<TxId, MempoolAcceptResult> results_final;
// Results from individual validation which will be returned if no other
// result is available for this transaction. "Nonfinal" because if a
// transaction fails by itself but succeeds later (i.e. when evaluated with
// a fee-bumping child), the result in this map may be discarded.
std::map<TxId, MempoolAcceptResult> individual_results_nonfinal;
bool quit_early{false};
std::vector<CTransactionRef> txns_package_eval;
for (const auto &tx : package) {
const auto &txid = tx->GetId();
// An already confirmed tx is treated as one not in mempool, because all
// we know is that the inputs aren't available.
if (m_pool.exists(txid)) {
// Exact transaction already exists in the mempool.
// Node operators are free to set their mempool policies however
// they please, nodes may receive transactions in different orders,
// and malicious counterparties may try to take advantage of policy
// differences to pin or delay propagation of transactions. As such,
// it's possible for some package transaction(s) to already be in
// the mempool, and we don't want to reject the entire package in
// that case (as that could be a censorship vector). De-duplicate
// the transactions that are already in the mempool, and only call
// AcceptMultipleTransactions() with the new transactions. This
// ensures we don't double-count transaction counts and sizes when
// checking ancestor/descendant limits, or double-count transaction
// fees for fee-related policy.
auto iter = m_pool.GetIter(txid);
assert(iter != std::nullopt);
results_final.emplace(txid, MempoolAcceptResult::MempoolTx(
(*iter.value())->GetTxSize(),
(*iter.value())->GetFee()));
} else {
// Transaction does not already exist in the mempool.
// Try submitting the transaction on its own.
const auto single_package_res = AcceptSubPackage({tx}, args);
const auto &single_res = single_package_res.m_tx_results.at(txid);
if (single_res.m_result_type ==
MempoolAcceptResult::ResultType::VALID) {
// The transaction succeeded on its own and is now in the
// mempool. Don't include it in package validation, because its
// fees should only be "used" once.
assert(m_pool.exists(txid));
results_final.emplace(txid, single_res);
} else if (single_res.m_state.GetResult() !=
TxValidationResult::TX_PACKAGE_RECONSIDERABLE &&
single_res.m_state.GetResult() !=
TxValidationResult::TX_MISSING_INPUTS) {
// Package validation policy only differs from individual policy
// in its evaluation of feerate. For example, if a transaction
// fails here due to violation of a consensus rule, the result
// will not change when it is submitted as part of a package. To
// minimize the amount of repeated work, unless the transaction
// fails due to feerate or missing inputs (its parent is a
// previous transaction in the package that failed due to
// feerate), don't run package validation. Note that this
// decision might not make sense if different types of packages
// are allowed in the future. Continue individually validating
// the rest of the transactions, because some of them may still
// be valid.
quit_early = true;
package_state_quit_early.Invalid(
PackageValidationResult::PCKG_TX, "transaction failed");
individual_results_nonfinal.emplace(txid, single_res);
} else {
individual_results_nonfinal.emplace(txid, single_res);
txns_package_eval.push_back(tx);
}
}
}
auto multi_submission_result =
quit_early || txns_package_eval.empty()
? PackageMempoolAcceptResult(package_state_quit_early, {})
: AcceptSubPackage(txns_package_eval, args);
PackageValidationState &package_state_final =
multi_submission_result.m_state;
// Make sure we haven't exceeded max mempool size.
// Package transactions that were submitted to mempool or already in mempool
// may be evicted.
m_pool.LimitSize(m_active_chainstate.CoinsTip());
for (const auto &tx : package) {
const auto &txid = tx->GetId();
if (multi_submission_result.m_tx_results.count(txid) > 0) {
// We shouldn't have re-submitted if the tx result was already in
// results_final.
Assume(results_final.count(txid) == 0);
// If it was submitted, check to see if the tx is still in the
// mempool. It could have been evicted due to LimitMempoolSize()
// above.
const auto &txresult =
multi_submission_result.m_tx_results.at(txid);
if (txresult.m_result_type ==
MempoolAcceptResult::ResultType::VALID &&
!m_pool.exists(txid)) {
package_state_final.Invalid(PackageValidationResult::PCKG_TX,
"transaction failed");
TxValidationState mempool_full_state;
mempool_full_state.Invalid(
TxValidationResult::TX_MEMPOOL_POLICY, "mempool full");
results_final.emplace(
txid, MempoolAcceptResult::Failure(mempool_full_state));
} else {
results_final.emplace(txid, txresult);
}
} else if (const auto final_it{results_final.find(txid)};
final_it != results_final.end()) {
// Already-in-mempool transaction. Check to see if it's still there,
// as it could have been evicted when LimitMempoolSize() was called.
Assume(final_it->second.m_result_type !=
MempoolAcceptResult::ResultType::INVALID);
Assume(individual_results_nonfinal.count(txid) == 0);
if (!m_pool.exists(tx->GetId())) {
package_state_final.Invalid(PackageValidationResult::PCKG_TX,
"transaction failed");
TxValidationState mempool_full_state;
mempool_full_state.Invalid(
TxValidationResult::TX_MEMPOOL_POLICY, "mempool full");
// Replace the previous result.
results_final.erase(txid);
results_final.emplace(
txid, MempoolAcceptResult::Failure(mempool_full_state));
}
} else if (const auto non_final_it{
individual_results_nonfinal.find(txid)};
non_final_it != individual_results_nonfinal.end()) {
Assume(non_final_it->second.m_result_type ==
MempoolAcceptResult::ResultType::INVALID);
// Interesting result from previous processing.
results_final.emplace(txid, non_final_it->second);
}
}
Assume(results_final.size() == package.size());
return PackageMempoolAcceptResult(package_state_final,
std::move(results_final));
}
} // namespace
MempoolAcceptResult AcceptToMemoryPool(Chainstate &active_chainstate,
const CTransactionRef &tx,
int64_t accept_time, bool bypass_limits,
bool test_accept,
unsigned int heightOverride) {
AssertLockHeld(::cs_main);
assert(active_chainstate.GetMempool() != nullptr);
CTxMemPool &pool{*active_chainstate.GetMempool()};
std::vector<COutPoint> coins_to_uncache;
auto args = MemPoolAccept::ATMPArgs::SingleAccept(
active_chainstate.m_chainman.GetConfig(), accept_time, bypass_limits,
coins_to_uncache, test_accept, heightOverride);
const MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate)
.AcceptSingleTransaction(tx, args);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
// Remove coins that were not present in the coins cache before calling
// ATMPW; this is to prevent memory DoS in case we receive a large
// number of invalid transactions that attempt to overrun the in-memory
// coins cache
// (`CCoinsViewCache::cacheCoins`).
for (const COutPoint &outpoint : coins_to_uncache) {
active_chainstate.CoinsTip().Uncache(outpoint);
}
}
// After we've (potentially) uncached entries, ensure our coins cache is
// still within its size limits
BlockValidationState stateDummy;
active_chainstate.FlushStateToDisk(stateDummy, FlushStateMode::PERIODIC);
return result;
}
PackageMempoolAcceptResult ProcessNewPackage(Chainstate &active_chainstate,
CTxMemPool &pool,
const Package &package,
bool test_accept) {
AssertLockHeld(cs_main);
assert(!package.empty());
assert(std::all_of(package.cbegin(), package.cend(),
[](const auto &tx) { return tx != nullptr; }));
const Config &config = active_chainstate.m_chainman.GetConfig();
std::vector<COutPoint> coins_to_uncache;
const auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
AssertLockHeld(cs_main);
if (test_accept) {
auto args = MemPoolAccept::ATMPArgs::PackageTestAccept(
config, GetTime(), coins_to_uncache);
return MemPoolAccept(pool, active_chainstate)
.AcceptMultipleTransactions(package, args);
} else {
auto args = MemPoolAccept::ATMPArgs::PackageChildWithParents(
config, GetTime(), coins_to_uncache);
return MemPoolAccept(pool, active_chainstate)
.AcceptPackage(package, args);
}
}();
// Uncache coins pertaining to transactions that were not submitted to the
// mempool.
if (test_accept || result.m_state.IsInvalid()) {
for (const COutPoint &hashTx : coins_to_uncache) {
active_chainstate.CoinsTip().Uncache(hashTx);
}
}
// Ensure the coins cache is still within limits.
BlockValidationState state_dummy;
active_chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);
return result;
}
Amount GetBlockSubsidy(int nHeight, const Consensus::Params &consensusParams) {
int halvings = nHeight / consensusParams.nSubsidyHalvingInterval;
// Force block reward to zero when right shift is undefined.
if (halvings >= 64) {
return Amount::zero();
}
Amount nSubsidy = 50 * COIN;
// Subsidy is cut in half every 210,000 blocks which will occur
// approximately every 4 years.
return ((nSubsidy / SATOSHI) >> halvings) * SATOSHI;
}
CoinsViews::CoinsViews(DBParams db_params, CoinsViewOptions options)
: m_dbview{std::move(db_params), std::move(options)},
m_catcherview(&m_dbview) {}
void CoinsViews::InitCache() {
AssertLockHeld(::cs_main);
m_cacheview = std::make_unique<CCoinsViewCache>(&m_catcherview);
}
Chainstate::Chainstate(CTxMemPool *mempool, BlockManager &blockman,
ChainstateManager &chainman,
std::optional<BlockHash> from_snapshot_blockhash)
: m_mempool(mempool), m_blockman(blockman), m_chainman(chainman),
m_from_snapshot_blockhash(from_snapshot_blockhash) {}
const CBlockIndex *Chainstate::SnapshotBase() {
if (!m_from_snapshot_blockhash) {
return nullptr;
}
if (!m_cached_snapshot_base) {
m_cached_snapshot_base = Assert(
m_chainman.m_blockman.LookupBlockIndex(*m_from_snapshot_blockhash));
}
return m_cached_snapshot_base;
}
void Chainstate::InitCoinsDB(size_t cache_size_bytes, bool in_memory,
bool should_wipe, std::string leveldb_name) {
if (m_from_snapshot_blockhash) {
leveldb_name += node::SNAPSHOT_CHAINSTATE_SUFFIX;
}
m_coins_views = std::make_unique<CoinsViews>(
DBParams{.path = m_chainman.m_options.datadir / leveldb_name,
.cache_bytes = cache_size_bytes,
.memory_only = in_memory,
.wipe_data = should_wipe,
.obfuscate = true,
.options = m_chainman.m_options.coins_db},
m_chainman.m_options.coins_view);
}
void Chainstate::InitCoinsCache(size_t cache_size_bytes) {
AssertLockHeld(::cs_main);
assert(m_coins_views != nullptr);
m_coinstip_cache_size_bytes = cache_size_bytes;
m_coins_views->InitCache();
}
// Note that though this is marked const, we may end up modifying
// `m_cached_finished_ibd`, which is a performance-related implementation
// detail. This function must be marked `const` so that `CValidationInterface`
// clients (which are given a `const Chainstate*`) can call it.
//
bool ChainstateManager::IsInitialBlockDownload() const {
// Optimization: pre-test latch before taking the lock.
if (m_cached_finished_ibd.load(std::memory_order_relaxed)) {
return false;
}
LOCK(cs_main);
if (m_cached_finished_ibd.load(std::memory_order_relaxed)) {
return false;
}
if (m_blockman.LoadingBlocks()) {
return true;
}
CChain &chain{ActiveChain()};
if (chain.Tip() == nullptr) {
return true;
}
if (chain.Tip()->nChainWork < MinimumChainWork()) {
return true;
}
if (chain.Tip()->Time() < Now<NodeSeconds>() - m_options.max_tip_age) {
return true;
}
LogPrintf("Leaving InitialBlockDownload (latching to false)\n");
m_cached_finished_ibd.store(true, std::memory_order_relaxed);
return false;
}
void Chainstate::CheckForkWarningConditions() {
AssertLockHeld(cs_main);
// Before we get past initial download, we cannot reliably alert about forks
// (we assume we don't get stuck on a fork before finishing our initial
// sync)
if (m_chainman.IsInitialBlockDownload()) {
return;
}
// If our best fork is no longer within 72 blocks (+/- 12 hours if no one
// mines it) of our head, or if it is back on the active chain, drop it
if (m_best_fork_tip && (m_chain.Height() - m_best_fork_tip->nHeight >= 72 ||
m_chain.Contains(m_best_fork_tip))) {
m_best_fork_tip = nullptr;
}
if (m_best_fork_tip ||
(m_chainman.m_best_invalid &&
m_chainman.m_best_invalid->nChainWork >
m_chain.Tip()->nChainWork + (GetBlockProof(*m_chain.Tip()) * 6))) {
if (!GetfLargeWorkForkFound() && m_best_fork_base) {
std::string warning =
std::string("'Warning: Large-work fork detected, forking after "
"block ") +
m_best_fork_base->phashBlock->ToString() + std::string("'");
m_chainman.GetNotifications().warning(warning);
}
if (m_best_fork_tip && m_best_fork_base) {
LogPrintf("%s: Warning: Large fork found\n forking the "
"chain at height %d (%s)\n lasting to height %d "
"(%s).\nChain state database corruption likely.\n",
__func__, m_best_fork_base->nHeight,
m_best_fork_base->phashBlock->ToString(),
m_best_fork_tip->nHeight,
m_best_fork_tip->phashBlock->ToString());
SetfLargeWorkForkFound(true);
} else {
LogPrintf("%s: Warning: Found invalid chain at least ~6 blocks "
"longer than our best chain.\nChain state database "
"corruption likely.\n",
__func__);
SetfLargeWorkInvalidChainFound(true);
}
} else {
SetfLargeWorkForkFound(false);
SetfLargeWorkInvalidChainFound(false);
}
}
void Chainstate::CheckForkWarningConditionsOnNewFork(
CBlockIndex *pindexNewForkTip) {
AssertLockHeld(cs_main);
// If we are on a fork that is sufficiently large, set a warning flag.
const CBlockIndex *pfork = m_chain.FindFork(pindexNewForkTip);
// We define a condition where we should warn the user about as a fork of at
// least 7 blocks with a tip within 72 blocks (+/- 12 hours if no one mines
// it) of ours. We use 7 blocks rather arbitrarily as it represents just
// under 10% of sustained network hash rate operating on the fork, or a
// chain that is entirely longer than ours and invalid (note that this
// should be detected by both). We define it this way because it allows us
// to only store the highest fork tip (+ base) which meets the 7-block
// condition and from this always have the most-likely-to-cause-warning fork
if (pfork &&
(!m_best_fork_tip ||
pindexNewForkTip->nHeight > m_best_fork_tip->nHeight) &&
pindexNewForkTip->nChainWork - pfork->nChainWork >
(GetBlockProof(*pfork) * 7) &&
m_chain.Height() - pindexNewForkTip->nHeight < 72) {
m_best_fork_tip = pindexNewForkTip;
m_best_fork_base = pfork;
}
CheckForkWarningConditions();
}
// Called both upon regular invalid block discovery *and* InvalidateBlock
void Chainstate::InvalidChainFound(CBlockIndex *pindexNew) {
AssertLockHeld(cs_main);
if (!m_chainman.m_best_invalid ||
pindexNew->nChainWork > m_chainman.m_best_invalid->nChainWork) {
m_chainman.m_best_invalid = pindexNew;
}
if (m_chainman.m_best_header != nullptr &&
m_chainman.m_best_header->GetAncestor(pindexNew->nHeight) ==
pindexNew) {
m_chainman.m_best_header = m_chain.Tip();
}
// If the invalid chain found is supposed to be finalized, we need to move
// back the finalization point.
if (IsBlockAvalancheFinalized(pindexNew)) {
LOCK(cs_avalancheFinalizedBlockIndex);
m_avalancheFinalizedBlockIndex = pindexNew->pprev;
}
LogPrintf("%s: invalid block=%s height=%d log2_work=%f date=%s\n",
__func__, pindexNew->GetBlockHash().ToString(),
pindexNew->nHeight,
log(pindexNew->nChainWork.getdouble()) / log(2.0),
FormatISO8601DateTime(pindexNew->GetBlockTime()));
CBlockIndex *tip = m_chain.Tip();
assert(tip);
LogPrintf("%s: current best=%s height=%d log2_work=%f date=%s\n",
__func__, tip->GetBlockHash().ToString(), m_chain.Height(),
log(tip->nChainWork.getdouble()) / log(2.0),
FormatISO8601DateTime(tip->GetBlockTime()));
}
// Same as InvalidChainFound, above, except not called directly from
// InvalidateBlock, which does its own setBlockIndexCandidates management.
void Chainstate::InvalidBlockFound(CBlockIndex *pindex,
const BlockValidationState &state) {
AssertLockHeld(cs_main);
if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
pindex->nStatus = pindex->nStatus.withFailed();
m_chainman.m_failed_blocks.insert(pindex);
m_blockman.m_dirty_blockindex.insert(pindex);
InvalidChainFound(pindex);
}
}
void SpendCoins(CCoinsViewCache &view, const CTransaction &tx, CTxUndo &txundo,
int nHeight) {
// Mark inputs spent.
if (tx.IsCoinBase()) {
return;
}
txundo.vprevout.reserve(tx.vin.size());
for (const CTxIn &txin : tx.vin) {
txundo.vprevout.emplace_back();
bool is_spent = view.SpendCoin(txin.prevout, &txundo.vprevout.back());
assert(is_spent);
}
}
void UpdateCoins(CCoinsViewCache &view, const CTransaction &tx, CTxUndo &txundo,
int nHeight) {
SpendCoins(view, tx, txundo, nHeight);
AddCoins(view, tx, nHeight);
}
bool CScriptCheck::operator()() {
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
if (!VerifyScript(scriptSig, m_tx_out.scriptPubKey, nFlags,
CachingTransactionSignatureChecker(
ptxTo, nIn, m_tx_out.nValue, cacheStore, txdata),
metrics, &error)) {
return false;
}
if ((pTxLimitSigChecks &&
!pTxLimitSigChecks->consume_and_check(metrics.nSigChecks)) ||
(pBlockLimitSigChecks &&
!pBlockLimitSigChecks->consume_and_check(metrics.nSigChecks))) {
// we can't assign a meaningful script error (since the script
// succeeded), but remove the ScriptError::OK which could be
// misinterpreted.
error = ScriptError::SIGCHECKS_LIMIT_EXCEEDED;
return false;
}
return true;
}
bool CheckInputScripts(const CTransaction &tx, TxValidationState &state,
const CCoinsViewCache &inputs, const uint32_t flags,
bool sigCacheStore, bool scriptCacheStore,
const PrecomputedTransactionData &txdata,
int &nSigChecksOut, TxSigCheckLimiter &txLimitSigChecks,
CheckInputsLimiter *pBlockLimitSigChecks,
std::vector<CScriptCheck> *pvChecks) {
AssertLockHeld(cs_main);
assert(!tx.IsCoinBase());
if (pvChecks) {
pvChecks->reserve(tx.vin.size());
}
// First check if script executions have been cached with the same flags.
// Note that this assumes that the inputs provided are correct (ie that the
// transaction hash which is in tx's prevouts properly commits to the
// scriptPubKey in the inputs view of that transaction).
ScriptCacheKey hashCacheEntry(tx, flags);
if (IsKeyInScriptCache(hashCacheEntry, !scriptCacheStore, nSigChecksOut)) {
if (!txLimitSigChecks.consume_and_check(nSigChecksOut) ||
(pBlockLimitSigChecks &&
!pBlockLimitSigChecks->consume_and_check(nSigChecksOut))) {
return state.Invalid(TxValidationResult::TX_CONSENSUS,
"too-many-sigchecks");
}
return true;
}
int nSigChecksTotal = 0;
for (size_t i = 0; i < tx.vin.size(); i++) {
const COutPoint &prevout = tx.vin[i].prevout;
const Coin &coin = inputs.AccessCoin(prevout);
assert(!coin.IsSpent());
// We very carefully only pass in things to CScriptCheck which are
// clearly committed to by tx's hash. This provides a sanity
// check that our caching is not introducing consensus failures through
// additional data in, eg, the coins being spent being checked as a part
// of CScriptCheck.
// Verify signature
CScriptCheck check(coin.GetTxOut(), tx, i, flags, sigCacheStore, txdata,
&txLimitSigChecks, pBlockLimitSigChecks);
// If pvChecks is not null, defer the check execution to the caller.
if (pvChecks) {
pvChecks->push_back(std::move(check));
continue;
}
if (!check()) {
ScriptError scriptError = check.GetScriptError();
// Compute flags without the optional standardness flags.
// This differs from MANDATORY_SCRIPT_VERIFY_FLAGS as it contains
// additional upgrade flags (see AcceptToMemoryPoolWorker variable
// extraFlags).
uint32_t mandatoryFlags =
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS;
if (flags != mandatoryFlags) {
// Check whether the failure was caused by a non-mandatory
// script verification check. If so, ensure we return
// NOT_STANDARD instead of CONSENSUS to avoid downstream users
// splitting the network between upgraded and non-upgraded nodes
// by banning CONSENSUS-failing data providers.
CScriptCheck check2(coin.GetTxOut(), tx, i, mandatoryFlags,
sigCacheStore, txdata);
if (check2()) {
return state.Invalid(
TxValidationResult::TX_NOT_STANDARD,
strprintf("non-mandatory-script-verify-flag (%s)",
ScriptErrorString(scriptError)));
}
// update the error message to reflect the mandatory violation.
scriptError = check2.GetScriptError();
}
// MANDATORY flag failures correspond to
// TxValidationResult::TX_CONSENSUS. Because CONSENSUS failures are
// the most serious case of validation failures, we may need to
// consider using RECENT_CONSENSUS_CHANGE for any script failure
// that could be due to non-upgraded nodes which we may want to
// support, to avoid splitting the network (but this depends on the
// details of how net_processing handles such errors).
return state.Invalid(
TxValidationResult::TX_CONSENSUS,
strprintf("mandatory-script-verify-flag-failed (%s)",
ScriptErrorString(scriptError)));
}
nSigChecksTotal += check.GetScriptExecutionMetrics().nSigChecks;
}
nSigChecksOut = nSigChecksTotal;
if (scriptCacheStore && !pvChecks) {
// We executed all of the provided scripts, and were told to cache the
// result. Do so now.
AddKeyInScriptCache(hashCacheEntry, nSigChecksTotal);
}
return true;
}
bool AbortNode(BlockValidationState &state, const std::string &strMessage,
const bilingual_str &userMessage) {
AbortNode(strMessage, userMessage);
return state.Error(strMessage);
}
/** Restore the UTXO in a Coin at a given COutPoint. */
DisconnectResult UndoCoinSpend(Coin &&undo, CCoinsViewCache &view,
const COutPoint &out) {
bool fClean = true;
if (view.HaveCoin(out)) {
// Overwriting transaction output.
fClean = false;
}
if (undo.GetHeight() == 0) {
// Missing undo metadata (height and coinbase). Older versions included
// this information only in undo records for the last spend of a
// transactions' outputs. This implies that it must be present for some
// other output of the same tx.
const Coin &alternate = AccessByTxid(view, out.GetTxId());
if (alternate.IsSpent()) {
// Adding output for transaction without known metadata
return DisconnectResult::FAILED;
}
// This is somewhat ugly, but hopefully utility is limited. This is only
// useful when working from legacy on disck data. In any case, putting
// the correct information in there doesn't hurt.
const_cast<Coin &>(undo) = Coin(undo.GetTxOut(), alternate.GetHeight(),
alternate.IsCoinBase());
}
// If the coin already exists as an unspent coin in the cache, then the
// possible_overwrite parameter to AddCoin must be set to true. We have
// already checked whether an unspent coin exists above using HaveCoin, so
// we don't need to guess. When fClean is false, an unspent coin already
// existed and it is an overwrite.
view.AddCoin(out, std::move(undo), !fClean);
return fClean ? DisconnectResult::OK : DisconnectResult::UNCLEAN;
}
/**
* Undo the effects of this block (with given index) on the UTXO set represented
* by coins. When FAILED is returned, view is left in an indeterminate state.
*/
DisconnectResult Chainstate::DisconnectBlock(const CBlock &block,
const CBlockIndex *pindex,
CCoinsViewCache &view) {
AssertLockHeld(::cs_main);
CBlockUndo blockUndo;
if (!m_blockman.UndoReadFromDisk(blockUndo, *pindex)) {
error("DisconnectBlock(): failure reading undo data");
return DisconnectResult::FAILED;
}
return ApplyBlockUndo(std::move(blockUndo), block, pindex, view);
}
DisconnectResult ApplyBlockUndo(CBlockUndo &&blockUndo, const CBlock &block,
const CBlockIndex *pindex,
CCoinsViewCache &view) {
bool fClean = true;
if (blockUndo.vtxundo.size() + 1 != block.vtx.size()) {
error("DisconnectBlock(): block and undo data inconsistent");
return DisconnectResult::FAILED;
}
// First, restore inputs.
for (size_t i = 1; i < block.vtx.size(); i++) {
const CTransaction &tx = *(block.vtx[i]);
CTxUndo &txundo = blockUndo.vtxundo[i - 1];
if (txundo.vprevout.size() != tx.vin.size()) {
error("DisconnectBlock(): transaction and undo data inconsistent");
return DisconnectResult::FAILED;
}
for (size_t j = 0; j < tx.vin.size(); j++) {
const COutPoint &out = tx.vin[j].prevout;
DisconnectResult res =
UndoCoinSpend(std::move(txundo.vprevout[j]), view, out);
if (res == DisconnectResult::FAILED) {
return DisconnectResult::FAILED;
}
fClean = fClean && res != DisconnectResult::UNCLEAN;
}
// At this point, all of txundo.vprevout should have been moved out.
}
// Second, revert created outputs.
for (const auto &ptx : block.vtx) {
const CTransaction &tx = *ptx;
const TxId &txid = tx.GetId();
const bool is_coinbase = tx.IsCoinBase();
// Check that all outputs are available and match the outputs in the
// block itself exactly.
for (size_t o = 0; o < tx.vout.size(); o++) {
if (tx.vout[o].scriptPubKey.IsUnspendable()) {
continue;
}
COutPoint out(txid, o);
Coin coin;
bool is_spent = view.SpendCoin(out, &coin);
if (!is_spent || tx.vout[o] != coin.GetTxOut() ||
uint32_t(pindex->nHeight) != coin.GetHeight() ||
is_coinbase != coin.IsCoinBase()) {
// transaction output mismatch
fClean = false;
}
}
}
// Move best block pointer to previous block.
view.SetBestBlock(block.hashPrevBlock);
return fClean ? DisconnectResult::OK : DisconnectResult::UNCLEAN;
}
static CCheckQueue<CScriptCheck> scriptcheckqueue(128);
void StartScriptCheckWorkerThreads(int threads_num) {
scriptcheckqueue.StartWorkerThreads(threads_num);
}
void StopScriptCheckWorkerThreads() {
scriptcheckqueue.StopWorkerThreads();
}
// Returns the script flags which should be checked for the block after
// the given block.
static uint32_t GetNextBlockScriptFlags(const CBlockIndex *pindex,
const ChainstateManager &chainman) {
const Consensus::Params &consensusparams = chainman.GetConsensus();
uint32_t flags = SCRIPT_VERIFY_NONE;
// Enforce P2SH (BIP16)
if (DeploymentActiveAfter(pindex, chainman, Consensus::DEPLOYMENT_P2SH)) {
flags |= SCRIPT_VERIFY_P2SH;
}
// Enforce the DERSIG (BIP66) rule.
if (DeploymentActiveAfter(pindex, chainman, Consensus::DEPLOYMENT_DERSIG)) {
flags |= SCRIPT_VERIFY_DERSIG;
}
// Start enforcing CHECKLOCKTIMEVERIFY (BIP65) rule.
if (DeploymentActiveAfter(pindex, chainman, Consensus::DEPLOYMENT_CLTV)) {
flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
}
// Start enforcing CSV (BIP68, BIP112 and BIP113) rule.
if (DeploymentActiveAfter(pindex, chainman, Consensus::DEPLOYMENT_CSV)) {
flags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
}
// If the UAHF is enabled, we start accepting replay protected txns
if (IsUAHFenabled(consensusparams, pindex)) {
flags |= SCRIPT_VERIFY_STRICTENC;
flags |= SCRIPT_ENABLE_SIGHASH_FORKID;
}
// If the DAA HF is enabled, we start rejecting transaction that use a high
// s in their signature. We also make sure that signature that are supposed
// to fail (for instance in multisig or other forms of smart contracts) are
// null.
if (IsDAAEnabled(consensusparams, pindex)) {
flags |= SCRIPT_VERIFY_LOW_S;
flags |= SCRIPT_VERIFY_NULLFAIL;
}
// When the magnetic anomaly fork is enabled, we start accepting
// transactions using the OP_CHECKDATASIG opcode and it's verify
// alternative. We also start enforcing push only signatures and
// clean stack.
if (IsMagneticAnomalyEnabled(consensusparams, pindex)) {
flags |= SCRIPT_VERIFY_SIGPUSHONLY;
flags |= SCRIPT_VERIFY_CLEANSTACK;
}
if (IsGravitonEnabled(consensusparams, pindex)) {
flags |= SCRIPT_ENABLE_SCHNORR_MULTISIG;
flags |= SCRIPT_VERIFY_MINIMALDATA;
}
if (IsPhononEnabled(consensusparams, pindex)) {
flags |= SCRIPT_ENFORCE_SIGCHECKS;
}
// We make sure this node will have replay protection during the next hard
// fork.
if (IsReplayProtectionEnabled(consensusparams, pindex)) {
flags |= SCRIPT_ENABLE_REPLAY_PROTECTION;
}
return flags;
}
static int64_t nTimeCheck = 0;
static int64_t nTimeForks = 0;
static int64_t nTimeVerify = 0;
static int64_t nTimeConnect = 0;
static int64_t nTimeIndex = 0;
static int64_t nTimeTotal = 0;
static int64_t nBlocksTotal = 0;
/**
* Apply the effects of this block (with given index) on the UTXO set
* represented by coins. Validity checks that depend on the UTXO set are also
* done; ConnectBlock() can fail if those validity checks fail (among other
* reasons).
*/
bool Chainstate::ConnectBlock(const CBlock &block, BlockValidationState &state,
CBlockIndex *pindex, CCoinsViewCache &view,
BlockValidationOptions options, Amount *blockFees,
bool fJustCheck) {
AssertLockHeld(cs_main);
assert(pindex);
const BlockHash block_hash{block.GetHash()};
assert(*pindex->phashBlock == block_hash);
int64_t nTimeStart = GetTimeMicros();
const CChainParams &params{m_chainman.GetParams()};
const Consensus::Params &consensusParams = params.GetConsensus();
// Check it again in case a previous version let a bad block in
// NOTE: We don't currently (re-)invoke ContextualCheckBlock() or
// ContextualCheckBlockHeader() here. This means that if we add a new
// consensus rule that is enforced in one of those two functions, then we
// may have let in a block that violates the rule prior to updating the
// software, and we would NOT be enforcing the rule here. Fully solving
// upgrade from one software version to the next after a consensus rule
// change is potentially tricky and issue-specific.
// Also, currently the rule against blocks more than 2 hours in the future
// is enforced in ContextualCheckBlockHeader(); we wouldn't want to
// re-enforce that rule here (at least until we make it impossible for
// m_adjusted_time_callback() to go backward).
if (!CheckBlock(block, state, consensusParams,
options.withCheckPoW(!fJustCheck)
.withCheckMerkleRoot(!fJustCheck))) {
if (state.GetResult() == BlockValidationResult::BLOCK_MUTATED) {
// We don't write down blocks to disk if they may have been
// corrupted, so this should be impossible unless we're having
// hardware problems.
return AbortNode(state, "Corrupt block found indicating potential "
"hardware failure; shutting down");
}
return error("%s: Consensus::CheckBlock: %s", __func__,
state.ToString());
}
// Verify that the view's current state corresponds to the previous block
BlockHash hashPrevBlock =
pindex->pprev == nullptr ? BlockHash() : pindex->pprev->GetBlockHash();
assert(hashPrevBlock == view.GetBestBlock());
nBlocksTotal++;
// Special case for the genesis block, skipping connection of its
// transactions (its coinbase is unspendable)
if (block_hash == consensusParams.hashGenesisBlock) {
if (!fJustCheck) {
view.SetBestBlock(pindex->GetBlockHash());
}
return true;
}
bool fScriptChecks = true;
if (!m_chainman.AssumedValidBlock().IsNull()) {
// We've been configured with the hash of a block which has been
// externally verified to have a valid history. A suitable default value
// is included with the software and updated from time to time. Because
// validity relative to a piece of software is an objective fact these
// defaults can be easily reviewed. This setting doesn't force the
// selection of any particular chain but makes validating some faster by
// effectively caching the result of part of the verification.
BlockMap::const_iterator it{
m_blockman.m_block_index.find(m_chainman.AssumedValidBlock())};
if (it != m_blockman.m_block_index.end()) {
if (it->second.GetAncestor(pindex->nHeight) == pindex &&
m_chainman.m_best_header->GetAncestor(pindex->nHeight) ==
pindex &&
m_chainman.m_best_header->nChainWork >=
m_chainman.MinimumChainWork()) {
// This block is a member of the assumed verified chain and an
// ancestor of the best header.
// Script verification is skipped when connecting blocks under
// the assumevalid block. Assuming the assumevalid block is
// valid this is safe because block merkle hashes are still
// computed and checked, Of course, if an assumed valid block is
// invalid due to false scriptSigs this optimization would allow
// an invalid chain to be accepted.
// The equivalent time check discourages hash power from
// extorting the network via DOS attack into accepting an
// invalid block through telling users they must manually set
// assumevalid. Requiring a software change or burying the
// invalid block, regardless of the setting, makes it hard to
// hide the implication of the demand. This also avoids having
// release candidates that are hardly doing any signature
// verification at all in testing without having to artificially
// set the default assumed verified block further back. The test
// against the minimum chain work prevents the skipping when
// denied access to any chain at least as good as the expected
// chain.
fScriptChecks = (GetBlockProofEquivalentTime(
*m_chainman.m_best_header, *pindex,
*m_chainman.m_best_header,
consensusParams) <= 60 * 60 * 24 * 7 * 2);
}
}
}
int64_t nTime1 = GetTimeMicros();
nTimeCheck += nTime1 - nTimeStart;
LogPrint(BCLog::BENCH, " - Sanity checks: %.2fms [%.2fs (%.2fms/blk)]\n",
MILLI * (nTime1 - nTimeStart), nTimeCheck * MICRO,
nTimeCheck * MILLI / nBlocksTotal);
// Do not allow blocks that contain transactions which 'overwrite' older
// transactions, unless those are already completely spent. If such
// overwrites are allowed, coinbases and transactions depending upon those
// can be duplicated to remove the ability to spend the first instance --
// even after being sent to another address.
// See BIP30, CVE-2012-1909, and http://r6.ca/blog/20120206T005236Z.html
// for more information. This rule was originally applied to all blocks
// with a timestamp after March 15, 2012, 0:00 UTC. Now that the whole
// chain is irreversibly beyond that time it is applied to all blocks
// except the two in the chain that violate it. This prevents exploiting
// the issue against nodes during their initial block download.
bool fEnforceBIP30 = !((pindex->nHeight == 91842 &&
pindex->GetBlockHash() ==
uint256S("0x00000000000a4d0a398161ffc163c503763"
"b1f4360639393e0e4c8e300e0caec")) ||
(pindex->nHeight == 91880 &&
pindex->GetBlockHash() ==
uint256S("0x00000000000743f190a18c5577a3c2d2a1f"
"610ae9601ac046a38084ccb7cd721")));
// Once BIP34 activated it was not possible to create new duplicate
// coinbases and thus other than starting with the 2 existing duplicate
// coinbase pairs, not possible to create overwriting txs. But by the time
// BIP34 activated, in each of the existing pairs the duplicate coinbase had
// overwritten the first before the first had been spent. Since those
// coinbases are sufficiently buried it's no longer possible to create
// further duplicate transactions descending from the known pairs either. If
// we're on the known chain at height greater than where BIP34 activated, we
// can save the db accesses needed for the BIP30 check.
// BIP34 requires that a block at height X (block X) has its coinbase
// scriptSig start with a CScriptNum of X (indicated height X). The above
// logic of no longer requiring BIP30 once BIP34 activates is flawed in the
// case that there is a block X before the BIP34 height of 227,931 which has
// an indicated height Y where Y is greater than X. The coinbase for block
// X would also be a valid coinbase for block Y, which could be a BIP30
// violation. An exhaustive search of all mainnet coinbases before the
// BIP34 height which have an indicated height greater than the block height
// reveals many occurrences. The 3 lowest indicated heights found are
// 209,921, 490,897, and 1,983,702 and thus coinbases for blocks at these 3
// heights would be the first opportunity for BIP30 to be violated.
// The search reveals a great many blocks which have an indicated height
// greater than 1,983,702, so we simply remove the optimization to skip
// BIP30 checking for blocks at height 1,983,702 or higher. Before we reach
// that block in another 25 years or so, we should take advantage of a
// future consensus change to do a new and improved version of BIP34 that
// will actually prevent ever creating any duplicate coinbases in the
// future.
static constexpr int BIP34_IMPLIES_BIP30_LIMIT = 1983702;
// There is no potential to create a duplicate coinbase at block 209,921
// because this is still before the BIP34 height and so explicit BIP30
// checking is still active.
// The final case is block 176,684 which has an indicated height of
// 490,897. Unfortunately, this issue was not discovered until about 2 weeks
// before block 490,897 so there was not much opportunity to address this
// case other than to carefully analyze it and determine it would not be a
// problem. Block 490,897 was, in fact, mined with a different coinbase than
// block 176,684, but it is important to note that even if it hadn't been or
// is remined on an alternate fork with a duplicate coinbase, we would still
// not run into a BIP30 violation. This is because the coinbase for 176,684
// is spent in block 185,956 in transaction
// d4f7fbbf92f4a3014a230b2dc70b8058d02eb36ac06b4a0736d9d60eaa9e8781. This
// spending transaction can't be duplicated because it also spends coinbase
// 0328dd85c331237f18e781d692c92de57649529bd5edf1d01036daea32ffde29. This
// coinbase has an indicated height of over 4.2 billion, and wouldn't be
// duplicatable until that height, and it's currently impossible to create a
// chain that long. Nevertheless we may wish to consider a future soft fork
// which retroactively prevents block 490,897 from creating a duplicate
// coinbase. The two historical BIP30 violations often provide a confusing
// edge case when manipulating the UTXO and it would be simpler not to have
// another edge case to deal with.
// testnet3 has no blocks before the BIP34 height with indicated heights
// post BIP34 before approximately height 486,000,000 and presumably will
// be reset before it reaches block 1,983,702 and starts doing unnecessary
// BIP30 checking again.
assert(pindex->pprev);
CBlockIndex *pindexBIP34height =
pindex->pprev->GetAncestor(consensusParams.BIP34Height);
// Only continue to enforce if we're below BIP34 activation height or the
// block hash at that height doesn't correspond.
fEnforceBIP30 =
fEnforceBIP30 &&
(!pindexBIP34height ||
!(pindexBIP34height->GetBlockHash() == consensusParams.BIP34Hash));
// TODO: Remove BIP30 checking from block height 1,983,702 on, once we have
// a consensus change that ensures coinbases at those heights can not
// duplicate earlier coinbases.
if (fEnforceBIP30 || pindex->nHeight >= BIP34_IMPLIES_BIP30_LIMIT) {
for (const auto &tx : block.vtx) {
for (size_t o = 0; o < tx->vout.size(); o++) {
if (view.HaveCoin(COutPoint(tx->GetId(), o))) {
LogPrintf("ERROR: ConnectBlock(): tried to overwrite "
"transaction\n");
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
"bad-txns-BIP30");
}
}
}
}
// Enforce BIP68 (sequence locks).
int nLockTimeFlags = 0;
if (DeploymentActiveAt(*pindex, consensusParams,
Consensus::DEPLOYMENT_CSV)) {
nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE;
}
const uint32_t flags = GetNextBlockScriptFlags(pindex->pprev, m_chainman);
int64_t nTime2 = GetTimeMicros();
nTimeForks += nTime2 - nTime1;
LogPrint(BCLog::BENCH, " - Fork checks: %.2fms [%.2fs (%.2fms/blk)]\n",
MILLI * (nTime2 - nTime1), nTimeForks * MICRO,
nTimeForks * MILLI / nBlocksTotal);
std::vector<int> prevheights;
Amount nFees = Amount::zero();
int nInputs = 0;
// Limit the total executed signature operations in the block, a consensus
// rule. Tracking during the CPU-consuming part (validation of uncached
// inputs) is per-input atomic and validation in each thread stops very
// quickly after the limit is exceeded, so an adversary cannot cause us to
// exceed the limit by much at all.
CheckInputsLimiter nSigChecksBlockLimiter(
GetMaxBlockSigChecksCount(options.getExcessiveBlockSize()));
std::vector<TxSigCheckLimiter> nSigChecksTxLimiters;
nSigChecksTxLimiters.resize(block.vtx.size() - 1);
CBlockUndo blockundo;
blockundo.vtxundo.resize(block.vtx.size() - 1);
CCheckQueueControl<CScriptCheck> control(fScriptChecks ? &scriptcheckqueue
: nullptr);
// Add all outputs
try {
for (const auto &ptx : block.vtx) {
AddCoins(view, *ptx, pindex->nHeight);
}
} catch (const std::logic_error &e) {
// This error will be thrown from AddCoin if we try to connect a block
// containing duplicate transactions. Such a thing should normally be
// caught early nowadays (due to ContextualCheckBlock's CTOR
// enforcement) however some edge cases can escape that:
// - ContextualCheckBlock does not get re-run after saving the block to
// disk, and older versions may have saved a weird block.
// - its checks are not applied to pre-CTOR chains, which we might visit
// with checkpointing off.
LogPrintf("ERROR: ConnectBlock(): tried to overwrite transaction\n");
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
"tx-duplicate");
}
size_t txIndex = 0;
// nSigChecksRet may be accurate (found in cache) or 0 (checks were
// deferred into vChecks).
int nSigChecksRet;
for (const auto &ptx : block.vtx) {
const CTransaction &tx = *ptx;
const bool isCoinBase = tx.IsCoinBase();
nInputs += tx.vin.size();
{
Amount txfee = Amount::zero();
TxValidationState tx_state;
if (!isCoinBase &&
!Consensus::CheckTxInputs(tx, tx_state, view, pindex->nHeight,
txfee)) {
// Any transaction validation failure in ConnectBlock is a block
// consensus failure.
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(),
tx_state.GetDebugMessage());
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__,
tx.GetId().ToString(), state.ToString());
}
nFees += txfee;
}
if (!MoneyRange(nFees)) {
LogPrintf("ERROR: %s: accumulated fee in the block out of range.\n",
__func__);
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
"bad-txns-accumulated-fee-outofrange");
}
// The following checks do not apply to the coinbase.
if (isCoinBase) {
continue;
}
// Check that transaction is BIP68 final BIP68 lock checks (as
// opposed to nLockTime checks) must be in ConnectBlock because they
// require the UTXO set.
prevheights.resize(tx.vin.size());
for (size_t j = 0; j < tx.vin.size(); j++) {
prevheights[j] = view.AccessCoin(tx.vin[j].prevout).GetHeight();
}
if (!SequenceLocks(tx, nLockTimeFlags, prevheights, *pindex)) {
LogPrintf("ERROR: %s: contains a non-BIP68-final transaction\n",
__func__);
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
"bad-txns-nonfinal");
}
// Don't cache results if we're actually connecting blocks (still
// consult the cache, though).
bool fCacheResults = fJustCheck;
const bool fEnforceSigCheck = flags & SCRIPT_ENFORCE_SIGCHECKS;
if (!fEnforceSigCheck) {
// Historically, there has been transactions with a very high
// sigcheck count, so we need to disable this check for such
// transactions.
nSigChecksTxLimiters[txIndex] = TxSigCheckLimiter::getDisabled();
}
std::vector<CScriptCheck> vChecks;
TxValidationState tx_state;
if (fScriptChecks &&
!CheckInputScripts(tx, tx_state, view, flags, fCacheResults,
fCacheResults, PrecomputedTransactionData(tx),
nSigChecksRet, nSigChecksTxLimiters[txIndex],
&nSigChecksBlockLimiter, &vChecks)) {
// Any transaction validation failure in ConnectBlock is a block
// consensus failure
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(),
tx_state.GetDebugMessage());
return error(
"ConnectBlock(): CheckInputScripts on %s failed with %s",
tx.GetId().ToString(), state.ToString());
}
control.Add(std::move(vChecks));
// Note: this must execute in the same iteration as CheckTxInputs (not
// in a separate loop) in order to detect double spends. However,
// this does not prevent double-spending by duplicated transaction
// inputs in the same transaction (cf. CVE-2018-17144) -- that check is
// done in CheckBlock (CheckRegularTransaction).
SpendCoins(view, tx, blockundo.vtxundo.at(txIndex), pindex->nHeight);
txIndex++;
}
int64_t nTime3 = GetTimeMicros();
nTimeConnect += nTime3 - nTime2;
LogPrint(BCLog::BENCH,
" - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) "
"[%.2fs (%.2fms/blk)]\n",
(unsigned)block.vtx.size(), MILLI * (nTime3 - nTime2),
MILLI * (nTime3 - nTime2) / block.vtx.size(),
nInputs <= 1 ? 0 : MILLI * (nTime3 - nTime2) / (nInputs - 1),
nTimeConnect * MICRO, nTimeConnect * MILLI / nBlocksTotal);
const Amount blockReward =
nFees + GetBlockSubsidy(pindex->nHeight, consensusParams);
if (block.vtx[0]->GetValueOut() > blockReward) {
LogPrintf("ERROR: ConnectBlock(): coinbase pays too much (actual=%d vs "
"limit=%d)\n",
block.vtx[0]->GetValueOut(), blockReward);
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
"bad-cb-amount");
}
if (blockFees) {
*blockFees = nFees;
}
if (!control.Wait()) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
"blk-bad-inputs", "parallel script check failed");
}
int64_t nTime4 = GetTimeMicros();
nTimeVerify += nTime4 - nTime2;
LogPrint(
BCLog::BENCH,
" - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n",
nInputs - 1, MILLI * (nTime4 - nTime2),
nInputs <= 1 ? 0 : MILLI * (nTime4 - nTime2) / (nInputs - 1),
nTimeVerify * MICRO, nTimeVerify * MILLI / nBlocksTotal);
if (fJustCheck) {
return true;
}
if (!m_blockman.WriteUndoDataForBlock(blockundo, state, *pindex)) {
return false;
}
if (!pindex->IsValid(BlockValidity::SCRIPTS)) {
pindex->RaiseValidity(BlockValidity::SCRIPTS);
m_blockman.m_dirty_blockindex.insert(pindex);
}
// add this block to the view's block chain
view.SetBestBlock(pindex->GetBlockHash());
int64_t nTime5 = GetTimeMicros();
nTimeIndex += nTime5 - nTime4;
LogPrint(BCLog::BENCH, " - Index writing: %.2fms [%.2fs (%.2fms/blk)]\n",
MILLI * (nTime5 - nTime4), nTimeIndex * MICRO,
nTimeIndex * MILLI / nBlocksTotal);
TRACE6(validation, block_connected, block_hash.data(), pindex->nHeight,
block.vtx.size(), nInputs, nSigChecksRet,
// in microseconds (µs)
nTime5 - nTimeStart);
return true;
}
CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState() {
AssertLockHeld(::cs_main);
return this->GetCoinsCacheSizeState(m_coinstip_cache_size_bytes,
m_mempool ? m_mempool->m_max_size_bytes
: 0);
}
CoinsCacheSizeState
Chainstate::GetCoinsCacheSizeState(size_t max_coins_cache_size_bytes,
size_t max_mempool_size_bytes) {
AssertLockHeld(::cs_main);
int64_t nMempoolUsage = m_mempool ? m_mempool->DynamicMemoryUsage() : 0;
int64_t cacheSize = CoinsTip().DynamicMemoryUsage();
int64_t nTotalSpace =
max_coins_cache_size_bytes +
std::max<int64_t>(int64_t(max_mempool_size_bytes) - nMempoolUsage, 0);
//! No need to periodic flush if at least this much space still available.
static constexpr int64_t MAX_BLOCK_COINSDB_USAGE_BYTES =
10 * 1024 * 1024; // 10MB
int64_t large_threshold = std::max(
(9 * nTotalSpace) / 10, nTotalSpace - MAX_BLOCK_COINSDB_USAGE_BYTES);
if (cacheSize > nTotalSpace) {
LogPrintf("Cache size (%s) exceeds total space (%s)\n", cacheSize,
nTotalSpace);
return CoinsCacheSizeState::CRITICAL;
} else if (cacheSize > large_threshold) {
return CoinsCacheSizeState::LARGE;
}
return CoinsCacheSizeState::OK;
}
bool Chainstate::FlushStateToDisk(BlockValidationState &state,
FlushStateMode mode, int nManualPruneHeight) {
LOCK(cs_main);
assert(this->CanFlushToDisk());
std::set<int> setFilesToPrune;
bool full_flush_completed = false;
const size_t coins_count = CoinsTip().GetCacheSize();
const size_t coins_mem_usage = CoinsTip().DynamicMemoryUsage();
try {
{
bool fFlushForPrune = false;
bool fDoFullFlush = false;
CoinsCacheSizeState cache_state = GetCoinsCacheSizeState();
LOCK(m_blockman.cs_LastBlockFile);
if (m_blockman.IsPruneMode() &&
(m_blockman.m_check_for_pruning || nManualPruneHeight > 0) &&
!fReindex) {
// Make sure we don't prune any of the prune locks bestblocks.
// Pruning is height-based.
int last_prune{m_chain.Height()};
// prune lock that actually was the limiting factor, only used
// for logging
std::optional<std::string> limiting_lock;
for (const auto &prune_lock : m_blockman.m_prune_locks) {
if (prune_lock.second.height_first ==
std::numeric_limits<int>::max()) {
continue;
}
// Remove the buffer and one additional block here to get
// actual height that is outside of the buffer
const int lock_height{prune_lock.second.height_first -
PRUNE_LOCK_BUFFER - 1};
last_prune = std::max(1, std::min(last_prune, lock_height));
if (last_prune == lock_height) {
limiting_lock = prune_lock.first;
}
}
if (limiting_lock) {
LogPrint(BCLog::PRUNE, "%s limited pruning to height %d\n",
limiting_lock.value(), last_prune);
}
if (nManualPruneHeight > 0) {
LOG_TIME_MILLIS_WITH_CATEGORY(
"find files to prune (manual)", BCLog::BENCH);
m_blockman.FindFilesToPruneManual(
setFilesToPrune,
std::min(last_prune, nManualPruneHeight), *this,
m_chainman);
} else {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune",
BCLog::BENCH);
m_blockman.FindFilesToPrune(setFilesToPrune, last_prune,
*this, m_chainman);
m_blockman.m_check_for_pruning = false;
}
if (!setFilesToPrune.empty()) {
fFlushForPrune = true;
if (!m_blockman.m_have_pruned) {
m_blockman.m_block_tree_db->WriteFlag(
"prunedblockfiles", true);
m_blockman.m_have_pruned = true;
}
}
}
const auto nNow = GetTime<std::chrono::microseconds>();
// Avoid writing/flushing immediately after startup.
if (m_last_write.count() == 0) {
m_last_write = nNow;
}
if (m_last_flush.count() == 0) {
m_last_flush = nNow;
}
// The cache is large and we're within 10% and 10 MiB of the limit,
// but we have time now (not in the middle of a block processing).
bool fCacheLarge = mode == FlushStateMode::PERIODIC &&
cache_state >= CoinsCacheSizeState::LARGE;
// The cache is over the limit, we have to write now.
bool fCacheCritical = mode == FlushStateMode::IF_NEEDED &&
cache_state >= CoinsCacheSizeState::CRITICAL;
// It's been a while since we wrote the block index to disk. Do this
// frequently, so we don't need to redownload after a crash.
bool fPeriodicWrite = mode == FlushStateMode::PERIODIC &&
nNow > m_last_write + DATABASE_WRITE_INTERVAL;
// It's been very long since we flushed the cache. Do this
// infrequently, to optimize cache usage.
bool fPeriodicFlush = mode == FlushStateMode::PERIODIC &&
nNow > m_last_flush + DATABASE_FLUSH_INTERVAL;
// Combine all conditions that result in a full cache flush.
fDoFullFlush = (mode == FlushStateMode::ALWAYS) || fCacheLarge ||
fCacheCritical || fPeriodicFlush || fFlushForPrune;
// Write blocks and block index to disk.
if (fDoFullFlush || fPeriodicWrite) {
// Ensure we can write block index
if (!CheckDiskSpace(gArgs.GetBlocksDirPath())) {
return AbortNode(state, "Disk space is too low!",
_("Disk space is too low!"));
}
{
LOG_TIME_MILLIS_WITH_CATEGORY(
"write block and undo data to disk", BCLog::BENCH);
// First make sure all block and undo data is flushed to
// disk.
// TODO: Handle return error, or add detailed comment why
// it is safe to not return an error upon failure.
if (!m_blockman.FlushChainstateBlockFile(
m_chain.Height())) {
LogPrintLevel(BCLog::VALIDATION, BCLog::Level::Warning,
"%s: Failed to flush block file.\n",
__func__);
}
}
// Then update all block file information (which may refer to
// block and undo files).
{
LOG_TIME_MILLIS_WITH_CATEGORY("write block index to disk",
BCLog::BENCH);
if (!m_blockman.WriteBlockIndexDB()) {
return AbortNode(
state, "Failed to write to block index database");
}
}
// Finally remove any pruned files
if (fFlushForPrune) {
LOG_TIME_MILLIS_WITH_CATEGORY("unlink pruned files",
BCLog::BENCH);
m_blockman.UnlinkPrunedFiles(setFilesToPrune);
}
m_last_write = nNow;
}
// Flush best chain related state. This can only be done if the
// blocks / block index write was also done.
if (fDoFullFlush && !CoinsTip().GetBestBlock().IsNull()) {
LOG_TIME_MILLIS_WITH_CATEGORY(
strprintf("write coins cache to disk (%d coins, %.2fkB)",
coins_count, coins_mem_usage / 1000),
BCLog::BENCH);
// Typical Coin structures on disk are around 48 bytes in size.
// Pushing a new one to the database can cause it to be written
// twice (once in the log, and once in the tables). This is
// already an overestimation, as most will delete an existing
// entry or overwrite one. Still, use a conservative safety
// factor of 2.
if (!CheckDiskSpace(gArgs.GetDataDirNet(),
48 * 2 * 2 * CoinsTip().GetCacheSize())) {
return AbortNode(state, "Disk space is too low!",
_("Disk space is too low!"));
}
// Flush the chainstate (which may refer to block index
// entries).
if (!CoinsTip().Flush()) {
return AbortNode(state, "Failed to write to coin database");
}
m_last_flush = nNow;
full_flush_completed = true;
}
TRACE5(utxocache, flush,
// in microseconds (µs)
GetTimeMicros() - nNow.count(), uint32_t(mode), coins_count,
uint64_t(coins_mem_usage), fFlushForPrune);
}
if (full_flush_completed) {
// Update best block in wallet (so we can detect restored wallets).
GetMainSignals().ChainStateFlushed(this->GetRole(),
m_chain.GetLocator());
}
} catch (const std::runtime_error &e) {
return AbortNode(state, std::string("System error while flushing: ") +
e.what());
}
return true;
}
void Chainstate::ForceFlushStateToDisk() {
BlockValidationState state;
if (!this->FlushStateToDisk(state, FlushStateMode::ALWAYS)) {
LogPrintf("%s: failed to flush state (%s)\n", __func__,
state.ToString());
}
}
void Chainstate::PruneAndFlush() {
BlockValidationState state;
m_blockman.m_check_for_pruning = true;
if (!this->FlushStateToDisk(state, FlushStateMode::NONE)) {
LogPrintf("%s: failed to flush state (%s)\n", __func__,
state.ToString());
}
}
static void UpdateTipLog(const CCoinsViewCache &coins_tip,
const CBlockIndex *tip, const CChainParams &params,
const std::string &func_name,
const std::string &prefix)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
LogPrintf("%s%s: new best=%s height=%d version=0x%08x log2_work=%f tx=%ld "
"date='%s' progress=%f cache=%.1fMiB(%utxo)\n",
prefix, func_name, tip->GetBlockHash().ToString(), tip->nHeight,
tip->nVersion, log(tip->nChainWork.getdouble()) / log(2.0),
tip->GetChainTxCount(),
FormatISO8601DateTime(tip->GetBlockTime()),
GuessVerificationProgress(params.TxData(), tip),
coins_tip.DynamicMemoryUsage() * (1.0 / (1 << 20)),
coins_tip.GetCacheSize());
}
void Chainstate::UpdateTip(const CBlockIndex *pindexNew) {
AssertLockHeld(::cs_main);
const auto &coins_tip = CoinsTip();
const CChainParams &params{m_chainman.GetParams()};
// The remainder of the function isn't relevant if we are not acting on
// the active chainstate, so return if need be.
if (this != &m_chainman.ActiveChainstate()) {
// Only log every so often so that we don't bury log messages at the
// tip.
constexpr int BACKGROUND_LOG_INTERVAL = 2000;
if (pindexNew->nHeight % BACKGROUND_LOG_INTERVAL == 0) {
UpdateTipLog(coins_tip, pindexNew, params, __func__,
"[background validation] ");
}
return;
}
// New best block
if (m_mempool) {
m_mempool->AddTransactionsUpdated(1);
}
{
LOCK(g_best_block_mutex);
g_best_block = pindexNew;
g_best_block_cv.notify_all();
}
UpdateTipLog(coins_tip, pindexNew, params, __func__, "");
}
/**
* Disconnect m_chain's tip.
* After calling, the mempool will be in an inconsistent state, with
* transactions from disconnected blocks being added to disconnectpool. You
* should make the mempool consistent again by calling updateMempoolForReorg.
* with cs_main held.
*
* If disconnectpool is nullptr, then no disconnected transactions are added to
* disconnectpool (note that the caller is responsible for mempool consistency
* in any case).
*/
bool Chainstate::DisconnectTip(BlockValidationState &state,
DisconnectedBlockTransactions *disconnectpool) {
AssertLockHeld(cs_main);
if (m_mempool) {
AssertLockHeld(m_mempool->cs);
}
CBlockIndex *pindexDelete = m_chain.Tip();
assert(pindexDelete);
assert(pindexDelete->pprev);
// Read block from disk.
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
CBlock &block = *pblock;
if (!m_blockman.ReadBlockFromDisk(block, *pindexDelete)) {
return error("DisconnectTip(): Failed to read block");
}
// Apply the block atomically to the chain state.
int64_t nStart = GetTimeMicros();
{
CCoinsViewCache view(&CoinsTip());
assert(view.GetBestBlock() == pindexDelete->GetBlockHash());
if (DisconnectBlock(block, pindexDelete, view) !=
DisconnectResult::OK) {
return error("DisconnectTip(): DisconnectBlock %s failed",
pindexDelete->GetBlockHash().ToString());
}
bool flushed = view.Flush();
assert(flushed);
}
LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n",
(GetTimeMicros() - nStart) * MILLI);
{
// Prune locks that began at or after the tip should be moved backward
// so they get a chance to reorg
const int max_height_first{pindexDelete->nHeight - 1};
for (auto &prune_lock : m_blockman.m_prune_locks) {
if (prune_lock.second.height_first <= max_height_first) {
continue;
}
prune_lock.second.height_first = max_height_first;
LogPrint(BCLog::PRUNE, "%s prune lock moved back to %d\n",
prune_lock.first, max_height_first);
}
}
// Write the chain state to disk, if necessary.
if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) {
return false;
}
if (m_mempool) {
// If this block is deactivating a fork, we move all mempool
// transactions in front of disconnectpool for reprocessing in a future
// updateMempoolForReorg call
if (pindexDelete->pprev != nullptr &&
GetNextBlockScriptFlags(pindexDelete, m_chainman) !=
GetNextBlockScriptFlags(pindexDelete->pprev, m_chainman)) {
LogPrint(BCLog::MEMPOOL,
"Disconnecting mempool due to rewind of upgrade block\n");
if (disconnectpool) {
disconnectpool->importMempool(*m_mempool);
}
m_mempool->clear();
}
if (disconnectpool) {
disconnectpool->addForBlock(block.vtx, *m_mempool);
}
}
m_chain.SetTip(*pindexDelete->pprev);
UpdateTip(pindexDelete->pprev);
// Let wallets know transactions went from 1-confirmed to
// 0-confirmed or conflicted:
GetMainSignals().BlockDisconnected(pblock, pindexDelete);
return true;
}
static int64_t nTimeReadFromDisk = 0;
static int64_t nTimeConnectTotal = 0;
static int64_t nTimeFlush = 0;
static int64_t nTimeChainState = 0;
static int64_t nTimePostConnect = 0;
/**
* Connect a new block to m_chain. pblock is either nullptr or a pointer to
* a CBlock corresponding to pindexNew, to bypass loading it again from disk.
*/
bool Chainstate::ConnectTip(BlockValidationState &state,
BlockPolicyValidationState &blockPolicyState,
CBlockIndex *pindexNew,
const std::shared_ptr<const CBlock> &pblock,
DisconnectedBlockTransactions &disconnectpool,
const avalanche::Processor *const avalanche,
const ChainstateRole chainstate_role) {
AssertLockHeld(cs_main);
if (m_mempool) {
AssertLockHeld(m_mempool->cs);
}
const Consensus::Params &consensusParams = m_chainman.GetConsensus();
assert(pindexNew->pprev == m_chain.Tip());
// Read block from disk.
int64_t nTime1 = GetTimeMicros();
std::shared_ptr<const CBlock> pthisBlock;
if (!pblock) {
std::shared_ptr<CBlock> pblockNew = std::make_shared<CBlock>();
if (!m_blockman.ReadBlockFromDisk(*pblockNew, *pindexNew)) {
return AbortNode(state, "Failed to read block");
}
pthisBlock = pblockNew;
} else {
pthisBlock = pblock;
}
const CBlock &blockConnecting = *pthisBlock;
// Apply the block atomically to the chain state.
int64_t nTime2 = GetTimeMicros();
nTimeReadFromDisk += nTime2 - nTime1;
int64_t nTime3;
LogPrint(BCLog::BENCH, " - Load block from disk: %.2fms [%.2fs]\n",
(nTime2 - nTime1) * MILLI, nTimeReadFromDisk * MICRO);
{
Amount blockFees{Amount::zero()};
CCoinsViewCache view(&CoinsTip());
bool rv = ConnectBlock(blockConnecting, state, pindexNew, view,
BlockValidationOptions(m_chainman.GetConfig()),
&blockFees);
GetMainSignals().BlockChecked(blockConnecting, state);
if (!rv) {
if (state.IsInvalid()) {
InvalidBlockFound(pindexNew, state);
}
return error("%s: ConnectBlock %s failed, %s", __func__,
pindexNew->GetBlockHash().ToString(),
state.ToString());
}
/**
* The block is valid by consensus rules so now we check if the block
* passes all block policy checks. If not, then park the block and bail.
*
* We check block parking policies before flushing changes to the UTXO
* set. This allows us to avoid rewinding everything immediately after.
*
* Only check block parking policies the first time the block is
* connected. Avalanche voting can override the parking decision made by
* these policies.
*/
const BlockHash blockhash = pindexNew->GetBlockHash();
if (!m_chainman.IsInitialBlockDownload() &&
!m_filterParkingPoliciesApplied.contains(blockhash)) {
m_filterParkingPoliciesApplied.insert(blockhash);
const Amount blockReward =
blockFees +
GetBlockSubsidy(pindexNew->nHeight, consensusParams);
std::vector<std::unique_ptr<ParkingPolicy>> parkingPolicies;
parkingPolicies.emplace_back(std::make_unique<MinerFundPolicy>(
consensusParams, *pindexNew, blockConnecting, blockReward));
if (avalanche) {
// Only enable the RTT policy if the node already finalized a
// block. This is because it's very possible that new blocks
// will be parked after a node restart (but after IBD) if the
// node is behind by a few blocks. We want to make sure that the
// node will be able to switch back to the right tip in this
// case.
if (avalanche->hasFinalizedTip()) {
// Special case for testnet, don't reject blocks mined with
// the min difficulty
if (!consensusParams.fPowAllowMinDifficultyBlocks ||
(blockConnecting.GetBlockTime() <=
pindexNew->pprev->GetBlockTime() +
2 * consensusParams.nPowTargetSpacing)) {
parkingPolicies.emplace_back(
std::make_unique<RTTPolicy>(consensusParams,
*pindexNew));
}
}
parkingPolicies.emplace_back(
std::make_unique<StakingRewardsPolicy>(
*avalanche, consensusParams, *pindexNew,
blockConnecting, blockReward));
if (m_mempool) {
parkingPolicies.emplace_back(
std::make_unique<PreConsensusPolicy>(
*pindexNew, blockConnecting, m_mempool));
}
}
// If any block policy is violated, bail on the first one found
if (std::find_if_not(parkingPolicies.begin(), parkingPolicies.end(),
[&](const auto &policy) {
bool ret = (*policy)(blockPolicyState);
if (!ret) {
LogPrintf(
"Park block because it "
"violated a block policy: %s\n",
blockPolicyState.ToString());
}
return ret;
}) != parkingPolicies.end()) {
pindexNew->nStatus = pindexNew->nStatus.withParked();
m_blockman.m_dirty_blockindex.insert(pindexNew);
return false;
}
}
nTime3 = GetTimeMicros();
nTimeConnectTotal += nTime3 - nTime2;
assert(nBlocksTotal > 0);
LogPrint(BCLog::BENCH,
" - Connect total: %.2fms [%.2fs (%.2fms/blk)]\n",
(nTime3 - nTime2) * MILLI, nTimeConnectTotal * MICRO,
nTimeConnectTotal * MILLI / nBlocksTotal);
bool flushed = view.Flush();
assert(flushed);
}
int64_t nTime4 = GetTimeMicros();
nTimeFlush += nTime4 - nTime3;
LogPrint(BCLog::BENCH, " - Flush: %.2fms [%.2fs (%.2fms/blk)]\n",
(nTime4 - nTime3) * MILLI, nTimeFlush * MICRO,
nTimeFlush * MILLI / nBlocksTotal);
// Write the chain state to disk, if necessary.
if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) {
return false;
}
int64_t nTime5 = GetTimeMicros();
nTimeChainState += nTime5 - nTime4;
LogPrint(BCLog::BENCH,
" - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n",
(nTime5 - nTime4) * MILLI, nTimeChainState * MICRO,
nTimeChainState * MILLI / nBlocksTotal);
// Remove conflicting transactions from the mempool;
if (m_mempool) {
disconnectpool.removeForBlock(blockConnecting.vtx, *m_mempool);
// If this block is activating a fork, we move all mempool transactions
// in front of disconnectpool for reprocessing in a future
// updateMempoolForReorg call
if (pindexNew->pprev != nullptr &&
GetNextBlockScriptFlags(pindexNew, m_chainman) !=
GetNextBlockScriptFlags(pindexNew->pprev, m_chainman)) {
LogPrint(
BCLog::MEMPOOL,
"Disconnecting mempool due to acceptance of upgrade block\n");
disconnectpool.importMempool(*m_mempool);
}
}
// Update m_chain & related variables.
m_chain.SetTip(*pindexNew);
UpdateTip(pindexNew);
int64_t nTime6 = GetTimeMicros();
nTimePostConnect += nTime6 - nTime5;
nTimeTotal += nTime6 - nTime1;
LogPrint(BCLog::BENCH,
" - Connect postprocess: %.2fms [%.2fs (%.2fms/blk)]\n",
(nTime6 - nTime5) * MILLI, nTimePostConnect * MICRO,
nTimePostConnect * MILLI / nBlocksTotal);
LogPrint(BCLog::BENCH, "- Connect block: %.2fms [%.2fs (%.2fms/blk)]\n",
(nTime6 - nTime1) * MILLI, nTimeTotal * MICRO,
nTimeTotal * MILLI / nBlocksTotal);
// If we are the background validation chainstate, check to see if we are
// done validating the snapshot (i.e. our tip has reached the snapshot's
// base block).
if (this != &m_chainman.ActiveChainstate()) {
// This call may set `m_disabled`, which is referenced immediately
// afterwards in ActivateBestChain, so that we stop connecting blocks
// past the snapshot base.
m_chainman.MaybeCompleteSnapshotValidation();
}
GetMainSignals().BlockConnected(chainstate_role, pthisBlock, pindexNew);
return true;
}
/**
* Return the tip of the chain with the most work in it, that isn't known to be
* invalid (it's however far from certain to be valid).
*/
CBlockIndex *Chainstate::FindMostWorkChain(
std::vector<const CBlockIndex *> &blocksToReconcile, bool fAutoUnpark) {
AssertLockHeld(::cs_main);
do {
CBlockIndex *pindexNew = nullptr;
// Find the best candidate header.
{
std::set<CBlockIndex *, CBlockIndexWorkComparator>::reverse_iterator
it = setBlockIndexCandidates.rbegin();
if (it == setBlockIndexCandidates.rend()) {
return nullptr;
}
pindexNew = *it;
}
// If this block will cause an avalanche finalized block to be reorged,
// then we park it.
{
LOCK(cs_avalancheFinalizedBlockIndex);
if (m_avalancheFinalizedBlockIndex &&
!AreOnTheSameFork(pindexNew, m_avalancheFinalizedBlockIndex)) {
LogPrintf("Park block %s because it forks prior to the "
"avalanche finalized chaintip.\n",
pindexNew->GetBlockHash().ToString());
pindexNew->nStatus = pindexNew->nStatus.withParked();
m_blockman.m_dirty_blockindex.insert(pindexNew);
}
}
const CBlockIndex *pindexFork = m_chain.FindFork(pindexNew);
// Check whether all blocks on the path between the currently active
// chain and the candidate are valid. Just going until the active chain
// is an optimization, as we know all blocks in it are valid already.
CBlockIndex *pindexTest = pindexNew;
bool hasValidAncestor = true;
while (hasValidAncestor && pindexTest && pindexTest != pindexFork) {
assert(pindexTest->HaveNumChainTxs() || pindexTest->nHeight == 0);
// If this is a parked chain, but it has enough PoW, clear the park
// state.
bool fParkedChain = pindexTest->nStatus.isOnParkedChain();
if (fAutoUnpark && fParkedChain) {
const CBlockIndex *pindexTip = m_chain.Tip();
// During initialization, pindexTip and/or pindexFork may be
// null. In this case, we just ignore the fact that the chain is
// parked.
if (!pindexTip || !pindexFork) {
UnparkBlock(pindexTest);
continue;
}
// A parked chain can be unparked if it has twice as much PoW
// accumulated as the main chain has since the fork block.
CBlockIndex const *pindexExtraPow = pindexTip;
arith_uint256 requiredWork = pindexTip->nChainWork;
switch (pindexTip->nHeight - pindexFork->nHeight) {
// Limit the penality for depth 1, 2 and 3 to half a block
// worth of work to ensure we don't fork accidentally.
case 3:
case 2:
pindexExtraPow = pindexExtraPow->pprev;
// FALLTHROUGH
case 1: {
const arith_uint256 deltaWork =
pindexExtraPow->nChainWork - pindexFork->nChainWork;
requiredWork += (deltaWork >> 1);
break;
}
default:
requiredWork +=
pindexExtraPow->nChainWork - pindexFork->nChainWork;
break;
}
if (pindexNew->nChainWork > requiredWork) {
// We have enough, clear the parked state.
LogPrintf("Unpark chain up to block %s as it has "
"accumulated enough PoW.\n",
pindexNew->GetBlockHash().ToString());
fParkedChain = false;
UnparkBlock(pindexTest);
}
}
// Pruned nodes may have entries in setBlockIndexCandidates for
// which block files have been deleted. Remove those as candidates
// for the most work chain if we come across them; we can't switch
// to a chain unless we have all the non-active-chain parent blocks.
bool fInvalidChain = pindexTest->nStatus.isInvalid();
bool fMissingData = !pindexTest->nStatus.hasData();
if (!(fInvalidChain || fParkedChain || fMissingData)) {
// The current block is acceptable, move to the parent, up to
// the fork point.
pindexTest = pindexTest->pprev;
continue;
}
// Candidate chain is not usable (either invalid or parked or
// missing data)
hasValidAncestor = false;
setBlockIndexCandidates.erase(pindexTest);
if (fInvalidChain && (m_chainman.m_best_invalid == nullptr ||
pindexNew->nChainWork >
m_chainman.m_best_invalid->nChainWork)) {
m_chainman.m_best_invalid = pindexNew;
}
if (fParkedChain && (m_chainman.m_best_parked == nullptr ||
pindexNew->nChainWork >
m_chainman.m_best_parked->nChainWork)) {
m_chainman.m_best_parked = pindexNew;
}
LogPrintf("Considered switching to better tip %s but that chain "
"contains a%s%s%s block.\n",
pindexNew->GetBlockHash().ToString(),
fInvalidChain ? "n invalid" : "",
fParkedChain ? " parked" : "",
fMissingData ? " missing-data" : "");
CBlockIndex *pindexFailed = pindexNew;
// Remove the entire chain from the set.
while (pindexTest != pindexFailed) {
if (fInvalidChain || fParkedChain) {
pindexFailed->nStatus =
pindexFailed->nStatus.withFailedParent(fInvalidChain)
.withParkedParent(fParkedChain);
} else if (fMissingData) {
// If we're missing data, then add back to
// m_blocks_unlinked, so that if the block arrives in the
// future we can try adding to setBlockIndexCandidates
// again.
m_blockman.m_blocks_unlinked.insert(
std::make_pair(pindexFailed->pprev, pindexFailed));
}
setBlockIndexCandidates.erase(pindexFailed);
pindexFailed = pindexFailed->pprev;
}
if (fInvalidChain || fParkedChain) {
// We discovered a new chain tip that is either parked or
// invalid, we may want to warn.
CheckForkWarningConditionsOnNewFork(pindexNew);
}
}
blocksToReconcile.push_back(pindexNew);
// We found a candidate that has valid ancestors. This is our guy.
if (hasValidAncestor) {
return pindexNew;
}
} while (true);
}
/**
* Delete all entries in setBlockIndexCandidates that are worse than the current
* tip.
*/
void Chainstate::PruneBlockIndexCandidates() {
// Note that we can't delete the current block itself, as we may need to
// return to it later in case a reorganization to a better block fails.
auto it = setBlockIndexCandidates.begin();
while (it != setBlockIndexCandidates.end() &&
setBlockIndexCandidates.value_comp()(*it, m_chain.Tip())) {
setBlockIndexCandidates.erase(it++);
}
// Either the current tip or a successor of it we're working towards is left
// in setBlockIndexCandidates.
assert(!setBlockIndexCandidates.empty());
}
/**
* Try to make some progress towards making pindexMostWork the active block.
* pblock is either nullptr or a pointer to a CBlock corresponding to
* pindexMostWork.
*
* @returns true unless a system error occurred
*/
bool Chainstate::ActivateBestChainStep(
BlockValidationState &state, CBlockIndex *pindexMostWork,
const std::shared_ptr<const CBlock> &pblock, bool &fInvalidFound,
const avalanche::Processor *const avalanche,
const ChainstateRole chainstate_role) {
AssertLockHeld(cs_main);
if (m_mempool) {
AssertLockHeld(m_mempool->cs);
}
const CBlockIndex *pindexOldTip = m_chain.Tip();
const CBlockIndex *pindexFork = m_chain.FindFork(pindexMostWork);
// Disconnect active blocks which are no longer in the best chain.
bool fBlocksDisconnected = false;
DisconnectedBlockTransactions disconnectpool;
while (m_chain.Tip() && m_chain.Tip() != pindexFork) {
if (m_mempool && !fBlocksDisconnected) {
// Import and clear mempool; we must do this to preserve
// topological ordering in the mempool index. This is ok since
// inserts into the mempool are very fast now in our new
// implementation.
disconnectpool.importMempool(*m_mempool);
}
if (!DisconnectTip(state, &disconnectpool)) {
// This is likely a fatal error, but keep the mempool consistent,
// just in case. Only remove from the mempool in this case.
if (m_mempool) {
disconnectpool.updateMempoolForReorg(*this, false, *m_mempool);
}
// If we're unable to disconnect a block during normal operation,
// then that is a failure of our local system -- we should abort
// rather than stay on a less work chain.
AbortNode(state,
"Failed to disconnect block; see debug.log for details");
return false;
}
fBlocksDisconnected = true;
}
// Build list of new blocks to connect.
std::vector<CBlockIndex *> vpindexToConnect;
bool fContinue = true;
int nHeight = pindexFork ? pindexFork->nHeight : -1;
while (fContinue && nHeight != pindexMostWork->nHeight) {
// Don't iterate the entire list of potential improvements toward the
// best tip, as we likely only need a few blocks along the way.
int nTargetHeight = std::min(nHeight + 32, pindexMostWork->nHeight);
vpindexToConnect.clear();
vpindexToConnect.reserve(nTargetHeight - nHeight);
CBlockIndex *pindexIter = pindexMostWork->GetAncestor(nTargetHeight);
while (pindexIter && pindexIter->nHeight != nHeight) {
vpindexToConnect.push_back(pindexIter);
pindexIter = pindexIter->pprev;
}
nHeight = nTargetHeight;
// Connect new blocks.
for (CBlockIndex *pindexConnect : reverse_iterate(vpindexToConnect)) {
BlockPolicyValidationState blockPolicyState;
if (!ConnectTip(state, blockPolicyState, pindexConnect,
pindexConnect == pindexMostWork
? pblock
: std::shared_ptr<const CBlock>(),
disconnectpool, avalanche, chainstate_role)) {
if (state.IsInvalid()) {
// The block violates a consensus rule.
if (state.GetResult() !=
BlockValidationResult::BLOCK_MUTATED) {
InvalidChainFound(vpindexToConnect.back());
}
state = BlockValidationState();
fInvalidFound = true;
fContinue = false;
break;
}
if (blockPolicyState.IsInvalid()) {
// The block violates a policy rule.
fContinue = false;
break;
}
// A system error occurred (disk space, database error, ...).
// Make the mempool consistent with the current tip, just in
// case any observers try to use it before shutdown.
if (m_mempool) {
disconnectpool.updateMempoolForReorg(*this, false,
*m_mempool);
}
return false;
} else {
PruneBlockIndexCandidates();
if (!pindexOldTip ||
m_chain.Tip()->nChainWork > pindexOldTip->nChainWork) {
// We're in a better position than we were. Return
// temporarily to release the lock.
fContinue = false;
break;
}
}
}
}
if (m_mempool) {
if (fBlocksDisconnected || !disconnectpool.isEmpty()) {
// If any blocks were disconnected, we need to update the mempool
// even if disconnectpool is empty. The disconnectpool may also be
// non-empty if the mempool was imported due to new validation rules
// being in effect.
LogPrint(BCLog::MEMPOOL,
"Updating mempool due to reorganization or "
"rules upgrade/downgrade\n");
disconnectpool.updateMempoolForReorg(*this, true, *m_mempool);
}
m_mempool->check(this->CoinsTip(), this->m_chain.Height() + 1);
}
// Callbacks/notifications for a new best chain.
if (fInvalidFound) {
CheckForkWarningConditionsOnNewFork(pindexMostWork);
} else {
CheckForkWarningConditions();
}
return true;
}
static SynchronizationState GetSynchronizationState(bool init) {
if (!init) {
return SynchronizationState::POST_INIT;
}
if (::fReindex) {
return SynchronizationState::INIT_REINDEX;
}
return SynchronizationState::INIT_DOWNLOAD;
}
static bool NotifyHeaderTip(ChainstateManager &chainman)
LOCKS_EXCLUDED(cs_main) {
bool fNotify = false;
bool fInitialBlockDownload = false;
static CBlockIndex *pindexHeaderOld = nullptr;
CBlockIndex *pindexHeader = nullptr;
{
LOCK(cs_main);
pindexHeader = chainman.m_best_header;
if (pindexHeader != pindexHeaderOld) {
fNotify = true;
fInitialBlockDownload = chainman.IsInitialBlockDownload();
pindexHeaderOld = pindexHeader;
}
}
// Send block tip changed notifications without cs_main
if (fNotify) {
chainman.GetNotifications().headerTip(
GetSynchronizationState(fInitialBlockDownload),
pindexHeader->nHeight, pindexHeader->nTime, false);
}
return fNotify;
}
static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) {
AssertLockNotHeld(cs_main);
if (GetMainSignals().CallbacksPending() > 10) {
SyncWithValidationInterfaceQueue();
}
}
bool Chainstate::ActivateBestChain(BlockValidationState &state,
std::shared_ptr<const CBlock> pblock,
avalanche::Processor *const avalanche) {
AssertLockNotHeld(m_chainstate_mutex);
// Note that while we're often called here from ProcessNewBlock, this is
// far from a guarantee. Things in the P2P/RPC will often end up calling
// us in the middle of ProcessNewBlock - do not assume pblock is set
// sanely for performance or correctness!
AssertLockNotHeld(::cs_main);
// ABC maintains a fair degree of expensive-to-calculate internal state
// because this function periodically releases cs_main so that it does not
// lock up other threads for too long during large connects - and to allow
// for e.g. the callback queue to drain we use m_chainstate_mutex to enforce
// mutual exclusion so that only one caller may execute this function at a
// time
LOCK(m_chainstate_mutex);
// Belt-and-suspenders check that we aren't attempting to advance the
// background chainstate past the snapshot base block.
if (WITH_LOCK(::cs_main, return m_disabled)) {
LogPrintf("m_disabled is set - this chainstate should not be in "
"operation. Please report this as a bug. %s\n",
PACKAGE_BUGREPORT);
return false;
}
CBlockIndex *pindexMostWork = nullptr;
CBlockIndex *pindexNewTip = nullptr;
int nStopAtHeight = gArgs.GetIntArg("-stopatheight", DEFAULT_STOPATHEIGHT);
bool exited_ibd{false};
do {
// Block until the validation queue drains. This should largely
// never happen in normal operation, however may happen during
// reindex, causing memory blowup if we run too far ahead.
// Note that if a validationinterface callback ends up calling
// ActivateBestChain this may lead to a deadlock! We should
// probably have a DEBUG_LOCKORDER test for this in the future.
LimitValidationInterfaceQueue();
std::vector<const CBlockIndex *> blocksToReconcile;
bool blocks_connected = false;
const bool fAutoUnpark =
gArgs.GetBoolArg("-automaticunparking", !avalanche);
{
LOCK(cs_main);
// Lock transaction pool for at least as long as it takes for
// updateMempoolForReorg to be executed if needed
LOCK(MempoolMutex());
const bool was_in_ibd = m_chainman.IsInitialBlockDownload();
CBlockIndex *starting_tip = m_chain.Tip();
do {
// We absolutely may not unlock cs_main until we've made forward
// progress (with the exception of shutdown due to hardware
// issues, low disk space, etc).
if (pindexMostWork == nullptr) {
pindexMostWork =
FindMostWorkChain(blocksToReconcile, fAutoUnpark);
}
// Whether we have anything to do at all.
if (pindexMostWork == nullptr ||
pindexMostWork == m_chain.Tip()) {
break;
}
bool fInvalidFound = false;
std::shared_ptr<const CBlock> nullBlockPtr;
// BlockConnected signals must be sent for the original role;
// in case snapshot validation is completed during
// ActivateBestChainStep, the result of GetRole() changes from
// BACKGROUND to NORMAL.
const ChainstateRole chainstate_role{this->GetRole()};
if (!ActivateBestChainStep(
state, pindexMostWork,
pblock && pblock->GetHash() ==
pindexMostWork->GetBlockHash()
? pblock
: nullBlockPtr,
fInvalidFound, avalanche, chainstate_role)) {
// A system error occurred
return false;
}
blocks_connected = true;
if (fInvalidFound ||
(pindexMostWork && pindexMostWork->nStatus.isParked())) {
// Wipe cache, we may need another branch now.
pindexMostWork = nullptr;
}
pindexNewTip = m_chain.Tip();
// This will have been toggled in
// ActivateBestChainStep -> ConnectTip ->
// MaybeCompleteSnapshotValidation, if at all, so we should
// catch it here.
//
// Break this do-while to ensure we don't advance past the base
// snapshot.
if (m_disabled) {
break;
}
} while (!m_chain.Tip() ||
(starting_tip && CBlockIndexWorkComparator()(
m_chain.Tip(), starting_tip)));
// Check the index once we're done with the above loop, since
// we're going to release cs_main soon. If the index is in a bad
// state now, then it's better to know immediately rather than
// randomly have it cause a problem in a race.
m_chainman.CheckBlockIndex();
if (blocks_connected) {
const CBlockIndex *pindexFork = m_chain.FindFork(starting_tip);
bool still_in_ibd = m_chainman.IsInitialBlockDownload();
if (was_in_ibd && !still_in_ibd) {
// Active chainstate has exited IBD
exited_ibd = true;
}
// Notify external listeners about the new tip.
// Enqueue while holding cs_main to ensure that UpdatedBlockTip
// is called in the order in which blocks are connected
if (this == &m_chainman.ActiveChainstate() &&
pindexFork != pindexNewTip) {
// Notify ValidationInterface subscribers
GetMainSignals().UpdatedBlockTip(pindexNewTip, pindexFork,
still_in_ibd);
// Always notify the UI if a new block tip was connected
m_chainman.GetNotifications().blockTip(
GetSynchronizationState(still_in_ibd), *pindexNewTip);
}
}
}
// When we reach this point, we switched to a new tip (stored in
// pindexNewTip).
if (avalanche) {
const CBlockIndex *pfinalized =
WITH_LOCK(cs_avalancheFinalizedBlockIndex,
return m_avalancheFinalizedBlockIndex);
for (const CBlockIndex *pindex : blocksToReconcile) {
avalanche->addToReconcile(pindex);
// Compute staking rewards for all blocks with more chainwork to
// just after the finalized block. We could stop at the fork
// point, but this is more robust.
if (blocks_connected) {
const CBlockIndex *pindexTest = pindex;
while (pindexTest && pindexTest != pfinalized) {
if (pindexTest->nHeight < pindex->nHeight - 3) {
// Only compute up to some max depth
break;
}
avalanche->computeStakingReward(pindexTest);
pindexTest = pindexTest->pprev;
}
}
}
}
if (!blocks_connected) {
return true;
}
if (nStopAtHeight && pindexNewTip &&
pindexNewTip->nHeight >= nStopAtHeight) {
StartShutdown();
}
if (exited_ibd) {
// If a background chainstate is in use, we may need to rebalance
// our allocation of caches once a chainstate exits initial block
// download.
LOCK(::cs_main);
m_chainman.MaybeRebalanceCaches();
}
if (WITH_LOCK(::cs_main, return m_disabled)) {
// Background chainstate has reached the snapshot base block, so
// exit.
// Restart indexes to resume indexing for all blocks unique to the
// snapshot chain. This resumes indexing "in order" from where the
// indexing on the background validation chain left off.
//
// This cannot be done while holding cs_main (within
// MaybeCompleteSnapshotValidation) or a cs_main deadlock will
// occur.
if (m_chainman.snapshot_download_completed) {
m_chainman.snapshot_download_completed();
}
break;
}
// We check shutdown only after giving ActivateBestChainStep a chance to
// run once so that we never shutdown before connecting the genesis
// block during LoadChainTip(). Previously this caused an assert()
// failure during shutdown in such cases as the UTXO DB flushing checks
// that the best block hash is non-null.
if (ShutdownRequested()) {
break;
}
} while (pindexNewTip != pindexMostWork);
// Write changes periodically to disk, after relay.
if (!FlushStateToDisk(state, FlushStateMode::PERIODIC)) {
return false;
}
return true;
}
bool Chainstate::PreciousBlock(BlockValidationState &state, CBlockIndex *pindex,
avalanche::Processor *const avalanche) {
AssertLockNotHeld(m_chainstate_mutex);
AssertLockNotHeld(::cs_main);
{
LOCK(cs_main);
if (pindex->nChainWork < m_chain.Tip()->nChainWork) {
// Nothing to do, this block is not at the tip.
return true;
}
if (m_chain.Tip()->nChainWork > m_chainman.nLastPreciousChainwork) {
// The chain has been extended since the last call, reset the
// counter.
m_chainman.nBlockReverseSequenceId = -1;
}
m_chainman.nLastPreciousChainwork = m_chain.Tip()->nChainWork;
setBlockIndexCandidates.erase(pindex);
pindex->nSequenceId = m_chainman.nBlockReverseSequenceId;
if (m_chainman.nBlockReverseSequenceId >
std::numeric_limits<int32_t>::min()) {
// We can't keep reducing the counter if somebody really wants to
// call preciousblock 2**31-1 times on the same set of tips...
m_chainman.nBlockReverseSequenceId--;
}
// In case this was parked, unpark it.
UnparkBlock(pindex);
// Make sure it is added to the candidate list if appropriate.
if (pindex->IsValid(BlockValidity::TRANSACTIONS) &&
pindex->HaveNumChainTxs()) {
setBlockIndexCandidates.insert(pindex);
PruneBlockIndexCandidates();
}
}
return ActivateBestChain(state, /*pblock=*/nullptr, avalanche);
}
namespace {
// Leverage RAII to run a functor at scope end
template <typename Func> struct Defer {
Func func;
Defer(Func &&f) : func(std::move(f)) {}
~Defer() { func(); }
};
} // namespace
bool Chainstate::UnwindBlock(BlockValidationState &state, CBlockIndex *pindex,
bool invalidate) {
// Genesis block can't be invalidated or parked
assert(pindex);
if (pindex->nHeight == 0) {
return false;
}
CBlockIndex *to_mark_failed_or_parked = pindex;
bool pindex_was_in_chain = false;
int disconnected = 0;
// We do not allow ActivateBestChain() to run while UnwindBlock() is
// running, as that could cause the tip to change while we disconnect
// blocks. (Note for backport of Core PR16849: we acquire
// LOCK(m_chainstate_mutex) in the Park, Invalidate and FinalizeBlock
// functions due to differences in our code)
AssertLockHeld(m_chainstate_mutex);
// We'll be acquiring and releasing cs_main below, to allow the validation
// callbacks to run. However, we should keep the block index in a
// consistent state as we disconnect blocks -- in particular we need to
// add equal-work blocks to setBlockIndexCandidates as we disconnect.
// To avoid walking the block index repeatedly in search of candidates,
// build a map once so that we can look up candidate blocks by chain
// work as we go.
std::multimap<const arith_uint256, CBlockIndex *> candidate_blocks_by_work;
{
LOCK(cs_main);
for (auto &entry : m_blockman.m_block_index) {
CBlockIndex *candidate = &entry.second;
// We don't need to put anything in our active chain into the
// multimap, because those candidates will be found and considered
// as we disconnect.
// Instead, consider only non-active-chain blocks that have at
// least as much work as where we expect the new tip to end up.
if (!m_chain.Contains(candidate) &&
!CBlockIndexWorkComparator()(candidate, pindex->pprev) &&
candidate->IsValid(BlockValidity::TRANSACTIONS) &&
candidate->HaveNumChainTxs()) {
candidate_blocks_by_work.insert(
std::make_pair(candidate->nChainWork, candidate));
}
}
}
{
LOCK(cs_main);
// Lock for as long as disconnectpool is in scope to make sure
// UpdateMempoolForReorg is called after DisconnectTip without unlocking
// in between
LOCK(MempoolMutex());
constexpr int maxDisconnectPoolBlocks = 10;
bool ret = false;
DisconnectedBlockTransactions disconnectpool;
// After 10 blocks this becomes nullptr, so that DisconnectTip will
// stop giving us unwound block txs if we are doing a deep unwind.
DisconnectedBlockTransactions *optDisconnectPool = &disconnectpool;
// Disable thread safety analysis because we can't require m_mempool->cs
// as m_mempool can be null. We keep the runtime analysis though.
Defer deferred([&]() NO_THREAD_SAFETY_ANALYSIS {
AssertLockHeld(cs_main);
if (m_mempool && !disconnectpool.isEmpty()) {
AssertLockHeld(m_mempool->cs);
// DisconnectTip will add transactions to disconnectpool.
// When all unwinding is done and we are on a new tip, we must
// add all transactions back to the mempool against the new tip.
disconnectpool.updateMempoolForReorg(*this,
/* fAddToMempool = */ ret,
*m_mempool);
}
});
// Disconnect (descendants of) pindex, and mark them invalid.
while (true) {
if (ShutdownRequested()) {
break;
}
// Make sure the queue of validation callbacks doesn't grow
// unboundedly.
// FIXME this commented code is a regression and could cause OOM if
// a very old block is invalidated via the invalidateblock RPC.
// This can be uncommented if the main signals are moved away from
// cs_main or this code is refactored so that cs_main can be
// released at this point.
//
// LimitValidationInterfaceQueue();
if (!m_chain.Contains(pindex)) {
break;
}
if (m_mempool && disconnected == 0) {
// On first iteration, we grab all the mempool txs to preserve
// topological ordering. This has the side-effect of temporarily
// clearing the mempool, but we will re-add later in
// updateMempoolForReorg() (above). This technique guarantees
// mempool consistency as well as ensures that our topological
// entry_id index is always correct.
disconnectpool.importMempool(*m_mempool);
}
pindex_was_in_chain = true;
CBlockIndex *invalid_walk_tip = m_chain.Tip();
// ActivateBestChain considers blocks already in m_chain
// unconditionally valid already, so force disconnect away from it.
ret = DisconnectTip(state, optDisconnectPool);
++disconnected;
if (optDisconnectPool && disconnected > maxDisconnectPoolBlocks) {
// Stop using the disconnect pool after 10 blocks. After 10
// blocks we no longer add block tx's to the disconnectpool.
// However, when this scope ends we will reconcile what's
// in the pool with the new tip (in the deferred d'tor above).
optDisconnectPool = nullptr;
}
if (!ret) {
return false;
}
assert(invalid_walk_tip->pprev == m_chain.Tip());
// We immediately mark the disconnected blocks as invalid.
// This prevents a case where pruned nodes may fail to
// invalidateblock and be left unable to start as they have no tip
// candidates (as there are no blocks that meet the "have data and
// are not invalid per nStatus" criteria for inclusion in
// setBlockIndexCandidates).
invalid_walk_tip->nStatus =
invalidate ? invalid_walk_tip->nStatus.withFailed()
: invalid_walk_tip->nStatus.withParked();
m_blockman.m_dirty_blockindex.insert(invalid_walk_tip);
setBlockIndexCandidates.insert(invalid_walk_tip->pprev);
if (invalid_walk_tip == to_mark_failed_or_parked->pprev &&
(invalidate ? to_mark_failed_or_parked->nStatus.hasFailed()
: to_mark_failed_or_parked->nStatus.isParked())) {
// We only want to mark the last disconnected block as
// Failed (or Parked); its children need to be FailedParent (or
// ParkedParent) instead.
to_mark_failed_or_parked->nStatus =
(invalidate
? to_mark_failed_or_parked->nStatus.withFailed(false)
.withFailedParent()
: to_mark_failed_or_parked->nStatus.withParked(false)
.withParkedParent());
m_blockman.m_dirty_blockindex.insert(to_mark_failed_or_parked);
}
// Add any equal or more work headers to setBlockIndexCandidates
auto candidate_it = candidate_blocks_by_work.lower_bound(
invalid_walk_tip->pprev->nChainWork);
while (candidate_it != candidate_blocks_by_work.end()) {
if (!CBlockIndexWorkComparator()(candidate_it->second,
invalid_walk_tip->pprev)) {
setBlockIndexCandidates.insert(candidate_it->second);
candidate_it = candidate_blocks_by_work.erase(candidate_it);
} else {
++candidate_it;
}
}
// Track the last disconnected block, so we can correct its
// FailedParent (or ParkedParent) status in future iterations, or,
// if it's the last one, call InvalidChainFound on it.
to_mark_failed_or_parked = invalid_walk_tip;
}
}
m_chainman.CheckBlockIndex();
{
LOCK(cs_main);
if (m_chain.Contains(to_mark_failed_or_parked)) {
// If the to-be-marked invalid block is in the active chain,
// something is interfering and we can't proceed.
return false;
}
// Mark pindex (or the last disconnected block) as invalid (or parked),
// even when it never was in the main chain.
to_mark_failed_or_parked->nStatus =
invalidate ? to_mark_failed_or_parked->nStatus.withFailed()
: to_mark_failed_or_parked->nStatus.withParked();
m_blockman.m_dirty_blockindex.insert(to_mark_failed_or_parked);
if (invalidate) {
m_chainman.m_failed_blocks.insert(to_mark_failed_or_parked);
}
// If any new blocks somehow arrived while we were disconnecting
// (above), then the pre-calculation of what should go into
// setBlockIndexCandidates may have missed entries. This would
// technically be an inconsistency in the block index, but if we clean
// it up here, this should be an essentially unobservable error.
// Loop back over all block index entries and add any missing entries
// to setBlockIndexCandidates.
for (auto &[_, block_index] : m_blockman.m_block_index) {
if (block_index.IsValid(BlockValidity::TRANSACTIONS) &&
block_index.HaveNumChainTxs() &&
!setBlockIndexCandidates.value_comp()(&block_index,
m_chain.Tip())) {
setBlockIndexCandidates.insert(&block_index);
}
}
if (invalidate) {
InvalidChainFound(to_mark_failed_or_parked);
}
}
// Only notify about a new block tip if the active chain was modified.
if (pindex_was_in_chain) {
m_chainman.GetNotifications().blockTip(
GetSynchronizationState(m_chainman.IsInitialBlockDownload()),
*to_mark_failed_or_parked->pprev);
}
return true;
}
bool Chainstate::InvalidateBlock(BlockValidationState &state,
CBlockIndex *pindex) {
AssertLockNotHeld(m_chainstate_mutex);
AssertLockNotHeld(::cs_main);
// See 'Note for backport of Core PR16849' in Chainstate::UnwindBlock
LOCK(m_chainstate_mutex);
return UnwindBlock(state, pindex, true);
}
bool Chainstate::ParkBlock(BlockValidationState &state, CBlockIndex *pindex) {
AssertLockNotHeld(m_chainstate_mutex);
AssertLockNotHeld(::cs_main);
// See 'Note for backport of Core PR16849' in Chainstate::UnwindBlock
LOCK(m_chainstate_mutex);
return UnwindBlock(state, pindex, false);
}
template <typename F>
bool Chainstate::UpdateFlagsForBlock(CBlockIndex *pindexBase,
CBlockIndex *pindex, F f) {
BlockStatus newStatus = f(pindex->nStatus);
if (pindex->nStatus != newStatus &&
(!pindexBase ||
pindex->GetAncestor(pindexBase->nHeight) == pindexBase)) {
pindex->nStatus = newStatus;
m_blockman.m_dirty_blockindex.insert(pindex);
if (newStatus.isValid()) {
m_chainman.m_failed_blocks.erase(pindex);
}
if (pindex->IsValid(BlockValidity::TRANSACTIONS) &&
pindex->HaveNumChainTxs() &&
setBlockIndexCandidates.value_comp()(m_chain.Tip(), pindex)) {
setBlockIndexCandidates.insert(pindex);
}
return true;
}
return false;
}
template <typename F, typename C, typename AC>
void Chainstate::UpdateFlags(CBlockIndex *pindex, CBlockIndex *&pindexReset,
F f, C fChild, AC fAncestorWasChanged) {
AssertLockHeld(cs_main);
// Update the current block and ancestors; while we're doing this, identify
// which was the deepest ancestor we changed.
CBlockIndex *pindexDeepestChanged = pindex;
for (auto pindexAncestor = pindex; pindexAncestor != nullptr;
pindexAncestor = pindexAncestor->pprev) {
if (UpdateFlagsForBlock(nullptr, pindexAncestor, f)) {
pindexDeepestChanged = pindexAncestor;
}
}
if (pindexReset &&
pindexReset->GetAncestor(pindexDeepestChanged->nHeight) ==
pindexDeepestChanged) {
// reset pindexReset if it had a modified ancestor.
pindexReset = nullptr;
}
// Update all blocks under modified blocks.
for (auto &[_, block_index] : m_blockman.m_block_index) {
UpdateFlagsForBlock(pindex, &block_index, fChild);
UpdateFlagsForBlock(pindexDeepestChanged, &block_index,
fAncestorWasChanged);
}
}
void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) {
AssertLockHeld(cs_main);
UpdateFlags(
pindex, m_chainman.m_best_invalid,
[](const BlockStatus status) {
return status.withClearedFailureFlags();
},
[](const BlockStatus status) {
return status.withClearedFailureFlags();
},
[](const BlockStatus status) {
return status.withFailedParent(false);
});
}
void Chainstate::TryAddBlockIndexCandidate(CBlockIndex *pindex) {
AssertLockHeld(cs_main);
// The block only is a candidate for the most-work-chain if it has the same
// or more work than our current tip.
if (m_chain.Tip() != nullptr &&
setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) {
return;
}
bool is_active_chainstate = this == &m_chainman.ActiveChainstate();
if (is_active_chainstate) {
// The active chainstate should always add entries that have more
// work than the tip.
setBlockIndexCandidates.insert(pindex);
} else if (!m_disabled) {
// For the background chainstate, we only consider connecting blocks
// towards the snapshot base (which can't be nullptr or else we'll
// never make progress).
const CBlockIndex *snapshot_base{
Assert(m_chainman.GetSnapshotBaseBlock())};
if (snapshot_base->GetAncestor(pindex->nHeight) == pindex) {
setBlockIndexCandidates.insert(pindex);
}
}
}
void Chainstate::UnparkBlockImpl(CBlockIndex *pindex, bool fClearChildren) {
AssertLockHeld(cs_main);
UpdateFlags(
pindex, m_chainman.m_best_parked,
[](const BlockStatus status) {
return status.withClearedParkedFlags();
},
[fClearChildren](const BlockStatus status) {
return fClearChildren ? status.withClearedParkedFlags()
: status.withParkedParent(false);
},
[](const BlockStatus status) {
return status.withParkedParent(false);
});
}
void Chainstate::UnparkBlockAndChildren(CBlockIndex *pindex) {
return UnparkBlockImpl(pindex, true);
}
void Chainstate::UnparkBlock(CBlockIndex *pindex) {
return UnparkBlockImpl(pindex, false);
}
bool Chainstate::AvalancheFinalizeBlock(CBlockIndex *pindex,
avalanche::Processor &avalanche) {
AssertLockNotHeld(cs_avalancheFinalizedBlockIndex);
if (!pindex) {
return false;
}
if (!m_chain.Contains(pindex)) {
LogPrint(BCLog::AVALANCHE,
"The block to mark finalized by avalanche is not on the "
"active chain: %s\n",
pindex->GetBlockHash().ToString());
return false;
}
avalanche.cleanupStakingRewards(pindex->nHeight);
if (IsBlockAvalancheFinalized(pindex)) {
return true;
}
{
LOCK(cs_avalancheFinalizedBlockIndex);
m_avalancheFinalizedBlockIndex = pindex;
}
WITH_LOCK(cs_main, GetMainSignals().BlockFinalized(pindex));
return true;
}
void Chainstate::ClearAvalancheFinalizedBlock() {
LOCK(cs_avalancheFinalizedBlockIndex);
m_avalancheFinalizedBlockIndex = nullptr;
}
bool Chainstate::IsBlockAvalancheFinalized(const CBlockIndex *pindex) const {
LOCK(cs_avalancheFinalizedBlockIndex);
return pindex && m_avalancheFinalizedBlockIndex &&
m_avalancheFinalizedBlockIndex->GetAncestor(pindex->nHeight) ==
pindex;
}
/**
* Mark a block as having its data received and checked (up to
* BLOCK_VALID_TRANSACTIONS).
*/
void ChainstateManager::ReceivedBlockTransactions(const CBlock &block,
CBlockIndex *pindexNew,
const FlatFilePos &pos) {
pindexNew->nTx = block.vtx.size();
// Typically nChainTX will be 0 at this point, but it can be nonzero if this
// is a pruned block which is being downloaded again, or if this is an
// assumeutxo snapshot block which has a hardcoded m_chain_tx_count value
// from the snapshot metadata. If the pindex is not the snapshot block and
// the nChainTx value is not zero, assert that value is actually correct.
auto prev_tx_sum = [](CBlockIndex &block) {
return block.nTx + (block.pprev ? block.pprev->nChainTx : 0);
};
if (!Assume(pindexNew->nChainTx == 0 ||
pindexNew->nChainTx == prev_tx_sum(*pindexNew) ||
pindexNew == GetSnapshotBaseBlock())) {
LogPrintf("Internal bug detected: block %d has unexpected nChainTx %i "
"that should be %i. Please report this issue here: %s\n",
pindexNew->nHeight, pindexNew->nChainTx,
prev_tx_sum(*pindexNew), PACKAGE_BUGREPORT);
pindexNew->nChainTx = 0;
}
pindexNew->nSize = ::GetSerializeSize(block, PROTOCOL_VERSION);
pindexNew->nFile = pos.nFile;
pindexNew->nDataPos = pos.nPos;
pindexNew->nUndoPos = 0;
pindexNew->nStatus = pindexNew->nStatus.withData();
pindexNew->RaiseValidity(BlockValidity::TRANSACTIONS);
m_blockman.m_dirty_blockindex.insert(pindexNew);
if (pindexNew->pprev == nullptr || pindexNew->pprev->HaveNumChainTxs()) {
// If pindexNew is the genesis block or all parents are
// BLOCK_VALID_TRANSACTIONS.
std::deque<CBlockIndex *> queue;
queue.push_back(pindexNew);
// Recursively process any descendant blocks that now may be eligible to
// be connected.
while (!queue.empty()) {
CBlockIndex *pindex = queue.front();
queue.pop_front();
// Before setting nChainTx, assert that it is 0 or already set to
// the correct value. This assert will fail after receiving the
// assumeutxo snapshot block if assumeutxo snapshot metadata has an
// incorrect hardcoded AssumeutxoData::nChainTx value.
if (!Assume(pindex->nChainTx == 0 ||
pindex->nChainTx == prev_tx_sum(*pindex))) {
LogPrintf(
"Internal bug detected: block %d has unexpected nChainTx "
"%i that should be %i. Please report this issue here: %s\n",
pindex->nHeight, pindex->nChainTx, prev_tx_sum(*pindex),
PACKAGE_BUGREPORT);
}
pindex->nChainTx = prev_tx_sum(*pindex);
if (pindex->nSequenceId == 0) {
// We assign a sequence is when transaction are received to
// prevent a miner from being able to broadcast a block but not
// its content. However, a sequence id may have been set
// manually, for instance via PreciousBlock, in which case, we
// don't need to assign one.
pindex->nSequenceId = nBlockSequenceId++;
}
for (Chainstate *c : GetAll()) {
c->TryAddBlockIndexCandidate(pindex);
}
std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
range = m_blockman.m_blocks_unlinked.equal_range(pindex);
while (range.first != range.second) {
std::multimap<CBlockIndex *, CBlockIndex *>::iterator it =
range.first;
queue.push_back(it->second);
range.first++;
m_blockman.m_blocks_unlinked.erase(it);
}
}
} else if (pindexNew->pprev &&
pindexNew->pprev->IsValid(BlockValidity::TREE)) {
m_blockman.m_blocks_unlinked.insert(
std::make_pair(pindexNew->pprev, pindexNew));
}
}
/**
* Return true if the provided block header is valid.
* Only verify PoW if blockValidationOptions is configured to do so.
* This allows validation of headers on which the PoW hasn't been done.
* For example: to validate template handed to mining software.
* Do not call this for any check that depends on the context.
* For context-dependent calls, see ContextualCheckBlockHeader.
*/
static bool CheckBlockHeader(const CBlockHeader &block,
BlockValidationState &state,
const Consensus::Params &params,
BlockValidationOptions validationOptions) {
// Check proof of work matches claimed amount
if (validationOptions.shouldValidatePoW() &&
!CheckProofOfWork(block.GetHash(), block.nBits, params)) {
return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER,
"high-hash", "proof of work failed");
}
return true;
}
bool CheckBlock(const CBlock &block, BlockValidationState &state,
const Consensus::Params &params,
BlockValidationOptions validationOptions) {
// These are checks that are independent of context.
if (block.fChecked) {
return true;
}
// Check that the header is valid (particularly PoW). This is mostly
// redundant with the call in AcceptBlockHeader.
if (!CheckBlockHeader(block, state, params, validationOptions)) {
return false;
}
// Check the merkle root.
if (validationOptions.shouldValidateMerkleRoot()) {
bool mutated;
uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated);
if (block.hashMerkleRoot != hashMerkleRoot2) {
return state.Invalid(BlockValidationResult::BLOCK_MUTATED,
"bad-txnmrklroot", "hashMerkleRoot mismatch");
}
// 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) {
return state.Invalid(BlockValidationResult::BLOCK_MUTATED,
"bad-txns-duplicate", "duplicate transaction");
}
}
// 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.
// First transaction must be coinbase.
if (block.vtx.empty()) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
"bad-cb-missing", "first tx is not coinbase");
}
// Size limits.
auto nMaxBlockSize = validationOptions.getExcessiveBlockSize();
// Bail early if there is no way this block is of reasonable size.
if ((block.vtx.size() * MIN_TRANSACTION_SIZE) > nMaxBlockSize) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
"bad-blk-length", "size limits failed");
}
auto currentBlockSize = ::GetSerializeSize(block, PROTOCOL_VERSION);
if (currentBlockSize > nMaxBlockSize) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
"bad-blk-length", "size limits failed");
}
// And a valid coinbase.
TxValidationState tx_state;
if (!CheckCoinbase(*block.vtx[0], tx_state)) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(),
strprintf("Coinbase check failed (txid %s) %s",
block.vtx[0]->GetId().ToString(),
tx_state.GetDebugMessage()));
}
// Check transactions for regularity, skipping the first. Note that this
// is the first time we check that all after the first are !IsCoinBase.
for (size_t i = 1; i < block.vtx.size(); i++) {
auto *tx = block.vtx[i].get();
if (!CheckRegularTransaction(*tx, tx_state)) {
return state.Invalid(
BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(),
strprintf("Transaction check failed (txid %s) %s",
tx->GetId().ToString(), tx_state.GetDebugMessage()));
}
}
if (validationOptions.shouldValidatePoW() &&
validationOptions.shouldValidateMerkleRoot()) {
block.fChecked = true;
}
return true;
}
bool HasValidProofOfWork(const std::vector<CBlockHeader> &headers,
const Consensus::Params &consensusParams) {
return std::all_of(headers.cbegin(), headers.cend(),
[&](const auto &header) {
return CheckProofOfWork(
header.GetHash(), header.nBits, consensusParams);
});
}
arith_uint256 CalculateHeadersWork(const std::vector<CBlockHeader> &headers) {
arith_uint256 total_work{0};
for (const CBlockHeader &header : headers) {
CBlockIndex dummy(header);
total_work += GetBlockProof(dummy);
}
return total_work;
}
/**
* Context-dependent validity checks.
* By "context", we mean only the previous block headers, but not the UTXO
* set; UTXO-related validity checks are done in ConnectBlock().
* NOTE: This function is not currently invoked by ConnectBlock(), so we
* should consider upgrade issues if we change which consensus rules are
* enforced in this function (eg by adding a new consensus rule). See comment
* in ConnectBlock().
* Note that -reindex-chainstate skips the validation that happens here!
*/
static bool ContextualCheckBlockHeader(
const CBlockHeader &block, BlockValidationState &state,
BlockManager &blockman, ChainstateManager &chainman,
const CBlockIndex *pindexPrev, NodeClock::time_point now,
const std::optional<CCheckpointData> &test_checkpoints = std::nullopt)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
assert(pindexPrev != nullptr);
const int nHeight = pindexPrev->nHeight + 1;
const CChainParams &params = chainman.GetParams();
// Check proof of work
if (block.nBits != GetNextWorkRequired(pindexPrev, &block, params)) {
LogPrintf("bad bits after height: %d\n", pindexPrev->nHeight);
return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER,
"bad-diffbits", "incorrect proof of work");
}
// Check against checkpoints
if (chainman.m_options.checkpoints_enabled) {
const CCheckpointData &checkpoints =
test_checkpoints ? test_checkpoints.value() : params.Checkpoints();
// Check that the block chain matches the known block chain up to a
// checkpoint.
if (!Checkpoints::CheckBlock(checkpoints, nHeight, block.GetHash())) {
LogPrint(BCLog::VALIDATION,
"ERROR: %s: rejected by checkpoint lock-in at %d\n",
__func__, nHeight);
return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT,
"checkpoint mismatch");
}
// Don't accept any forks from the main chain prior to last checkpoint.
// GetLastCheckpoint finds the last checkpoint in MapCheckpoints that's
// in our BlockIndex().
const CBlockIndex *pcheckpoint =
blockman.GetLastCheckpoint(checkpoints);
if (pcheckpoint && nHeight < pcheckpoint->nHeight) {
LogPrint(BCLog::VALIDATION,
"ERROR: %s: forked chain older than last checkpoint "
"(height %d)\n",
__func__, nHeight);
return state.Invalid(BlockValidationResult::BLOCK_CHECKPOINT,
"bad-fork-prior-to-checkpoint");
}
}
// Check timestamp against prev
if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) {
return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER,
"time-too-old", "block's timestamp is too early");
}
// Check timestamp
if (block.Time() > now + std::chrono::seconds{MAX_FUTURE_BLOCK_TIME}) {
return state.Invalid(BlockValidationResult::BLOCK_TIME_FUTURE,
"time-too-new",
"block timestamp too far in the future");
}
// Reject blocks with outdated version
if ((block.nVersion < 2 &&
DeploymentActiveAfter(pindexPrev, chainman,
Consensus::DEPLOYMENT_HEIGHTINCB)) ||
(block.nVersion < 3 &&
DeploymentActiveAfter(pindexPrev, chainman,
Consensus::DEPLOYMENT_DERSIG)) ||
(block.nVersion < 4 &&
DeploymentActiveAfter(pindexPrev, chainman,
Consensus::DEPLOYMENT_CLTV))) {
return state.Invalid(
BlockValidationResult::BLOCK_INVALID_HEADER,
strprintf("bad-version(0x%08x)", block.nVersion),
strprintf("rejected nVersion=0x%08x block", block.nVersion));
}
return true;
}
bool ContextualCheckTransactionForCurrentBlock(
const CBlockIndex &active_chain_tip, const Consensus::Params &params,
const CTransaction &tx, TxValidationState &state) {
AssertLockHeld(cs_main);
// ContextualCheckTransactionForCurrentBlock() uses
// active_chain_tip.Height()+1 to evaluate nLockTime because when
// IsFinalTx() is called within AcceptBlock(), the height of the
// block *being* evaluated is what is used. Thus if we want to know if a
// transaction can be part of the *next* block, we need to call
// ContextualCheckTransaction() with one more than
// active_chain_tip.Height().
const int nBlockHeight = active_chain_tip.nHeight + 1;
// BIP113 will require that time-locked transactions have nLockTime set to
// less than the median time of the previous block they're contained in.
// When the next block is created its previous block will be the current
// chain tip, so we use that to calculate the median time passed to
// ContextualCheckTransaction().
// This time can also be used for consensus upgrades.
const int64_t nMedianTimePast{active_chain_tip.GetMedianTimePast()};
return ContextualCheckTransaction(params, tx, state, nBlockHeight,
nMedianTimePast);
}
/**
* NOTE: This function is not currently invoked by ConnectBlock(), so we
* should consider upgrade issues if we change which consensus rules are
* enforced in this function (eg by adding a new consensus rule). See comment
* in ConnectBlock().
* Note that -reindex-chainstate skips the validation that happens here!
*/
static bool ContextualCheckBlock(const CBlock &block,
BlockValidationState &state,
const ChainstateManager &chainman,
const CBlockIndex *pindexPrev) {
const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1;
// Enforce BIP113 (Median Time Past).
bool enforce_locktime_median_time_past{false};
if (DeploymentActiveAfter(pindexPrev, chainman,
Consensus::DEPLOYMENT_CSV)) {
assert(pindexPrev != nullptr);
enforce_locktime_median_time_past = true;
}
const int64_t nMedianTimePast =
pindexPrev == nullptr ? 0 : pindexPrev->GetMedianTimePast();
const int64_t nLockTimeCutoff{enforce_locktime_median_time_past
? nMedianTimePast
: block.GetBlockTime()};
const Consensus::Params params = chainman.GetConsensus();
const bool fIsMagneticAnomalyEnabled =
IsMagneticAnomalyEnabled(params, pindexPrev);
// Check transactions:
// - canonical ordering
// - ensure they are finalized
// - check they have the minimum size
const CTransaction *prevTx = nullptr;
for (const auto &ptx : block.vtx) {
const CTransaction &tx = *ptx;
if (fIsMagneticAnomalyEnabled) {
if (prevTx && (tx.GetId() <= prevTx->GetId())) {
if (tx.GetId() == prevTx->GetId()) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
"tx-duplicate",
strprintf("Duplicated transaction %s",
tx.GetId().ToString()));
}
return state.Invalid(
BlockValidationResult::BLOCK_CONSENSUS, "tx-ordering",
strprintf("Transaction order is invalid (%s < %s)",
tx.GetId().ToString(),
prevTx->GetId().ToString()));
}
if (prevTx || !tx.IsCoinBase()) {
prevTx = &tx;
}
}
TxValidationState tx_state;
if (!ContextualCheckTransaction(params, tx, tx_state, nHeight,
nLockTimeCutoff)) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(),
tx_state.GetDebugMessage());
}
}
// Enforce rule that the coinbase starts with serialized block height
if (DeploymentActiveAfter(pindexPrev, chainman,
Consensus::DEPLOYMENT_HEIGHTINCB)) {
CScript expect = CScript() << nHeight;
if (block.vtx[0]->vin[0].scriptSig.size() < expect.size() ||
!std::equal(expect.begin(), expect.end(),
block.vtx[0]->vin[0].scriptSig.begin())) {
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
"bad-cb-height",
"block height mismatch in coinbase");
}
}
return true;
}
/**
* If the provided block header is valid, add it to the block index.
*
* Returns true if the block is successfully added to the block index.
*/
bool ChainstateManager::AcceptBlockHeader(
const CBlockHeader &block, BlockValidationState &state,
CBlockIndex **ppindex, bool min_pow_checked,
const std::optional<CCheckpointData> &test_checkpoints) {
AssertLockHeld(cs_main);
const Config &config = this->GetConfig();
const CChainParams &chainparams = config.GetChainParams();
// Check for duplicate
BlockHash hash = block.GetHash();
BlockMap::iterator miSelf{m_blockman.m_block_index.find(hash)};
if (hash != chainparams.GetConsensus().hashGenesisBlock) {
if (miSelf != m_blockman.m_block_index.end()) {
// Block header is already known.
CBlockIndex *pindex = &(miSelf->second);
if (ppindex) {
*ppindex = pindex;
}
if (pindex->nStatus.isInvalid()) {
LogPrint(BCLog::VALIDATION, "%s: block %s is marked invalid\n",
__func__, hash.ToString());
return state.Invalid(
BlockValidationResult::BLOCK_CACHED_INVALID, "duplicate");
}
return true;
}
if (!CheckBlockHeader(block, state, chainparams.GetConsensus(),
BlockValidationOptions(config))) {
LogPrint(BCLog::VALIDATION,
"%s: Consensus::CheckBlockHeader: %s, %s\n", __func__,
hash.ToString(), state.ToString());
return false;
}
// Get prev block index
BlockMap::iterator mi{
m_blockman.m_block_index.find(block.hashPrevBlock)};
if (mi == m_blockman.m_block_index.end()) {
LogPrint(BCLog::VALIDATION,
"header %s has prev block not found: %s\n",
hash.ToString(), block.hashPrevBlock.ToString());
return state.Invalid(BlockValidationResult::BLOCK_MISSING_PREV,
"prev-blk-not-found");
}
CBlockIndex *pindexPrev = &((*mi).second);
assert(pindexPrev);
if (pindexPrev->nStatus.isInvalid()) {
LogPrint(BCLog::VALIDATION,
"header %s has prev block invalid: %s\n", hash.ToString(),
block.hashPrevBlock.ToString());
return state.Invalid(BlockValidationResult::BLOCK_INVALID_PREV,
"bad-prevblk");
}
if (!ContextualCheckBlockHeader(
block, state, m_blockman, *this, pindexPrev,
m_options.adjusted_time_callback(), test_checkpoints)) {
LogPrint(BCLog::VALIDATION,
"%s: Consensus::ContextualCheckBlockHeader: %s, %s\n",
__func__, hash.ToString(), state.ToString());
return false;
}
/* Determine if this block descends from any block which has been found
* invalid (m_failed_blocks), then mark pindexPrev and any blocks
* between them as failed. For example:
*
* D3
* /
* B2 - C2
* / \
* A D2 - E2 - F2
* \
* B1 - C1 - D1 - E1
*
* In the case that we attempted to reorg from E1 to F2, only to find
* C2 to be invalid, we would mark D2, E2, and F2 as BLOCK_FAILED_CHILD
* but NOT D3 (it was not in any of our candidate sets at the time).
*
* In any case D3 will also be marked as BLOCK_FAILED_CHILD at restart
* in LoadBlockIndex.
*/
if (!pindexPrev->IsValid(BlockValidity::SCRIPTS)) {
// The above does not mean "invalid": it checks if the previous
// block hasn't been validated up to BlockValidity::SCRIPTS. This is
// a performance optimization, in the common case of adding a new
// block to the tip, we don't need to iterate over the failed blocks
// list.
for (const CBlockIndex *failedit : m_failed_blocks) {
if (pindexPrev->GetAncestor(failedit->nHeight) == failedit) {
assert(failedit->nStatus.hasFailed());
CBlockIndex *invalid_walk = pindexPrev;
while (invalid_walk != failedit) {
invalid_walk->nStatus =
invalid_walk->nStatus.withFailedParent();
m_blockman.m_dirty_blockindex.insert(invalid_walk);
invalid_walk = invalid_walk->pprev;
}
LogPrint(BCLog::VALIDATION,
"header %s has prev block invalid: %s\n",
hash.ToString(), block.hashPrevBlock.ToString());
return state.Invalid(
BlockValidationResult::BLOCK_INVALID_PREV,
"bad-prevblk");
}
}
}
}
if (!min_pow_checked) {
LogPrint(BCLog::VALIDATION,
"%s: not adding new block header %s, missing anti-dos "
"proof-of-work validation\n",
__func__, hash.ToString());
return state.Invalid(BlockValidationResult::BLOCK_HEADER_LOW_WORK,
"too-little-chainwork");
}
CBlockIndex *pindex{m_blockman.AddToBlockIndex(block, m_best_header)};
if (ppindex) {
*ppindex = pindex;
}
// Since this is the earliest point at which we have determined that a
// header is both new and valid, log here.
//
// These messages are valuable for detecting potential selfish mining
// behavior; if multiple displacing headers are seen near simultaneously
// across many nodes in the network, this might be an indication of selfish
// mining. Having this log by default when not in IBD ensures broad
// availability of this data in case investigation is merited.
const auto msg = strprintf("Saw new header hash=%s height=%d",
hash.ToString(), pindex->nHeight);
if (IsInitialBlockDownload()) {
LogPrintLevel(BCLog::VALIDATION, BCLog::Level::Debug, "%s\n", msg);
} else {
LogPrintf("%s\n", msg);
}
return true;
}
// Exposed wrapper for AcceptBlockHeader
bool ChainstateManager::ProcessNewBlockHeaders(
const std::vector<CBlockHeader> &headers, bool min_pow_checked,
BlockValidationState &state, const CBlockIndex **ppindex,
const std::optional<CCheckpointData> &test_checkpoints) {
AssertLockNotHeld(cs_main);
{
LOCK(cs_main);
for (const CBlockHeader &header : headers) {
// Use a temp pindex instead of ppindex to avoid a const_cast
CBlockIndex *pindex = nullptr;
bool accepted = AcceptBlockHeader(
header, state, &pindex, min_pow_checked, test_checkpoints);
CheckBlockIndex();
if (!accepted) {
return false;
}
if (ppindex) {
*ppindex = pindex;
}
}
}
if (NotifyHeaderTip(*this)) {
if (IsInitialBlockDownload() && ppindex && *ppindex) {
const CBlockIndex &last_accepted{**ppindex};
const int64_t blocks_left{
(GetTime() - last_accepted.GetBlockTime()) /
this->GetConsensus().nPowTargetSpacing};
const double progress{100.0 * last_accepted.nHeight /
(last_accepted.nHeight + blocks_left)};
LogPrintf("Synchronizing blockheaders, height: %d (~%.2f%%)\n",
last_accepted.nHeight, progress);
}
}
return true;
}
void ChainstateManager::ReportHeadersPresync(const arith_uint256 &work,
int64_t height,
int64_t timestamp) {
AssertLockNotHeld(cs_main);
{
LOCK(cs_main);
// Don't report headers presync progress if we already have a
// post-minchainwork header chain.
// This means we lose reporting for potentially legimate, but unlikely,
// deep reorgs, but prevent attackers that spam low-work headers from
// filling our logs.
if (m_best_header->nChainWork >=
UintToArith256(GetConsensus().nMinimumChainWork)) {
return;
}
// Rate limit headers presync updates to 4 per second, as these are not
// subject to DoS protection.
auto now = Now<SteadyMilliseconds>();
if (now < m_last_presync_update + 250ms) {
return;
}
m_last_presync_update = now;
}
bool initial_download = IsInitialBlockDownload();
GetNotifications().headerTip(GetSynchronizationState(initial_download),
height, timestamp, /*presync=*/true);
if (initial_download) {
const int64_t blocks_left{(GetTime() - timestamp) /
GetConsensus().nPowTargetSpacing};
const double progress{100.0 * height / (height + blocks_left)};
LogPrintf("Pre-synchronizing blockheaders, height: %d (~%.2f%%)\n",
height, progress);
}
}
bool ChainstateManager::AcceptBlock(const std::shared_ptr<const CBlock> &pblock,
BlockValidationState &state,
bool fRequested, const FlatFilePos *dbp,
bool *fNewBlock, bool min_pow_checked) {
AssertLockHeld(cs_main);
const CBlock &block = *pblock;
if (fNewBlock) {
*fNewBlock = false;
}
CBlockIndex *pindex = nullptr;
bool accepted_header{
AcceptBlockHeader(block, state, &pindex, min_pow_checked)};
CheckBlockIndex();
if (!accepted_header) {
return false;
}
// Check all requested blocks that we do not already have for validity and
// save them to disk. Skip processing of unrequested blocks as an anti-DoS
// measure, unless the blocks have more work than the active chain tip, and
// aren't too far ahead of it, so are likely to be attached soon.
bool fAlreadyHave = pindex->nStatus.hasData();
// TODO: deal better with return value and error conditions for duplicate
// and unrequested blocks.
if (fAlreadyHave) {
return true;
}
// Compare block header timestamps and received times of the block and the
// chaintip. If they have the same chain height, use these diffs as a
// tie-breaker, attempting to pick the more honestly-mined block.
int64_t newBlockTimeDiff = std::llabs(pindex->GetReceivedTimeDiff());
int64_t chainTipTimeDiff =
ActiveTip() ? std::llabs(ActiveTip()->GetReceivedTimeDiff()) : 0;
bool isSameHeight =
ActiveTip() && (pindex->nChainWork == ActiveTip()->nChainWork);
if (isSameHeight) {
LogPrintf("Chain tip timestamp-to-received-time difference: hash=%s, "
"diff=%d\n",
ActiveTip()->GetBlockHash().ToString(), chainTipTimeDiff);
LogPrintf("New block timestamp-to-received-time difference: hash=%s, "
"diff=%d\n",
pindex->GetBlockHash().ToString(), newBlockTimeDiff);
}
bool fHasMoreOrSameWork =
(ActiveTip() ? pindex->nChainWork >= ActiveTip()->nChainWork : true);
// Blocks that are too out-of-order needlessly limit the effectiveness of
// pruning, because pruning will not delete block files that contain any
// blocks which are too close in height to the tip. Apply this test
// regardless of whether pruning is enabled; it should generally be safe to
// not process unrequested blocks.
bool fTooFarAhead{pindex->nHeight >
ActiveHeight() + int(MIN_BLOCKS_TO_KEEP)};
// TODO: Decouple this function from the block download logic by removing
// fRequested
// This requires some new chain data structure to efficiently look up if a
// block is in a chain leading to a candidate for best tip, despite not
// being such a candidate itself.
// Note that this would break the getblockfrompeer RPC
// If we didn't ask for it:
if (!fRequested) {
// This is a previously-processed block that was pruned.
if (pindex->nTx != 0) {
return true;
}
// Don't process less-work chains.
if (!fHasMoreOrSameWork) {
return true;
}
// Block height is too high.
if (fTooFarAhead) {
return true;
}
// Protect against DoS attacks from low-work chains.
// If our tip is behind, a peer could try to send us
// low-work blocks on a fake chain that we would never
// request; don't process these.
if (pindex->nChainWork < MinimumChainWork()) {
return true;
}
}
if (!CheckBlock(block, state,
m_options.config.GetChainParams().GetConsensus(),
BlockValidationOptions(m_options.config)) ||
!ContextualCheckBlock(block, state, *this, pindex->pprev)) {
if (state.IsInvalid() &&
state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
pindex->nStatus = pindex->nStatus.withFailed();
m_blockman.m_dirty_blockindex.insert(pindex);
}
return error("%s: %s (block %s)", __func__, state.ToString(),
block.GetHash().ToString());
}
// If connecting the new block would require rewinding more than one block
// from the active chain (i.e., a "deep reorg"), then mark the new block as
// parked. If it has enough work then it will be automatically unparked
// later, during FindMostWorkChain. We mark the block as parked at the very
// last minute so we can make sure everything is ready to be reorged if
// needed.
if (gArgs.GetBoolArg("-parkdeepreorg", true)) {
// Blocks that are below the snapshot height can't cause reorgs, as the
// active tip is at least thousands of blocks higher. Don't park them,
// they will most likely connect on the tip of the background chain.
std::optional<int> snapshot_base_height = GetSnapshotBaseHeight();
const bool is_background_block =
snapshot_base_height && BackgroundSyncInProgress() &&
pindex->nHeight <= snapshot_base_height;
const CBlockIndex *pindexFork = ActiveChain().FindFork(pindex);
if (!is_background_block && pindexFork &&
pindexFork->nHeight + 1 < ActiveHeight()) {
LogPrintf("Park block %s as it would cause a deep reorg.\n",
pindex->GetBlockHash().ToString());
pindex->nStatus = pindex->nStatus.withParked();
m_blockman.m_dirty_blockindex.insert(pindex);
}
}
// Header is valid/has work and the merkle tree is good.
// Relay now, but if it does not build on our best tip, let the
// SendMessages loop relay it.
if (!IsInitialBlockDownload() && ActiveTip() == pindex->pprev) {
GetMainSignals().NewPoWValidBlock(pindex, pblock);
}
// Write block to history file
if (fNewBlock) {
*fNewBlock = true;
}
try {
FlatFilePos blockPos{
m_blockman.SaveBlockToDisk(block, pindex->nHeight, dbp)};
if (blockPos.IsNull()) {
state.Error(strprintf(
"%s: Failed to find position to write new block to disk",
__func__));
return false;
}
ReceivedBlockTransactions(block, pindex, blockPos);
} catch (const std::runtime_error &e) {
return AbortNode(state, std::string("System error: ") + e.what());
}
// TODO: FlushStateToDisk() handles flushing of both block and chainstate
// data, so we should move this to ChainstateManager so that we can be more
// intelligent about how we flush.
// For now, since FlushStateMode::NONE is used, all that can happen is that
// the block files may be pruned, so we can just call this on one
// chainstate (particularly if we haven't implemented pruning with
// background validation yet).
ActiveChainstate().FlushStateToDisk(state, FlushStateMode::NONE);
CheckBlockIndex();
return true;
}
bool ChainstateManager::ProcessNewBlock(
const std::shared_ptr<const CBlock> &block, bool force_processing,
bool min_pow_checked, bool *new_block,
avalanche::Processor *const avalanche) {
AssertLockNotHeld(cs_main);
{
if (new_block) {
*new_block = false;
}
BlockValidationState state;
// CheckBlock() does not support multi-threaded block validation
// because CBlock::fChecked can cause data race.
// Therefore, the following critical section must include the
// CheckBlock() call as well.
LOCK(cs_main);
// Skipping AcceptBlock() for CheckBlock() failures means that we will
// never mark a block as invalid if CheckBlock() fails. This is
// protective against consensus failure if there are any unknown form
// s of block malleability that cause CheckBlock() to fail; see e.g.
// CVE-2012-2459 and
// https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-February/016697.html.
// Because CheckBlock() is not very expensive, the anti-DoS benefits of
// caching failure (of a definitely-invalid block) are not substantial.
bool ret = CheckBlock(*block, state, this->GetConsensus(),
BlockValidationOptions(this->GetConfig()));
if (ret) {
// Store to disk
ret = AcceptBlock(block, state, force_processing, nullptr,
new_block, min_pow_checked);
}
if (!ret) {
GetMainSignals().BlockChecked(*block, state);
return error("%s: AcceptBlock FAILED (%s)", __func__,
state.ToString());
}
}
NotifyHeaderTip(*this);
// Only used to report errors, not invalidity - ignore it
BlockValidationState state;
if (!ActiveChainstate().ActivateBestChain(state, block, avalanche)) {
return error("%s: ActivateBestChain failed (%s)", __func__,
state.ToString());
}
Chainstate *bg_chain{WITH_LOCK(cs_main, return BackgroundSyncInProgress()
? m_ibd_chainstate.get()
: nullptr)};
BlockValidationState bg_state;
if (bg_chain && !bg_chain->ActivateBestChain(bg_state, block)) {
return error("%s: [background] ActivateBestChain failed (%s)", __func__,
bg_state.ToString());
}
return true;
}
MempoolAcceptResult
ChainstateManager::ProcessTransaction(const CTransactionRef &tx,
bool test_accept) {
AssertLockHeld(cs_main);
Chainstate &active_chainstate = ActiveChainstate();
if (!active_chainstate.GetMempool()) {
TxValidationState state;
state.Invalid(TxValidationResult::TX_NO_MEMPOOL, "no-mempool");
return MempoolAcceptResult::Failure(state);
}
auto result = AcceptToMemoryPool(active_chainstate, tx, GetTime(),
/*bypass_limits=*/false, test_accept);
active_chainstate.GetMempool()->check(
active_chainstate.CoinsTip(), active_chainstate.m_chain.Height() + 1);
return result;
}
bool TestBlockValidity(
BlockValidationState &state, const CChainParams &params,
Chainstate &chainstate, const CBlock &block, CBlockIndex *pindexPrev,
const std::function<NodeClock::time_point()> &adjusted_time_callback,
BlockValidationOptions validationOptions) {
AssertLockHeld(cs_main);
assert(pindexPrev && pindexPrev == chainstate.m_chain.Tip());
CCoinsViewCache viewNew(&chainstate.CoinsTip());
BlockHash block_hash(block.GetHash());
CBlockIndex indexDummy(block);
indexDummy.pprev = pindexPrev;
indexDummy.nHeight = pindexPrev->nHeight + 1;
indexDummy.phashBlock = &block_hash;
// NOTE: CheckBlockHeader is called by CheckBlock
if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman,
chainstate.m_chainman, pindexPrev,
adjusted_time_callback())) {
return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__,
state.ToString());
}
if (!CheckBlock(block, state, params.GetConsensus(), validationOptions)) {
return error("%s: Consensus::CheckBlock: %s", __func__,
state.ToString());
}
if (!ContextualCheckBlock(block, state, chainstate.m_chainman,
pindexPrev)) {
return error("%s: Consensus::ContextualCheckBlock: %s", __func__,
state.ToString());
}
if (!chainstate.ConnectBlock(block, state, &indexDummy, viewNew,
validationOptions, nullptr, true)) {
return false;
}
assert(state.IsValid());
return true;
}
/* This function is called from the RPC code for pruneblockchain */
void PruneBlockFilesManual(Chainstate &active_chainstate,
int nManualPruneHeight) {
BlockValidationState state;
if (active_chainstate.FlushStateToDisk(state, FlushStateMode::NONE,
nManualPruneHeight)) {
LogPrintf("%s: failed to flush state (%s)\n", __func__,
state.ToString());
}
}
void Chainstate::LoadMempool(const fs::path &load_path,
FopenFn mockable_fopen_function) {
if (!m_mempool) {
return;
}
::LoadMempool(*m_mempool, load_path, *this, mockable_fopen_function);
m_mempool->SetLoadTried(!ShutdownRequested());
}
bool Chainstate::LoadChainTip() {
AssertLockHeld(cs_main);
const CCoinsViewCache &coins_cache = CoinsTip();
// Never called when the coins view is empty
assert(!coins_cache.GetBestBlock().IsNull());
const CBlockIndex *tip = m_chain.Tip();
if (tip && tip->GetBlockHash() == coins_cache.GetBestBlock()) {
return true;
}
// Load pointer to end of best chain
CBlockIndex *pindex =
m_blockman.LookupBlockIndex(coins_cache.GetBestBlock());
if (!pindex) {
return false;
}
m_chain.SetTip(*pindex);
PruneBlockIndexCandidates();
tip = m_chain.Tip();
LogPrintf(
"Loaded best chain: hashBestChain=%s height=%d date=%s progress=%f\n",
tip->GetBlockHash().ToString(), m_chain.Height(),
FormatISO8601DateTime(tip->GetBlockTime()),
GuessVerificationProgress(m_chainman.GetParams().TxData(), tip));
return true;
}
CVerifyDB::CVerifyDB(Notifications &notifications)
: m_notifications{notifications} {
m_notifications.progress(_("Verifying blocks…"), 0, false);
}
CVerifyDB::~CVerifyDB() {
m_notifications.progress(bilingual_str{}, 100, false);
}
VerifyDBResult CVerifyDB::VerifyDB(Chainstate &chainstate,
CCoinsView &coinsview, int nCheckLevel,
int nCheckDepth) {
AssertLockHeld(cs_main);
const Config &config = chainstate.m_chainman.GetConfig();
const CChainParams &params = config.GetChainParams();
const Consensus::Params &consensusParams = params.GetConsensus();
if (chainstate.m_chain.Tip() == nullptr ||
chainstate.m_chain.Tip()->pprev == nullptr) {
return VerifyDBResult::SUCCESS;
}
// Verify blocks in the best chain
if (nCheckDepth <= 0 || nCheckDepth > chainstate.m_chain.Height()) {
nCheckDepth = chainstate.m_chain.Height();
}
nCheckLevel = std::max(0, std::min(4, nCheckLevel));
LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth,
nCheckLevel);
CCoinsViewCache coins(&coinsview);
CBlockIndex *pindex;
CBlockIndex *pindexFailure = nullptr;
int nGoodTransactions = 0;
BlockValidationState state;
int reportDone = 0;
bool skipped_no_block_data{false};
bool skipped_l3_checks{false};
LogPrintf("Verification progress: 0%%\n");
const bool is_snapshot_cs{chainstate.m_from_snapshot_blockhash};
for (pindex = chainstate.m_chain.Tip(); pindex && pindex->pprev;
pindex = pindex->pprev) {
const int percentageDone = std::max(
1, std::min(99, (int)(((double)(chainstate.m_chain.Height() -
pindex->nHeight)) /
(double)nCheckDepth *
(nCheckLevel >= 4 ? 50 : 100))));
if (reportDone < percentageDone / 10) {
// report every 10% step
LogPrintf("Verification progress: %d%%\n", percentageDone);
reportDone = percentageDone / 10;
}
m_notifications.progress(_("Verifying blocks…"), percentageDone, false);
if (pindex->nHeight <= chainstate.m_chain.Height() - nCheckDepth) {
break;
}
if ((chainstate.m_blockman.IsPruneMode() || is_snapshot_cs) &&
!pindex->nStatus.hasData()) {
// If pruning or running under an assumeutxo snapshot, only go
// back as far as we have data.
LogPrintf("VerifyDB(): block verification stopping at height %d "
"(no data). This could be due to pruning or use of an "
"assumeutxo snapshot.\n",
pindex->nHeight);
skipped_no_block_data = true;
break;
}
CBlock block;
// check level 0: read from disk
if (!chainstate.m_blockman.ReadBlockFromDisk(block, *pindex)) {
LogPrintf(
"Verification error: ReadBlockFromDisk failed at %d, hash=%s\n",
pindex->nHeight, pindex->GetBlockHash().ToString());
return VerifyDBResult::CORRUPTED_BLOCK_DB;
}
// check level 1: verify block validity
if (nCheckLevel >= 1 && !CheckBlock(block, state, consensusParams,
BlockValidationOptions(config))) {
LogPrintf(
"Verification error: found bad block at %d, hash=%s (%s)\n",
pindex->nHeight, pindex->GetBlockHash().ToString(),
state.ToString());
return VerifyDBResult::CORRUPTED_BLOCK_DB;
}
// check level 2: verify undo validity
if (nCheckLevel >= 2 && pindex) {
CBlockUndo undo;
if (!pindex->GetUndoPos().IsNull()) {
if (!chainstate.m_blockman.UndoReadFromDisk(undo, *pindex)) {
LogPrintf("Verification error: found bad undo data at %d, "
"hash=%s\n",
pindex->nHeight,
pindex->GetBlockHash().ToString());
return VerifyDBResult::CORRUPTED_BLOCK_DB;
}
}
}
// check level 3: check for inconsistencies during memory-only
// disconnect of tip blocks
size_t curr_coins_usage = coins.DynamicMemoryUsage() +
chainstate.CoinsTip().DynamicMemoryUsage();
if (nCheckLevel >= 3) {
if (curr_coins_usage <= chainstate.m_coinstip_cache_size_bytes) {
assert(coins.GetBestBlock() == pindex->GetBlockHash());
DisconnectResult res =
chainstate.DisconnectBlock(block, pindex, coins);
if (res == DisconnectResult::FAILED) {
LogPrintf("Verification error: irrecoverable inconsistency "
"in block data at %d, hash=%s\n",
pindex->nHeight,
pindex->GetBlockHash().ToString());
return VerifyDBResult::CORRUPTED_BLOCK_DB;
}
if (res == DisconnectResult::UNCLEAN) {
nGoodTransactions = 0;
pindexFailure = pindex;
} else {
nGoodTransactions += block.vtx.size();
}
} else {
skipped_l3_checks = true;
}
}
if (ShutdownRequested()) {
return VerifyDBResult::INTERRUPTED;
}
}
if (pindexFailure) {
LogPrintf("Verification error: coin database inconsistencies found "
"(last %i blocks, %i good transactions before that)\n",
chainstate.m_chain.Height() - pindexFailure->nHeight + 1,
nGoodTransactions);
return VerifyDBResult::CORRUPTED_BLOCK_DB;
}
if (skipped_l3_checks) {
LogPrintf("Skipped verification of level >=3 (insufficient database "
"cache size). Consider increasing -dbcache.\n");
}
// store block count as we move pindex at check level >= 4
int block_count = chainstate.m_chain.Height() - pindex->nHeight;
// check level 4: try reconnecting blocks
if (nCheckLevel >= 4 && !skipped_l3_checks) {
while (pindex != chainstate.m_chain.Tip()) {
const int percentageDone = std::max(
1, std::min(99, 100 - int(double(chainstate.m_chain.Height() -
pindex->nHeight) /
double(nCheckDepth) * 50)));
if (reportDone < percentageDone / 10) {
// report every 10% step
LogPrintf("Verification progress: %d%%\n", percentageDone);
reportDone = percentageDone / 10;
}
m_notifications.progress(_("Verifying blocks…"), percentageDone,
false);
pindex = chainstate.m_chain.Next(pindex);
CBlock block;
if (!chainstate.m_blockman.ReadBlockFromDisk(block, *pindex)) {
LogPrintf("Verification error: ReadBlockFromDisk failed at %d, "
"hash=%s\n",
pindex->nHeight, pindex->GetBlockHash().ToString());
return VerifyDBResult::CORRUPTED_BLOCK_DB;
}
if (!chainstate.ConnectBlock(block, state, pindex, coins,
BlockValidationOptions(config))) {
LogPrintf("Verification error: found unconnectable block at "
"%d, hash=%s (%s)\n",
pindex->nHeight, pindex->GetBlockHash().ToString(),
state.ToString());
return VerifyDBResult::CORRUPTED_BLOCK_DB;
}
if (ShutdownRequested()) {
return VerifyDBResult::INTERRUPTED;
}
}
}
LogPrintf("Verification: No coin database inconsistencies in last %i "
"blocks (%i transactions)\n",
block_count, nGoodTransactions);
if (skipped_l3_checks) {
return VerifyDBResult::SKIPPED_L3_CHECKS;
}
if (skipped_no_block_data) {
return VerifyDBResult::SKIPPED_MISSING_BLOCKS;
}
return VerifyDBResult::SUCCESS;
}
/**
* Apply the effects of a block on the utxo cache, ignoring that it may already
* have been applied.
*/
bool Chainstate::RollforwardBlock(const CBlockIndex *pindex,
CCoinsViewCache &view) {
AssertLockHeld(cs_main);
// TODO: merge with ConnectBlock
CBlock block;
if (!m_blockman.ReadBlockFromDisk(block, *pindex)) {
return error("ReplayBlock(): ReadBlockFromDisk failed at %d, hash=%s",
pindex->nHeight, pindex->GetBlockHash().ToString());
}
for (const CTransactionRef &tx : block.vtx) {
// Pass check = true as every addition may be an overwrite.
AddCoins(view, *tx, pindex->nHeight, true);
}
for (const CTransactionRef &tx : block.vtx) {
if (tx->IsCoinBase()) {
continue;
}
for (const CTxIn &txin : tx->vin) {
view.SpendCoin(txin.prevout);
}
}
return true;
}
bool Chainstate::ReplayBlocks() {
LOCK(cs_main);
CCoinsView &db = this->CoinsDB();
CCoinsViewCache cache(&db);
std::vector<BlockHash> hashHeads = db.GetHeadBlocks();
if (hashHeads.empty()) {
// We're already in a consistent state.
return true;
}
if (hashHeads.size() != 2) {
return error("ReplayBlocks(): unknown inconsistent state");
}
m_chainman.GetNotifications().progress(_("Replaying blocks…"), 0, false);
LogPrintf("Replaying blocks\n");
// Old tip during the interrupted flush.
const CBlockIndex *pindexOld = nullptr;
// New tip during the interrupted flush.
const CBlockIndex *pindexNew;
// Latest block common to both the old and the new tip.
const CBlockIndex *pindexFork = nullptr;
if (m_blockman.m_block_index.count(hashHeads[0]) == 0) {
return error(
"ReplayBlocks(): reorganization to unknown block requested");
}
pindexNew = &(m_blockman.m_block_index[hashHeads[0]]);
if (!hashHeads[1].IsNull()) {
// The old tip is allowed to be 0, indicating it's the first flush.
if (m_blockman.m_block_index.count(hashHeads[1]) == 0) {
return error(
"ReplayBlocks(): reorganization from unknown block requested");
}
pindexOld = &(m_blockman.m_block_index[hashHeads[1]]);
pindexFork = LastCommonAncestor(pindexOld, pindexNew);
assert(pindexFork != nullptr);
}
// Rollback along the old branch.
while (pindexOld != pindexFork) {
if (pindexOld->nHeight > 0) {
// Never disconnect the genesis block.
CBlock block;
if (!m_blockman.ReadBlockFromDisk(block, *pindexOld)) {
return error("RollbackBlock(): ReadBlockFromDisk() failed at "
"%d, hash=%s",
pindexOld->nHeight,
pindexOld->GetBlockHash().ToString());
}
LogPrintf("Rolling back %s (%i)\n",
pindexOld->GetBlockHash().ToString(), pindexOld->nHeight);
DisconnectResult res = DisconnectBlock(block, pindexOld, cache);
if (res == DisconnectResult::FAILED) {
return error(
"RollbackBlock(): DisconnectBlock failed at %d, hash=%s",
pindexOld->nHeight, pindexOld->GetBlockHash().ToString());
}
// If DisconnectResult::UNCLEAN is returned, it means a non-existing
// UTXO was deleted, or an existing UTXO was overwritten. It
// corresponds to cases where the block-to-be-disconnect never had
// all its operations applied to the UTXO set. However, as both
// writing a UTXO and deleting a UTXO are idempotent operations, the
// result is still a version of the UTXO set with the effects of
// that block undone.
}
pindexOld = pindexOld->pprev;
}
// Roll forward from the forking point to the new tip.
int nForkHeight = pindexFork ? pindexFork->nHeight : 0;
for (int nHeight = nForkHeight + 1; nHeight <= pindexNew->nHeight;
++nHeight) {
const CBlockIndex &pindex{*Assert(pindexNew->GetAncestor(nHeight))};
LogPrintf("Rolling forward %s (%i)\n", pindex.GetBlockHash().ToString(),
nHeight);
m_chainman.GetNotifications().progress(
_("Replaying blocks…"),
(int)((nHeight - nForkHeight) * 100.0 /
(pindexNew->nHeight - nForkHeight)),
false);
if (!RollforwardBlock(&pindex, cache)) {
return false;
}
}
cache.SetBestBlock(pindexNew->GetBlockHash());
cache.Flush();
m_chainman.GetNotifications().progress(bilingual_str{}, 100, false);
return true;
}
// May NOT be used after any connections are up as much of the peer-processing
// logic assumes a consistent block index state
void Chainstate::ClearBlockIndexCandidates() {
AssertLockHeld(::cs_main);
m_best_fork_tip = nullptr;
m_best_fork_base = nullptr;
setBlockIndexCandidates.clear();
}
bool ChainstateManager::DumpRecentHeadersTime(const fs::path &filePath) const {
AssertLockHeld(cs_main);
if (!m_options.store_recent_headers_time) {
return false;
}
// Dump enough headers for RTT computation, with a few extras in case a
// reorg occurs.
const uint64_t numHeaders{20};
try {
const fs::path filePathTmp = filePath + ".new";
FILE *filestr = fsbridge::fopen(filePathTmp, "wb");
if (!filestr) {
return false;
}
CAutoFile file(filestr, SER_DISK, CLIENT_VERSION);
file << HEADERS_TIME_VERSION;
file << numHeaders;
const CBlockIndex *index = ActiveTip();
bool missingIndex{false};
for (uint64_t i = 0; i < numHeaders; i++) {
if (!index) {
LogPrintf("Missing block index, stopping the headers time "
"dumping after %d blocks.\n",
i);
missingIndex = true;
break;
}
file << index->GetBlockHash();
file << index->GetHeaderReceivedTime();
index = index->pprev;
}
if (!FileCommit(file.Get())) {
throw std::runtime_error(strprintf("Failed to commit to file %s",
PathToString(filePathTmp)));
}
file.fclose();
if (missingIndex) {
fs::remove(filePathTmp);
return false;
}
if (!RenameOver(filePathTmp, filePath)) {
throw std::runtime_error(strprintf("Rename failed from %s to %s",
PathToString(filePathTmp),
PathToString(filePath)));
}
} catch (const std::exception &e) {
LogPrintf("Failed to dump the headers time: %s.\n", e.what());
return false;
}
LogPrintf("Successfully dumped the last %d headers time to %s.\n",
numHeaders, PathToString(filePath));
return true;
}
bool ChainstateManager::LoadRecentHeadersTime(const fs::path &filePath) {
AssertLockHeld(cs_main);
if (!m_options.store_recent_headers_time) {
return false;
}
FILE *filestr = fsbridge::fopen(filePath, "rb");
CAutoFile file(filestr, SER_DISK, CLIENT_VERSION);
if (file.IsNull()) {
LogPrintf("Failed to open header times from disk, skipping.\n");
return false;
}
try {
uint64_t version;
file >> version;
if (version != HEADERS_TIME_VERSION) {
LogPrintf("Unsupported header times file version, skipping.\n");
return false;
}
uint64_t numBlocks;
file >> numBlocks;
for (uint64_t i = 0; i < numBlocks; i++) {
BlockHash blockHash;
int64_t receiveTime;
file >> blockHash;
file >> receiveTime;
CBlockIndex *index = m_blockman.LookupBlockIndex(blockHash);
if (!index) {
LogPrintf("Missing index for block %s, stopping the headers "
"time loading after %d blocks.\n",
blockHash.ToString(), i);
return false;
}
index->nTimeReceived = receiveTime;
}
} catch (const std::exception &e) {
LogPrintf("Failed to read the headers time file data on disk: %s.\n",
e.what());
return false;
}
return true;
}
bool ChainstateManager::LoadBlockIndex() {
AssertLockHeld(cs_main);
// Load block index from databases
bool needs_init = fReindex;
if (!fReindex) {
bool ret{m_blockman.LoadBlockIndexDB(SnapshotBlockhash())};
if (!ret) {
return false;
}
m_blockman.ScanAndUnlinkAlreadyPrunedFiles();
std::vector<CBlockIndex *> vSortedByHeight{
m_blockman.GetAllBlockIndices()};
std::sort(vSortedByHeight.begin(), vSortedByHeight.end(),
CBlockIndexHeightOnlyComparator());
for (CBlockIndex *pindex : vSortedByHeight) {
if (ShutdownRequested()) {
return false;
}
// If we have an assumeutxo-based chainstate, then the snapshot
// block will be a candidate for the tip, but it may not be
// VALID_TRANSACTIONS (eg if we haven't yet downloaded the block),
// so we special-case the snapshot block as a potential candidate
// here.
if (pindex == GetSnapshotBaseBlock() ||
(pindex->IsValid(BlockValidity::TRANSACTIONS) &&
(pindex->HaveNumChainTxs() || pindex->pprev == nullptr))) {
for (Chainstate *chainstate : GetAll()) {
chainstate->TryAddBlockIndexCandidate(pindex);
}
}
if (pindex->nStatus.isInvalid() &&
(!m_best_invalid ||
pindex->nChainWork > m_best_invalid->nChainWork)) {
m_best_invalid = pindex;
}
if (pindex->nStatus.isOnParkedChain() &&
(!m_best_parked ||
pindex->nChainWork > m_best_parked->nChainWork)) {
m_best_parked = pindex;
}
if (pindex->IsValid(BlockValidity::TREE) &&
(m_best_header == nullptr ||
CBlockIndexWorkComparator()(m_best_header, pindex))) {
m_best_header = pindex;
}
}
needs_init = m_blockman.m_block_index.empty();
}
if (needs_init) {
// Everything here is for *new* reindex/DBs. Thus, though
// LoadBlockIndexDB may have set fReindex if we shut down
// mid-reindex previously, we don't check fReindex and
// instead only check it prior to LoadBlockIndexDB to set
// needs_init.
LogPrintf("Initializing databases...\n");
}
return true;
}
bool Chainstate::LoadGenesisBlock() {
LOCK(cs_main);
const CChainParams &params{m_chainman.GetParams()};
// Check whether we're already initialized by checking for genesis in
// m_blockman.m_block_index. Note that we can't use m_chain here, since it
// is set based on the coins db, not the block index db, which is the only
// thing loaded at this point.
if (m_blockman.m_block_index.count(params.GenesisBlock().GetHash())) {
return true;
}
try {
const CBlock &block = params.GenesisBlock();
FlatFilePos blockPos{m_blockman.SaveBlockToDisk(block, 0, nullptr)};
if (blockPos.IsNull()) {
return error("%s: writing genesis block to disk failed", __func__);
}
CBlockIndex *pindex =
m_blockman.AddToBlockIndex(block, m_chainman.m_best_header);
m_chainman.ReceivedBlockTransactions(block, pindex, blockPos);
} catch (const std::runtime_error &e) {
return error("%s: failed to write genesis block: %s", __func__,
e.what());
}
return true;
}
void ChainstateManager::LoadExternalBlockFile(
FILE *fileIn, FlatFilePos *dbp,
std::multimap<BlockHash, FlatFilePos> *blocks_with_unknown_parent,
avalanche::Processor *const avalanche) {
// Either both should be specified (-reindex), or neither (-loadblock).
assert(!dbp == !blocks_with_unknown_parent);
int64_t nStart = GetTimeMillis();
const CChainParams &params{GetParams()};
int nLoaded = 0;
try {
// This takes over fileIn and calls fclose() on it in the CBufferedFile
// destructor. Make sure we have at least 2*MAX_TX_SIZE space in there
// so any transaction can fit in the buffer.
CBufferedFile blkdat(fileIn, 2 * MAX_TX_SIZE, MAX_TX_SIZE + 8, SER_DISK,
CLIENT_VERSION);
// nRewind indicates where to resume scanning in case something goes
// wrong, such as a block fails to deserialize.
uint64_t nRewind = blkdat.GetPos();
while (!blkdat.eof()) {
if (ShutdownRequested()) {
return;
}
blkdat.SetPos(nRewind);
// Start one byte further next time, in case of failure.
nRewind++;
// Remove former limit.
blkdat.SetLimit();
unsigned int nSize = 0;
try {
// Locate a header.
uint8_t buf[CMessageHeader::MESSAGE_START_SIZE];
blkdat.FindByte(std::byte(params.DiskMagic()[0]));
nRewind = blkdat.GetPos() + 1;
blkdat >> buf;
if (memcmp(buf, params.DiskMagic().data(),
CMessageHeader::MESSAGE_START_SIZE)) {
continue;
}
// Read size.
blkdat >> nSize;
if (nSize < 80) {
continue;
}
} catch (const std::exception &) {
// No valid block header found; don't complain.
// (this happens at the end of every blk.dat file)
break;
}
try {
// read block header
const uint64_t nBlockPos{blkdat.GetPos()};
if (dbp) {
dbp->nPos = nBlockPos;
}
blkdat.SetLimit(nBlockPos + nSize);
CBlockHeader header;
blkdat >> header;
const BlockHash hash{header.GetHash()};
// Skip the rest of this block (this may read from disk
// into memory); position to the marker before the next block,
// but it's still possible to rewind to the start of the
// current block (without a disk read).
nRewind = nBlockPos + nSize;
blkdat.SkipTo(nRewind);
// needs to remain available after the cs_main lock is released
// to avoid duplicate reads from disk
std::shared_ptr<CBlock> pblock{};
{
LOCK(cs_main);
// detect out of order blocks, and store them for later
if (hash != params.GetConsensus().hashGenesisBlock &&
!m_blockman.LookupBlockIndex(header.hashPrevBlock)) {
LogPrint(
BCLog::REINDEX,
"%s: Out of order block %s, parent %s not known\n",
__func__, hash.ToString(),
header.hashPrevBlock.ToString());
if (dbp && blocks_with_unknown_parent) {
blocks_with_unknown_parent->emplace(
header.hashPrevBlock, *dbp);
}
continue;
}
// process in case the block isn't known yet
const CBlockIndex *pindex =
m_blockman.LookupBlockIndex(hash);
if (!pindex || !pindex->nStatus.hasData()) {
// This block can be processed immediately; rewind to
// its start, read and deserialize it.
blkdat.SetPos(nBlockPos);
pblock = std::make_shared<CBlock>();
blkdat >> *pblock;
nRewind = blkdat.GetPos();
BlockValidationState state;
if (AcceptBlock(pblock, state, true, dbp, nullptr,
true)) {
nLoaded++;
}
if (state.IsError()) {
break;
}
} else if (hash != params.GetConsensus().hashGenesisBlock &&
pindex->nHeight % 1000 == 0) {
LogPrint(
BCLog::REINDEX,
"Block Import: already had block %s at height %d\n",
hash.ToString(), pindex->nHeight);
}
}
// Activate the genesis block so normal node progress can
// continue
if (hash == params.GetConsensus().hashGenesisBlock) {
bool genesis_activation_failure = false;
for (auto c : GetAll()) {
BlockValidationState state;
if (!c->ActivateBestChain(state, nullptr, avalanche)) {
genesis_activation_failure = true;
break;
}
}
if (genesis_activation_failure) {
break;
}
}
if (m_blockman.IsPruneMode() && !fReindex && pblock) {
// Must update the tip for pruning to work while importing
// with -loadblock. This is a tradeoff to conserve disk
// space at the expense of time spent updating the tip to be
// able to prune. Otherwise, ActivateBestChain won't be
// called by the import process until after all of the block
// files are loaded. ActivateBestChain can be called by
// concurrent network message processing, but that is not
// reliable for the purpose of pruning while importing.
bool activation_failure = false;
for (auto c : GetAll()) {
BlockValidationState state;
if (!c->ActivateBestChain(state, pblock, avalanche)) {
LogPrint(BCLog::REINDEX,
"failed to activate chain (%s)\n",
state.ToString());
activation_failure = true;
break;
}
}
if (activation_failure) {
break;
}
}
NotifyHeaderTip(*this);
if (!blocks_with_unknown_parent) {
continue;
}
// Recursively process earlier encountered successors of this
// block
std::deque<BlockHash> queue;
queue.push_back(hash);
while (!queue.empty()) {
BlockHash head = queue.front();
queue.pop_front();
auto range = blocks_with_unknown_parent->equal_range(head);
while (range.first != range.second) {
std::multimap<BlockHash, FlatFilePos>::iterator it =
range.first;
std::shared_ptr<CBlock> pblockrecursive =
std::make_shared<CBlock>();
if (m_blockman.ReadBlockFromDisk(*pblockrecursive,
it->second)) {
LogPrint(
BCLog::REINDEX,
"%s: Processing out of order child %s of %s\n",
__func__, pblockrecursive->GetHash().ToString(),
head.ToString());
LOCK(cs_main);
BlockValidationState dummy;
if (AcceptBlock(pblockrecursive, dummy, true,
&it->second, nullptr, true)) {
nLoaded++;
queue.push_back(pblockrecursive->GetHash());
}
}
range.first++;
blocks_with_unknown_parent->erase(it);
NotifyHeaderTip(*this);
}
}
} catch (const std::exception &e) {
// Historical bugs added extra data to the block files that does
// not deserialize cleanly. Commonly this data is between
// readable blocks, but it does not really matter. Such data is
// not fatal to the import process. The code that reads the
// block files deals with invalid data by simply ignoring it. It
// continues to search for the next {4 byte magic message start
// bytes + 4 byte length + block} that does deserialize cleanly
// and passes all of the other block validation checks dealing
// with POW and the merkle root, etc... We merely note with this
// informational log message when unexpected data is
// encountered. We could also be experiencing a storage system
// read error, or a read of a previous bad write. These are
// possible, but less likely scenarios. We don't have enough
// information to tell a difference here. The reindex process is
// not the place to attempt to clean and/or compact the block
// files. If so desired, a studious node operator may use
// knowledge of the fact that the block files are not entirely
// pristine in order to prepare a set of pristine, and perhaps
// ordered, block files for later reindexing.
LogPrint(BCLog::REINDEX,
"%s: unexpected data at file offset 0x%x - %s. "
"continuing\n",
__func__, (nRewind - 1), e.what());
}
}
} catch (const std::runtime_error &e) {
AbortNode(std::string("System error: ") + e.what());
}
LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded,
GetTimeMillis() - nStart);
}
void ChainstateManager::CheckBlockIndex() {
if (!ShouldCheckBlockIndex()) {
return;
}
LOCK(cs_main);
// During a reindex, we read the genesis block and call CheckBlockIndex
// before ActivateBestChain, so we have the genesis block in
// m_blockman.m_block_index but no active chain. (A few of the tests when
// iterating the block tree require that m_chain has been initialized.)
if (ActiveChain().Height() < 0) {
assert(m_blockman.m_block_index.size() <= 1);
return;
}
// Build forward-pointing map of the entire block tree.
std::multimap<CBlockIndex *, CBlockIndex *> forward;
for (auto &[_, block_index] : m_blockman.m_block_index) {
forward.emplace(block_index.pprev, &block_index);
}
assert(forward.size() == m_blockman.m_block_index.size());
std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
rangeGenesis = forward.equal_range(nullptr);
CBlockIndex *pindex = rangeGenesis.first->second;
rangeGenesis.first++;
// There is only one index entry with parent nullptr.
assert(rangeGenesis.first == rangeGenesis.second);
// Iterate over the entire block tree, using depth-first search.
// Along the way, remember whether there are blocks on the path from genesis
// block being explored which are the first to have certain properties.
size_t nNodes = 0;
int nHeight = 0;
// Oldest ancestor of pindex which is invalid.
CBlockIndex *pindexFirstInvalid = nullptr;
// Oldest ancestor of pindex which is parked.
CBlockIndex *pindexFirstParked = nullptr;
// Oldest ancestor of pindex which does not have data available, since
// assumeutxo snapshot if used.
CBlockIndex *pindexFirstMissing = nullptr;
// Oldest ancestor of pindex for which nTx == 0, since assumeutxo snapshot
// if used..
CBlockIndex *pindexFirstNeverProcessed = nullptr;
// Oldest ancestor of pindex which does not have BLOCK_VALID_TREE
// (regardless of being valid or not).
CBlockIndex *pindexFirstNotTreeValid = nullptr;
// Oldest ancestor of pindex which does not have BLOCK_VALID_TRANSACTIONS
// (regardless of being valid or not), since assumeutxo snapshot if used.
CBlockIndex *pindexFirstNotTransactionsValid = nullptr;
// Oldest ancestor of pindex which does not have BLOCK_VALID_CHAIN
// (regardless of being valid or not), since assumeutxo snapshot if used.
CBlockIndex *pindexFirstNotChainValid = nullptr;
// Oldest ancestor of pindex which does not have BLOCK_VALID_SCRIPTS
// (regardless of being valid or not), since assumeutxo snapshot if used.
CBlockIndex *pindexFirstNotScriptsValid = nullptr;
// After checking an assumeutxo snapshot block, reset pindexFirst pointers
// to earlier blocks that have not been downloaded or validated yet, so
// checks for later blocks can assume the earlier blocks were validated and
// be stricter, testing for more requirements.
const CBlockIndex *snap_base{GetSnapshotBaseBlock()};
CBlockIndex *snap_first_missing{}, *snap_first_notx{}, *snap_first_notv{},
*snap_first_nocv{}, *snap_first_nosv{};
auto snap_update_firsts = [&] {
if (pindex == snap_base) {
std::swap(snap_first_missing, pindexFirstMissing);
std::swap(snap_first_notx, pindexFirstNeverProcessed);
std::swap(snap_first_notv, pindexFirstNotTransactionsValid);
std::swap(snap_first_nocv, pindexFirstNotChainValid);
std::swap(snap_first_nosv, pindexFirstNotScriptsValid);
}
};
while (pindex != nullptr) {
nNodes++;
if (pindexFirstInvalid == nullptr && pindex->nStatus.hasFailed()) {
pindexFirstInvalid = pindex;
}
if (pindexFirstParked == nullptr && pindex->nStatus.isParked()) {
pindexFirstParked = pindex;
}
if (pindexFirstMissing == nullptr && !pindex->nStatus.hasData()) {
pindexFirstMissing = pindex;
}
if (pindexFirstNeverProcessed == nullptr && pindex->nTx == 0) {
pindexFirstNeverProcessed = pindex;
}
if (pindex->pprev != nullptr && pindexFirstNotTreeValid == nullptr &&
pindex->nStatus.getValidity() < BlockValidity::TREE) {
pindexFirstNotTreeValid = pindex;
}
if (pindex->pprev != nullptr) {
if (pindexFirstNotTransactionsValid == nullptr &&
pindex->nStatus.getValidity() < BlockValidity::TRANSACTIONS) {
pindexFirstNotTransactionsValid = pindex;
}
if (pindexFirstNotChainValid == nullptr &&
pindex->nStatus.getValidity() < BlockValidity::CHAIN) {
pindexFirstNotChainValid = pindex;
}
if (pindexFirstNotScriptsValid == nullptr &&
pindex->nStatus.getValidity() < BlockValidity::SCRIPTS) {
pindexFirstNotScriptsValid = pindex;
}
}
// Begin: actual consistency checks.
if (pindex->pprev == nullptr) {
// Genesis block checks.
// Genesis block's hash must match.
assert(pindex->GetBlockHash() == GetConsensus().hashGenesisBlock);
for (auto c : GetAll()) {
if (c->m_chain.Genesis() != nullptr) {
// The chain's genesis block must be this block.
assert(pindex == c->m_chain.Genesis());
}
}
}
if (!pindex->HaveNumChainTxs()) {
// nSequenceId can't be set positive for blocks that aren't linked
// (negative is used for preciousblock)
assert(pindex->nSequenceId <= 0);
}
// VALID_TRANSACTIONS is equivalent to nTx > 0 for all nodes (whether or
// not pruning has occurred). HAVE_DATA is only equivalent to nTx > 0
// (or VALID_TRANSACTIONS) if no pruning has occurred.
if (!m_blockman.m_have_pruned) {
// If we've never pruned, then HAVE_DATA should be equivalent to nTx
// > 0
assert(pindex->nStatus.hasData() == (pindex->nTx > 0));
assert(pindexFirstMissing == pindexFirstNeverProcessed);
} else if (pindex->nStatus.hasData()) {
// If we have pruned, then we can only say that HAVE_DATA implies
// nTx > 0
assert(pindex->nTx > 0);
}
if (pindex->nStatus.hasUndo()) {
assert(pindex->nStatus.hasData());
}
if (snap_base && snap_base->GetAncestor(pindex->nHeight) == pindex) {
// Assumed-valid blocks should connect to the main chain.
assert(pindex->nStatus.getValidity() >= BlockValidity::TREE);
}
// There should only be an nTx value if we have
// actually seen a block's transactions.
// This is pruning-independent.
assert((pindex->nStatus.getValidity() >= BlockValidity::TRANSACTIONS) ==
(pindex->nTx > 0));
// All parents having had data (at some point) is equivalent to all
// parents being VALID_TRANSACTIONS, which is equivalent to
// HaveNumChainTxs().
assert((pindexFirstNeverProcessed == nullptr || pindex == snap_base) ==
(pindex->HaveNumChainTxs()));
assert((pindexFirstNotTransactionsValid == nullptr ||
pindex == snap_base) == (pindex->HaveNumChainTxs()));
// nHeight must be consistent.
assert(pindex->nHeight == nHeight);
// For every block except the genesis block, the chainwork must be
// larger than the parent's.
assert(pindex->pprev == nullptr ||
pindex->nChainWork >= pindex->pprev->nChainWork);
// The pskip pointer must point back for all but the first 2 blocks.
assert(nHeight < 2 ||
(pindex->pskip && (pindex->pskip->nHeight < nHeight)));
// All m_blockman.m_block_index entries must at least be TREE valid
assert(pindexFirstNotTreeValid == nullptr);
if (pindex->nStatus.getValidity() >= BlockValidity::TREE) {
// TREE valid implies all parents are TREE valid
assert(pindexFirstNotTreeValid == nullptr);
}
if (pindex->nStatus.getValidity() >= BlockValidity::CHAIN) {
// CHAIN valid implies all parents are CHAIN valid
assert(pindexFirstNotChainValid == nullptr);
}
if (pindex->nStatus.getValidity() >= BlockValidity::SCRIPTS) {
// SCRIPTS valid implies all parents are SCRIPTS valid
assert(pindexFirstNotScriptsValid == nullptr);
}
if (pindexFirstInvalid == nullptr) {
// Checks for not-invalid blocks.
// The failed mask cannot be set for blocks without invalid parents.
assert(!pindex->nStatus.isInvalid());
}
if (pindexFirstParked == nullptr) {
// Checks for not-parked blocks.
// The parked mask cannot be set for blocks without parked parents.
// (i.e., hasParkedParent only if an ancestor is properly parked).
assert(!pindex->nStatus.isOnParkedChain());
}
// Make sure nChainTx sum is correctly computed.
if (!pindex->pprev) {
// If no previous block, nTx and nChainTx must be the same.
assert(pindex->nChainTx == pindex->nTx);
} else if (pindex->pprev->nChainTx > 0 && pindex->nTx > 0) {
// If previous nChainTx is set and number of transactions in block
// is known, sum must be set.
assert(pindex->nChainTx == pindex->nTx + pindex->pprev->nChainTx);
} else {
// Otherwise nChainTx should only be set if this is a snapshot
// block, and must be set if it is.
assert((pindex->nChainTx != 0) == (pindex == snap_base));
}
// Chainstate-specific checks on setBlockIndexCandidates
for (auto c : GetAll()) {
if (c->m_chain.Tip() == nullptr) {
continue;
}
// Two main factors determine whether pindex is a candidate in
// setBlockIndexCandidates:
//
// - If pindex has less work than the chain tip, it should not be a
// candidate, and this will be asserted below. Otherwise it is a
// potential candidate.
//
// - If pindex or one of its parent blocks back to the genesis block
// or an assumeutxo snapshot never downloaded transactions
// (pindexFirstNeverProcessed is non-null), it should not be a
// candidate, and this will be asserted below. The only exception
// is if pindex itself is an assumeutxo snapshot block. Then it is
// also a potential candidate.
if (!CBlockIndexWorkComparator()(pindex, c->m_chain.Tip()) &&
(pindexFirstNeverProcessed == nullptr || pindex == snap_base)) {
// If pindex was detected as invalid (pindexFirstInvalid is
// non-null), it is not required to be in
// setBlockIndexCandidates.
if (pindexFirstInvalid == nullptr) {
// If this chainstate is the active chainstate, pindex
// must be in setBlockIndexCandidates. Otherwise, this
// chainstate is a background validation chainstate, and
// pindex only needs to be added if it is an ancestor of
// the snapshot that is being validated.
if (c == &ActiveChainstate() ||
GetSnapshotBaseBlock()->GetAncestor(pindex->nHeight) ==
pindex) {
// If pindex and all its parents back to the genesis
// block or an assumeutxo snapshot block downloaded
// transactions, transactions, and the transactions were
// not pruned (pindexFirstMissing is null), it is a
// potential candidate or was parked. The check excludes
// pruned blocks, because if any blocks were pruned
// between pindex the current chain tip, pindex will
// only temporarily be added to setBlockIndexCandidates,
// before being moved to m_blocks_unlinked. This check
// could be improved to verify that if all blocks
// between the chain tip and pindex have data, pindex
// must be a candidate.
if (pindexFirstMissing == nullptr) {
assert(pindex->nStatus.isOnParkedChain() ||
c->setBlockIndexCandidates.count(pindex));
}
// If pindex is the chain tip, it also is a potential
// candidate.
//
// If the chainstate was loaded from a snapshot and
// pindex is the base of the snapshot, pindex is also a
// potential candidate.
if (pindex == c->m_chain.Tip() ||
pindex == c->SnapshotBase()) {
assert(c->setBlockIndexCandidates.count(pindex));
}
}
// If some parent is missing, then it could be that this
// block was in setBlockIndexCandidates but had to be
// removed because of the missing data. In this case it must
// be in m_blocks_unlinked -- see test below.
}
} else {
// If this block sorts worse than the current tip or some
// ancestor's block has never been seen, it cannot be in
// setBlockIndexCandidates.
assert(c->setBlockIndexCandidates.count(pindex) == 0);
}
}
// Check whether this block is in m_blocks_unlinked.
std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
rangeUnlinked =
m_blockman.m_blocks_unlinked.equal_range(pindex->pprev);
bool foundInUnlinked = false;
while (rangeUnlinked.first != rangeUnlinked.second) {
assert(rangeUnlinked.first->first == pindex->pprev);
if (rangeUnlinked.first->second == pindex) {
foundInUnlinked = true;
break;
}
rangeUnlinked.first++;
}
if (pindex->pprev && pindex->nStatus.hasData() &&
pindexFirstNeverProcessed != nullptr &&
pindexFirstInvalid == nullptr) {
// If this block has block data available, some parent was never
// received, and has no invalid parents, it must be in
// m_blocks_unlinked.
assert(foundInUnlinked);
}
if (!pindex->nStatus.hasData()) {
// Can't be in m_blocks_unlinked if we don't HAVE_DATA
assert(!foundInUnlinked);
}
if (pindexFirstMissing == nullptr) {
// We aren't missing data for any parent -- cannot be in
// m_blocks_unlinked.
assert(!foundInUnlinked);
}
if (pindex->pprev && pindex->nStatus.hasData() &&
pindexFirstNeverProcessed == nullptr &&
pindexFirstMissing != nullptr) {
// We HAVE_DATA for this block, have received data for all parents
// at some point, but we're currently missing data for some parent.
assert(m_blockman.m_have_pruned);
// This block may have entered m_blocks_unlinked if:
// - it has a descendant that at some point had more work than the
// tip, and
// - we tried switching to that descendant but were missing
// data for some intermediate block between m_chain and the
// tip.
// So if this block is itself better than any m_chain.Tip() and it
// wasn't in setBlockIndexCandidates, then it must be in
// m_blocks_unlinked.
for (auto c : GetAll()) {
const bool is_active = c == &ActiveChainstate();
if (!CBlockIndexWorkComparator()(pindex, c->m_chain.Tip()) &&
c->setBlockIndexCandidates.count(pindex) == 0) {
if (pindexFirstInvalid == nullptr) {
if (is_active ||
snap_base->GetAncestor(pindex->nHeight) == pindex) {
assert(foundInUnlinked);
}
}
}
}
}
// Perhaps too slow
// assert(pindex->GetBlockHash() == pindex->GetBlockHeader().GetHash());
// End: actual consistency checks.
// Try descending into the first subnode.
snap_update_firsts();
std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
range = forward.equal_range(pindex);
if (range.first != range.second) {
// A subnode was found.
pindex = range.first->second;
nHeight++;
continue;
}
// This is a leaf node. Move upwards until we reach a node of which we
// have not yet visited the last child.
while (pindex) {
// We are going to either move to a parent or a sibling of pindex.
snap_update_firsts();
// If pindex was the first with a certain property, unset the
// corresponding variable.
if (pindex == pindexFirstInvalid) {
pindexFirstInvalid = nullptr;
}
if (pindex == pindexFirstParked) {
pindexFirstParked = nullptr;
}
if (pindex == pindexFirstMissing) {
pindexFirstMissing = nullptr;
}
if (pindex == pindexFirstNeverProcessed) {
pindexFirstNeverProcessed = nullptr;
}
if (pindex == pindexFirstNotTreeValid) {
pindexFirstNotTreeValid = nullptr;
}
if (pindex == pindexFirstNotTransactionsValid) {
pindexFirstNotTransactionsValid = nullptr;
}
if (pindex == pindexFirstNotChainValid) {
pindexFirstNotChainValid = nullptr;
}
if (pindex == pindexFirstNotScriptsValid) {
pindexFirstNotScriptsValid = nullptr;
}
// Find our parent.
CBlockIndex *pindexPar = pindex->pprev;
// Find which child we just visited.
std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
rangePar = forward.equal_range(pindexPar);
while (rangePar.first->second != pindex) {
// Our parent must have at least the node we're coming from as
// child.
assert(rangePar.first != rangePar.second);
rangePar.first++;
}
// Proceed to the next one.
rangePar.first++;
if (rangePar.first != rangePar.second) {
// Move to the sibling.
pindex = rangePar.first->second;
break;
} else {
// Move up further.
pindex = pindexPar;
nHeight--;
continue;
}
}
}
// Check that we actually traversed the entire map.
assert(nNodes == forward.size());
}
std::string Chainstate::ToString() {
AssertLockHeld(::cs_main);
CBlockIndex *tip = m_chain.Tip();
return strprintf("Chainstate [%s] @ height %d (%s)",
m_from_snapshot_blockhash ? "snapshot" : "ibd",
tip ? tip->nHeight : -1,
tip ? tip->GetBlockHash().ToString() : "null");
}
bool Chainstate::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size) {
AssertLockHeld(::cs_main);
if (coinstip_size == m_coinstip_cache_size_bytes &&
coinsdb_size == m_coinsdb_cache_size_bytes) {
// Cache sizes are unchanged, no need to continue.
return true;
}
size_t old_coinstip_size = m_coinstip_cache_size_bytes;
m_coinstip_cache_size_bytes = coinstip_size;
m_coinsdb_cache_size_bytes = coinsdb_size;
CoinsDB().ResizeCache(coinsdb_size);
LogPrintf("[%s] resized coinsdb cache to %.1f MiB\n", this->ToString(),
coinsdb_size * (1.0 / 1024 / 1024));
LogPrintf("[%s] resized coinstip cache to %.1f MiB\n", this->ToString(),
coinstip_size * (1.0 / 1024 / 1024));
BlockValidationState state;
bool ret;
if (coinstip_size > old_coinstip_size) {
// Likely no need to flush if cache sizes have grown.
ret = FlushStateToDisk(state, FlushStateMode::IF_NEEDED);
} else {
// Otherwise, flush state to disk and deallocate the in-memory coins
// map.
ret = FlushStateToDisk(state, FlushStateMode::ALWAYS);
}
return ret;
}
//! Guess how far we are in the verification process at the given block index
//! require cs_main if pindex has not been validated yet (because the chain's
//! transaction count might be unset) This conditional lock requirement might be
//! confusing, see: https://github.com/bitcoin/bitcoin/issues/15994
double GuessVerificationProgress(const ChainTxData &data,
const CBlockIndex *pindex) {
if (pindex == nullptr) {
return 0.0;
}
if (pindex->nChainTx == 0) {
LogPrintLevel(BCLog::VALIDATION, BCLog::Level::Debug,
"Block %d has unset m_chain_tx_count. Unable to "
"estimate verification progress.\n",
pindex->nHeight);
return 0.0;
}
int64_t nNow = time(nullptr);
double fTxTotal;
if (pindex->GetChainTxCount() <= data.nTxCount) {
fTxTotal = data.nTxCount + (nNow - data.nTime) * data.dTxRate;
} else {
fTxTotal = pindex->GetChainTxCount() +
(nNow - pindex->GetBlockTime()) * data.dTxRate;
}
return std::min<double>(pindex->GetChainTxCount() / fTxTotal, 1.0);
}
std::optional<BlockHash> ChainstateManager::SnapshotBlockhash() const {
LOCK(::cs_main);
if (m_active_chainstate && m_active_chainstate->m_from_snapshot_blockhash) {
// If a snapshot chainstate exists, it will always be our active.
return m_active_chainstate->m_from_snapshot_blockhash;
}
return std::nullopt;
}
std::vector<Chainstate *> ChainstateManager::GetAll() {
LOCK(::cs_main);
std::vector<Chainstate *> out;
for (Chainstate *pchainstate :
{m_ibd_chainstate.get(), m_snapshot_chainstate.get()}) {
if (this->IsUsable(pchainstate)) {
out.push_back(pchainstate);
}
}
return out;
}
Chainstate &ChainstateManager::InitializeChainstate(CTxMemPool *mempool) {
AssertLockHeld(::cs_main);
assert(!m_ibd_chainstate);
assert(!m_active_chainstate);
m_ibd_chainstate = std::make_unique<Chainstate>(mempool, m_blockman, *this);
m_active_chainstate = m_ibd_chainstate.get();
return *m_active_chainstate;
}
[[nodiscard]] static bool DeleteCoinsDBFromDisk(const fs::path &db_path,
bool is_snapshot)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
if (is_snapshot) {
fs::path base_blockhash_path =
db_path / node::SNAPSHOT_BLOCKHASH_FILENAME;
try {
const bool existed{fs::remove(base_blockhash_path)};
if (!existed) {
LogPrintf("[snapshot] snapshot chainstate dir being removed "
"lacks %s file\n",
fs::PathToString(node::SNAPSHOT_BLOCKHASH_FILENAME));
}
} catch (const fs::filesystem_error &e) {
LogPrintf("[snapshot] failed to remove file %s: %s\n",
fs::PathToString(base_blockhash_path),
fsbridge::get_filesystem_error_message(e));
}
}
std::string path_str = fs::PathToString(db_path);
LogPrintf("Removing leveldb dir at %s\n", path_str);
// We have to destruct before this call leveldb::DB in order to release the
// db lock, otherwise `DestroyDB` will fail. See `leveldb::~DBImpl()`.
const bool destroyed = dbwrapper::DestroyDB(path_str, {}).ok();
if (!destroyed) {
LogPrintf("error: leveldb DestroyDB call failed on %s\n", path_str);
}
// Datadir should be removed from filesystem; otherwise initialization may
// detect it on subsequent statups and get confused.
//
// If the base_blockhash_path removal above fails in the case of snapshot
// chainstates, this will return false since leveldb won't remove a
// non-empty directory.
return destroyed && !fs::exists(db_path);
}
bool ChainstateManager::ActivateSnapshot(AutoFile &coins_file,
const SnapshotMetadata &metadata,
bool in_memory) {
BlockHash base_blockhash = metadata.m_base_blockhash;
if (this->SnapshotBlockhash()) {
LogPrintf("[snapshot] can't activate a snapshot-based chainstate more "
"than once\n");
return false;
}
{
LOCK(::cs_main);
if (Assert(m_active_chainstate->GetMempool())->size() > 0) {
LogPrintf("[snapshot] can't activate a snapshot when mempool not "
"empty\n");
return false;
}
}
int64_t current_coinsdb_cache_size{0};
int64_t current_coinstip_cache_size{0};
// Cache percentages to allocate to each chainstate.
//
// These particular percentages don't matter so much since they will only be
// relevant during snapshot activation; caches are rebalanced at the
// conclusion of this function. We want to give (essentially) all available
// cache capacity to the snapshot to aid the bulk load later in this
// function.
static constexpr double IBD_CACHE_PERC = 0.01;
static constexpr double SNAPSHOT_CACHE_PERC = 0.99;
{
LOCK(::cs_main);
// Resize the coins caches to ensure we're not exceeding memory limits.
//
// Allocate the majority of the cache to the incoming snapshot
// chainstate, since (optimistically) getting to its tip will be the top
// priority. We'll need to call `MaybeRebalanceCaches()` once we're done
// with this function to ensure the right allocation (including the
// possibility that no snapshot was activated and that we should restore
// the active chainstate caches to their original size).
//
current_coinsdb_cache_size =
this->ActiveChainstate().m_coinsdb_cache_size_bytes;
current_coinstip_cache_size =
this->ActiveChainstate().m_coinstip_cache_size_bytes;
// Temporarily resize the active coins cache to make room for the
// newly-created snapshot chain.
this->ActiveChainstate().ResizeCoinsCaches(
static_cast<size_t>(current_coinstip_cache_size * IBD_CACHE_PERC),
static_cast<size_t>(current_coinsdb_cache_size * IBD_CACHE_PERC));
}
auto snapshot_chainstate =
WITH_LOCK(::cs_main, return std::make_unique<Chainstate>(
/* mempool */ nullptr, m_blockman, *this,
base_blockhash));
{
LOCK(::cs_main);
snapshot_chainstate->InitCoinsDB(
static_cast<size_t>(current_coinsdb_cache_size *
SNAPSHOT_CACHE_PERC),
in_memory, false, "chainstate");
snapshot_chainstate->InitCoinsCache(static_cast<size_t>(
current_coinstip_cache_size * SNAPSHOT_CACHE_PERC));
}
auto cleanup_bad_snapshot =
[&](const char *reason) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
LogPrintf("[snapshot] activation failed - %s\n", reason);
this->MaybeRebalanceCaches();
// PopulateAndValidateSnapshot can return (in error) before the
// leveldb datadir has been created, so only attempt removal if we
// got that far.
if (auto snapshot_datadir = node::FindSnapshotChainstateDir()) {
// We have to destruct leveldb::DB in order to release the db
// lock, otherwise DestroyDB() (in DeleteCoinsDBFromDisk()) will
// fail. See `leveldb::~DBImpl()`. Destructing the chainstate
// (and so resetting the coinsviews object) does this.
snapshot_chainstate.reset();
bool removed = DeleteCoinsDBFromDisk(*snapshot_datadir,
/*is_snapshot=*/true);
if (!removed) {
AbortNode(strprintf(
"Failed to remove snapshot chainstate dir (%s). "
"Manually remove it before restarting.\n",
fs::PathToString(*snapshot_datadir)));
}
}
return false;
};
if (!this->PopulateAndValidateSnapshot(*snapshot_chainstate, coins_file,
metadata)) {
LOCK(::cs_main);
return cleanup_bad_snapshot("population failed");
}
// cs_main required for rest of snapshot activation.
LOCK(::cs_main);
// Do a final check to ensure that the snapshot chainstate is actually a
// more work chain than the active chainstate; a user could have loaded a
// snapshot very late in the IBD process, and we wouldn't want to load a
// useless chainstate.
if (!CBlockIndexWorkComparator()(ActiveTip(),
snapshot_chainstate->m_chain.Tip())) {
return cleanup_bad_snapshot("work does not exceed active chainstate");
}
// If not in-memory, persist the base blockhash for use during subsequent
// initialization.
if (!in_memory) {
if (!node::WriteSnapshotBaseBlockhash(*snapshot_chainstate)) {
return cleanup_bad_snapshot("could not write base blockhash");
}
}
assert(!m_snapshot_chainstate);
m_snapshot_chainstate.swap(snapshot_chainstate);
const bool chaintip_loaded = m_snapshot_chainstate->LoadChainTip();
assert(chaintip_loaded);
// Transfer possession of the mempool to the snapshot chainstate.
// Mempool is empty at this point because we're still in IBD.
Assert(m_active_chainstate->m_mempool->size() == 0);
Assert(!m_snapshot_chainstate->m_mempool);
m_snapshot_chainstate->m_mempool = m_active_chainstate->m_mempool;
m_active_chainstate->m_mempool = nullptr;
m_active_chainstate = m_snapshot_chainstate.get();
m_blockman.m_snapshot_height = this->GetSnapshotBaseHeight();
LogPrintf("[snapshot] successfully activated snapshot %s\n",
base_blockhash.ToString());
LogPrintf("[snapshot] (%.2f MB)\n",
m_snapshot_chainstate->CoinsTip().DynamicMemoryUsage() /
(1000 * 1000));
this->MaybeRebalanceCaches();
return true;
}
static void FlushSnapshotToDisk(CCoinsViewCache &coins_cache,
bool snapshot_loaded) {
LOG_TIME_MILLIS_WITH_CATEGORY_MSG_ONCE(
strprintf("%s (%.2f MB)",
snapshot_loaded ? "saving snapshot chainstate"
: "flushing coins cache",
coins_cache.DynamicMemoryUsage() / (1000 * 1000)),
BCLog::LogFlags::ALL);
coins_cache.Flush();
}
struct StopHashingException : public std::exception {
const char *what() const throw() override {
return "ComputeUTXOStats interrupted by shutdown.";
}
};
static void SnapshotUTXOHashBreakpoint() {
if (ShutdownRequested()) {
throw StopHashingException();
}
}
bool ChainstateManager::PopulateAndValidateSnapshot(
Chainstate &snapshot_chainstate, AutoFile &coins_file,
const SnapshotMetadata &metadata) {
// It's okay to release cs_main before we're done using `coins_cache`
// because we know that nothing else will be referencing the newly created
// snapshot_chainstate yet.
CCoinsViewCache &coins_cache =
*WITH_LOCK(::cs_main, return &snapshot_chainstate.CoinsTip());
BlockHash base_blockhash = metadata.m_base_blockhash;
CBlockIndex *snapshot_start_block = WITH_LOCK(
::cs_main, return m_blockman.LookupBlockIndex(base_blockhash));
if (!snapshot_start_block) {
// Needed for ComputeUTXOStats to determine the
// height and to avoid a crash when base_blockhash.IsNull()
LogPrintf("[snapshot] Did not find snapshot start blockheader %s\n",
base_blockhash.ToString());
return false;
}
int base_height = snapshot_start_block->nHeight;
const auto &maybe_au_data = GetParams().AssumeutxoForHeight(base_height);
if (!maybe_au_data) {
LogPrintf("[snapshot] assumeutxo height in snapshot metadata not "
"recognized (%d) - refusing to load snapshot\n",
base_height);
return false;
}
const AssumeutxoData &au_data = *maybe_au_data;
// This work comparison is a duplicate check with the one performed later in
// ActivateSnapshot(), but is done so that we avoid doing the long work of
// staging a snapshot that isn't actually usable.
if (WITH_LOCK(::cs_main, return !CBlockIndexWorkComparator()(
ActiveTip(), snapshot_start_block))) {
LogPrintf("[snapshot] activation failed - work does not exceed active "
"chainstate\n");
return false;
}
- COutPoint outpoint;
- Coin coin;
const uint64_t coins_count = metadata.m_coins_count;
uint64_t coins_left = metadata.m_coins_count;
- LogPrintf("[snapshot] loading coins from snapshot %s\n",
+ LogPrintf("[snapshot] loading %d coins from snapshot %s\n", coins_left,
base_blockhash.ToString());
int64_t coins_processed{0};
while (coins_left > 0) {
try {
- coins_file >> outpoint;
- coins_file >> coin;
- } catch (const std::ios_base::failure &) {
- LogPrintf("[snapshot] bad snapshot format or truncated snapshot "
- "after deserializing %d coins\n",
- coins_count - coins_left);
- return false;
- }
- if (coin.GetHeight() > uint32_t(base_height) ||
- // Avoid integer wrap-around in coinstats.cpp:ApplyHash
- outpoint.GetN() >=
- std::numeric_limits<decltype(outpoint.GetN())>::max()) {
- LogPrintf(
- "[snapshot] bad snapshot data after deserializing %d coins\n",
- coins_count - coins_left);
- return false;
- }
- if (!MoneyRange(coin.GetTxOut().nValue)) {
- LogPrintf("[snapshot] bad snapshot data after deserializing %d "
- "coins - bad tx out value\n",
- coins_count - coins_left);
- return false;
- }
- coins_cache.EmplaceCoinInternalDANGER(std::move(outpoint),
- std::move(coin));
-
- --coins_left;
- ++coins_processed;
-
- if (coins_processed % 1000000 == 0) {
- LogPrintf("[snapshot] %d coins loaded (%.2f%%, %.2f MB)\n",
- coins_processed,
- static_cast<float>(coins_processed) * 100 /
- static_cast<float>(coins_count),
- coins_cache.DynamicMemoryUsage() / (1000 * 1000));
- }
-
- // Batch write and flush (if we need to) every so often.
- //
- // If our average Coin size is roughly 41 bytes, checking every 120,000
- // coins means <5MB of memory imprecision.
- if (coins_processed % 120000 == 0) {
- if (ShutdownRequested()) {
+ TxId txid;
+ coins_file >> txid;
+ size_t coins_per_txid{0};
+ coins_per_txid = ReadCompactSize(coins_file);
+
+ if (coins_per_txid > coins_left) {
+ LogPrintf("[snapshot] mismatch in coins count in snapshot "
+ "metadata and actual snapshot data\n");
return false;
}
- const auto snapshot_cache_state = WITH_LOCK(
- ::cs_main, return snapshot_chainstate.GetCoinsCacheSizeState());
+ for (size_t i = 0; i < coins_per_txid; i++) {
+ Coin coin;
+ COutPoint outpoint{
+ txid, static_cast<uint32_t>(ReadCompactSize(coins_file))};
+ coins_file >> coin;
+ // Avoid integer wrap-around in coinstats.cpp:ApplyHash
+ if (coin.GetHeight() > uint32_t(base_height) ||
+ outpoint.GetN() >=
+ std::numeric_limits<decltype(outpoint.GetN())>::max()) {
+ LogPrintf("[snapshot] bad snapshot data after "
+ "deserializing %d coins\n",
+ coins_count - coins_left);
+ return false;
+ }
+ if (!MoneyRange(coin.GetTxOut().nValue)) {
+ LogPrintf("[snapshot] bad snapshot data after "
+ "deserializing %d coins - bad tx out value\n",
+ coins_count - coins_left);
+ return false;
+ }
+ coins_cache.EmplaceCoinInternalDANGER(std::move(outpoint),
+ std::move(coin));
+
+ --coins_left;
+ ++coins_processed;
+
+ if (coins_processed % 1000000 == 0) {
+ LogPrintf("[snapshot] %d coins loaded (%.2f%%, %.2f MB)\n",
+ coins_processed,
+ static_cast<float>(coins_processed) * 100 /
+ static_cast<float>(coins_count),
+ coins_cache.DynamicMemoryUsage() / (1000 * 1000));
+ }
- if (snapshot_cache_state >= CoinsCacheSizeState::CRITICAL) {
- // This is a hack - we don't know what the actual best block is,
- // but that doesn't matter for the purposes of flushing the
- // cache here. We'll set this to its correct value
- // (`base_blockhash`) below after the coins are loaded.
- coins_cache.SetBestBlock(BlockHash{GetRandHash()});
+ // Batch write and flush (if we need to) every so often.
+ //
+ // If our average Coin size is roughly 41 bytes, checking every
+ // 120,000 coins means <5MB of memory imprecision.
+ if (coins_processed % 120000 == 0) {
+ if (ShutdownRequested()) {
+ return false;
+ }
- // No need to acquire cs_main since this chainstate isn't being
- // used yet.
- FlushSnapshotToDisk(coins_cache, /*snapshot_loaded=*/false);
+ const auto snapshot_cache_state = WITH_LOCK(
+ ::cs_main,
+ return snapshot_chainstate.GetCoinsCacheSizeState());
+
+ if (snapshot_cache_state >= CoinsCacheSizeState::CRITICAL) {
+ // This is a hack - we don't know what the actual best
+ // block is, but that doesn't matter for the purposes of
+ // flushing the cache here. We'll set this to its
+ // correct value (`base_blockhash`) below after the
+ // coins are loaded.
+ coins_cache.SetBestBlock(BlockHash{GetRandHash()});
+
+ // No need to acquire cs_main since this chainstate
+ // isn't being used yet.
+ FlushSnapshotToDisk(coins_cache,
+ /*snapshot_loaded=*/false);
+ }
+ }
}
+ } catch (const std::ios_base::failure &) {
+ LogPrintf("[snapshot] bad snapshot format or truncated snapshot "
+ "after deserializing %d coins\n",
+ coins_processed);
+ return false;
}
}
// Important that we set this. This and the coins_cache accesses above are
// sort of a layer violation, but either we reach into the innards of
// CCoinsViewCache here or we have to invert some of the Chainstate to
// embed them in a snapshot-activation-specific CCoinsViewCache bulk load
// method.
coins_cache.SetBestBlock(base_blockhash);
bool out_of_coins{false};
try {
- coins_file >> outpoint;
+ TxId txid;
+ coins_file >> txid;
} catch (const std::ios_base::failure &) {
// We expect an exception since we should be out of coins.
out_of_coins = true;
}
if (!out_of_coins) {
LogPrintf("[snapshot] bad snapshot - coins left over after "
"deserializing %d coins\n",
coins_count);
return false;
}
LogPrintf("[snapshot] loaded %d (%.2f MB) coins from snapshot %s\n",
coins_count, coins_cache.DynamicMemoryUsage() / (1000 * 1000),
base_blockhash.ToString());
// No need to acquire cs_main since this chainstate isn't being used yet.
FlushSnapshotToDisk(coins_cache, /*snapshot_loaded=*/true);
assert(coins_cache.GetBestBlock() == base_blockhash);
// As above, okay to immediately release cs_main here since no other context
// knows about the snapshot_chainstate.
CCoinsViewDB *snapshot_coinsdb =
WITH_LOCK(::cs_main, return &snapshot_chainstate.CoinsDB());
std::optional<CCoinsStats> maybe_stats;
try {
maybe_stats = ComputeUTXOStats(CoinStatsHashType::HASH_SERIALIZED,
snapshot_coinsdb, m_blockman,
SnapshotUTXOHashBreakpoint);
} catch (StopHashingException const &) {
return false;
}
if (!maybe_stats.has_value()) {
LogPrintf("[snapshot] failed to generate coins stats\n");
return false;
}
// Assert that the deserialized chainstate contents match the expected
// assumeutxo value.
if (AssumeutxoHash{maybe_stats->hashSerialized} !=
au_data.hash_serialized) {
LogPrintf("[snapshot] bad snapshot content hash: expected %s, got %s\n",
au_data.hash_serialized.ToString(),
maybe_stats->hashSerialized.ToString());
return false;
}
snapshot_chainstate.m_chain.SetTip(*snapshot_start_block);
// The remainder of this function requires modifying data protected by
// cs_main.
LOCK(::cs_main);
// Fake various pieces of CBlockIndex state:
CBlockIndex *index = nullptr;
// Don't make any modifications to the genesis block since it shouldn't be
// necessary, and since the genesis block doesn't have normal flags like
// BLOCK_VALID_SCRIPTS set.
constexpr int AFTER_GENESIS_START{1};
for (int i = AFTER_GENESIS_START; i <= snapshot_chainstate.m_chain.Height();
++i) {
index = snapshot_chainstate.m_chain[i];
m_blockman.m_dirty_blockindex.insert(index);
// Changes to the block index will be flushed to disk after this call
// returns in `ActivateSnapshot()`, when `MaybeRebalanceCaches()` is
// called, since we've added a snapshot chainstate and therefore will
// have to downsize the IBD chainstate, which will result in a call to
// `FlushStateToDisk(ALWAYS)`.
}
assert(index);
assert(index == snapshot_start_block);
index->nChainTx = au_data.nChainTx;
snapshot_chainstate.setBlockIndexCandidates.insert(snapshot_start_block);
LogPrintf("[snapshot] validated snapshot (%.2f MB)\n",
coins_cache.DynamicMemoryUsage() / (1000 * 1000));
return true;
}
// Currently, this function holds cs_main for its duration, which could be for
// multiple minutes due to the ComputeUTXOStats call. This hold is necessary
// because we need to avoid advancing the background validation chainstate
// farther than the snapshot base block - and this function is also invoked
// from within ConnectTip, i.e. from within ActivateBestChain, so cs_main is
// held anyway.
//
// Eventually (TODO), we could somehow separate this function's runtime from
// maintenance of the active chain, but that will either require
//
// (i) setting `m_disabled` immediately and ensuring all chainstate accesses go
// through IsUsable() checks, or
//
// (ii) giving each chainstate its own lock instead of using cs_main for
// everything.
SnapshotCompletionResult ChainstateManager::MaybeCompleteSnapshotValidation(
std::function<void(bilingual_str)> shutdown_fnc) {
AssertLockHeld(cs_main);
if (m_ibd_chainstate.get() == &this->ActiveChainstate() ||
!this->IsUsable(m_snapshot_chainstate.get()) ||
!this->IsUsable(m_ibd_chainstate.get()) ||
!m_ibd_chainstate->m_chain.Tip()) {
// Nothing to do - this function only applies to the background
// validation chainstate.
return SnapshotCompletionResult::SKIPPED;
}
const int snapshot_tip_height = this->ActiveHeight();
const int snapshot_base_height = *Assert(this->GetSnapshotBaseHeight());
const CBlockIndex &index_new = *Assert(m_ibd_chainstate->m_chain.Tip());
if (index_new.nHeight < snapshot_base_height) {
// Background IBD not complete yet.
return SnapshotCompletionResult::SKIPPED;
}
assert(SnapshotBlockhash());
BlockHash snapshot_blockhash = *Assert(SnapshotBlockhash());
auto handle_invalid_snapshot = [&]() EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
bilingual_str user_error = strprintf(
_("%s failed to validate the -assumeutxo snapshot state. "
"This indicates a hardware problem, or a bug in the software, or "
"a bad software modification that allowed an invalid snapshot to "
"be loaded. As a result of this, the node will shut down and "
"stop using any state that was built on the snapshot, resetting "
"the chain height from %d to %d. On the next restart, the node "
"will resume syncing from %d without using any snapshot data. "
"Please report this incident to %s, including how you obtained "
"the snapshot. The invalid snapshot chainstate will be left on "
"disk in case it is helpful in diagnosing the issue that caused "
"this error."),
PACKAGE_NAME, snapshot_tip_height, snapshot_base_height,
snapshot_base_height, PACKAGE_BUGREPORT);
LogPrintf("[snapshot] !!! %s\n", user_error.original);
LogPrintf("[snapshot] deleting snapshot, reverting to validated chain, "
"and stopping node\n");
m_active_chainstate = m_ibd_chainstate.get();
m_snapshot_chainstate->m_disabled = true;
assert(!this->IsUsable(m_snapshot_chainstate.get()));
assert(this->IsUsable(m_ibd_chainstate.get()));
auto rename_result = m_snapshot_chainstate->InvalidateCoinsDBOnDisk();
if (!rename_result) {
user_error = strprintf(Untranslated("%s\n%s"), user_error,
util::ErrorString(rename_result));
}
shutdown_fnc(user_error);
};
if (index_new.GetBlockHash() != snapshot_blockhash) {
LogPrintf(
"[snapshot] supposed base block %s does not match the "
"snapshot base block %s (height %d). Snapshot is not valid.\n",
index_new.ToString(), snapshot_blockhash.ToString(),
snapshot_base_height);
handle_invalid_snapshot();
return SnapshotCompletionResult::BASE_BLOCKHASH_MISMATCH;
}
assert(index_new.nHeight == snapshot_base_height);
int curr_height = m_ibd_chainstate->m_chain.Height();
assert(snapshot_base_height == curr_height);
assert(snapshot_base_height == index_new.nHeight);
assert(this->IsUsable(m_snapshot_chainstate.get()));
assert(this->GetAll().size() == 2);
CCoinsViewDB &ibd_coins_db = m_ibd_chainstate->CoinsDB();
m_ibd_chainstate->ForceFlushStateToDisk();
const auto &maybe_au_data =
this->GetParams().AssumeutxoForHeight(curr_height);
if (!maybe_au_data) {
LogPrintf("[snapshot] assumeutxo data not found for height "
"(%d) - refusing to validate snapshot\n",
curr_height);
handle_invalid_snapshot();
return SnapshotCompletionResult::MISSING_CHAINPARAMS;
}
const AssumeutxoData &au_data = *maybe_au_data;
std::optional<CCoinsStats> maybe_ibd_stats;
LogPrintf(
"[snapshot] computing UTXO stats for background chainstate to validate "
"snapshot - this could take a few minutes\n");
try {
maybe_ibd_stats =
ComputeUTXOStats(CoinStatsHashType::HASH_SERIALIZED, &ibd_coins_db,
m_blockman, SnapshotUTXOHashBreakpoint);
} catch (StopHashingException const &) {
return SnapshotCompletionResult::STATS_FAILED;
}
if (!maybe_ibd_stats) {
LogPrintf(
"[snapshot] failed to generate stats for validation coins db\n");
// While this isn't a problem with the snapshot per se, this condition
// prevents us from validating the snapshot, so we should shut down and
// let the user handle the issue manually.
handle_invalid_snapshot();
return SnapshotCompletionResult::STATS_FAILED;
}
const auto &ibd_stats = *maybe_ibd_stats;
// Compare the background validation chainstate's UTXO set hash against the
// hard-coded assumeutxo hash we expect.
//
// TODO: For belt-and-suspenders, we could cache the UTXO set
// hash for the snapshot when it's loaded in its chainstate's leveldb. We
// could then reference that here for an additional check.
if (AssumeutxoHash{ibd_stats.hashSerialized} != au_data.hash_serialized) {
LogPrintf("[snapshot] hash mismatch: actual=%s, expected=%s\n",
ibd_stats.hashSerialized.ToString(),
au_data.hash_serialized.ToString());
handle_invalid_snapshot();
return SnapshotCompletionResult::HASH_MISMATCH;
}
LogPrintf("[snapshot] snapshot beginning at %s has been fully validated\n",
snapshot_blockhash.ToString());
m_ibd_chainstate->m_disabled = true;
this->MaybeRebalanceCaches();
return SnapshotCompletionResult::SUCCESS;
}
Chainstate &ChainstateManager::ActiveChainstate() const {
LOCK(::cs_main);
assert(m_active_chainstate);
return *m_active_chainstate;
}
bool ChainstateManager::IsSnapshotActive() const {
LOCK(::cs_main);
return m_snapshot_chainstate &&
m_active_chainstate == m_snapshot_chainstate.get();
}
void ChainstateManager::MaybeRebalanceCaches() {
AssertLockHeld(::cs_main);
bool ibd_usable = this->IsUsable(m_ibd_chainstate.get());
bool snapshot_usable = this->IsUsable(m_snapshot_chainstate.get());
assert(ibd_usable || snapshot_usable);
if (ibd_usable && !snapshot_usable) {
LogPrintf("[snapshot] allocating all cache to the IBD chainstate\n");
// Allocate everything to the IBD chainstate.
m_ibd_chainstate->ResizeCoinsCaches(m_total_coinstip_cache,
m_total_coinsdb_cache);
} else if (snapshot_usable && !ibd_usable) {
// If background validation has completed and snapshot is our active
// chain...
LogPrintf(
"[snapshot] allocating all cache to the snapshot chainstate\n");
// Allocate everything to the snapshot chainstate.
m_snapshot_chainstate->ResizeCoinsCaches(m_total_coinstip_cache,
m_total_coinsdb_cache);
} else if (ibd_usable && snapshot_usable) {
// If both chainstates exist, determine who needs more cache based on
// IBD status.
//
// Note: shrink caches first so that we don't inadvertently overwhelm
// available memory.
if (IsInitialBlockDownload()) {
m_ibd_chainstate->ResizeCoinsCaches(m_total_coinstip_cache * 0.05,
m_total_coinsdb_cache * 0.05);
m_snapshot_chainstate->ResizeCoinsCaches(
m_total_coinstip_cache * 0.95, m_total_coinsdb_cache * 0.95);
} else {
m_snapshot_chainstate->ResizeCoinsCaches(
m_total_coinstip_cache * 0.05, m_total_coinsdb_cache * 0.05);
m_ibd_chainstate->ResizeCoinsCaches(m_total_coinstip_cache * 0.95,
m_total_coinsdb_cache * 0.95);
}
}
}
void ChainstateManager::ResetChainstates() {
m_ibd_chainstate.reset();
m_snapshot_chainstate.reset();
m_active_chainstate = nullptr;
}
/**
* Apply default chain params to nullopt members.
* This helps to avoid coding errors around the accidental use of the compare
* operators that accept nullopt, thus ignoring the intended default value.
*/
static ChainstateManager::Options &&Flatten(ChainstateManager::Options &&opts) {
if (!opts.check_block_index.has_value()) {
opts.check_block_index =
opts.config.GetChainParams().DefaultConsistencyChecks();
}
if (!opts.minimum_chain_work.has_value()) {
opts.minimum_chain_work = UintToArith256(
opts.config.GetChainParams().GetConsensus().nMinimumChainWork);
}
if (!opts.assumed_valid_block.has_value()) {
opts.assumed_valid_block =
opts.config.GetChainParams().GetConsensus().defaultAssumeValid;
}
Assert(opts.adjusted_time_callback);
return std::move(opts);
}
ChainstateManager::ChainstateManager(
Options options, node::BlockManager::Options blockman_options)
: m_options{Flatten(std::move(options))},
m_blockman{std::move(blockman_options)} {}
bool ChainstateManager::DetectSnapshotChainstate(CTxMemPool *mempool) {
assert(!m_snapshot_chainstate);
std::optional<fs::path> path = node::FindSnapshotChainstateDir();
if (!path) {
return false;
}
std::optional<BlockHash> base_blockhash =
node::ReadSnapshotBaseBlockhash(*path);
if (!base_blockhash) {
return false;
}
LogPrintf("[snapshot] detected active snapshot chainstate (%s) - loading\n",
fs::PathToString(*path));
this->ActivateExistingSnapshot(*base_blockhash);
return true;
}
Chainstate &
ChainstateManager::ActivateExistingSnapshot(BlockHash base_blockhash) {
assert(!m_snapshot_chainstate);
m_snapshot_chainstate = std::make_unique<Chainstate>(nullptr, m_blockman,
*this, base_blockhash);
LogPrintf("[snapshot] switching active chainstate to %s\n",
m_snapshot_chainstate->ToString());
// Mempool is empty at this point because we're still in IBD.
Assert(m_active_chainstate->m_mempool->size() == 0);
Assert(!m_snapshot_chainstate->m_mempool);
m_snapshot_chainstate->m_mempool = m_active_chainstate->m_mempool;
m_active_chainstate->m_mempool = nullptr;
m_active_chainstate = m_snapshot_chainstate.get();
return *m_snapshot_chainstate;
}
static fs::path GetSnapshotCoinsDBPath(Chainstate &cs)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
// Should never be called on a non-snapshot chainstate.
assert(cs.m_from_snapshot_blockhash);
auto storage_path_maybe = cs.CoinsDB().StoragePath();
// Should never be called with a non-existent storage path.
assert(storage_path_maybe);
return *storage_path_maybe;
}
util::Result<void> Chainstate::InvalidateCoinsDBOnDisk() {
fs::path snapshot_datadir = GetSnapshotCoinsDBPath(*this);
// Coins views no longer usable.
m_coins_views.reset();
auto invalid_path = snapshot_datadir + "_INVALID";
std::string dbpath = fs::PathToString(snapshot_datadir);
std::string target = fs::PathToString(invalid_path);
LogPrintf("[snapshot] renaming snapshot datadir %s to %s\n", dbpath,
target);
// The invalid snapshot datadir is simply moved and not deleted because we
// may want to do forensics later during issue investigation. The user is
// instructed accordingly in MaybeCompleteSnapshotValidation().
try {
fs::rename(snapshot_datadir, invalid_path);
} catch (const fs::filesystem_error &e) {
auto src_str = fs::PathToString(snapshot_datadir);
auto dest_str = fs::PathToString(invalid_path);
LogPrintf("%s: error renaming file '%s' -> '%s': %s\n", __func__,
src_str, dest_str, e.what());
return util::Error{strprintf(_("Rename of '%s' -> '%s' failed. "
"You should resolve this by manually "
"moving or deleting the invalid "
"snapshot directory %s, otherwise you "
"will encounter the same error again "
"on the next startup."),
src_str, dest_str, src_str)};
}
return {};
}
bool ChainstateManager::DeleteSnapshotChainstate() {
AssertLockHeld(::cs_main);
Assert(m_snapshot_chainstate);
Assert(m_ibd_chainstate);
fs::path snapshot_datadir =
Assert(node::FindSnapshotChainstateDir()).value();
if (!DeleteCoinsDBFromDisk(snapshot_datadir, /*is_snapshot=*/true)) {
LogPrintf("Deletion of %s failed. Please remove it manually to "
"continue reindexing.\n",
fs::PathToString(snapshot_datadir));
return false;
}
m_active_chainstate = m_ibd_chainstate.get();
m_active_chainstate->m_mempool = m_snapshot_chainstate->m_mempool;
m_snapshot_chainstate.reset();
return true;
}
ChainstateRole Chainstate::GetRole() const {
if (m_chainman.GetAll().size() <= 1) {
return ChainstateRole::NORMAL;
}
return (this != &m_chainman.ActiveChainstate())
? ChainstateRole::BACKGROUND
: ChainstateRole::ASSUMEDVALID;
}
const CBlockIndex *ChainstateManager::GetSnapshotBaseBlock() const {
return m_active_chainstate ? m_active_chainstate->SnapshotBase() : nullptr;
}
std::optional<int> ChainstateManager::GetSnapshotBaseHeight() const {
const CBlockIndex *base = this->GetSnapshotBaseBlock();
return base ? std::make_optional(base->nHeight) : std::nullopt;
}
bool ChainstateManager::ValidatedSnapshotCleanup() {
AssertLockHeld(::cs_main);
auto get_storage_path = [](auto &chainstate) EXCLUSIVE_LOCKS_REQUIRED(
::cs_main) -> std::optional<fs::path> {
if (!(chainstate && chainstate->HasCoinsViews())) {
return {};
}
return chainstate->CoinsDB().StoragePath();
};
std::optional<fs::path> ibd_chainstate_path_maybe =
get_storage_path(m_ibd_chainstate);
std::optional<fs::path> snapshot_chainstate_path_maybe =
get_storage_path(m_snapshot_chainstate);
if (!this->IsSnapshotValidated()) {
// No need to clean up.
return false;
}
// If either path doesn't exist, that means at least one of the chainstates
// is in-memory, in which case we can't do on-disk cleanup. You'd better be
// in a unittest!
if (!ibd_chainstate_path_maybe || !snapshot_chainstate_path_maybe) {
LogPrintf("[snapshot] snapshot chainstate cleanup cannot happen with "
"in-memory chainstates. You are testing, right?\n");
return false;
}
const auto &snapshot_chainstate_path = *snapshot_chainstate_path_maybe;
const auto &ibd_chainstate_path = *ibd_chainstate_path_maybe;
// Since we're going to be moving around the underlying leveldb filesystem
// content for each chainstate, make sure that the chainstates (and their
// constituent CoinsViews members) have been destructed first.
//
// The caller of this method will be responsible for reinitializing
// chainstates if they want to continue operation.
this->ResetChainstates();
// No chainstates should be considered usable.
assert(this->GetAll().size() == 0);
LogPrintf("[snapshot] deleting background chainstate directory (now "
"unnecessary) (%s)\n",
fs::PathToString(ibd_chainstate_path));
fs::path tmp_old{ibd_chainstate_path + "_todelete"};
auto rename_failed_abort = [](fs::path p_old, fs::path p_new,
const fs::filesystem_error &err) {
LogPrintf("Error renaming file (%s): %s\n", fs::PathToString(p_old),
err.what());
AbortNode(strprintf(
"Rename of '%s' -> '%s' failed. "
"Cannot clean up the background chainstate leveldb directory.",
fs::PathToString(p_old), fs::PathToString(p_new)));
};
try {
fs::rename(ibd_chainstate_path, tmp_old);
} catch (const fs::filesystem_error &e) {
rename_failed_abort(ibd_chainstate_path, tmp_old, e);
throw;
}
LogPrintf("[snapshot] moving snapshot chainstate (%s) to "
"default chainstate directory (%s)\n",
fs::PathToString(snapshot_chainstate_path),
fs::PathToString(ibd_chainstate_path));
try {
fs::rename(snapshot_chainstate_path, ibd_chainstate_path);
} catch (const fs::filesystem_error &e) {
rename_failed_abort(snapshot_chainstate_path, ibd_chainstate_path, e);
throw;
}
if (!DeleteCoinsDBFromDisk(tmp_old, /*is_snapshot=*/false)) {
// No need to AbortNode because once the unneeded bg chainstate data is
// moved, it will not interfere with subsequent initialization.
LogPrintf("Deletion of %s failed. Please remove it manually, as the "
"directory is now unnecessary.\n",
fs::PathToString(tmp_old));
} else {
LogPrintf("[snapshot] deleted background chainstate directory (%s)\n",
fs::PathToString(ibd_chainstate_path));
}
return true;
}
Chainstate &ChainstateManager::GetChainstateForIndexing() {
// We can't always return `m_ibd_chainstate` because after background
// validation has completed,
// `m_snapshot_chainstate == m_active_chainstate`, but it can be indexed.
return (this->GetAll().size() > 1) ? *m_ibd_chainstate
: *m_active_chainstate;
}
std::pair<int, int>
ChainstateManager::GetPruneRange(const Chainstate &chainstate,
int last_height_can_prune) {
if (chainstate.m_chain.Height() <= 0) {
return {0, 0};
}
int prune_start{0};
if (this->GetAll().size() > 1 &&
m_snapshot_chainstate.get() == &chainstate) {
// Leave the blocks in the background IBD chain alone if we're pruning
// the snapshot chain.
prune_start = *Assert(GetSnapshotBaseHeight()) + 1;
}
int max_prune = std::max<int>(0, chainstate.m_chain.Height() -
static_cast<int>(MIN_BLOCKS_TO_KEEP));
// last block to prune is the lesser of (caller-specified height,
// MIN_BLOCKS_TO_KEEP from the tip)
//
// While you might be tempted to prune the background chainstate more
// aggressively (i.e. fewer MIN_BLOCKS_TO_KEEP), this won't work with index
// building - specifically blockfilterindex requires undo data, and if
// we don't maintain this trailing window, we hit indexing failures.
int prune_end = std::min(last_height_can_prune, max_prune);
return {prune_start, prune_end};
}
diff --git a/src/validation.h b/src/validation.h
index fd9998835..158b8a406 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -1,1655 +1,1661 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2019 The Bitcoin Core developers
// Copyright (c) 2017-2020 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_VALIDATION_H
#define BITCOIN_VALIDATION_H
#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif
#include <arith_uint256.h>
#include <attributes.h>
#include <blockfileinfo.h>
#include <blockindexcomparators.h>
#include <chain.h>
#include <common/bloom.h>
#include <config.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
#include <deploymentstatus.h>
#include <disconnectresult.h>
#include <flatfile.h>
#include <kernel/chain.h>
#include <kernel/chainparams.h>
#include <kernel/chainstatemanager_opts.h>
#include <kernel/cs_main.h>
#include <node/blockstorage.h>
#include <policy/packages.h>
#include <script/script_error.h>
#include <script/script_metrics.h>
#include <shutdown.h>
#include <sync.h>
#include <txdb.h>
#include <txmempool.h> // For CTxMemPool::cs
#include <uint256.h>
#include <util/check.h>
#include <util/fs.h>
#include <util/result.h>
#include <util/translation.h>
#include <atomic>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <thread>
#include <type_traits>
#include <utility>
#include <vector>
class BlockPolicyValidationState;
class CChainParams;
class Chainstate;
class ChainstateManager;
class CScriptCheck;
class CTxMemPool;
class CTxUndo;
class DisconnectedBlockTransactions;
struct ChainTxData;
struct FlatFilePos;
struct PrecomputedTransactionData;
struct LockPoints;
struct AssumeutxoData;
namespace node {
class SnapshotMetadata;
} // namespace node
namespace Consensus {
struct Params;
} // namespace Consensus
namespace avalanche {
class Processor;
} // namespace avalanche
#define MIN_TRANSACTION_SIZE \
(::GetSerializeSize(CTransaction(), PROTOCOL_VERSION))
/** Maximum number of dedicated script-checking threads allowed */
static const int MAX_SCRIPTCHECK_THREADS = 15;
/** -par default (number of script-checking threads, 0 = auto) */
static const int DEFAULT_SCRIPTCHECK_THREADS = 0;
static const bool DEFAULT_PEERBLOOMFILTERS = true;
/** Default for -stopatheight */
static const int DEFAULT_STOPATHEIGHT = 0;
/**
* Block files containing a block-height within MIN_BLOCKS_TO_KEEP of
* ActiveChain().Tip() will not be pruned.
*/
static const unsigned int MIN_BLOCKS_TO_KEEP = 288;
static const signed int DEFAULT_CHECKBLOCKS = 6;
static constexpr int DEFAULT_CHECKLEVEL{3};
/**
* Require that user allocate at least 550 MiB for block & undo files
* (blk???.dat and rev???.dat)
* At 1MB per block, 288 blocks = 288MB.
* Add 15% for Undo data = 331MB
* Add 20% for Orphan block rate = 397MB
* We want the low water mark after pruning to be at least 397 MB and since we
* prune in full block file chunks, we need the high water mark which triggers
* the prune to be one 128MB block file + added 15% undo data = 147MB greater
* for a total of 545MB
* Setting the target to >= 550 MiB will make it likely we can respect the
* target.
*/
static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 550 * 1024 * 1024;
/** Current sync state passed to tip changed callbacks. */
enum class SynchronizationState { INIT_REINDEX, INIT_DOWNLOAD, POST_INIT };
extern GlobalMutex g_best_block_mutex;
extern std::condition_variable g_best_block_cv;
/** Used to notify getblocktemplate RPC of new tips. */
extern const CBlockIndex *g_best_block;
/** Documentation for argument 'checklevel'. */
extern const std::vector<std::string> CHECKLEVEL_DOC;
class BlockValidationOptions {
private:
uint64_t excessiveBlockSize;
bool checkPoW : 1;
bool checkMerkleRoot : 1;
public:
// Do full validation by default
explicit BlockValidationOptions(const Config &config);
explicit BlockValidationOptions(uint64_t _excessiveBlockSize,
bool _checkPow = true,
bool _checkMerkleRoot = true)
: excessiveBlockSize(_excessiveBlockSize), checkPoW(_checkPow),
checkMerkleRoot(_checkMerkleRoot) {}
BlockValidationOptions withCheckPoW(bool _checkPoW = true) const {
BlockValidationOptions ret = *this;
ret.checkPoW = _checkPoW;
return ret;
}
BlockValidationOptions
withCheckMerkleRoot(bool _checkMerkleRoot = true) const {
BlockValidationOptions ret = *this;
ret.checkMerkleRoot = _checkMerkleRoot;
return ret;
}
bool shouldValidatePoW() const { return checkPoW; }
bool shouldValidateMerkleRoot() const { return checkMerkleRoot; }
uint64_t getExcessiveBlockSize() const { return excessiveBlockSize; }
};
/**
* Run instances of script checking worker threads
*/
void StartScriptCheckWorkerThreads(int threads_num);
/**
* Stop all of the script checking worker threads
*/
void StopScriptCheckWorkerThreads();
Amount GetBlockSubsidy(int nHeight, const Consensus::Params &consensusParams);
bool AbortNode(BlockValidationState &state, const std::string &strMessage,
const bilingual_str &userMessage = bilingual_str{});
/**
* Guess verification progress (as a fraction between 0.0=genesis and
* 1.0=current tip).
*/
double GuessVerificationProgress(const ChainTxData &data,
const CBlockIndex *pindex);
/** Prune block files up to a given height */
void PruneBlockFilesManual(Chainstate &active_chainstate,
int nManualPruneHeight);
// clang-format off
/**
* Validation result for a transaction evaluated by MemPoolAccept (single or
* package).
* Here are the expected fields and properties of a result depending on its
* ResultType, applicable to results returned from package evaluation:
*+--------------------------+-----------+-------------------------------------------+---------------+
*| Field or property | VALID | INVALID | MEMPOOL_ENTRY |
*| | |-------------------------------------------| |
*| | | TX_PACKAGE_RECONSIDERABLE | Other | |
*+--------------------------+-----------+---------------------------+---------------+---------------+
*| txid in mempool? | yes | no | no* | yes |
*| m_state | IsValid() | IsInvalid() | IsInvalid() | IsValid() |
*| m_replaced_transactions | yes | no | no | no |
*| m_vsize | yes | no | no | yes |
*| m_base_fees | yes | no | no | yes |
*| m_effective_feerate | yes | yes | no | no |
*| m_txids_fee_calculations | yes | yes | no | no |
*+--------------------------+-----------+---------------------------+---------------+---------------+
* (*) Individual transaction acceptance doesn't return MEMPOOL_ENTRY. It
* returns INVALID, with the error txn-already-in-mempool. In this case, the
* txid may be in the mempool for a tx conflict (TX_AVALANCHE_RECONSIDERABLE).
*/
// clang-format on
struct MempoolAcceptResult {
/** Used to indicate the results of mempool validation. */
enum class ResultType {
//! Fully validated, valid.
VALID,
//! Invalid.
INVALID,
//! Valid, transaction was already in the mempool.
MEMPOOL_ENTRY,
};
/** Result type. Present in all MempoolAcceptResults. */
const ResultType m_result_type;
/** Contains information about why the transaction failed. */
const TxValidationState m_state;
/**
* Virtual size as used by the mempool, calculated using serialized size
* and sigchecks.
*/
const std::optional<int64_t> m_vsize;
/** Raw base fees in satoshis. */
const std::optional<Amount> m_base_fees;
/**
* The feerate at which this transaction was considered. This includes any
* fee delta added using prioritisetransaction (i.e. modified fees). If this
* transaction was submitted as a package, this is the package feerate,
* which may also include its descendants and/or ancestors
* (see m_txids_fee_calculations below).
*/
const std::optional<CFeeRate> m_effective_feerate;
/**
* Contains the txids of the transactions used for fee-related checks.
* Includes this transaction's txid and may include others if this
* transaction was validated as part of a package. This is not necessarily
* equivalent to the list of transactions passed to ProcessNewPackage().
*/
const std::optional<std::vector<TxId>> m_txids_fee_calculations;
static MempoolAcceptResult Failure(TxValidationState state) {
return MempoolAcceptResult(state);
}
static MempoolAcceptResult
FeeFailure(TxValidationState state, CFeeRate effective_feerate,
const std::vector<TxId> &txids_fee_calculations) {
return MempoolAcceptResult(state, effective_feerate,
txids_fee_calculations);
}
/** Constructor for success case */
static MempoolAcceptResult
Success(int64_t vsize, Amount fees, CFeeRate effective_feerate,
const std::vector<TxId> &txids_fee_calculations) {
return MempoolAcceptResult(ResultType::VALID, vsize, fees,
effective_feerate, txids_fee_calculations);
}
/**
* Constructor for already-in-mempool case. It wouldn't replace any
* transactions.
*/
static MempoolAcceptResult MempoolTx(int64_t vsize, Amount fees) {
return MempoolAcceptResult(vsize, fees);
}
// Private constructors. Use static methods MempoolAcceptResult::Success,
// etc. to construct.
private:
/** Constructor for failure case */
explicit MempoolAcceptResult(TxValidationState state)
: m_result_type(ResultType::INVALID), m_state(state),
m_base_fees(std::nullopt) {
// Can be invalid or error
Assume(!state.IsValid());
}
/** Generic constructor for success cases */
explicit MempoolAcceptResult(
ResultType result_type, int64_t vsize, Amount fees,
CFeeRate effective_feerate,
const std::vector<TxId> &txids_fee_calculations)
: m_result_type(result_type), m_vsize{vsize}, m_base_fees(fees),
m_effective_feerate(effective_feerate),
m_txids_fee_calculations(txids_fee_calculations) {}
/** Constructor for fee-related failure case */
explicit MempoolAcceptResult(
TxValidationState state, CFeeRate effective_feerate,
const std::vector<TxId> &txids_fee_calculations)
: m_result_type(ResultType::INVALID), m_state(state),
m_effective_feerate(effective_feerate),
m_txids_fee_calculations(txids_fee_calculations) {}
/** Constructor for already-in-mempool case. */
explicit MempoolAcceptResult(int64_t vsize, Amount fees)
: m_result_type(ResultType::MEMPOOL_ENTRY), m_vsize{vsize},
m_base_fees(fees) {}
};
/**
* Validation result for package mempool acceptance.
*/
struct PackageMempoolAcceptResult {
PackageValidationState m_state;
/**
* Map from txid to finished MempoolAcceptResults. The client is
* responsible for keeping track of the transaction objects themselves.
* If a result is not present, it means validation was unfinished for that
* transaction. If there was a package-wide error (see result in m_state),
* m_tx_results will be empty.
*/
std::map<TxId, MempoolAcceptResult> m_tx_results;
explicit PackageMempoolAcceptResult(
PackageValidationState state,
std::map<TxId, MempoolAcceptResult> &&results)
: m_state{state}, m_tx_results(std::move(results)) {}
/**
* Constructor to create a PackageMempoolAcceptResult from a
* MempoolAcceptResult
*/
explicit PackageMempoolAcceptResult(const TxId &txid,
const MempoolAcceptResult &result)
: m_tx_results{{txid, result}} {}
};
/**
* Try to add a transaction to the mempool. This is an internal function and is
* exposed only for testing. Client code should use
* ChainstateManager::ProcessTransaction()
*
* @param[in] active_chainstate Reference to the active chainstate.
* @param[in] tx The transaction to submit for mempool
* acceptance.
* @param[in] accept_time The timestamp for adding the transaction to
* the mempool.
* It is also used to determine when the entry
* expires.
* @param[in] bypass_limits When true, don't enforce mempool fee and
* capacity limits.
* @param[in] test_accept When true, run validation checks but don't
* submit to mempool.
* @param[in] heightOverride Override the block height of the transaction.
* Used only upon reorg.
*
* @returns a MempoolAcceptResult indicating whether the transaction was
* accepted/rejected with reason.
*/
MempoolAcceptResult
AcceptToMemoryPool(Chainstate &active_chainstate, const CTransactionRef &tx,
int64_t accept_time, bool bypass_limits,
bool test_accept = false, unsigned int heightOverride = 0)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Validate (and maybe submit) a package to the mempool.
* See doc/policy/packages.md for full detailson package validation rules.
*
* @param[in] test_accept When true, run validation checks but don't
* submit to mempool.
* @returns a PackageMempoolAcceptResult which includes a MempoolAcceptResult
* for each transaction. If a transaction fails, validation will exit early
* and some results may be missing. It is also possible for the package to
* be partially submitted.
*/
PackageMempoolAcceptResult
ProcessNewPackage(Chainstate &active_chainstate, CTxMemPool &pool,
const Package &txns, bool test_accept)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Simple class for regulating resource usage during CheckInputScripts (and
* CScriptCheck), atomic so as to be compatible with parallel validation.
*/
class CheckInputsLimiter {
protected:
std::atomic<int64_t> remaining;
public:
explicit CheckInputsLimiter(int64_t limit) : remaining(limit) {}
bool consume_and_check(int consumed) {
auto newvalue = (remaining -= consumed);
return newvalue >= 0;
}
bool check() { return remaining >= 0; }
};
class TxSigCheckLimiter : public CheckInputsLimiter {
public:
TxSigCheckLimiter() : CheckInputsLimiter(MAX_TX_SIGCHECKS) {}
// Let's make this bad boy copiable.
TxSigCheckLimiter(const TxSigCheckLimiter &rhs)
: CheckInputsLimiter(rhs.remaining.load()) {}
TxSigCheckLimiter &operator=(const TxSigCheckLimiter &rhs) {
remaining = rhs.remaining.load();
return *this;
}
static TxSigCheckLimiter getDisabled() {
TxSigCheckLimiter txLimiter;
// Historically, there has not been a transaction with more than 20k sig
// checks on testnet or mainnet, so this effectively disable sigchecks.
txLimiter.remaining = 20000;
return txLimiter;
}
};
/**
* Check whether all of this transaction's input scripts succeed.
*
* This involves ECDSA signature checks so can be computationally intensive.
* This function should only be called after the cheap sanity checks in
* CheckTxInputs passed.
*
* If pvChecks is not nullptr, script checks are pushed onto it instead of being
* performed inline. Any script checks which are not necessary (eg due to script
* execution cache hits) are, obviously, not pushed onto pvChecks/run.
*
* Upon success nSigChecksOut will be filled in with either:
* - correct total for all inputs, or,
* - 0, in the case when checks were pushed onto pvChecks (i.e., a cache miss
* with pvChecks non-null), in which case the total can be found by executing
* pvChecks and adding the results.
*
* Setting sigCacheStore/scriptCacheStore to false will remove elements from the
* corresponding cache which are matched. This is useful for checking blocks
* where we will likely never need the cache entry again.
*
* pLimitSigChecks can be passed to limit the sigchecks count either in parallel
* or serial validation. With pvChecks null (serial validation), breaking the
* pLimitSigChecks limit will abort evaluation early and return false. With
* pvChecks not-null (parallel validation): the cached nSigChecks may itself
* break the limit in which case false is returned, OR, each entry in the
* returned pvChecks must be executed exactly once in order to probe the limit
* accurately.
*/
bool CheckInputScripts(const CTransaction &tx, TxValidationState &state,
const CCoinsViewCache &view, const uint32_t flags,
bool sigCacheStore, bool scriptCacheStore,
const PrecomputedTransactionData &txdata,
int &nSigChecksOut, TxSigCheckLimiter &txLimitSigChecks,
CheckInputsLimiter *pBlockLimitSigChecks,
std::vector<CScriptCheck> *pvChecks)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Handy shortcut to full fledged CheckInputScripts call.
*/
static inline bool
CheckInputScripts(const CTransaction &tx, TxValidationState &state,
const CCoinsViewCache &view, const uint32_t flags,
bool sigCacheStore, bool scriptCacheStore,
const PrecomputedTransactionData &txdata, int &nSigChecksOut)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
TxSigCheckLimiter nSigChecksTxLimiter;
return CheckInputScripts(tx, state, view, flags, sigCacheStore,
scriptCacheStore, txdata, nSigChecksOut,
nSigChecksTxLimiter, nullptr, nullptr);
}
/**
* Mark all the coins corresponding to a given transaction inputs as spent.
*/
void SpendCoins(CCoinsViewCache &view, const CTransaction &tx, CTxUndo &txundo,
int nHeight);
/**
* Apply the effects of this transaction on the UTXO set represented by view.
*/
void UpdateCoins(CCoinsViewCache &view, const CTransaction &tx, CTxUndo &txundo,
int nHeight);
/**
* Calculate LockPoints required to check if transaction will be BIP68 final in
* the next block to be created on top of tip.
*
* @param[in] tip Chain tip for which tx sequence locks are
* calculated. For example, the tip of the current active chain.
* @param[in] coins_view Any CCoinsView that provides access to the
* relevant coins for checking sequence locks. For example, it can be a
* CCoinsViewCache that isn't connected to anything but contains all the
* relevant coins, or a CCoinsViewMemPool that is connected to the mempool
* and chainstate UTXO set. In the latter case, the caller is responsible
* for holding the appropriate locks to ensure that calls to GetCoin()
* return correct coins.
* @param[in] tx The transaction being evaluated.
*
* @returns The resulting height and time calculated and the hash of the block
* needed for calculation, or std::nullopt if there is an error.
*/
std::optional<LockPoints> CalculateLockPointsAtTip(CBlockIndex *tip,
const CCoinsView &coins_view,
const CTransaction &tx);
/**
* Check if transaction will be BIP68 final in the next block to be created on
* top of tip.
* @param[in] tip Chain tip to check tx sequence locks against.
* For example, the tip of the current active chain.
* @param[in] lock_points LockPoints containing the height and time at
* which this transaction is final.
* Simulates calling SequenceLocks() with data from the tip passed in.
*/
bool CheckSequenceLocksAtTip(CBlockIndex *tip, const LockPoints &lock_points);
/**
* Closure representing one script verification.
* Note that this stores references to the spending transaction.
*
* Note that if pLimitSigChecks is passed, then failure does not imply that
* scripts have failed.
*/
class CScriptCheck {
private:
CTxOut m_tx_out;
const CTransaction *ptxTo;
unsigned int nIn;
uint32_t nFlags;
bool cacheStore;
ScriptError error{ScriptError::UNKNOWN};
ScriptExecutionMetrics metrics;
PrecomputedTransactionData txdata;
TxSigCheckLimiter *pTxLimitSigChecks;
CheckInputsLimiter *pBlockLimitSigChecks;
public:
CScriptCheck(const CTxOut &outIn, const CTransaction &txToIn,
unsigned int nInIn, uint32_t nFlagsIn, bool cacheIn,
const PrecomputedTransactionData &txdataIn,
TxSigCheckLimiter *pTxLimitSigChecksIn = nullptr,
CheckInputsLimiter *pBlockLimitSigChecksIn = nullptr)
: m_tx_out(outIn), ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn),
cacheStore(cacheIn), txdata(txdataIn),
pTxLimitSigChecks(pTxLimitSigChecksIn),
pBlockLimitSigChecks(pBlockLimitSigChecksIn) {}
CScriptCheck(const CScriptCheck &) = delete;
CScriptCheck &operator=(const CScriptCheck &) = delete;
CScriptCheck(CScriptCheck &&) = default;
CScriptCheck &operator=(CScriptCheck &&) = default;
bool operator()();
ScriptError GetScriptError() const { return error; }
ScriptExecutionMetrics GetScriptExecutionMetrics() const { return metrics; }
};
// CScriptCheck is used a lot in std::vector, make sure that's efficient
static_assert(std::is_nothrow_move_assignable_v<CScriptCheck>);
static_assert(std::is_nothrow_move_constructible_v<CScriptCheck>);
static_assert(std::is_nothrow_destructible_v<CScriptCheck>);
/** Functions for validating blocks and updating the block tree */
/**
* Context-independent validity checks.
*
* Returns true if the provided block is valid (has valid header,
* transactions are valid, block is a valid size, etc.)
*/
bool CheckBlock(const CBlock &block, BlockValidationState &state,
const Consensus::Params &params,
BlockValidationOptions validationOptions);
/**
* This is a variant of ContextualCheckTransaction which computes the contextual
* check for a transaction based on the chain tip.
*
* See consensus/consensus.h for flag definitions.
*/
bool ContextualCheckTransactionForCurrentBlock(
const CBlockIndex &active_chain_tip, const Consensus::Params &params,
const CTransaction &tx, TxValidationState &state)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/**
* Check a block is completely valid from start to finish (only works on top of
* our current best block)
*/
bool TestBlockValidity(
BlockValidationState &state, const CChainParams &params,
Chainstate &chainstate, const CBlock &block, CBlockIndex *pindexPrev,
const std::function<NodeClock::time_point()> &adjusted_time_callback,
BlockValidationOptions validationOptions) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Check with the proof of work on each blockheader matches the value in nBits
*/
bool HasValidProofOfWork(const std::vector<CBlockHeader> &headers,
const Consensus::Params &consensusParams);
/** Return the sum of the work on a given set of headers */
arith_uint256 CalculateHeadersWork(const std::vector<CBlockHeader> &headers);
enum class VerifyDBResult {
SUCCESS,
CORRUPTED_BLOCK_DB,
INTERRUPTED,
SKIPPED_L3_CHECKS,
SKIPPED_MISSING_BLOCKS,
};
/**
* RAII wrapper for VerifyDB: Verify consistency of the block and coin
* databases.
*/
class CVerifyDB {
private:
kernel::Notifications &m_notifications;
public:
CVerifyDB();
public:
explicit CVerifyDB(kernel::Notifications &notifications);
~CVerifyDB();
[[nodiscard]] VerifyDBResult VerifyDB(Chainstate &chainstate,
CCoinsView &coinsview,
int nCheckLevel, int nCheckDepth)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
};
/** @see Chainstate::FlushStateToDisk */
enum class FlushStateMode { NONE, IF_NEEDED, PERIODIC, ALWAYS };
/**
* A convenience class for constructing the CCoinsView* hierarchy used
* to facilitate access to the UTXO set.
*
* This class consists of an arrangement of layered CCoinsView objects,
* preferring to store and retrieve coins in memory via `m_cacheview` but
* ultimately falling back on cache misses to the canonical store of UTXOs on
* disk, `m_dbview`.
*/
class CoinsViews {
public:
//! The lowest level of the CoinsViews cache hierarchy sits in a leveldb
//! database on disk. All unspent coins reside in this store.
CCoinsViewDB m_dbview GUARDED_BY(cs_main);
//! This view wraps access to the leveldb instance and handles read errors
//! gracefully.
CCoinsViewErrorCatcher m_catcherview GUARDED_BY(cs_main);
//! This is the top layer of the cache hierarchy - it keeps as many coins in
//! memory as can fit per the dbcache setting.
std::unique_ptr<CCoinsViewCache> m_cacheview GUARDED_BY(cs_main);
//! This constructor initializes CCoinsViewDB and CCoinsViewErrorCatcher
//! instances, but it *does not* create a CCoinsViewCache instance by
//! default. This is done separately because the presence of the cache has
//! implications on whether or not we're allowed to flush the cache's state
//! to disk, which should not be done until the health of the database is
//! verified.
//!
//! All arguments forwarded onto CCoinsViewDB.
CoinsViews(DBParams db_params, CoinsViewOptions options);
//! Initialize the CCoinsViewCache member.
void InitCache() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
};
enum class CoinsCacheSizeState {
//! The coins cache is in immediate need of a flush.
CRITICAL = 2,
//! The cache is at >= 90% capacity.
LARGE = 1,
OK = 0
};
/**
* Chainstate stores and provides an API to update our local knowledge of the
* current best chain.
*
* Eventually, the API here is targeted at being exposed externally as a
* consumable libconsensus library, so any functions added must only call
* other class member functions, pure functions in other parts of the consensus
* library, callbacks via the validation interface, or read/write-to-disk
* functions (eventually this will also be via callbacks).
*
* Anything that is contingent on the current tip of the chain is stored here,
* whereas block information and metadata independent of the current tip is
* kept in `BlockManager`.
*/
class Chainstate {
protected:
/**
* The ChainState Mutex.
* A lock that must be held when modifying this ChainState.
*/
Mutex m_chainstate_mutex;
//! Optional mempool that is kept in sync with the chain.
//! Only the active chainstate has a mempool.
CTxMemPool *m_mempool;
//! Manages the UTXO set, which is a reflection of the contents of
//! `m_chain`.
std::unique_ptr<CoinsViews> m_coins_views;
//! This toggle exists for use when doing background validation for UTXO
//! snapshots.
//!
//! In the expected case, it is set once the background validation chain
//! reaches the same height as the base of the snapshot and its UTXO set is
//! found to hash to the expected assumeutxo value. It signals that we
//! should no longer connect blocks to the background chainstate. When set
//! on the background validation chainstate, it signifies that we have fully
//! validated the snapshot chainstate.
//!
//! In the unlikely case that the snapshot chainstate is found to be
//! invalid, this is set to true on the snapshot chainstate.
bool m_disabled GUARDED_BY(::cs_main){false};
mutable Mutex cs_avalancheFinalizedBlockIndex;
/**
* The best block via avalanche voting.
* This block cannot be reorged in any way except by explicit user action.
*/
const CBlockIndex *m_avalancheFinalizedBlockIndex
GUARDED_BY(cs_avalancheFinalizedBlockIndex) = nullptr;
/**
* Filter to prevent parking a block due to block policies more than once.
* After first application of block policies, Avalanche voting will
* determine the final acceptance state. Rare false positives will be
* reconciled by the network and should not have any negative impact.
*/
CRollingBloomFilter m_filterParkingPoliciesApplied =
CRollingBloomFilter{1000, 0.000001};
CBlockIndex const *m_best_fork_tip = nullptr;
CBlockIndex const *m_best_fork_base = nullptr;
//! Cached result of LookupBlockIndex(*m_from_snapshot_blockhash)
const CBlockIndex *m_cached_snapshot_base GUARDED_BY(::cs_main){nullptr};
public:
//! Reference to a BlockManager instance which itself is shared across all
//! Chainstate instances.
node::BlockManager &m_blockman;
//! The chainstate manager that owns this chainstate. The reference is
//! necessary so that this instance can check whether it is the active
//! chainstate within deeply nested method calls.
ChainstateManager &m_chainman;
explicit Chainstate(
CTxMemPool *mempool, node::BlockManager &blockman,
ChainstateManager &chainman,
std::optional<BlockHash> from_snapshot_blockhash = std::nullopt);
//! Return the current role of the chainstate. See `ChainstateManager`
//! documentation for a description of the different types of chainstates.
//!
//! @sa ChainstateRole
ChainstateRole GetRole() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/**
* Initialize the CoinsViews UTXO set database management data structures.
* The in-memory cache is initialized separately.
*
* All parameters forwarded to CoinsViews.
*/
void InitCoinsDB(size_t cache_size_bytes, bool in_memory, bool should_wipe,
std::string leveldb_name = "chainstate");
//! Initialize the in-memory coins cache (to be done after the health of the
//! on-disk database is verified).
void InitCoinsCache(size_t cache_size_bytes)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! @returns whether or not the CoinsViews object has been fully initialized
//! and we can
//! safely flush this object to disk.
bool CanFlushToDisk() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
return m_coins_views && m_coins_views->m_cacheview;
}
//! The current chain of blockheaders we consult and build on.
//! @see CChain, CBlockIndex.
CChain m_chain;
/**
* The blockhash which is the base of the snapshot this chainstate was
* created from.
*
* std::nullopt if this chainstate was not created from a snapshot.
*/
const std::optional<BlockHash> m_from_snapshot_blockhash{};
/**
* The base of the snapshot this chainstate was created from.
*
* nullptr if this chainstate was not created from a snapshot.
*/
const CBlockIndex *SnapshotBase() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/**
* The set of all CBlockIndex entries that have as much work as our current
* tip or more, and transaction data needed to be validated (with
* BLOCK_VALID_TRANSACTIONS for each block and its parents back to the
* genesis block or an assumeutxo snapshot block). Entries may be failed,
* though, and pruning nodes may be missing the data for the block.
*/
std::set<CBlockIndex *, CBlockIndexWorkComparator> setBlockIndexCandidates;
//! @returns A reference to the in-memory cache of the UTXO set.
CCoinsViewCache &CoinsTip() EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
Assert(m_coins_views);
return *Assert(m_coins_views->m_cacheview);
}
//! @returns A reference to the on-disk UTXO set database.
CCoinsViewDB &CoinsDB() EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
return Assert(m_coins_views)->m_dbview;
}
//! @returns A pointer to the mempool.
CTxMemPool *GetMempool() { return m_mempool; }
//! @returns A reference to a wrapped view of the in-memory UTXO set that
//! handles disk read errors gracefully.
CCoinsViewErrorCatcher &CoinsErrorCatcher()
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
AssertLockHeld(::cs_main);
return Assert(m_coins_views)->m_catcherview;
}
//! Destructs all objects related to accessing the UTXO set.
void ResetCoinsViews() { m_coins_views.reset(); }
//! Does this chainstate have a UTXO set attached?
bool HasCoinsViews() const { return (bool)m_coins_views; }
//! The cache size of the on-disk coins view.
size_t m_coinsdb_cache_size_bytes{0};
//! The cache size of the in-memory coins view.
size_t m_coinstip_cache_size_bytes{0};
//! Resize the CoinsViews caches dynamically and flush state to disk.
//! @returns true unless an error occurred during the flush.
bool ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/**
* Update the on-disk chain state.
* The caches and indexes are flushed depending on the mode we're called
* with if they're too large, if it's been a while since the last write, or
* always and in all cases if we're in prune mode and are deleting files.
*
* If FlushStateMode::NONE is used, then FlushStateToDisk(...) won't do
* anything besides checking if we need to prune.
*
* @returns true unless a system error occurred
*/
bool FlushStateToDisk(BlockValidationState &state, FlushStateMode mode,
int nManualPruneHeight = 0);
//! Unconditionally flush all changes to disk.
void ForceFlushStateToDisk();
//! Prune blockfiles from the disk if necessary and then flush chainstate
//! changes if we pruned.
void PruneAndFlush();
/**
* Find the best known block, and make it the tip of the block chain. The
* result is either failure or an activated best chain. pblock is either
* nullptr or a pointer to a block that is already loaded (to avoid loading
* it again from disk).
*
* ActivateBestChain is split into steps (see ActivateBestChainStep) so that
* we avoid holding cs_main for an extended period of time; the length of
* this call may be quite long during reindexing or a substantial reorg.
*
* May not be called with cs_main held. May not be called in a
* validationinterface callback.
*
* Note that if this is called while a snapshot chainstate is active, and if
* it is called on a background chainstate whose tip has reached the base
* block of the snapshot, its execution will take *MINUTES* while it hashes
* the background UTXO set to verify the assumeutxo value the snapshot was
* activated with. `cs_main` will be held during this time.
* @returns true unless a system error occurred
*/
bool ActivateBestChain(BlockValidationState &state,
std::shared_ptr<const CBlock> pblock = nullptr,
avalanche::Processor *const avalanche = nullptr)
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex,
!cs_avalancheFinalizedBlockIndex)
LOCKS_EXCLUDED(cs_main);
// Block (dis)connection on a given view:
DisconnectResult DisconnectBlock(const CBlock &block,
const CBlockIndex *pindex,
CCoinsViewCache &view)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
bool ConnectBlock(const CBlock &block, BlockValidationState &state,
CBlockIndex *pindex, CCoinsViewCache &view,
BlockValidationOptions options,
Amount *blockFees = nullptr, bool fJustCheck = false)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
// Apply the effects of a block disconnection on the UTXO set.
bool DisconnectTip(BlockValidationState &state,
DisconnectedBlockTransactions *disconnectpool)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
// Manual block validity manipulation:
/**
* Mark a block as precious and reorganize.
*
* May not be called in a validationinterface callback.
*/
bool PreciousBlock(BlockValidationState &state, CBlockIndex *pindex,
avalanche::Processor *const avalanche = nullptr)
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex,
!cs_avalancheFinalizedBlockIndex)
LOCKS_EXCLUDED(cs_main);
/** Mark a block as invalid. */
bool InvalidateBlock(BlockValidationState &state, CBlockIndex *pindex)
LOCKS_EXCLUDED(cs_main)
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex,
!cs_avalancheFinalizedBlockIndex);
/** Park a block. */
bool ParkBlock(BlockValidationState &state, CBlockIndex *pindex)
LOCKS_EXCLUDED(cs_main)
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex,
!cs_avalancheFinalizedBlockIndex);
/**
* Mark a block as finalized by avalanche.
*/
bool AvalancheFinalizeBlock(CBlockIndex *pindex,
avalanche::Processor &avalanche)
EXCLUSIVE_LOCKS_REQUIRED(!cs_avalancheFinalizedBlockIndex);
/**
* Clear avalanche finalization.
*/
void ClearAvalancheFinalizedBlock()
EXCLUSIVE_LOCKS_REQUIRED(!cs_avalancheFinalizedBlockIndex);
/**
* Checks if a block is finalized by avalanche voting.
*/
bool IsBlockAvalancheFinalized(const CBlockIndex *pindex) const
EXCLUSIVE_LOCKS_REQUIRED(!cs_avalancheFinalizedBlockIndex);
/** Remove invalidity status from a block and its descendants. */
void ResetBlockFailureFlags(CBlockIndex *pindex)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
template <typename F>
bool UpdateFlagsForBlock(CBlockIndex *pindexBase, CBlockIndex *pindex, F f)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
template <typename F, typename C, typename AC>
void UpdateFlags(CBlockIndex *pindex, CBlockIndex *&pindexReset, F f,
C fChild, AC fAncestorWasChanged)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Remove parked status from a block and its descendants. */
void UnparkBlockAndChildren(CBlockIndex *pindex)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Remove parked status from a block. */
void UnparkBlock(CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Replay blocks that aren't fully applied to the database. */
bool ReplayBlocks();
/**
* Ensures we have a genesis block in the block tree, possibly writing one
* to disk.
*/
bool LoadGenesisBlock();
void TryAddBlockIndexCandidate(CBlockIndex *pindex)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
void PruneBlockIndexCandidates();
void ClearBlockIndexCandidates() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/** Find the last common block of this chain and a locator. */
const CBlockIndex *FindForkInGlobalIndex(const CBlockLocator &locator) const
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Load the persisted mempool from disk */
void
LoadMempool(const fs::path &load_path,
fsbridge::FopenFn mockable_fopen_function = fsbridge::fopen);
/** Update the chain tip based on database information, i.e. CoinsTip()'s
* best block. */
bool LoadChainTip() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
//! Dictates whether we need to flush the cache to disk or not.
//!
//! @return the state of the size of the coins cache.
CoinsCacheSizeState GetCoinsCacheSizeState()
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
CoinsCacheSizeState
GetCoinsCacheSizeState(size_t max_coins_cache_size_bytes,
size_t max_mempool_size_bytes)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
std::string ToString() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Indirection necessary to make lock annotations work with an optional
//! mempool.
RecursiveMutex *MempoolMutex() const LOCK_RETURNED(m_mempool->cs) {
return m_mempool ? &m_mempool->cs : nullptr;
}
private:
bool ActivateBestChainStep(
BlockValidationState &state, CBlockIndex *pindexMostWork,
const std::shared_ptr<const CBlock> &pblock, bool &fInvalidFound,
const avalanche::Processor *const avalanche = nullptr,
ChainstateRole = ChainstateRole::NORMAL)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs,
!cs_avalancheFinalizedBlockIndex);
bool ConnectTip(BlockValidationState &state,
BlockPolicyValidationState &blockPolicyState,
CBlockIndex *pindexNew,
const std::shared_ptr<const CBlock> &pblock,
DisconnectedBlockTransactions &disconnectpool,
const avalanche::Processor *const avalanche = nullptr,
ChainstateRole chainstate_role = ChainstateRole::NORMAL)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs,
!cs_avalancheFinalizedBlockIndex);
void InvalidBlockFound(CBlockIndex *pindex,
const BlockValidationState &state)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, !cs_avalancheFinalizedBlockIndex);
CBlockIndex *
FindMostWorkChain(std::vector<const CBlockIndex *> &blocksToReconcile,
bool fAutoUnpark)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, !cs_avalancheFinalizedBlockIndex);
bool RollforwardBlock(const CBlockIndex *pindex, CCoinsViewCache &inputs)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
void UnparkBlockImpl(CBlockIndex *pindex, bool fClearChildren)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
bool UnwindBlock(BlockValidationState &state, CBlockIndex *pindex,
bool invalidate)
EXCLUSIVE_LOCKS_REQUIRED(m_chainstate_mutex,
!cs_avalancheFinalizedBlockIndex);
void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
void CheckForkWarningConditionsOnNewFork(CBlockIndex *pindexNewForkTip)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
void InvalidChainFound(CBlockIndex *pindexNew)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, !cs_avalancheFinalizedBlockIndex);
const CBlockIndex *FindBlockToFinalize(CBlockIndex *pindexNew)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Check warning conditions and do some notifications on new chain tip set.
*/
void UpdateTip(const CBlockIndex *pindexNew)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
std::chrono::microseconds m_last_write{0};
std::chrono::microseconds m_last_flush{0};
/**
* In case of an invalid snapshot, rename the coins leveldb directory so
* that it can be examined for issue diagnosis.
*/
[[nodiscard]] util::Result<void> InvalidateCoinsDBOnDisk()
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
friend ChainstateManager;
};
enum class SnapshotCompletionResult {
SUCCESS,
SKIPPED,
// Expected assumeutxo configuration data is not found for the height of the
// base block.
MISSING_CHAINPARAMS,
// Failed to generate UTXO statistics (to check UTXO set hash) for the
// background chainstate.
STATS_FAILED,
// The UTXO set hash of the background validation chainstate does not match
// the one expected by assumeutxo chainparams.
HASH_MISMATCH,
// The blockhash of the current tip of the background validation chainstate
// does not match the one expected by the snapshot chainstate.
BASE_BLOCKHASH_MISMATCH,
};
/**
* Provides an interface for creating and interacting with one or two
* chainstates: an IBD chainstate generated by downloading blocks, and
* an optional snapshot chainstate loaded from a UTXO snapshot. Managed
* chainstates can be maintained at different heights simultaneously.
*
* This class provides abstractions that allow the retrieval of the current
* most-work chainstate ("Active") as well as chainstates which may be in
* background use to validate UTXO snapshots.
*
* Definitions:
*
* *IBD chainstate*: a chainstate whose current state has been "fully"
* validated by the initial block download process.
*
* *Snapshot chainstate*: a chainstate populated by loading in an
* assumeutxo UTXO snapshot.
*
* *Active chainstate*: the chainstate containing the current most-work
* chain. Consulted by most parts of the system (net_processing,
* wallet) as a reflection of the current chain and UTXO set.
* This may either be an IBD chainstate or a snapshot chainstate.
*
* *Background IBD chainstate*: an IBD chainstate for which the
* IBD process is happening in the background while use of the
* active (snapshot) chainstate allows the rest of the system to function.
*/
class ChainstateManager {
private:
//! The chainstate used under normal operation (i.e. "regular" IBD) or, if
//! a snapshot is in use, for background validation.
//!
//! Its contents (including on-disk data) will be deleted *upon shutdown*
//! after background validation of the snapshot has completed. We do not
//! free the chainstate contents immediately after it finishes validation
//! to cautiously avoid a case where some other part of the system is still
//! using this pointer (e.g. net_processing).
//!
//! Once this pointer is set to a corresponding chainstate, it will not
//! be reset until init.cpp:Shutdown().
//!
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
//! that call.
std::unique_ptr<Chainstate> m_ibd_chainstate GUARDED_BY(::cs_main);
//! A chainstate initialized on the basis of a UTXO snapshot. If this is
//! non-null, it is always our active chainstate.
//!
//! Once this pointer is set to a corresponding chainstate, it will not
//! be reset until init.cpp:Shutdown().
//!
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
//! that call.
std::unique_ptr<Chainstate> m_snapshot_chainstate GUARDED_BY(::cs_main);
//! Points to either the ibd or snapshot chainstate; indicates our
//! most-work chain.
//!
//! This is especially important when, e.g., calling ActivateBestChain()
//! on all chainstates because we are not able to hold ::cs_main going into
//! that call.
Chainstate *m_active_chainstate GUARDED_BY(::cs_main){nullptr};
CBlockIndex *m_best_invalid GUARDED_BY(::cs_main){nullptr};
CBlockIndex *m_best_parked GUARDED_BY(::cs_main){nullptr};
//! Internal helper for ActivateSnapshot().
+ //!
+ //! De-serialization of a snapshot that is created with
+ //! CreateUTXOSnapshot() in rpc/blockchain.cpp.
+ //! To reduce space the serialization format of the snapshot avoids
+ //! duplication of tx hashes. The code takes advantage of the guarantee by
+ //! leveldb that keys are lexicographically sorted.
[[nodiscard]] bool
PopulateAndValidateSnapshot(Chainstate &snapshot_chainstate,
AutoFile &coins_file,
const node::SnapshotMetadata &metadata);
/**
* If a block header hasn't already been seen, call CheckBlockHeader on it,
* ensure that it doesn't descend from an invalid block, and then add it to
* m_block_index.
* Caller must set min_pow_checked=true in order to add a new header to the
* block index (permanent memory storage), indicating that the header is
* known to be part of a sufficiently high-work chain (anti-dos check).
*/
bool AcceptBlockHeader(
const CBlockHeader &block, BlockValidationState &state,
CBlockIndex **ppindex, bool min_pow_checked,
const std::optional<CCheckpointData> &test_checkpoints = std::nullopt)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
friend Chainstate;
//! Return true if a chainstate is considered usable.
//!
//! This is false when a background validation chainstate has completed its
//! validation of an assumed-valid chainstate, or when a snapshot
//! chainstate has been found to be invalid.
bool IsUsable(const Chainstate *const pchainstate) const
EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
return pchainstate && !pchainstate->m_disabled;
}
/** Most recent headers presync progress update, for rate-limiting. */
SteadyMilliseconds m_last_presync_update GUARDED_BY(::cs_main){};
public:
using Options = kernel::ChainstateManagerOpts;
explicit ChainstateManager(Options options,
node::BlockManager::Options blockman_options);
//! Function to restart active indexes; set dynamically to avoid a circular
//! dependency on `base/index.cpp`.
std::function<void()> snapshot_download_completed = std::function<void()>();
const Config &GetConfig() const { return m_options.config; }
const CChainParams &GetParams() const {
return m_options.config.GetChainParams();
}
const Consensus::Params &GetConsensus() const {
return m_options.config.GetChainParams().GetConsensus();
}
bool ShouldCheckBlockIndex() const {
return *Assert(m_options.check_block_index);
}
const arith_uint256 &MinimumChainWork() const {
return *Assert(m_options.minimum_chain_work);
}
const BlockHash &AssumedValidBlock() const {
return *Assert(m_options.assumed_valid_block);
}
kernel::Notifications &GetNotifications() const {
return m_options.notifications;
};
/**
* Make various assertions about the state of the block index.
*
* By default this only executes fully when using the Regtest chain;
* see: m_options.check_block_index.
*/
void CheckBlockIndex();
/**
* Alias for ::cs_main.
* Should be used in new code to make it easier to make ::cs_main a member
* of this class.
* Generally, methods of this class should be annotated to require this
* mutex. This will make calling code more verbose, but also help to:
* - Clarify that the method will acquire a mutex that heavily affects
* overall performance.
* - Force call sites to think how long they need to acquire the mutex to
* get consistent results.
*/
RecursiveMutex &GetMutex() const LOCK_RETURNED(::cs_main) {
return ::cs_main;
}
const Options m_options;
std::thread m_thread_load;
//! A single BlockManager instance is shared across each constructed
//! chainstate to avoid duplicating block metadata.
node::BlockManager m_blockman;
/**
* Whether initial block download has ended and IsInitialBlockDownload
* should return false from now on.
*
* Mutable because we need to be able to mark IsInitialBlockDownload()
* const, which latches this for caching purposes.
*/
mutable std::atomic<bool> m_cached_finished_ibd{false};
/**
* Every received block is assigned a unique and increasing identifier, so
* we know which one to give priority in case of a fork.
* Blocks loaded from disk are assigned id 0, so start the counter at 1.
*/
std::atomic<int32_t> nBlockSequenceId{1};
/** Decreasing counter (used by subsequent preciousblock calls). */
int32_t nBlockReverseSequenceId = -1;
/** chainwork for the last block that preciousblock has been applied to. */
arith_uint256 nLastPreciousChainwork = 0;
// Reset the memory-only sequence counters we use to track block arrival
// (used by tests to reset state)
void ResetBlockSequenceCounters() EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
nBlockSequenceId = 1;
nBlockReverseSequenceId = -1;
}
/**
* In order to efficiently track invalidity of headers, we keep the set of
* blocks which we tried to connect and found to be invalid here (ie which
* were set to BLOCK_FAILED_VALID since the last restart). We can then
* walk this set and check if a new header is a descendant of something in
* this set, preventing us from having to walk m_block_index when we try
* to connect a bad block and fail.
*
* While this is more complicated than marking everything which descends
* from an invalid block as invalid at the time we discover it to be
* invalid, doing so would require walking all of m_block_index to find all
* descendants. Since this case should be very rare, keeping track of all
* BLOCK_FAILED_VALID blocks in a set should be just fine and work just as
* well.
*
* Because we already walk m_block_index in height-order at startup, we go
* ahead and mark descendants of invalid blocks as FAILED_CHILD at that
* time, instead of putting things in this set.
*/
std::set<CBlockIndex *> m_failed_blocks;
/**
* Best header we've seen so far (used for getheaders queries' starting
* points).
*/
CBlockIndex *m_best_header GUARDED_BY(::cs_main){nullptr};
//! The total number of bytes available for us to use across all in-memory
//! coins caches. This will be split somehow across chainstates.
int64_t m_total_coinstip_cache{0};
//
//! The total number of bytes available for us to use across all leveldb
//! coins databases. This will be split somehow across chainstates.
int64_t m_total_coinsdb_cache{0};
//! Instantiate a new chainstate.
//!
//! @param[in] mempool The mempool to pass to the chainstate
// constructor
Chainstate &InitializeChainstate(CTxMemPool *mempool)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Get all chainstates currently being used.
std::vector<Chainstate *> GetAll();
//! Construct and activate a Chainstate on the basis of UTXO snapshot data.
//!
//! Steps:
//!
//! - Initialize an unused Chainstate.
//! - Load its `CoinsViews` contents from `coins_file`.
//! - Verify that the hash of the resulting coinsdb matches the expected
//! hash per assumeutxo chain parameters.
//! - Wait for our headers chain to include the base block of the snapshot.
//! - "Fast forward" the tip of the new chainstate to the base of the
//! snapshot, faking nTx* block index data along the way.
//! - Move the new chainstate to `m_snapshot_chainstate` and make it our
//! ActiveChainstate().
[[nodiscard]] bool ActivateSnapshot(AutoFile &coins_file,
const node::SnapshotMetadata &metadata,
bool in_memory);
//! Once the background validation chainstate has reached the height which
//! is the base of the UTXO snapshot in use, compare its coins to ensure
//! they match those expected by the snapshot.
//!
//! If the coins match (expected), then mark the validation chainstate for
//! deletion and continue using the snapshot chainstate as active.
//! Otherwise, revert to using the ibd chainstate and shutdown.
SnapshotCompletionResult MaybeCompleteSnapshotValidation(
std::function<void(bilingual_str)> shutdown_fnc =
[](bilingual_str msg) { AbortNode(msg.original, msg); })
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Returns nullptr if no snapshot has been loaded.
const CBlockIndex *GetSnapshotBaseBlock() const
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! The most-work chain.
Chainstate &ActiveChainstate() const;
CChain &ActiveChain() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) {
return ActiveChainstate().m_chain;
}
int ActiveHeight() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) {
return ActiveChain().Height();
}
CBlockIndex *ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) {
return ActiveChain().Tip();
}
//! The state of a background sync (for net processing)
bool BackgroundSyncInProgress() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) {
return IsUsable(m_snapshot_chainstate.get()) &&
IsUsable(m_ibd_chainstate.get());
}
//! The tip of the background sync chain
const CBlockIndex *GetBackgroundSyncTip() const
EXCLUSIVE_LOCKS_REQUIRED(GetMutex()) {
return BackgroundSyncInProgress() ? m_ibd_chainstate->m_chain.Tip()
: nullptr;
}
node::BlockMap &BlockIndex() EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
AssertLockHeld(::cs_main);
return m_blockman.m_block_index;
}
//! @returns true if a snapshot-based chainstate is in use. Also implies
//! that a background validation chainstate is also in use.
bool IsSnapshotActive() const;
std::optional<BlockHash> SnapshotBlockhash() const;
//! Is there a snapshot in use and has it been fully validated?
bool IsSnapshotValidated() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
return m_snapshot_chainstate && m_ibd_chainstate &&
m_ibd_chainstate->m_disabled;
}
/**
* Check whether we are doing an initial block download (synchronizing from
* disk or network)
*/
bool IsInitialBlockDownload() const;
/**
* Import blocks from an external file
*
* During reindexing, this function is called for each block file
* (datadir/blocks/blk?????.dat). It reads all blocks contained in the given
* file and attempts to process them (add them to the block index). The
* blocks may be out of order within each file and across files. Often this
* function reads a block but finds that its parent hasn't been read yet, so
* the block can't be processed yet. The function will add an entry to the
* blocks_with_unknown_parent map (which is passed as an argument), so that
* when the block's parent is later read and processed, this function can
* re-read the child block from disk and process it.
*
* Because a block's parent may be in a later file, not just later in the
* same file, the blocks_with_unknown_parent map must be passed in and out
* with each call. It's a multimap, rather than just a map, because multiple
* blocks may have the same parent (when chain splits or stale blocks
* exist). It maps from parent-hash to child-disk-position.
*
* This function can also be used to read blocks from user-specified block
* files using the -loadblock= option. There's no unknown-parent tracking,
* so the last two arguments are omitted.
*
*
* @param[in] fileIn FILE handle to file containing blocks to read
* @param[in] dbp (optional) Disk block position (only for reindex)
* @param[in,out] blocks_with_unknown_parent
* (optional) Map of disk positions for blocks with
* unknown parent, key is parent block hash
* (only used for reindex)
*/
void LoadExternalBlockFile(FILE *fileIn, FlatFilePos *dbp = nullptr,
std::multimap<BlockHash, FlatFilePos>
*blocks_with_unknown_parent = nullptr,
avalanche::Processor *const avalanche = nullptr);
/**
* Process an incoming block. This only returns after the best known valid
* block is made active. Note that it does not, however, guarantee that the
* specific block passed to it has been checked for validity!
*
* If you want to *possibly* get feedback on whether block is valid, you
* must install a CValidationInterface (see validationinterface.h) - this
* will have its BlockChecked method called whenever *any* block completes
* validation.
*
* Note that we guarantee that either the proof-of-work is valid on block,
* or (and possibly also) BlockChecked will have been called.
*
* May not be called in a validationinterface callback.
*
* @param[in] block The block we want to process.
* @param[in] force_processing Process this block even if unrequested;
* used for non-network block sources.
* @param[in] min_pow_checked True if proof-of-work anti-DoS checks have
* been done by caller for headers chain
* (note: only affects headers acceptance; if
* block header is already present in block
* index then this parameter has no effect)
* @param[out] new_block A boolean which is set to indicate if the block
* was first received via this call.
* @returns If the block was processed, independently of block validity
*/
bool ProcessNewBlock(const std::shared_ptr<const CBlock> &block,
bool force_processing, bool min_pow_checked,
bool *new_block,
avalanche::Processor *const avalanche = nullptr)
LOCKS_EXCLUDED(cs_main);
/**
* Process incoming block headers.
*
* May not be called in a validationinterface callback.
*
* @param[in] block The block headers themselves.
* @param[in] min_pow_checked True if proof-of-work anti-DoS checks have
* been done by caller for headers chain
* @param[out] state This may be set to an Error state if any error
* occurred processing them.
* @param[out] ppindex If set, the pointer will be set to point to the
* last new block index object for the given
* headers.
* @return True if block headers were accepted as valid.
*/
bool ProcessNewBlockHeaders(
const std::vector<CBlockHeader> &block, bool min_pow_checked,
BlockValidationState &state, const CBlockIndex **ppindex = nullptr,
const std::optional<CCheckpointData> &test_checkpoints = std::nullopt)
LOCKS_EXCLUDED(cs_main);
/**
* Sufficiently validate a block for disk storage (and store on disk).
*
* @param[in] pblock The block we want to process.
* @param[in] fRequested Whether we requested this block from a
* peer.
* @param[in] dbp The location on disk, if we are importing
* this block from prior storage.
* @param[in] min_pow_checked True if proof-of-work anti-DoS checks have
* been done by caller for headers chain
*
* @param[out] state The state of the block validation.
* @param[out] fNewBlock Optional return parameter to indicate if the
* block is new to our storage.
*
* @returns False if the block or header is invalid, or if saving to disk
* fails (likely a fatal error); true otherwise.
*/
bool AcceptBlock(const std::shared_ptr<const CBlock> &pblock,
BlockValidationState &state, bool fRequested,
const FlatFilePos *dbp, bool *fNewBlock,
bool min_pow_checked) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
void ReceivedBlockTransactions(const CBlock &block, CBlockIndex *pindexNew,
const FlatFilePos &pos)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Try to add a transaction to the memory pool.
*
* @param[in] tx The transaction to submit for mempool
* acceptance.
* @param[in] test_accept When true, run validation checks but don't
* submit to mempool.
*/
[[nodiscard]] MempoolAcceptResult
ProcessTransaction(const CTransactionRef &tx, bool test_accept = false)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
//! Load the block tree and coins database from disk, initializing state if
//! we're running with -reindex
bool LoadBlockIndex() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
//! Check to see if caches are out of balance and if so, call
//! ResizeCoinsCaches() as needed.
void MaybeRebalanceCaches() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/**
* This is used by net_processing to report pre-synchronization progress of
* headers, as headers are not yet fed to validation during that time, but
* validation is (for now) responsible for logging and signalling through
* NotifyHeaderTip, so it needs this information.
*/
void ReportHeadersPresync(const arith_uint256 &work, int64_t height,
int64_t timestamp);
//! When starting up, search the datadir for a chainstate based on a UTXO
//! snapshot that is in the process of being validated.
bool DetectSnapshotChainstate(CTxMemPool *mempool)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
void ResetChainstates() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Remove the snapshot-based chainstate and all on-disk artifacts.
//! Used when reindex{-chainstate} is called during snapshot use.
[[nodiscard]] bool DeleteSnapshotChainstate()
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Switch the active chainstate to one based on a UTXO snapshot that was
//! loaded previously.
Chainstate &ActivateExistingSnapshot(BlockHash base_blockhash)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! If we have validated a snapshot chain during this runtime, copy its
//! chainstate directory over to the main `chainstate` location, completing
//! validation of the snapshot.
//!
//! If the cleanup succeeds, the caller will need to ensure chainstates are
//! reinitialized, since ResetChainstates() will be called before leveldb
//! directories are moved or deleted.
//!
//! @sa node/chainstate:LoadChainstate()
bool ValidatedSnapshotCleanup() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! @returns the chainstate that indexes should consult when ensuring that
//! an index is synced with a chain where we can expect block index
//! entries to have BLOCK_HAVE_DATA beneath the tip.
//!
//! In other words, give us the chainstate for which we can reasonably
//! expect that all blocks beneath the tip have been indexed. In practice
//! this means when using an assumed-valid chainstate based upon a
//! snapshot, return only the fully validated chain.
Chainstate &GetChainstateForIndexing() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Return the [start, end] (inclusive) of block heights we can prune.
//!
//! start > end is possible, meaning no blocks can be pruned.
std::pair<int, int> GetPruneRange(const Chainstate &chainstate,
int last_height_can_prune)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
//! Return the height of the base block of the snapshot in use, if one
//! exists, else nullopt.
std::optional<int> GetSnapshotBaseHeight() const
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
/** Dump the recent block headers reception time to a file. */
bool DumpRecentHeadersTime(const fs::path &filePath) const
EXCLUSIVE_LOCKS_REQUIRED(GetMutex());
/** Load the recent block headers reception time from a file. */
bool LoadRecentHeadersTime(const fs::path &filePath)
EXCLUSIVE_LOCKS_REQUIRED(GetMutex());
};
/** Deployment* info via ChainstateManager */
template <typename DEP>
bool DeploymentActiveAfter(const CBlockIndex *pindexPrev,
const ChainstateManager &chainman, DEP dep) {
return DeploymentActiveAfter(pindexPrev, chainman.GetConsensus(), dep);
}
template <typename DEP>
bool DeploymentActiveAt(const CBlockIndex &index,
const ChainstateManager &chainman, DEP dep) {
return DeploymentActiveAt(index, chainman.GetConsensus(), dep);
}
#endif // BITCOIN_VALIDATION_H
diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py
index 83c25aeda..e8eef5320 100644
--- a/test/functional/feature_assumeutxo.py
+++ b/test/functional/feature_assumeutxo.py
@@ -1,722 +1,735 @@
# Copyright (c) 2021-present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test for assumeutxo, a means of quickly bootstrapping a node using
a serialized version of the UTXO set at a certain height, which corresponds
to a hash that has been compiled into bitcoind.
The assumeutxo value generated and used here is committed to in
`CRegTestParams::m_assumeutxo_data` in `src/kernel/chainparams.cpp`.
## Possible test improvements
Interesting test cases could be loading an assumeutxo snapshot file with:
- TODO: Valid snapshot file, but referencing a snapshot block that turns out to be
invalid, or has an invalid parent
- TODO: Valid snapshot file and snapshot block, but the block is not on the
most-work chain
Interesting starting states could be loading a snapshot when the current chain tip is:
- TODO: An ancestor of snapshot block
- TODO: The snapshot block
- TODO: A descendant of the snapshot block
- TODO: Not an ancestor or a descendant of the snapshot block and has more work
"""
import os
import time
from dataclasses import dataclass
from shutil import rmtree
from test_framework.messages import CBlockHeader, CTransaction, FromHex, msg_headers
from test_framework.p2p import P2PInterface
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error, try_rpc
from test_framework.wallet import MiniWallet, getnewdestination
START_HEIGHT = 199
SNAPSHOT_BASE_HEIGHT = 299
FINAL_HEIGHT = 399
COMPLETE_IDX = {"synced": True, "best_block_height": FINAL_HEIGHT}
class AssumeutxoTest(BitcoinTestFramework):
def set_test_params(self):
"""Use the pregenerated, deterministic chain up to height 199."""
self.num_nodes = 4
self.rpc_timeout = 120
self.extra_args = [
[],
["-fastprune", "-prune=1", "-blockfilterindex=1", "-coinstatsindex=1"],
[
"-persistmempool=0",
"-txindex=1",
"-blockfilterindex=1",
"-coinstatsindex=1",
],
[],
]
def setup_network(self):
"""Start with the nodes disconnected so that one can generate a snapshot
including blocks the other hasn't yet seen."""
self.add_nodes(4)
self.start_nodes(extra_args=self.extra_args)
def test_invalid_snapshot_scenarios(self, valid_snapshot_path):
self.log.info("Test different scenarios of loading invalid snapshot files")
with open(valid_snapshot_path, "rb") as f:
valid_snapshot_contents = f.read()
bad_snapshot_path = valid_snapshot_path + ".mod"
def expected_error(log_msg="", rpc_details=""):
with self.nodes[1].assert_debug_log([log_msg]):
assert_raises_rpc_error(
-32603,
f"Unable to load UTXO snapshot{rpc_details}",
self.nodes[1].loadtxoutset,
bad_snapshot_path,
)
self.log.info(
" - snapshot file refering to a block that is not in the assumeutxo parameters"
)
prev_block_hash = self.nodes[0].getblockhash(SNAPSHOT_BASE_HEIGHT - 1)
# Represents any unknown block hash
bogus_block_hash = "0" * 64
for bad_block_hash in [bogus_block_hash, prev_block_hash]:
with open(bad_snapshot_path, "wb") as f:
# block hash of the snapshot base is stored right at the start (first 32 bytes)
f.write(
bytes.fromhex(bad_block_hash)[::-1] + valid_snapshot_contents[32:]
)
error_details = f", assumeutxo block hash in snapshot metadata not recognized ({bad_block_hash})"
expected_error(rpc_details=error_details)
self.log.info(" - snapshot file with wrong number of coins")
valid_num_coins = int.from_bytes(valid_snapshot_contents[32 : 32 + 8], "little")
for off in [-1, +1]:
with open(bad_snapshot_path, "wb") as f:
f.write(valid_snapshot_contents[:32])
f.write((valid_num_coins + off).to_bytes(8, "little"))
f.write(valid_snapshot_contents[32 + 8 :])
expected_error(
log_msg=(
"bad snapshot - coins left over after deserializing 298 coins"
if off == -1
else "bad snapshot format or truncated snapshot after deserializing 299 coins"
)
)
- self.log.info(" - snapshot file with alternated UTXO data")
+ self.log.info(
+ " - snapshot file with alternated but parsable UTXO data results in different hash"
+ )
cases = [
# (content, offset, wrong_hash, custom_message)
# wrong outpoint hash
[
b"\xff" * 32,
0,
"74fab8900700c8a0a4c6b50330252d92d651088939a41b307a8fcdddfed65f77",
None,
],
- # wrong outpoint index
+ # wrong txid coins count
+ # We expect the first txid in the dump to have one coin, and we set the value to 2.
+ # The deserialization code manages to accidentally read a second coin before choking
+ # on the third coin when if finally finds some random data that doesn't pass sanity
+ # tests (block height > snapshot height).
[
- (1).to_bytes(4, "little"),
+ (2).to_bytes(1, "little"),
32,
+ None,
+ "[snapshot] bad snapshot data after deserializing 2 coins",
+ ],
+ # wrong outpoint index
+ [
+ b"\x01",
+ 33,
"3872c8e52554070ca410faff98e42f63c99d08f536be343af7c04143e0e8f2b2",
None,
],
# wrong coin code VARINT((coinbase ? 1 : 0) | (height << 1))
# We expect b"\x81" (as seen in a dump of valid_snapshot_path)
[
b"\x80",
- 36,
+ 34,
"b14c9595737179fe57e6d7a9f8e879a440833fa95ba52d210f1f7e3c02be64b2",
None,
],
# another wrong coin code
[
b"\x83",
- 36,
+ 34,
"296b4acd0d41dd4c0e845a7ed0cbdc602e5a7d092d5b4948a72835d2158f1e8e",
None,
],
# wrong coin case with height 364 and coinbase 0
[
b"\x84\x58",
- 36,
+ 34,
None,
"[snapshot] bad snapshot data after deserializing 0 coins",
],
# Amount exceeds MAX_MONEY
[
b"\xCA\xD2\x8F\x5A",
- 38,
+ 36,
None,
"[snapshot] bad snapshot data after deserializing 0 coins - bad tx out value",
],
]
for content, offset, wrong_hash, custom_message in cases:
with open(bad_snapshot_path, "wb") as f:
f.write(valid_snapshot_contents[: (32 + 8 + offset)])
f.write(content)
f.write(valid_snapshot_contents[(32 + 8 + offset + len(content)) :])
log_msg = (
custom_message
if custom_message is not None
else f"[snapshot] bad snapshot content hash: expected a966794ed5a2f9debaefc7ca48dbc5d5e12a89ff9fe45bd00ec5732d074580a9, got {wrong_hash}"
)
expected_error(log_msg=log_msg)
def test_headers_not_synced(self, valid_snapshot_path):
for node in self.nodes[1:]:
assert_raises_rpc_error(
-32603,
"The base block header (118a7d5473bccce9b314789e14ce426fc65fb09dfeda0131032bb6d86ed2fd0b) must appear in the headers chain. Make sure all headers are synced, and call this RPC again.",
node.loadtxoutset,
valid_snapshot_path,
)
def test_invalid_mempool_state(self, dump_output_path):
self.log.info("Test bitcoind should fail when mempool not empty.")
node = self.nodes[2]
tx = MiniWallet(node).send_self_transfer(from_node=node)
assert tx["txid"] in node.getrawmempool()
# Attempt to load the snapshot on Node 2 and expect it to fail
with node.assert_debug_log(
expected_msgs=[
"[snapshot] can't activate a snapshot when mempool not empty"
]
):
assert_raises_rpc_error(
-32603,
"Unable to load UTXO snapshot",
node.loadtxoutset,
dump_output_path,
)
self.restart_node(2, extra_args=self.extra_args[2])
def test_invalid_file_path(self):
self.log.info("Test bitcoind should fail when file path is invalid.")
node = self.nodes[0]
path = os.path.join(node.datadir, self.chain, "invalid", "path")
assert_raises_rpc_error(
-8,
f"Couldn't open file {path} for reading.",
node.loadtxoutset,
path,
)
def test_snapshot_in_a_divergent_chain(self, dump_output_path):
n0 = self.nodes[0]
n3 = self.nodes[3]
assert_equal(n0.getblockcount(), FINAL_HEIGHT)
assert_equal(n3.getblockcount(), START_HEIGHT)
self.log.info(
"Check importing a snapshot where current chain-tip is not an ancestor of the snapshot block but has less work"
)
# Generate a divergent chain in n3 up to 298
self.generate(n3, nblocks=99, sync_fun=self.no_op)
assert_equal(n3.getblockcount(), SNAPSHOT_BASE_HEIGHT - 1)
# Try importing the snapshot and assert its success
loaded = n3.loadtxoutset(dump_output_path)
assert_equal(loaded["base_height"], SNAPSHOT_BASE_HEIGHT)
normal, snapshot = n3.getchainstates()["chainstates"]
assert_equal(normal["blocks"], START_HEIGHT + 99)
assert_equal(snapshot["blocks"], SNAPSHOT_BASE_HEIGHT)
# Now lets sync the nodes and wait for the background validation to finish
self.connect_nodes(0, 3)
self.sync_blocks(nodes=(n0, n3))
self.wait_until(lambda: len(n3.getchainstates()["chainstates"]) == 1)
def test_sync_from_assumeutxo_node(self, snapshot):
"""
This test verifies that:
1. An IBD node can sync headers from an AssumeUTXO node at any time.
2. IBD nodes do not request historical blocks from AssumeUTXO nodes while they
are syncing the background-chain.
3. The assumeUTXO node dynamically adjusts the network services it offers
according to its state.
4. IBD nodes can fully sync from AssumeUTXO nodes after they finish the
background-chain sync.
"""
self.log.info("Testing IBD-sync from assumeUTXO node")
# Node2 starts clean and loads the snapshot.
# Node3 starts clean and seeks to sync-up from snapshot_node.
miner = self.nodes[0]
snapshot_node = self.nodes[2]
ibd_node = self.nodes[3]
# Start test fresh by cleaning up node directories
for node in (snapshot_node, ibd_node):
self.stop_node(node.index)
rmtree(node.chain_path)
self.start_node(node.index, extra_args=self.extra_args[node.index])
# Sync-up headers chain on snapshot_node to load snapshot
headers_provider_conn = snapshot_node.add_p2p_connection(P2PInterface())
headers_provider_conn.wait_for_getheaders()
msg = msg_headers()
for block_num in range(1, miner.getblockcount() + 1):
msg.headers.append(
FromHex(
CBlockHeader(),
miner.getblockheader(miner.getblockhash(block_num), verbose=False),
)
)
headers_provider_conn.send_message(msg)
# Ensure headers arrived
default_value = {"status": ""} # No status
headers_tip_hash = miner.getbestblockhash()
self.wait_until(
lambda: next(
filter(
lambda x: x["hash"] == headers_tip_hash,
snapshot_node.getchaintips(),
),
default_value,
)["status"]
== "headers-only"
)
snapshot_node.disconnect_p2ps()
# Load snapshot
snapshot_node.loadtxoutset(snapshot["path"])
# Connect nodes and verify the ibd_node can sync-up the headers-chain from the snapshot_node
self.connect_nodes(ibd_node.index, snapshot_node.index)
snapshot_block_hash = snapshot["base_hash"]
self.wait_until(
lambda: next(
filter(
lambda x: x["hash"] == snapshot_block_hash, ibd_node.getchaintips()
),
default_value,
)["status"]
== "headers-only"
)
# Once the headers-chain is synced, the ibd_node must avoid requesting historical blocks from the snapshot_node.
# If it does request such blocks, the snapshot_node will ignore requests it cannot fulfill, causing the ibd_node
# to stall. This stall could last for up to 10 min, ultimately resulting in an abrupt disconnection due to the
# ibd_node's perceived unresponsiveness.
# Sleep here because we can't detect when a node avoids requesting blocks from other peer.
time.sleep(3)
assert_equal(len(ibd_node.getpeerinfo()[0]["inflight"]), 0)
# Now disconnect nodes and finish background chain sync
self.disconnect_nodes(ibd_node.index, snapshot_node.index)
self.connect_nodes(snapshot_node.index, miner.index)
self.sync_blocks(nodes=(miner, snapshot_node))
# Check the base snapshot block was stored and ensure node signals full-node service support
self.wait_until(
lambda: not try_rpc(
-1, "Block not found", snapshot_node.getblock, snapshot_block_hash
)
)
self.wait_until(
lambda: "NETWORK" in snapshot_node.getnetworkinfo()["localservicesnames"]
)
# Now that the snapshot_node is synced, verify the ibd_node can sync from it
self.connect_nodes(snapshot_node.index, ibd_node.index)
assert "NETWORK" in ibd_node.getpeerinfo()[0]["servicesnames"]
self.sync_blocks(nodes=(ibd_node, snapshot_node))
def assert_only_network_limited_service(self, node):
node_services = node.getnetworkinfo()["localservicesnames"]
assert "NETWORK" not in node_services
assert "NETWORK_LIMITED" in node_services
def run_test(self):
"""
Bring up two (disconnected) nodes, mine some new blocks on the first,
and generate a UTXO snapshot.
Load the snapshot into the second, ensure it syncs to tip and completes
background validation when connected to the first.
"""
n0 = self.nodes[0]
n1 = self.nodes[1]
n2 = self.nodes[2]
n3 = self.nodes[3]
self.mini_wallet = MiniWallet(n0)
# Mock time for a deterministic chain
for n in self.nodes:
n.setmocktime(n.getblockheader(n.getbestblockhash())["time"])
# Generate a series of blocks that `n0` will have in the snapshot,
# but that n1 and n2 don't yet see.
assert n0.getblockcount() == START_HEIGHT
blocks = {START_HEIGHT: Block(n0.getbestblockhash(), 1, START_HEIGHT + 1)}
for i in range(100):
block_tx = 1
if i % 3 == 0:
self.mini_wallet.send_self_transfer(from_node=n0)
block_tx += 1
self.generate(n0, nblocks=1, sync_fun=self.no_op)
height = n0.getblockcount()
hash_ = n0.getbestblockhash()
blocks[height] = Block(
hash_, block_tx, blocks[height - 1].chain_tx + block_tx
)
if i == 4:
# Create a stale block that forks off the main chain before the snapshot.
temp_invalid = n0.getbestblockhash()
n0.invalidateblock(temp_invalid)
stale_hash = self.generateblock(
n0, output="raw(aaaa)", transactions=[], sync_fun=self.no_op
)["hash"]
n0.invalidateblock(stale_hash)
n0.reconsiderblock(temp_invalid)
stale_block = n0.getblock(stale_hash, 0)
self.log.info("-- Testing assumeutxo + some indexes + pruning")
assert_equal(n0.getblockcount(), SNAPSHOT_BASE_HEIGHT)
assert_equal(n1.getblockcount(), START_HEIGHT)
self.log.info(f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}")
dump_output = n0.dumptxoutset("utxos.dat")
self.log.info("Test loading snapshot when headers are not synced")
self.test_headers_not_synced(dump_output["path"])
# In order for the snapshot to activate, we have to ferry over the new
# headers to n1 and n2 so that they see the header of the snapshot's
# base block while disconnected from n0.
for i in range(1, 300):
block = n0.getblock(n0.getblockhash(i), 0)
# make n1 and n2 aware of the new header, but don't give them the
# block.
n1.submitheader(block)
n2.submitheader(block)
n3.submitheader(block)
# Ensure everyone is seeing the same headers.
for n in self.nodes:
assert_equal(n.getblockchaininfo()["headers"], SNAPSHOT_BASE_HEIGHT)
assert_equal(
dump_output["txoutset_hash"],
"a966794ed5a2f9debaefc7ca48dbc5d5e12a89ff9fe45bd00ec5732d074580a9",
)
assert_equal(dump_output["nchaintx"], blocks[SNAPSHOT_BASE_HEIGHT].chain_tx)
assert_equal(n0.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)
# Mine more blocks on top of the snapshot that n1 hasn't yet seen. This
# will allow us to test n1's sync-to-tip on top of a snapshot.
self.generate(n0, nblocks=100, sync_fun=self.no_op)
assert_equal(n0.getblockcount(), FINAL_HEIGHT)
assert_equal(n1.getblockcount(), START_HEIGHT)
assert_equal(n0.getblockchaininfo()["blocks"], FINAL_HEIGHT)
self.test_invalid_mempool_state(dump_output["path"])
self.test_invalid_snapshot_scenarios(dump_output["path"])
self.test_invalid_file_path()
# Prune-node sanity check
assert "NETWORK" not in n1.getnetworkinfo()["localservicesnames"]
self.log.info(f"Loading snapshot into second node from {dump_output['path']}")
loaded = n1.loadtxoutset(dump_output["path"])
assert_equal(loaded["coins_loaded"], SNAPSHOT_BASE_HEIGHT)
assert_equal(loaded["base_height"], SNAPSHOT_BASE_HEIGHT)
self.log.info("Confirm that local services remain unchanged")
# Since n1 is a pruned node, the 'NETWORK' service flag must always be unset.
self.assert_only_network_limited_service(n1)
def check_tx_counts(final: bool) -> None:
"""Check nTx and nChainTx intermediate values right after loading
the snapshot, and final values after the snapshot is validated."""
for height, block in blocks.items():
tx = n1.getblockheader(block.hash)["nTx"]
chain_tx = n1.getchaintxstats(nblocks=1, blockhash=block.hash)[
"txcount"
]
# Intermediate nTx of the starting block should be set, but nTx of
# later blocks should be 0 before they are downloaded
if final or height == START_HEIGHT:
assert_equal(tx, block.tx)
else:
assert_equal(tx, 0)
# Intermediate nChainTx of the starting block and snapshot block
# should be set, but others should be 0 until they are downloaded.
if final or height in (START_HEIGHT, SNAPSHOT_BASE_HEIGHT):
assert_equal(chain_tx, block.chain_tx)
else:
assert_equal(chain_tx, 0)
check_tx_counts(final=False)
normal, snapshot = n1.getchainstates()["chainstates"]
assert_equal(normal["blocks"], START_HEIGHT)
assert_equal(normal.get("snapshot_blockhash"), None)
assert_equal(normal["validated"], True)
assert_equal(snapshot["blocks"], SNAPSHOT_BASE_HEIGHT)
assert_equal(snapshot["snapshot_blockhash"], dump_output["base_hash"])
assert_equal(snapshot["validated"], False)
assert_equal(n1.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)
self.log.info(
"Submit a stale block that forked off the chain before the snapshot"
)
# Normally a block like this would not be downloaded, but if it is
# submitted early before the background chain catches up to the fork
# point, it winds up in m_blocks_unlinked and triggers a corner case
# that previously crashed CheckBlockIndex.
n1.submitblock(stale_block)
n1.getchaintips()
n1.getblock(stale_hash)
self.log.info(
"Submit a spending transaction for a snapshot chainstate coin to the mempool"
)
# spend the coinbase output of the first block that is not available on node1
spend_coin_blockhash = n1.getblockhash(START_HEIGHT + 1)
assert_raises_rpc_error(
-1, "Block not found on disk", n1.getblock, spend_coin_blockhash
)
prev_tx = n0.getblock(spend_coin_blockhash, 3)["tx"][0]
prevout = {
"txid": prev_tx["txid"],
"vout": 0,
"scriptPubKey": prev_tx["vout"][0]["scriptPubKey"]["hex"],
"amount": prev_tx["vout"][0]["value"],
}
privkey = n0.get_deterministic_priv_key().key
raw_tx = n1.createrawtransaction(
[prevout], {getnewdestination()[2]: 24_990_000}
)
signed_tx = n1.signrawtransactionwithkey(raw_tx, [privkey], [prevout])["hex"]
signed_txid = FromHex(CTransaction(), signed_tx).rehash()
assert n1.gettxout(prev_tx["txid"], 0) is not None
n1.sendrawtransaction(signed_tx)
assert signed_txid in n1.getrawmempool()
assert not n1.gettxout(prev_tx["txid"], 0)
PAUSE_HEIGHT = FINAL_HEIGHT - 40
self.log.info("Restarting node to stop at height %d", PAUSE_HEIGHT)
self.restart_node(
1, extra_args=[f"-stopatheight={PAUSE_HEIGHT}", *self.extra_args[1]]
)
# Upon restart during snapshot tip sync, the node must remain in 'limited' mode.
self.assert_only_network_limited_service(n1)
# Finally connect the nodes and let them sync.
#
# Set `wait_for_connect=False` to avoid a race between performing connection
# assertions and the -stopatheight tripping.
self.connect_nodes(0, 1, wait_for_connect=False)
n1.wait_until_stopped()
self.log.info("Checking that blocks are segmented on disk")
assert self.has_blockfile(n1, "00000"), "normal blockfile missing"
assert self.has_blockfile(n1, "00001"), "assumed blockfile missing"
assert not self.has_blockfile(n1, "00002"), "too many blockfiles"
self.log.info(
"Restarted node before snapshot validation completed, reloading..."
)
self.restart_node(1, extra_args=self.extra_args[1])
# Upon restart, the node must remain in 'limited' mode
self.assert_only_network_limited_service(n1)
# Send snapshot block to n1 out of order. This makes the test less
# realistic because normally the snapshot block is one of the last
# blocks downloaded, but its useful to test because it triggers more
# corner cases in ReceivedBlockTransactions() and CheckBlockIndex()
# setting and testing nChainTx values, and it exposed previous bugs.
snapshot_hash = n0.getblockhash(SNAPSHOT_BASE_HEIGHT)
snapshot_block = n0.getblock(snapshot_hash, 0)
n1.submitblock(snapshot_block)
self.connect_nodes(0, 1)
self.log.info(f"Ensuring snapshot chain syncs to tip. ({FINAL_HEIGHT})")
self.wait_until(
lambda: n1.getchainstates()["chainstates"][-1]["blocks"] == FINAL_HEIGHT
)
self.sync_blocks(nodes=(n0, n1))
self.log.info("Ensuring background validation completes")
self.wait_until(lambda: len(n1.getchainstates()["chainstates"]) == 1)
# Since n1 is a pruned node, it will not signal NODE_NETWORK after
# completing the background sync.
self.assert_only_network_limited_service(n1)
# Ensure indexes have synced.
completed_idx_state = {
"basic block filter index": COMPLETE_IDX,
"coinstatsindex": COMPLETE_IDX,
}
self.wait_until(lambda: n1.getindexinfo() == completed_idx_state)
self.log.info("Re-check nTx and nChainTx values")
check_tx_counts(final=True)
for i in (0, 1):
n = self.nodes[i]
self.log.info(
f"Restarting node {i} to ensure (Check|Load)BlockIndex passes"
)
self.restart_node(i, extra_args=self.extra_args[i])
assert_equal(n.getblockchaininfo()["blocks"], FINAL_HEIGHT)
(chainstate,) = n.getchainstates()["chainstates"]
assert_equal(chainstate["blocks"], FINAL_HEIGHT)
if i != 0:
# Ensure indexes have synced for the assumeutxo node
self.wait_until(lambda: n.getindexinfo() == completed_idx_state)
# Node 2: all indexes + reindex
# -----------------------------
self.log.info("-- Testing all indexes + reindex")
assert_equal(n2.getblockcount(), START_HEIGHT)
# sanity check
assert "NETWORK" in n2.getnetworkinfo()["localservicesnames"]
self.log.info(f"Loading snapshot into third node from {dump_output['path']}")
loaded = n2.loadtxoutset(dump_output["path"])
assert_equal(loaded["coins_loaded"], SNAPSHOT_BASE_HEIGHT)
assert_equal(loaded["base_height"], SNAPSHOT_BASE_HEIGHT)
# Even though n2 is a full node, it will unset the 'NETWORK' service flag during
# snapshot loading. This indicates other peers that the node will temporarily
# not provide historical blocks.
self.log.info("Check node2 updated the local services during snapshot load")
self.assert_only_network_limited_service(n2)
for reindex_arg in ["-reindex=1", "-reindex-chainstate=1"]:
self.log.info(
f"Check that restarting with {reindex_arg} will delete the snapshot chainstate"
)
self.restart_node(2, extra_args=[reindex_arg, *self.extra_args[2]])
assert_equal(1, len(n2.getchainstates()["chainstates"]))
for i in range(1, 300):
block = n0.getblock(n0.getblockhash(i), 0)
n2.submitheader(block)
loaded = n2.loadtxoutset(dump_output["path"])
assert_equal(loaded["coins_loaded"], SNAPSHOT_BASE_HEIGHT)
assert_equal(loaded["base_height"], SNAPSHOT_BASE_HEIGHT)
normal, snapshot = n2.getchainstates()["chainstates"]
assert_equal(normal["blocks"], START_HEIGHT)
assert_equal(normal.get("snapshot_blockhash"), None)
assert_equal(normal["validated"], True)
assert_equal(snapshot["blocks"], SNAPSHOT_BASE_HEIGHT)
assert_equal(snapshot["snapshot_blockhash"], dump_output["base_hash"])
assert_equal(snapshot["validated"], False)
self.log.info(
"Check that loading the snapshot again will fail because there is already an active snapshot."
)
with n2.assert_debug_log(
expected_msgs=[
"[snapshot] can't activate a snapshot-based chainstate more than once"
]
):
assert_raises_rpc_error(
-32603,
"Unable to load UTXO snapshot",
n2.loadtxoutset,
dump_output["path"],
)
# Upon restart, the node must stay in 'limited' mode until the background
# chain sync completes.
self.restart_node(2, extra_args=self.extra_args[2])
self.assert_only_network_limited_service(n2)
self.connect_nodes(0, 2)
self.wait_until(
lambda: n2.getchainstates()["chainstates"][-1]["blocks"] == FINAL_HEIGHT
)
self.sync_blocks(nodes=(n0, n2))
self.log.info("Ensuring background validation completes")
self.wait_until(lambda: len(n2.getchainstates()["chainstates"]) == 1)
# Once background chain sync completes, the full node must start offering
# historical blocks again.
self.wait_until(
lambda: {"NETWORK", "NETWORK_LIMITED"}.issubset(
n2.getnetworkinfo()["localservicesnames"]
)
)
completed_idx_state = {
"basic block filter index": COMPLETE_IDX,
"coinstatsindex": COMPLETE_IDX,
"txindex": COMPLETE_IDX,
}
self.wait_until(lambda: n2.getindexinfo() == completed_idx_state)
for i in (0, 2):
n = self.nodes[i]
self.log.info(
f"Restarting node {i} to ensure (Check|Load)BlockIndex passes"
)
self.restart_node(i, extra_args=self.extra_args[i])
assert_equal(n.getblockchaininfo()["blocks"], FINAL_HEIGHT)
(chainstate,) = n.getchainstates()["chainstates"]
assert_equal(chainstate["blocks"], FINAL_HEIGHT)
if i != 0:
# Ensure indexes have synced for the assumeutxo node
self.wait_until(lambda: n.getindexinfo() == completed_idx_state)
self.log.info("Test -reindex-chainstate of an assumeutxo-synced node")
self.restart_node(2, extra_args=["-reindex-chainstate=1", *self.extra_args[2]])
assert_equal(n2.getblockchaininfo()["blocks"], FINAL_HEIGHT)
self.wait_until(lambda: n2.getblockcount() == FINAL_HEIGHT)
self.log.info("Test -reindex of an assumeutxo-synced node")
self.restart_node(2, extra_args=["-reindex=1", *self.extra_args[2]])
self.connect_nodes(0, 2)
self.wait_until(lambda: n2.getblockcount() == FINAL_HEIGHT)
self.test_snapshot_in_a_divergent_chain(dump_output["path"])
# The following test cleans node2 and node3 chain directories.
self.test_sync_from_assumeutxo_node(snapshot=dump_output)
@dataclass
class Block:
hash: str
tx: int
chain_tx: int
if __name__ == "__main__":
AssumeutxoTest().main()
diff --git a/test/functional/rpc_dumptxoutset.py b/test/functional/rpc_dumptxoutset.py
index 30aaa38d3..9b307824b 100644
--- a/test/functional/rpc_dumptxoutset.py
+++ b/test/functional/rpc_dumptxoutset.py
@@ -1,62 +1,62 @@
# Copyright (c) 2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the generation of UTXO snapshots using `dumptxoutset`.
"""
import hashlib
from pathlib import Path
from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error
class DumptxoutsetTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def run_test(self):
"""Test a trivial usage of the dumptxoutset RPC command."""
node = self.nodes[0]
mocktime = node.getblockheader(node.getblockhash(0))["time"] + 1
node.setmocktime(mocktime)
self.generate(node, COINBASE_MATURITY)
FILENAME = "txoutset.dat"
out = node.dumptxoutset(FILENAME)
expected_path = Path(node.datadir) / self.chain / FILENAME
assert expected_path.is_file()
assert_equal(out["coins_written"], 100)
assert_equal(out["base_height"], 100)
assert_equal(out["path"], str(expected_path))
# Blockhash should be deterministic based on mocked time.
assert_equal(
out["base_hash"],
"53bd41279adf93df5bb1f51c84d3409aeba14d4276238ae30f813ff27e711ad7",
)
with open(str(expected_path), "rb") as f:
digest = hashlib.sha256(f.read()).hexdigest()
# UTXO snapshot hash should be deterministic based on mocked time.
assert_equal(
digest,
- "5403d65d5310532fdec241643ef1857368a934b0878859436cf768bee003d135",
+ "998dfe4323bfa181a1eccc7c2f71d84c44dfce60da3c6271862f63a65f28072e",
)
assert_equal(
out["txoutset_hash"],
"f00b066f5014ef37f0bb40ab455f995a85ac475bc3beaa721476854a37fb4268",
)
assert_equal(out["nchaintx"], 101)
# Specifying a path to an existing file will fail.
assert_raises_rpc_error(
-8, f"{FILENAME} already exists", node.dumptxoutset, FILENAME
)
if __name__ == "__main__":
DumptxoutsetTest().main()

File Metadata

Mime Type
text/x-diff
Expires
Wed, May 21, 20:56 (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5865925
Default Alt Text
(589 KB)

Event Timeline