Make SetBestChain() atomic

In case a reorganisation fails, the internal state could become
inconsistent (memory only). Previously, a cache per block connect
or disconnect action was used, so blocks could not be applied in
a partial way. Extend this to a cache for the entire reorganisation,
making it atomic entirely. This also simplifies the code a bit.
This commit is contained in:
Pieter Wuille 2012-12-01 16:46:23 +01:00
parent cd7fb7d1de
commit d33a9218ab

View File

@ -1667,7 +1667,9 @@ bool CBlock::ConnectBlock(CBlockIndex* pindex, CCoinsViewCache &view, bool fJust
bool SetBestChain(CBlockIndex* pindexNew) bool SetBestChain(CBlockIndex* pindexNew)
{ {
CCoinsViewCache &view = *pcoinsTip; // All modifications to the coin state will be done in this cache.
// Only when all have succeeded, we push it to pcoinsTip.
CCoinsViewCache view(*pcoinsTip, true);
// special case for attaching the genesis block // special case for attaching the genesis block
// note that no ConnectBlock is called, so its coinbase output is non-spendable // note that no ConnectBlock is called, so its coinbase output is non-spendable
@ -1720,11 +1722,8 @@ bool SetBestChain(CBlockIndex* pindexNew)
CBlock block; CBlock block;
if (!block.ReadFromDisk(pindex)) if (!block.ReadFromDisk(pindex))
return error("SetBestBlock() : ReadFromDisk for disconnect failed"); return error("SetBestBlock() : ReadFromDisk for disconnect failed");
CCoinsViewCache viewTemp(view, true); if (!block.DisconnectBlock(pindex, view))
if (!block.DisconnectBlock(pindex, viewTemp))
return error("SetBestBlock() : DisconnectBlock %s failed", BlockHashStr(pindex->GetBlockHash()).c_str()); return error("SetBestBlock() : DisconnectBlock %s failed", BlockHashStr(pindex->GetBlockHash()).c_str());
if (!viewTemp.Flush())
return error("SetBestBlock() : Cache flush failed after disconnect");
// Queue memory transactions to resurrect // Queue memory transactions to resurrect
BOOST_FOREACH(const CTransaction& tx, block.vtx) BOOST_FOREACH(const CTransaction& tx, block.vtx)
@ -1738,26 +1737,27 @@ bool SetBestChain(CBlockIndex* pindexNew)
CBlock block; CBlock block;
if (!block.ReadFromDisk(pindex)) if (!block.ReadFromDisk(pindex))
return error("SetBestBlock() : ReadFromDisk for connect failed"); return error("SetBestBlock() : ReadFromDisk for connect failed");
CCoinsViewCache viewTemp(view, true); if (!block.ConnectBlock(pindex, view)) {
if (!block.ConnectBlock(pindex, viewTemp)) {
InvalidChainFound(pindexNew); InvalidChainFound(pindexNew);
InvalidBlockFound(pindex); InvalidBlockFound(pindex);
return error("SetBestBlock() : ConnectBlock %s failed", BlockHashStr(pindex->GetBlockHash()).c_str()); return error("SetBestBlock() : ConnectBlock %s failed", BlockHashStr(pindex->GetBlockHash()).c_str());
} }
if (!viewTemp.Flush())
return error("SetBestBlock() : Cache flush failed after connect");
// Queue memory transactions to delete // Queue memory transactions to delete
BOOST_FOREACH(const CTransaction& tx, block.vtx) BOOST_FOREACH(const CTransaction& tx, block.vtx)
vDelete.push_back(tx); vDelete.push_back(tx);
} }
// Flush changes to global coin state
if (!view.Flush())
return error("SetBestBlock() : unable to modify coin state");
// Make sure it's successfully written to disk before changing memory structure // Make sure it's successfully written to disk before changing memory structure
bool fIsInitialDownload = IsInitialBlockDownload(); bool fIsInitialDownload = IsInitialBlockDownload();
if (!fIsInitialDownload || view.GetCacheSize() > nCoinCacheSize) { if (!fIsInitialDownload || pcoinsTip->GetCacheSize() > nCoinCacheSize) {
FlushBlockFile(); FlushBlockFile();
pblocktree->Sync(); pblocktree->Sync();
if (!view.Flush()) if (!pcoinsTip->Flush())
return false; return false;
} }