mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-31 10:41:08 +00:00
557b41a38ccf2929ca1e5271db1701e5fbe781af validation: make `IsInitialBlockDownload()` lock-free (Lőrinc) b9c0ab3b75a19d7a1f7c01762374ce85f2d0d7be chain: add `CChain::IsTipRecent` helper (Lőrinc) 8d531c6210eb05bc424c971f621bb0b688ff70e6 validation: invert `m_cached_finished_ibd` to `m_cached_is_ibd` (Lőrinc) 8be54e3b19677b02e19d054a4a5b2f1968bb1c46 test: cover IBD exit conditions (Lőrinc) Pull request description: This PR is a follow-up to the stale #32885. ### Problem `ChainstateManager::IsInitialBlockDownload()` currently acquires `cs_main` internally, even though most existing call sites already hold the lock. This becomes relevant for proposals like #34054, which would call `IsInitialBlockDownload()` from the scheduler thread without holding `cs_main`, potentially introducing lock contention. ### Fix Make `ChainstateManager::IsInitialBlockDownload()` lock-free by caching its result in a single atomic `m_cached_is_ibd` (true while in IBD, latched to false on exit). Move the IBD exit checks out of `IsInitialBlockDownload()` (reader-side) into a new `ChainstateManager::UpdateIBDStatus()` (writer-side, called under cs_main). Call UpdateIBDStatus() at strategic points where IBD exit conditions may change, after active chain tip updates in `ConnectTip()`, `DisconnectTip()`, and `LoadChainTip()`, and after `ImportBlocks()` returns. With this, `IsInitialBlockDownload()` becomes a lock-free atomic read, avoiding internal `cs_main` acquisition on hot paths. ### Testing and Benchmarks This isn't strictly an optimization (though some usecases might benefit from it), so rather as a sanity check I ran a reindex-chainstate and an `AssumeUTXO` load (without background validation). <details> <summary>assumeutxo load | 910000 blocks | dbcache 4500 | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | xfs | SSD</summary> ``` COMMITS="595504a43209bead162da54a204df7d140a25f0e 63e822b637f67242e3689adedc0155b34100e651"; \ CC=gcc; CXX=g++; \ BASE_DIR="/mnt/my_storage"; DATA_DIR="$BASE_DIR/ShallowBitcoinData"; LOG_DIR="$BASE_DIR/logs"; UTXO_SNAPSHOT_PATH="$BASE_DIR/utxo-910000.dat"; \ (echo ""; for c in $COMMITS; do git fetch -q origin $c && git log -1 --pretty='%h %s' $c || exit 1; done; echo "") && \ for DBCACHE in 4500; do \ (echo "assumeutxo load | 910000 blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(df -T $BASE_DIR | awk 'NR==2{print $2}') | $(lsblk -no ROTA $(df --output=source $BASE_DIR | tail -1) | grep -q 0 && echo SSD || echo HDD)";) &&\ hyperfine \ --sort command \ --runs 3 \ --export-json "$BASE_DIR/assumeutxo-$(sed -E 's/(\w{8})\w+ ?/\1-/g;s/-$//'<<<"$COMMITS")-$DBCACHE-$CC-$(date +%s).json" \ --parameter-list COMMIT ${COMMITS// /,} \ --prepare "killall -9 bitcoind 2>/dev/null; rm -rf $DATA_DIR/blocks $DATA_DIR/chainstate $DATA_DIR/chainstate_snapshot $DATA_DIR/debug.log; git clean -fxd; git reset --hard {COMMIT} && \ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo && ninja -C build bitcoind bitcoin-cli -j2 && \ ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=1 -printtoconsole=0; sleep 20 && \ ./build/bin/bitcoind -datadir=$DATA_DIR -daemon -blocksonly -connect=0 -dbcache=$DBCACHE -printtoconsole=0; sleep 20" \ --conclude "build/bin/bitcoin-cli -datadir=$DATA_DIR stop || true; killall bitcoind || true; sleep 10; \ echo '{COMMIT} | dbcache=$DBCACHE | chainstate: $(find $DATA_DIR/chainstate_snapshot -type f 2>/dev/null | wc -l) files, $(du -sb $DATA_DIR/chainstate_snapshot 2>/dev/null | cut -f1) bytes' >> $DATA_DIR/debug.log; \ cp $DATA_DIR/debug.log $LOG_DIR/debug-assumeutxo-{COMMIT}-dbcache-$DBCACHE-$(date +%s).log" \ "COMPILER=$CC DBCACHE=$DBCACHE ./build/bin/bitcoin-cli -datadir=$DATA_DIR -rpcclienttimeout=0 loadtxoutset $UTXO_SNAPSHOT_PATH"; \ done 595504a432 Merge bitcoin/bitcoin#34236: Add sedited to trusted-keys 63e822b637 validation: make `IsInitialBlockDownload()` lock-free assumeutxo load | 910000 blocks | dbcache 4500 | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | xfs | SSD Benchmark 1: COMPILER=gcc DBCACHE=4500 ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/ShallowBitcoinData -rpcclienttimeout=0 loadtxoutset /mnt/my_storage/utxo-910000.dat (COMMIT = 595504a43209bead162da54a204df7d140a25f0e) Time (mean ± σ): 418.452 s ± 0.461 s [User: 0.001 s, System: 0.001 s] Range (min … max): 418.070 s … 418.964 s 3 runs Benchmark 2: COMPILER=gcc DBCACHE=4500 ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/ShallowBitcoinData -rpcclienttimeout=0 loadtxoutset /mnt/my_storage/utxo-910000.dat (COMMIT = 63e822b637f67242e3689adedc0155b34100e651) Time (mean ± σ): 415.994 s ± 0.294 s [User: 0.001 s, System: 0.001 s] Range (min … max): 415.788 s … 416.330 s 3 runs Relative speed comparison 1.01 ± 0.00 COMPILER=gcc DBCACHE=4500 ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/ShallowBitcoinData -rpcclienttimeout=0 loadtxoutset /mnt/my_storage/utxo-910000.dat (COMMIT = 595504a43209bead162da54a204df7d140a25f0e) 1.00 COMPILER=gcc DBCACHE=4500 ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/ShallowBitcoinData -rpcclienttimeout=0 loadtxoutset /mnt/my_storage/utxo-910000.dat (COMMIT = 63e822b637f67242e3689adedc0155b34100e651) ``` </details> <details> <summary>2026-01-12 | reindex-chainstate | 931139 blocks | dbcache 4500 | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | SSD</summary> ``` for DBCACHE in 4500; do \ COMMITS="595504a43209bead162da54a204df7d140a25f0e 63e822b637f67242e3689adedc0155b34100e651"; \ STOP=931139; CC=gcc; CXX=g++; \ BASE_DIR="/mnt/my_storage"; DATA_DIR="$BASE_DIR/BitcoinData"; LOG_DIR="$BASE_DIR/logs"; \ (echo ""; for c in $COMMITS; do git fetch -q origin $c && git log -1 --pretty='%h %s' $c || exit 1; done) && \ (echo "" && echo "$(date -I) | reindex-chainstate | ${STOP} blocks | dbcache ${DBCACHE} | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | SSD"; echo "") &&\ hyperfine \ --sort command \ --runs 1 \ --export-json "$BASE_DIR/rdx-$(sed -E 's/(\w{8})\w+ ?/\1-/g;s/-$//'<<<"$COMMITS")-$STOP-$DBCACHE-$CC.json" \ --parameter-list COMMIT ${COMMITS// /,} \ --prepare "killall -9 bitcoind 2>/dev/null; rm -f $DATA_DIR/debug.log; git clean -fxd; git reset --hard {COMMIT} && \ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_IPC=OFF && ninja -C build bitcoind -j1 && \ ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=1000 -printtoconsole=0; sleep 20; rm -f $DATA_DIR/debug.log" \ --conclude "killall bitcoind || true; sleep 5; grep -q 'height=0' $DATA_DIR/debug.log && grep -q 'Disabling script verification at block #1' $DATA_DIR/debug.log && grep -q 'height=$STOP' $DATA_DIR/debug.log; \ cp $DATA_DIR/debug.log $LOG_DIR/debug-{COMMIT}-$(date +%s).log" \ "COMPILER=$CC ./build/bin/bitcoind -datadir=$DATA_DIR -stopatheight=$STOP -dbcache=$DBCACHE -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0"; done 595504a432 Merge bitcoin/bitcoin#34236: Add sedited to trusted-keys 63e822b637 validation: make `IsInitialBlockDownload()` lock-free 2026-01-12 | reindex-chainstate | 931139 blocks | dbcache 4500 | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | SSD Benchmark 1: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=931139 -dbcache=4500 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 595504a43209bead162da54a204df7d140a25f0e) Time (abs ≡): 17187.310 s [User: 33104.415 s, System: 937.548 s] Benchmark 2: COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=931139 -dbcache=4500 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 63e822b637f67242e3689adedc0155b34100e651) Time (abs ≡): 17240.300 s [User: 33164.803 s, System: 976.485 s] Relative speed comparison 1.00 COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=931139 -dbcache=4500 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 595504a43209bead162da54a204df7d140a25f0e) 1.00 COMPILER=gcc ./build/bin/bitcoind -datadir=/mnt/my_storage/BitcoinData -stopatheight=931139 -dbcache=4500 -reindex-chainstate -blocksonly -connect=0 -printtoconsole=0 (COMMIT = 63e822b637f67242e3689adedc0155b34100e651) ``` </details> ACKs for top commit: sedited: ACK 557b41a38ccf2929ca1e5271db1701e5fbe781af sipa: utACK 557b41a38ccf2929ca1e5271db1701e5fbe781af mzumsande: Code Review ACK 557b41a38ccf2929ca1e5271db1701e5fbe781af Tree-SHA512: 174015b9785846fc3375bd9d6e4ef91de47ffb659a94d645c49d333a33a32986d5c3cb2eb5a0a7245f96580ed6fea4ba5b7f93cac7e42e3b225f0a01538a2e5c
457 lines
16 KiB
C++
457 lines
16 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-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.
|
|
|
|
#ifndef BITCOIN_CHAIN_H
|
|
#define BITCOIN_CHAIN_H
|
|
|
|
#include <arith_uint256.h>
|
|
#include <consensus/params.h>
|
|
#include <flatfile.h>
|
|
#include <kernel/cs_main.h>
|
|
#include <primitives/block.h>
|
|
#include <serialize.h>
|
|
#include <sync.h>
|
|
#include <uint256.h>
|
|
#include <util/time.h>
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
/**
|
|
* Maximum amount of time that a block timestamp is allowed to exceed the
|
|
* current time before the block will be accepted.
|
|
*/
|
|
static constexpr int64_t MAX_FUTURE_BLOCK_TIME = 2 * 60 * 60;
|
|
|
|
/**
|
|
* Timestamp window used as a grace period by code that compares external
|
|
* timestamps (such as timestamps passed to RPCs, or wallet key creation times)
|
|
* to block timestamps. This should be set at least as high as
|
|
* MAX_FUTURE_BLOCK_TIME.
|
|
*/
|
|
static constexpr int64_t TIMESTAMP_WINDOW = MAX_FUTURE_BLOCK_TIME;
|
|
//! Init values for CBlockIndex nSequenceId when loaded from disk
|
|
static constexpr int32_t SEQ_ID_BEST_CHAIN_FROM_DISK = 0;
|
|
static constexpr int32_t SEQ_ID_INIT_FROM_DISK = 1;
|
|
|
|
enum BlockStatus : uint32_t {
|
|
//! Unused.
|
|
BLOCK_VALID_UNKNOWN = 0,
|
|
|
|
//! Reserved (was BLOCK_VALID_HEADER).
|
|
BLOCK_VALID_RESERVED = 1,
|
|
|
|
//! All parent headers found, difficulty matches, timestamp >= median previous. Implies all parents
|
|
//! are also at least TREE.
|
|
BLOCK_VALID_TREE = 2,
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* 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::m_chain_tx_count 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 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 previous blocks back to the genesis block or an assumeutxo snapshot block
|
|
//! are at least VALID_SCRIPTS.
|
|
BLOCK_VALID_SCRIPTS = 5,
|
|
|
|
//! All validity bits.
|
|
BLOCK_VALID_MASK = BLOCK_VALID_RESERVED | BLOCK_VALID_TREE | BLOCK_VALID_TRANSACTIONS |
|
|
BLOCK_VALID_CHAIN | BLOCK_VALID_SCRIPTS,
|
|
|
|
BLOCK_HAVE_DATA = 8, //!< full block available in blk*.dat
|
|
BLOCK_HAVE_UNDO = 16, //!< undo data available in rev*.dat
|
|
BLOCK_HAVE_MASK = BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO,
|
|
|
|
BLOCK_FAILED_VALID = 32, //!< stage after last reached validness failed
|
|
BLOCK_FAILED_CHILD = 64, //!< descends from failed block
|
|
BLOCK_FAILED_MASK = BLOCK_FAILED_VALID | BLOCK_FAILED_CHILD,
|
|
|
|
BLOCK_OPT_WITNESS = 128, //!< block data in blk*.dat was received with a witness-enforcing client
|
|
|
|
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
|
|
* genesis block at the root, with each block potentially having multiple
|
|
* candidates to be the next block. A blockindex may have multiple pprev pointing
|
|
* to it, but at most one of them can be part of the currently active branch.
|
|
*/
|
|
class CBlockIndex
|
|
{
|
|
public:
|
|
//! pointer to the hash of the block, if any. Memory is owned by this CBlockIndex
|
|
const uint256* phashBlock{nullptr};
|
|
|
|
//! pointer to the index of the predecessor of this block
|
|
CBlockIndex* pprev{nullptr};
|
|
|
|
//! pointer to the index of some further predecessor of this block
|
|
CBlockIndex* pskip{nullptr};
|
|
|
|
//! height of the entry in the chain. The genesis block has height 0
|
|
int nHeight{0};
|
|
|
|
//! Which # file this block is stored in (blk?????.dat)
|
|
int nFile GUARDED_BY(::cs_main){0};
|
|
|
|
//! Byte offset within blk?????.dat where this block's data is stored
|
|
unsigned int nDataPos GUARDED_BY(::cs_main){0};
|
|
|
|
//! Byte offset within rev?????.dat where this block's undo data is stored
|
|
unsigned int nUndoPos GUARDED_BY(::cs_main){0};
|
|
|
|
//! (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. 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
|
|
unsigned int nTx{0};
|
|
|
|
//! (memory only) Number of transactions in the chain up to and including this block.
|
|
//! 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.
|
|
uint64_t m_chain_tx_count{0};
|
|
|
|
//! Verification status of this block. See enum BlockStatus
|
|
//!
|
|
//! Note: this value is modified to show BLOCK_OPT_WITNESS during UTXO snapshot
|
|
//! load to avoid a spurious startup failure requiring -reindex.
|
|
//! @sa NeedsRedownload
|
|
//! @sa ActivateSnapshot
|
|
uint32_t nStatus GUARDED_BY(::cs_main){0};
|
|
|
|
//! block header
|
|
int32_t nVersion{0};
|
|
uint256 hashMerkleRoot{};
|
|
uint32_t nTime{0};
|
|
uint32_t nBits{0};
|
|
uint32_t nNonce{0};
|
|
|
|
//! (memory only) Sequential id assigned to distinguish order in which blocks are received.
|
|
//! Initialized to SEQ_ID_INIT_FROM_DISK{1} when loading blocks from disk, except for blocks
|
|
//! belonging to the best chain which overwrite it to SEQ_ID_BEST_CHAIN_FROM_DISK{0}.
|
|
int32_t nSequenceId{SEQ_ID_INIT_FROM_DISK};
|
|
|
|
//! (memory only) Maximum nTime in the chain up to and including this block.
|
|
unsigned int nTimeMax{0};
|
|
|
|
explicit CBlockIndex(const CBlockHeader& block)
|
|
: nVersion{block.nVersion},
|
|
hashMerkleRoot{block.hashMerkleRoot},
|
|
nTime{block.nTime},
|
|
nBits{block.nBits},
|
|
nNonce{block.nNonce}
|
|
{
|
|
}
|
|
|
|
FlatFilePos GetBlockPos() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
|
|
{
|
|
AssertLockHeld(::cs_main);
|
|
FlatFilePos ret;
|
|
if (nStatus & BLOCK_HAVE_DATA) {
|
|
ret.nFile = nFile;
|
|
ret.nPos = nDataPos;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
FlatFilePos GetUndoPos() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
|
|
{
|
|
AssertLockHeld(::cs_main);
|
|
FlatFilePos ret;
|
|
if (nStatus & BLOCK_HAVE_UNDO) {
|
|
ret.nFile = nFile;
|
|
ret.nPos = nUndoPos;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
CBlockHeader GetBlockHeader() const
|
|
{
|
|
CBlockHeader block;
|
|
block.nVersion = nVersion;
|
|
if (pprev)
|
|
block.hashPrevBlock = pprev->GetBlockHash();
|
|
block.hashMerkleRoot = hashMerkleRoot;
|
|
block.nTime = nTime;
|
|
block.nBits = nBits;
|
|
block.nNonce = nNonce;
|
|
return block;
|
|
}
|
|
|
|
uint256 GetBlockHash() const
|
|
{
|
|
assert(phashBlock != nullptr);
|
|
return *phashBlock;
|
|
}
|
|
|
|
/**
|
|
* 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, since its m_chain_tx_count value will have
|
|
* been set manually based on the related AssumeutxoData entry.
|
|
*/
|
|
bool HaveNumChainTxs() const { return m_chain_tx_count != 0; }
|
|
|
|
NodeSeconds Time() const
|
|
{
|
|
return NodeSeconds{std::chrono::seconds{nTime}};
|
|
}
|
|
|
|
int64_t GetBlockTime() const
|
|
{
|
|
return (int64_t)nTime;
|
|
}
|
|
|
|
int64_t GetBlockTimeMax() const
|
|
{
|
|
return (int64_t)nTimeMax;
|
|
}
|
|
|
|
static constexpr int nMedianTimeSpan = 11;
|
|
|
|
int64_t GetMedianTimePast() const
|
|
{
|
|
int64_t pmedian[nMedianTimeSpan];
|
|
int64_t* pbegin = &pmedian[nMedianTimeSpan];
|
|
int64_t* pend = &pmedian[nMedianTimeSpan];
|
|
|
|
const CBlockIndex* pindex = this;
|
|
for (int i = 0; i < nMedianTimeSpan && pindex; i++, pindex = pindex->pprev)
|
|
*(--pbegin) = pindex->GetBlockTime();
|
|
|
|
std::sort(pbegin, pend);
|
|
return pbegin[(pend - pbegin) / 2];
|
|
}
|
|
|
|
std::string ToString() const;
|
|
|
|
//! Check whether this block index entry is valid up to the passed validity level.
|
|
bool IsValid(enum BlockStatus nUpTo) const
|
|
EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
|
|
{
|
|
AssertLockHeld(::cs_main);
|
|
assert(!(nUpTo & ~BLOCK_VALID_MASK)); // Only validity flags allowed.
|
|
if (nStatus & BLOCK_FAILED_MASK)
|
|
return false;
|
|
return ((nStatus & BLOCK_VALID_MASK) >= nUpTo);
|
|
}
|
|
|
|
//! 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)
|
|
{
|
|
AssertLockHeld(::cs_main);
|
|
assert(!(nUpTo & ~BLOCK_VALID_MASK)); // Only validity flags allowed.
|
|
if (nStatus & BLOCK_FAILED_MASK) return false;
|
|
|
|
if ((nStatus & BLOCK_VALID_MASK) < nUpTo) {
|
|
nStatus = (nStatus & ~BLOCK_VALID_MASK) | nUpTo;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//! Build the skiplist pointer for this entry.
|
|
void BuildSkip();
|
|
|
|
//! Efficiently find an ancestor of this block.
|
|
CBlockIndex* GetAncestor(int height);
|
|
const CBlockIndex* GetAncestor(int height) const;
|
|
|
|
CBlockIndex() = default;
|
|
~CBlockIndex() = default;
|
|
|
|
protected:
|
|
//! CBlockIndex should not allow public copy construction because equality
|
|
//! comparison via pointer is very common throughout the codebase, making
|
|
//! use of copy a footgun. Also, use of copies do not have the benefit
|
|
//! of simplifying lifetime considerations due to attributes like pprev and
|
|
//! pskip, which are at risk of becoming dangling pointers in a copied
|
|
//! instance.
|
|
//!
|
|
//! We declare these protected instead of simply deleting them so that
|
|
//! CDiskBlockIndex can reuse copy construction.
|
|
CBlockIndex(const CBlockIndex&) = default;
|
|
CBlockIndex& operator=(const CBlockIndex&) = delete;
|
|
CBlockIndex(CBlockIndex&&) = delete;
|
|
CBlockIndex& operator=(CBlockIndex&&) = delete;
|
|
};
|
|
|
|
/** Compute how much work an nBits value corresponds to. */
|
|
arith_uint256 GetBitsProof(uint32_t bits);
|
|
|
|
/** Compute how much work a block index entry corresponds to. */
|
|
inline arith_uint256 GetBlockProof(const CBlockIndex& block) { return GetBitsProof(block.nBits); }
|
|
|
|
/** Compute how much work a block header corresponds to. */
|
|
inline arith_uint256 GetBlockProof(const CBlockHeader& header) { return GetBitsProof(header.nBits); }
|
|
|
|
/** Return the time it would take to redo the work difference between from and to, assuming the current hashrate corresponds to the difficulty at tip, in seconds. */
|
|
int64_t GetBlockProofEquivalentTime(const CBlockIndex& to, const CBlockIndex& from, const CBlockIndex& tip, const Consensus::Params&);
|
|
/** Find the forking point between two chain tips. */
|
|
const CBlockIndex* LastCommonAncestor(const CBlockIndex* pa, const CBlockIndex* pb);
|
|
|
|
|
|
/** Used to marshal pointers into hashes for db storage. */
|
|
class CDiskBlockIndex : public CBlockIndex
|
|
{
|
|
/** Historically CBlockLocator's version field has been written to disk
|
|
* streams as the client version, but the value has never been used.
|
|
*
|
|
* Hard-code to the highest client version ever written.
|
|
* SerParams can be used if the field requires any meaning in the future.
|
|
**/
|
|
static constexpr int DUMMY_VERSION = 259900;
|
|
|
|
public:
|
|
uint256 hashPrev;
|
|
|
|
CDiskBlockIndex()
|
|
{
|
|
hashPrev = uint256();
|
|
}
|
|
|
|
explicit CDiskBlockIndex(const CBlockIndex* pindex) : CBlockIndex(*pindex)
|
|
{
|
|
hashPrev = (pprev ? pprev->GetBlockHash() : uint256());
|
|
}
|
|
|
|
SERIALIZE_METHODS(CDiskBlockIndex, obj)
|
|
{
|
|
LOCK(::cs_main);
|
|
int _nVersion = DUMMY_VERSION;
|
|
READWRITE(VARINT_MODE(_nVersion, VarIntMode::NONNEGATIVE_SIGNED));
|
|
|
|
READWRITE(VARINT_MODE(obj.nHeight, VarIntMode::NONNEGATIVE_SIGNED));
|
|
READWRITE(VARINT(obj.nStatus));
|
|
READWRITE(VARINT(obj.nTx));
|
|
if (obj.nStatus & (BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO)) READWRITE(VARINT_MODE(obj.nFile, VarIntMode::NONNEGATIVE_SIGNED));
|
|
if (obj.nStatus & BLOCK_HAVE_DATA) READWRITE(VARINT(obj.nDataPos));
|
|
if (obj.nStatus & BLOCK_HAVE_UNDO) READWRITE(VARINT(obj.nUndoPos));
|
|
|
|
// block header
|
|
READWRITE(obj.nVersion);
|
|
READWRITE(obj.hashPrev);
|
|
READWRITE(obj.hashMerkleRoot);
|
|
READWRITE(obj.nTime);
|
|
READWRITE(obj.nBits);
|
|
READWRITE(obj.nNonce);
|
|
}
|
|
|
|
uint256 ConstructBlockHash() const
|
|
{
|
|
CBlockHeader block;
|
|
block.nVersion = nVersion;
|
|
block.hashPrevBlock = hashPrev;
|
|
block.hashMerkleRoot = hashMerkleRoot;
|
|
block.nTime = nTime;
|
|
block.nBits = nBits;
|
|
block.nNonce = nNonce;
|
|
return block.GetHash();
|
|
}
|
|
|
|
uint256 GetBlockHash() = delete;
|
|
std::string ToString() = delete;
|
|
};
|
|
|
|
/** An in-memory indexed chain of blocks. */
|
|
class CChain
|
|
{
|
|
private:
|
|
std::vector<CBlockIndex*> vChain;
|
|
|
|
public:
|
|
CChain() = default;
|
|
CChain(const CChain&) = delete;
|
|
CChain& operator=(const CChain&) = delete;
|
|
|
|
/** Returns the index entry for the genesis block of this chain, or nullptr if none. */
|
|
CBlockIndex* Genesis() const
|
|
{
|
|
return vChain.size() > 0 ? vChain[0] : nullptr;
|
|
}
|
|
|
|
/** Returns the index entry for the tip of this chain, or nullptr if none. */
|
|
CBlockIndex* Tip() const
|
|
{
|
|
return vChain.size() > 0 ? vChain[vChain.size() - 1] : nullptr;
|
|
}
|
|
|
|
/** Returns the index entry at a particular height in this chain, or nullptr if no such height exists. */
|
|
CBlockIndex* operator[](int nHeight) const
|
|
{
|
|
if (nHeight < 0 || nHeight >= (int)vChain.size())
|
|
return nullptr;
|
|
return vChain[nHeight];
|
|
}
|
|
|
|
/** Efficiently check whether a block is present in this chain. */
|
|
bool Contains(const CBlockIndex* pindex) const
|
|
{
|
|
return (*this)[pindex->nHeight] == pindex;
|
|
}
|
|
|
|
/** Find the successor of a block in this chain, or nullptr if the given index is not found or is the tip. */
|
|
CBlockIndex* Next(const CBlockIndex* pindex) const
|
|
{
|
|
if (Contains(pindex))
|
|
return (*this)[pindex->nHeight + 1];
|
|
else
|
|
return nullptr;
|
|
}
|
|
|
|
/** Return the maximal height in the chain. Is equal to chain.Tip() ? chain.Tip()->nHeight : -1. */
|
|
int Height() const
|
|
{
|
|
return int(vChain.size()) - 1;
|
|
}
|
|
|
|
/** Check whether this chain's tip exists, has enough work, and is recent. */
|
|
bool IsTipRecent(const arith_uint256& min_chain_work, std::chrono::seconds max_tip_age) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
|
|
{
|
|
const auto tip{Tip()};
|
|
return tip &&
|
|
tip->nChainWork >= min_chain_work &&
|
|
tip->Time() >= Now<NodeSeconds>() - max_tip_age;
|
|
}
|
|
|
|
/** Set/initialize a chain with a given tip. */
|
|
void SetTip(CBlockIndex& block);
|
|
|
|
/** Find the last common block between this chain and a block index entry. */
|
|
const CBlockIndex* FindFork(const CBlockIndex* pindex) const;
|
|
|
|
/** Find the earliest block with timestamp equal or greater than the given time and height equal or greater than the given height. */
|
|
CBlockIndex* FindEarliestAtLeast(int64_t nTime, int height) const;
|
|
};
|
|
|
|
/** Get a locator for a block index entry. */
|
|
CBlockLocator GetLocator(const CBlockIndex* index);
|
|
|
|
/** Construct a list of hash entries to put in a locator. */
|
|
std::vector<uint256> LocatorEntries(const CBlockIndex* index);
|
|
|
|
#endif // BITCOIN_CHAIN_H
|