|
|
@ -1464,23 +1464,24 @@ bool CTransaction::ClientCheckInputs() const |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &view) |
|
|
|
bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &view, bool *pfClean) |
|
|
|
{ |
|
|
|
{ |
|
|
|
assert(pindex == view.GetBestBlock()); |
|
|
|
assert(pindex == view.GetBestBlock()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pfClean) |
|
|
|
|
|
|
|
*pfClean = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool fClean = true; |
|
|
|
|
|
|
|
|
|
|
|
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"); |
|
|
|
return error("DisconnectBlock() : no undo data available"); |
|
|
|
if (!blockUndo.ReadFromDisk(pos, pindex->pprev->GetBlockHash())) |
|
|
|
FILE *file = OpenUndoFile(pos, true); |
|
|
|
return error("DisconnectBlock() : failure reading undo data"); |
|
|
|
if (file == NULL) |
|
|
|
|
|
|
|
return error("DisconnectBlock() : undo file not available"); |
|
|
|
|
|
|
|
CAutoFile fileUndo(file, SER_DISK, CLIENT_VERSION); |
|
|
|
|
|
|
|
fileUndo >> blockUndo; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert(blockUndo.vtxundo.size() + 1 == vtx.size()); |
|
|
|
if (blockUndo.vtxundo.size() + 1 != vtx.size()) |
|
|
|
|
|
|
|
return error("DisconnectBlock() : block and undo data inconsistent"); |
|
|
|
|
|
|
|
|
|
|
|
// undo transactions in reverse order
|
|
|
|
// undo transactions in reverse order
|
|
|
|
for (int i = vtx.size() - 1; i >= 0; i--) { |
|
|
|
for (int i = vtx.size() - 1; i >= 0; i--) { |
|
|
@ -1488,13 +1489,15 @@ bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &view) |
|
|
|
uint256 hash = tx.GetHash(); |
|
|
|
uint256 hash = tx.GetHash(); |
|
|
|
|
|
|
|
|
|
|
|
// check that all outputs are available
|
|
|
|
// check that all outputs are available
|
|
|
|
if (!view.HaveCoins(hash)) |
|
|
|
if (!view.HaveCoins(hash)) { |
|
|
|
return error("DisconnectBlock() : outputs still spent? database corrupted"); |
|
|
|
fClean = fClean && error("DisconnectBlock() : outputs still spent? database corrupted"); |
|
|
|
|
|
|
|
view.SetCoins(hash, CCoins()); |
|
|
|
|
|
|
|
} |
|
|
|
CCoins &outs = view.GetCoins(hash); |
|
|
|
CCoins &outs = view.GetCoins(hash); |
|
|
|
|
|
|
|
|
|
|
|
CCoins outsBlock = CCoins(tx, pindex->nHeight); |
|
|
|
CCoins outsBlock = CCoins(tx, pindex->nHeight); |
|
|
|
if (outs != outsBlock) |
|
|
|
if (outs != outsBlock) |
|
|
|
return error("DisconnectBlock() : added transaction mismatch? database corrupted"); |
|
|
|
fClean = fClean && error("DisconnectBlock() : added transaction mismatch? database corrupted"); |
|
|
|
|
|
|
|
|
|
|
|
// remove outputs
|
|
|
|
// remove outputs
|
|
|
|
outs = CCoins(); |
|
|
|
outs = CCoins(); |
|
|
@ -1502,24 +1505,27 @@ bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &view) |
|
|
|
// restore inputs
|
|
|
|
// restore inputs
|
|
|
|
if (i > 0) { // not coinbases
|
|
|
|
if (i > 0) { // not coinbases
|
|
|
|
const CTxUndo &txundo = blockUndo.vtxundo[i-1]; |
|
|
|
const CTxUndo &txundo = blockUndo.vtxundo[i-1]; |
|
|
|
assert(txundo.vprevout.size() == tx.vin.size()); |
|
|
|
if (txundo.vprevout.size() != tx.vin.size()) |
|
|
|
|
|
|
|
return error("DisconnectBlock() : transaction and undo data inconsistent"); |
|
|
|
for (unsigned int j = tx.vin.size(); j-- > 0;) { |
|
|
|
for (unsigned int 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]; |
|
|
|
CCoins coins; |
|
|
|
CCoins coins; |
|
|
|
view.GetCoins(out.hash, coins); // this can fail if the prevout was already entirely spent
|
|
|
|
view.GetCoins(out.hash, coins); // this can fail if the prevout was already entirely spent
|
|
|
|
if (coins.IsPruned()) { |
|
|
|
if (undo.nHeight != 0) { |
|
|
|
if (undo.nHeight == 0) |
|
|
|
// undo data contains height: this is the last output of the prevout tx being spent
|
|
|
|
return error("DisconnectBlock() : undo data doesn't contain tx metadata? database corrupted"); |
|
|
|
if (!coins.IsPruned()) |
|
|
|
|
|
|
|
fClean = fClean && error("DisconnectBlock() : undo data overwriting existing transaction"); |
|
|
|
|
|
|
|
coins = CCoins(); |
|
|
|
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 (undo.nHeight != 0) |
|
|
|
if (coins.IsPruned()) |
|
|
|
return error("DisconnectBlock() : undo data contains unneeded tx metadata? database corrupted"); |
|
|
|
fClean = fClean && error("DisconnectBlock() : undo data adding output to missing transaction"); |
|
|
|
} |
|
|
|
} |
|
|
|
if (coins.IsAvailable(out.n)) |
|
|
|
if (coins.IsAvailable(out.n)) |
|
|
|
return error("DisconnectBlock() : prevout output not spent? database corrupted"); |
|
|
|
fClean = fClean && error("DisconnectBlock() : undo data overwriting existing output"); |
|
|
|
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; |
|
|
@ -1532,7 +1538,12 @@ bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &view) |
|
|
|
// move best block pointer to prevout block
|
|
|
|
// move best block pointer to prevout block
|
|
|
|
view.SetBestBlock(pindex->pprev); |
|
|
|
view.SetBestBlock(pindex->pprev); |
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
if (pfClean) { |
|
|
|
|
|
|
|
*pfClean = fClean; |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return fClean; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void static FlushBlockFile() |
|
|
|
void static FlushBlockFile() |
|
|
@ -1651,9 +1662,9 @@ bool CBlock::ConnectBlock(CBlockIndex* pindex, CCoinsViewCache &view, bool fJust |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (pindex->GetUndoPos().IsNull()) { |
|
|
|
if (pindex->GetUndoPos().IsNull()) { |
|
|
|
CDiskBlockPos pos; |
|
|
|
CDiskBlockPos pos; |
|
|
|
if (!FindUndoPos(pindex->nFile, pos, ::GetSerializeSize(blockundo, SER_DISK, CLIENT_VERSION) + 8)) |
|
|
|
if (!FindUndoPos(pindex->nFile, pos, ::GetSerializeSize(blockundo, SER_DISK, CLIENT_VERSION) + 40)) |
|
|
|
return error("ConnectBlock() : FindUndoPos failed"); |
|
|
|
return error("ConnectBlock() : FindUndoPos failed"); |
|
|
|
if (!blockundo.WriteToDisk(pos)) |
|
|
|
if (!blockundo.WriteToDisk(pos, pindex->pprev->GetBlockHash())) |
|
|
|
return error("ConnectBlock() : CBlockUndo::WriteToDisk failed"); |
|
|
|
return error("ConnectBlock() : CBlockUndo::WriteToDisk failed"); |
|
|
|
|
|
|
|
|
|
|
|
// update nUndoPos in block index
|
|
|
|
// update nUndoPos in block index
|
|
|
@ -2377,36 +2388,77 @@ bool static LoadBlockIndexDB() |
|
|
|
BlockHashStr(hashBestChain).c_str(), nBestHeight, |
|
|
|
BlockHashStr(hashBestChain).c_str(), nBestHeight, |
|
|
|
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pindexBest->GetBlockTime()).c_str()); |
|
|
|
DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pindexBest->GetBlockTime()).c_str()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool VerifyDB() { |
|
|
|
|
|
|
|
if (pindexBest == NULL || pindexBest->pprev == NULL) |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
|
|
|
|
// Verify blocks in the best chain
|
|
|
|
// Verify blocks in the best chain
|
|
|
|
int nCheckLevel = GetArg("-checklevel", 1); |
|
|
|
int nCheckLevel = GetArg("-checklevel", 3); |
|
|
|
int nCheckDepth = GetArg( "-checkblocks", 2500); |
|
|
|
int nCheckDepth = GetArg( "-checkblocks", 2500); |
|
|
|
if (nCheckDepth == 0) |
|
|
|
if (nCheckDepth == 0) |
|
|
|
nCheckDepth = 1000000000; // suffices until the year 19000
|
|
|
|
nCheckDepth = 1000000000; // suffices until the year 19000
|
|
|
|
if (nCheckDepth > nBestHeight) |
|
|
|
if (nCheckDepth > nBestHeight) |
|
|
|
nCheckDepth = nBestHeight; |
|
|
|
nCheckDepth = nBestHeight; |
|
|
|
|
|
|
|
nCheckLevel = std::max(0, std::min(4, nCheckLevel)); |
|
|
|
printf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel); |
|
|
|
printf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel); |
|
|
|
CBlockIndex* pindexFork = NULL; |
|
|
|
CCoinsViewCache coins(*pcoinsTip, true); |
|
|
|
|
|
|
|
CBlockIndex* pindexState = pindexBest; |
|
|
|
|
|
|
|
CBlockIndex* pindexFailure = NULL; |
|
|
|
|
|
|
|
int nGoodTransactions = 0; |
|
|
|
for (CBlockIndex* pindex = pindexBest; pindex && pindex->pprev; pindex = pindex->pprev) |
|
|
|
for (CBlockIndex* pindex = pindexBest; pindex && pindex->pprev; pindex = pindex->pprev) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (fRequestShutdown || pindex->nHeight < nBestHeight-nCheckDepth) |
|
|
|
if (fRequestShutdown || pindex->nHeight < nBestHeight-nCheckDepth) |
|
|
|
break; |
|
|
|
break; |
|
|
|
CBlock block; |
|
|
|
CBlock block; |
|
|
|
|
|
|
|
// check level 0: read from disk
|
|
|
|
if (!block.ReadFromDisk(pindex)) |
|
|
|
if (!block.ReadFromDisk(pindex)) |
|
|
|
return error("LoadBlockIndex() : block.ReadFromDisk failed"); |
|
|
|
return error("VerifyDB() : *** block.ReadFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString().c_str()); |
|
|
|
// check level 1: verify block validity
|
|
|
|
// check level 1: verify block validity
|
|
|
|
if (nCheckLevel>0 && !block.CheckBlock()) |
|
|
|
if (nCheckLevel >= 1 && !block.CheckBlock()) |
|
|
|
{ |
|
|
|
return error("VerifyDB() : *** found bad block at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString().c_str()); |
|
|
|
printf("LoadBlockIndex() : *** found bad block at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString().c_str()); |
|
|
|
// check level 2: verify undo validity
|
|
|
|
pindexFork = pindex->pprev; |
|
|
|
if (nCheckLevel >= 2 && pindex) { |
|
|
|
|
|
|
|
CBlockUndo undo; |
|
|
|
|
|
|
|
CDiskBlockPos pos = pindex->GetUndoPos(); |
|
|
|
|
|
|
|
if (!pos.IsNull()) { |
|
|
|
|
|
|
|
if (!undo.ReadFromDisk(pos, pindex->pprev->GetBlockHash())) |
|
|
|
|
|
|
|
return error("VerifyDB() : *** found bad undo data at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString().c_str()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// check level 3: check for inconsistencies during memory-only disconnect of tip blocks
|
|
|
|
|
|
|
|
if (nCheckLevel >= 3 && pindex == pindexState && (coins.GetCacheSize() + pcoinsTip->GetCacheSize()) <= 2*nCoinCacheSize + 32000) { |
|
|
|
|
|
|
|
bool fClean = true; |
|
|
|
|
|
|
|
if (!block.DisconnectBlock(pindex, coins, &fClean)) |
|
|
|
|
|
|
|
return error("VerifyDB() : *** irrecoverable inconsistency in block data at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString().c_str()); |
|
|
|
|
|
|
|
pindexState = pindex->pprev; |
|
|
|
|
|
|
|
if (!fClean) { |
|
|
|
|
|
|
|
nGoodTransactions = 0; |
|
|
|
|
|
|
|
pindexFailure = pindex; |
|
|
|
|
|
|
|
} else |
|
|
|
|
|
|
|
nGoodTransactions += block.vtx.size(); |
|
|
|
} |
|
|
|
} |
|
|
|
// TODO: stronger verifications
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
if (pindexFork && !fRequestShutdown) |
|
|
|
if (pindexFailure) |
|
|
|
{ |
|
|
|
return error("VerifyDB() : *** coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", pindexBest->nHeight - pindexFailure->nHeight + 1, nGoodTransactions); |
|
|
|
// TODO: reorg back
|
|
|
|
|
|
|
|
return error("LoadBlockIndex(): chain database corrupted"); |
|
|
|
// check level 4: try reconnecting blocks
|
|
|
|
|
|
|
|
if (nCheckLevel >= 4) { |
|
|
|
|
|
|
|
CBlockIndex *pindex = pindexState; |
|
|
|
|
|
|
|
while (pindex != pindexBest && !fRequestShutdown) { |
|
|
|
|
|
|
|
pindex = pindex->pnext; |
|
|
|
|
|
|
|
CBlock block; |
|
|
|
|
|
|
|
if (!block.ReadFromDisk(pindex)) |
|
|
|
|
|
|
|
return error("VerifyDB() : *** block.ReadFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString().c_str()); |
|
|
|
|
|
|
|
if (!block.ConnectBlock(pindex, coins)) |
|
|
|
|
|
|
|
return error("VerifyDB() : *** found unconnectable block at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString().c_str()); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
printf("No coin database inconsistencies in last %i blocks (%i transactions)\n", pindexBest->nHeight - pindexState->nHeight, nGoodTransactions); |
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|