Skip to content

Commit 5693530

Browse files
committed
refactor: have CCoins* data managed under CChainState
This change encapsulates UTXO set data within CChainState instances, removing global data `pcoinsTip` and `pcoinsviewdb`. This is necessary if we want to maintain multiple chainstates with their own rendering of the UTXO set. We introduce a class CoinsViews which consolidates the construction of a CCoins* hierarchy. Construction of its various pieces (db, coinscatcher, in-memory cache) is split up so that we avoid flushing bad state to disk if startup is interrupted. We also introduce `CChainState::CanFlushToDisk()` which tells us when it is safe to flush the chainstate based on this partial construction. This commit could be broken into smaller pieces, but it would require more ephemeral diffs to, e.g., temporarily change CCoinsViewDB's constructor invocations. Other changes: - A parameter has been added to the CCoinsViewDB constructor that allows the name of the corresponding leveldb directory to be specified. Thanks to Russell Yanofsky and Marco Falke for helpful feedback.
1 parent fae6ab6 commit 5693530

File tree

7 files changed

+170
-43
lines changed

7 files changed

+170
-43
lines changed

src/init.cpp

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
#include <blockfilter.h>
1616
#include <chain.h>
1717
#include <chainparams.h>
18-
#include <coins.h>
1918
#include <compat/sanity.h>
2019
#include <consensus/validation.h>
2120
#include <fs.h>
@@ -149,7 +148,6 @@ NODISCARD static bool CreatePidFile()
149148
// shutdown thing.
150149
//
151150

152-
static std::unique_ptr<CCoinsViewErrorCatcher> pcoinscatcher;
153151
static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle;
154152

155153
static boost::thread_group threadGroup;
@@ -234,8 +232,11 @@ void Shutdown(InitInterfaces& interfaces)
234232
}
235233

