Skip to content

Commit 832fa2d

Browse files
committed
Merge bitcoin/bitcoin#25574: validation: Improve error handling when VerifyDB dosn't finish successfully
0af16e7 doc: add release note for #25574 (Martin Zumsande) 57ef2a4 validation: report if pruning prevents completion of verification (Martin Zumsande) 0c7785b init, validation: Improve handling if VerifyDB() fails due to insufficient dbcache (Martin Zumsande) d6f781f validation: return VerifyDBResult::INTERRUPTED if verification was interrupted (Martin Zumsande) 6360b53 validation: Change return value of VerifyDB to enum type (Martin Zumsande) Pull request description: `VerifyDB()` can fail to complete due to insufficient dbcache at the level 3 checks. This PR improves the error handling in this case in the following ways: - The rpc `-verifychain` now returns false if the check can't be completed due to insufficient cache - During init, we only log a warning if the default values for `-checkblocks` and `-checklevel` are taken and the check doesn't complete. However, if the user actively specifies one of these args, we return with an InitError if we can't complete the check. This PR also changes `-verifychain` RPC to return `false` if the verification didn't finish due to missing block data (pruning) or due to being interrupted by the node being shutdown. Previously, this PR also included a fix for a possible assert during verification - this was done in #27009 (now merged). ACKs for top commit: achow101: ACK 0af16e7 ryanofsky: Code review ACK 0af16e7. Only small suggested changes since the last review, like renaming some of the enum values. I did leave more suggestions, but they are not very important and could be followups john-moffett: ACK 0af16e7 MarcoFalke: lgtm re-ACK 0af16e7 🎚 Tree-SHA512: 84b4f767cf9bfbafef362312757c9bf765b41ae3977f4ece840e40c52a2266b1457832df0cdf70440be0aac2168d9b58fc817238630b0b6812f3836ca950bc0e
2 parents 9f6ef0c + 0af16e7 commit 832fa2d

File tree

9 files changed

+85
-27
lines changed

9 files changed

+85
-27
lines changed

doc/release-notes-25574.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Updated settings
2+
----------------
3+
4+
If the `-checkblocks` or `-checklevel` options are explicitly provided by the
5+
user, but the verification checks cannot be completed due to an insufficient
6+
dbcache, Bitcoin Core will now return an error at startup. (#25574)
7+
8+
RPC
9+
---
10+
The `-verifychain` RPC will now return `false` if the checks didn't fail,
11+
but couldn't be completed at the desired depth and level. This could be due
12+
to missing data while pruning, due to an insufficient dbcache or due to
13+
the node being shutdown before the call could finish. (#25574)

src/init.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1495,6 +1495,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
14951495
options.prune = chainman.m_blockman.IsPruneMode();
14961496
options.check_blocks = args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS);
14971497
options.check_level = args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL);
1498+
options.require_full_verification = args.IsArgSet("-checkblocks") || args.IsArgSet("-checklevel");
14981499
options.check_interrupt = ShutdownRequested;
14991500
options.coins_error_cb = [] {
15001501
uiInterface.ThreadSafeMessageBox(
@@ -1526,7 +1527,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
15261527
}
15271528
}
15281529

1529-
if (status == node::ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB) {
1530+
if (status == node::ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB || status == node::ChainstateLoadStatus::FAILURE_INSUFFICIENT_DBCACHE) {
15301531
return InitError(error);
15311532
}
15321533

src/node/chainstate.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,23 @@ ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const C
192192
"Only rebuild the block database if you are sure that your computer's date and time are correct")};
193193
}
194194

195-
if (!CVerifyDB().VerifyDB(
196-
*chainstate, chainman.GetConsensus(), chainstate->CoinsDB(),
197-
options.check_level,
198-
options.check_blocks)) {
195+
VerifyDBResult result = CVerifyDB().VerifyDB(
196+
*chainstate, chainman.GetConsensus(), chainstate->CoinsDB(),
197+
options.check_level,
198+
options.check_blocks);
199+
switch (result) {
200+
case VerifyDBResult::SUCCESS:
201+
case VerifyDBResult::INTERRUPTED:
202+
case VerifyDBResult::SKIPPED_MISSING_BLOCKS:
203+
break;
204+
case VerifyDBResult::CORRUPTED_BLOCK_DB:
199205
return {ChainstateLoadStatus::FAILURE, _("Corrupted block database detected")};
200-
}
206+
case VerifyDBResult::SKIPPED_L3_CHECKS:
207+
if (options.require_full_verification) {
208+
return {ChainstateLoadStatus::FAILURE_INSUFFICIENT_DBCACHE, _("Insufficient dbcache for block verification")};
209+
}
210+
break;
211+
} // no default case, so the compiler can warn about missing cases
201212
}
202213
}
203214

