Skip to content

Commit e32c2a5

Browse files
committed
Avoid opening copied wallet databases simultaneously
Make sure wallet databases have unique fileids. If they don't, throw an error. BDB caches do not work properly when more than one open database has the same fileid, because values written to one database may show up in reads to other databases. Bitcoin will never create different databases with the same fileid, but users can create them by manually copying database files. BDB caching bug was reported by Chris Moore <[email protected]> #11429 Fixes #11429
1 parent 551d7bf commit e32c2a5

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

src/wallet/db.cpp

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,12 +401,48 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb
401401
nFlags, // Flags
402402
0);
403403

404+
std::string error;
404405
if (ret != 0) {
406+
error = strprintf("CDB: Error %d, can't open database %s", ret, strFilename);
407+
}
408+
409+
u_int8_t fileid[DB_FILE_ID_LEN];
410+
if (error.empty()) {
411+
ret = pdb->get_mpf()->get_fileid(fileid);
412+
if (ret != 0) {
413+
error = strprintf("CDB: Can't open database %s (get_fileid failed with %d)", strFilename, ret);
414+
}
415+
}
416+
417+
if (error.empty()) {
418+
// Make sure database has a unique fileid. If it doesn't, throw
419+
// an error. BDB caches do not work properly when more than one
420+
// open database has the same fileid (values written to one
421+
// database may show up in reads to other databases).
422+
//
423+
// Bitcoin will never create different databases with the same
424+
// fileid, but this error can be triggered if users manually
425+
// copy database files.
426+
for (const auto& item : env->mapDb) {
427+
u_int8_t item_fileid[DB_FILE_ID_LEN];
428+
if (item.second && item.second->get_mpf()->get_fileid(item_fileid) == 0 &&
429+
memcmp(fileid, item_fileid, sizeof(fileid)) == 0) {
430+
const char* item_filename = nullptr;
431+
item.second->get_dbname(&item_filename, nullptr);
432+
error = strprintf("CDB: Can't open database %s (duplicates fileid %s from %s)", strFilename,
433+
HexStr(std::begin(item_fileid), std::end(item_fileid)),
434+
item_filename ? item_filename : "(unknown database)");
435+
break;
436+
}
437+
}
438+
}
439+
440+
if (!error.empty()) {
405441
delete pdb;
406442
pdb = nullptr;
407443
--env->mapFileUseCount[strFile];
408444
strFile = "";
409-
throw std::runtime_error(strprintf("CDB: Error %d, can't open database %s", ret, strFilename));
445+
throw std::runtime_error(error);
410446
}
411447

412448
if (fCreate && !Exists(std::string("version"))) {

test/functional/multiwallet.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
Verify that a bitcoind node can load multiple wallet files
88
"""
99
import os
10+
import shutil
1011

1112
from test_framework.test_framework import BitcoinTestFramework
1213
from test_framework.util import assert_equal, assert_raises_jsonrpc
@@ -29,6 +30,11 @@ def run_test(self):
2930
os.mkdir(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w11'))
3031
self.assert_start_raises_init_error(0, ['-wallet=w11'], 'Error loading wallet w11. -wallet filename must be a regular file.')
3132

33+
# should not initialize if one wallet is a copy of another
34+
shutil.copyfile(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w2'),
35+
os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w22'))
36+
self.assert_start_raises_init_error(0, ['-wallet=w2', '-wallet=w22'], 'duplicates fileid')
37+
3238
# should not initialize if wallet file is a symlink
3339
os.symlink(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w1'), os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w12'))
3440
self.assert_start_raises_init_error(0, ['-wallet=w12'], 'Error loading wallet w12. -wallet filename must be a regular file.')

0 commit comments

Comments
 (0)