Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13116018
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
234 KB
Subscribers
None
View Options
diff --git a/src/validation.cpp b/src/validation.cpp
index f28df40329..8f9c79aed9 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1,5627 +1,5631 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Copyright (c) 2017-2018 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 <arith_uint256.h>
#include <blockindexworkcomparator.h>
#include <blockvalidity.h>
#include <chainparams.h>
#include <checkpoints.h>
#include <checkqueue.h>
#include <config.h>
#include <consensus/activation.h>
#include <consensus/consensus.h>
#include <consensus/merkle.h>
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
#include <flatfile.h>
#include <fs.h>
#include <hash.h>
#include <index/txindex.h>
#include <init.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <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 <script/standard.h>
#include <timedata.h>
#include <tinyformat.h>
#include <txdb.h>
#include <txmempool.h>
#include <ui_interface.h>
#include <undo.h>
#include <util.h>
#include <utilmoneystr.h>
#include <utilstrencodings.h>
#include <validationinterface.h>
#include <warnings.h>
#include <boost/algorithm/string/replace.hpp>
#include <boost/thread.hpp> // boost::this_thread::interruption_point() (mingw)
#include <atomic>
#include <future>
#include <sstream>
#include <thread>
#if defined(NDEBUG)
#error "Bitcoin cannot be compiled without assertions."
#endif
#define MICRO 0.000001
#define MILLI 0.001
class ConnectTrace;
/**
* CChainState stores and provides an API to update our local knowledge of the
* current best chain and header tree.
*
* It generally provides access to the current block tree, as well as functions
* to provide new data, which it will appropriately validate and incorporate in
* its state as necessary.
*
* 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).
*/
class CChainState {
private:
/**
* The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for
* itself and all ancestors) and as good as our current tip or better.
* Entries may be failed, though, and pruning nodes may be missing the data
* for the block.
*/
std::set<CBlockIndex *, CBlockIndexWorkComparator> setBlockIndexCandidates;
/**
* the ChainState CriticalSection
* A lock that must be held when modifying this ChainState - held in
* ActivateBestChain()
*/
CCriticalSection m_cs_chainstate;
/**
* 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;
/**
* 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 mapBlockIndex 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 mapBlockIndex 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 mapBlockIndex 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;
public:
CChain chainActive;
BlockMap mapBlockIndex;
std::multimap<CBlockIndex *, CBlockIndex *> mapBlocksUnlinked;
CBlockIndex *pindexBestInvalid = nullptr;
CBlockIndex *pindexBestParked = nullptr;
bool LoadBlockIndex(const Config &config, CBlockTreeDB &blocktree);
bool ActivateBestChain(
const Config &config, CValidationState &state,
std::shared_ptr<const CBlock> pblock = std::shared_ptr<const CBlock>());
bool AcceptBlockHeader(const Config &config, const CBlockHeader &block,
CValidationState &state, CBlockIndex **ppindex);
bool AcceptBlock(const Config &config,
const std::shared_ptr<const CBlock> &pblock,
CValidationState &state, bool fRequested,
const CDiskBlockPos *dbp, bool *fNewBlock);
// Block (dis)connection on a given view:
DisconnectResult DisconnectBlock(const CBlock &block,
const CBlockIndex *pindex,
CCoinsViewCache &view);
bool ConnectBlock(const Config &config, const CBlock &block,
CValidationState &state, CBlockIndex *pindex,
CCoinsViewCache &view, bool fJustCheck = false);
// Block disconnection on our pcoinsTip:
bool DisconnectTip(const Config &config, CValidationState &state,
DisconnectedBlockTransactions *disconnectpool);
// Manual block validity manipulation:
bool PreciousBlock(const Config &config, CValidationState &state,
CBlockIndex *pindex);
bool UnwindBlock(const Config &config, CValidationState &state,
CBlockIndex *pindex, bool invalidate);
bool ResetBlockFailureFlags(CBlockIndex *pindex);
template <typename F>
void UpdateFlagsForBlock(CBlockIndex *pindexBase, CBlockIndex *pindex, F f);
template <typename F, typename C>
void UpdateFlags(CBlockIndex *pindex, F f, C fchild);
template <typename F> void UpdateFlags(CBlockIndex *pindex, F f);
/** Remove parked status from a block and its descendants. */
bool UnparkBlockImpl(CBlockIndex *pindex, bool fClearChildren);
bool ReplayBlocks(const Config &config, CCoinsView *view);
bool RewindBlockIndex(const Config &config);
bool LoadGenesisBlock(const CChainParams &chainparams);
void PruneBlockIndexCandidates();
void UnloadBlockIndex();
private:
bool ActivateBestChainStep(const Config &config, CValidationState &state,
CBlockIndex *pindexMostWork,
const std::shared_ptr<const CBlock> &pblock,
bool &fInvalidFound, ConnectTrace &connectTrace);
bool ConnectTip(const Config &config, CValidationState &state,
CBlockIndex *pindexNew,
const std::shared_ptr<const CBlock> &pblock,
ConnectTrace &connectTrace,
DisconnectedBlockTransactions &disconnectpool);
CBlockIndex *AddToBlockIndex(const CBlockHeader &block);
/** Create a new block index entry for a given block hash */
CBlockIndex *InsertBlockIndex(const uint256 &hash);
void CheckBlockIndex(const Consensus::Params &consensusParams);
void InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state);
CBlockIndex *FindMostWorkChain();
bool ReceivedBlockTransactions(const CBlock &block, CValidationState &state,
CBlockIndex *pindexNew,
const CDiskBlockPos &pos);
bool RollforwardBlock(const CBlockIndex *pindex, CCoinsViewCache &inputs,
const Config &config);
} g_chainstate;
/**
* Global state
*/
CCriticalSection cs_main;
BlockMap &mapBlockIndex = g_chainstate.mapBlockIndex;
CChain &chainActive = g_chainstate.chainActive;
CBlockIndex *pindexBestHeader = nullptr;
Mutex g_best_block_mutex;
std::condition_variable g_best_block_cv;
uint256 g_best_block;
int nScriptCheckThreads = 0;
std::atomic_bool fImporting(false);
std::atomic_bool fReindex(false);
bool fHavePruned = false;
bool fPruneMode = false;
bool fIsBareMultisigStd = DEFAULT_PERMIT_BAREMULTISIG;
bool fRequireStandard = true;
bool fCheckBlockIndex = false;
bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED;
size_t nCoinCacheUsage = 5000 * 300;
uint64_t nPruneTarget = 0;
int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
uint256 hashAssumeValid;
arith_uint256 nMinimumChainWork;
CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE_PER_KB);
Amount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
CTxMemPool g_mempool;
std::atomic_bool g_is_mempool_loaded{false};
/** Constant stuff for coinbase transactions we create: */
CScript COINBASE_FLAGS;
const std::string strMessageMagic = "Bitcoin Signed Message:\n";
// Internal stuff
namespace {
CBlockIndex *&pindexBestInvalid = g_chainstate.pindexBestInvalid;
CBlockIndex *&pindexBestParked = g_chainstate.pindexBestParked;
/**
* The best finalized block.
* This block cannot be reorged in any way, shape or form.
*/
CBlockIndex const *pindexFinalized;
/**
* All pairs A->B, where A (or one of its ancestors) misses transactions, but B
* has transactions. Pruned nodes may have entries where B is missing data.
*/
std::multimap<CBlockIndex *, CBlockIndex *> &mapBlocksUnlinked =
g_chainstate.mapBlocksUnlinked;
CCriticalSection cs_LastBlockFile;
std::vector<CBlockFileInfo> vinfoBlockFile;
int nLastBlockFile = 0;
/**
* Global flag to indicate we should check to see if there are block/undo files
* that should be deleted. Set on startup or if we allocate more file space when
* we're in prune mode.
*/
bool fCheckForPruning = false;
/** Dirty block index entries. */
std::set<const CBlockIndex *> setDirtyBlockIndex;
/** Dirty block file entries. */
std::set<int> setDirtyFileInfo;
} // namespace
CBlockIndex *FindForkInGlobalIndex(const CChain &chain,
const CBlockLocator &locator) {
AssertLockHeld(cs_main);
// Find the first block the caller has in the main chain
for (const uint256 &hash : locator.vHave) {
CBlockIndex *pindex = LookupBlockIndex(hash);
if (pindex) {
if (chain.Contains(pindex)) {
return pindex;
}
if (pindex->GetAncestor(chain.Height()) == chain.Tip()) {
return chain.Tip();
}
}
}
return chain.Genesis();
}
std::unique_ptr<CCoinsViewDB> pcoinsdbview;
std::unique_ptr<CCoinsViewCache> pcoinsTip;
std::unique_ptr<CBlockTreeDB> pblocktree;
enum class FlushStateMode { NONE, IF_NEEDED, PERIODIC, ALWAYS };
// See definition for documentation
static bool FlushStateToDisk(const CChainParams &chainParams,
CValidationState &state, FlushStateMode mode,
int nManualPruneHeight = 0);
static void FindFilesToPruneManual(std::set<int> &setFilesToPrune,
int nManualPruneHeight);
static void FindFilesToPrune(std::set<int> &setFilesToPrune,
uint64_t nPruneAfterHeight);
static FILE *OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly = false);
static FlatFileSeq BlockFileSeq();
static FlatFileSeq UndoFileSeq();
static uint32_t GetNextBlockScriptFlags(const Config &config,
const CBlockIndex *pindex);
bool TestLockPointValidity(const LockPoints *lp) {
AssertLockHeld(cs_main);
assert(lp);
// If there are relative lock times then the maxInputBlock will be set
// If there are no relative lock times, the LockPoints don't depend on the
// chain
if (lp->maxInputBlock) {
// Check whether chainActive is an extension of the block at which the
// LockPoints calculation was valid. If not LockPoints are no longer
// valid.
if (!chainActive.Contains(lp->maxInputBlock)) {
return false;
}
}
// LockPoints still valid
return true;
}
bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints *lp,
bool useExistingLockPoints) {
AssertLockHeld(cs_main);
AssertLockHeld(g_mempool.cs);
CBlockIndex *tip = chainActive.Tip();
assert(tip != nullptr);
CBlockIndex index;
index.pprev = tip;
// CheckSequenceLocks() uses chainActive.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 chainActive.Height()
index.nHeight = tip->nHeight + 1;
std::pair<int, int64_t> lockPair;
if (useExistingLockPoints) {
assert(lp);
lockPair.first = lp->height;
lockPair.second = lp->time;
} else {
// pcoinsTip contains the UTXO set for chainActive.Tip()
CCoinsViewMemPool viewMemPool(pcoinsTip.get(), g_mempool);
std::vector<int> prevheights;
prevheights.resize(tx.vin.size());
for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
const CTxIn &txin = tx.vin[txinIndex];
Coin coin;
if (!viewMemPool.GetCoin(txin.prevout, coin)) {
return error("%s: Missing input", __func__);
}
if (coin.GetHeight() == MEMPOOL_HEIGHT) {
// Assume all mempool transaction confirm in the next block
prevheights[txinIndex] = tip->nHeight + 1;
} else {
prevheights[txinIndex] = coin.GetHeight();
}
}
lockPair = CalculateSequenceLocks(tx, flags, &prevheights, index);
if (lp) {
lp->height = lockPair.first;
lp->time = lockPair.second;
// Also store the hash of the block with the highest height of all
// the blocks which have sequence locked prevouts. This hash needs
// to still be on the chain for these LockPoint calculations to be
// valid.
// Note: It is impossible to correctly calculate a maxInputBlock if
// any of the sequence locked inputs depend on unconfirmed txs,
// except in the special case where the relative lock time/height is
// 0, which is equivalent to no sequence lock. Since we assume input
// height of tip+1 for mempool txs and test the resulting lockPair
// from CalculateSequenceLocks against tip+1. We know
// EvaluateSequenceLocks will fail if there was a non-zero sequence
// lock on a mempool input, so we can use the return value of
// CheckSequenceLocks to indicate the LockPoints validity.
int maxInputHeight = 0;
for (int height : prevheights) {
// Can ignore mempool inputs since we'll fail if they had
// non-zero locks.
if (height != tip->nHeight + 1) {
maxInputHeight = std::max(maxInputHeight, height);
}
}
lp->maxInputBlock = tip->GetAncestor(maxInputHeight);
}
}
return EvaluateSequenceLocks(index, lockPair);
}
/** Convert CValidationState to a human-readable message for logging */
std::string FormatStateMessage(const CValidationState &state) {
return strprintf(
"%s%s (code %i)", state.GetRejectReason(),
state.GetDebugMessage().empty() ? "" : ", " + state.GetDebugMessage(),
state.GetRejectCode());
}
static bool IsMagneticAnomalyEnabledForCurrentBlock(const Config &config) {
AssertLockHeld(cs_main);
return IsMagneticAnomalyEnabled(config, chainActive.Tip());
}
// 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 Config &config,
int64_t nMedianTimePast) {
return nMedianTimePast >=
gArgs.GetArg(
"-replayprotectionactivationtime",
config.GetChainParams().GetConsensus().gravitonActivationTime);
}
static bool IsReplayProtectionEnabled(const Config &config,
const CBlockIndex *pindexPrev) {
if (pindexPrev == nullptr) {
return false;
}
return IsReplayProtectionEnabled(config, pindexPrev->GetMedianTimePast());
}
static bool IsReplayProtectionEnabledForCurrentBlock(const Config &config) {
AssertLockHeld(cs_main);
return IsReplayProtectionEnabled(config, chainActive.Tip());
}
// Used to avoid mempool polluting consensus critical paths if CCoinsViewMempool
// were somehow broken and returning the wrong scriptPubKeys
static bool CheckInputsFromMempoolAndCache(
const CTransaction &tx, CValidationState &state,
const CCoinsViewCache &view, const CTxMemPool &pool, const uint32_t flags,
bool cacheSigStore, PrecomputedTransactionData &txdata) {
AssertLockHeld(cs_main);
// pool.cs should be locked already, but go ahead and re-take the lock here
// to enforce that mempool doesn't change between when we check the view and
// when we actually call through to CheckInputs
LOCK(pool.cs);
assert(!tx.IsCoinBase());
for (const CTxIn &txin : tx.vin) {
const Coin &coin = view.AccessCoin(txin.prevout);
// At this point we haven't actually checked if the coins are all
// available (or shouldn't assume we have, since CheckInputs does). So
// we just return failure if the inputs are not available here, and then
// only have to check equivalence for available inputs.
if (coin.IsSpent()) {
return false;
}
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 &coinFromDisk = pcoinsTip->AccessCoin(txin.prevout);
assert(!coinFromDisk.IsSpent());
assert(coinFromDisk.GetTxOut() == coin.GetTxOut());
}
}
return CheckInputs(tx, state, view, true, flags, cacheSigStore, true,
txdata);
}
static bool AcceptToMemoryPoolWorker(
const Config &config, CTxMemPool &pool, CValidationState &state,
const CTransactionRef &ptx, bool fLimitFree, bool *pfMissingInputs,
int64_t nAcceptTime, bool fOverrideMempoolLimit, const Amount nAbsurdFee,
std::vector<COutPoint> &coins_to_uncache, bool test_accept) {
AssertLockHeld(cs_main);
const CTransaction &tx = *ptx;
const TxId txid = tx.GetId();
// mempool "read lock" (held through
// GetMainSignals().TransactionAddedToMempool())
LOCK(pool.cs);
if (pfMissingInputs) {
*pfMissingInputs = false;
}
// 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/-regtest)
std::string reason;
if (fRequireStandard && !IsStandardTx(tx, reason)) {
return state.DoS(0, false, REJECT_NONSTANDARD, 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.
CValidationState ctxState;
if (!ContextualCheckTransactionForCurrentBlock(
config, tx, ctxState, STANDARD_LOCKTIME_VERIFY_FLAGS)) {
// 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.DoS(
0, false, REJECT_NONSTANDARD, ctxState.GetRejectReason(),
ctxState.CorruptionPossible(), ctxState.GetDebugMessage());
}
// Is it already in the memory pool?
if (pool.exists(txid)) {
return state.Invalid(false, REJECT_DUPLICATE, "txn-already-in-mempool");
}
// Check for conflicts with in-memory transactions
for (const CTxIn &txin : tx.vin) {
auto itConflicting = pool.mapNextTx.find(txin.prevout);
if (itConflicting != pool.mapNextTx.end()) {
// Disable replacement feature for good
return state.Invalid(false, REJECT_DUPLICATE,
"txn-mempool-conflict");
}
}
{
CCoinsView dummy;
CCoinsViewCache view(&dummy);
LockPoints lp;
CCoinsViewMemPool viewMemPool(pcoinsTip.get(), pool);
view.SetBackend(viewMemPool);
// Do all inputs exist?
for (const CTxIn txin : tx.vin) {
if (!pcoinsTip->HaveCoinInCache(txin.prevout)) {
coins_to_uncache.push_back(txin.prevout);
}
if (!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 (pcoinsTip->HaveCoinInCache(COutPoint(txid, out))) {
return state.Invalid(false, REJECT_DUPLICATE,
"txn-already-known");
}
}
// Otherwise assume this might be an orphan tx for which we just
// haven't seen parents yet.
if (pfMissingInputs) {
*pfMissingInputs = true;
}
// fMissingInputs and !state.IsInvalid() is used to detect this
// condition, don't set state.Invalid()
return false;
}
}
// Are the actual inputs available?
if (!view.HaveInputs(tx)) {
return state.Invalid(false, REJECT_DUPLICATE,
"bad-txns-inputs-spent");
}
// Bring the best block into scope.
view.GetBestBlock();
// We have all inputs cached now, so switch back to dummy, so we don't
// need to keep lock on mempool.
view.SetBackend(dummy);
// 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. Must keep pool.cs for this unless we change
// CheckSequenceLocks to take a CoinsViewCache instead of create its
// own.
if (!CheckSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp)) {
return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final");
}
Amount nFees = Amount::zero();
if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view),
nFees)) {
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__,
tx.GetId().ToString(), FormatStateMessage(state));
}
// Check for non-standard pay-to-script-hash in inputs
if (fRequireStandard && !AreInputsStandard(tx, view)) {
return state.Invalid(false, REJECT_NONSTANDARD,
"bad-txns-nonstandard-inputs");
}
int64_t nSigOpsCount =
GetTransactionSigOpCount(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS);
// nModifiedFees includes any fee deltas from PrioritiseTransaction
Amount nModifiedFees = nFees;
double nPriorityDummy = 0;
pool.ApplyDeltas(txid, nPriorityDummy, nModifiedFees);
Amount inChainInputValue;
double dPriority =
view.GetPriority(tx, chainActive.Height(), inChainInputValue);
// Keep track of transactions that spend a coinbase, which we re-scan
// during reorgs to ensure COINBASE_MATURITY is still met.
bool fSpendsCoinbase = false;
for (const CTxIn &txin : tx.vin) {
const Coin &coin = view.AccessCoin(txin.prevout);
if (coin.IsCoinBase()) {
fSpendsCoinbase = true;
break;
}
}
CTxMemPoolEntry entry(ptx, nFees, nAcceptTime, dPriority,
chainActive.Height(), inChainInputValue,
fSpendsCoinbase, nSigOpsCount, lp);
unsigned int nSize = entry.GetTxSize();
// Check that the transaction doesn't have an excessive number of
// sigops, making it impossible to mine. Since the coinbase transaction
// itself can contain sigops MAX_STANDARD_TX_SIGOPS is less than
// MAX_BLOCK_SIGOPS_PER_MB; we still consider this an invalid rather
// than merely non-standard transaction.
if (nSigOpsCount > MAX_STANDARD_TX_SIGOPS) {
return state.DoS(0, false, REJECT_NONSTANDARD,
"bad-txns-too-many-sigops", false,
strprintf("%d", nSigOpsCount));
}
Amount mempoolRejectFee =
pool.GetMinFee(
gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) *
1000000)
.GetFee(nSize);
if (mempoolRejectFee > Amount::zero() &&
nModifiedFees < mempoolRejectFee) {
return state.DoS(
0, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met",
false, strprintf("%d < %d", nModifiedFees, mempoolRejectFee));
}
if (gArgs.GetBoolArg("-relaypriority", DEFAULT_RELAYPRIORITY) &&
nModifiedFees < minRelayTxFee.GetFee(nSize) &&
!AllowFree(entry.GetPriority(chainActive.Height() + 1))) {
// Require that free transactions have sufficient priority to be
// mined in the next block.
return state.DoS(0, false, REJECT_INSUFFICIENTFEE,
"insufficient priority");
}
// Continuously rate-limit free (really, very-low-fee) transactions.
// This mitigates 'penny-flooding' -- sending thousands of free
// transactions just to be annoying or make others' transactions take
// longer to confirm.
if (fLimitFree && nModifiedFees < minRelayTxFee.GetFee(nSize)) {
static CCriticalSection csFreeLimiter;
static double dFreeCount;
static int64_t nLastTime;
int64_t nNow = GetTime();
LOCK(csFreeLimiter);
// Use an exponentially decaying ~10-minute window:
dFreeCount *= pow(1.0 - 1.0 / 600.0, double(nNow - nLastTime));
nLastTime = nNow;
// -limitfreerelay unit is thousand-bytes-per-minute
// At default rate it would take over a month to fill 1GB
// NOTE: Use the actual size here, and not the fee size since this
// is counting real size for the rate limiter.
if (dFreeCount + nSize >=
gArgs.GetArg("-limitfreerelay", DEFAULT_LIMITFREERELAY) * 10 *
1000) {
return state.DoS(0, false, REJECT_INSUFFICIENTFEE,
"rate limited free transaction");
}
LogPrint(BCLog::MEMPOOL, "Rate limit dFreeCount: %g => %g\n",
dFreeCount, dFreeCount + nSize);
dFreeCount += nSize;
}
if (nAbsurdFee != Amount::zero() && nFees > nAbsurdFee) {
return state.Invalid(false, REJECT_HIGHFEE, "absurdly-high-fee",
strprintf("%d > %d", nFees, nAbsurdFee));
}
// Calculate in-mempool ancestors, up to a limit.
CTxMemPool::setEntries setAncestors;
size_t nLimitAncestors =
gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
size_t nLimitAncestorSize =
gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) *
1000;
size_t nLimitDescendants =
gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
size_t nLimitDescendantSize =
gArgs.GetArg("-limitdescendantsize",
DEFAULT_DESCENDANT_SIZE_LIMIT) *
1000;
std::string errString;
if (!pool.CalculateMemPoolAncestors(
entry, setAncestors, nLimitAncestors, nLimitAncestorSize,
nLimitDescendants, nLimitDescendantSize, errString)) {
return state.DoS(0, false, REJECT_NONSTANDARD,
"too-long-mempool-chain", false, errString);
}
// Set extraFlags as a set of flags that needs to be activated.
uint32_t extraFlags = SCRIPT_VERIFY_NONE;
if (IsReplayProtectionEnabledForCurrentBlock(config)) {
extraFlags |= SCRIPT_ENABLE_REPLAY_PROTECTION;
}
if (IsMagneticAnomalyEnabledForCurrentBlock(config)) {
extraFlags |= SCRIPT_VERIFY_CHECKDATASIG_SIGOPS;
}
// Make sure whatever we need to activate is actually activated.
const uint32_t scriptVerifyFlags =
STANDARD_SCRIPT_VERIFY_FLAGS | extraFlags;
// Check against previous transactions. This is done last to help
// prevent CPU exhaustion denial-of-service attacks.
PrecomputedTransactionData txdata(tx);
if (!CheckInputs(tx, state, view, true, scriptVerifyFlags, true, false,
txdata)) {
// State filled in by CheckInputs.
return false;
}
// 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.
uint32_t nextBlockScriptVerifyFlags =
GetNextBlockScriptFlags(config, chainActive.Tip());
if (!CheckInputsFromMempoolAndCache(tx, state, view, pool,
nextBlockScriptVerifyFlags, true,
txdata)) {
return error("%s: BUG! PLEASE REPORT THIS! CheckInputs failed "
"against next-block but not STANDARD flags %s, %s",
__func__, txid.ToString(), FormatStateMessage(state));
}
if (test_accept) {
// Tx was accepted, but not added
return true;
}
// Store transaction in memory.
pool.addUnchecked(txid, entry, setAncestors);
// Trim mempool and check if tx was trimmed.
if (!fOverrideMempoolLimit) {
pool.LimitSize(
gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000,
gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 *
60);
if (!pool.exists(txid)) {
return state.DoS(0, false, REJECT_INSUFFICIENTFEE,
"mempool full");
}
}
}
GetMainSignals().TransactionAddedToMempool(ptx);
return true;
}
/**
* (try to) add transaction to memory pool with a specified acceptance time.
*/
static bool
AcceptToMemoryPoolWithTime(const Config &config, CTxMemPool &pool,
CValidationState &state, const CTransactionRef &tx,
bool fLimitFree, bool *pfMissingInputs,
int64_t nAcceptTime, bool fOverrideMempoolLimit,
const Amount nAbsurdFee, bool test_accept) {
std::vector<COutPoint> coins_to_uncache;
bool res = AcceptToMemoryPoolWorker(
config, pool, state, tx, fLimitFree, pfMissingInputs, nAcceptTime,
fOverrideMempoolLimit, nAbsurdFee, coins_to_uncache, test_accept);
if (!res) {
for (const COutPoint &outpoint : coins_to_uncache) {
pcoinsTip->Uncache(outpoint);
}
}
// After we've (potentially) uncached entries, ensure our coins cache is
// still within its size limits
CValidationState stateDummy;
FlushStateToDisk(config.GetChainParams(), stateDummy,
FlushStateMode::PERIODIC);
return res;
}
bool AcceptToMemoryPool(const Config &config, CTxMemPool &pool,
CValidationState &state, const CTransactionRef &tx,
bool fLimitFree, bool *pfMissingInputs,
bool fOverrideMempoolLimit, const Amount nAbsurdFee,
bool test_accept) {
return AcceptToMemoryPoolWithTime(
config, pool, state, tx, fLimitFree, pfMissingInputs, GetTime(),
fOverrideMempoolLimit, nAbsurdFee, test_accept);
}
/**
* Return transaction in txOut, and if it was found inside a block, its hash is
* placed in hashBlock. If blockIndex is provided, the transaction is fetched
* from the corresponding block.
*/
bool GetTransaction(const Config &config, const TxId &txid,
CTransactionRef &txOut, uint256 &hashBlock, bool fAllowSlow,
CBlockIndex *blockIndex) {
CBlockIndex *pindexSlow = blockIndex;
LOCK(cs_main);
if (!blockIndex) {
CTransactionRef ptx = g_mempool.get(txid);
if (ptx) {
txOut = ptx;
return true;
}
if (g_txindex) {
return g_txindex->FindTx(txid, hashBlock, txOut);
}
// use coin database to locate block that contains transaction, and scan
// it
if (fAllowSlow) {
const Coin &coin = AccessByTxid(*pcoinsTip, txid);
if (!coin.IsSpent()) {
pindexSlow = chainActive[coin.GetHeight()];
}
}
}
if (pindexSlow) {
CBlock block;
if (ReadBlockFromDisk(block, pindexSlow, config)) {
for (const auto &tx : block.vtx) {
if (tx->GetId() == txid) {
txOut = tx;
hashBlock = pindexSlow->GetBlockHash();
return true;
}
}
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////////
//
// CBlock and CBlockIndex
//
static bool WriteBlockToDisk(const CBlock &block, CDiskBlockPos &pos,
const CMessageHeader::MessageMagic &messageStart) {
// Open history file to append
CAutoFile fileout(OpenBlockFile(pos), SER_DISK, CLIENT_VERSION);
if (fileout.IsNull()) {
return error("WriteBlockToDisk: OpenBlockFile failed");
}
// Write index header
unsigned int nSize = GetSerializeSize(fileout, block);
fileout << FLATDATA(messageStart) << nSize;
// Write block
long fileOutPos = ftell(fileout.Get());
if (fileOutPos < 0) {
return error("WriteBlockToDisk: ftell failed");
}
pos.nPos = (unsigned int)fileOutPos;
fileout << block;
return true;
}
bool ReadBlockFromDisk(CBlock &block, const CDiskBlockPos &pos,
const Config &config) {
block.SetNull();
// Open history file to read
CAutoFile filein(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION);
if (filein.IsNull()) {
return error("ReadBlockFromDisk: OpenBlockFile failed for %s",
pos.ToString());
}
// Read block
try {
filein >> block;
} catch (const std::exception &e) {
return error("%s: Deserialize or I/O error - %s at %s", __func__,
e.what(), pos.ToString());
}
// Check the header
if (!CheckProofOfWork(block.GetHash(), block.nBits, config)) {
return error("ReadBlockFromDisk: Errors in block header at %s",
pos.ToString());
}
return true;
}
bool ReadBlockFromDisk(CBlock &block, const CBlockIndex *pindex,
const Config &config) {
CDiskBlockPos blockPos;
{
LOCK(cs_main);
blockPos = pindex->GetBlockPos();
}
if (!ReadBlockFromDisk(block, blockPos, config)) {
return false;
}
if (block.GetHash() != pindex->GetBlockHash()) {
return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() "
"doesn't match index for %s at %s",
pindex->ToString(), pindex->GetBlockPos().ToString());
}
return true;
}
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;
}
bool IsInitialBlockDownload() {
// Once this function has returned false, it must remain false.
static std::atomic<bool> latchToFalse{false};
// Optimization: pre-test latch before taking the lock.
if (latchToFalse.load(std::memory_order_relaxed)) {
return false;
}
LOCK(cs_main);
if (latchToFalse.load(std::memory_order_relaxed)) {
return false;
}
if (fImporting || fReindex) {
return true;
}
if (chainActive.Tip() == nullptr) {
return true;
}
if (chainActive.Tip()->nChainWork < nMinimumChainWork) {
return true;
}
if (chainActive.Tip()->GetBlockTime() < (GetTime() - nMaxTipAge)) {
return true;
}
LogPrintf("Leaving InitialBlockDownload (latching to false)\n");
latchToFalse.store(true, std::memory_order_relaxed);
return false;
}
CBlockIndex const *pindexBestForkTip = nullptr;
CBlockIndex const *pindexBestForkBase = nullptr;
static void AlertNotify(const std::string &strMessage) {
uiInterface.NotifyAlertChanged();
std::string strCmd = gArgs.GetArg("-alertnotify", "");
if (strCmd.empty()) {
return;
}
// Alert text should be plain ascii coming from a trusted source, but to be
// safe we first strip anything not in safeChars, then add single quotes
// around the whole string before passing it to the shell:
std::string singleQuote("'");
std::string safeStatus = SanitizeString(strMessage);
safeStatus = singleQuote + safeStatus + singleQuote;
boost::replace_all(strCmd, "%s", safeStatus);
std::thread t(runCommand, strCmd);
// thread runs free
t.detach();
}
static void 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 (IsInitialBlockDownload()) {
return;
}
// If our best fork is no longer within 72 blocks (+/- 12 hours if no one
// mines it) of our head, drop it
if (pindexBestForkTip &&
chainActive.Height() - pindexBestForkTip->nHeight >= 72) {
pindexBestForkTip = nullptr;
}
if (pindexBestForkTip ||
(pindexBestInvalid &&
pindexBestInvalid->nChainWork >
chainActive.Tip()->nChainWork +
(GetBlockProof(*chainActive.Tip()) * 6))) {
if (!GetfLargeWorkForkFound() && pindexBestForkBase) {
std::string warning =
std::string("'Warning: Large-work fork detected, forking after "
"block ") +
pindexBestForkBase->phashBlock->ToString() + std::string("'");
AlertNotify(warning);
}
if (pindexBestForkTip && pindexBestForkBase) {
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__, pindexBestForkBase->nHeight,
pindexBestForkBase->phashBlock->ToString(),
pindexBestForkTip->nHeight,
pindexBestForkTip->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);
}
}
static void
CheckForkWarningConditionsOnNewFork(const CBlockIndex *pindexNewForkTip) {
AssertLockHeld(cs_main);
// If we are on a fork that is sufficiently large, set a warning flag.
const CBlockIndex *pfork = chainActive.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 &&
(!pindexBestForkTip ||
pindexNewForkTip->nHeight > pindexBestForkTip->nHeight) &&
pindexNewForkTip->nChainWork - pfork->nChainWork >
(GetBlockProof(*pfork) * 7) &&
chainActive.Height() - pindexNewForkTip->nHeight < 72) {
pindexBestForkTip = pindexNewForkTip;
pindexBestForkBase = pfork;
}
CheckForkWarningConditions();
}
static void InvalidChainFound(CBlockIndex *pindexNew) {
if (!pindexBestInvalid ||
pindexNew->nChainWork > pindexBestInvalid->nChainWork) {
pindexBestInvalid = pindexNew;
}
// If the invalid chain found is supposed to be finalized, we need to move
// back the finalization point.
if (IsBlockFinalized(pindexNew)) {
pindexFinalized = pindexNew->pprev;
}
LogPrintf("%s: invalid block=%s height=%d log2_work=%.8g date=%s\n",
__func__, pindexNew->GetBlockHash().ToString(),
pindexNew->nHeight,
log(pindexNew->nChainWork.getdouble()) / log(2.0),
FormatISO8601DateTime(pindexNew->GetBlockTime()));
CBlockIndex *tip = chainActive.Tip();
assert(tip);
LogPrintf("%s: current best=%s height=%d log2_work=%.8g date=%s\n",
__func__, tip->GetBlockHash().ToString(), chainActive.Height(),
log(tip->nChainWork.getdouble()) / log(2.0),
FormatISO8601DateTime(tip->GetBlockTime()));
}
void CChainState::InvalidBlockFound(CBlockIndex *pindex,
const CValidationState &state) {
if (!state.CorruptionPossible()) {
pindex->nStatus = pindex->nStatus.withFailed();
m_failed_blocks.insert(pindex);
setDirtyBlockIndex.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);
}
void UpdateCoins(CCoinsViewCache &view, const CTransaction &tx, int nHeight) {
// Mark inputs spent.
if (!tx.IsCoinBase()) {
for (const CTxIn &txin : tx.vin) {
bool is_spent = view.SpendCoin(txin.prevout);
assert(is_spent);
}
}
// Add outputs.
AddCoins(view, tx, nHeight);
}
bool CScriptCheck::operator()() {
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
return VerifyScript(scriptSig, scriptPubKey, nFlags,
CachingTransactionSignatureChecker(ptxTo, nIn, amount,
cacheStore, txdata),
&error);
}
int GetSpendHeight(const CCoinsViewCache &inputs) {
LOCK(cs_main);
CBlockIndex *pindexPrev = LookupBlockIndex(inputs.GetBestBlock());
return pindexPrev->nHeight + 1;
}
bool CheckInputs(const CTransaction &tx, CValidationState &state,
const CCoinsViewCache &inputs, bool fScriptChecks,
const uint32_t flags, bool sigCacheStore,
bool scriptCacheStore,
const PrecomputedTransactionData &txdata,
std::vector<CScriptCheck> *pvChecks) {
assert(!tx.IsCoinBase());
if (pvChecks) {
pvChecks->reserve(tx.vin.size());
}
// Skip script verification 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.
if (!fScriptChecks) {
return true;
}
// 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).
uint256 hashCacheEntry = GetScriptCacheKey(tx, flags);
if (IsKeyInScriptCache(hashCacheEntry, !scriptCacheStore)) {
return true;
}
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.
const CScript &scriptPubKey = coin.GetTxOut().scriptPubKey;
const Amount amount = coin.GetTxOut().nValue;
// Verify signature
CScriptCheck check(scriptPubKey, amount, tx, i, flags, sigCacheStore,
txdata);
if (pvChecks) {
pvChecks->push_back(std::move(check));
} else if (!check()) {
// 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, don't trigger DoS
// protection to avoid splitting the network on the basis of
// relay policy disagreements.
CScriptCheck check2(scriptPubKey, amount, tx, i, mandatoryFlags,
sigCacheStore, txdata);
if (check2()) {
return state.Invalid(
false, REJECT_NONSTANDARD,
strprintf("non-mandatory-script-verify-flag (%s)",
ScriptErrorString(check.GetScriptError())));
}
}
// Failures of other flags indicate a transaction that is invalid in
// new blocks, e.g. a invalid P2SH. We DoS ban such nodes as they
// are not following the protocol. That said during an upgrade
// careful thought should be taken as to the correct behavior - we
// may want to continue peering with non-upgraded nodes even after
// soft-fork super-majority signaling has occurred.
return state.DoS(
100, false, REJECT_INVALID,
strprintf("mandatory-script-verify-flag-failed (%s)",
ScriptErrorString(check.GetScriptError())));
}
}
if (scriptCacheStore && !pvChecks) {
// We executed all of the provided scripts, and were told to cache the
// result. Do so now.
AddKeyInScriptCache(hashCacheEntry);
}
return true;
}
namespace {
bool UndoWriteToDisk(const CBlockUndo &blockundo, CDiskBlockPos &pos,
const uint256 &hashBlock,
const CMessageHeader::MessageMagic &messageStart) {
// Open history file to append
CAutoFile fileout(OpenUndoFile(pos), SER_DISK, CLIENT_VERSION);
if (fileout.IsNull()) {
return error("%s: OpenUndoFile failed", __func__);
}
// Write index header
unsigned int nSize = GetSerializeSize(fileout, blockundo);
fileout << FLATDATA(messageStart) << nSize;
// Write undo data
long fileOutPos = ftell(fileout.Get());
if (fileOutPos < 0) {
return error("%s: ftell failed", __func__);
}
pos.nPos = (unsigned int)fileOutPos;
fileout << blockundo;
// calculate & write checksum
CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
hasher << hashBlock;
hasher << blockundo;
fileout << hasher.GetHash();
return true;
}
static bool UndoReadFromDisk(CBlockUndo &blockundo, const CBlockIndex *pindex) {
CDiskBlockPos pos = pindex->GetUndoPos();
if (pos.IsNull()) {
return error("%s: no undo data available", __func__);
}
// Open history file to read
CAutoFile filein(OpenUndoFile(pos, true), SER_DISK, CLIENT_VERSION);
if (filein.IsNull()) {
return error("%s: OpenUndoFile failed", __func__);
}
// Read block
uint256 hashChecksum;
// We need a CHashVerifier as reserializing may lose data
CHashVerifier<CAutoFile> verifier(&filein);
try {
verifier << pindex->pprev->GetBlockHash();
verifier >> blockundo;
filein >> hashChecksum;
} catch (const std::exception &e) {
return error("%s: Deserialize or I/O error - %s", __func__, e.what());
}
// Verify checksum
if (hashChecksum != verifier.GetHash()) {
return error("%s: Checksum mismatch", __func__);
}
return true;
}
/** Abort with a message */
static bool AbortNode(const std::string &strMessage,
const std::string &userMessage = "") {
SetMiscWarning(strMessage);
LogPrintf("*** %s\n", strMessage);
uiInterface.ThreadSafeMessageBox(
userMessage.empty() ? _("Error: A fatal internal error occurred, see "
"debug.log for details")
: userMessage,
"", CClientUIInterface::MSG_ERROR);
StartShutdown();
return false;
}
static bool AbortNode(CValidationState &state, const std::string &strMessage,
const std::string &userMessage = "") {
AbortNode(strMessage, userMessage);
return state.Error(strMessage);
}
} // namespace
/** Restore the UTXO in a Coin at a given COutPoint. */
DisconnectResult UndoCoinSpend(const 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 DISCONNECT_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());
}
// The potential_overwrite parameter to AddCoin is only allowed to be false
// if we know for sure that the coin did not already exist in the cache. As
// we have queried for that above using HaveCoin, we don't need to guess.
// When fClean is false, a coin already existed and it is an overwrite.
view.AddCoin(out, std::move(undo), !fClean);
return fClean ? DISCONNECT_OK : DISCONNECT_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 CChainState::DisconnectBlock(const CBlock &block,
const CBlockIndex *pindex,
CCoinsViewCache &view) {
CBlockUndo blockUndo;
if (!UndoReadFromDisk(blockUndo, pindex)) {
error("DisconnectBlock(): failure reading undo data");
return DISCONNECT_FAILED;
}
return ApplyBlockUndo(blockUndo, block, pindex, view);
}
DisconnectResult ApplyBlockUndo(const 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 DISCONNECT_FAILED;
}
// First, restore inputs.
for (size_t i = 1; i < block.vtx.size(); i++) {
const CTransaction &tx = *(block.vtx[i]);
const CTxUndo &txundo = blockUndo.vtxundo[i - 1];
if (txundo.vprevout.size() != tx.vin.size()) {
error("DisconnectBlock(): transaction and undo data inconsistent");
return DISCONNECT_FAILED;
}
for (size_t j = 0; j < tx.vin.size(); j++) {
const COutPoint &out = tx.vin[j].prevout;
const Coin &undo = txundo.vprevout[j];
DisconnectResult res = UndoCoinSpend(undo, view, out);
if (res == DISCONNECT_FAILED) {
return DISCONNECT_FAILED;
}
fClean = fClean && res != DISCONNECT_UNCLEAN;
}
}
// 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 ? DISCONNECT_OK : DISCONNECT_UNCLEAN;
}
static void FlushBlockFile(bool fFinalize = false) {
LOCK(cs_LastBlockFile);
CDiskBlockPos block_pos_old(nLastBlockFile,
vinfoBlockFile[nLastBlockFile].nSize);
CDiskBlockPos undo_pos_old(nLastBlockFile,
vinfoBlockFile[nLastBlockFile].nUndoSize);
bool status = true;
status &= BlockFileSeq().Flush(block_pos_old, fFinalize);
status &= UndoFileSeq().Flush(undo_pos_old, fFinalize);
if (!status) {
AbortNode("Flushing block file to disk failed. This is likely the "
"result of an I/O error.");
}
}
static bool FindUndoPos(CValidationState &state, int nFile, CDiskBlockPos &pos,
unsigned int nAddSize);
static bool WriteUndoDataForBlock(const CBlockUndo &blockundo,
CValidationState &state, CBlockIndex *pindex,
const CChainParams &chainparams) {
// Write undo information to disk
if (pindex->GetUndoPos().IsNull()) {
CDiskBlockPos _pos;
if (!FindUndoPos(
state, pindex->nFile, _pos,
::GetSerializeSize(blockundo, SER_DISK, CLIENT_VERSION) + 40)) {
return error("ConnectBlock(): FindUndoPos failed");
}
if (!UndoWriteToDisk(blockundo, _pos, pindex->pprev->GetBlockHash(),
chainparams.DiskMagic())) {
return AbortNode(state, "Failed to write undo data");
}
// update nUndoPos in block index
pindex->nUndoPos = _pos.nPos;
pindex->nStatus = pindex->nStatus.withUndo();
setDirtyBlockIndex.insert(pindex);
}
return true;
}
static CCheckQueue<CScriptCheck> scriptcheckqueue(128);
void ThreadScriptCheck() {
RenameThread("bitcoin-scriptch");
scriptcheckqueue.Thread();
}
int32_t ComputeBlockVersion(const CBlockIndex *pindexPrev,
const Consensus::Params ¶ms) {
int32_t nVersion = VERSIONBITS_TOP_BITS;
return nVersion;
}
// Returns the script flags which should be checked for the block after
// the given block.
static uint32_t GetNextBlockScriptFlags(const Config &config,
const CBlockIndex *pindex) {
AssertLockHeld(cs_main);
const Consensus::Params &consensusParams =
config.GetChainParams().GetConsensus();
uint32_t flags = SCRIPT_VERIFY_NONE;
// Start enforcing P2SH (BIP16)
if ((pindex->nHeight + 1) >= consensusParams.BIP16Height) {
flags |= SCRIPT_VERIFY_P2SH;
}
// Start enforcing the DERSIG (BIP66) rule.
if ((pindex->nHeight + 1) >= consensusParams.BIP66Height) {
flags |= SCRIPT_VERIFY_DERSIG;
}
// Start enforcing CHECKLOCKTIMEVERIFY (BIP65) rule.
if ((pindex->nHeight + 1) >= consensusParams.BIP65Height) {
flags |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
}
// Start enforcing CSV (BIP68, BIP112 and BIP113) rule.
if ((pindex->nHeight + 1) >= consensusParams.CSVHeight) {
flags |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
}
// If the UAHF is enabled, we start accepting replay protected txns
if (IsUAHFenabled(config, 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(config, 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(config, pindex)) {
flags |= SCRIPT_VERIFY_CHECKDATASIG_SIGOPS;
flags |= SCRIPT_VERIFY_SIGPUSHONLY;
flags |= SCRIPT_VERIFY_CLEANSTACK;
}
// We make sure this node will have replay protection during the next hard
// fork.
if (IsReplayProtectionEnabled(config, 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 nTimeCallbacks = 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 CChainState::ConnectBlock(const Config &config, const CBlock &block,
CValidationState &state, CBlockIndex *pindex,
CCoinsViewCache &view, bool fJustCheck) {
AssertLockHeld(cs_main);
assert(pindex);
assert(*pindex->phashBlock == block.GetHash());
int64_t nTimeStart = GetTimeMicros();
// 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 (see RewindBlockIndex()
// for one general approach that was used for BIP 141 deployment).
// 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
// GetAdjustedTime() to go backward).
BlockValidationOptions validationOptions =
BlockValidationOptions(!fJustCheck, !fJustCheck);
if (!CheckBlock(config, block, state, validationOptions)) {
return error("%s: Consensus::CheckBlock: %s", __func__,
FormatStateMessage(state));
}
// Verify that the view's current state corresponds to the previous block
uint256 hashPrevBlock =
pindex->pprev == nullptr ? uint256() : pindex->pprev->GetBlockHash();
assert(hashPrevBlock == view.GetBestBlock());
// Special case for the genesis block, skipping connection of its
// transactions (its coinbase is unspendable)
const Consensus::Params &consensusParams =
config.GetChainParams().GetConsensus();
if (block.GetHash() == consensusParams.hashGenesisBlock) {
if (!fJustCheck) {
view.SetBestBlock(pindex->GetBlockHash());
}
return true;
}
nBlocksTotal++;
bool fScriptChecks = true;
if (!hashAssumeValid.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 = mapBlockIndex.find(hashAssumeValid);
if (it != mapBlockIndex.end()) {
if (it->second->GetAncestor(pindex->nHeight) == pindex &&
pindexBestHeader->GetAncestor(pindex->nHeight) == pindex &&
pindexBestHeader->nChainWork >= nMinimumChainWork) {
// This block is a member of the assumed verified chain and an
// ancestor of the best header. 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 nMinimumChainWork prevents the
// skipping when denied access to any chain at least as good as
// the expected chain.
fScriptChecks =
(GetBlockProofEquivalentTime(
*pindexBestHeader, *pindex, *pindexBestHeader,
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 and
// http://r6.ca/blog/20120206T005236Z.html for more information. This logic
// is not necessary for memory pool transactions, as AcceptToMemoryPool
// already refuses previously-known transaction ids entirely. 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 its 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.
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));
if (fEnforceBIP30) {
for (const auto &tx : block.vtx) {
for (size_t o = 0; o < tx->vout.size(); o++) {
if (view.HaveCoin(COutPoint(tx->GetId(), o))) {
return state.DoS(
100,
error("ConnectBlock(): tried to overwrite transaction"),
REJECT_INVALID, "bad-txns-BIP30");
}
}
}
}
// Start enforcing BIP68 (sequence locks).
int nLockTimeFlags = 0;
if (pindex->nHeight >= consensusParams.CSVHeight) {
nLockTimeFlags |= LOCKTIME_VERIFY_SEQUENCE;
}
const uint32_t flags = GetNextBlockScriptFlags(config, pindex->pprev);
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);
CBlockUndo blockundo;
CCheckQueueControl<CScriptCheck> control(fScriptChecks ? &scriptcheckqueue
: nullptr);
std::vector<int> prevheights;
Amount nFees = Amount::zero();
int nInputs = 0;
// Sigops counting. We need to do it again because of P2SH.
uint64_t nSigOpsCount = 0;
const uint64_t currentBlockSize =
::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION);
const uint64_t nMaxSigOpsCount = GetMaxBlockSigOpsCount(currentBlockSize);
blockundo.vtxundo.reserve(block.vtx.size() - 1);
for (const auto &ptx : block.vtx) {
const CTransaction &tx = *ptx;
nInputs += tx.vin.size();
if (tx.IsCoinBase()) {
// We've already checked for sigops count before P2SH in CheckBlock.
nSigOpsCount += GetSigOpCountWithoutP2SH(tx, flags);
}
// We do not need to throw when a transaction is duplicated. If they are
// in the same block, CheckBlock will catch it, and if they are in a
// different block, it'll register as a double spend or BIP30 violation.
// In both cases, we get a more meaningful feedback out of it.
AddCoins(view, tx, pindex->nHeight, true);
}
for (const auto &ptx : block.vtx) {
const CTransaction &tx = *ptx;
if (tx.IsCoinBase()) {
continue;
}
Amount txfee = Amount::zero();
if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight,
txfee)) {
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__,
tx.GetId().ToString(), FormatStateMessage(state));
}
nFees += txfee;
if (!MoneyRange(nFees)) {
return state.DoS(
100,
error("%s: accumulated fee in the block out of range.",
__func__),
REJECT_INVALID, "bad-txns-accumulated-fee-outofrange");
}
// 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)) {
return state.DoS(
100,
error("%s: contains a non-BIP68-final transaction", __func__),
REJECT_INVALID, "bad-txns-nonfinal");
}
// GetTransactionSigOpCount counts 2 types of sigops:
// * legacy (always)
// * p2sh (when P2SH enabled in flags and excludes coinbase)
auto txSigOpsCount = GetTransactionSigOpCount(tx, view, flags);
if (txSigOpsCount > MAX_TX_SIGOPS_COUNT) {
return state.DoS(100, false, REJECT_INVALID, "bad-txn-sigops");
}
nSigOpsCount += txSigOpsCount;
if (nSigOpsCount > nMaxSigOpsCount) {
return state.DoS(100, error("ConnectBlock(): too many sigops"),
REJECT_INVALID, "bad-blk-sigops");
}
// Don't cache results if we're actually connecting blocks (still
// consult the cache, though).
bool fCacheResults = fJustCheck;
std::vector<CScriptCheck> vChecks;
if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults,
fCacheResults, PrecomputedTransactionData(tx),
&vChecks)) {
return error("ConnectBlock(): CheckInputs on %s failed with %s",
tx.GetId().ToString(), FormatStateMessage(state));
}
control.Add(vChecks);
blockundo.vtxundo.push_back(CTxUndo());
SpendCoins(view, tx, blockundo.vtxundo.back(), pindex->nHeight);
}
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);
Amount blockReward =
nFees + GetBlockSubsidy(pindex->nHeight, consensusParams);
if (block.vtx[0]->GetValueOut() > blockReward) {
return state.DoS(100,
error("ConnectBlock(): coinbase pays too much "
"(actual=%d vs limit=%d)",
block.vtx[0]->GetValueOut(), blockReward),
REJECT_INVALID, "bad-cb-amount");
}
if (!control.Wait()) {
return state.DoS(100, false, REJECT_INVALID, "blk-bad-inputs", false,
"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 (!WriteUndoDataForBlock(blockundo, state, pindex,
config.GetChainParams())) {
return false;
}
if (!pindex->IsValid(BlockValidity::SCRIPTS)) {
pindex->RaiseValidity(BlockValidity::SCRIPTS);
setDirtyBlockIndex.insert(pindex);
}
assert(pindex->phashBlock);
// 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);
int64_t nTime6 = GetTimeMicros();
nTimeCallbacks += nTime6 - nTime5;
LogPrint(BCLog::BENCH, " - Callbacks: %.2fms [%.2fs (%.2fms/blk)]\n",
MILLI * (nTime6 - nTime5), nTimeCallbacks * MICRO,
nTimeCallbacks * MILLI / nBlocksTotal);
return true;
}
/**
* 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.
*/
static bool FlushStateToDisk(const CChainParams &chainparams,
CValidationState &state, FlushStateMode mode,
int nManualPruneHeight) {
int64_t nMempoolUsage = g_mempool.DynamicMemoryUsage();
LOCK(cs_main);
static int64_t nLastWrite = 0;
static int64_t nLastFlush = 0;
std::set<int> setFilesToPrune;
bool full_flush_completed = false;
try {
{
bool fFlushForPrune = false;
bool fDoFullFlush = false;
LOCK(cs_LastBlockFile);
if (fPruneMode && (fCheckForPruning || nManualPruneHeight > 0) &&
!fReindex) {
if (nManualPruneHeight > 0) {
FindFilesToPruneManual(setFilesToPrune, nManualPruneHeight);
} else {
FindFilesToPrune(setFilesToPrune,
chainparams.PruneAfterHeight());
fCheckForPruning = false;
}
if (!setFilesToPrune.empty()) {
fFlushForPrune = true;
if (!fHavePruned) {
pblocktree->WriteFlag("prunedblockfiles", true);
fHavePruned = true;
}
}
}
int64_t nNow = GetTimeMicros();
// Avoid writing/flushing immediately after startup.
if (nLastWrite == 0) {
nLastWrite = nNow;
}
if (nLastFlush == 0) {
nLastFlush = nNow;
}
int64_t nMempoolSizeMax =
gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
int64_t cacheSize = pcoinsTip->DynamicMemoryUsage();
int64_t nTotalSpace =
nCoinCacheUsage +
std::max<int64_t>(nMempoolSizeMax - nMempoolUsage, 0);
// 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 &&
cacheSize > std::max((9 * nTotalSpace) / 10,
nTotalSpace -
MAX_BLOCK_COINSDB_USAGE * 1024 * 1024);
// The cache is over the limit, we have to write now.
bool fCacheCritical =
mode == FlushStateMode::IF_NEEDED && cacheSize > nTotalSpace;
// 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 > nLastWrite + (int64_t)DATABASE_WRITE_INTERVAL * 1000000;
// It's been very long since we flushed the cache. Do this
// infrequently, to optimize cache usage.
bool fPeriodicFlush =
mode == FlushStateMode::PERIODIC &&
nNow > nLastFlush + (int64_t)DATABASE_FLUSH_INTERVAL * 1000000;
// 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) {
// Depend on nMinDiskSpace to ensure we can write block index
if (!CheckDiskSpace(GetBlocksDir())) {
return AbortNode(state, "Disk space is low!",
_("Error: Disk space is low!"));
}
// First make sure all block and undo data is flushed to disk.
FlushBlockFile();
// Then update all block file information (which may refer to
// block and undo files).
{
std::vector<std::pair<int, const CBlockFileInfo *>> vFiles;
vFiles.reserve(setDirtyFileInfo.size());
for (int i : setDirtyFileInfo) {
vFiles.push_back(std::make_pair(i, &vinfoBlockFile[i]));
}
setDirtyFileInfo.clear();
std::vector<const CBlockIndex *> vBlocks;
vBlocks.reserve(setDirtyBlockIndex.size());
for (const CBlockIndex *cbi : setDirtyBlockIndex) {
vBlocks.push_back(cbi);
}
setDirtyBlockIndex.clear();
if (!pblocktree->WriteBatchSync(vFiles, nLastBlockFile,
vBlocks)) {
return AbortNode(
state, "Failed to write to block index database");
}
}
// Finally remove any pruned files
if (fFlushForPrune) {
UnlinkPrunedFiles(setFilesToPrune);
}
nLastWrite = nNow;
}
// Flush best chain related state. This can only be done if the
// blocks / block index write was also done.
if (fDoFullFlush && !pcoinsTip->GetBestBlock().IsNull()) {
// 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(GetDataDir(),
48 * 2 * 2 * pcoinsTip->GetCacheSize())) {
return AbortNode(state, "Disk space is low!",
_("Error: Disk space is low!"));
}
// Flush the chainstate (which may refer to block index
// entries).
if (!pcoinsTip->Flush()) {
return AbortNode(state, "Failed to write to coin database");
}
nLastFlush = nNow;
full_flush_completed = true;
}
}
if (full_flush_completed) {
// Update best block in wallet (so we can detect restored wallets).
GetMainSignals().ChainStateFlushed(chainActive.GetLocator());
}
} catch (const std::runtime_error &e) {
return AbortNode(state, std::string("System error while flushing: ") +
e.what());
}
return true;
}
void FlushStateToDisk() {
CValidationState state;
const CChainParams &chainparams = Params();
FlushStateToDisk(chainparams, state, FlushStateMode::ALWAYS);
}
void PruneAndFlush() {
CValidationState state;
fCheckForPruning = true;
const CChainParams &chainparams = Params();
FlushStateToDisk(chainparams, state, FlushStateMode::NONE);
}
/** Check warning conditions and do some notifications on new chain tip set. */
static void UpdateTip(const Config &config, CBlockIndex *pindexNew) {
// New best block
g_mempool.AddTransactionsUpdated(1);
{
LOCK(g_best_block_mutex);
g_best_block = pindexNew->GetBlockHash();
g_best_block_cv.notify_all();
}
LogPrintf(
"%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu "
"date='%s' progress=%f cache=%.1fMiB(%utxo)\n",
__func__, pindexNew->GetBlockHash().ToString(), pindexNew->nHeight,
pindexNew->nVersion, log(pindexNew->nChainWork.getdouble()) / log(2.0),
(unsigned long)pindexNew->nChainTx,
FormatISO8601DateTime(pindexNew->GetBlockTime()),
GuessVerificationProgress(config.GetChainParams().TxData(), pindexNew),
pcoinsTip->DynamicMemoryUsage() * (1.0 / (1 << 20)),
pcoinsTip->GetCacheSize());
}
/**
* Disconnect chainActive'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 CChainState::DisconnectTip(const Config &config, CValidationState &state,
DisconnectedBlockTransactions *disconnectpool) {
CBlockIndex *pindexDelete = chainActive.Tip();
assert(pindexDelete);
// Read block from disk.
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
CBlock &block = *pblock;
if (!ReadBlockFromDisk(block, pindexDelete, config)) {
return AbortNode(state, "Failed to read block");
}
// Apply the block atomically to the chain state.
int64_t nStart = GetTimeMicros();
{
CCoinsViewCache view(pcoinsTip.get());
assert(view.GetBestBlock() == pindexDelete->GetBlockHash());
if (DisconnectBlock(block, pindexDelete, view) != DISCONNECT_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);
// Write the chain state to disk, if necessary.
if (!FlushStateToDisk(config.GetChainParams(), state,
FlushStateMode::IF_NEEDED)) {
return false;
}
// 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(config, pindexDelete) !=
GetNextBlockScriptFlags(config, pindexDelete->pprev)) {
LogPrint(BCLog::MEMPOOL,
"Disconnecting mempool due to rewind of upgrade block\n");
if (disconnectpool) {
disconnectpool->importMempool(g_mempool);
}
g_mempool.clear();
}
if (disconnectpool) {
disconnectpool->addForBlock(block.vtx);
}
// If the tip is finalized, then undo it.
if (pindexFinalized == pindexDelete) {
pindexFinalized = pindexDelete->pprev;
}
chainActive.SetTip(pindexDelete->pprev);
// Update chainActive and related variables.
UpdateTip(config, pindexDelete->pprev);
// Let wallets know transactions went from 1-confirmed to
// 0-confirmed or conflicted:
GetMainSignals().BlockDisconnected(pblock);
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;
struct PerBlockConnectTrace {
CBlockIndex *pindex = nullptr;
std::shared_ptr<const CBlock> pblock;
std::shared_ptr<std::vector<CTransactionRef>> conflictedTxs;
PerBlockConnectTrace()
: conflictedTxs(std::make_shared<std::vector<CTransactionRef>>()) {}
};
/**
* Used to track blocks whose transactions were applied to the UTXO state as a
* part of a single ActivateBestChainStep call.
*
* This class also tracks transactions that are removed from the mempool as
* conflicts (per block) and can be used to pass all those transactions through
* SyncTransaction.
*
* This class assumes (and asserts) that the conflicted transactions for a given
* block are added via mempool callbacks prior to the BlockConnected()
* associated with those transactions. If any transactions are marked
* conflicted, it is assumed that an associated block will always be added.
*
* This class is single-use, once you call GetBlocksConnected() you have to
* throw it away and make a new one.
*/
class ConnectTrace {
private:
std::vector<PerBlockConnectTrace> blocksConnected;
CTxMemPool &pool;
public:
explicit ConnectTrace(CTxMemPool &_pool) : blocksConnected(1), pool(_pool) {
pool.NotifyEntryRemoved.connect(
boost::bind(&ConnectTrace::NotifyEntryRemoved, this, _1, _2));
}
~ConnectTrace() {
pool.NotifyEntryRemoved.disconnect(
boost::bind(&ConnectTrace::NotifyEntryRemoved, this, _1, _2));
}
void BlockConnected(CBlockIndex *pindex,
std::shared_ptr<const CBlock> pblock) {
assert(!blocksConnected.back().pindex);
assert(pindex);
assert(pblock);
blocksConnected.back().pindex = pindex;
blocksConnected.back().pblock = std::move(pblock);
blocksConnected.emplace_back();
}
std::vector<PerBlockConnectTrace> &GetBlocksConnected() {
// We always keep one extra block at the end of our list because blocks
// are added after all the conflicted transactions have been filled in.
// Thus, the last entry should always be an empty one waiting for the
// transactions from the next block. We pop the last entry here to make
// sure the list we return is sane.
assert(!blocksConnected.back().pindex);
assert(blocksConnected.back().conflictedTxs->empty());
blocksConnected.pop_back();
return blocksConnected;
}
void NotifyEntryRemoved(CTransactionRef txRemoved,
MemPoolRemovalReason reason) {
assert(!blocksConnected.back().pindex);
if (reason == MemPoolRemovalReason::CONFLICT) {
blocksConnected.back().conflictedTxs->emplace_back(
std::move(txRemoved));
}
}
};
static bool FinalizeBlockInternal(const Config &config, CValidationState &state,
const CBlockIndex *pindex) {
AssertLockHeld(cs_main);
if (pindex->nStatus.isInvalid()) {
// We try to finalize an invalid block.
return state.DoS(100,
error("%s: Trying to finalize invalid block %s",
__func__, pindex->GetBlockHash().ToString()),
REJECT_INVALID, "finalize-invalid-block");
}
// Check that the request is consistent with current finalization.
if (pindexFinalized && !AreOnTheSameFork(pindex, pindexFinalized)) {
return state.DoS(
20,
error("%s: Trying to finalize block %s which conflicts "
"with already finalized block",
__func__, pindex->GetBlockHash().ToString()),
REJECT_AGAINST_FINALIZED, "bad-fork-prior-finalized");
}
if (IsBlockFinalized(pindex)) {
// The block is already finalized.
return true;
}
// We have a new block to finalize.
pindexFinalized = pindex;
return true;
}
static const CBlockIndex *FindBlockToFinalize(const Config &config,
CBlockIndex *pindexNew) {
AssertLockHeld(cs_main);
const int32_t maxreorgdepth =
gArgs.GetArg("-maxreorgdepth", DEFAULT_MAX_REORG_DEPTH);
const int64_t finalizationdelay =
gArgs.GetArg("-finalizationdelay", DEFAULT_MIN_FINALIZATION_DELAY);
// Find our candidate.
// If maxreorgdepth is < 0 pindex will be null and auto finalization
// disabled
const CBlockIndex *pindex =
pindexNew->GetAncestor(pindexNew->nHeight - maxreorgdepth);
int64_t now = GetTime();
// If the finalization delay is not expired since the startup time,
// finalization should be avoided. Header receive time is not saved to disk
// and so cannot be anterior to startup time.
if (now < (GetStartupTime() + finalizationdelay)) {
return nullptr;
}
// While our candidate is not eligible (finalization delay not expired), try
// the previous one.
while (pindex && (pindex != pindexFinalized)) {
// Check that the block to finalize is known for a long enough time.
// This test will ensure that an attacker could not cause a block to
// finalize by forking the chain with a depth > maxreorgdepth.
// If the block is loaded from disk, header receive time is 0 and the
// block will be finalized. This is safe because the delay since the
// node startup is already expired.
auto headerReceivedTime = pindex->GetHeaderReceivedTime();
// If finalization delay is <= 0, finalization always occurs immediately
if (now >= (headerReceivedTime + finalizationdelay)) {
return pindex;
}
pindex = pindex->pprev;
}
return nullptr;
}
/**
* Connect a new block to chainActive. pblock is either nullptr or a pointer to
* a CBlock corresponding to pindexNew, to bypass loading it again from disk.
*
* The block is always added to connectTrace (either after loading from disk or
* by copying pblock) - if that is not intended, care must be taken to remove
* the last entry in blocksConnected in case of failure.
*/
bool CChainState::ConnectTip(const Config &config, CValidationState &state,
CBlockIndex *pindexNew,
const std::shared_ptr<const CBlock> &pblock,
ConnectTrace &connectTrace,
DisconnectedBlockTransactions &disconnectpool) {
AssertLockHeld(cs_main);
assert(pindexNew->pprev == chainActive.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 (!ReadBlockFromDisk(*pblockNew, pindexNew, config)) {
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);
{
CCoinsViewCache view(pcoinsTip.get());
bool rv = ConnectBlock(config, blockConnecting, state, pindexNew, view);
GetMainSignals().BlockChecked(blockConnecting, state);
if (!rv) {
if (state.IsInvalid()) {
InvalidBlockFound(pindexNew, state);
}
return error("ConnectTip(): ConnectBlock %s failed (%s)",
pindexNew->GetBlockHash().ToString(),
FormatStateMessage(state));
}
// Update the finalized block.
const CBlockIndex *pindexToFinalize =
FindBlockToFinalize(config, pindexNew);
if (pindexToFinalize &&
!FinalizeBlockInternal(config, state, pindexToFinalize)) {
state.SetCorruptionPossible();
return error("ConnectTip(): FinalizeBlock %s failed (%s)",
pindexNew->GetBlockHash().ToString(),
FormatStateMessage(state));
}
nTime3 = GetTimeMicros();
nTimeConnectTotal += nTime3 - nTime2;
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(config.GetChainParams(), 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.;
g_mempool.removeForBlock(blockConnecting.vtx, pindexNew->nHeight);
disconnectpool.removeForBlock(blockConnecting.vtx);
// 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(config, pindexNew) !=
GetNextBlockScriptFlags(config, pindexNew->pprev)) {
LogPrint(BCLog::MEMPOOL,
"Disconnecting mempool due to acceptance of upgrade block\n");
disconnectpool.importMempool(g_mempool);
}
// Update chainActive & related variables.
chainActive.SetTip(pindexNew);
UpdateTip(config, 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);
connectTrace.BlockConnected(pindexNew, std::move(pthisBlock));
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 *CChainState::FindMostWorkChain() {
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 a finalized block to be reorged, then we
// mark it as invalid.
if (pindexFinalized && !AreOnTheSameFork(pindexNew, pindexFinalized)) {
LogPrintf("Mark block %s invalid because it forks prior to the "
"finalization point %d.\n",
pindexNew->GetBlockHash().ToString(),
pindexFinalized->nHeight);
pindexNew->nStatus = pindexNew->nStatus.withFailed();
InvalidChainFound(pindexNew);
}
const CBlockIndex *pindexFork = chainActive.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->nChainTx || pindexTest->nHeight == 0);
// If this is a parked chain, but it has enough PoW, clear the park
// state.
bool fParkedChain = pindexTest->nStatus.isOnParkedChain();
if (fParkedChain && gArgs.GetBoolArg("-parkdeepreorg", true)) {
const CBlockIndex *pindexTip = chainActive.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 block %s as its chain has accumulated "
"enough PoW.\n",
pindexTest->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 missing data)
hasValidAncestor = false;
setBlockIndexCandidates.erase(pindexTest);
if (fInvalidChain &&
(pindexBestInvalid == nullptr ||
pindexNew->nChainWork > pindexBestInvalid->nChainWork)) {
pindexBestInvalid = pindexNew;
}
if (fParkedChain &&
(pindexBestParked == nullptr ||
pindexNew->nChainWork > pindexBestParked->nChainWork)) {
pindexBestParked = pindexNew;
}
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
// mapBlocksUnlinked, so that if the block arrives in the
// future we can try adding to setBlockIndexCandidates
// again.
mapBlocksUnlinked.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);
}
}
// 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 CChainState::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, chainActive.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.
*/
bool CChainState::ActivateBestChainStep(
const Config &config, CValidationState &state, CBlockIndex *pindexMostWork,
const std::shared_ptr<const CBlock> &pblock, bool &fInvalidFound,
ConnectTrace &connectTrace) {
AssertLockHeld(cs_main);
const CBlockIndex *pindexOldTip = chainActive.Tip();
const CBlockIndex *pindexFork = chainActive.FindFork(pindexMostWork);
// Disconnect active blocks which are no longer in the best chain.
bool fBlocksDisconnected = false;
DisconnectedBlockTransactions disconnectpool;
while (chainActive.Tip() && chainActive.Tip() != pindexFork) {
if (!DisconnectTip(config, state, &disconnectpool)) {
// This is likely a fatal error, but keep the mempool consistent,
// just in case. Only remove from the mempool in this case.
disconnectpool.updateMempoolForReorg(config, false);
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)) {
if (!ConnectTip(config, state, pindexConnect,
pindexConnect == pindexMostWork
? pblock
: std::shared_ptr<const CBlock>(),
connectTrace, disconnectpool)) {
if (state.IsInvalid()) {
// The block violates a consensus rule.
if (!state.CorruptionPossible()) {
InvalidChainFound(vpindexToConnect.back());
}
state = CValidationState();
fInvalidFound = true;
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.
disconnectpool.updateMempoolForReorg(config, false);
return false;
} else {
PruneBlockIndexCandidates();
if (!pindexOldTip ||
chainActive.Tip()->nChainWork > pindexOldTip->nChainWork) {
// We're in a better position than we were. Return
// temporarily to release the lock.
fContinue = false;
break;
}
}
}
}
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(config, true);
}
g_mempool.check(pcoinsTip.get());
// Callbacks/notifications for a new best chain.
if (fInvalidFound) {
CheckForkWarningConditionsOnNewFork(pindexMostWork);
} else {
CheckForkWarningConditions();
}
return true;
}
static void NotifyHeaderTip() {
bool fNotify = false;
bool fInitialBlockDownload = false;
static CBlockIndex *pindexHeaderOld = nullptr;
CBlockIndex *pindexHeader = nullptr;
{
LOCK(cs_main);
pindexHeader = pindexBestHeader;
if (pindexHeader != pindexHeaderOld) {
fNotify = true;
fInitialBlockDownload = IsInitialBlockDownload();
pindexHeaderOld = pindexHeader;
}
}
// Send block tip changed notifications without cs_main
if (fNotify) {
uiInterface.NotifyHeaderTip(fInitialBlockDownload, pindexHeader);
}
}
bool CChainState::ActivateBestChain(const Config &config,
CValidationState &state,
std::shared_ptr<const CBlock> pblock) {
// 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_cs_chainstate to enforce
// mutual exclusion so that only one caller may execute this function at a
// time
LOCK(m_cs_chainstate);
CBlockIndex *pindexMostWork = nullptr;
CBlockIndex *pindexNewTip = nullptr;
int nStopAtHeight = gArgs.GetArg("-stopatheight", DEFAULT_STOPATHEIGHT);
do {
boost::this_thread::interruption_point();
if (GetMainSignals().CallbacksPending() > 10) {
// 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.
SyncWithValidationInterfaceQueue();
}
{
LOCK(cs_main);
CBlockIndex *starting_tip = chainActive.Tip();
bool blocks_connected = false;
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).
// Destructed before cs_main is unlocked
ConnectTrace connectTrace(g_mempool);
if (pindexMostWork == nullptr) {
pindexMostWork = FindMostWorkChain();
}
// Whether we have anything to do at all.
if (pindexMostWork == nullptr ||
pindexMostWork == chainActive.Tip()) {
break;
}
bool fInvalidFound = false;
std::shared_ptr<const CBlock> nullBlockPtr;
if (!ActivateBestChainStep(
config, state, pindexMostWork,
pblock && pblock->GetHash() ==
pindexMostWork->GetBlockHash()
? pblock
: nullBlockPtr,
fInvalidFound, connectTrace)) {
return false;
}
blocks_connected = true;
if (fInvalidFound) {
// Wipe cache, we may need another branch now.
pindexMostWork = nullptr;
}
pindexNewTip = chainActive.Tip();
for (const PerBlockConnectTrace &trace :
connectTrace.GetBlocksConnected()) {
assert(trace.pblock && trace.pindex);
GetMainSignals().BlockConnected(trace.pblock, trace.pindex,
trace.conflictedTxs);
}
} while (!chainActive.Tip() ||
(starting_tip && CBlockIndexWorkComparator()(
chainActive.Tip(), starting_tip)));
if (!blocks_connected) {
return true;
}
const CBlockIndex *pindexFork = chainActive.FindFork(starting_tip);
bool fInitialDownload = IsInitialBlockDownload();
// 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 (pindexFork != pindexNewTip) {
// Notify ValidationInterface subscribers
GetMainSignals().UpdatedBlockTip(pindexNewTip, pindexFork,
fInitialDownload);
// Always notify the UI if a new block tip was connected
uiInterface.NotifyBlockTip(fInitialDownload, pindexNewTip);
}
}
// When we reach this point, we switched to a new tip (stored in
// pindexNewTip).
if (nStopAtHeight && pindexNewTip &&
pindexNewTip->nHeight >= nStopAtHeight) {
StartShutdown();
}
// 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);
const CChainParams ¶ms = config.GetChainParams();
CheckBlockIndex(params.GetConsensus());
// Write changes periodically to disk, after relay.
if (!FlushStateToDisk(params, state, FlushStateMode::PERIODIC)) {
return false;
}
return true;
}
bool ActivateBestChain(const Config &config, CValidationState &state,
std::shared_ptr<const CBlock> pblock) {
return g_chainstate.ActivateBestChain(config, state, std::move(pblock));
}
bool CChainState::PreciousBlock(const Config &config, CValidationState &state,
CBlockIndex *pindex) {
{
LOCK(cs_main);
if (pindex->nChainWork < chainActive.Tip()->nChainWork) {
// Nothing to do, this block is not at the tip.
return true;
}
if (chainActive.Tip()->nChainWork > nLastPreciousChainwork) {
// The chain has been extended since the last call, reset the
// counter.
nBlockReverseSequenceId = -1;
}
nLastPreciousChainwork = chainActive.Tip()->nChainWork;
setBlockIndexCandidates.erase(pindex);
pindex->nSequenceId = nBlockReverseSequenceId;
if (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...
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->nChainTx) {
setBlockIndexCandidates.insert(pindex);
PruneBlockIndexCandidates();
}
}
return ActivateBestChain(config, state);
}
bool PreciousBlock(const Config &config, CValidationState &state,
CBlockIndex *pindex) {
return g_chainstate.PreciousBlock(config, state, pindex);
}
bool CChainState::UnwindBlock(const Config &config, CValidationState &state,
CBlockIndex *pindex, bool invalidate) {
AssertLockHeld(cs_main);
// We first disconnect backwards and then mark the 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).
bool pindex_was_in_chain = false;
CBlockIndex *invalid_walk_tip = chainActive.Tip();
DisconnectedBlockTransactions disconnectpool;
while (chainActive.Contains(pindex)) {
pindex_was_in_chain = true;
// ActivateBestChain considers blocks already in chainActive
// unconditionally valid already, so force disconnect away from it.
if (!DisconnectTip(config, state, &disconnectpool)) {
// It's probably hopeless to try to make the mempool consistent
// here if DisconnectTip failed, but we can try.
disconnectpool.updateMempoolForReorg(config, false);
return false;
}
}
// Now mark the blocks we just disconnected as descendants invalid
// (note this may not be all descendants).
while (pindex_was_in_chain && invalid_walk_tip != pindex) {
invalid_walk_tip->nStatus =
invalidate ? invalid_walk_tip->nStatus.withFailedParent()
: invalid_walk_tip->nStatus.withParkedParent();
setDirtyBlockIndex.insert(invalid_walk_tip);
invalid_walk_tip = invalid_walk_tip->pprev;
}
// Mark the block as either invalid or parked.
pindex->nStatus = invalidate ? pindex->nStatus.withFailed()
: pindex->nStatus.withParked();
setDirtyBlockIndex.insert(pindex);
if (invalidate) {
m_failed_blocks.insert(pindex);
}
// DisconnectTip will add transactions to disconnectpool; try to add these
// back to the mempool.
disconnectpool.updateMempoolForReorg(config, true);
// The resulting new best tip may not be in setBlockIndexCandidates anymore,
// so add it again.
for (const std::pair<const uint256, CBlockIndex *> &it : mapBlockIndex) {
CBlockIndex *i = it.second;
if (i->IsValid(BlockValidity::TRANSACTIONS) && i->nChainTx &&
!setBlockIndexCandidates.value_comp()(i, chainActive.Tip())) {
setBlockIndexCandidates.insert(i);
}
}
if (invalidate) {
InvalidChainFound(pindex);
}
- uiInterface.NotifyBlockTip(IsInitialBlockDownload(), pindex->pprev);
+
+ // Only notify about a new block tip if the active chain was modified.
+ if (pindex_was_in_chain) {
+ uiInterface.NotifyBlockTip(IsInitialBlockDownload(), pindex->pprev);
+ }
return true;
}
bool FinalizeBlockAndInvalidate(const Config &config, CValidationState &state,
CBlockIndex *pindex) {
AssertLockHeld(cs_main);
if (!FinalizeBlockInternal(config, state, pindex)) {
// state is set by FinalizeBlockInternal.
return false;
}
// We have a valid candidate, make sure it is not parked.
if (pindex->nStatus.isOnParkedChain()) {
UnparkBlock(pindex);
}
// If the finalized block is not on the active chain, we need to rewind.
if (!AreOnTheSameFork(pindex, chainActive.Tip())) {
const CBlockIndex *pindexFork = chainActive.FindFork(pindex);
CBlockIndex *pindexToInvalidate =
chainActive.Tip()->GetAncestor(pindexFork->nHeight + 1);
return InvalidateBlock(config, state, pindexToInvalidate);
}
return true;
}
bool InvalidateBlock(const Config &config, CValidationState &state,
CBlockIndex *pindex) {
return g_chainstate.UnwindBlock(config, state, pindex, true);
}
bool ParkBlock(const Config &config, CValidationState &state,
CBlockIndex *pindex) {
return g_chainstate.UnwindBlock(config, state, pindex, false);
}
template <typename F>
void CChainState::UpdateFlagsForBlock(CBlockIndex *pindexBase,
CBlockIndex *pindex, F f) {
BlockStatus newStatus = f(pindex->nStatus);
if (pindex->nStatus != newStatus &&
pindex->GetAncestor(pindexBase->nHeight) == pindexBase) {
pindex->nStatus = newStatus;
setDirtyBlockIndex.insert(pindex);
if (newStatus.isValid()) {
m_failed_blocks.erase(pindex);
}
if (pindex->IsValid(BlockValidity::TRANSACTIONS) && pindex->nChainTx &&
setBlockIndexCandidates.value_comp()(chainActive.Tip(), pindex)) {
setBlockIndexCandidates.insert(pindex);
}
}
}
template <typename F, typename C>
void CChainState::UpdateFlags(CBlockIndex *pindex, F f, C fchild) {
AssertLockHeld(cs_main);
// Update the current block.
UpdateFlagsForBlock(pindex, pindex, f);
// Update the flags from this block and all its descendants.
BlockMap::iterator it = mapBlockIndex.begin();
while (it != mapBlockIndex.end()) {
UpdateFlagsForBlock(pindex, it->second, fchild);
it++;
}
// Update the flags from all ancestors too.
while (pindex != nullptr) {
BlockStatus newStatus = f(pindex->nStatus);
if (pindex->nStatus != newStatus) {
pindex->nStatus = newStatus;
setDirtyBlockIndex.insert(pindex);
if (newStatus.isValid()) {
m_failed_blocks.erase(pindex);
}
}
pindex = pindex->pprev;
}
}
template <typename F> void CChainState::UpdateFlags(CBlockIndex *pindex, F f) {
// Handy shorthand.
UpdateFlags(pindex, f, f);
}
bool CChainState::ResetBlockFailureFlags(CBlockIndex *pindex) {
AssertLockHeld(cs_main);
if (pindexBestInvalid &&
(pindexBestInvalid->GetAncestor(pindex->nHeight) == pindex ||
pindex->GetAncestor(pindexBestInvalid->nHeight) ==
pindexBestInvalid)) {
// Reset the invalid block marker if it is about to be cleared.
pindexBestInvalid = nullptr;
}
// In case we are reconsidering something before the finalization point,
// move the finalization point to the last common ancestor.
if (pindexFinalized) {
pindexFinalized = LastCommonAncestor(pindex, pindexFinalized);
}
UpdateFlags(pindex, [](const BlockStatus status) {
return status.withClearedFailureFlags();
});
return true;
}
bool ResetBlockFailureFlags(CBlockIndex *pindex) {
return g_chainstate.ResetBlockFailureFlags(pindex);
}
bool CChainState::UnparkBlockImpl(CBlockIndex *pindex, bool fClearChildren) {
AssertLockHeld(cs_main);
if (pindexBestParked &&
(pindexBestParked->GetAncestor(pindex->nHeight) == pindex ||
pindex->GetAncestor(pindexBestParked->nHeight) == pindexBestParked)) {
// Reset the parked block marker if it is about to be cleared.
pindexBestParked = nullptr;
}
UpdateFlags(pindex,
[](const BlockStatus status) {
return status.withClearedParkedFlags();
},
[fClearChildren](const BlockStatus status) {
return fClearChildren ? status.withClearedParkedFlags()
: status.withParkedParent(false);
});
return true;
}
bool UnparkBlockAndChildren(CBlockIndex *pindex) {
return g_chainstate.UnparkBlockImpl(pindex, true);
}
bool UnparkBlock(CBlockIndex *pindex) {
return g_chainstate.UnparkBlockImpl(pindex, false);
}
const CBlockIndex *GetFinalizedBlock() {
AssertLockHeld(cs_main);
return pindexFinalized;
}
bool IsBlockFinalized(const CBlockIndex *pindex) {
AssertLockHeld(cs_main);
return pindexFinalized &&
pindexFinalized->GetAncestor(pindex->nHeight) == pindex;
}
CBlockIndex *CChainState::AddToBlockIndex(const CBlockHeader &block) {
AssertLockHeld(cs_main);
// Check for duplicate
uint256 hash = block.GetHash();
BlockMap::iterator it = mapBlockIndex.find(hash);
if (it != mapBlockIndex.end()) {
return it->second;
}
// Construct new block index object
CBlockIndex *pindexNew = new CBlockIndex(block);
// We assign the sequence id to blocks only when the full data is available,
// to avoid miners withholding blocks but broadcasting headers, to get a
// competitive advantage.
pindexNew->nSequenceId = 0;
BlockMap::iterator mi =
mapBlockIndex.insert(std::make_pair(hash, pindexNew)).first;
pindexNew->phashBlock = &((*mi).first);
BlockMap::iterator miPrev = mapBlockIndex.find(block.hashPrevBlock);
if (miPrev != mapBlockIndex.end()) {
pindexNew->pprev = (*miPrev).second;
pindexNew->nHeight = pindexNew->pprev->nHeight + 1;
pindexNew->BuildSkip();
}
pindexNew->nTimeReceived = GetTime();
pindexNew->nTimeMax =
(pindexNew->pprev
? std::max(pindexNew->pprev->nTimeMax, pindexNew->nTime)
: pindexNew->nTime);
pindexNew->nChainWork =
(pindexNew->pprev ? pindexNew->pprev->nChainWork : 0) +
GetBlockProof(*pindexNew);
pindexNew->RaiseValidity(BlockValidity::TREE);
if (pindexBestHeader == nullptr ||
pindexBestHeader->nChainWork < pindexNew->nChainWork) {
pindexBestHeader = pindexNew;
}
setDirtyBlockIndex.insert(pindexNew);
return pindexNew;
}
/**
* Mark a block as having its data received and checked (up to
* BLOCK_VALID_TRANSACTIONS).
*/
bool CChainState::ReceivedBlockTransactions(const CBlock &block,
CValidationState &state,
CBlockIndex *pindexNew,
const CDiskBlockPos &pos) {
pindexNew->nTx = block.vtx.size();
pindexNew->nChainTx = 0;
pindexNew->nFile = pos.nFile;
pindexNew->nDataPos = pos.nPos;
pindexNew->nUndoPos = 0;
pindexNew->nStatus = pindexNew->nStatus.withData();
pindexNew->RaiseValidity(BlockValidity::TRANSACTIONS);
setDirtyBlockIndex.insert(pindexNew);
if (pindexNew->pprev == nullptr || pindexNew->pprev->nChainTx) {
// 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();
pindex->nChainTx =
(pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
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++;
}
if (chainActive.Tip() == nullptr ||
!setBlockIndexCandidates.value_comp()(pindex,
chainActive.Tip())) {
setBlockIndexCandidates.insert(pindex);
}
std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
range = mapBlocksUnlinked.equal_range(pindex);
while (range.first != range.second) {
std::multimap<CBlockIndex *, CBlockIndex *>::iterator it =
range.first;
queue.push_back(it->second);
range.first++;
mapBlocksUnlinked.erase(it);
}
}
} else if (pindexNew->pprev &&
pindexNew->pprev->IsValid(BlockValidity::TREE)) {
mapBlocksUnlinked.insert(std::make_pair(pindexNew->pprev, pindexNew));
}
return true;
}
static bool FindBlockPos(CDiskBlockPos &pos, unsigned int nAddSize,
unsigned int nHeight, uint64_t nTime,
bool fKnown = false) {
LOCK(cs_LastBlockFile);
unsigned int nFile = fKnown ? pos.nFile : nLastBlockFile;
if (vinfoBlockFile.size() <= nFile) {
vinfoBlockFile.resize(nFile + 1);
}
if (!fKnown) {
while (vinfoBlockFile[nFile].nSize + nAddSize >= MAX_BLOCKFILE_SIZE) {
nFile++;
if (vinfoBlockFile.size() <= nFile) {
vinfoBlockFile.resize(nFile + 1);
}
}
pos.nFile = nFile;
pos.nPos = vinfoBlockFile[nFile].nSize;
}
if ((int)nFile != nLastBlockFile) {
if (!fKnown) {
LogPrintf("Leaving block file %i: %s\n", nLastBlockFile,
vinfoBlockFile[nLastBlockFile].ToString());
}
FlushBlockFile(!fKnown);
nLastBlockFile = nFile;
}
vinfoBlockFile[nFile].AddBlock(nHeight, nTime);
if (fKnown) {
vinfoBlockFile[nFile].nSize =
std::max(pos.nPos + nAddSize, vinfoBlockFile[nFile].nSize);
} else {
vinfoBlockFile[nFile].nSize += nAddSize;
}
if (!fKnown) {
bool out_of_space;
size_t bytes_allocated =
BlockFileSeq().Allocate(pos, nAddSize, out_of_space);
if (out_of_space) {
return AbortNode("Disk space is low!",
_("Error: Disk space is low!"));
}
if (bytes_allocated != 0 && fPruneMode) {
fCheckForPruning = true;
}
}
setDirtyFileInfo.insert(nFile);
return true;
}
static bool FindUndoPos(CValidationState &state, int nFile, CDiskBlockPos &pos,
unsigned int nAddSize) {
pos.nFile = nFile;
LOCK(cs_LastBlockFile);
pos.nPos = vinfoBlockFile[nFile].nUndoSize;
vinfoBlockFile[nFile].nUndoSize += nAddSize;
setDirtyFileInfo.insert(nFile);
bool out_of_space;
size_t bytes_allocated =
UndoFileSeq().Allocate(pos, nAddSize, out_of_space);
if (out_of_space) {
return AbortNode(state, "Disk space is low!",
_("Error: Disk space is low!"));
}
if (bytes_allocated != 0 && fPruneMode) {
fCheckForPruning = true;
}
return true;
}
/**
* 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 Config &config, const CBlockHeader &block, CValidationState &state,
BlockValidationOptions validationOptions = BlockValidationOptions()) {
// Check proof of work matches claimed amount
if (validationOptions.shouldValidatePoW() &&
!CheckProofOfWork(block.GetHash(), block.nBits, config)) {
return state.DoS(50, false, REJECT_INVALID, "high-hash", false,
"proof of work failed");
}
return true;
}
bool CheckBlock(const Config &config, const CBlock &block,
CValidationState &state,
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(config, block, state, validationOptions)) {
return false;
}
// Check the merkle root.
if (validationOptions.shouldValidateMerkleRoot()) {
bool mutated;
uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated);
if (block.hashMerkleRoot != hashMerkleRoot2) {
return state.DoS(100, false, REJECT_INVALID, "bad-txnmrklroot",
true, "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.DoS(100, false, REJECT_INVALID, "bad-txns-duplicate",
true, "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.DoS(100, false, REJECT_INVALID, "bad-cb-missing", false,
"first tx is not coinbase");
}
// Size limits.
auto nMaxBlockSize = config.GetMaxBlockSize();
// Bail early if there is no way this block is of reasonable size.
if ((block.vtx.size() * MIN_TRANSACTION_SIZE) > nMaxBlockSize) {
return state.DoS(100, false, REJECT_INVALID, "bad-blk-length", false,
"size limits failed");
}
auto currentBlockSize =
::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION);
if (currentBlockSize > nMaxBlockSize) {
return state.DoS(100, false, REJECT_INVALID, "bad-blk-length", false,
"size limits failed");
}
// And a valid coinbase.
if (!CheckCoinbase(*block.vtx[0], state)) {
return state.Invalid(false, state.GetRejectCode(),
state.GetRejectReason(),
strprintf("Coinbase check failed (txid %s) %s",
block.vtx[0]->GetId().ToString(),
state.GetDebugMessage()));
}
// Keep track of the sigops count.
uint64_t nSigOps = 0;
auto nMaxSigOpsCount = GetMaxBlockSigOpsCount(currentBlockSize);
// Check transactions
auto txCount = block.vtx.size();
auto *tx = block.vtx[0].get();
size_t i = 0;
while (true) {
// Count the sigops for the current transaction. If the total sigops
// count is too high, the the block is invalid.
nSigOps += GetSigOpCountWithoutP2SH(*tx, STANDARD_SCRIPT_VERIFY_FLAGS);
if (nSigOps > nMaxSigOpsCount) {
return state.DoS(100, false, REJECT_INVALID, "bad-blk-sigops",
false, "out-of-bounds SigOpCount");
}
// Go to the next transaction.
i++;
// We reached the end of the block, success.
if (i >= txCount) {
break;
}
// Check that the transaction is valid. Because this check differs for
// the coinbase, the loop is arranged such as this only runs after at
// least one increment.
tx = block.vtx[i].get();
if (!CheckRegularTransaction(*tx, state)) {
return state.Invalid(
false, state.GetRejectCode(), state.GetRejectReason(),
strprintf("Transaction check failed (txid %s) %s",
tx->GetId().ToString(), state.GetDebugMessage()));
}
}
if (validationOptions.shouldValidatePoW() &&
validationOptions.shouldValidateMerkleRoot()) {
block.fChecked = true;
}
return true;
}
/**
* 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 Config &config,
const CBlockHeader &block,
CValidationState &state,
const CBlockIndex *pindexPrev,
int64_t nAdjustedTime) {
assert(pindexPrev != nullptr);
const int nHeight = pindexPrev->nHeight + 1;
// Check proof of work
const Consensus::Params &consensusParams =
config.GetChainParams().GetConsensus();
if (block.nBits != GetNextWorkRequired(pindexPrev, &block, config)) {
LogPrintf("bad bits after height: %d\n", pindexPrev->nHeight);
return state.DoS(100, false, REJECT_INVALID, "bad-diffbits", false,
"incorrect proof of work");
}
// Check against checkpoints
if (fCheckpointsEnabled) {
const CCheckpointData &checkpoints =
config.GetChainParams().Checkpoints();
// Check that the block chain matches the known block chain up to a
// checkpoint.
if (!Checkpoints::CheckBlock(checkpoints, nHeight, block.GetHash())) {
return state.DoS(100,
error("%s: rejected by checkpoint lock-in at %d",
__func__, nHeight),
REJECT_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 MapBlockIndex.
CBlockIndex *pcheckpoint = Checkpoints::GetLastCheckpoint(checkpoints);
if (pcheckpoint && nHeight < pcheckpoint->nHeight) {
return state.DoS(
100,
error("%s: forked chain older than last checkpoint (height %d)",
__func__, nHeight),
REJECT_CHECKPOINT, "bad-fork-prior-to-checkpoint");
}
}
// Check timestamp against prev
if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) {
return state.Invalid(false, REJECT_INVALID, "time-too-old",
"block's timestamp is too early");
}
// Check timestamp
if (block.GetBlockTime() > nAdjustedTime + MAX_FUTURE_BLOCK_TIME) {
return state.Invalid(false, REJECT_INVALID, "time-too-new",
"block timestamp too far in the future");
}
// Reject outdated version blocks when 95% (75% on testnet) of the network
// has upgraded:
// check for version 2, 3 and 4 upgrades
if ((block.nVersion < 2 && nHeight >= consensusParams.BIP34Height) ||
(block.nVersion < 3 && nHeight >= consensusParams.BIP66Height) ||
(block.nVersion < 4 && nHeight >= consensusParams.BIP65Height)) {
return state.Invalid(
false, REJECT_OBSOLETE,
strprintf("bad-version(0x%08x)", block.nVersion),
strprintf("rejected nVersion=0x%08x block", block.nVersion));
}
return true;
}
bool ContextualCheckTransactionForCurrentBlock(const Config &config,
const CTransaction &tx,
CValidationState &state,
int flags) {
AssertLockHeld(cs_main);
// By convention a negative value for flags indicates that the current
// network-enforced consensus rules should be used. In a future soft-fork
// scenario that would mean checking which rules would be enforced for the
// next block and setting the appropriate flags. At the present time no
// soft-forks are scheduled, so no flags are set.
flags = std::max(flags, 0);
// ContextualCheckTransactionForCurrentBlock() uses chainActive.Height()+1
// to evaluate nLockTime because when IsFinalTx() is called within
// CBlock::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 chainActive.Height().
const int nBlockHeight = chainActive.Height() + 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() if LOCKTIME_MEDIAN_TIME_PAST is set.
const int64_t nMedianTimePast =
chainActive.Tip() == nullptr ? 0
: chainActive.Tip()->GetMedianTimePast();
const int64_t nLockTimeCutoff = (flags & LOCKTIME_MEDIAN_TIME_PAST)
? nMedianTimePast
: GetAdjustedTime();
return ContextualCheckTransaction(config, tx, state, nBlockHeight,
nLockTimeCutoff, 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 Config &config, const CBlock &block,
CValidationState &state,
const CBlockIndex *pindexPrev) {
const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1;
const Consensus::Params &consensusParams =
config.GetChainParams().GetConsensus();
// Start enforcing BIP113 (Median Time Past).
int nLockTimeFlags = 0;
if (nHeight >= consensusParams.CSVHeight) {
nLockTimeFlags |= LOCKTIME_MEDIAN_TIME_PAST;
}
const int64_t nMedianTimePast =
pindexPrev == nullptr ? 0 : pindexPrev->GetMedianTimePast();
const int64_t nLockTimeCutoff = (nLockTimeFlags & LOCKTIME_MEDIAN_TIME_PAST)
? nMedianTimePast
: block.GetBlockTime();
const bool fIsMagneticAnomalyEnabled =
IsMagneticAnomalyEnabled(config, pindexPrev);
// Check that all transactions are finalized
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.DoS(100, false, REJECT_INVALID, "tx-duplicate",
false,
strprintf("Duplicated transaction %s",
tx.GetId().ToString()));
}
return state.DoS(
100, false, REJECT_INVALID, "tx-ordering", false,
strprintf("Transaction order is invalid (%s < %s)",
tx.GetId().ToString(),
prevTx->GetId().ToString()));
}
if (prevTx || !tx.IsCoinBase()) {
prevTx = &tx;
}
}
if (!ContextualCheckTransaction(config, tx, state, nHeight,
nLockTimeCutoff, nMedianTimePast)) {
// state set by ContextualCheckTransaction.
return false;
}
}
// Enforce rule that the coinbase starts with serialized block height
if (nHeight >= consensusParams.BIP34Height) {
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.DoS(100, false, REJECT_INVALID, "bad-cb-height", false,
"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 CChainState::AcceptBlockHeader(const Config &config,
const CBlockHeader &block,
CValidationState &state,
CBlockIndex **ppindex) {
AssertLockHeld(cs_main);
const CChainParams &chainparams = config.GetChainParams();
// Check for duplicate
uint256 hash = block.GetHash();
BlockMap::iterator miSelf = mapBlockIndex.find(hash);
CBlockIndex *pindex = nullptr;
if (hash != chainparams.GetConsensus().hashGenesisBlock) {
if (miSelf != mapBlockIndex.end()) {
// Block header is already known.
pindex = miSelf->second;
if (ppindex) {
*ppindex = pindex;
}
if (pindex->nStatus.isInvalid()) {
return state.Invalid(error("%s: block %s is marked invalid",
__func__, hash.ToString()),
0, "duplicate");
}
return true;
}
if (!CheckBlockHeader(config, block, state)) {
return error("%s: Consensus::CheckBlockHeader: %s, %s", __func__,
hash.ToString(), FormatStateMessage(state));
}
// Get prev block index
BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock);
if (mi == mapBlockIndex.end()) {
return state.DoS(10, error("%s: prev block not found", __func__), 0,
"prev-blk-not-found");
}
CBlockIndex *pindexPrev = (*mi).second;
assert(pindexPrev);
if (pindexPrev->nStatus.isInvalid()) {
return state.DoS(100, error("%s: prev block invalid", __func__),
REJECT_INVALID, "bad-prevblk");
}
if (!ContextualCheckBlockHeader(config, block, state, pindexPrev,
GetAdjustedTime())) {
return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s",
__func__, hash.ToString(), FormatStateMessage(state));
}
if (!pindexPrev->IsValid(BlockValidity::SCRIPTS)) {
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();
setDirtyBlockIndex.insert(invalid_walk);
invalid_walk = invalid_walk->pprev;
}
return state.DoS(100,
error("%s: prev block invalid", __func__),
REJECT_INVALID, "bad-prevblk");
}
}
}
}
if (pindex == nullptr) {
pindex = AddToBlockIndex(block);
}
if (ppindex) {
*ppindex = pindex;
}
CheckBlockIndex(chainparams.GetConsensus());
return true;
}
// Exposed wrapper for AcceptBlockHeader
bool ProcessNewBlockHeaders(const Config &config,
const std::vector<CBlockHeader> &headers,
CValidationState &state,
const CBlockIndex **ppindex,
CBlockHeader *first_invalid) {
if (first_invalid != nullptr) {
first_invalid->SetNull();
}
{
LOCK(cs_main);
for (const CBlockHeader &header : headers) {
// Use a temp pindex instead of ppindex to avoid a const_cast
CBlockIndex *pindex = nullptr;
if (!g_chainstate.AcceptBlockHeader(config, header, state,
&pindex)) {
if (first_invalid) {
*first_invalid = header;
}
return false;
}
if (ppindex) {
*ppindex = pindex;
}
}
}
NotifyHeaderTip();
return true;
}
/**
* Store block on disk. If dbp is non-nullptr, the file is known to already
* reside on disk.
*/
static CDiskBlockPos SaveBlockToDisk(const CBlock &block, int nHeight,
const CChainParams &chainparams,
const CDiskBlockPos *dbp) {
unsigned int nBlockSize =
::GetSerializeSize(block, SER_DISK, CLIENT_VERSION);
CDiskBlockPos blockPos;
if (dbp != nullptr) {
blockPos = *dbp;
}
if (!FindBlockPos(blockPos, nBlockSize + 8, nHeight, block.GetBlockTime(),
dbp != nullptr)) {
error("%s: FindBlockPos failed", __func__);
return CDiskBlockPos();
}
if (dbp == nullptr) {
if (!WriteBlockToDisk(block, blockPos, chainparams.DiskMagic())) {
AbortNode("Failed to write block");
return CDiskBlockPos();
}
}
return blockPos;
}
/**
* Store a block on disk.
*
* @param[in] config The global config.
* @param[in-out] pblock The block we want to accept.
* @param[in] fRequested A boolean to indicate if this block was requested
* from our peers.
* @param[in] dbp If non-null, the disk position of the block.
* @param[in-out] fNewBlock True if block was first received via this call.
* @return True if the block is accepted as a valid block and written to disk.
*/
bool CChainState::AcceptBlock(const Config &config,
const std::shared_ptr<const CBlock> &pblock,
CValidationState &state, bool fRequested,
const CDiskBlockPos *dbp, bool *fNewBlock) {
AssertLockHeld(cs_main);
const CBlock &block = *pblock;
if (fNewBlock) {
*fNewBlock = false;
}
CBlockIndex *pindex = nullptr;
if (!AcceptBlockHeader(config, block, state, &pindex)) {
return false;
}
// Try to process all requested blocks that we don't have, but only
// process an unrequested block if it's new and has enough work to
// advance our tip, and isn't too many blocks ahead.
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 =
chainActive.Tip() ? std::llabs(chainActive.Tip()->GetReceivedTimeDiff())
: 0;
bool isSameHeight = chainActive.Tip() &&
(pindex->nChainWork == chainActive.Tip()->nChainWork);
if (isSameHeight) {
LogPrintf("Chain tip timestamp-to-received-time difference: hash=%s, "
"diff=%d\n",
chainActive.Tip()->GetBlockHash().ToString(),
chainTipTimeDiff);
LogPrintf("New block timestamp-to-received-time difference: hash=%s, "
"diff=%d\n",
pindex->GetBlockHash().ToString(), newBlockTimeDiff);
}
bool fHasMoreOrSameWork =
(chainActive.Tip() ? pindex->nChainWork >= chainActive.Tip()->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 > int(chainActive.Height() + 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.
// 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 < nMinimumChainWork) {
return true;
}
}
if (fNewBlock) {
*fNewBlock = true;
}
if (!CheckBlock(config, block, state) ||
!ContextualCheckBlock(config, block, state, pindex->pprev)) {
if (state.IsInvalid() && !state.CorruptionPossible()) {
pindex->nStatus = pindex->nStatus.withFailed();
setDirtyBlockIndex.insert(pindex);
}
return error("%s: %s (block %s)", __func__, FormatStateMessage(state),
block.GetHash().ToString());
}
// If this is a deep reorg (a regorg of more than one block), preemptively
// mark the chain as parked. If it has enough work, it'll unpark
// automatically. 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)) {
const CBlockIndex *pindexFork = chainActive.FindFork(pindex);
if (pindexFork && pindexFork->nHeight + 1 < pindex->nHeight) {
LogPrintf("Park block %s as it would cause a deep reorg.\n",
pindex->GetBlockHash().ToString());
pindex->nStatus = pindex->nStatus.withParked();
setDirtyBlockIndex.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() && chainActive.Tip() == pindex->pprev) {
GetMainSignals().NewPoWValidBlock(pindex, pblock);
}
const CChainParams &chainparams = config.GetChainParams();
// Write block to history file
try {
CDiskBlockPos blockPos =
SaveBlockToDisk(block, pindex->nHeight, chainparams, dbp);
if (blockPos.IsNull()) {
state.Error(strprintf(
"%s: Failed to find position to write new block to disk",
__func__));
return false;
}
if (!ReceivedBlockTransactions(block, state, pindex, blockPos)) {
return error("AcceptBlock(): ReceivedBlockTransactions failed");
}
} catch (const std::runtime_error &e) {
return AbortNode(state, std::string("System error: ") + e.what());
}
if (fCheckForPruning) {
// we just allocated more disk space for block files.
FlushStateToDisk(config.GetChainParams(), state, FlushStateMode::NONE);
}
CheckBlockIndex(chainparams.GetConsensus());
return true;
}
bool ProcessNewBlock(const Config &config,
const std::shared_ptr<const CBlock> pblock,
bool fForceProcessing, bool *fNewBlock) {
AssertLockNotHeld(cs_main);
{
if (fNewBlock) {
*fNewBlock = false;
}
CValidationState 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);
// Ensure that CheckBlock() passes before calling AcceptBlock, as
// belt-and-suspenders.
bool ret = CheckBlock(config, *pblock, state);
if (ret) {
// Store to disk
ret = g_chainstate.AcceptBlock(
config, pblock, state, fForceProcessing, nullptr, fNewBlock);
}
if (!ret) {
GetMainSignals().BlockChecked(*pblock, state);
return error("%s: AcceptBlock FAILED (%s)", __func__,
state.GetDebugMessage());
}
}
NotifyHeaderTip();
// Only used to report errors, not invalidity - ignore it
CValidationState state;
if (!g_chainstate.ActivateBestChain(config, state, pblock)) {
return error("%s: ActivateBestChain failed", __func__);
}
return true;
}
bool TestBlockValidity(const Config &config, CValidationState &state,
const CBlock &block, CBlockIndex *pindexPrev,
BlockValidationOptions validationOptions) {
AssertLockHeld(cs_main);
assert(pindexPrev && pindexPrev == chainActive.Tip());
CCoinsViewCache viewNew(pcoinsTip.get());
uint256 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(config, block, state, pindexPrev,
GetAdjustedTime())) {
return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__,
FormatStateMessage(state));
}
if (!CheckBlock(config, block, state, validationOptions)) {
return error("%s: Consensus::CheckBlock: %s", __func__,
FormatStateMessage(state));
}
if (!ContextualCheckBlock(config, block, state, pindexPrev)) {
return error("%s: Consensus::ContextualCheckBlock: %s", __func__,
FormatStateMessage(state));
}
if (!g_chainstate.ConnectBlock(config, block, state, &indexDummy, viewNew,
true)) {
return false;
}
assert(state.IsValid());
return true;
}
/**
* BLOCK PRUNING CODE
*/
/**
* Calculate the amount of disk space the block & undo files currently use.
*/
uint64_t CalculateCurrentUsage() {
LOCK(cs_LastBlockFile);
uint64_t retval = 0;
for (const CBlockFileInfo &file : vinfoBlockFile) {
retval += file.nSize + file.nUndoSize;
}
return retval;
}
/**
* Prune a block file (modify associated database entries)
*/
void PruneOneBlockFile(const int fileNumber) {
LOCK(cs_LastBlockFile);
for (const auto &entry : mapBlockIndex) {
CBlockIndex *pindex = entry.second;
if (pindex->nFile == fileNumber) {
pindex->nStatus = pindex->nStatus.withData(false).withUndo(false);
pindex->nFile = 0;
pindex->nDataPos = 0;
pindex->nUndoPos = 0;
setDirtyBlockIndex.insert(pindex);
// Prune from mapBlocksUnlinked -- any block we prune would have
// to be downloaded again in order to consider its chain, at which
// point it would be considered as a candidate for
// mapBlocksUnlinked or setBlockIndexCandidates.
std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
range = mapBlocksUnlinked.equal_range(pindex->pprev);
while (range.first != range.second) {
std::multimap<CBlockIndex *, CBlockIndex *>::iterator _it =
range.first;
range.first++;
if (_it->second == pindex) {
mapBlocksUnlinked.erase(_it);
}
}
}
}
vinfoBlockFile[fileNumber].SetNull();
setDirtyFileInfo.insert(fileNumber);
}
void UnlinkPrunedFiles(const std::set<int> &setFilesToPrune) {
for (const int i : setFilesToPrune) {
CDiskBlockPos pos(i, 0);
fs::remove(BlockFileSeq().FileName(pos));
fs::remove(UndoFileSeq().FileName(pos));
LogPrintf("Prune: %s deleted blk/rev (%05u)\n", __func__, i);
}
}
/**
* Calculate the block/rev files to delete based on height specified by user
* with RPC command pruneblockchain
*/
static void FindFilesToPruneManual(std::set<int> &setFilesToPrune,
int nManualPruneHeight) {
assert(fPruneMode && nManualPruneHeight > 0);
LOCK2(cs_main, cs_LastBlockFile);
if (chainActive.Tip() == nullptr) {
return;
}
// last block to prune is the lesser of (user-specified height,
// MIN_BLOCKS_TO_KEEP from the tip)
unsigned int nLastBlockWeCanPrune =
std::min((unsigned)nManualPruneHeight,
chainActive.Tip()->nHeight - MIN_BLOCKS_TO_KEEP);
int count = 0;
for (int fileNumber = 0; fileNumber < nLastBlockFile; fileNumber++) {
if (vinfoBlockFile[fileNumber].nSize == 0 ||
vinfoBlockFile[fileNumber].nHeightLast > nLastBlockWeCanPrune) {
continue;
}
PruneOneBlockFile(fileNumber);
setFilesToPrune.insert(fileNumber);
count++;
}
LogPrintf("Prune (Manual): prune_height=%d removed %d blk/rev pairs\n",
nLastBlockWeCanPrune, count);
}
/* This function is called from the RPC code for pruneblockchain */
void PruneBlockFilesManual(int nManualPruneHeight) {
CValidationState state;
const CChainParams &chainparams = Params();
FlushStateToDisk(chainparams, state, FlushStateMode::NONE,
nManualPruneHeight);
}
/**
* Prune block and undo files (blk???.dat and undo???.dat) so that the disk
* space used is less than a user-defined target. The user sets the target (in
* MB) on the command line or in config file. This will be run on startup and
* whenever new space is allocated in a block or undo file, staying below the
* target. Changing back to unpruned requires a reindex (which in this case
* means the blockchain must be re-downloaded.)
*
* Pruning functions are called from FlushStateToDisk when the global
* fCheckForPruning flag has been set. Block and undo files are deleted in
* lock-step (when blk00003.dat is deleted, so is rev00003.dat.). Pruning cannot
* take place until the longest chain is at least a certain length (100000 on
* mainnet, 1000 on testnet, 1000 on regtest). Pruning will never delete a block
* within a defined distance (currently 288) from the active chain's tip. The
* block index is updated by unsetting HAVE_DATA and HAVE_UNDO for any blocks
* that were stored in the deleted files. A db flag records the fact that at
* least some block files have been pruned.
*
* @param[out] setFilesToPrune The set of file indices that can be unlinked
* will be returned
*/
static void FindFilesToPrune(std::set<int> &setFilesToPrune,
uint64_t nPruneAfterHeight) {
LOCK2(cs_main, cs_LastBlockFile);
if (chainActive.Tip() == nullptr || nPruneTarget == 0) {
return;
}
if (uint64_t(chainActive.Tip()->nHeight) <= nPruneAfterHeight) {
return;
}
unsigned int nLastBlockWeCanPrune =
chainActive.Tip()->nHeight - MIN_BLOCKS_TO_KEEP;
uint64_t nCurrentUsage = CalculateCurrentUsage();
// We don't check to prune until after we've allocated new space for files,
// so we should leave a buffer under our target to account for another
// allocation before the next pruning.
uint64_t nBuffer = BLOCKFILE_CHUNK_SIZE + UNDOFILE_CHUNK_SIZE;
uint64_t nBytesToPrune;
int count = 0;
if (nCurrentUsage + nBuffer >= nPruneTarget) {
for (int fileNumber = 0; fileNumber < nLastBlockFile; fileNumber++) {
nBytesToPrune = vinfoBlockFile[fileNumber].nSize +
vinfoBlockFile[fileNumber].nUndoSize;
if (vinfoBlockFile[fileNumber].nSize == 0) {
continue;
}
// are we below our target?
if (nCurrentUsage + nBuffer < nPruneTarget) {
break;
}
// don't prune files that could have a block within
// MIN_BLOCKS_TO_KEEP of the main chain's tip but keep scanning
if (vinfoBlockFile[fileNumber].nHeightLast > nLastBlockWeCanPrune) {
continue;
}
PruneOneBlockFile(fileNumber);
// Queue up the files for removal
setFilesToPrune.insert(fileNumber);
nCurrentUsage -= nBytesToPrune;
count++;
}
}
LogPrint(BCLog::PRUNE,
"Prune: target=%dMiB actual=%dMiB diff=%dMiB "
"max_prune_height=%d removed %d blk/rev pairs\n",
nPruneTarget / 1024 / 1024, nCurrentUsage / 1024 / 1024,
((int64_t)nPruneTarget - (int64_t)nCurrentUsage) / 1024 / 1024,
nLastBlockWeCanPrune, count);
}
static FlatFileSeq BlockFileSeq() {
return FlatFileSeq(GetBlocksDir(), "blk", BLOCKFILE_CHUNK_SIZE);
}
static FlatFileSeq UndoFileSeq() {
return FlatFileSeq(GetBlocksDir(), "rev", UNDOFILE_CHUNK_SIZE);
}
FILE *OpenBlockFile(const CDiskBlockPos &pos, bool fReadOnly) {
return BlockFileSeq().Open(pos, fReadOnly);
}
/** Open an undo file (rev?????.dat) */
static FILE *OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly) {
return UndoFileSeq().Open(pos, fReadOnly);
}
fs::path GetBlockPosFilename(const CDiskBlockPos &pos) {
return BlockFileSeq().FileName(pos);
}
CBlockIndex *CChainState::InsertBlockIndex(const uint256 &hash) {
AssertLockHeld(cs_main);
if (hash.IsNull()) {
return nullptr;
}
// Return existing
BlockMap::iterator mi = mapBlockIndex.find(hash);
if (mi != mapBlockIndex.end()) {
return (*mi).second;
}
// Create new
CBlockIndex *pindexNew = new CBlockIndex();
mi = mapBlockIndex.insert(std::make_pair(hash, pindexNew)).first;
pindexNew->phashBlock = &((*mi).first);
return pindexNew;
}
bool CChainState::LoadBlockIndex(const Config &config,
CBlockTreeDB &blocktree) {
if (!blocktree.LoadBlockIndexGuts(config, [this](const uint256 &hash) {
return this->InsertBlockIndex(hash);
})) {
return false;
}
boost::this_thread::interruption_point();
// Calculate nChainWork
std::vector<std::pair<int, CBlockIndex *>> vSortedByHeight;
vSortedByHeight.reserve(mapBlockIndex.size());
for (const std::pair<uint256, CBlockIndex *> &item : mapBlockIndex) {
CBlockIndex *pindex = item.second;
vSortedByHeight.push_back(std::make_pair(pindex->nHeight, pindex));
}
sort(vSortedByHeight.begin(), vSortedByHeight.end());
for (const std::pair<int, CBlockIndex *> &item : vSortedByHeight) {
CBlockIndex *pindex = item.second;
pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) +
GetBlockProof(*pindex);
pindex->nTimeMax =
(pindex->pprev ? std::max(pindex->pprev->nTimeMax, pindex->nTime)
: pindex->nTime);
// We can link the chain of blocks for which we've received transactions
// at some point. Pruned nodes may have deleted the block.
if (pindex->nTx > 0) {
if (pindex->pprev) {
if (pindex->pprev->nChainTx) {
pindex->nChainTx = pindex->pprev->nChainTx + pindex->nTx;
} else {
pindex->nChainTx = 0;
mapBlocksUnlinked.insert(
std::make_pair(pindex->pprev, pindex));
}
} else {
pindex->nChainTx = pindex->nTx;
}
}
if (!pindex->nStatus.hasFailed() && pindex->pprev &&
pindex->pprev->nStatus.hasFailed()) {
pindex->nStatus = pindex->nStatus.withFailedParent();
setDirtyBlockIndex.insert(pindex);
}
if (pindex->IsValid(BlockValidity::TRANSACTIONS) &&
(pindex->nChainTx || pindex->pprev == nullptr)) {
setBlockIndexCandidates.insert(pindex);
}
if (pindex->nStatus.isInvalid() &&
(!pindexBestInvalid ||
pindex->nChainWork > pindexBestInvalid->nChainWork)) {
pindexBestInvalid = pindex;
}
if (pindex->nStatus.isOnParkedChain() &&
(!pindexBestParked ||
pindex->nChainWork > pindexBestParked->nChainWork)) {
pindexBestParked = pindex;
}
if (pindex->pprev) {
pindex->BuildSkip();
}
if (pindex->IsValid(BlockValidity::TREE) &&
(pindexBestHeader == nullptr ||
CBlockIndexWorkComparator()(pindexBestHeader, pindex))) {
pindexBestHeader = pindex;
}
}
return true;
}
bool static LoadBlockIndexDB(const Config &config) {
if (!g_chainstate.LoadBlockIndex(config, *pblocktree)) {
return false;
}
// Load block file info
pblocktree->ReadLastBlockFile(nLastBlockFile);
vinfoBlockFile.resize(nLastBlockFile + 1);
LogPrintf("%s: last block file = %i\n", __func__, nLastBlockFile);
for (int nFile = 0; nFile <= nLastBlockFile; nFile++) {
pblocktree->ReadBlockFileInfo(nFile, vinfoBlockFile[nFile]);
}
LogPrintf("%s: last block file info: %s\n", __func__,
vinfoBlockFile[nLastBlockFile].ToString());
for (int nFile = nLastBlockFile + 1; true; nFile++) {
CBlockFileInfo info;
if (pblocktree->ReadBlockFileInfo(nFile, info)) {
vinfoBlockFile.push_back(info);
} else {
break;
}
}
// Check presence of blk files
LogPrintf("Checking all blk files are present...\n");
std::set<int> setBlkDataFiles;
for (const std::pair<uint256, CBlockIndex *> &item : mapBlockIndex) {
CBlockIndex *pindex = item.second;
if (pindex->nStatus.hasData()) {
setBlkDataFiles.insert(pindex->nFile);
}
}
for (const int i : setBlkDataFiles) {
CDiskBlockPos pos(i, 0);
if (CAutoFile(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION)
.IsNull()) {
return false;
}
}
// Check whether we have ever pruned block & undo files
pblocktree->ReadFlag("prunedblockfiles", fHavePruned);
if (fHavePruned) {
LogPrintf(
"LoadBlockIndexDB(): Block files have previously been pruned\n");
}
// Check whether we need to continue reindexing
bool fReindexing = false;
pblocktree->ReadReindexing(fReindexing);
if (fReindexing) {
fReindex = true;
}
return true;
}
bool LoadChainTip(const Config &config) {
AssertLockHeld(cs_main);
if (chainActive.Tip() &&
chainActive.Tip()->GetBlockHash() == pcoinsTip->GetBestBlock()) {
return true;
}
if (pcoinsTip->GetBestBlock().IsNull() && mapBlockIndex.size() == 1) {
// In case we just added the genesis block, connect it now, so
// that we always have a chainActive.Tip() when we return.
LogPrintf("%s: Connecting genesis block...\n", __func__);
CValidationState state;
if (!ActivateBestChain(config, state)) {
return false;
}
}
// Load pointer to end of best chain
CBlockIndex *pindex = LookupBlockIndex(pcoinsTip->GetBestBlock());
if (!pindex) {
return false;
}
chainActive.SetTip(pindex);
g_chainstate.PruneBlockIndexCandidates();
LogPrintf(
"Loaded best chain: hashBestChain=%s height=%d date=%s progress=%f\n",
chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(),
FormatISO8601DateTime(chainActive.Tip()->GetBlockTime()),
GuessVerificationProgress(config.GetChainParams().TxData(),
chainActive.Tip()));
return true;
}
CVerifyDB::CVerifyDB() {
uiInterface.ShowProgress(_("Verifying blocks..."), 0, false);
}
CVerifyDB::~CVerifyDB() {
uiInterface.ShowProgress("", 100, false);
}
bool CVerifyDB::VerifyDB(const Config &config, CCoinsView *coinsview,
int nCheckLevel, int nCheckDepth) {
LOCK(cs_main);
if (chainActive.Tip() == nullptr || chainActive.Tip()->pprev == nullptr) {
return true;
}
// Verify blocks in the best chain
if (nCheckDepth <= 0 || nCheckDepth > chainActive.Height()) {
nCheckDepth = chainActive.Height();
}
nCheckLevel = std::max(0, std::min(4, nCheckLevel));
LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth,
nCheckLevel);
CCoinsViewCache coins(coinsview);
CBlockIndex *pindexState = chainActive.Tip();
CBlockIndex *pindexFailure = nullptr;
int nGoodTransactions = 0;
CValidationState state;
int reportDone = 0;
LogPrintf("[0%%]...");
for (CBlockIndex *pindex = chainActive.Tip(); pindex && pindex->pprev;
pindex = pindex->pprev) {
boost::this_thread::interruption_point();
int percentageDone = std::max(
1, std::min(
99,
(int)(((double)(chainActive.Height() - pindex->nHeight)) /
(double)nCheckDepth * (nCheckLevel >= 4 ? 50 : 100))));
if (reportDone < percentageDone / 10) {
// report every 10% step
LogPrintf("[%d%%]...", percentageDone);
reportDone = percentageDone / 10;
}
uiInterface.ShowProgress(_("Verifying blocks..."), percentageDone,
false);
if (pindex->nHeight < chainActive.Height() - nCheckDepth) {
break;
}
if (fPruneMode && !pindex->nStatus.hasData()) {
// If pruning, only go back as far as we have data.
LogPrintf("VerifyDB(): block verification stopping at height %d "
"(pruning, no data)\n",
pindex->nHeight);
break;
}
CBlock block;
// check level 0: read from disk
if (!ReadBlockFromDisk(block, pindex, config)) {
return error(
"VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s",
pindex->nHeight, pindex->GetBlockHash().ToString());
}
// check level 1: verify block validity
if (nCheckLevel >= 1 && !CheckBlock(config, block, state)) {
return error("%s: *** found bad block at %d, hash=%s (%s)\n",
__func__, pindex->nHeight,
pindex->GetBlockHash().ToString(),
FormatStateMessage(state));
}
// check level 2: verify undo validity
if (nCheckLevel >= 2 && pindex) {
CBlockUndo undo;
if (!pindex->GetUndoPos().IsNull()) {
if (!UndoReadFromDisk(undo, pindex)) {
return error(
"VerifyDB(): *** found bad undo data at %d, hash=%s\n",
pindex->nHeight, pindex->GetBlockHash().ToString());
}
}
}
// check level 3: check for inconsistencies during memory-only
// disconnect of tip blocks
if (nCheckLevel >= 3 && pindex == pindexState &&
(coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <=
nCoinCacheUsage) {
assert(coins.GetBestBlock() == pindex->GetBlockHash());
DisconnectResult res =
g_chainstate.DisconnectBlock(block, pindex, coins);
if (res == DISCONNECT_FAILED) {
return error("VerifyDB(): *** irrecoverable inconsistency in "
"block data at %d, hash=%s",
pindex->nHeight,
pindex->GetBlockHash().ToString());
}
pindexState = pindex->pprev;
if (res == DISCONNECT_UNCLEAN) {
nGoodTransactions = 0;
pindexFailure = pindex;
} else {
nGoodTransactions += block.vtx.size();
}
}
if (ShutdownRequested()) {
return true;
}
}
if (pindexFailure) {
return error("VerifyDB(): *** coin database inconsistencies found "
"(last %i blocks, %i good transactions before that)\n",
chainActive.Height() - pindexFailure->nHeight + 1,
nGoodTransactions);
}
// check level 4: try reconnecting blocks
if (nCheckLevel >= 4) {
CBlockIndex *pindex = pindexState;
while (pindex != chainActive.Tip()) {
boost::this_thread::interruption_point();
uiInterface.ShowProgress(
_("Verifying blocks..."),
std::max(
1, std::min(99, 100 - (int)(((double)(chainActive.Height() -
pindex->nHeight)) /
(double)nCheckDepth * 50))),
false);
pindex = chainActive.Next(pindex);
CBlock block;
if (!ReadBlockFromDisk(block, pindex, config)) {
return error(
"VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s",
pindex->nHeight, pindex->GetBlockHash().ToString());
}
if (!g_chainstate.ConnectBlock(config, block, state, pindex,
coins)) {
return error(
"VerifyDB(): *** found unconnectable block at %d, hash=%s",
pindex->nHeight, pindex->GetBlockHash().ToString());
}
}
}
LogPrintf("[DONE].\n");
LogPrintf("No coin database inconsistencies in last %i blocks (%i "
"transactions)\n",
chainActive.Height() - pindexState->nHeight, nGoodTransactions);
return true;
}
/**
* Apply the effects of a block on the utxo cache, ignoring that it may already
* have been applied.
*/
bool CChainState::RollforwardBlock(const CBlockIndex *pindex,
CCoinsViewCache &view,
const Config &config) {
// TODO: merge with ConnectBlock
CBlock block;
if (!ReadBlockFromDisk(block, pindex, config)) {
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 CChainState::ReplayBlocks(const Config &config, CCoinsView *view) {
LOCK(cs_main);
CCoinsViewCache cache(view);
std::vector<uint256> hashHeads = view->GetHeadBlocks();
if (hashHeads.empty()) {
// We're already in a consistent state.
return true;
}
if (hashHeads.size() != 2) {
return error("ReplayBlocks(): unknown inconsistent state");
}
uiInterface.ShowProgress(_("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 (mapBlockIndex.count(hashHeads[0]) == 0) {
return error(
"ReplayBlocks(): reorganization to unknown block requested");
}
pindexNew = mapBlockIndex[hashHeads[0]];
if (!hashHeads[1].IsNull()) {
// The old tip is allowed to be 0, indicating it's the first flush.
if (mapBlockIndex.count(hashHeads[1]) == 0) {
return error(
"ReplayBlocks(): reorganization from unknown block requested");
}
pindexOld = mapBlockIndex[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 (!ReadBlockFromDisk(block, pindexOld, config)) {
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 == DISCONNECT_FAILED) {
return error(
"RollbackBlock(): DisconnectBlock failed at %d, hash=%s",
pindexOld->nHeight, pindexOld->GetBlockHash().ToString());
}
// If DISCONNECT_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 = pindexNew->GetAncestor(nHeight);
LogPrintf("Rolling forward %s (%i)\n",
pindex->GetBlockHash().ToString(), nHeight);
if (!RollforwardBlock(pindex, cache, config)) {
return false;
}
}
cache.SetBestBlock(pindexNew->GetBlockHash());
cache.Flush();
uiInterface.ShowProgress("", 100, false);
return true;
}
bool ReplayBlocks(const Config &config, CCoinsView *view) {
return g_chainstate.ReplayBlocks(config, view);
}
bool CChainState::RewindBlockIndex(const Config &config) {
LOCK(cs_main);
const CChainParams ¶ms = config.GetChainParams();
int nHeight = chainActive.Height() + 1;
// nHeight is now the height of the first insufficiently-validated block, or
// tipheight + 1
CValidationState state;
CBlockIndex *pindex = chainActive.Tip();
while (chainActive.Height() >= nHeight) {
if (fPruneMode && !chainActive.Tip()->nStatus.hasData()) {
// If pruning, don't try rewinding past the HAVE_DATA point; since
// older blocks can't be served anyway, there's no need to walk
// further, and trying to DisconnectTip() will fail (and require a
// needless reindex/redownload of the blockchain).
break;
}
if (!DisconnectTip(config, state, nullptr)) {
return error(
"RewindBlockIndex: unable to disconnect block at height %i",
pindex->nHeight);
}
// Occasionally flush state to disk.
if (!FlushStateToDisk(params, state, FlushStateMode::PERIODIC)) {
return false;
}
}
// Reduce validity flag and have-data flags.
// We do this after actual disconnecting, otherwise we'll end up writing the
// lack of data to disk before writing the chainstate, resulting in a
// failure to continue if interrupted.
for (const auto &entry : mapBlockIndex) {
CBlockIndex *pindexIter = entry.second;
if (pindexIter->IsValid(BlockValidity::TRANSACTIONS) &&
pindexIter->nChainTx) {
setBlockIndexCandidates.insert(pindexIter);
}
}
if (chainActive.Tip() != nullptr) {
// We can't prune block index candidates based on our tip if we have
// no tip due to chainActive being empty!
PruneBlockIndexCandidates();
CheckBlockIndex(params.GetConsensus());
}
return true;
}
bool RewindBlockIndex(const Config &config) {
if (!g_chainstate.RewindBlockIndex(config)) {
return false;
}
if (chainActive.Tip() != nullptr) {
// FlushStateToDisk can possibly read chainActive. Be conservative
// and skip it here, we're about to -reindex-chainstate anyway, so
// it'll get called a bunch real soon.
CValidationState state;
if (!FlushStateToDisk(config.GetChainParams(), state,
FlushStateMode::ALWAYS)) {
return 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 CChainState::UnloadBlockIndex() {
nBlockSequenceId = 1;
m_failed_blocks.clear();
setBlockIndexCandidates.clear();
}
// May NOT be used after any connections are up as much
// of the peer-processing logic assumes a consistent
// block index state
void UnloadBlockIndex() {
LOCK(cs_main);
chainActive.SetTip(nullptr);
pindexFinalized = nullptr;
pindexBestInvalid = nullptr;
pindexBestParked = nullptr;
pindexBestHeader = nullptr;
pindexBestForkTip = nullptr;
pindexBestForkBase = nullptr;
g_mempool.clear();
mapBlocksUnlinked.clear();
vinfoBlockFile.clear();
nLastBlockFile = 0;
setDirtyBlockIndex.clear();
setDirtyFileInfo.clear();
for (BlockMap::value_type &entry : mapBlockIndex) {
delete entry.second;
}
mapBlockIndex.clear();
fHavePruned = false;
g_chainstate.UnloadBlockIndex();
}
bool LoadBlockIndex(const Config &config) {
// Load block index from databases
bool needs_init = fReindex;
if (!fReindex) {
bool ret = LoadBlockIndexDB(config);
if (!ret) {
return false;
}
needs_init = mapBlockIndex.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 CChainState::LoadGenesisBlock(const CChainParams &chainparams) {
LOCK(cs_main);
// Check whether we're already initialized by checking for genesis in
// mapBlockIndex. Note that we can't use chainActive 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 (mapBlockIndex.count(chainparams.GenesisBlock().GetHash())) {
return true;
}
try {
CBlock &block = const_cast<CBlock &>(chainparams.GenesisBlock());
CDiskBlockPos blockPos =
SaveBlockToDisk(block, 0, chainparams, nullptr);
if (blockPos.IsNull()) {
return error("%s: writing genesis block to disk failed", __func__);
}
CBlockIndex *pindex = AddToBlockIndex(block);
CValidationState state;
if (!ReceivedBlockTransactions(block, state, pindex, blockPos)) {
return error("%s: genesis block not accepted", __func__);
}
} catch (const std::runtime_error &e) {
return error("%s: failed to write genesis block: %s", __func__,
e.what());
}
return true;
}
bool LoadGenesisBlock(const CChainParams &chainparams) {
return g_chainstate.LoadGenesisBlock(chainparams);
}
bool LoadExternalBlockFile(const Config &config, FILE *fileIn,
CDiskBlockPos *dbp) {
// Map of disk positions for blocks with unknown parent (only used for
// reindex)
static std::multimap<uint256, CDiskBlockPos> mapBlocksUnknownParent;
int64_t nStart = GetTimeMillis();
const CChainParams &chainparams = config.GetChainParams();
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);
uint64_t nRewind = blkdat.GetPos();
while (!blkdat.eof()) {
boost::this_thread::interruption_point();
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(chainparams.DiskMagic()[0]);
nRewind = blkdat.GetPos() + 1;
blkdat >> FLATDATA(buf);
if (memcmp(buf, std::begin(chainparams.DiskMagic()),
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.
break;
}
try {
// read block
uint64_t nBlockPos = blkdat.GetPos();
if (dbp) {
dbp->nPos = nBlockPos;
}
blkdat.SetLimit(nBlockPos + nSize);
blkdat.SetPos(nBlockPos);
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
CBlock &block = *pblock;
blkdat >> block;
nRewind = blkdat.GetPos();
uint256 hash = block.GetHash();
{
LOCK(cs_main);
// detect out of order blocks, and store them for later
if (hash != chainparams.GetConsensus().hashGenesisBlock &&
!LookupBlockIndex(block.hashPrevBlock)) {
LogPrint(
BCLog::REINDEX,
"%s: Out of order block %s, parent %s not known\n",
__func__, hash.ToString(),
block.hashPrevBlock.ToString());
if (dbp) {
mapBlocksUnknownParent.insert(
std::make_pair(block.hashPrevBlock, *dbp));
}
continue;
}
// process in case the block isn't known yet
CBlockIndex *pindex = LookupBlockIndex(hash);
if (!pindex || !pindex->nStatus.hasData()) {
CValidationState state;
if (g_chainstate.AcceptBlock(config, pblock, state,
true, dbp, nullptr)) {
nLoaded++;
}
if (state.IsError()) {
break;
}
} else if (hash != chainparams.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 == chainparams.GetConsensus().hashGenesisBlock) {
CValidationState state;
if (!ActivateBestChain(config, state)) {
break;
}
}
NotifyHeaderTip();
// Recursively process earlier encountered successors of this
// block
std::deque<uint256> queue;
queue.push_back(hash);
while (!queue.empty()) {
uint256 head = queue.front();
queue.pop_front();
std::pair<std::multimap<uint256, CDiskBlockPos>::iterator,
std::multimap<uint256, CDiskBlockPos>::iterator>
range = mapBlocksUnknownParent.equal_range(head);
while (range.first != range.second) {
std::multimap<uint256, CDiskBlockPos>::iterator it =
range.first;
std::shared_ptr<CBlock> pblockrecursive =
std::make_shared<CBlock>();
if (ReadBlockFromDisk(*pblockrecursive, it->second,
config)) {
LogPrint(
BCLog::REINDEX,
"%s: Processing out of order child %s of %s\n",
__func__, pblockrecursive->GetHash().ToString(),
head.ToString());
LOCK(cs_main);
CValidationState dummy;
if (g_chainstate.AcceptBlock(
config, pblockrecursive, dummy, true,
&it->second, nullptr)) {
nLoaded++;
queue.push_back(pblockrecursive->GetHash());
}
}
range.first++;
mapBlocksUnknownParent.erase(it);
NotifyHeaderTip();
}
}
} catch (const std::exception &e) {
LogPrintf("%s: Deserialize or I/O error - %s\n", __func__,
e.what());
}
}
} catch (const std::runtime_error &e) {
AbortNode(std::string("System error: ") + e.what());
}
if (nLoaded > 0) {
LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded,
GetTimeMillis() - nStart);
}
return nLoaded > 0;
}
void CChainState::CheckBlockIndex(const Consensus::Params &consensusParams) {
if (!fCheckBlockIndex) {
return;
}
LOCK(cs_main);
// During a reindex, we read the genesis block and call CheckBlockIndex
// before ActivateBestChain, so we have the genesis block in mapBlockIndex
// but no active chain. (A few of the tests when iterating the block tree
// require that chainActive has been initialized.)
if (chainActive.Height() < 0) {
assert(mapBlockIndex.size() <= 1);
return;
}
// Build forward-pointing map of the entire block tree.
std::multimap<CBlockIndex *, CBlockIndex *> forward;
for (auto &entry : mapBlockIndex) {
forward.emplace(entry.second->pprev, entry.second);
}
assert(forward.size() == mapBlockIndex.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.
CBlockIndex *pindexFirstMissing = nullptr;
// Oldest ancestor of pindex for which nTx == 0.
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).
CBlockIndex *pindexFirstNotTransactionsValid = nullptr;
// Oldest ancestor of pindex which does not have BLOCK_VALID_CHAIN
// (regardless of being valid or not).
CBlockIndex *pindexFirstNotChainValid = nullptr;
// Oldest ancestor of pindex which does not have BLOCK_VALID_SCRIPTS
// (regardless of being valid or not).
CBlockIndex *pindexFirstNotScriptsValid = nullptr;
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 &&
pindexFirstNotTransactionsValid == nullptr &&
pindex->nStatus.getValidity() < BlockValidity::TRANSACTIONS) {
pindexFirstNotTransactionsValid = pindex;
}
if (pindex->pprev != nullptr && pindexFirstNotChainValid == nullptr &&
pindex->nStatus.getValidity() < BlockValidity::CHAIN) {
pindexFirstNotChainValid = pindex;
}
if (pindex->pprev != nullptr && 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() == consensusParams.hashGenesisBlock);
// The current active chain's genesis block must be this block.
assert(pindex == chainActive.Genesis());
}
if (pindex->nChainTx == 0) {
// 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 (!fHavePruned) {
// 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());
}
// 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 nChainTx
// being set.
// nChainTx != 0 is used to signal that all parent blocks have been
// processed (but may have been pruned).
assert((pindexFirstNeverProcessed != nullptr) ==
(pindex->nChainTx == 0));
assert((pindexFirstNotTransactionsValid != nullptr) ==
(pindex->nChainTx == 0));
// 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 mapBlockIndex 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-invalid blocks.
// The failed mask cannot be set for blocks without invalid parents.
assert(!pindex->nStatus.isOnParkedChain());
}
if (!CBlockIndexWorkComparator()(pindex, chainActive.Tip()) &&
pindexFirstNeverProcessed == nullptr) {
if (pindexFirstInvalid == nullptr) {
// If this block sorts at least as good as the current tip and
// is valid and we have all data for its parents, it must be in
// setBlockIndexCandidates or be parked.
if (pindexFirstMissing == nullptr) {
assert(pindex->nStatus.isOnParkedChain() ||
setBlockIndexCandidates.count(pindex));
}
// chainActive.Tip() must also be there even if some data has
// been pruned.
if (pindex == chainActive.Tip()) {
assert(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
// mapBlocksUnlinked -- 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(setBlockIndexCandidates.count(pindex) == 0);
}
// Check whether this block is in mapBlocksUnlinked.
std::pair<std::multimap<CBlockIndex *, CBlockIndex *>::iterator,
std::multimap<CBlockIndex *, CBlockIndex *>::iterator>
rangeUnlinked = mapBlocksUnlinked.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
// mapBlocksUnlinked.
assert(foundInUnlinked);
}
if (!pindex->nStatus.hasData()) {
// Can't be in mapBlocksUnlinked if we don't HAVE_DATA
assert(!foundInUnlinked);
}
if (pindexFirstMissing == nullptr) {
// We aren't missing data for any parent -- cannot be in
// mapBlocksUnlinked.
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.
// We must have pruned.
assert(fHavePruned);
// This block may have entered mapBlocksUnlinked 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 chainActive and the
// tip.
// So if this block is itself better than chainActive.Tip() and it
// wasn't in
// setBlockIndexCandidates, then it must be in mapBlocksUnlinked.
if (!CBlockIndexWorkComparator()(pindex, chainActive.Tip()) &&
setBlockIndexCandidates.count(pindex) == 0) {
if (pindexFirstInvalid == nullptr) {
assert(foundInUnlinked);
}
}
}
// Perhaps too slow
// assert(pindex->GetBlockHash() == pindex->GetBlockHeader().GetHash());
// End: actual consistency checks.
// Try descending into the first subnode.
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.
// 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 CBlockFileInfo::ToString() const {
return strprintf(
"CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)",
nBlocks, nSize, nHeightFirst, nHeightLast,
FormatISO8601DateTime(nTimeFirst), FormatISO8601DateTime(nTimeLast));
}
CBlockFileInfo *GetBlockFileInfo(size_t n) {
LOCK(cs_LastBlockFile);
return &vinfoBlockFile.at(n);
}
static const uint64_t MEMPOOL_DUMP_VERSION = 1;
bool LoadMempool(const Config &config) {
int64_t nExpiryTimeout =
gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60;
FILE *filestr = fsbridge::fopen(GetDataDir() / "mempool.dat", "rb");
CAutoFile file(filestr, SER_DISK, CLIENT_VERSION);
if (file.IsNull()) {
LogPrintf(
"Failed to open mempool file from disk. Continuing anyway.\n");
return false;
}
int64_t count = 0;
int64_t skipped = 0;
int64_t failed = 0;
int64_t nNow = GetTime();
try {
uint64_t version;
file >> version;
if (version != MEMPOOL_DUMP_VERSION) {
return false;
}
uint64_t num;
file >> num;
double prioritydummy = 0;
while (num--) {
CTransactionRef tx;
int64_t nTime;
int64_t nFeeDelta;
file >> tx;
file >> nTime;
file >> nFeeDelta;
Amount amountdelta = nFeeDelta * SATOSHI;
if (amountdelta != Amount::zero()) {
g_mempool.PrioritiseTransaction(tx->GetId(), prioritydummy,
amountdelta);
}
CValidationState state;
if (nTime + nExpiryTimeout > nNow) {
LOCK(cs_main);
AcceptToMemoryPoolWithTime(
config, g_mempool, state, tx, true /* fLimitFree */,
nullptr /* pfMissingInputs */, nTime,
false /* fOverrideMempoolLimit */,
Amount::zero() /* nAbsurdFee */, false /* test_accept */);
if (state.IsValid()) {
++count;
} else {
++failed;
}
} else {
++skipped;
}
if (ShutdownRequested()) {
return false;
}
}
std::map<uint256, Amount> mapDeltas;
file >> mapDeltas;
for (const auto &i : mapDeltas) {
g_mempool.PrioritiseTransaction(i.first, prioritydummy, i.second);
}
} catch (const std::exception &e) {
LogPrintf("Failed to deserialize mempool data on disk: %s. Continuing "
"anyway.\n",
e.what());
return false;
}
LogPrintf("Imported mempool transactions from disk: %i successes, %i "
"failed, %i expired\n",
count, failed, skipped);
return true;
}
bool DumpMempool() {
int64_t start = GetTimeMicros();
std::map<uint256, Amount> mapDeltas;
std::vector<TxMempoolInfo> vinfo;
static Mutex dump_mutex;
LOCK(dump_mutex);
{
LOCK(g_mempool.cs);
for (const auto &i : g_mempool.mapDeltas) {
mapDeltas[i.first] = i.second.second;
}
vinfo = g_mempool.infoAll();
}
int64_t mid = GetTimeMicros();
try {
FILE *filestr = fsbridge::fopen(GetDataDir() / "mempool.dat.new", "wb");
if (!filestr) {
return false;
}
CAutoFile file(filestr, SER_DISK, CLIENT_VERSION);
uint64_t version = MEMPOOL_DUMP_VERSION;
file << version;
file << uint64_t(vinfo.size());
for (const auto &i : vinfo) {
file << *(i.tx);
file << int64_t(i.nTime);
file << i.nFeeDelta;
mapDeltas.erase(i.tx->GetId());
}
file << mapDeltas;
if (!FileCommit(file.Get())) {
throw std::runtime_error("FileCommit failed");
}
file.fclose();
RenameOver(GetDataDir() / "mempool.dat.new",
GetDataDir() / "mempool.dat");
int64_t last = GetTimeMicros();
LogPrintf("Dumped mempool: %gs to copy, %gs to dump\n",
(mid - start) * MICRO, (last - mid) * MICRO);
} catch (const std::exception &e) {
LogPrintf("Failed to dump mempool: %s. Continuing anyway.\n", e.what());
return false;
}
return true;
}
//! 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 nChainTx might
//! be unset)
double GuessVerificationProgress(const ChainTxData &data,
const CBlockIndex *pindex) {
if (pindex == nullptr) {
return 0.0;
}
// This function assumes the lock on cs_main is already held (see the
// above comment). This is a temporary check until PR15997 is backported.
// https://github.com/bitcoin/bitcoin/pull/15997
AssertLockHeld(cs_main);
int64_t nNow = time(nullptr);
double fTxTotal;
if (pindex->nChainTx <= data.nTxCount) {
fTxTotal = data.nTxCount + (nNow - data.nTime) * data.dTxRate;
} else {
fTxTotal =
pindex->nChainTx + (nNow - pindex->GetBlockTime()) * data.dTxRate;
}
return pindex->nChainTx / fTxTotal;
}
class CMainCleanup {
public:
CMainCleanup() {}
~CMainCleanup() {
// block headers
for (const std::pair<const uint256, CBlockIndex *> &it :
mapBlockIndex) {
delete it.second;
}
mapBlockIndex.clear();
}
} instance_of_cmaincleanup;
diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py
index 2e03dce65d..917dac4a86 100755
--- a/test/functional/rpc_blockchain.py
+++ b/test/functional/rpc_blockchain.py
@@ -1,298 +1,354 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2016 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 RPCs related to blockchainstate.
Test the following RPCs:
- getblockchaininfo
- gettxoutsetinfo
- getdifficulty
- getbestblockhash
- getblockhash
- getblockheader
- getchaintxstats
- getnetworkhashps
- verifychain
Tests correspond to code in rpc/blockchain.cpp.
"""
from decimal import Decimal
import http.client
import subprocess
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_greater_than_or_equal,
assert_raises,
assert_raises_rpc_error,
assert_is_hash_string,
assert_is_hex_string,
)
+from test_framework.blocktools import (
+ create_block,
+ create_coinbase,
+)
+from test_framework.messages import (
+ msg_block,
+)
+from test_framework.mininode import (
+ P2PInterface,
+ network_thread_start,
+)
class BlockchainTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [['-stopatheight=207', '-prune=1']]
def run_test(self):
self._test_getblockchaininfo()
self._test_getchaintxstats()
self._test_gettxoutsetinfo()
self._test_getblockheader()
self._test_getdifficulty()
self._test_getnetworkhashps()
self._test_stopatheight()
self._test_getblock()
+ self._test_waitforblockheight()
assert self.nodes[0].verifychain(4, 0)
def _test_getblockchaininfo(self):
self.log.info("Test getblockchaininfo")
keys = [
'bestblockhash',
'blocks',
'chain',
'chainwork',
'difficulty',
'headers',
'initialblockdownload',
'mediantime',
'pruned',
'size_on_disk',
'verificationprogress',
'warnings',
]
res = self.nodes[0].getblockchaininfo()
# result should have these additional pruning keys if manual pruning is
# enabled
assert_equal(sorted(res.keys()), sorted(
['pruneheight', 'automatic_pruning'] + keys))
# size_on_disk should be > 0
assert_greater_than(res['size_on_disk'], 0)
# pruneheight should be greater or equal to 0
assert_greater_than_or_equal(res['pruneheight'], 0)
# check other pruning fields given that prune=1
assert res['pruned']
assert not res['automatic_pruning']
self.restart_node(0, ['-stopatheight=207'])
res = self.nodes[0].getblockchaininfo()
# should have exact keys
assert_equal(sorted(res.keys()), keys)
self.restart_node(0, ['-stopatheight=207', '-prune=550'])
res = self.nodes[0].getblockchaininfo()
# result should have these additional pruning keys if prune=550
assert_equal(sorted(res.keys()), sorted(
['pruneheight', 'automatic_pruning', 'prune_target_size'] + keys))
# check related fields
assert res['pruned']
assert_equal(res['pruneheight'], 0)
assert res['automatic_pruning']
assert_equal(res['prune_target_size'], 576716800)
assert_greater_than(res['size_on_disk'], 0)
def _test_getchaintxstats(self):
self.log.info("Test getchaintxstats")
# Test `getchaintxstats` invalid extra parameters
assert_raises_rpc_error(
-1, 'getchaintxstats', self.nodes[0].getchaintxstats, 0, '', 0)
# Test `getchaintxstats` invalid `nblocks`
assert_raises_rpc_error(
-1, "JSON value is not an integer as expected", self.nodes[0].getchaintxstats, '')
assert_raises_rpc_error(
-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, -1)
assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[
0].getchaintxstats, self.nodes[0].getblockcount())
# Test `getchaintxstats` invalid `blockhash`
assert_raises_rpc_error(
-1, "JSON value is not a string as expected", self.nodes[0].getchaintxstats, blockhash=0)
assert_raises_rpc_error(
-5, "Block not found", self.nodes[0].getchaintxstats, blockhash='0')
blockhash = self.nodes[0].getblockhash(200)
self.nodes[0].invalidateblock(blockhash)
assert_raises_rpc_error(
-8, "Block is not in main chain", self.nodes[0].getchaintxstats, blockhash=blockhash)
self.nodes[0].reconsiderblock(blockhash)
chaintxstats = self.nodes[0].getchaintxstats(1)
# 200 txs plus genesis tx
assert_equal(chaintxstats['txcount'], 201)
# tx rate should be 1 per 10 minutes, or 1/600
# we have to round because of binary math
assert_equal(round(chaintxstats['txrate'] * 600, 10), Decimal(1))
b1_hash = self.nodes[0].getblockhash(1)
b1 = self.nodes[0].getblock(b1_hash)
b200_hash = self.nodes[0].getblockhash(200)
b200 = self.nodes[0].getblock(b200_hash)
time_diff = b200['mediantime'] - b1['mediantime']
chaintxstats = self.nodes[0].getchaintxstats()
assert_equal(chaintxstats['time'], b200['time'])
assert_equal(chaintxstats['txcount'], 201)
assert_equal(chaintxstats['window_final_block_hash'], b200_hash)
assert_equal(chaintxstats['window_block_count'], 199)
assert_equal(chaintxstats['window_tx_count'], 199)
assert_equal(chaintxstats['window_interval'], time_diff)
assert_equal(
round(chaintxstats['txrate'] * time_diff, 10), Decimal(199))
chaintxstats = self.nodes[0].getchaintxstats(blockhash=b1_hash)
assert_equal(chaintxstats['time'], b1['time'])
assert_equal(chaintxstats['txcount'], 2)
assert_equal(chaintxstats['window_final_block_hash'], b1_hash)
assert_equal(chaintxstats['window_block_count'], 0)
assert('window_tx_count' not in chaintxstats)
assert('window_interval' not in chaintxstats)
assert('txrate' not in chaintxstats)
def _test_gettxoutsetinfo(self):
node = self.nodes[0]
res = node.gettxoutsetinfo()
assert_equal(res['total_amount'], Decimal('8725.00000000'))
assert_equal(res['transactions'], 200)
assert_equal(res['height'], 200)
assert_equal(res['txouts'], 200)
assert_equal(res['bogosize'], 17000),
assert_equal(res['bestblock'], node.getblockhash(200))
size = res['disk_size']
assert size > 6400
assert size < 64000
assert_equal(len(res['bestblock']), 64)
assert_equal(len(res['hash_serialized']), 64)
self.log.info(
"Test that gettxoutsetinfo() works for blockchain with just the genesis block")
b1hash = node.getblockhash(1)
node.invalidateblock(b1hash)
res2 = node.gettxoutsetinfo()
assert_equal(res2['transactions'], 0)
assert_equal(res2['total_amount'], Decimal('0'))
assert_equal(res2['height'], 0)
assert_equal(res2['txouts'], 0)
assert_equal(res2['bogosize'], 0),
assert_equal(res2['bestblock'], node.getblockhash(0))
assert_equal(len(res2['hash_serialized']), 64)
self.log.info(
"Test that gettxoutsetinfo() returns the same result after invalidate/reconsider block")
node.reconsiderblock(b1hash)
res3 = node.gettxoutsetinfo()
assert_equal(res['total_amount'], res3['total_amount'])
assert_equal(res['transactions'], res3['transactions'])
assert_equal(res['height'], res3['height'])
assert_equal(res['txouts'], res3['txouts'])
assert_equal(res['bogosize'], res3['bogosize'])
assert_equal(res['bestblock'], res3['bestblock'])
assert_equal(res['hash_serialized'], res3['hash_serialized'])
def _test_getblockheader(self):
node = self.nodes[0]
assert_raises_rpc_error(-5, "Block not found",
node.getblockheader, "nonsense")
besthash = node.getbestblockhash()
secondbesthash = node.getblockhash(199)
header = node.getblockheader(besthash)
assert_equal(header['hash'], besthash)
assert_equal(header['height'], 200)
assert_equal(header['confirmations'], 1)
assert_equal(header['previousblockhash'], secondbesthash)
assert_is_hex_string(header['chainwork'])
assert_is_hash_string(header['hash'])
assert_is_hash_string(header['previousblockhash'])
assert_is_hash_string(header['merkleroot'])
assert_is_hash_string(header['bits'], length=None)
assert isinstance(header['time'], int)
assert isinstance(header['mediantime'], int)
assert isinstance(header['nonce'], int)
assert isinstance(header['version'], int)
assert isinstance(int(header['versionHex'], 16), int)
assert isinstance(header['difficulty'], Decimal)
def _test_getdifficulty(self):
difficulty = self.nodes[0].getdifficulty()
# 1 hash in 2 should be valid, so difficulty should be 1/2**31
# binary => decimal => binary math is why we do this check
assert abs(difficulty * 2**31 - 1) < 0.0001
def _test_getnetworkhashps(self):
hashes_per_second = self.nodes[0].getnetworkhashps()
# This should be 2 hashes every 10 minutes or 1/300
assert abs(hashes_per_second * 300 - 1) < 0.0001
def _test_stopatheight(self):
assert_equal(self.nodes[0].getblockcount(), 200)
self.nodes[0].generate(6)
assert_equal(self.nodes[0].getblockcount(), 206)
self.log.debug('Node should not stop at this height')
assert_raises(subprocess.TimeoutExpired,
lambda: self.nodes[0].process.wait(timeout=3))
try:
self.nodes[0].generate(1)
except (ConnectionError, http.client.BadStatusLine):
pass # The node already shut down before response
self.log.debug('Node should stop at this height...')
self.nodes[0].wait_until_stopped()
self.start_node(0)
assert_equal(self.nodes[0].getblockcount(), 207)
def _test_getblock(self):
# Checks for getblock verbose outputs
node = self.nodes[0]
getblockinfo = node.getblock(node.getblockhash(1), 2)
gettransactioninfo = node.gettransaction(getblockinfo['tx'][0]['txid'])
getblockheaderinfo = node.getblockheader(node.getblockhash(1), True)
assert_equal(getblockinfo['hash'], gettransactioninfo['blockhash'])
assert_equal(
getblockinfo['confirmations'], gettransactioninfo['confirmations'])
assert_equal(getblockinfo['height'], getblockheaderinfo['height'])
assert_equal(
getblockinfo['versionHex'], getblockheaderinfo['versionHex'])
assert_equal(getblockinfo['version'], getblockheaderinfo['version'])
assert_equal(getblockinfo['size'], 188)
assert_equal(
getblockinfo['merkleroot'], getblockheaderinfo['merkleroot'])
# Verify transaction data by check the hex values
for tx in getblockinfo['tx']:
getrawtransaction = node.getrawtransaction(tx['txid'], True)
assert_equal(tx['hex'], getrawtransaction['hex'])
assert_equal(getblockinfo['time'], getblockheaderinfo['time'])
assert_equal(
getblockinfo['mediantime'], getblockheaderinfo['mediantime'])
assert_equal(getblockinfo['nonce'], getblockheaderinfo['nonce'])
assert_equal(getblockinfo['bits'], getblockheaderinfo['bits'])
assert_equal(
getblockinfo['difficulty'], getblockheaderinfo['difficulty'])
assert_equal(
getblockinfo['chainwork'], getblockheaderinfo['chainwork'])
assert_equal(
getblockinfo['previousblockhash'], getblockheaderinfo['previousblockhash'])
assert_equal(
getblockinfo['nextblockhash'], getblockheaderinfo['nextblockhash'])
+ def _test_waitforblockheight(self):
+ self.log.info("Test waitforblockheight")
+
+ node = self.nodes[0]
+
+ # Start a P2P connection since we'll need to create some blocks.
+ node.add_p2p_connection(P2PInterface())
+ network_thread_start()
+ node.p2p.wait_for_verack()
+
+ current_height = node.getblock(node.getbestblockhash())['height']
+
+ # Create a fork somewhere below our current height, invalidate the tip
+ # of that fork, and then ensure that waitforblockheight still
+ # works as expected.
+ #
+ # (Previously this was broken based on setting
+ # `rpc/blockchain.cpp:latestblock` incorrectly.)
+ #
+ b20hash = node.getblockhash(20)
+ b20 = node.getblock(b20hash)
+
+ def solve_and_send_block(prevhash, height, time):
+ b = create_block(prevhash, create_coinbase(height), time)
+ b.solve()
+ node.p2p.send_message(msg_block(b))
+ node.p2p.sync_with_ping()
+ return b
+
+ b21f = solve_and_send_block(int(b20hash, 16), 21, b20['time'] + 1)
+ b22f = solve_and_send_block(b21f.sha256, 22, b21f.nTime + 1)
+
+ node.invalidateblock(b22f.hash)
+
+ def assert_waitforheight(height, timeout=2):
+ assert_equal(
+ node.waitforblockheight(height, timeout)['height'],
+ current_height)
+
+ assert_waitforheight(0)
+ assert_waitforheight(current_height - 1)
+ assert_waitforheight(current_height)
+ assert_waitforheight(current_height + 1)
+
if __name__ == '__main__':
BlockchainTest().main()
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Mar 2, 13:07 (18 h, 22 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5175071
Default Alt Text
(234 KB)
Attached To
rSTAGING Bitcoin ABC staging
Event Timeline
Log In to Comment