Skip to content

Commit 2b88f67

Browse files
committed
Merge #14320: [bugfix] wallet: Fix duplicate fileid detection
4ea7732 tests: add test case for loading copied wallet twice (Chun Kuan Lee) 2d796fa wallet: Fix duplicate fileid (Chun Kuan Lee) Pull request description: The implementation in current master can not detect if the file ID is duplicate with flushed `BerkeleyEnvironment`. This PR would store the file ID in a global variable `g_fileids` and release it when the `BerkeleyDatabase` close. So it won't have to rely on a `Db*`. Fix #14304 Tree-SHA512: 0632254b696bb4c671b5e2e5781e9012df54ba3c6ab0f919d9f6d31f374d3b0f8bd968b90b537884ac8c3d2906afdd58c2ce258666263464c7dbd636960b0e8f
2 parents 6241eb3 + 4ea7732 commit 2b88f67

File tree

3 files changed

+30
-12
lines changed

3 files changed

+30
-12
lines changed

src/wallet/db.cpp

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <boost/thread.hpp>
2121

2222
namespace {
23+
2324
//! Make sure database has a unique fileid within the environment. If it
2425
//! doesn't, throw an error. BDB caches do not work properly when more than one
2526
//! open database has the same fileid (values written to one database may show
@@ -29,25 +30,19 @@ namespace {
2930
//! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html),
3031
//! so bitcoin should never create different databases with the same fileid, but
3132
//! this error can be triggered if users manually copy database files.
32-
void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db)
33+
void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filename, Db& db, WalletDatabaseFileId& fileid)
3334
{
3435
if (env.IsMock()) return;
3536

36-
u_int8_t fileid[DB_FILE_ID_LEN];
37-
int ret = db.get_mpf()->get_fileid(fileid);
37+
int ret = db.get_mpf()->get_fileid(fileid.value);
3838
if (ret != 0) {
3939
throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (get_fileid failed with %d)", filename, ret));
4040
}
4141

42-
for (const auto& item : env.mapDb) {
43-
u_int8_t item_fileid[DB_FILE_ID_LEN];
44-
if (item.second && item.second->get_mpf()->get_fileid(item_fileid) == 0 &&
45-
memcmp(fileid, item_fileid, sizeof(fileid)) == 0) {
46-
const char* item_filename = nullptr;
47-
item.second->get_dbname(&item_filename, nullptr);
42+
for (const auto& item : env.m_fileids) {
43+
if (fileid == item.second && &fileid != &item.second) {
4844
throw std::runtime_error(strprintf("BerkeleyBatch: Can't open database %s (duplicates fileid %s from %s)", filename,
49-
HexStr(std::begin(item_fileid), std::end(item_fileid)),
50-
item_filename ? item_filename : "(unknown database)"));
45+
HexStr(std::begin(item.second.value), std::end(item.second.value)), item.first));
5146
}
5247
}
5348
}
@@ -56,6 +51,11 @@ CCriticalSection cs_db;
5651
std::map<std::string, BerkeleyEnvironment> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to open db environment.
5752
} // namespace
5853

54+
bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const
55+
{
56+
return memcmp(value, &rhs.value, sizeof(value)) == 0;
57+
}
58+
5959
BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
6060
{
6161
fs::path env_directory;
@@ -504,7 +504,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo
504504
// versions of BDB have an set_lk_exclusive method for this
505505
// purpose, but the older version we use does not.)
506506
for (const auto& env : g_dbenvs) {
507-
CheckUniqueFileid(env.second, strFilename, *pdb_temp);
507+
CheckUniqueFileid(env.second, strFilename, *pdb_temp, this->env->m_fileids[strFilename]);
508508
}
509509

510510
pdb = pdb_temp.release();
@@ -826,6 +826,13 @@ void BerkeleyDatabase::Flush(bool shutdown)
826826
LOCK(cs_db);
827827
g_dbenvs.erase(env->Directory().string());
828828
env = nullptr;
829+
} else {
830+
// TODO: To avoid g_dbenvs.erase erasing the environment prematurely after the
831+
// first database shutdown when multiple databases are open in the same
832+
// environment, should replace raw database `env` pointers with shared or weak
833+
// pointers, or else separate the database and environment shutdowns so
834+
// environments can be shut down after databases.
835+
env->m_fileids.erase(strFile);
829836
}
830837
}
831838
}

src/wallet/db.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,19 @@
1818
#include <map>
1919
#include <memory>
2020
#include <string>
21+
#include <unordered_map>
2122
#include <vector>
2223

2324
#include <db_cxx.h>
2425

2526
static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
2627
static const bool DEFAULT_WALLET_PRIVDB = true;
2728

29+
struct WalletDatabaseFileId {
30+
u_int8_t value[DB_FILE_ID_LEN];
31+
bool operator==(const WalletDatabaseFileId& rhs) const;
32+
};
33+
2834
class BerkeleyEnvironment
2935
{
3036
private:
@@ -38,6 +44,7 @@ class BerkeleyEnvironment
3844
std::unique_ptr<DbEnv> dbenv;
3945
std::map<std::string, int> mapFileUseCount;
4046
std::map<std::string, Db*> mapDb;
47+
std::unordered_map<std::string, WalletDatabaseFileId> m_fileids;
4148
std::condition_variable_any m_db_in_use;
4249

4350
BerkeleyEnvironment(const fs::path& env_directory);

test/functional/wallet_multiwallet.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ def wallet_file(name):
230230
# Fail to load if one wallet is a copy of another
231231
assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
232232

233+
# Fail to load if one wallet is a copy of another, test this twice to make sure that we don't re-introduce #14304
234+
assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
235+
236+
233237
# Fail to load if wallet file is a symlink
234238
if os.name != 'nt':
235239
assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink')

0 commit comments

Comments
 (0)