commit
1f4b80a437
3 changed files with 147 additions and 43 deletions
|
@ -300,7 +300,7 @@ std::string HelpMessage()
|
||||||
" -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" +
|
" -rescan " + _("Rescan the block chain for missing wallet transactions") + "\n" +
|
||||||
" -salvagewallet " + _("Attempt to recover private keys from a corrupt wallet.dat") + "\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" +
|
" -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" +
|
" -loadblock=<file> " + _("Imports blocks from external blk000??.dat file") + "\n" +
|
||||||
" -reindex " + _("Rebuild blockchain index from current blk000??.dat files") + "\n" +
|
" -reindex " + _("Rebuild blockchain index from current blk000??.dat files") + "\n" +
|
||||||
|
|
||||||
|
@ -752,7 +752,10 @@ bool AppInit2()
|
||||||
pblocktree->WriteReindexing(true);
|
pblocktree->WriteReindexing(true);
|
||||||
|
|
||||||
if (!LoadBlockIndex())
|
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
|
// as LoadBlockIndex can take several minutes, it's possible the user
|
||||||
// requested to kill bitcoin-qt during the last operation. If so, exit.
|
// requested to kill bitcoin-qt during the last operation. If so, exit.
|
||||||
|
|
128
src/main.cpp
128
src/main.cpp
|
@ -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());
|
||||||
|
|
||||||
CBlockUndo blockUndo;
|
if (pfClean)
|
||||||
{
|
*pfClean = false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(blockUndo.vtxundo.size() + 1 == vtx.size());
|
bool fClean = true;
|
||||||
|
|
||||||
|
CBlockUndo 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");
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
55
src/main.h
55
src/main.h
|
@ -128,6 +128,8 @@ FILE* OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly = false);
|
||||||
bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp = NULL);
|
bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp = NULL);
|
||||||
/** Load the block tree and coins database from disk */
|
/** Load the block tree and coins database from disk */
|
||||||
bool LoadBlockIndex();
|
bool LoadBlockIndex();
|
||||||
|
/** Verify consistency of the block and coin databases */
|
||||||
|
bool VerifyDB();
|
||||||
/** Print the loaded block tree */
|
/** Print the loaded block tree */
|
||||||
void PrintBlockTree();
|
void PrintBlockTree();
|
||||||
/** Find a block by height in the currently-connected chain */
|
/** Find a block by height in the currently-connected chain */
|
||||||
|
@ -748,7 +750,7 @@ public:
|
||||||
READWRITE(vtxundo);
|
READWRITE(vtxundo);
|
||||||
)
|
)
|
||||||
|
|
||||||
bool WriteToDisk(CDiskBlockPos &pos)
|
bool WriteToDisk(CDiskBlockPos &pos, const uint256 &hashBlock)
|
||||||
{
|
{
|
||||||
// Open history file to append
|
// Open history file to append
|
||||||
CAutoFile fileout = CAutoFile(OpenUndoFile(pos), SER_DISK, CLIENT_VERSION);
|
CAutoFile fileout = CAutoFile(OpenUndoFile(pos), SER_DISK, CLIENT_VERSION);
|
||||||
|
@ -766,6 +768,12 @@ public:
|
||||||
pos.nPos = (unsigned int)fileOutPos;
|
pos.nPos = (unsigned int)fileOutPos;
|
||||||
fileout << *this;
|
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
|
// Flush stdio buffers and commit to disk before returning
|
||||||
fflush(fileout);
|
fflush(fileout);
|
||||||
if (!IsInitialBlockDownload())
|
if (!IsInitialBlockDownload())
|
||||||
|
@ -773,6 +781,44 @@ public:
|
||||||
|
|
||||||
return true;
|
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
|
/** pruned version of CTransaction: only retains metadata and unspent transaction outputs
|
||||||
|
@ -1307,8 +1353,11 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Undo the effects of this block (with given index) on the UTXO set represented by coins
|
/** Undo the effects of this block (with given index) on the UTXO set represented by coins.
|
||||||
bool DisconnectBlock(CBlockIndex *pindex, CCoinsViewCache &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
|
// 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);
|
bool ConnectBlock(CBlockIndex *pindex, CCoinsViewCache &coins, bool fJustCheck=false);
|
||||||
|
|
Loading…
Reference in a new issue