src/node/chainstate.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct ChainstateLoadOptions {
2525
bool reindex{false};
2626
bool reindex_chainstate{false};
2727
bool prune{false};
28+
bool require_full_verification{true};
2829
int64_t check_blocks{DEFAULT_CHECKBLOCKS};
2930
int64_t check_level{DEFAULT_CHECKLEVEL};
3031
std::function<bool()> check_interrupt;
@@ -35,7 +36,13 @@ struct ChainstateLoadOptions {
3536
//! case, and treat other cases as errors. More complex applications may want to
3637
//! try reindexing in the generic failure case, and pass an interrupt callback
3738
//! and exit cleanly in the interrupted case.
38-
enum class ChainstateLoadStatus { SUCCESS, FAILURE, FAILURE_INCOMPATIBLE_DB, INTERRUPTED };
39+
enum class ChainstateLoadStatus {
40+
SUCCESS,
41+
FAILURE,
42+
FAILURE_INCOMPATIBLE_DB,
43+
FAILURE_INSUFFICIENT_DBCACHE,
44+
INTERRUPTED,
45+
};
3946

4047
//! Chainstate load status code and optional error string.
4148
using ChainstateLoadResult = std::tuple<ChainstateLoadStatus, bilingual_str>;

src/rpc/blockchain.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,7 +1110,7 @@ static RPCHelpMan verifychain()
11101110
{"nblocks", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%d, 0=all", DEFAULT_CHECKBLOCKS)}, "The number of blocks to check."},
11111111
},
11121112
RPCResult{
1113-
RPCResult::Type::BOOL, "", "Verified or not"},
1113+
RPCResult::Type::BOOL, "", "Verification finished successfully. If false, check debug.log for reason."},
11141114
RPCExamples{
11151115
HelpExampleCli("verifychain", "")
11161116
+ HelpExampleRpc("verifychain", "")
@@ -1125,7 +1125,7 @@ static RPCHelpMan verifychain()
11251125

11261126
Chainstate& active_chainstate = chainman.ActiveChainstate();
11271127
return CVerifyDB().VerifyDB(
1128-
active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth);
1128+
active_chainstate, chainman.GetParams().GetConsensus(), active_chainstate.CoinsTip(), check_level, check_depth) == VerifyDBResult::SUCCESS;
11291129
},
11301130
};
11311131
}

src/test/util/setup_common.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ void TestingSetup::LoadVerifyActivateChainstate()
222222
options.prune = chainman.m_blockman.IsPruneMode();
223223
options.check_blocks = m_args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS);
224224
options.check_level = m_args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL);
225+
options.require_full_verification = m_args.IsArgSet("-checkblocks") || m_args.IsArgSet("-checklevel");
225226
auto [status, error] = LoadChainstate(chainman, m_cache_sizes, options);
226227
assert(status == node::ChainstateLoadStatus::SUCCESS);
227228

src/validation.cpp

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4060,7 +4060,7 @@ CVerifyDB::~CVerifyDB()
40604060
uiInterface.ShowProgress("", 100, false);
40614061
}
40624062