236234
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
237-
if (pcoinsTip != nullptr) {
238-
::ChainstateActive().ForceFlushStateToDisk();
235+
//
236+
// g_chainstate is referenced here directly (instead of ::ChainstateActive()) because it
237+
// may not have been initialized yet.
238+
if (g_chainstate && g_chainstate->CanFlushToDisk()) {
239+
g_chainstate->ForceFlushStateToDisk();
239240
}
240241

241242
// After there are no more peers/RPC left to give us new data which may generate
@@ -250,12 +251,10 @@ void Shutdown(InitInterfaces& interfaces)
250251

251252
{
252253
LOCK(cs_main);
253-
if (pcoinsTip != nullptr) {
254-
::ChainstateActive().ForceFlushStateToDisk();
254+
if (g_chainstate && g_chainstate->CanFlushToDisk()) {
255+
g_chainstate->ForceFlushStateToDisk();
256+
g_chainstate->ResetCoinsViews();
255257
}
256-
pcoinsTip.reset();
257-
pcoinscatcher.reset();
258-
pcoinsdbview.reset();
259258
pblocktree.reset();
260259
}
261260
for (const auto& client : interfaces.chain_clients) {
@@ -1466,10 +1465,10 @@ bool AppInitMain(InitInterfaces& interfaces)
14661465
bool is_coinsview_empty;
14671466
try {
14681467
LOCK(cs_main);
1468+
// This statement makes ::ChainstateActive() usable.
1469+
g_chainstate = MakeUnique<CChainState>();
14691470
UnloadBlockIndex();
1470-
pcoinsTip.reset();
1471-
pcoinsdbview.reset();
1472-
pcoinscatcher.reset();
1471+
14731472
// new CBlockTreeDB tries to delete the existing file, which
14741473
// fails if it's still open from the previous loop. Close it first:
14751474
pblocktree.reset();
@@ -1520,33 +1519,38 @@ bool AppInitMain(InitInterfaces& interfaces)
15201519
// At this point we're either in reindex or we've loaded a useful
15211520
// block tree into BlockIndex()!
15221521

1523-
pcoinsdbview.reset(new CCoinsViewDB(nCoinDBCache, false, fReset || fReindexChainState));
1524-
pcoinscatcher.reset(new CCoinsViewErrorCatcher(pcoinsdbview.get()));
1525-
pcoinscatcher->AddReadErrCallback([]() {
1522+
::ChainstateActive().InitCoinsDB(
1523+
/* cache_size_bytes */ nCoinDBCache,
1524+
/* in_memory */ false,
1525+
/* should_wipe */ fReset || fReindexChainState);
1526+
1527+
::ChainstateActive().CoinsErrorCatcher().AddReadErrCallback([]() {
15261528
uiInterface.ThreadSafeMessageBox(
15271529
_("Error reading from database, shutting down.").translated,
15281530
"", CClientUIInterface::MSG_ERROR);
15291531
});
15301532

15311533
// If necessary, upgrade from older database format.
15321534
// This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
1533-
if (!pcoinsdbview->Upgrade()) {
1535+
if (!::ChainstateActive().CoinsDB().Upgrade()) {
15341536
strLoadError = _("Error upgrading chainstate database").translated;
15351537
break;
15361538
}
15371539

15381540
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
1539-
if (!ReplayBlocks(chainparams, pcoinsdbview.get())) {
1541+
if (!ReplayBlocks(chainparams, &::ChainstateActive().CoinsDB())) {
15401542
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.").translated;
15411543
break;
15421544
}
15431545

15441546
// The on-disk coinsdb is now in a good state, create the cache
1545-
pcoinsTip.reset(new CCoinsViewCache(pcoinscatcher.get()));
1547+
::ChainstateActive().InitCoinsCache();
1548+
assert(::ChainstateActive().CanFlushToDisk());
15461549

1547-
is_coinsview_empty = fReset || fReindexChainState || pcoinsTip->GetBestBlock().IsNull();
1550+
is_coinsview_empty = fReset || fReindexChainState ||
1551+
::ChainstateActive().CoinsTip().GetBestBlock().IsNull();
15481552
if (!is_coinsview_empty) {
1549-
// LoadChainTip sets ::ChainActive() based on pcoinsTip's best block
1553+
// LoadChainTip sets ::ChainActive() based on CoinsTip()'s best block
15501554
if (!LoadChainTip(chainparams)) {
15511555
strLoadError = _("Error initializing block database").translated;
15521556
break;
@@ -1588,7 +1592,7 @@ bool AppInitMain(InitInterfaces& interfaces)
15881592
break;
15891593
}
15901594

1591-
if (!CVerifyDB().VerifyDB(chainparams, pcoinsdbview.get(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
1595+
if (!CVerifyDB().VerifyDB(chainparams, &::ChainstateActive().CoinsDB(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
15921596
gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) {
15931597
strLoadError = _("Corrupted block database detected").translated;
15941598
break;

src/rpc/blockchain.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,7 +1062,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request)
10621062

10631063
CCoinsStats stats;
10641064
::ChainstateActive().ForceFlushStateToDisk();
1065-
if (GetUTXOStats(pcoinsdbview.get(), stats)) {
1065+
if (GetUTXOStats(&::ChainstateActive().CoinsDB(), stats)) {
10661066
ret.pushKV("height", (int64_t)stats.nHeight);
10671067
ret.pushKV("bestblock", stats.hashBlock.GetHex());
10681068
ret.pushKV("transactions", (int64_t)stats.nTransactions);
@@ -2206,7 +2206,7 @@ UniValue scantxoutset(const JSONRPCRequest& request)
22062206
{
22072207
LOCK(cs_main);
22082208
::ChainstateActive().ForceFlushStateToDisk();
2209-
pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor());
2209+
pcursor = std::unique_ptr<CCoinsViewCursor>(::ChainstateActive().CoinsDB().Cursor());
22102210
assert(pcursor);
22112211
}
22122212
bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins);

src/test/setup_common.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,12 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha
8585

8686
mempool.setSanityCheck(1.0);
8787
pblocktree.reset(new CBlockTreeDB(1 << 20, true));
88-
pcoinsdbview.reset(new CCoinsViewDB(1 << 23, true));
89-
pcoinsTip.reset(new CCoinsViewCache(pcoinsdbview.get()));
88+
g_chainstate = MakeUnique<CChainState>();
89+
::ChainstateActive().InitCoinsDB(
90+
/* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false);
91+
assert(!::ChainstateActive().CanFlushToDisk());
92+
::ChainstateActive().InitCoinsCache();
93+
assert(::ChainstateActive().CanFlushToDisk());
9094
if (!LoadGenesisBlock(chainparams)) {
9195
throw std::runtime_error("LoadGenesisBlock failed.");
9296
}
@@ -113,8 +117,7 @@ TestingSetup::~TestingSetup()
113117
g_connman.reset();
114118
g_banman.reset();
115119
UnloadBlockIndex();
116-
pcoinsTip.reset();
117-
pcoinsdbview.reset();
120+
g_chainstate.reset();
118121
pblocktree.reset();
119122
}
120123

src/txdb.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ struct CoinEntry {
5252

5353
}
5454

55-
CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe, true)
55+
CCoinsViewDB::CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, bool fWipe) : db(ldb_path, nCacheSize, fMemory, fWipe, true)
5656
{
5757
}
5858

src/txdb.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ class CCoinsViewDB final : public CCoinsView
4848
protected:
4949
CDBWrapper db;
5050
public:
51-
explicit CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false);
51+
/**
52+
* @param[in] ldb_path Location in the filesystem where leveldb data will be stored.
53+
*/
54+
explicit CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, bool fWipe);
5255

5356
bool GetCoin(const COutPoint &outpoint, Coin &coin) const override;
5457
bool HaveCoin(const COutPoint &outpoint) const override;

src/validation.cpp

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,17 @@ namespace {
8282
BlockManager g_blockman;
8383
} // anon namespace
8484

85-
static CChainState g_chainstate(g_blockman);
85+
std::unique_ptr<CChainState> g_chainstate;
8686

87-
CChainState& ChainstateActive() { return g_chainstate; }
87+
CChainState& ChainstateActive() {
88+
assert(g_chainstate);
89+
return *g_chainstate;
90+
}
8891

89-
CChain& ChainActive() { return g_chainstate.m_chain; }
92+
CChain& ChainActive() {
93+
assert(g_chainstate);
94+
return g_chainstate->m_chain;
95+
}
9096

9197
/**
9298
* Mutex to guard access to validation specific variables, such as reading
@@ -173,8 +179,6 @@ CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& loc
173179
return chain.Genesis();
174180
}
175181

176-
std::unique_ptr<CCoinsViewDB> pcoinsdbview;
177-
std::unique_ptr<CCoinsViewCache> pcoinsTip;
178182
std::unique_ptr<CBlockTreeDB> pblocktree;
179183

180184
// See definition for documentation
@@ -525,7 +529,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
525529
}
526530

527531
// Note: this call may add txin.prevout to the coins cache
528-
// (pcoinsTip.cacheCoins) by way of FetchCoin(). It should be removed
532+
// (CoinsTip().cacheCoins) by way of FetchCoin(). It should be removed
529533
// later (via coins_to_uncache) if this tx turns out to be invalid.
530534
if (!view.HaveCoin(txin.prevout)) {
531535
// Are inputs missing because we already have the tx?
@@ -1041,6 +1045,40 @@ CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams)
10411045
return nSubsidy;
10421046
}
10431047

1048+
CoinsViews::CoinsViews(
1049+
std::string ldb_name,
1050+
size_t cache_size_bytes,
1051+
bool in_memory,
1052+
bool should_wipe) : m_dbview(
1053+
GetDataDir() / ldb_name, cache_size_bytes, in_memory, should_wipe),
1054+
m_catcherview(&m_dbview) {}
1055+
1056+
void CoinsViews::InitCache()
1057+
{
1058+
m_cacheview = MakeUnique<CCoinsViewCache>(&m_catcherview);
1059+
}
1060+
1061+
// NOTE: for now m_blockman is set to a global, but this will be changed
1062+
// in a future commit.
1063+
CChainState::CChainState() : m_blockman(g_blockman) {}
1064+
1065+
1066+
void CChainState::InitCoinsDB(
1067+
size_t cache_size_bytes,
1068+
bool in_memory,
1069+
bool should_wipe,
1070+
std::string leveldb_name)
1071+
{
1072+
m_coins_views = MakeUnique<CoinsViews>(
1073+
leveldb_name, cache_size_bytes, in_memory, should_wipe);
1074+
}
1075+
1076+
void CChainState::InitCoinsCache()
1077+
{
1078+
assert(m_coins_views != nullptr);
1079+
m_coins_views->InitCache();
1080+
}
1081+
10441082
// Note that though this is marked const, we may end up modifying `m_cached_finished_ibd`, which
10451083
// is a performance-related implementation detail. This function must be marked
10461084
// `const` so that `CValidationInterface` clients (which are given a `const CChainState*`)
@@ -1982,6 +2020,7 @@ bool CChainState::FlushStateToDisk(
19822020
{
19832021
int64_t nMempoolUsage = mempool.DynamicMemoryUsage();
19842022
LOCK(cs_main);
2023+
assert(this->CanFlushToDisk());
19852024
static int64_t nLastWrite = 0;
19862025
static int64_t nLastFlush = 0;
19872026
std::set<int> setFilesToPrune;

0 commit comments

Comments
 (0)