// Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2020 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLET_DB_H #define BITCOIN_WALLET_DB_H #include #include #include #include #include #include #include #include #include #include #include #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsuggest-override" #endif #include #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop #endif struct bilingual_str; static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100; static const bool DEFAULT_WALLET_PRIVDB = true; struct WalletDatabaseFileId { u_int8_t value[DB_FILE_ID_LEN]; bool operator==(const WalletDatabaseFileId& rhs) const; }; class BerkeleyDatabase; class BerkeleyEnvironment { private: bool fDbEnvInit; bool fMockDb; // Don't change into fs::path, as that can result in // shutdown problems/crashes caused by a static initialized internal pointer. std::string strPath; public: std::unique_ptr dbenv; std::map mapFileUseCount; std::map> m_databases; std::unordered_map m_fileids; std::condition_variable_any m_db_in_use; BerkeleyEnvironment(const fs::path& env_directory); BerkeleyEnvironment(); ~BerkeleyEnvironment(); void Reset(); bool IsMock() const { return fMockDb; } bool IsInitialized() const { return fDbEnvInit; } bool IsDatabaseLoaded(const std::string& db_filename) const { return m_databases.find(db_filename) != m_databases.end(); } fs::path Directory() const { return strPath; } bool Verify(const std::string& strFile); bool Open(bool retry); void Close(); void Flush(bool fShutdown); void CheckpointLSN(const std::string& strFile); void CloseDb(const std::string& strFile); void ReloadDbEnv(); DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC) { DbTxn* ptxn = nullptr; int ret = dbenv->txn_begin(nullptr, &ptxn, flags); if (!ptxn || ret != 0) return nullptr; return ptxn; } }; /** Return whether a wallet database is currently loaded. */ bool IsWalletLoaded(const fs::path& wallet_path); /** Given a wallet directory path or legacy file path, return path to main data file in the wallet database. */ fs::path WalletDataFilePath(const fs::path& wallet_path); void SplitWalletPath(const fs::path& wallet_path, fs::path& env_directory, std::string& database_filename); /** Get BerkeleyEnvironment and database filename given a wallet path. */ std::shared_ptr GetWalletEnv(const fs::path& wallet_path, std::string& database_filename); /** An instance of this class represents one database. * For BerkeleyDB this is just a (env, strFile) tuple. **/ class BerkeleyDatabase { friend class BerkeleyBatch; public: /** Create dummy DB handle */ BerkeleyDatabase() : nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(nullptr) { } /** Create DB handle to real database */ BerkeleyDatabase(std::shared_ptr env, std::string filename) : nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(std::move(env)), strFile(std::move(filename)) { auto inserted = this->env->m_databases.emplace(strFile, std::ref(*this)); assert(inserted.second); } ~BerkeleyDatabase() { if (env) { size_t erased = env->m_databases.erase(strFile); assert(erased == 1); } } /** Return object for accessing database at specified path. */ static std::unique_ptr Create(const fs::path& path) { std::string filename; return MakeUnique(GetWalletEnv(path, filename), std::move(filename)); } /** Return object for accessing dummy database with no read/write capabilities. */ static std::unique_ptr CreateDummy() { return MakeUnique(); } /** Return object for accessing temporary in-memory database. */ static std::unique_ptr CreateMock() { return MakeUnique(std::make_shared(), ""); } /** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero */ bool Rewrite(const char* pszSkip=nullptr); /** Back up the entire database to a file. */ bool Backup(const std::string& strDest) const; /** Make sure all changes are flushed to disk. */ void Flush(bool shutdown); void IncrementUpdateCounter(); void ReloadDbEnv(); std::atomic nUpdateCounter; unsigned int nLastSeen; unsigned int nLastFlushed; int64_t nLastWalletUpdate; /** * Pointer to shared database environment. * * Normally there is only one BerkeleyDatabase object per * BerkeleyEnvivonment, but in the special, backwards compatible case where * multiple wallet BDB data files are loaded from the same directory, this * will point to a shared instance that gets freed when the last data file * is closed. */ std::shared_ptr env; /** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */ std::unique_ptr m_db; private: std::string strFile; /** Return whether this database handle is a dummy for testing. * Only to be used at a low level, application should ideally not care * about this. */ bool IsDummy() const { return env == nullptr; } }; /** RAII class that provides access to a Berkeley database */ class BerkeleyBatch { /** RAII class that automatically cleanses its data on destruction */ class SafeDbt final { Dbt m_dbt; public: // construct Dbt with internally-managed data SafeDbt(); // construct Dbt with provided data SafeDbt(void* data, size_t size); ~SafeDbt(); // delegate to Dbt const void* get_data() const; u_int32_t get_size() const; // conversion operator to access the underlying Dbt operator Dbt*(); }; protected: Db* pdb; std::string strFile; DbTxn* activeTxn; bool fReadOnly; bool fFlushOnClose; BerkeleyEnvironment *env; public: explicit BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode = "r+", bool fFlushOnCloseIn=true); ~BerkeleyBatch() { Close(); } BerkeleyBatch(const BerkeleyBatch&) = delete; BerkeleyBatch& operator=(const BerkeleyBatch&) = delete; void Flush(); void Close(); /* flush the wallet passively (TRY_LOCK) ideal to be called periodically */ static bool PeriodicFlush(BerkeleyDatabase& database); /* verifies the database environment */ static bool VerifyEnvironment(const fs::path& file_path, bilingual_str& errorStr); /* verifies the database file */ static bool VerifyDatabaseFile(const fs::path& file_path, bilingual_str& errorStr); template bool Read(const K& key, T& value) { if (!pdb) return false; // Key CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; SafeDbt datKey(ssKey.data(), ssKey.size()); // Read SafeDbt datValue; int ret = pdb->get(activeTxn, datKey, datValue, 0); bool success = false; if (datValue.get_data() != nullptr) { // Unserialize value try { CDataStream ssValue((char*)datValue.get_data(), (char*)datValue.get_data() + datValue.get_size(), SER_DISK, CLIENT_VERSION); ssValue >> value; success = true; } catch (const std::exception&) { // In this case success remains 'false' } } return ret == 0 && success; } template bool Write(const K& key, const T& value, bool fOverwrite = true) { if (!pdb) return true; if (fReadOnly) assert(!"Write called on database in read-only mode"); // Key CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; SafeDbt datKey(ssKey.data(), ssKey.size()); // Value CDataStream ssValue(SER_DISK, CLIENT_VERSION); ssValue.reserve(10000); ssValue << value; SafeDbt datValue(ssValue.data(), ssValue.size()); // Write int ret = pdb->put(activeTxn, datKey, datValue, (fOverwrite ? 0 : DB_NOOVERWRITE)); return (ret == 0); } template bool Erase(const K& key) { if (!pdb) return false; if (fReadOnly) assert(!"Erase called on database in read-only mode"); // Key CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; SafeDbt datKey(ssKey.data(), ssKey.size()); // Erase int ret = pdb->del(activeTxn, datKey, 0); return (ret == 0 || ret == DB_NOTFOUND); } template bool Exists(const K& key) { if (!pdb) return false; // Key CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; SafeDbt datKey(ssKey.data(), ssKey.size()); // Exists int ret = pdb->exists(activeTxn, datKey, 0); return (ret == 0); } Dbc* GetCursor() { if (!pdb) return nullptr; Dbc* pcursor = nullptr; int ret = pdb->cursor(nullptr, &pcursor, 0); if (ret != 0) return nullptr; return pcursor; } int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue) { // Read at cursor SafeDbt datKey; SafeDbt datValue; int ret = pcursor->get(datKey, datValue, DB_NEXT); if (ret != 0) return ret; else if (datKey.get_data() == nullptr || datValue.get_data() == nullptr) return 99999; // Convert to streams ssKey.SetType(SER_DISK); ssKey.clear(); ssKey.write((char*)datKey.get_data(), datKey.get_size()); ssValue.SetType(SER_DISK); ssValue.clear(); ssValue.write((char*)datValue.get_data(), datValue.get_size()); return 0; } bool TxnBegin() { if (!pdb || activeTxn) return false; DbTxn* ptxn = env->TxnBegin(); if (!ptxn) return false; activeTxn = ptxn; return true; } bool TxnCommit() { if (!pdb || !activeTxn) return false; int ret = activeTxn->commit(0); activeTxn = nullptr; return (ret == 0); } bool TxnAbort() { if (!pdb || !activeTxn) return false; int ret = activeTxn->abort(); activeTxn = nullptr; return (ret == 0); } bool static Rewrite(BerkeleyDatabase& database, const char* pszSkip = nullptr); }; std::string BerkeleyDatabaseVersion(); #endif // BITCOIN_WALLET_DB_H