1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2009-2018 The Bitcoin Core developers
3 // Distributed under the MIT software license, see the accompanying
4 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 
6 #ifndef BITCOIN_WALLET_DB_H
7 #define BITCOIN_WALLET_DB_H
8 
9 #include <clientversion.h>
10 #include <fs.h>
11 #include <serialize.h>
12 #include <streams.h>
13 #include <sync.h>
14 #include <util/system.h>
15 #include <version.h>
16 
17 #include <atomic>
18 #include <map>
19 #include <memory>
20 #include <string>
21 #include <unordered_map>
22 #include <vector>
23 
24 #include <db_cxx.h>
25 
26 static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
27 static const bool DEFAULT_WALLET_PRIVDB = true;
28 
29 struct WalletDatabaseFileId {
30     u_int8_t value[DB_FILE_ID_LEN];
31     bool operator==(const WalletDatabaseFileId& rhs) const;
32 };
33 
34 class BerkeleyDatabase;
35 
36 class BerkeleyEnvironment
37 {
38 private:
39     bool fDbEnvInit;
40     bool fMockDb;
41     // Don't change into fs::path, as that can result in
42     // shutdown problems/crashes caused by a static initialized internal pointer.
43     std::string strPath;
44 
45 public:
46     std::unique_ptr<DbEnv> dbenv;
47     std::map<std::string, int> mapFileUseCount;
48     std::map<std::string, std::reference_wrapper<BerkeleyDatabase>> m_databases;
49     std::unordered_map<std::string, WalletDatabaseFileId> m_fileids;
50     std::condition_variable_any m_db_in_use;
51 
52     BerkeleyEnvironment(const fs::path& env_directory);
53     BerkeleyEnvironment();
54     ~BerkeleyEnvironment();
55     void Reset();
56 
IsMock()57     bool IsMock() const { return fMockDb; }
IsInitialized()58     bool IsInitialized() const { return fDbEnvInit; }
IsDatabaseLoaded(const std::string & db_filename)59     bool IsDatabaseLoaded(const std::string& db_filename) const { return m_databases.find(db_filename) != m_databases.end(); }
Directory()60     fs::path Directory() const { return strPath; }
61 
62     /**
63      * Verify that database file strFile is OK. If it is not,
64      * call the callback to try to recover.
65      * This must be called BEFORE strFile is opened.
66      * Returns true if strFile is OK.
67      */
68     enum class VerifyResult { VERIFY_OK,
69                         RECOVER_OK,
70                         RECOVER_FAIL };
71     typedef bool (*recoverFunc_type)(const fs::path& file_path, std::string& out_backup_filename);
72     VerifyResult Verify(const std::string& strFile, recoverFunc_type recoverFunc, std::string& out_backup_filename);
73     /**
74      * Salvage data from a file that Verify says is bad.
75      * fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method documentation).
76      * Appends binary key/value pairs to vResult, returns true if successful.
77      * NOTE: reads the entire database into memory, so cannot be used
78      * for huge databases.
79      */
80     typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
81     bool Salvage(const std::string& strFile, bool fAggressive, std::vector<KeyValPair>& vResult);
82 
83     bool Open(bool retry);
84     void Close();
85     void Flush(bool fShutdown);
86     void CheckpointLSN(const std::string& strFile);
87 
88     void CloseDb(const std::string& strFile);
89     void ReloadDbEnv();
90 
91     DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC)
92     {
93         DbTxn* ptxn = nullptr;
94         int ret = dbenv->txn_begin(nullptr, &ptxn, flags);
95         if (!ptxn || ret != 0)
96             return nullptr;
97         return ptxn;
98     }
99 };
100 
101 /** Return whether a wallet database is currently loaded. */
102 bool IsWalletLoaded(const fs::path& wallet_path);
103 
104 /** Get BerkeleyEnvironment and database filename given a wallet path. */
105 std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
106 
107 /** An instance of this class represents one database.
108  * For BerkeleyDB this is just a (env, strFile) tuple.
109  **/
110 class BerkeleyDatabase
111 {
112     friend class BerkeleyBatch;
113 public:
114     /** Create dummy DB handle */
BerkeleyDatabase()115     BerkeleyDatabase() : nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(nullptr)
116     {
117     }
118 
119     /** Create DB handle to real database */
BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env,std::string filename)120     BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, std::string filename) :
121         nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(std::move(env)), strFile(std::move(filename))
122     {
123         auto inserted = this->env->m_databases.emplace(strFile, std::ref(*this));
124         assert(inserted.second);
125     }
126 
~BerkeleyDatabase()127     ~BerkeleyDatabase() {
128         if (env) {
129             size_t erased = env->m_databases.erase(strFile);
130             assert(erased == 1);
131         }
132     }
133 
134     /** Return object for accessing database at specified path. */
Create(const fs::path & path)135     static std::unique_ptr<BerkeleyDatabase> Create(const fs::path& path)
136     {
137         std::string filename;
138         return MakeUnique<BerkeleyDatabase>(GetWalletEnv(path, filename), std::move(filename));
139     }
140 
141     /** Return object for accessing dummy database with no read/write capabilities. */
CreateDummy()142     static std::unique_ptr<BerkeleyDatabase> CreateDummy()
143     {
144         return MakeUnique<BerkeleyDatabase>();
145     }
146 
147     /** Return object for accessing temporary in-memory database. */
CreateMock()148     static std::unique_ptr<BerkeleyDatabase> CreateMock()
149     {
150         return MakeUnique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "");
151     }
152 
153     /** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero
154      */
155     bool Rewrite(const char* pszSkip=nullptr);
156 
157     /** Back up the entire database to a file.
158      */
159     bool Backup(const std::string& strDest);
160 
161     /** Make sure all changes are flushed to disk.
162      */
163     void Flush(bool shutdown);
164 
165     void IncrementUpdateCounter();
166 
167     void ReloadDbEnv();
168 
169     std::atomic<unsigned int> nUpdateCounter;
170     unsigned int nLastSeen;
171     unsigned int nLastFlushed;
172     int64_t nLastWalletUpdate;
173 
174     /**
175      * Pointer to shared database environment.
176      *
177      * Normally there is only one BerkeleyDatabase object per
178      * BerkeleyEnvivonment, but in the special, backwards compatible case where
179      * multiple wallet BDB data files are loaded from the same directory, this
180      * will point to a shared instance that gets freed when the last data file
181      * is closed.
182      */
183     std::shared_ptr<BerkeleyEnvironment> env;
184 
185     /** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */
186     std::unique_ptr<Db> m_db;
187 
188 private:
189     std::string strFile;
190 
191     /** Return whether this database handle is a dummy for testing.
192      * Only to be used at a low level, application should ideally not care
193      * about this.
194      */
IsDummy()195     bool IsDummy() { return env == nullptr; }
196 };
197 
198 /** RAII class that provides access to a Berkeley database */
199 class BerkeleyBatch
200 {
201     /** RAII class that automatically cleanses its data on destruction */
202     class SafeDbt final
203     {
204         Dbt m_dbt;
205 
206     public:
207         // construct Dbt with internally-managed data
208         SafeDbt();
209         // construct Dbt with provided data
210         SafeDbt(void* data, size_t size);
211         ~SafeDbt();
212 
213         // delegate to Dbt
214         const void* get_data() const;
215         u_int32_t get_size() const;
216 
217         // conversion operator to access the underlying Dbt
218         operator Dbt*();
219     };
220 
221 protected:
222     Db* pdb;
223     std::string strFile;
224     DbTxn* activeTxn;
225     bool fReadOnly;
226     bool fFlushOnClose;
227     BerkeleyEnvironment *env;
228 
229 public:
230     explicit BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode = "r+", bool fFlushOnCloseIn=true);
~BerkeleyBatch()231     ~BerkeleyBatch() { Close(); }
232 
233     BerkeleyBatch(const BerkeleyBatch&) = delete;
234     BerkeleyBatch& operator=(const BerkeleyBatch&) = delete;
235 
236     void Flush();
237     void Close();
238     static bool Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename);
239 
240     /* flush the wallet passively (TRY_LOCK)
241        ideal to be called periodically */
242     static bool PeriodicFlush(BerkeleyDatabase& database);
243     /* verifies the database environment */
244     static bool VerifyEnvironment(const fs::path& file_path, std::string& errorStr);
245     /* verifies the database file */
246     static bool VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc);
247 
248     template <typename K, typename T>
Read(const K & key,T & value)249     bool Read(const K& key, T& value)
250     {
251         if (!pdb)
252             return false;
253 
254         // Key
255         CDataStream ssKey(SER_DISK, CLIENT_VERSION);
256         ssKey.reserve(1000);
257         ssKey << key;
258         SafeDbt datKey(ssKey.data(), ssKey.size());
259 
260         // Read
261         SafeDbt datValue;
262         int ret = pdb->get(activeTxn, datKey, datValue, 0);
263         bool success = false;
264         if (datValue.get_data() != nullptr) {
265             // Unserialize value
266             try {
267                 CDataStream ssValue((char*)datValue.get_data(), (char*)datValue.get_data() + datValue.get_size(), SER_DISK, CLIENT_VERSION);
268                 ssValue >> value;
269                 success = true;
270             } catch (const std::exception&) {
271                 // In this case success remains 'false'
272             }
273         }
274         return ret == 0 && success;
275     }
276 
277     template <typename K, typename T>
278     bool Write(const K& key, const T& value, bool fOverwrite = true)
279     {
280         if (!pdb)
281             return true;
282         if (fReadOnly)
283             assert(!"Write called on database in read-only mode");
284 
285         // Key
286         CDataStream ssKey(SER_DISK, CLIENT_VERSION);
287         ssKey.reserve(1000);
288         ssKey << key;
289         SafeDbt datKey(ssKey.data(), ssKey.size());
290 
291         // Value
292         CDataStream ssValue(SER_DISK, CLIENT_VERSION);
293         ssValue.reserve(10000);
294         ssValue << value;
295         SafeDbt datValue(ssValue.data(), ssValue.size());
296 
297         // Write
298         int ret = pdb->put(activeTxn, datKey, datValue, (fOverwrite ? 0 : DB_NOOVERWRITE));
299         return (ret == 0);
300     }
301 
302     template <typename K>
Erase(const K & key)303     bool Erase(const K& key)
304     {
305         if (!pdb)
306             return false;
307         if (fReadOnly)
308             assert(!"Erase called on database in read-only mode");
309 
310         // Key
311         CDataStream ssKey(SER_DISK, CLIENT_VERSION);
312         ssKey.reserve(1000);
313         ssKey << key;
314         SafeDbt datKey(ssKey.data(), ssKey.size());
315 
316         // Erase
317         int ret = pdb->del(activeTxn, datKey, 0);
318         return (ret == 0 || ret == DB_NOTFOUND);
319     }
320 
321     template <typename K>
Exists(const K & key)322     bool Exists(const K& key)
323     {
324         if (!pdb)
325             return false;
326 
327         // Key
328         CDataStream ssKey(SER_DISK, CLIENT_VERSION);
329         ssKey.reserve(1000);
330         ssKey << key;
331         SafeDbt datKey(ssKey.data(), ssKey.size());
332 
333         // Exists
334         int ret = pdb->exists(activeTxn, datKey, 0);
335         return (ret == 0);
336     }
337 
GetCursor()338     Dbc* GetCursor()
339     {
340         if (!pdb)
341             return nullptr;
342         Dbc* pcursor = nullptr;
343         int ret = pdb->cursor(nullptr, &pcursor, 0);
344         if (ret != 0)
345             return nullptr;
346         return pcursor;
347     }
348 
ReadAtCursor(Dbc * pcursor,CDataStream & ssKey,CDataStream & ssValue)349     int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue)
350     {
351         // Read at cursor
352         SafeDbt datKey;
353         SafeDbt datValue;
354         int ret = pcursor->get(datKey, datValue, DB_NEXT);
355         if (ret != 0)
356             return ret;
357         else if (datKey.get_data() == nullptr || datValue.get_data() == nullptr)
358             return 99999;
359 
360         // Convert to streams
361         ssKey.SetType(SER_DISK);
362         ssKey.clear();
363         ssKey.write((char*)datKey.get_data(), datKey.get_size());
364         ssValue.SetType(SER_DISK);
365         ssValue.clear();
366         ssValue.write((char*)datValue.get_data(), datValue.get_size());
367         return 0;
368     }
369 
TxnBegin()370     bool TxnBegin()
371     {
372         if (!pdb || activeTxn)
373             return false;
374         DbTxn* ptxn = env->TxnBegin();
375         if (!ptxn)
376             return false;
377         activeTxn = ptxn;
378         return true;
379     }
380 
TxnCommit()381     bool TxnCommit()
382     {
383         if (!pdb || !activeTxn)
384             return false;
385         int ret = activeTxn->commit(0);
386         activeTxn = nullptr;
387         return (ret == 0);
388     }
389 
TxnAbort()390     bool TxnAbort()
391     {
392         if (!pdb || !activeTxn)
393             return false;
394         int ret = activeTxn->abort();
395         activeTxn = nullptr;
396         return (ret == 0);
397     }
398 
ReadVersion(int & nVersion)399     bool ReadVersion(int& nVersion)
400     {
401         nVersion = 0;
402         return Read(std::string("version"), nVersion);
403     }
404 
WriteVersion(int nVersion)405     bool WriteVersion(int nVersion)
406     {
407         return Write(std::string("version"), nVersion);
408     }
409 
410     bool static Rewrite(BerkeleyDatabase& database, const char* pszSkip = nullptr);
411 };
412 
413 #endif // BITCOIN_WALLET_DB_H
414