Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ if test "$enable_werror" = "yes"; then
fi

if test "$CXXFLAGS_overridden" = "no"; then
WARN_CXXFLAGS="$WARN_CXXFLAGS -ferror-limit=100"
AX_CHECK_COMPILE_FLAG([-Wall], [WARN_CXXFLAGS="$WARN_CXXFLAGS -Wall"], [], [$CXXFLAG_WERROR])
AX_CHECK_COMPILE_FLAG([-Wextra], [WARN_CXXFLAGS="$WARN_CXXFLAGS -Wextra"], [], [$CXXFLAG_WERROR])
AX_CHECK_COMPILE_FLAG([-Wgnu], [WARN_CXXFLAGS="$WARN_CXXFLAGS -Wgnu"], [], [$CXXFLAG_WERROR])
Expand Down
12 changes: 3 additions & 9 deletions doc/design/assumeutxo.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,12 @@ The utility script

## Design notes

- A new block index `nStatus` flag is introduced, `BLOCK_ASSUMED_VALID`, to mark block
index entries that are required to be assumed-valid by a chainstate created
from a UTXO snapshot. This flag is used as a way to modify certain
CheckBlockIndex() logic to account for index entries that are pending validation by a
chainstate running asynchronously in the background.

- The concept of UTXO snapshots is treated as an implementation detail that lives
behind the ChainstateManager interface. The external presentation of the changes
required to facilitate the use of UTXO snapshots is the understanding that there are
now certain regions of the chain that can be temporarily assumed to be valid (using
the nStatus flag mentioned above). In certain cases, e.g. wallet rescanning, this is
very similar to dealing with a pruned chain.
now certain regions of the chain that can be temporarily assumed to be valid.
In certain cases, e.g. wallet rescanning, this is very similar to dealing with
a pruned chain.

Logic outside ChainstateManager should try not to know about snapshots, instead
preferring to work in terms of more general states like assumed-valid.
Expand Down
2 changes: 1 addition & 1 deletion src/bench/wallet_create_tx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ void generateFakeBlock(const CChainParams& params,

// notify wallet
const auto& pindex = WITH_LOCK(::cs_main, return context.chainman->ActiveChain().Tip());
wallet.blockConnected(ChainstateRole::NORMAL, kernel::MakeBlockInfo(pindex, &block));
wallet.blockConnected({}, kernel::MakeBlockInfo(pindex, &block));
}

struct PreSelectInputs {
Expand Down
13 changes: 5 additions & 8 deletions src/bitcoin-chainstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,9 @@ int main(int argc, char* argv[])
}
}

for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(state, nullptr)) {
std::cerr << "Failed to connect best block (" << state.ToString() << ")" << std::endl;
goto epilogue;
}
if (auto result = chainman.ActivateBestChains(); !result) {
std::cerr << util::ErrorString(result).original << std::endl;
goto epilogue;
}

