diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/init.cpp | 46 | ||||
-rw-r--r-- | src/rpc/blockchain.cpp | 4 | ||||
-rw-r--r-- | src/test/setup_common.cpp | 11 | ||||
-rw-r--r-- | src/txdb.cpp | 2 | ||||
-rw-r--r-- | src/txdb.h | 5 | ||||
-rw-r--r-- | src/validation.cpp | 51 | ||||
-rw-r--r-- | src/validation.h | 94 |
7 files changed, 170 insertions, 43 deletions
diff --git a/src/init.cpp b/src/init.cpp index bb3ff8d88f..50ae0c6252 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -15,7 +15,6 @@ #include <blockfilter.h> #include <chain.h> #include <chainparams.h> -#include <coins.h> #include <compat/sanity.h> #include <consensus/validation.h> #include <fs.h> @@ -149,7 +148,6 @@ NODISCARD static bool CreatePidFile() // shutdown thing. // -static std::unique_ptr<CCoinsViewErrorCatcher> pcoinscatcher; static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle; static boost::thread_group threadGroup; @@ -234,8 +232,11 @@ void Shutdown(InitInterfaces& interfaces) } // FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing - if (pcoinsTip != nullptr) { - ::ChainstateActive().ForceFlushStateToDisk(); + // + // g_chainstate is referenced here directly (instead of ::ChainstateActive()) because it + // may not have been initialized yet. + if (g_chainstate && g_chainstate->CanFlushToDisk()) { + g_chainstate->ForceFlushStateToDisk(); } // After there are no more peers/RPC left to give us new data which may generate @@ -250,12 +251,10 @@ void Shutdown(InitInterfaces& interfaces) { LOCK(cs_main); - if (pcoinsTip != nullptr) { - ::ChainstateActive().ForceFlushStateToDisk(); + if (g_chainstate && g_chainstate->CanFlushToDisk()) { + g_chainstate->ForceFlushStateToDisk(); + g_chainstate->ResetCoinsViews(); } - pcoinsTip.reset(); - pcoinscatcher.reset(); - pcoinsdbview.reset(); pblocktree.reset(); } for (const auto& client : interfaces.chain_clients) { @@ -1466,10 +1465,10 @@ bool AppInitMain(InitInterfaces& interfaces) bool is_coinsview_empty; try { LOCK(cs_main); + // This statement makes ::ChainstateActive() usable. + g_chainstate = MakeUnique<CChainState>(); UnloadBlockIndex(); - pcoinsTip.reset(); - pcoinsdbview.reset(); - pcoinscatcher.reset(); + // new CBlockTreeDB tries to delete the existing file, which // fails if it's still open from the previous loop. Close it first: pblocktree.reset(); @@ -1520,9 +1519,12 @@ bool AppInitMain(InitInterfaces& interfaces) // At this point we're either in reindex or we've loaded a useful // block tree into BlockIndex()! - pcoinsdbview.reset(new CCoinsViewDB(nCoinDBCache, false, fReset || fReindexChainState)); - pcoinscatcher.reset(new CCoinsViewErrorCatcher(pcoinsdbview.get())); - pcoinscatcher->AddReadErrCallback([]() { + ::ChainstateActive().InitCoinsDB( + /* cache_size_bytes */ nCoinDBCache, + /* in_memory */ false, + /* should_wipe */ fReset || fReindexChainState); + + ::ChainstateActive().CoinsErrorCatcher().AddReadErrCallback([]() { uiInterface.ThreadSafeMessageBox( _("Error reading from database, shutting down.").translated, "", CClientUIInterface::MSG_ERROR); @@ -1530,23 +1532,25 @@ bool AppInitMain(InitInterfaces& interfaces) // If necessary, upgrade from older database format. // This is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate - if (!pcoinsdbview->Upgrade()) { + if (!::ChainstateActive().CoinsDB().Upgrade()) { strLoadError = _("Error upgrading chainstate database").translated; break; } // ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate - if (!ReplayBlocks(chainparams, pcoinsdbview.get())) { + if (!ReplayBlocks(chainparams, &::ChainstateActive().CoinsDB())) { strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.").translated; break; } // The on-disk coinsdb is now in a good state, create the cache - pcoinsTip.reset(new CCoinsViewCache(pcoinscatcher.get())); + ::ChainstateActive().InitCoinsCache(); + assert(::ChainstateActive().CanFlushToDisk()); - is_coinsview_empty = fReset || fReindexChainState || pcoinsTip->GetBestBlock().IsNull(); + is_coinsview_empty = fReset || fReindexChainState || + ::ChainstateActive().CoinsTip().GetBestBlock().IsNull(); if (!is_coinsview_empty) { - // LoadChainTip sets ::ChainActive() based on pcoinsTip's best block + // LoadChainTip sets ::ChainActive() based on CoinsTip()'s best block if (!LoadChainTip(chainparams)) { strLoadError = _("Error initializing block database").translated; break; @@ -1588,7 +1592,7 @@ bool AppInitMain(InitInterfaces& interfaces) break; } - if (!CVerifyDB().VerifyDB(chainparams, pcoinsdbview.get(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL), + if (!CVerifyDB().VerifyDB(chainparams, &::ChainstateActive().CoinsDB(), gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL), gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS))) { strLoadError = _("Corrupted block database detected").translated; break; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c35c478f33..8061939a26 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1062,7 +1062,7 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request) CCoinsStats stats; ::ChainstateActive().ForceFlushStateToDisk(); - if (GetUTXOStats(pcoinsdbview.get(), stats)) { + if (GetUTXOStats(&::ChainstateActive().CoinsDB(), stats)) { ret.pushKV("height", (int64_t)stats.nHeight); ret.pushKV("bestblock", stats.hashBlock.GetHex()); ret.pushKV("transactions", (int64_t)stats.nTransactions); @@ -2206,7 +2206,7 @@ UniValue scantxoutset(const JSONRPCRequest& request) { LOCK(cs_main); ::ChainstateActive().ForceFlushStateToDisk(); - pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor()); + pcursor = std::unique_ptr<CCoinsViewCursor>(::ChainstateActive().CoinsDB().Cursor()); assert(pcursor); } bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins); diff --git a/src/test/setup_common.cpp b/src/test/setup_common.cpp index de877fd167..0ff081e472 100644 --- a/src/test/setup_common.cpp +++ b/src/test/setup_common.cpp @@ -85,8 +85,12 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha mempool.setSanityCheck(1.0); pblocktree.reset(new CBlockTreeDB(1 << 20, true)); - pcoinsdbview.reset(new CCoinsViewDB(1 << 23, true)); - pcoinsTip.reset(new CCoinsViewCache(pcoinsdbview.get())); + g_chainstate = MakeUnique<CChainState>(); + ::ChainstateActive().InitCoinsDB( + /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); + assert(!::ChainstateActive().CanFlushToDisk()); + ::ChainstateActive().InitCoinsCache(); + assert(::ChainstateActive().CanFlushToDisk()); if (!LoadGenesisBlock(chainparams)) { throw std::runtime_error("LoadGenesisBlock failed."); } @@ -113,8 +117,7 @@ TestingSetup::~TestingSetup() g_connman.reset(); g_banman.reset(); UnloadBlockIndex(); - pcoinsTip.reset(); - pcoinsdbview.reset(); + g_chainstate.reset(); pblocktree.reset(); } diff --git a/src/txdb.cpp b/src/txdb.cpp index df9851396e..18be07e6db 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -52,7 +52,7 @@ struct CoinEntry { } -CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe, true) +CCoinsViewDB::CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, bool fWipe) : db(ldb_path, nCacheSize, fMemory, fWipe, true) { } diff --git a/src/txdb.h b/src/txdb.h index c4ece11503..140ce2c7ff 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -48,7 +48,10 @@ class CCoinsViewDB final : public CCoinsView protected: CDBWrapper db; public: - explicit CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); + /** + * @param[in] ldb_path Location in the filesystem where leveldb data will be stored. + */ + explicit CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, bool fWipe); bool GetCoin(const COutPoint &outpoint, Coin &coin) const override; bool HaveCoin(const COutPoint &outpoint) const override; diff --git a/src/validation.cpp b/src/validation.cpp index f9d38a3099..0f24321aaf 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -82,11 +82,17 @@ namespace { BlockManager g_blockman; } // anon namespace -static CChainState g_chainstate(g_blockman); +std::unique_ptr<CChainState> g_chainstate; -CChainState& ChainstateActive() { return g_chainstate; } +CChainState& ChainstateActive() { + assert(g_chainstate); + return *g_chainstate; +} -CChain& ChainActive() { return g_chainstate.m_chain; } +CChain& ChainActive() { + assert(g_chainstate); + return g_chainstate->m_chain; +} /** * Mutex to guard access to validation specific variables, such as reading @@ -173,8 +179,6 @@ CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& loc return chain.Genesis(); } -std::unique_ptr<CCoinsViewDB> pcoinsdbview; -std::unique_ptr<CCoinsViewCache> pcoinsTip; std::unique_ptr<CBlockTreeDB> pblocktree; // See definition for documentation @@ -525,7 +529,7 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool } // Note: this call may add txin.prevout to the coins cache - // (pcoinsTip.cacheCoins) by way of FetchCoin(). It should be removed + // (CoinsTip().cacheCoins) by way of FetchCoin(). It should be removed // later (via coins_to_uncache) if this tx turns out to be invalid. if (!view.HaveCoin(txin.prevout)) { // Are inputs missing because we already have the tx? @@ -1041,6 +1045,40 @@ CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams) return nSubsidy; } +CoinsViews::CoinsViews( + std::string ldb_name, + size_t cache_size_bytes, + bool in_memory, + bool should_wipe) : m_dbview( + GetDataDir() / ldb_name, cache_size_bytes, in_memory, should_wipe), + m_catcherview(&m_dbview) {} + +void CoinsViews::InitCache() +{ + m_cacheview = MakeUnique<CCoinsViewCache>(&m_catcherview); +} + +// NOTE: for now m_blockman is set to a global, but this will be changed +// in a future commit. +CChainState::CChainState() : m_blockman(g_blockman) {} + + +void CChainState::InitCoinsDB( + size_t cache_size_bytes, + bool in_memory, + bool should_wipe, + std::string leveldb_name) +{ + m_coins_views = MakeUnique<CoinsViews>( + leveldb_name, cache_size_bytes, in_memory, should_wipe); +} + +void CChainState::InitCoinsCache() +{ + assert(m_coins_views != nullptr); + m_coins_views->InitCache(); +} + // Note that though this is marked const, we may end up modifying `m_cached_finished_ibd`, which // is a performance-related implementation detail. This function must be marked // `const` so that `CValidationInterface` clients (which are given a `const CChainState*`) @@ -1982,6 +2020,7 @@ bool CChainState::FlushStateToDisk( { int64_t nMempoolUsage = mempool.DynamicMemoryUsage(); LOCK(cs_main); + assert(this->CanFlushToDisk()); static int64_t nLastWrite = 0; static int64_t nLastFlush = 0; std::set<int> setFilesToPrune; diff --git a/src/validation.h b/src/validation.h index 6661369bf8..2a268d8cab 100644 --- a/src/validation.h +++ b/src/validation.h @@ -19,6 +19,7 @@ #include <script/script_error.h> #include <sync.h> #include <txmempool.h> // For CTxMemPool::cs +#include <txdb.h> #include <versionbits.h> #include <algorithm> @@ -37,7 +38,6 @@ class CBlockIndex; class CBlockTreeDB; class CBlockUndo; class CChainParams; -class CCoinsViewDB; class CInv; class CConnman; class CScriptCheck; @@ -505,8 +505,40 @@ public: CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); }; -/** Global variable that points to the active CCoinsView (protected by cs_main) */ -extern std::unique_ptr<CCoinsViewCache> pcoinsTip; +/** + * A convenience class for constructing the CCoinsView* hierarchy used + * to facilitate access to the UTXO set. + * + * This class consists of an arrangement of layered CCoinsView objects, + * preferring to store and retrieve coins in memory via `m_cacheview` but + * ultimately falling back on cache misses to the canonical store of UTXOs on + * disk, `m_dbview`. + */ +class CoinsViews { + +public: + //! The lowest level of the CoinsViews cache hierarchy sits in a leveldb database on disk. + //! All unspent coins reside in this store. + CCoinsViewDB m_dbview; + + //! This view wraps access to the leveldb instance and handles read errors gracefully. + CCoinsViewErrorCatcher m_catcherview; + + //! This is the top layer of the cache hierarchy - it keeps as many coins in memory as + //! can fit per the dbcache setting. + std::unique_ptr<CCoinsViewCache> m_cacheview; + + //! This constructor initializes CCoinsViewDB and CCoinsViewErrorCatcher instances, but it + //! *does not* create a CCoinsViewCache instance by default. This is done separately because the + //! presence of the cache has implications on whether or not we're allowed to flush the cache's + //! state to disk, which should not be done until the health of the database is verified. + //! + //! All arguments forwarded onto CCoinsViewDB. + CoinsViews(std::string ldb_name, size_t cache_size_bytes, bool in_memory, bool should_wipe); + + //! Initialize the CCoinsViewCache member. + void InitCache(); +}; /** * CChainState stores and provides an API to update our local knowledge of the @@ -556,12 +588,39 @@ private: //! easily as opposed to referencing a global. BlockManager& m_blockman; + //! Manages the UTXO set, which is a reflection of the contents of `m_chain`. + std::unique_ptr<CoinsViews> m_coins_views; + public: - CChainState(BlockManager& blockman) : m_blockman(blockman) { } + CChainState(BlockManager& blockman) : m_blockman(blockman) {} + CChainState(); + + /** + * Initialize the CoinsViews UTXO set database management data structures. The in-memory + * cache is initialized separately. + * + * All parameters forwarded to CoinsViews. + */ + void InitCoinsDB( + size_t cache_size_bytes, + bool in_memory, + bool should_wipe, + std::string leveldb_name = "chainstate"); + + //! Initialize the in-memory coins cache (to be done after the health of the on-disk database + //! is verified). + void InitCoinsCache(); + + //! @returns whether or not the CoinsViews object has been fully initialized and we can + //! safely flush this object to disk. + bool CanFlushToDisk() { + return m_coins_views && m_coins_views->m_cacheview; + } //! The current chain of blockheaders we consult and build on. //! @see CChain, CBlockIndex. CChain m_chain; + /** * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for itself and all ancestors) and * as good as our current tip or better. Entries may be failed, though, and pruning nodes may be @@ -572,9 +631,26 @@ public: //! @returns A reference to the in-memory cache of the UTXO set. CCoinsViewCache& CoinsTip() { - return *::pcoinsTip; + assert(m_coins_views->m_cacheview); + return *m_coins_views->m_cacheview.get(); + } + + //! @returns A reference to the on-disk UTXO set database. + CCoinsViewDB& CoinsDB() + { + return m_coins_views->m_dbview; } + //! @returns A reference to a wrapped view of the in-memory UTXO set that + //! handles disk read errors gracefully. + CCoinsViewErrorCatcher& CoinsErrorCatcher() + { + return m_coins_views->m_catcherview; + } + + //! Destructs all objects related to accessing the UTXO set. + void ResetCoinsViews() { m_coins_views.reset(); } + /** * Update the on-disk chain state. * The caches and indexes are flushed depending on the mode we're called with @@ -606,7 +682,7 @@ public: bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, const CChainParams& chainparams, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - // Block disconnection on our pcoinsTip: + // Apply the effects of a block disconnection on the UTXO set. bool DisconnectTip(CValidationState& state, const CChainParams& chainparams, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs); // Manual block validity manipulation: @@ -668,8 +744,10 @@ CChain& ChainActive(); /** @returns the global block index map. */ BlockMap& BlockIndex(); -/** Global variable that points to the coins database (protected by cs_main) */ -extern std::unique_ptr<CCoinsViewDB> pcoinsdbview; +// Most often ::ChainstateActive() should be used instead of this, but some code +// may not be able to assume that this has been initialized yet and so must use it +// directly, e.g. init.cpp. +extern std::unique_ptr<CChainState> g_chainstate; /** Global variable that points to the active block tree (protected by cs_main) */ extern std::unique_ptr<CBlockTreeDB> pblocktree; |