Skip to content

Commit

Permalink
Update LoadChainstate to use util::Result
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanofsky committed Jul 21, 2022
1 parent 779966d commit 6f2b3ee
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 49 deletions.
8 changes: 4 additions & 4 deletions src/bitcoin-chainstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ int main(int argc, char* argv[])
cache_sizes.coins = (450 << 20) - (2 << 20) - (2 << 22);
node::ChainstateLoadOptions options;
options.check_interrupt = [] { return false; };
auto [status, error] = node::LoadChainstate(chainman, cache_sizes, options);
if (status != node::ChainstateLoadStatus::SUCCESS) {
auto result = node::LoadChainstate(chainman, cache_sizes, options);
if (!result) {
std::cerr << "Failed to load Chain state from your datadir." << std::endl;
goto epilogue;
} else {
std::tie(status, error) = node::VerifyLoadedChainstate(chainman, options);
if (status != node::ChainstateLoadStatus::SUCCESS) {
result = node::VerifyLoadedChainstate(chainman, options);
if (!result) {
std::cerr << "Failed to verify loaded Chain state from your datadir." << std::endl;
goto epilogue;
}
Expand Down
17 changes: 9 additions & 8 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1453,33 +1453,34 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)

uiInterface.InitMessage(_("Loading block index…").translated);
const int64_t load_block_index_start_time = GetTimeMillis();
auto catch_exceptions = [](auto&& f) {
auto catch_exceptions = [](auto&& f) -> util::Result<void, node::ChainstateLoadError> {
try {
return f();
} catch (const std::exception& e) {
LogPrintf("%s\n", e.what());
return std::make_tuple(node::ChainstateLoadStatus::FAILURE, _("Error opening block database"));
return {util::Error{}, _("Error opening block database"), node::ChainstateLoadError::FAILURE};
}
};
auto [status, error] = catch_exceptions([&]{ return LoadChainstate(chainman, cache_sizes, options); });
if (status == node::ChainstateLoadStatus::SUCCESS) {
auto result = catch_exceptions([&]{ return LoadChainstate(chainman, cache_sizes, options); });
if (result) {
uiInterface.InitMessage(_("Verifying blocks…").translated);
if (chainman.m_blockman.m_have_pruned && options.check_blocks > MIN_BLOCKS_TO_KEEP) {
LogPrintfCategory(BCLog::PRUNE, "pruned datadir may not have more than %d blocks; only checking available blocks\n",
MIN_BLOCKS_TO_KEEP);
}
std::tie(status, error) = catch_exceptions([&]{ return VerifyLoadedChainstate(chainman, options);});
if (status == node::ChainstateLoadStatus::SUCCESS) {
result = catch_exceptions([&]{ return VerifyLoadedChainstate(chainman, options);});
if (result) {
fLoaded = true;
LogPrintf(" block index %15dms\n", GetTimeMillis() - load_block_index_start_time);
}
}

if (status == node::ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB) {
return InitError(error);
if (!result && result.GetFailure() == node::ChainstateLoadError::FAILURE_INCOMPATIBLE_DB) {
return InitError(util::ErrorString(result));
}

if (!fLoaded && !ShutdownRequested()) {
bilingual_str error = util::ErrorString(result);
// first suggest a reindex
if (!options.reindex) {
bool fRet = uiInterface.ThreadSafeQuestion(
Expand Down
46 changes: 24 additions & 22 deletions src/node/chainstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
#include <vector>

namespace node {
ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
const ChainstateLoadOptions& options)
util::Result<void, ChainstateLoadError> LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
const ChainstateLoadOptions& options)
{
auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull();
Expand All @@ -49,36 +49,36 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
}
}

if (options.check_interrupt && options.check_interrupt()) return {ChainstateLoadStatus::INTERRUPTED, {}};
if (options.check_interrupt && options.check_interrupt()) return {util::Error{}, bilingual_str{}, ChainstateLoadError::INTERRUPTED};

// LoadBlockIndex will load m_have_pruned if we've ever removed a
// block file from disk.
// Note that it also sets fReindex global based on the disk flag!
// From here on, fReindex and options.reindex values may be different!
if (!chainman.LoadBlockIndex()) {
if (options.check_interrupt && options.check_interrupt()) return {ChainstateLoadStatus::INTERRUPTED, {}};
return {ChainstateLoadStatus::FAILURE, _("Error loading block database")};
if (options.check_interrupt && options.check_interrupt()) return {util::Error{}, bilingual_str{}, ChainstateLoadError::INTERRUPTED};
return {util::Error{}, _("Error loading block database"), ChainstateLoadError::FAILURE};
}

if (!chainman.BlockIndex().empty() &&
!chainman.m_blockman.LookupBlockIndex(chainman.GetConsensus().hashGenesisBlock)) {
// If the loaded chain has a wrong genesis, bail out immediately
// (we're likely using a testnet datadir, or the other way around).
return {ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB, _("Incorrect or no genesis block found. Wrong datadir for network?")};
return {util::Error{}, _("Incorrect or no genesis block found. Wrong datadir for network?"), ChainstateLoadError::FAILURE_INCOMPATIBLE_DB};
}

// Check for changed -prune state. What we are concerned about is a user who has pruned blocks
// in the past, but is now trying to run unpruned.
if (chainman.m_blockman.m_have_pruned && !options.prune) {
return {ChainstateLoadStatus::FAILURE, _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain")};
return {util::Error{}, _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain"), ChainstateLoadError::FAILURE};
}

// At this point blocktree args are consistent with what's on disk.
// If we're not mid-reindex (based on disk + args), add a genesis block on disk
// (otherwise we use the one already on disk).
// This is called again in ThreadImport after the reindex completes.
if (!fReindex && !chainman.ActiveChainstate().LoadGenesisBlock()) {
return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")};
return {util::Error{}, _("Error initializing block database"), ChainstateLoadError::FAILURE};
}

// At this point we're either in reindex or we've loaded a useful
Expand All @@ -97,14 +97,15 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
// Refuse to load unsupported database format.
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
if (chainstate->CoinsDB().NeedsUpgrade()) {
return {ChainstateLoadStatus::FAILURE_INCOMPATIBLE_DB, _("Unsupported chainstate database format found. "
"Please restart with -reindex-chainstate. This will "
"rebuild the chainstate database.")};
return {util::Error{}, _("Unsupported chainstate database format found. "
"Please restart with -reindex-chainstate. This will "
"rebuild the chainstate database."),
ChainstateLoadError::FAILURE_INCOMPATIBLE_DB};
}

// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
if (!chainstate->ReplayBlocks()) {
return {ChainstateLoadStatus::FAILURE, _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.")};
return {util::Error{}, _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate."), ChainstateLoadError::FAILURE};
}

// The on-disk coinsdb is now in a good state, create the cache
Expand All @@ -114,7 +115,7 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
if (!is_coinsview_empty(chainstate)) {
// LoadChainTip initializes the chain based on CoinsTip()'s best block
if (!chainstate->LoadChainTip()) {
return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")};
return {util::Error{}, _("Error initializing block database"), ChainstateLoadError::FAILURE};
}
assert(chainstate->m_chain.Tip() != nullptr);
}
Expand All @@ -124,15 +125,15 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
auto chainstates{chainman.GetAll()};
if (std::any_of(chainstates.begin(), chainstates.end(),
[](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {
return {ChainstateLoadStatus::FAILURE, strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
chainman.GetConsensus().SegwitHeight)};
return {util::Error{}, strprintf(_("Witness data for blocks after height %d requires validation. Please restart with -reindex."),
chainman.GetConsensus().SegwitHeight), ChainstateLoadError::FAILURE};
};
}

return {ChainstateLoadStatus::SUCCESS, {}};
return {};
}

ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options)
util::Result<void, ChainstateLoadError> VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options)
{
auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
return options.reindex || options.reindex_chainstate || chainstate->CoinsTip().GetBestBlock().IsNull();
Expand All @@ -144,20 +145,21 @@ ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const C
if (!is_coinsview_empty(chainstate)) {
const CBlockIndex* tip = chainstate->m_chain.Tip();
if (tip && tip->nTime > GetTime() + MAX_FUTURE_BLOCK_TIME) {
return {ChainstateLoadStatus::FAILURE, _("The block database contains a block which appears to be from the future. "
"This may be due to your computer's date and time being set incorrectly. "
"Only rebuild the block database if you are sure that your computer's date and time are correct")};
return {util::Error{}, _("The block database contains a block which appears to be from the future. "
"This may be due to your computer's date and time being set incorrectly. "
"Only rebuild the block database if you are sure that your computer's date and time are correct"),
ChainstateLoadError::FAILURE};
}

if (!CVerifyDB().VerifyDB(
*chainstate, chainman.GetConsensus(), chainstate->CoinsDB(),
options.check_level,
options.check_blocks)) {
return {ChainstateLoadStatus::FAILURE, _("Corrupted block database detected")};
return {util::Error{}, _("Corrupted block database detected"), ChainstateLoadError::FAILURE};
}
}
}

return {ChainstateLoadStatus::SUCCESS, {}};
return {};
}
} // namespace node
20 changes: 9 additions & 11 deletions src/node/chainstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#ifndef BITCOIN_NODE_CHAINSTATE_H
#define BITCOIN_NODE_CHAINSTATE_H

#include <util/result.h>
#include <validation.h>

#include <cstdint>
Expand All @@ -31,14 +32,11 @@ struct ChainstateLoadOptions {
std::function<void()> coins_error_cb;
};

//! Chainstate load status. Simple applications can just check for the success
//! case, and treat other cases as errors. More complex applications may want to
//! try reindexing in the generic failure case, and pass an interrupt callback
//! and exit cleanly in the interrupted case.
enum class ChainstateLoadStatus { SUCCESS, FAILURE, FAILURE_INCOMPATIBLE_DB, INTERRUPTED };

//! Chainstate load status code and optional error string.
using ChainstateLoadResult = std::tuple<ChainstateLoadStatus, bilingual_str>;
//! Chainstate load errors. Simple applications can just treat all errors as
//! failures. More complex applications may want to try reindexing in the
//! generic error case, and pass an interrupt callback and exit cleanly in the
//! interrupted case.
enum class ChainstateLoadError { FAILURE, FAILURE_INCOMPATIBLE_DB, INTERRUPTED };

/** This sequence can have 4 types of outcomes:
*
Expand All @@ -53,9 +51,9 @@ using ChainstateLoadResult = std::tuple<ChainstateLoadStatus, bilingual_str>;
*
* LoadChainstate returns a (status code, error string) tuple.
*/
ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
const ChainstateLoadOptions& options);
ChainstateLoadResult VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options);
util::Result<void, ChainstateLoadError> LoadChainstate(ChainstateManager& chainman, const CacheSizes& cache_sizes,
const ChainstateLoadOptions& options);
util::Result<void, ChainstateLoadError> VerifyLoadedChainstate(ChainstateManager& chainman, const ChainstateLoadOptions& options);
} // namespace node

#endif // BITCOIN_NODE_CHAINSTATE_H
8 changes: 4 additions & 4 deletions src/test/util/setup_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,11 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
options.prune = node::fPruneMode;
options.check_blocks = m_args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS);
options.check_level = m_args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL);
auto [status, error] = LoadChainstate(*Assert(m_node.chainman), m_cache_sizes, options);
assert(status == node::ChainstateLoadStatus::SUCCESS);
auto result = LoadChainstate(*Assert(m_node.chainman), m_cache_sizes, options);
assert(result);

std::tie(status, error) = VerifyLoadedChainstate(*Assert(m_node.chainman), options);
assert(status == node::ChainstateLoadStatus::SUCCESS);
result = VerifyLoadedChainstate(*Assert(m_node.chainman), options);
assert(result);

BlockValidationState state;
if (!m_node.chainman->ActiveChainstate().ActivateBestChain(state)) {
Expand Down

0 comments on commit 6f2b3ee

Please sign in to comment.