Coin database checks #2145

Merged
merged 3 commits into from Jan 11, 2013
Jump to file or symbol
Failed to load files and symbols.
+146 −42
Split
View
@@ -300,7 +300,7 @@ std::string HelpMessage()
" -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" +
" -salvagewallet " + _("Attempt to recover private keys from a corrupt wallet.dat") + "\n" +
" -checkblocks=<n> " + _("How many blocks to check at startup (default: 2500, 0 = all)") + "\n" +
- " -checklevel=<n> " + _("How thorough the block verification is (0-6, default: 1)") + "\n" +
+ " -checklevel=<n> " + _("How thorough the block verification is (0-4, default: 3)") + "\n" +
" -loadblock=<file> " + _("Imports blocks from external blk000??.dat file") + "\n" +
" -reindex " + _("Rebuild blockchain index from current blk000??.dat files") + "\n" +
@@ -752,7 +752,10 @@ bool AppInit2()
pblocktree->WriteReindexing(true);
if (!LoadBlockIndex())
- return InitError(_("Error loading blkindex.dat"));
+ return InitError(_("Error loading block/coin databases"));
+
+ if (!VerifyDB())
+ return InitError(_("Corrupted database detected. Please restart the client with -reindex."));
// as LoadBlockIndex can take several minutes, it's possible the user
// requested to kill bitcoin-qt during the last operation. If so, exit.
View
@@ -1464,62 +1464,68 @@ bool CTransaction::ClientCheckInputs() const
-bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &view)
+bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &view, bool *pfClean)
{
assert(pindex == view.GetBestBlock());
+ if (pfClean)
+ *pfClean = false;
+
+ bool fClean = true;
+
CBlockUndo blockUndo;
- {
- CDiskBlockPos pos = pindex->GetUndoPos();
- if (pos.IsNull())
- return error("DisconnectBlock() : no undo data available");
- FILE *file = OpenUndoFile(pos, true);
- if (file == NULL)
- return error("DisconnectBlock() : undo file not available");
- CAutoFile fileUndo(file, SER_DISK, CLIENT_VERSION);
- fileUndo >> blockUndo;
- }
+ CDiskBlockPos pos = pindex->GetUndoPos();
+ if (pos.IsNull())
+ return error("DisconnectBlock() : no undo data available");
+ if (!blockUndo.ReadFromDisk(pos, pindex->pprev->GetBlockHash()))
+ return error("DisconnectBlock() : failure reading undo data");
- 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
for (int i = vtx.size() - 1; i >= 0; i--) {
const CTransaction &tx = vtx[i];
uint256 hash = tx.GetHash();
// check that all outputs are available
- if (!view.HaveCoins(hash))
- return error("DisconnectBlock() : outputs still spent? database corrupted");
+ if (!view.HaveCoins(hash)) {
+ fClean = fClean && error("DisconnectBlock() : outputs still spent? database corrupted");
+ view.SetCoins(hash, CCoins());
+ }
CCoins &outs = view.GetCoins(hash);
CCoins outsBlock = CCoins(tx, pindex->nHeight);
if (outs != outsBlock)
- return error("DisconnectBlock() : added transaction mismatch? database corrupted");
+ fClean = fClean && error("DisconnectBlock() : added transaction mismatch? database corrupted");
// remove outputs
outs = CCoins();
// restore inputs
if (i > 0) { // not coinbases
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;) {
const COutPoint &out = tx.vin[j].prevout;
const CTxInUndo &undo = txundo.vprevout[j];
CCoins coins;
view.GetCoins(out.hash, coins); // this can fail if the prevout was already entirely spent
- if (coins.IsPruned()) {
- if (undo.nHeight == 0)
- return error("DisconnectBlock() : undo data doesn't contain tx metadata? database corrupted");
+ if (undo.nHeight != 0) {
+ // undo data contains height: this is the last output of the prevout tx being spent
+ if (!coins.IsPruned())
+ fClean = fClean && error("DisconnectBlock() : undo data overwriting existing transaction");
+ coins = CCoins();
coins.fCoinBase = undo.fCoinBase;
coins.nHeight = undo.nHeight;
coins.nVersion = undo.nVersion;
} else {
- if (undo.nHeight != 0)
- return error("DisconnectBlock() : undo data contains unneeded tx metadata? database corrupted");
+ if (coins.IsPruned())
+ fClean = fClean && error("DisconnectBlock() : undo data adding output to missing transaction");
}
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)
coins.vout.resize(out.n+1);
coins.vout[out.n] = undo.txout;
@@ -1532,7 +1538,12 @@ bool CBlock::DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &view)
// move best block pointer to prevout block
view.SetBestBlock(pindex->pprev);
- return true;
+ if (pfClean) {
+ *pfClean = fClean;
+ return true;
+ } else {
+ return fClean;
+ }
}
void static FlushBlockFile()
@@ -1651,9 +1662,9 @@ bool CBlock::ConnectBlock(CBlockIndex* pindex, CCoinsViewCache &view, bool fJust
{
if (pindex->GetUndoPos().IsNull()) {
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");
- if (!blockundo.WriteToDisk(pos))
+ if (!blockundo.WriteToDisk(pos, pindex->pprev->GetBlockHash()))
return error("ConnectBlock() : CBlockUndo::WriteToDisk failed");
// update nUndoPos in block index
@@ -2377,36 +2388,77 @@ bool static LoadBlockIndexDB()
BlockHashStr(hashBestChain).c_str(), nBestHeight,
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
- int nCheckLevel = GetArg("-checklevel", 1);
+ int nCheckLevel = GetArg("-checklevel", 3);
int nCheckDepth = GetArg( "-checkblocks", 2500);
if (nCheckDepth == 0)
nCheckDepth = 1000000000; // suffices until the year 19000
if (nCheckDepth > nBestHeight)
nCheckDepth = nBestHeight;
+ nCheckLevel = std::max(0, std::min(4, 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)
{
if (fRequestShutdown || pindex->nHeight < nBestHeight-nCheckDepth)
break;
CBlock block;
+ // check level 0: read from disk
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
- if (nCheckLevel>0 && !block.CheckBlock())
- {
- printf("LoadBlockIndex() : *** found bad block at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString().c_str());
- pindexFork = pindex->pprev;
+ if (nCheckLevel >= 1 && !block.CheckBlock())
+ return error("VerifyDB() : *** found bad block at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString().c_str());
+ // check level 2: verify undo validity
+ 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)
- {
- // TODO: reorg back
- return error("LoadBlockIndex(): chain database corrupted");
+ if (pindexFailure)
+ return error("VerifyDB() : *** coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", pindexBest->nHeight - pindexFailure->nHeight + 1, nGoodTransactions);
+
+ // 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;
}
View
@@ -126,6 +126,8 @@ FILE* OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly = false);
bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp = NULL);
/** Load the block tree and coins database from disk */
bool LoadBlockIndex();
+/** Verify consistency of the block and coin databases */
+bool VerifyDB();
/** Print the loaded block tree */
void PrintBlockTree();
/** Find a block by height in the currently-connected chain */
@@ -746,7 +748,7 @@ class CBlockUndo
READWRITE(vtxundo);
)
- bool WriteToDisk(CDiskBlockPos &pos)
+ bool WriteToDisk(CDiskBlockPos &pos, const uint256 &hashBlock)
{
// Open history file to append
CAutoFile fileout = CAutoFile(OpenUndoFile(pos), SER_DISK, CLIENT_VERSION);
@@ -764,13 +766,57 @@ class CBlockUndo
pos.nPos = (unsigned int)fileOutPos;
fileout << *this;
+ // calculate & write checksum
+ CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
+ hasher << hashBlock;
+ hasher << *this;
+ fileout << hasher.GetHash();
+
// Flush stdio buffers and commit to disk before returning
fflush(fileout);
if (!IsInitialBlockDownload())
FileCommit(fileout);
return true;
}
+
+ bool ReadFromDisk(const CDiskBlockPos &pos, const uint256 &hashBlock)
+ {
+ // Open history file to read
+ CAutoFile filein = CAutoFile(OpenUndoFile(pos, true), SER_DISK, CLIENT_VERSION);
+ if (!filein)
+ return error("CBlockUndo::ReadFromDisk() : OpenBlockFile failed");
+
+ // Read block
+ uint256 hashChecksum;
+ try {
+ filein >> *this;
+ }
+ catch (std::exception &e) {
+ return error("%s() : deserialize or I/O error", __PRETTY_FUNCTION__);
+ }
+
+ // for compatibility with pre-release code that didn't write checksums to undo data
+ // TODO: replace by a simply 'filein >> hashChecksum' in the above try block
+ try {
+ filein >> hashChecksum;
+ } catch (std::exception &e) {
+ hashChecksum = 0;
+ }
+ uint32_t hashInit = hashChecksum.Get64(0) & 0xFFFFFFFFUL;
+ if (hashChecksum == 0 || memcmp(&hashInit, pchMessageStart, 4) == 0)
+ return true;
+
+ // Verify checksum
+ CHashWriter hasher(SER_GETHASH, PROTOCOL_VERSION);
+ hasher << hashBlock;
+ hasher << *this;
+ if (hashChecksum != hasher.GetHash())
+ return error("CBlockUndo::ReadFromDisk() : checksum mismatch");
+
+ return true;
+ }
+
};
/** pruned version of CTransaction: only retains metadata and unspent transaction outputs
@@ -1305,8 +1351,11 @@ class CBlock : public CBlockHeader
}
- // Undo the effects of this block (with given index) on the UTXO set represented by coins
- bool DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &coins);
+ /** Undo the effects of this block (with given index) on the UTXO set represented by coins.
+ * In case pfClean is provided, operation will try to be tolerant about errors, and *pfClean
+ * will be true if no problems were found. Otherwise, the return value will be false in case
+ * of problems. Note that in any case, coins may be modified. */
+ bool DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &coins, bool *pfClean = NULL);
// Apply the effects of this block (with given index) on the UTXO set represented by coins
bool ConnectBlock(CBlockIndex *pindex, CCoinsViewCache &coins, bool fJustCheck=false);