1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2009-2017 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.
12 #include <utilstrencodings.h>
13 #include <wallet/walletutil.h>
21 #include <boost/thread.hpp>
24 //! Make sure database has a unique fileid within the environment. If it
25 //! doesn't, throw an error. BDB caches do not work properly when more than one
26 //! open database has the same fileid (values written to one database may show
27 //! up in reads to other databases).
29 //! BerkeleyDB generates unique fileids by default
30 //! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html),
31 //! so bitcoin should never create different databases with the same fileid, but
32 //! this error can be triggered if users manually copy database files.
33 void CheckUniqueFileid(const CDBEnv
& env
, const std::string
& filename
, Db
& db
)
35 if (env
.IsMock()) return;
37 u_int8_t fileid
[DB_FILE_ID_LEN
];
38 int ret
= db
.get_mpf()->get_fileid(fileid
);
40 throw std::runtime_error(strprintf("CDB: Can't open database %s (get_fileid failed with %d)", filename
, ret
));
43 for (const auto& item
: env
.mapDb
) {
44 u_int8_t item_fileid
[DB_FILE_ID_LEN
];
45 if (item
.second
&& item
.second
->get_mpf()->get_fileid(item_fileid
) == 0 &&
46 memcmp(fileid
, item_fileid
, sizeof(fileid
)) == 0) {
47 const char* item_filename
= nullptr;
48 item
.second
->get_dbname(&item_filename
, nullptr);
49 throw std::runtime_error(strprintf("CDB: Can't open database %s (duplicates fileid %s from %s)", filename
,
50 HexStr(std::begin(item_fileid
), std::end(item_fileid
)),
51 item_filename
? item_filename
: "(unknown database)"));
63 void CDBEnv::EnvShutdown()
69 int ret
= dbenv
->close(0);
71 LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database environment: %s\n", ret
, DbEnv::strerror(ret
));
73 DbEnv((u_int32_t
)0).remove(strPath
.c_str(), 0);
78 dbenv
.reset(new DbEnv(DB_CXX_NO_EXCEPTIONS
));
98 bool CDBEnv::Open(const fs::path
& pathIn
)
103 boost::this_thread::interruption_point();
105 strPath
= pathIn
.string();
106 fs::path pathLogDir
= pathIn
/ "database";
107 TryCreateDirectories(pathLogDir
);
108 fs::path pathErrorFile
= pathIn
/ "db.log";
109 LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir
.string(), pathErrorFile
.string());
111 unsigned int nEnvFlags
= 0;
112 if (gArgs
.GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB
))
113 nEnvFlags
|= DB_PRIVATE
;
115 dbenv
->set_lg_dir(pathLogDir
.string().c_str());
116 dbenv
->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet
117 dbenv
->set_lg_bsize(0x10000);
118 dbenv
->set_lg_max(1048576);
119 dbenv
->set_lk_max_locks(40000);
120 dbenv
->set_lk_max_objects(40000);
121 dbenv
->set_errfile(fsbridge::fopen(pathErrorFile
, "a")); /// debug
122 dbenv
->set_flags(DB_AUTO_COMMIT
, 1);
123 dbenv
->set_flags(DB_TXN_WRITE_NOSYNC
, 1);
124 dbenv
->log_set_config(DB_LOG_AUTO_REMOVE
, 1);
125 int ret
= dbenv
->open(strPath
.c_str(),
137 return error("CDBEnv::Open: Error %d opening database environment: %s\n", ret
, DbEnv::strerror(ret
));
145 void CDBEnv::MakeMock()
148 throw std::runtime_error("CDBEnv::MakeMock: Already initialized");
150 boost::this_thread::interruption_point();
152 LogPrint(BCLog::DB
, "CDBEnv::MakeMock\n");
154 dbenv
->set_cachesize(1, 0, 1);
155 dbenv
->set_lg_bsize(10485760 * 4);
156 dbenv
->set_lg_max(10485760);
157 dbenv
->set_lk_max_locks(10000);
158 dbenv
->set_lk_max_objects(10000);
159 dbenv
->set_flags(DB_AUTO_COMMIT
, 1);
160 dbenv
->log_set_config(DB_LOG_IN_MEMORY
, 1);
161 int ret
= dbenv
->open(nullptr,
171 throw std::runtime_error(strprintf("CDBEnv::MakeMock: Error %d opening database environment.", ret
));
177 CDBEnv::VerifyResult
CDBEnv::Verify(const std::string
& strFile
, recoverFunc_type recoverFunc
, std::string
& out_backup_filename
)
180 assert(mapFileUseCount
.count(strFile
) == 0);
182 Db
db(dbenv
.get(), 0);
183 int result
= db
.verify(strFile
.c_str(), nullptr, nullptr, 0);
186 else if (recoverFunc
== nullptr)
190 bool fRecovered
= (*recoverFunc
)(strFile
, out_backup_filename
);
191 return (fRecovered
? RECOVER_OK
: RECOVER_FAIL
);
194 bool CDB::Recover(const std::string
& filename
, void *callbackDataIn
, bool (*recoverKVcallback
)(void* callbackData
, CDataStream ssKey
, CDataStream ssValue
), std::string
& newFilename
)
196 // Recovery procedure:
197 // move wallet file to walletfilename.timestamp.bak
198 // Call Salvage with fAggressive=true to
199 // get as much data as possible.
200 // Rewrite salvaged data to fresh wallet file
201 // Set -rescan so any missing transactions will be
203 int64_t now
= GetTime();
204 newFilename
= strprintf("%s.%d.bak", filename
, now
);
206 int result
= bitdb
.dbenv
->dbrename(nullptr, filename
.c_str(), nullptr,
207 newFilename
.c_str(), DB_AUTO_COMMIT
);
209 LogPrintf("Renamed %s to %s\n", filename
, newFilename
);
212 LogPrintf("Failed to rename %s to %s\n", filename
, newFilename
);
216 std::vector
<CDBEnv::KeyValPair
> salvagedData
;
217 bool fSuccess
= bitdb
.Salvage(newFilename
, true, salvagedData
);
218 if (salvagedData
.empty())
220 LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename
);
223 LogPrintf("Salvage(aggressive) found %u records\n", salvagedData
.size());
225 std::unique_ptr
<Db
> pdbCopy
= MakeUnique
<Db
>(bitdb
.dbenv
.get(), 0);
226 int ret
= pdbCopy
->open(nullptr, // Txn pointer
227 filename
.c_str(), // Filename
228 "main", // Logical db name
229 DB_BTREE
, // Database type
233 LogPrintf("Cannot create database file %s\n", filename
);
238 DbTxn
* ptxn
= bitdb
.TxnBegin();
239 for (CDBEnv::KeyValPair
& row
: salvagedData
)
241 if (recoverKVcallback
)
243 CDataStream
ssKey(row
.first
, SER_DISK
, CLIENT_VERSION
);
244 CDataStream
ssValue(row
.second
, SER_DISK
, CLIENT_VERSION
);
245 if (!(*recoverKVcallback
)(callbackDataIn
, ssKey
, ssValue
))
248 Dbt
datKey(&row
.first
[0], row
.first
.size());
249 Dbt
datValue(&row
.second
[0], row
.second
.size());
250 int ret2
= pdbCopy
->put(ptxn
, &datKey
, &datValue
, DB_NOOVERWRITE
);
260 bool CDB::VerifyEnvironment(const std::string
& walletFile
, const fs::path
& walletDir
, std::string
& errorStr
)
262 LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
263 LogPrintf("Using wallet %s\n", walletFile
);
265 // Wallet file must be a plain filename without a directory
266 if (walletFile
!= fs::basename(walletFile
) + fs::extension(walletFile
))
268 errorStr
= strprintf(_("Wallet %s resides outside wallet directory %s"), walletFile
, walletDir
.string());
272 if (!bitdb
.Open(walletDir
))
274 // try moving the database env out of the way
275 fs::path pathDatabase
= walletDir
/ "database";
276 fs::path pathDatabaseBak
= walletDir
/ strprintf("database.%d.bak", GetTime());
278 fs::rename(pathDatabase
, pathDatabaseBak
);
279 LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase
.string(), pathDatabaseBak
.string());
280 } catch (const fs::filesystem_error
&) {
281 // failure is ok (well, not really, but it's not worse than what we started with)
285 if (!bitdb
.Open(walletDir
)) {
286 // if it still fails, it probably means we can't even create the database env
287 errorStr
= strprintf(_("Error initializing wallet database environment %s!"), walletDir
);
294 bool CDB::VerifyDatabaseFile(const std::string
& walletFile
, const fs::path
& walletDir
, std::string
& warningStr
, std::string
& errorStr
, CDBEnv::recoverFunc_type recoverFunc
)
296 if (fs::exists(walletDir
/ walletFile
))
298 std::string backup_filename
;
299 CDBEnv::VerifyResult r
= bitdb
.Verify(walletFile
, recoverFunc
, backup_filename
);
300 if (r
== CDBEnv::RECOVER_OK
)
302 warningStr
= strprintf(_("Warning: Wallet file corrupt, data salvaged!"
303 " Original %s saved as %s in %s; if"
304 " your balance or transactions are incorrect you should"
305 " restore from a backup."),
306 walletFile
, backup_filename
, walletDir
);
308 if (r
== CDBEnv::RECOVER_FAIL
)
310 errorStr
= strprintf(_("%s corrupt, salvage failed"), walletFile
);
314 // also return true if files does not exists
318 /* End of headers, beginning of key/value data */
319 static const char *HEADER_END
= "HEADER=END";
320 /* End of key/value data */
321 static const char *DATA_END
= "DATA=END";
323 bool CDBEnv::Salvage(const std::string
& strFile
, bool fAggressive
, std::vector
<CDBEnv::KeyValPair
>& vResult
)
326 assert(mapFileUseCount
.count(strFile
) == 0);
328 u_int32_t flags
= DB_SALVAGE
;
330 flags
|= DB_AGGRESSIVE
;
332 std::stringstream strDump
;
334 Db
db(dbenv
.get(), 0);
335 int result
= db
.verify(strFile
.c_str(), nullptr, &strDump
, flags
);
336 if (result
== DB_VERIFY_BAD
) {
337 LogPrintf("CDBEnv::Salvage: Database salvage found errors, all data may not be recoverable.\n");
339 LogPrintf("CDBEnv::Salvage: Rerun with aggressive mode to ignore errors and continue.\n");
343 if (result
!= 0 && result
!= DB_VERIFY_BAD
) {
344 LogPrintf("CDBEnv::Salvage: Database salvage failed with result %d.\n", result
);
348 // Format of bdb dump is ascii lines:
357 while (!strDump
.eof() && strLine
!= HEADER_END
)
358 getline(strDump
, strLine
); // Skip past header
360 std::string keyHex
, valueHex
;
361 while (!strDump
.eof() && keyHex
!= DATA_END
) {
362 getline(strDump
, keyHex
);
363 if (keyHex
!= DATA_END
) {
366 getline(strDump
, valueHex
);
367 if (valueHex
== DATA_END
) {
368 LogPrintf("CDBEnv::Salvage: WARNING: Number of keys in data does not match number of values.\n");
371 vResult
.push_back(make_pair(ParseHex(keyHex
), ParseHex(valueHex
)));
375 if (keyHex
!= DATA_END
) {
376 LogPrintf("CDBEnv::Salvage: WARNING: Unexpected end of file while reading salvage output.\n");
380 return (result
== 0);
384 void CDBEnv::CheckpointLSN(const std::string
& strFile
)
386 dbenv
->txn_checkpoint(0, 0, 0);
389 dbenv
->lsn_reset(strFile
.c_str(), 0);
393 CDB::CDB(CWalletDBWrapper
& dbw
, const char* pszMode
, bool fFlushOnCloseIn
) : pdb(nullptr), activeTxn(nullptr)
395 fReadOnly
= (!strchr(pszMode
, '+') && !strchr(pszMode
, 'w'));
396 fFlushOnClose
= fFlushOnCloseIn
;
401 const std::string
&strFilename
= dbw
.strFile
;
403 bool fCreate
= strchr(pszMode
, 'c') != nullptr;
404 unsigned int nFlags
= DB_THREAD
;
410 if (!env
->Open(GetWalletDir()))
411 throw std::runtime_error("CDB: Failed to open database environment.");
413 pdb
= env
->mapDb
[strFilename
];
414 if (pdb
== nullptr) {
416 std::unique_ptr
<Db
> pdb_temp
= MakeUnique
<Db
>(env
->dbenv
.get(), 0);
418 bool fMockDb
= env
->IsMock();
420 DbMpoolFile
* mpf
= pdb_temp
->get_mpf();
421 ret
= mpf
->set_flags(DB_MPOOL_NOFILE
, 1);
423 throw std::runtime_error(strprintf("CDB: Failed to configure for no temp file backing for database %s", strFilename
));
427 ret
= pdb_temp
->open(nullptr, // Txn pointer
428 fMockDb
? nullptr : strFilename
.c_str(), // Filename
429 fMockDb
? strFilename
.c_str() : "main", // Logical db name
430 DB_BTREE
, // Database type
435 throw std::runtime_error(strprintf("CDB: Error %d, can't open database %s", ret
, strFilename
));
437 CheckUniqueFileid(*env
, strFilename
, *pdb_temp
);
439 pdb
= pdb_temp
.release();
440 env
->mapDb
[strFilename
] = pdb
;
442 if (fCreate
&& !Exists(std::string("version"))) {
443 bool fTmp
= fReadOnly
;
445 WriteVersion(CLIENT_VERSION
);
449 ++env
->mapFileUseCount
[strFilename
];
450 strFile
= strFilename
;
459 // Flush database activity from memory pool to disk log
460 unsigned int nMinutes
= 0;
464 env
->dbenv
->txn_checkpoint(nMinutes
? gArgs
.GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE
) * 1024 : 0, nMinutes
, 0);
467 void CWalletDBWrapper::IncrementUpdateCounter()
486 --env
->mapFileUseCount
[strFile
];
490 void CDBEnv::CloseDb(const std::string
& strFile
)
494 if (mapDb
[strFile
] != nullptr) {
495 // Close the database handle
496 Db
* pdb
= mapDb
[strFile
];
499 mapDb
[strFile
] = nullptr;
504 bool CDB::Rewrite(CWalletDBWrapper
& dbw
, const char* pszSkip
)
509 CDBEnv
*env
= dbw
.env
;
510 const std::string
& strFile
= dbw
.strFile
;
514 if (!env
->mapFileUseCount
.count(strFile
) || env
->mapFileUseCount
[strFile
] == 0) {
515 // Flush log data to the dat file
516 env
->CloseDb(strFile
);
517 env
->CheckpointLSN(strFile
);
518 env
->mapFileUseCount
.erase(strFile
);
520 bool fSuccess
= true;
521 LogPrintf("CDB::Rewrite: Rewriting %s...\n", strFile
);
522 std::string strFileRes
= strFile
+ ".rewrite";
523 { // surround usage of db with extra {}
525 std::unique_ptr
<Db
> pdbCopy
= MakeUnique
<Db
>(env
->dbenv
.get(), 0);
527 int ret
= pdbCopy
->open(nullptr, // Txn pointer
528 strFileRes
.c_str(), // Filename
529 "main", // Logical db name
530 DB_BTREE
, // Database type
534 LogPrintf("CDB::Rewrite: Can't create database file %s\n", strFileRes
);
538 Dbc
* pcursor
= db
.GetCursor();
541 CDataStream
ssKey(SER_DISK
, CLIENT_VERSION
);
542 CDataStream
ssValue(SER_DISK
, CLIENT_VERSION
);
543 int ret1
= db
.ReadAtCursor(pcursor
, ssKey
, ssValue
);
544 if (ret1
== DB_NOTFOUND
) {
547 } else if (ret1
!= 0) {
553 strncmp(ssKey
.data(), pszSkip
, std::min(ssKey
.size(), strlen(pszSkip
))) == 0)
555 if (strncmp(ssKey
.data(), "\x07version", 8) == 0) {
558 ssValue
<< CLIENT_VERSION
;
560 Dbt
datKey(ssKey
.data(), ssKey
.size());
561 Dbt
datValue(ssValue
.data(), ssValue
.size());
562 int ret2
= pdbCopy
->put(nullptr, &datKey
, &datValue
, DB_NOOVERWRITE
);
568 env
->CloseDb(strFile
);
569 if (pdbCopy
->close(0))
576 Db
dbA(env
->dbenv
.get(), 0);
577 if (dbA
.remove(strFile
.c_str(), nullptr, 0))
579 Db
dbB(env
->dbenv
.get(), 0);
580 if (dbB
.rename(strFileRes
.c_str(), nullptr, strFile
.c_str(), 0))
584 LogPrintf("CDB::Rewrite: Failed to rewrite database file %s\n", strFileRes
);
593 void CDBEnv::Flush(bool fShutdown
)
595 int64_t nStart
= GetTimeMillis();
596 // Flush log data to the actual data file on all files that are not in use
597 LogPrint(BCLog::DB
, "CDBEnv::Flush: Flush(%s)%s\n", fShutdown
? "true" : "false", fDbEnvInit
? "" : " database not started");
602 std::map
<std::string
, int>::iterator mi
= mapFileUseCount
.begin();
603 while (mi
!= mapFileUseCount
.end()) {
604 std::string strFile
= (*mi
).first
;
605 int nRefCount
= (*mi
).second
;
606 LogPrint(BCLog::DB
, "CDBEnv::Flush: Flushing %s (refcount = %d)...\n", strFile
, nRefCount
);
607 if (nRefCount
== 0) {
608 // Move log data to the dat file
610 LogPrint(BCLog::DB
, "CDBEnv::Flush: %s checkpoint\n", strFile
);
611 dbenv
->txn_checkpoint(0, 0, 0);
612 LogPrint(BCLog::DB
, "CDBEnv::Flush: %s detach\n", strFile
);
614 dbenv
->lsn_reset(strFile
.c_str(), 0);
615 LogPrint(BCLog::DB
, "CDBEnv::Flush: %s closed\n", strFile
);
616 mapFileUseCount
.erase(mi
++);
620 LogPrint(BCLog::DB
, "CDBEnv::Flush: Flush(%s)%s took %15dms\n", fShutdown
? "true" : "false", fDbEnvInit
? "" : " database not started", GetTimeMillis() - nStart
);
623 if (mapFileUseCount
.empty()) {
624 dbenv
->log_archive(&listp
, DB_ARCH_REMOVE
);
627 fs::remove_all(fs::path(strPath
) / "database");
633 bool CDB::PeriodicFlush(CWalletDBWrapper
& dbw
)
639 CDBEnv
*env
= dbw
.env
;
640 const std::string
& strFile
= dbw
.strFile
;
641 TRY_LOCK(bitdb
.cs_db
,lockDb
);
644 // Don't do this if any databases are in use
646 std::map
<std::string
, int>::iterator mit
= env
->mapFileUseCount
.begin();
647 while (mit
!= env
->mapFileUseCount
.end())
649 nRefCount
+= (*mit
).second
;
655 boost::this_thread::interruption_point();
656 std::map
<std::string
, int>::iterator mi
= env
->mapFileUseCount
.find(strFile
);
657 if (mi
!= env
->mapFileUseCount
.end())
659 LogPrint(BCLog::DB
, "Flushing %s\n", strFile
);
660 int64_t nStart
= GetTimeMillis();
662 // Flush wallet file so it's self contained
663 env
->CloseDb(strFile
);
664 env
->CheckpointLSN(strFile
);
666 env
->mapFileUseCount
.erase(mi
++);
667 LogPrint(BCLog::DB
, "Flushed %s %dms\n", strFile
, GetTimeMillis() - nStart
);
676 bool CWalletDBWrapper::Rewrite(const char* pszSkip
)
678 return CDB::Rewrite(*this, pszSkip
);
681 bool CWalletDBWrapper::Backup(const std::string
& strDest
)
690 if (!env
->mapFileUseCount
.count(strFile
) || env
->mapFileUseCount
[strFile
] == 0)
692 // Flush log data to the dat file
693 env
->CloseDb(strFile
);
694 env
->CheckpointLSN(strFile
);
695 env
->mapFileUseCount
.erase(strFile
);
698 fs::path pathSrc
= GetWalletDir() / strFile
;
699 fs::path
pathDest(strDest
);
700 if (fs::is_directory(pathDest
))
704 if (fs::equivalent(pathSrc
, pathDest
)) {
705 LogPrintf("cannot backup to wallet source file %s\n", pathDest
.string());
709 fs::copy_file(pathSrc
, pathDest
, fs::copy_option::overwrite_if_exists
);
710 LogPrintf("copied %s to %s\n", strFile
, pathDest
.string());
712 } catch (const fs::filesystem_error
& e
) {
713 LogPrintf("error copying %s to %s - %s\n", strFile
, pathDest
.string(), e
.what());
722 void CWalletDBWrapper::Flush(bool shutdown
)
725 env
->Flush(shutdown
);