1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <node/chainstate.h>
#include <consensus/params.h>
#include <node/blockstorage.h>
#include <validation.h>
namespace node {
std::optional<ChainstateLoadingError> LoadChainstate(bool fReset,
ChainstateManager& chainman,
CTxMemPool* mempool,
bool fPruneMode,
const Consensus::Params& consensus_params,
bool fReindexChainState,
int64_t nBlockTreeDBCache,
int64_t nCoinDBCache,
int64_t nCoinCacheUsage,
bool block_tree_db_in_memory,
bool coins_db_in_memory,
std::function<bool()> shutdown_requested,
std::function<void()> coins_error_cb)
{
auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull();
};
LOCK(cs_main);
chainman.InitializeChainstate(mempool);
chainman.m_total_coinstip_cache = nCoinCacheUsage;
chainman.m_total_coinsdb_cache = nCoinDBCache;
auto& pblocktree{chainman.m_blockman.m_block_tree_db};
// new CBlockTreeDB tries to delete the existing file, which
// fails if it's still open from the previous loop. Close it first:
pblocktree.reset();
pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, block_tree_db_in_memory, fReset));
if (fReset) {
pblocktree->WriteReindexing(true);
//If we're reindexing in prune mode, wipe away unusable block files and all undo data files
if (fPruneMode)
CleanupBlockRevFiles();
}
if (shutdown_requested && shutdown_requested()) return ChainstateLoadingError::SHUTDOWN_PROBED;
// LoadBlockIndex will load m_have_pruned if we've ever removed a
// block file from disk.
// Note that it also sets fReindex based on the disk flag!
// From here on out fReindex and fReset mean something different!
if (!chainman.LoadBlockIndex()) {
if (shutdown_requested && shutdown_requested()) return ChainstateLoadingError::SHUTDOWN_PROBED;
return ChainstateLoadingError::ERROR_LOADING_BLOCK_DB;
}
if (!chainman.BlockIndex().empty() &&
!chainman.m_blockman.LookupBlockIndex(consensus_params.hashGenesisBlock)) {
return ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK;
}
// 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 && !fPruneMode) {
return ChainstateLoadingError::ERROR_PRUNED_NEEDS_REINDEX;
}
// 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 ChainstateLoadingError::ERROR_LOAD_GENESIS_BLOCK_FAILED;
}
// At this point we're either in reindex or we've loaded a useful
// block tree into BlockIndex()!
for (CChainState* chainstate : chainman.GetAll()) {
chainstate->InitCoinsDB(
/*cache_size_bytes=*/nCoinDBCache,
/*in_memory=*/coins_db_in_memory,
/*should_wipe=*/fReset || fReindexChainState);
if (coins_error_cb) {
chainstate->CoinsErrorCatcher().AddReadErrCallback(coins_error_cb);
}
// 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 ChainstateLoadingError::ERROR_CHAINSTATE_UPGRADE_FAILED;
}
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
if (!chainstate->ReplayBlocks()) {
return ChainstateLoadingError::ERROR_REPLAYBLOCKS_FAILED;
}
// The on-disk coinsdb is now in a good state, create the cache
chainstate->InitCoinsCache(nCoinCacheUsage);
assert(chainstate->CanFlushToDisk());
if (!is_coinsview_empty(chainstate)) {
// LoadChainTip initializes the chain based on CoinsTip()'s best block
if (!chainstate->LoadChainTip()) {
return ChainstateLoadingError::ERROR_LOADCHAINTIP_FAILED;
}
assert(chainstate->m_chain.Tip() != nullptr);
}
}
if (!fReset) {
auto chainstates{chainman.GetAll()};
if (std::any_of(chainstates.begin(), chainstates.end(),
[](const CChainState* cs) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return cs->NeedsRedownload(); })) {
return ChainstateLoadingError::ERROR_BLOCKS_WITNESS_INSUFFICIENTLY_VALIDATED;
}
}
return std::nullopt;
}
std::optional<ChainstateLoadVerifyError> VerifyLoadedChainstate(ChainstateManager& chainman,
bool fReset,
bool fReindexChainState,
const Consensus::Params& consensus_params,
int check_blocks,
int check_level,
std::function<int64_t()> get_unix_time_seconds)
{
auto is_coinsview_empty = [&](CChainState* chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
return fReset || fReindexChainState || chainstate->CoinsTip().GetBestBlock().IsNull();
};
LOCK(cs_main);
for (CChainState* chainstate : chainman.GetAll()) {
if (!is_coinsview_empty(chainstate)) {
const CBlockIndex* tip = chainstate->m_chain.Tip();
if (tip && tip->nTime > get_unix_time_seconds() + MAX_FUTURE_BLOCK_TIME) {
return ChainstateLoadVerifyError::ERROR_BLOCK_FROM_FUTURE;
}
if (!CVerifyDB().VerifyDB(
*chainstate, consensus_params, chainstate->CoinsDB(),
check_level,
check_blocks)) {
return ChainstateLoadVerifyError::ERROR_CORRUPTED_BLOCK_DB;
}
}
}
return std::nullopt;
}
} // namespace node
|