// Main program logic starts here
Expand All @@ -161,7 +158,7 @@ int main(int argc, char* argv[])
LOCK(chainman.GetMutex());
std::cout
<< "\t" << "Reindexing: " << std::boolalpha << node::fReindex.load() << std::noboolalpha << std::endl
<< "\t" << "Snapshot Active: " << std::boolalpha << chainman.IsSnapshotActive() << std::noboolalpha << std::endl
<< "\t" << "Snapshot Active: " << std::boolalpha << (chainman.MostWorkChainstate().SnapshotBase() != nullptr) << std::noboolalpha << std::endl
<< "\t" << "Active Height: " << chainman.ActiveHeight() << std::endl
<< "\t" << "Active IBD: " << std::boolalpha << chainman.IsInitialBlockDownload() << std::noboolalpha << std::endl;
CBlockIndex* tip = chainman.ActiveTip();
Expand Down Expand Up @@ -293,7 +290,7 @@ int main(int argc, char* argv[])
GetMainSignals().FlushBackgroundCallbacks();
{
LOCK(cs_main);
for (Chainstate* chainstate : chainman.GetAll()) {
for (auto& chainstate : chainman.m_chainstates) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
chainstate->ResetCoinsViews();
Expand Down
67 changes: 19 additions & 48 deletions src/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,20 @@ enum BlockStatus : uint32_t {

/**
* Only first tx is coinbase, 2 <= coinbase input script length <= 100, transactions valid, no duplicate txids,
* sigops, size, merkle root. Implies all parents are at least TREE but not necessarily TRANSACTIONS. When all
* parent blocks also have TRANSACTIONS, CBlockIndex::nChainTx will be set.
* sigops, size, merkle root. Implies all parents are at least TREE but not necessarily TRANSACTIONS.
*
* If a block's validity is at least VALID_TRANSACTIONS, CBlockIndex::nTx will be set. If a block and all previous
* blocks back to the genesis block or an assumeutxo snapshot block are at least VALID_TRANSACTIONS,
* CBlockIndex::nChainTx will be set.
*/
BLOCK_VALID_TRANSACTIONS = 3,

//! Outputs do not overspend inputs, no double spends, coinbase output ok, no immature coinbase spends, BIP30.
//! Implies all parents are either at least VALID_CHAIN, or are ASSUMED_VALID
//! Implies all previous blocks back to the genesis block or an assumeutxo snapshot block are at least VALID_CHAIN.
BLOCK_VALID_CHAIN = 4,

//! Scripts & signatures ok. Implies all parents are either at least VALID_SCRIPTS, or are ASSUMED_VALID.
//! Scripts & signatures ok. Implies all previous blocks back to the genesis block or an assumeutxo snapshot block
//! are at least VALID_SCRIPTS.
BLOCK_VALID_SCRIPTS = 5,

//! All validity bits.
Expand All @@ -124,21 +128,8 @@ enum BlockStatus : uint32_t {

BLOCK_OPT_WITNESS = 128, //!< block data in blk*.dat was received with a witness-enforcing client

/**
* If ASSUMED_VALID is set, it means that this block has not been validated
* and has validity status less than VALID_SCRIPTS. Also that it may have
* descendant blocks with VALID_SCRIPTS set, because they can be validated
* based on an assumeutxo snapshot.
*
* When an assumeutxo snapshot is loaded, the ASSUMED_VALID flag is added to
* unvalidated blocks at the snapshot height and below. Then, as the background
* validation progresses, and these blocks are validated, the ASSUMED_VALID
* flags are removed. See `doc/design/assumeutxo.md` for details.
*
* This flag is only used to implement checks in CheckBlockIndex() and
* should not be used elsewhere.
*/
BLOCK_ASSUMED_VALID = 256,
BLOCK_STATUS_RESERVED = 256, //!< Unused flag that was previously set on assumeutxo snapshot blocks and their
//!< ancestors before they were validated, and unset when they were validated.
};

/** The block chain is a tree shaped structure starting with the
Expand Down Expand Up @@ -173,21 +164,16 @@ class CBlockIndex
//! (memory only) Total amount of work (expected number of hashes) in the chain up to and including this block
arith_uint256 nChainWork{};

//! Number of transactions in this block.
//! Number of transactions in this block. This will be nonzero if the block
//! reached the VALID_TRANSACTIONS level, and zero otherwise.
//! Note: in a potential headers-first mode, this number cannot be relied upon
//! Note: this value is faked during UTXO snapshot load to ensure that
//! LoadBlockIndex() will load index entries for blocks that we lack data for.
//! @sa ActivateSnapshot
unsigned int nTx{0};

//! (memory only) Number of transactions in the chain up to and including this block.
//! This value will be non-zero only if and only if transactions for this block and all its parents are available.
//! This value will be non-zero if this block and all previous blocks back
//! to the genesis block or an assumeutxo snapshot block have reached the
//! VALID_TRANSACTIONS level.
//! Change to 64-bit type before 2024 (assuming worst case of 60 byte transactions).
//!
//! Note: this value is faked during use of a UTXO snapshot because we don't
//! have the underlying block data available during snapshot load.
//! @sa AssumeutxoData
//! @sa ActivateSnapshot
unsigned int nChainTx{0};

//! Verification status of this block. See enum BlockStatus
Expand Down Expand Up @@ -262,15 +248,14 @@ class CBlockIndex
}

/**
* Check whether this block's and all previous blocks' transactions have been
* downloaded (and stored to disk) at some point.
* Check whether this block and all previous blocks back to the genesis block or an assumeutxo snapshot block have
* reached VALID_TRANSACTIONS and had transactions downloaded (and stored to disk) at some point.
*
* Does not imply the transactions are consensus-valid (ConnectTip might fail)
* Does not imply the transactions are still stored on disk. (IsBlockPruned might return true)
*
* Note that this will be true for the snapshot base block, if one is loaded (and
* all subsequent assumed-valid blocks) since its nChainTx value will have been set
* manually based on the related AssumeutxoData entry.
* Note that this will be true for the snapshot base block, if one is loaded, since its nChainTx value will have
* been set manually based on the related AssumeutxoData entry.
*/
bool HaveNumChainTxs() const { return nChainTx != 0; }

Expand Down Expand Up @@ -318,14 +303,6 @@ class CBlockIndex
return ((nStatus & BLOCK_VALID_MASK) >= nUpTo);
}

//! @returns true if the block is assumed-valid; this means it is queued to be
//! validated by a background chainstate.
bool IsAssumedValid() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
AssertLockHeld(::cs_main);
return nStatus & BLOCK_ASSUMED_VALID;
}

//! Raise the validity level of this block index entry.
//! Returns true if the validity was changed.
bool RaiseValidity(enum BlockStatus nUpTo) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
Expand All @@ -335,12 +312,6 @@ class CBlockIndex
if (nStatus & BLOCK_FAILED_MASK) return false;

if ((nStatus & BLOCK_VALID_MASK) < nUpTo) {
// If this block had been marked assumed-valid and we're raising
// its validity to a certain point, there is no longer an assumption.
if (nStatus & BLOCK_ASSUMED_VALID && nUpTo >= BLOCK_VALID_SCRIPTS) {
nStatus &= ~BLOCK_ASSUMED_VALID;
}

nStatus = (nStatus & ~BLOCK_VALID_MASK) | nUpTo;
return true;
}
Expand Down
13 changes: 5 additions & 8 deletions src/index/base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ bool BaseIndex::Init()
// m_chainstate member gives indexing code access to node internals. It is
// removed in followup https://github.com/bitcoin/bitcoin/pull/24230
m_chainstate = WITH_LOCK(::cs_main,
return &m_chain->context()->chainman->GetChainstateForIndexing());
return &m_chain->context()->chainman->ValidatedChainstate());
// Register to validation interface before setting the 'm_synced' flag, so that
// callbacks are not missed once m_synced is true.
RegisterValidationInterface(this);
Expand Down Expand Up @@ -260,13 +260,11 @@ bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_ti

