Changeset View
Changeset View
Standalone View
Standalone View
src/validation.cpp
Show First 20 Lines • Show All 1,491 Lines • ▼ Show 20 Lines | |||||
bool AbortNode(CValidationState &state, const std::string &strMessage, | bool AbortNode(CValidationState &state, const std::string &strMessage, | ||||
const std::string &userMessage = "") { | const std::string &userMessage = "") { | ||||
AbortNode(strMessage, userMessage); | AbortNode(strMessage, userMessage); | ||||
return state.Error(strMessage); | return state.Error(strMessage); | ||||
} | } | ||||
} // anon namespace | } // anon namespace | ||||
/** | /** Apply the undo operation of a CTxInUndo to the given chain state. */ | ||||
* Apply the undo operation of a CTxInUndo to the given chain state. | DisconnectResult ApplyTxInUndo(const CTxInUndo &undo, CCoinsViewCache &view, | ||||
* @param undo The undo object. | |||||
* @param view The coins view to which to apply the changes. | |||||
* @param out The out point that corresponds to the tx input. | |||||
* @return True on success. | |||||
*/ | |||||
bool ApplyTxInUndo(const CTxInUndo &undo, CCoinsViewCache &view, | |||||
const COutPoint &out) { | const COutPoint &out) { | ||||
bool fClean = true; | bool fClean = true; | ||||
CCoinsModifier coins = view.ModifyCoins(out.hash); | CCoinsModifier coins = view.ModifyCoins(out.hash); | ||||
if (undo.nHeight != 0) { | if (undo.nHeight != 0) { | ||||
// undo data contains height: this is the last output of the prevout tx | // undo data contains height: this is the last output of the prevout tx | ||||
// being spent | // being spent | ||||
if (!coins->IsPruned()) { | if (!coins->IsPruned()) { | ||||
fClean = fClean && | // Overwriting existing transaction. | ||||
error("%s: undo data overwriting existing transaction", | fClean = false; | ||||
__func__); | |||||
} | } | ||||
coins->Clear(); | |||||
coins->fCoinBase = undo.fCoinBase; | coins->fCoinBase = undo.fCoinBase; | ||||
coins->nHeight = undo.nHeight; | coins->nHeight = undo.nHeight; | ||||
coins->nVersion = undo.nVersion; | coins->nVersion = undo.nVersion; | ||||
} else { | } else { | ||||
if (coins->IsPruned()) { | if (coins->IsPruned()) { | ||||
fClean = fClean && | // Adding output to missing transaction. | ||||
error("%s: undo data adding output to missing transaction", | fClean = false; | ||||
__func__); | |||||
} | } | ||||
} | } | ||||
if (coins->IsAvailable(out.n)) { | if (coins->IsAvailable(out.n)) { | ||||
fClean = fClean && | // Overwriting existing output. | ||||
error("%s: undo data overwriting existing output", __func__); | fClean = false; | ||||
} | } | ||||
if (coins->vout.size() < out.n + 1) { | if (coins->vout.size() < out.n + 1) { | ||||
coins->vout.resize(out.n + 1); | coins->vout.resize(out.n + 1); | ||||
} | } | ||||
coins->vout[out.n] = undo.txout; | coins->vout[out.n] = undo.txout; | ||||
return fClean; | return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN; | ||||
} | } | ||||
bool DisconnectBlock(const CBlock &block, CValidationState &state, | /** | ||||
const CBlockIndex *pindex, CCoinsViewCache &view, | * Undo the effects of this block (with given index) on the UTXO set represented | ||||
bool *pfClean) { | * by coins. When UNCLEAN or 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()); | assert(pindex->GetBlockHash() == view.GetBestBlock()); | ||||
if (pfClean) { | |||||
*pfClean = false; | |||||
} | |||||
CBlockUndo blockUndo; | CBlockUndo blockUndo; | ||||
CDiskBlockPos pos = pindex->GetUndoPos(); | CDiskBlockPos pos = pindex->GetUndoPos(); | ||||
if (pos.IsNull()) { | if (pos.IsNull()) { | ||||
return error("DisconnectBlock(): no undo data available"); | error("DisconnectBlock(): no undo data available"); | ||||
return DISCONNECT_FAILED; | |||||
} | } | ||||
if (!UndoReadFromDisk(blockUndo, pos, pindex->pprev->GetBlockHash())) { | if (!UndoReadFromDisk(blockUndo, pos, pindex->pprev->GetBlockHash())) { | ||||
return error("DisconnectBlock(): failure reading undo data"); | error("DisconnectBlock(): failure reading undo data"); | ||||
return DISCONNECT_FAILED; | |||||
} | } | ||||
return ApplyBlockUndo(block, state, pindex, view, blockUndo, pfClean); | return ApplyBlockUndo(blockUndo, block, pindex, view); | ||||
} | } | ||||
bool ApplyBlockUndo(const CBlock &block, CValidationState &state, | DisconnectResult ApplyBlockUndo(const CBlockUndo &blockUndo, | ||||
const CBlockIndex *pindex, CCoinsViewCache &view, | const CBlock &block, const CBlockIndex *pindex, | ||||
const CBlockUndo &blockUndo, bool *pfClean) { | CCoinsViewCache &view) { | ||||
if (pfClean) { | |||||
*pfClean = false; | |||||
} | |||||
bool fClean = true; | bool fClean = true; | ||||
if (blockUndo.vtxundo.size() + 1 != block.vtx.size()) { | if (blockUndo.vtxundo.size() + 1 != block.vtx.size()) { | ||||
return error("DisconnectBlock(): block and undo data inconsistent"); | error("DisconnectBlock(): block and undo data inconsistent"); | ||||
return DISCONNECT_FAILED; | |||||
} | } | ||||
// Undo transactions in reverse order. | // Undo transactions in reverse order. | ||||
size_t i = block.vtx.size(); | size_t i = block.vtx.size(); | ||||
while (i-- > 0) { | while (i-- > 0) { | ||||
const CTransaction &tx = *(block.vtx[i]); | const CTransaction &tx = *(block.vtx[i]); | ||||
uint256 txid = tx.GetId(); | uint256 txid = tx.GetId(); | ||||
// Check that all outputs are available and match the outputs in the | // Check that all outputs are available and match the outputs in the | ||||
// block itself exactly. | // block itself exactly. | ||||
{ | { | ||||
CCoinsModifier outs = view.ModifyCoins(txid); | CCoinsModifier outs = view.ModifyCoins(txid); | ||||
outs->ClearUnspendable(); | outs->ClearUnspendable(); | ||||
CCoins outsBlock(tx, pindex->nHeight); | CCoins outsBlock(tx, pindex->nHeight); | ||||
// The CCoins serialization does not serialize negative numbers. No | // The CCoins serialization does not serialize negative numbers. No | ||||
// network rules currently depend on the version here, so an | // network rules currently depend on the version here, so an | ||||
// inconsistency is harmless but it must be corrected before txout | // inconsistency is harmless but it must be corrected before txout | ||||
// nversion ever influences a network rule. | // nversion ever influences a network rule. | ||||
if (outsBlock.nVersion < 0) { | if (outsBlock.nVersion < 0) { | ||||
outs->nVersion = outsBlock.nVersion; | outs->nVersion = outsBlock.nVersion; | ||||
} | } | ||||
if (*outs != outsBlock) { | if (*outs != outsBlock) { | ||||
fClean = fClean && error("DisconnectBlock(): added transaction " | // Transaction mismatch. | ||||
"mismatch? database corrupted"); | fClean = false; | ||||
} | } | ||||
// Remove outputs. | // Remove outputs. | ||||
outs->Clear(); | outs->Clear(); | ||||
} | } | ||||
// Restore inputs. | // Restore inputs. | ||||
if (i < 1) { | if (i < 1) { | ||||
// Skip the coinbase. | // Skip the coinbase. | ||||
continue; | continue; | ||||
} | } | ||||
const CTxUndo &txundo = blockUndo.vtxundo[i - 1]; | const CTxUndo &txundo = blockUndo.vtxundo[i - 1]; | ||||
if (txundo.vprevout.size() != tx.vin.size()) { | if (txundo.vprevout.size() != tx.vin.size()) { | ||||
return error("DisconnectBlock(): transaction and undo data " | error("DisconnectBlock(): transaction and undo data inconsistent"); | ||||
"inconsistent"); | return DISCONNECT_FAILED; | ||||
} | } | ||||
for (size_t j = tx.vin.size(); j-- > 0;) { | for (size_t j = tx.vin.size(); j-- > 0;) { | ||||
const COutPoint &out = tx.vin[j].prevout; | const COutPoint &out = tx.vin[j].prevout; | ||||
const CTxInUndo &undo = txundo.vprevout[j]; | const CTxInUndo &undo = txundo.vprevout[j]; | ||||
if (!ApplyTxInUndo(undo, view, out)) { | DisconnectResult res = ApplyTxInUndo(undo, view, out); | ||||
fClean = false; | if (res == DISCONNECT_FAILED) { | ||||
return DISCONNECT_FAILED; | |||||
} | } | ||||
fClean = fClean && res != DISCONNECT_UNCLEAN; | |||||
} | } | ||||
} | } | ||||
// Move best block pointer to previous block. | // Move best block pointer to previous block. | ||||
view.SetBestBlock(block.hashPrevBlock); | view.SetBestBlock(block.hashPrevBlock); | ||||
if (pfClean) { | |||||
*pfClean = fClean; | |||||
return true; | |||||
} | |||||
return fClean; | return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN; | ||||
} | } | ||||
void static FlushBlockFile(bool fFinalize = false) { | void static FlushBlockFile(bool fFinalize = false) { | ||||
LOCK(cs_LastBlockFile); | LOCK(cs_LastBlockFile); | ||||
CDiskBlockPos posOld(nLastBlockFile, 0); | CDiskBlockPos posOld(nLastBlockFile, 0); | ||||
FILE *fileOld = OpenBlockFile(posOld); | FILE *fileOld = OpenBlockFile(posOld); | ||||
▲ Show 20 Lines • Show All 668 Lines • ▼ Show 20 Lines | const Consensus::Params &consensusParams = | ||||
config.GetChainParams().GetConsensus(); | config.GetChainParams().GetConsensus(); | ||||
CBlockIndex *pindexDelete = chainActive.Tip(); | CBlockIndex *pindexDelete = chainActive.Tip(); | ||||
assert(pindexDelete); | assert(pindexDelete); | ||||
// Read block from disk. | // Read block from disk. | ||||
CBlock block; | CBlock block; | ||||
if (!ReadBlockFromDisk(block, pindexDelete, consensusParams)) { | if (!ReadBlockFromDisk(block, pindexDelete, consensusParams)) { | ||||
return AbortNode(state, "Failed to read block"); | return AbortNode(state, "Failed to read block"); | ||||
} | } | ||||
// Apply the block atomically to the chain state. | // Apply the block atomically to the chain state. | ||||
int64_t nStart = GetTimeMicros(); | int64_t nStart = GetTimeMicros(); | ||||
{ | { | ||||
CCoinsViewCache view(pcoinsTip); | CCoinsViewCache view(pcoinsTip); | ||||
if (!DisconnectBlock(block, state, pindexDelete, view)) { | if (DisconnectBlock(block, pindexDelete, view) != DISCONNECT_OK) { | ||||
return error("DisconnectTip(): DisconnectBlock %s failed", | return error("DisconnectTip(): DisconnectBlock %s failed", | ||||
pindexDelete->GetBlockHash().ToString()); | pindexDelete->GetBlockHash().ToString()); | ||||
} | } | ||||
bool flushed = view.Flush(); | bool flushed = view.Flush(); | ||||
assert(flushed); | assert(flushed); | ||||
} | } | ||||
LogPrint("bench", "- Disconnect block: %.2fms\n", | LogPrint("bench", "- Disconnect block: %.2fms\n", | ||||
(GetTimeMicros() - nStart) * 0.001); | (GetTimeMicros() - nStart) * 0.001); | ||||
// Write the chain state to disk, if necessary. | // Write the chain state to disk, if necessary. | ||||
if (!FlushStateToDisk(state, FLUSH_STATE_IF_NEEDED)) { | if (!FlushStateToDisk(state, FLUSH_STATE_IF_NEEDED)) { | ||||
return false; | return false; | ||||
} | } | ||||
// If this block was the activation of the UAHF, then we need to remove | // If this block was the activation of the UAHF, then we need to remove | ||||
// transactions that are valid only on the HF chain. There is no easy way to | // transactions that are valid only on the HF chain. There is no easy way to | ||||
// do this so we'll just discard the whole mempool and then add the | // do this so we'll just discard the whole mempool and then add the | ||||
▲ Show 20 Lines • Show All 1,633 Lines • ▼ Show 20 Lines | |||||
CVerifyDB::~CVerifyDB() { | CVerifyDB::~CVerifyDB() { | ||||
uiInterface.ShowProgress("", 100); | uiInterface.ShowProgress("", 100); | ||||
} | } | ||||
bool CVerifyDB::VerifyDB(const Config &config, const CChainParams &chainparams, | bool CVerifyDB::VerifyDB(const Config &config, const CChainParams &chainparams, | ||||
CCoinsView *coinsview, int nCheckLevel, | CCoinsView *coinsview, int nCheckLevel, | ||||
int nCheckDepth) { | int nCheckDepth) { | ||||
LOCK(cs_main); | LOCK(cs_main); | ||||
if (chainActive.Tip() == nullptr || chainActive.Tip()->pprev == nullptr) | if (chainActive.Tip() == nullptr || chainActive.Tip()->pprev == nullptr) { | ||||
return true; | return true; | ||||
} | |||||
// Verify blocks in the best chain | // Verify blocks in the best chain | ||||
if (nCheckDepth <= 0) | if (nCheckDepth <= 0) { | ||||
nCheckDepth = 1000000000; // suffices until the year 19000 | // suffices until the year 19000 | ||||
if (nCheckDepth > chainActive.Height()) nCheckDepth = chainActive.Height(); | nCheckDepth = 1000000000; | ||||
} | |||||
if (nCheckDepth > chainActive.Height()) { | |||||
nCheckDepth = chainActive.Height(); | |||||
} | |||||
nCheckLevel = std::max(0, std::min(4, nCheckLevel)); | nCheckLevel = std::max(0, std::min(4, nCheckLevel)); | ||||
LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth, | LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth, | ||||
nCheckLevel); | nCheckLevel); | ||||
CCoinsViewCache coins(coinsview); | CCoinsViewCache coins(coinsview); | ||||
CBlockIndex *pindexState = chainActive.Tip(); | CBlockIndex *pindexState = chainActive.Tip(); | ||||
CBlockIndex *pindexFailure = nullptr; | CBlockIndex *pindexFailure = nullptr; | ||||
int nGoodTransactions = 0; | int nGoodTransactions = 0; | ||||
CValidationState state; | CValidationState state; | ||||
int reportDone = 0; | int reportDone = 0; | ||||
LogPrintf("[0%%]..."); | LogPrintf("[0%%]..."); | ||||
for (CBlockIndex *pindex = chainActive.Tip(); pindex && pindex->pprev; | for (CBlockIndex *pindex = chainActive.Tip(); pindex && pindex->pprev; | ||||
pindex = pindex->pprev) { | pindex = pindex->pprev) { | ||||
boost::this_thread::interruption_point(); | boost::this_thread::interruption_point(); | ||||
int percentageDone = std::max( | int percentageDone = std::max( | ||||
1, std::min( | 1, std::min( | ||||
99, | 99, | ||||
(int)(((double)(chainActive.Height() - pindex->nHeight)) / | (int)(((double)(chainActive.Height() - pindex->nHeight)) / | ||||
(double)nCheckDepth * (nCheckLevel >= 4 ? 50 : 100)))); | (double)nCheckDepth * (nCheckLevel >= 4 ? 50 : 100)))); | ||||
if (reportDone < percentageDone / 10) { | if (reportDone < percentageDone / 10) { | ||||
// report every 10% step | // report every 10% step | ||||
LogPrintf("[%d%%]...", percentageDone); | LogPrintf("[%d%%]...", percentageDone); | ||||
reportDone = percentageDone / 10; | reportDone = percentageDone / 10; | ||||
} | } | ||||
uiInterface.ShowProgress(_("Verifying blocks..."), percentageDone); | uiInterface.ShowProgress(_("Verifying blocks..."), percentageDone); | ||||
if (pindex->nHeight < chainActive.Height() - nCheckDepth) break; | if (pindex->nHeight < chainActive.Height() - nCheckDepth) { | ||||
break; | |||||
} | |||||
if (fPruneMode && !(pindex->nStatus & BLOCK_HAVE_DATA)) { | if (fPruneMode && !(pindex->nStatus & BLOCK_HAVE_DATA)) { | ||||
// If pruning, only go back as far as we have data. | // If pruning, only go back as far as we have data. | ||||
LogPrintf("VerifyDB(): block verification stopping at height %d " | LogPrintf("VerifyDB(): block verification stopping at height %d " | ||||
"(pruning, no data)\n", | "(pruning, no data)\n", | ||||
pindex->nHeight); | pindex->nHeight); | ||||
break; | break; | ||||
} | } | ||||
CBlock block; | CBlock block; | ||||
// check level 0: read from disk | // check level 0: read from disk | ||||
if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus())) | if (!ReadBlockFromDisk(block, pindex, chainparams.GetConsensus())) { | ||||
return error( | return error( | ||||
"VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", | "VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", | ||||
pindex->nHeight, pindex->GetBlockHash().ToString()); | pindex->nHeight, pindex->GetBlockHash().ToString()); | ||||
} | |||||
// check level 1: verify block validity | // check level 1: verify block validity | ||||
if (nCheckLevel >= 1 && | if (nCheckLevel >= 1 && | ||||
!CheckBlock(config, block, state, chainparams.GetConsensus())) | !CheckBlock(config, block, state, chainparams.GetConsensus())) { | ||||
return error("%s: *** found bad block at %d, hash=%s (%s)\n", | return error("%s: *** found bad block at %d, hash=%s (%s)\n", | ||||
__func__, pindex->nHeight, | __func__, pindex->nHeight, | ||||
pindex->GetBlockHash().ToString(), | pindex->GetBlockHash().ToString(), | ||||
FormatStateMessage(state)); | FormatStateMessage(state)); | ||||
} | |||||
// check level 2: verify undo validity | // check level 2: verify undo validity | ||||
if (nCheckLevel >= 2 && pindex) { | if (nCheckLevel >= 2 && pindex) { | ||||
CBlockUndo undo; | CBlockUndo undo; | ||||
CDiskBlockPos pos = pindex->GetUndoPos(); | CDiskBlockPos pos = pindex->GetUndoPos(); | ||||
if (!pos.IsNull()) { | if (!pos.IsNull()) { | ||||
if (!UndoReadFromDisk(undo, pos, | if (!UndoReadFromDisk(undo, pos, | ||||
pindex->pprev->GetBlockHash())) { | pindex->pprev->GetBlockHash())) { | ||||
return error( | return error( | ||||
"VerifyDB(): *** found bad undo data at %d, hash=%s\n", | "VerifyDB(): *** found bad undo data at %d, hash=%s\n", | ||||
pindex->nHeight, pindex->GetBlockHash().ToString()); | pindex->nHeight, pindex->GetBlockHash().ToString()); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// check level 3: check for inconsistencies during memory-only | // check level 3: check for inconsistencies during memory-only | ||||
// disconnect of tip blocks | // disconnect of tip blocks | ||||
if (nCheckLevel >= 3 && pindex == pindexState && | if (nCheckLevel >= 3 && pindex == pindexState && | ||||
(coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <= | (coins.DynamicMemoryUsage() + pcoinsTip->DynamicMemoryUsage()) <= | ||||
nCoinCacheUsage) { | nCoinCacheUsage) { | ||||
bool fClean = true; | DisconnectResult res = DisconnectBlock(block, pindex, coins); | ||||
if (!DisconnectBlock(block, state, pindex, coins, &fClean)) { | if (res == DISCONNECT_FAILED) { | ||||
return error("VerifyDB(): *** irrecoverable inconsistency in " | return error("VerifyDB(): *** irrecoverable inconsistency in " | ||||
"block data at %d, hash=%s", | "block data at %d, hash=%s", | ||||
pindex->nHeight, | pindex->nHeight, | ||||
pindex->GetBlockHash().ToString()); | pindex->GetBlockHash().ToString()); | ||||
} | } | ||||
pindexState = pindex->pprev; | pindexState = pindex->pprev; | ||||
if (!fClean) { | if (res == DISCONNECT_UNCLEAN) { | ||||
nGoodTransactions = 0; | nGoodTransactions = 0; | ||||
pindexFailure = pindex; | pindexFailure = pindex; | ||||
} else | } else { | ||||
nGoodTransactions += block.vtx.size(); | nGoodTransactions += block.vtx.size(); | ||||
} | } | ||||
} | |||||
if (ShutdownRequested()) { | if (ShutdownRequested()) { | ||||
return true; | return true; | ||||
} | } | ||||
} | } | ||||
if (pindexFailure) { | if (pindexFailure) { | ||||
return error("VerifyDB(): *** coin database inconsistencies found " | return error("VerifyDB(): *** coin database inconsistencies found " | ||||
"(last %i blocks, %i good transactions before that)\n", | "(last %i blocks, %i good transactions before that)\n", | ||||
chainActive.Height() - pindexFailure->nHeight + 1, | chainActive.Height() - pindexFailure->nHeight + 1, | ||||
nGoodTransactions); | nGoodTransactions); | ||||
} | } | ||||
// check level 4: try reconnecting blocks | // check level 4: try reconnecting blocks | ||||
▲ Show 20 Lines • Show All 775 Lines • Show Last 20 Lines |