4063-
bool CVerifyDB::VerifyDB(
4063+
VerifyDBResult CVerifyDB::VerifyDB(
40644064
Chainstate& chainstate,
40654065
const Consensus::Params& consensus_params,
40664066
CCoinsView& coinsview,
@@ -4069,7 +4069,7 @@ bool CVerifyDB::VerifyDB(
40694069
AssertLockHeld(cs_main);
40704070

40714071
if (chainstate.m_chain.Tip() == nullptr || chainstate.m_chain.Tip()->pprev == nullptr) {
4072-
return true;
4072+
return VerifyDBResult::SUCCESS;
40734073
}
40744074

40754075
// Verify blocks in the best chain
@@ -4084,6 +4084,7 @@ bool CVerifyDB::VerifyDB(
40844084
int nGoodTransactions = 0;
40854085
BlockValidationState state;
40864086
int reportDone = 0;
4087+
bool skipped_no_block_data{false};
40874088
bool skipped_l3_checks{false};
40884089
LogPrintf("Verification progress: 0%%\n");
40894090

@@ -4103,25 +4104,29 @@ bool CVerifyDB::VerifyDB(
41034104
if ((chainstate.m_blockman.IsPruneMode() || is_snapshot_cs) && !(pindex->nStatus & BLOCK_HAVE_DATA)) {
41044105
// If pruning or running under an assumeutxo snapshot, only go
41054106
// back as far as we have data.
4106-
LogPrintf("VerifyDB(): block verification stopping at height %d (pruning, no data)\n", pindex->nHeight);
4107+
LogPrintf("VerifyDB(): block verification stopping at height %d (no data). This could be due to pruning or use of an assumeutxo snapshot.\n", pindex->nHeight);
4108+
skipped_no_block_data = true;
41074109
break;
41084110
}
41094111
CBlock block;
41104112
// check level 0: read from disk
41114113
if (!ReadBlockFromDisk(block, pindex, consensus_params)) {
4112-
return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
4114+
LogPrintf("Verification error: ReadBlockFromDisk failed at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
4115+
return VerifyDBResult::CORRUPTED_BLOCK_DB;
41134116
}
41144117
// check level 1: verify block validity
41154118
if (nCheckLevel >= 1 && !CheckBlock(block, state, consensus_params)) {
4116-
return error("%s: *** found bad block at %d, hash=%s (%s)\n", __func__,
4117-
pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());
4119+
LogPrintf("Verification error: found bad block at %d, hash=%s (%s)\n",
4120+
pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());
4121+
return VerifyDBResult::CORRUPTED_BLOCK_DB;
41184122
}
41194123
// check level 2: verify undo validity
41204124
if (nCheckLevel >= 2 && pindex) {
41214125
CBlockUndo undo;
41224126
if (!pindex->GetUndoPos().IsNull()) {
41234127
if (!UndoReadFromDisk(undo, pindex)) {
4124-
return error("VerifyDB(): *** found bad undo data at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
4128+
LogPrintf("Verification error: found bad undo data at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
4129+
return VerifyDBResult::CORRUPTED_BLOCK_DB;
41254130
}
41264131
}
41274132
}
@@ -4133,7 +4138,8 @@ bool CVerifyDB::VerifyDB(
41334138
assert(coins.GetBestBlock() == pindex->GetBlockHash());
41344139
DisconnectResult res = chainstate.DisconnectBlock(block, pindex, coins);
41354140
if (res == DISCONNECT_FAILED) {
4136-
return error("VerifyDB(): *** irrecoverable inconsistency in block data at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
4141+
LogPrintf("Verification error: irrecoverable inconsistency in block data at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
4142+
return VerifyDBResult::CORRUPTED_BLOCK_DB;
41374143
}
41384144
if (res == DISCONNECT_UNCLEAN) {
41394145
nGoodTransactions = 0;
@@ -4145,14 +4151,16 @@ bool CVerifyDB::VerifyDB(
41454151
skipped_l3_checks = true;
41464152
}
41474153
}
4148-
if (ShutdownRequested()) return true;
4154+
if (ShutdownRequested()) return VerifyDBResult::INTERRUPTED;
41494155
}
41504156
if (pindexFailure) {
4151-
return error("VerifyDB(): *** coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", chainstate.m_chain.Height() - pindexFailure->nHeight + 1, nGoodTransactions);
4157+
LogPrintf("Verification error: coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", chainstate.m_chain.Height() - pindexFailure->nHeight + 1, nGoodTransactions);
4158+
return VerifyDBResult::CORRUPTED_BLOCK_DB;
41524159
}
41534160
if (skipped_l3_checks) {
41544161
LogPrintf("Skipped verification of level >=3 (insufficient database cache size). Consider increasing -dbcache.\n");
41554162
}
4163+
41564164
// store block count as we move pindex at check level >= 4
41574165
int block_count = chainstate.m_chain.Height() - pindex->nHeight;
41584166

@@ -4168,18 +4176,27 @@ bool CVerifyDB::VerifyDB(
41684176
uiInterface.ShowProgress(_("Verifying blocks…").translated, percentageDone, false);
41694177
pindex = chainstate.m_chain.Next(pindex);
41704178
CBlock block;
4171-
if (!ReadBlockFromDisk(block, pindex, consensus_params))
4172-
return error("VerifyDB(): *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString());
4179+
if (!ReadBlockFromDisk(block, pindex, consensus_params)) {
4180+
LogPrintf("Verification error: ReadBlockFromDisk failed at %d, hash=%s\n", pindex->nHeight, pindex->GetBlockHash().ToString());
4181+
return VerifyDBResult::CORRUPTED_BLOCK_DB;
4182+
}
41734183
if (!chainstate.ConnectBlock(block, state, pindex, coins)) {
4174-
return error("VerifyDB(): *** found unconnectable block at %d, hash=%s (%s)", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());
4184+
LogPrintf("Verification error: found unconnectable block at %d, hash=%s (%s)\n", pindex->nHeight, pindex->GetBlockHash().ToString(), state.ToString());
4185+
return VerifyDBResult::CORRUPTED_BLOCK_DB;
41754186
}
4176-
if (ShutdownRequested()) return true;
4187+
if (ShutdownRequested()) return VerifyDBResult::INTERRUPTED;
41774188
}
41784189
}
41794190

41804191
LogPrintf("Verification: No coin database inconsistencies in last %i blocks (%i transactions)\n", block_count, nGoodTransactions);
41814192

4182-
return true;
4193+
if (skipped_l3_checks) {
4194+
return VerifyDBResult::SKIPPED_L3_CHECKS;
4195+
}
4196+
if (skipped_no_block_data) {
4197+
return VerifyDBResult::SKIPPED_MISSING_BLOCKS;
4198+
}
4199+
return VerifyDBResult::SUCCESS;
41834200
}
41844201

41854202
/** Apply the effects of a block on the utxo cache, ignoring that it may already have been applied. */

src/validation.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,12 +349,20 @@ bool HasValidProofOfWork(const std::vector<CBlockHeader>& headers, const Consens
349349
/** Return the sum of the work on a given set of headers */
350350
arith_uint256 CalculateHeadersWork(const std::vector<CBlockHeader>& headers);
351351

352+
enum class VerifyDBResult {
353+
SUCCESS,
354+
CORRUPTED_BLOCK_DB,
355+
INTERRUPTED,
356+
SKIPPED_L3_CHECKS,
357+
SKIPPED_MISSING_BLOCKS,
358+
};
359+
352360
/** RAII wrapper for VerifyDB: Verify consistency of the block and coin databases */
353361
class CVerifyDB {
354362
public:
355363
CVerifyDB();
356364
~CVerifyDB();
357-
bool VerifyDB(
365+
[[nodiscard]] VerifyDBResult VerifyDB(
358366
Chainstate& chainstate,
359367
const Consensus::Params& consensus_params,
360368
CCoinsView& coinsview,

test/functional/feature_pruning.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,8 @@ def reorg_test(self):
223223
def reorg_back(self):
224224
# Verify that a block on the old main chain fork has been pruned away
225225
assert_raises_rpc_error(-1, "Block not available (pruned data)", self.nodes[2].getblock, self.forkhash)
226-
with self.nodes[2].assert_debug_log(expected_msgs=['block verification stopping at height', '(pruning, no data)']):
227-
self.nodes[2].verifychain(checklevel=4, nblocks=0)
226+
with self.nodes[2].assert_debug_log(expected_msgs=['block verification stopping at height', '(no data)']):
227+
assert not self.nodes[2].verifychain(checklevel=4, nblocks=0)
228228
self.log.info(f"Will need to redownload block {self.forkheight}")
229229

230230
# Verify that we have enough history to reorg back to the fork point

0 commit comments

Comments
 (0)