void BaseIndex::BlockConnected(ChainstateRole role, const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
{
// Ignore events from the assumed-valid chain; we will process its blocks
// (sequentially) after it is fully verified by the background chainstate. This
// is to avoid any out-of-order indexing.
// Ignore events from not fully validated chains to avoid out-of-order indexing.
//
// TODO at some point we could parameterize whether a particular index can be
// built out of order, but for now just do the conservative simple thing.
if (role == ChainstateRole::ASSUMEDVALID) {
if (!role.validated) {
return;
}

Expand Down Expand Up @@ -317,9 +315,8 @@ void BaseIndex::BlockConnected(ChainstateRole role, const std::shared_ptr<const

void BaseIndex::ChainStateFlushed(ChainstateRole role, const CBlockLocator& locator)
{
// Ignore events from the assumed-valid chain; we will process its blocks
// (sequentially) after it is fully verified by the background chainstate.
if (role == ChainstateRole::ASSUMEDVALID) {
// Ignore events from not fully validated chains to avoid out-of-order indexing.
if (!role.validated) {
return;
}

Expand Down
8 changes: 4 additions & 4 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ void Shutdown(NodeContext& node)
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
if (node.chainman) {
LOCK(cs_main);
for (Chainstate* chainstate : node.chainman->GetAll()) {
for (auto& chainstate : node.chainman->m_chainstates) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
}
Expand Down Expand Up @@ -354,7 +354,7 @@ void Shutdown(NodeContext& node)

if (node.chainman) {
LOCK(cs_main);
for (Chainstate* chainstate : node.chainman->GetAll()) {
for (auto& chainstate : node.chainman->m_chainstates) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
chainstate->ResetCoinsViews();
Expand Down Expand Up @@ -1630,7 +1630,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
if (chainman.m_blockman.IsPruneMode()) {
if (!fReindex) {
LOCK(cs_main);
for (Chainstate* chainstate : chainman.GetAll()) {
for (auto& chainstate : chainman.m_chainstates) {
uiInterface.InitMessage(_("Pruning blockstore…").translated);
chainstate->PruneAndFlush();
}
Expand Down Expand Up @@ -1946,7 +1946,7 @@ bool StartIndexBackgroundSync(NodeContext& node)
std::optional<const CBlockIndex*> indexes_start_block;
std::string older_index_name;
ChainstateManager& chainman = *Assert(node.chainman);
const Chainstate& chainstate = WITH_LOCK(::cs_main, return chainman.GetChainstateForIndexing());
const Chainstate& chainstate = WITH_LOCK(::cs_main, return chainman.ValidatedChainstate());
const CChain& index_chain = chainstate.m_chain;

for (auto index : node.indexes) {
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <common/settings.h>
#include <primitives/transaction.h> // For CTransactionRef
#include <util/result.h>
#include <validationinterface.h>

#include <functional>
#include <memory>
Expand All @@ -28,7 +29,6 @@ class Coin;
class uint256;
enum class MemPoolRemovalReason;
enum class RBFTransactionState;
enum class ChainstateRole;
struct bilingual_str;
struct CBlockLocator;
struct FeeCalculation;
Expand Down
10 changes: 0 additions & 10 deletions src/kernel/chain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,3 @@ interfaces::BlockInfo MakeBlockInfo(const CBlockIndex* index, const CBlock* data
return info;
}
} // namespace kernel

std::ostream& operator<<(std::ostream& os, const ChainstateRole& role) {
switch(role) {
case ChainstateRole::NORMAL: os << "normal"; break;
case ChainstateRole::ASSUMEDVALID: os << "assumedvalid"; break;
case ChainstateRole::BACKGROUND: os << "background"; break;
default: os.setstate(std::ios_base::failbit);
}
return os;
}
17 changes: 0 additions & 17 deletions src/kernel/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,4 @@ interfaces::BlockInfo MakeBlockInfo(const CBlockIndex* block_index, const CBlock

} // namespace kernel

//! This enum describes the various roles a specific Chainstate instance can take.
//! Other parts of the system sometimes need to vary in behavior depending on the
//! existence of a background validation chainstate, e.g. when building indexes.
enum class ChainstateRole {
// Single chainstate in use, "normal" IBD mode.
NORMAL,

// Doing IBD-style validation in the background. Implies use of an assumed-valid
// chainstate.
BACKGROUND,

// Active assumed-valid chainstate. Implies use of a background IBD chainstate.
ASSUMEDVALID,
};

std::ostream& operator<<(std::ostream& os, const ChainstateRole& role);

#endif // BITCOIN_KERNEL_CHAIN_H
8 changes: 4 additions & 4 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1987,7 +1987,7 @@ void PeerManagerImpl::BlockConnected(

// The following task can be skipped since we don't maintain a mempool for
// the ibd/background chainstate.
if (role == ChainstateRole::BACKGROUND) {
if (!role.most_work) {
return;
}
m_orphanage.EraseForBlock(*pblock);
Expand Down Expand Up @@ -5964,12 +5964,12 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
// If a snapshot chainstate is in use, we want to find its next blocks
// before the background chainstate to prioritize getting to network tip.
FindNextBlocksToDownload(*peer, get_inflight_budget(), vToDownload, staller);
if (m_chainman.BackgroundSyncInProgress() && !IsLimitedPeer(*peer)) {
auto historical_blocks = m_chainman.GetHistoricalBlockRange();
if (historical_blocks && !IsLimitedPeer(*peer)) {
TryDownloadingHistoricalBlocks(
*peer,
get_inflight_budget(),
vToDownload, m_chainman.GetBackgroundSyncTip(),
Assert(m_chainman.GetSnapshotBaseBlock()));
vToDownload, historical_blocks->first, historical_blocks->second);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If practical, it would be nice to have this change in a seperate commit from the (simpler) changes above.

}
for (const CBlockIndex *pindex : vToDownload) {
uint32_t nFetchFlags = GetFetchFlags(*peer);
Expand Down
19 changes: 6 additions & 13 deletions src/node/blockstorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ void BlockManager::FindFilesToPruneManual(
count++;
}
LogPrintf("[%s] Prune (Manual): prune_height=%d removed %d blk/rev pairs\n",
chain.GetRole(), last_block_can_prune, count);
chain.GetName(), last_block_can_prune, count);
}

void BlockManager::FindFilesToPrune(
Expand All @@ -305,8 +305,9 @@ void BlockManager::FindFilesToPrune(
{
LOCK2(cs_main, cs_LastBlockFile);
// Distribute our -prune budget over all chainstates.
const int num_chainstates = chainman.HistoricalChainstate() ? 2 : 1;
const auto target = std::max(
MIN_DISK_SPACE_FOR_BLOCK_FILES, GetPruneTarget() / chainman.GetAll().size());
MIN_DISK_SPACE_FOR_BLOCK_FILES, GetPruneTarget() / num_chainstates);
const uint64_t target_sync_height = chainman.m_best_header->nHeight;

if (chain.m_chain.Height() < 0 || target == 0) {
Expand Down Expand Up @@ -366,7 +367,7 @@ void BlockManager::FindFilesToPrune(
}

LogPrint(BCLog::PRUNE, "[%s] target=%dMiB actual=%dMiB diff=%dMiB min_height=%d max_prune_height=%d removed %d blk/rev pairs\n",
chain.GetRole(), target / 1024 / 1024, nCurrentUsage / 1024 / 1024,
chain.GetName(), target / 1024 / 1024, nCurrentUsage / 1024 / 1024,
(int64_t(target) - int64_t(nCurrentUsage)) / 1024 / 1024,
min_block_to_prune, last_block_can_prune, count);
}
Expand Down Expand Up @@ -1199,16 +1200,8 @@ void ImportBlocks(ChainstateManager& chainman, std::vector<fs::path> vImportFile
}

// scan for better chains in the block chain database, that are not yet connected in the active best chain

// We can't hold cs_main during ActivateBestChain even though we're accessing
// the chainman unique_ptrs since ABC requires us not to be holding cs_main, so retrieve
// the relevant pointers before the ABC call.
for (Chainstate* chainstate : WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(state, nullptr)) {
chainman.GetNotifications().fatalError(strprintf("Failed to connect best block (%s)", state.ToString()));
return;
}
if (auto result = chainman.ActivateBestChains(); !result) {
chainman.GetNotifications().fatalError(util::ErrorString(result).original);
}
} // End scope of ImportingNow
}
Expand Down
Loading