|
|
|
@ -96,7 +96,7 @@ namespace {
@@ -96,7 +96,7 @@ namespace {
|
|
|
|
|
|
|
|
|
|
struct CBlockIndexWorkComparator |
|
|
|
|
{ |
|
|
|
|
bool operator()(CBlockIndex *pa, CBlockIndex *pb) const { |
|
|
|
|
bool operator()(const CBlockIndex *pa, const CBlockIndex *pb) const { |
|
|
|
|
// First sort by most total work, ...
|
|
|
|
|
if (pa->nChainWork > pb->nChainWork) return false; |
|
|
|
|
if (pa->nChainWork < pb->nChainWork) return true; |
|
|
|
@ -1331,17 +1331,19 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out)
@@ -1331,17 +1331,19 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out)
|
|
|
|
|
return DISCONNECT_FAILED; // adding output for transaction without known metadata
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
view.AddCoin(out, std::move(undo), undo.fCoinBase); |
|
|
|
|
// 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 UNCLEAN or FAILED is returned, view is left in an indeterminate state. */ |
|
|
|
|
* When FAILED is returned, view is left in an indeterminate state. */ |
|
|
|
|
static DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) |
|
|
|
|
{ |
|
|
|
|
assert(pindex->GetBlockHash() == view.GetBestBlock()); |
|
|
|
|
|
|
|
|
|
bool fClean = true; |
|
|
|
|
|
|
|
|
|
CBlockUndo blockUndo; |
|
|
|
@ -1946,6 +1948,7 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara
@@ -1946,6 +1948,7 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara
|
|
|
|
|
int64_t nStart = GetTimeMicros(); |
|
|
|
|
{ |
|
|
|
|
CCoinsViewCache view(pcoinsTip); |
|
|
|
|
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(); |
|
|
|
@ -3417,20 +3420,26 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams)
@@ -3417,20 +3420,26 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams)
|
|
|
|
|
pblocktree->ReadFlag("txindex", fTxIndex); |
|
|
|
|
LogPrintf("%s: transaction index %s\n", __func__, fTxIndex ? "enabled" : "disabled"); |
|
|
|
|
|
|
|
|
|
LoadChainTip(chainparams); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void LoadChainTip(const CChainParams& chainparams) |
|
|
|
|
{ |
|
|
|
|
if (chainActive.Tip() && chainActive.Tip()->GetBlockHash() == pcoinsTip->GetBestBlock()) return; |
|
|
|
|
|
|
|
|
|
// Load pointer to end of best chain
|
|
|
|
|
BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock()); |
|
|
|
|
if (it == mapBlockIndex.end()) |
|
|
|
|
return true; |
|
|
|
|
return; |
|
|
|
|
chainActive.SetTip(it->second); |
|
|
|
|
|
|
|
|
|
PruneBlockIndexCandidates(); |
|
|
|
|
|
|
|
|
|
LogPrintf("%s: hashBestChain=%s height=%d date=%s progress=%f\n", __func__, |
|
|
|
|
LogPrintf("Loaded best chain: hashBestChain=%s height=%d date=%s progress=%f\n", |
|
|
|
|
chainActive.Tip()->GetBlockHash().ToString(), chainActive.Height(), |
|
|
|
|
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()), |
|
|
|
|
GuessVerificationProgress(chainparams.TxData(), chainActive.Tip())); |
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
CVerifyDB::CVerifyDB() |
|
|
|
@ -3499,6 +3508,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview,
@@ -3499,6 +3508,7 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview,
|
|
|
|
|
} |
|
|
|
|
// 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 = 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()); |
|
|
|
@ -3538,6 +3548,92 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview,
@@ -3538,6 +3548,92 @@ bool CVerifyDB::VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview,
|
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** Apply the effects of a block on the utxo cache, ignoring that it may already have been applied. */ |
|
|
|
|
static bool RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& inputs, const CChainParams& params) |
|
|
|
|
{ |
|
|
|
|
// TODO: merge with ConnectBlock
|
|
|
|
|
CBlock block; |
|
|
|
|
if (!ReadBlockFromDisk(block, pindex, params.GetConsensus())) { |
|
|
|
|
return error("ReplayBlock(): ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (const CTransactionRef& tx : block.vtx) { |
|
|
|
|
if (!tx->IsCoinBase()) { |
|
|
|
|
for (const CTxIn &txin : tx->vin) { |
|
|
|
|
inputs.SpendCoin(txin.prevout); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Pass check = true as every addition may be an overwrite.
|
|
|
|
|
AddCoins(inputs, *tx, pindex->nHeight, true); |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool ReplayBlocks(const CChainParams& params, CCoinsView* view) |
|
|
|
|
{ |
|
|
|
|
LOCK(cs_main); |
|
|
|
|
|
|
|
|
|
CCoinsViewCache cache(view); |
|
|
|
|
|
|
|
|
|
std::vector<uint256> hashHeads = view->GetHeadBlocks(); |
|
|
|
|
if (hashHeads.empty()) return true; // We're already in a consistent state.
|
|
|
|
|
if (hashHeads.size() != 2) return error("ReplayBlocks(): unknown inconsistent state"); |
|
|
|
|
|
|
|
|
|
uiInterface.ShowProgress(_("Replaying blocks..."), 0); |
|
|
|
|
LogPrintf("Replaying blocks\n"); |
|
|
|
|
|
|
|
|
|
const CBlockIndex* pindexOld = nullptr; // Old tip during the interrupted flush.
|
|
|
|
|
const CBlockIndex* pindexNew; // New tip during the interrupted flush.
|
|
|
|
|
const CBlockIndex* pindexFork = nullptr; // Latest block common to both the old and the new tip.
|
|
|
|
|
|
|
|
|
|
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, params.GetConsensus())) { |
|
|
|
|
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, params)) return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cache.SetBestBlock(pindexNew->GetBlockHash()); |
|
|
|
|
cache.Flush(); |
|
|
|
|
uiInterface.ShowProgress("", 100); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool RewindBlockIndex(const CChainParams& params) |
|
|
|
|
{ |
|
|
|
|
LOCK(cs_main); |
|
|
|
|