validation: Make ReplayBlocks interruptible

Now, when an interrupt is received during ReplayBlocks the
intermediate progress is flushed:
In addition to updating the coins on disk, this
flush adjusts the head blocks, so that with the next
restart, the replay continues from where we stopped.
This commit is contained in:
Martin Zumsande 2024-05-17 17:49:20 -04:00
parent 57d611e53b
commit 4d1342fbcf
2 changed files with 31 additions and 9 deletions

View file

@ -95,17 +95,21 @@ bool CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashB
size_t count = 0; size_t count = 0;
size_t changed = 0; size_t changed = 0;
assert(!hashBlock.IsNull()); assert(!hashBlock.IsNull());
bool unfinished_replay{false};
uint256 old_tip = GetBestBlock(); uint256 old_tip = GetBestBlock();
if (old_tip.IsNull()) { if (old_tip.IsNull()) {
// We may be in the middle of replaying. // We may be in the process of replaying.
std::vector<uint256> old_heads = GetHeadBlocks(); std::vector<uint256> old_heads = GetHeadBlocks();
if (old_heads.size() == 2) { if (old_heads.size() == 2) {
if (old_heads[0] != hashBlock) { if (old_heads[0] != hashBlock) {
LogPrintLevel(BCLog::COINDB, BCLog::Level::Error, "The coins database detected an inconsistent state, likely due to a previous crash or shutdown. You will need to restart bitcoind with the -reindex-chainstate or -reindex configuration option.\n"); // We haven't reached the target of replaying yet.
// This happens if the replay was interrupted by the user.
unfinished_replay = true;
} else {
// Replaying has reached its end, flush the results.
old_tip = old_heads[1];
} }
assert(old_heads[0] == hashBlock);
old_tip = old_heads[1];
} }
} }
@ -113,8 +117,10 @@ bool CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashB
// transition from old_tip to hashBlock. // transition from old_tip to hashBlock.
// A vector is used for future extensibility, as we may want to support // A vector is used for future extensibility, as we may want to support
// interrupting after partial writes from multiple independent reorgs. // interrupting after partial writes from multiple independent reorgs.
batch.Erase(DB_BEST_BLOCK); if (!unfinished_replay) {
batch.Write(DB_HEAD_BLOCKS, Vector(hashBlock, old_tip)); batch.Erase(DB_BEST_BLOCK);
batch.Write(DB_HEAD_BLOCKS, Vector(hashBlock, old_tip));
}
for (auto it{cursor.Begin()}; it != cursor.End();) { for (auto it{cursor.Begin()}; it != cursor.End();) {
if (it->second.IsDirty()) { if (it->second.IsDirty()) {
@ -141,9 +147,19 @@ bool CCoinsViewDB::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &hashB
} }
} }
// In the last batch, mark the database as consistent with hashBlock again. if (unfinished_replay) {
batch.Erase(DB_HEAD_BLOCKS); // This is called when, during ReplayBlocks, an interrupt is received.
batch.Write(DB_BEST_BLOCK, hashBlock); // Update the replay origin (saved in second place in DB_HEAD_BLOCKS)
// so that the replay can pick up from here after restart.
// The replay target remains unchanged.
std::vector<uint256> old_heads = GetHeadBlocks();
assert(old_heads.size() == 2);
batch.Write(DB_HEAD_BLOCKS, Vector(old_heads[0], hashBlock));
} else {
// In the last batch, mark the database as consistent with hashBlock again.
batch.Erase(DB_HEAD_BLOCKS);
batch.Write(DB_BEST_BLOCK, hashBlock);
}
LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0)); LogDebug(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
bool ret = m_db->WriteBatch(batch); bool ret = m_db->WriteBatch(batch);

View file

@ -4976,6 +4976,12 @@ bool Chainstate::ReplayBlocks()
LogPrintf("Rolling forward %s (%i)\n", pindex.GetBlockHash().ToString(), nHeight); LogPrintf("Rolling forward %s (%i)\n", pindex.GetBlockHash().ToString(), nHeight);
m_chainman.GetNotifications().progress(_("Replaying blocks…"), (int)((nHeight - nForkHeight) * 100.0 / (pindexNew->nHeight - nForkHeight)), false); m_chainman.GetNotifications().progress(_("Replaying blocks…"), (int)((nHeight - nForkHeight) * 100.0 / (pindexNew->nHeight - nForkHeight)), false);
if (!RollforwardBlock(&pindex, cache)) return false; if (!RollforwardBlock(&pindex, cache)) return false;
if (m_chainman.m_interrupt) {
LogInfo("Flushing intermediate state of replay\n");
cache.SetBestBlock(pindex.GetBlockHash());
cache.Flush();
return false;
}
} }
cache.SetBestBlock(pindexNew->GetBlockHash()); cache.SetBestBlock(pindexNew->GetBlockHash());