Skip to content

Commit f09bc7e

Browse files
committed
Merge #12493: [wallet] Reopen CDBEnv after encryption instead of shutting down
c1dde3a No longer shutdown after encrypting the wallet (Andrew Chow) d7637c5 After encrypting the wallet, reload the database environment (Andrew Chow) 5d296ac Add function to close all Db's and reload the databae environment (Andrew Chow) a769461 Move BerkeleyEnvironment deletion from internal method to callsite (Andrew Chow) Pull request description: This is the replacement for #11678 which implements @ryanofsky's [suggestion](bitcoin/bitcoin#11678 (review)). Shutting down the software was to prevent the BDB environment from writing unencrypted private keys to disk in the database log files, as was noted [here](https://bitcointalk.org/index.php?topic=51474.msg616068#msg616068). This PR replaces the shutdown behavior with a CDBEnv flush, close, and reopen which achieves the same effect: everything is cleanly flushed and closed, the log files are removed, and then the environment reopened to continue normal operation. To ensure that no unencrypted private keys are in the log files after encrypting the wallet, I wrote [this script](https://gist.github.com/achow101/7f7143e6c3d3fdc034d3470e72823e9d) to pull private keys from the original wallet file and searches for these keys in the log files (note that you will have to change your file paths to make it work on your own machine). As for concerns about private keys being written to slack space or being kept in memory, these behaviors no longer exist after the original wallet encryption PR and the shutting down solution from 2011. cc @ryanofsky Tree-SHA512: 34b894283b0677a873d06dee46dff8424dec85a2973009ac9b84bcf3d22d05f227c494168c395219d9aee3178e420cf70d4b3eeacc9785aa86b6015d25758e75
2 parents a098245 + c1dde3a commit f09bc7e

File tree

11 files changed

+57
-32
lines changed

11 files changed

+57
-32
lines changed

src/qt/askpassphrasedialog.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,15 @@ void AskPassphraseDialog::accept()
123123
{
124124
QMessageBox::warning(this, tr("Wallet encrypted"),
125125
"<qt>" +
126-
tr("%1 will close now to finish the encryption process. "
126+
tr("Your wallet is now encrypted. "
127127
"Remember that encrypting your wallet cannot fully protect "
128-
"your bitcoins from being stolen by malware infecting your computer.").arg(tr(PACKAGE_NAME)) +
128+
"your bitcoins from being stolen by malware infecting your computer.") +
129129
"<br><br><b>" +
130130
tr("IMPORTANT: Any previous backups you have made of your wallet file "
131131
"should be replaced with the newly generated, encrypted wallet file. "
132132
"For security reasons, previous backups of the unencrypted wallet file "
133133
"will become useless as soon as you start using the new, encrypted wallet.") +
134134
"</b></qt>");
135-
QApplication::quit();
136135
}
137136
else
138137
{

src/wallet/db.cpp

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ void BerkeleyBatch::Close()
556556
LOCK(cs_db);
557557
--env->mapFileUseCount[strFile];
558558
}
559+
env->m_db_in_use.notify_all();
559560
}
560561

561562
void BerkeleyEnvironment::CloseDb(const std::string& strFile)
@@ -572,6 +573,32 @@ void BerkeleyEnvironment::CloseDb(const std::string& strFile)
572573
}
573574
}
574575

576+
void BerkeleyEnvironment::ReloadDbEnv()
577+
{
578+
// Make sure that no Db's are in use
579+
AssertLockNotHeld(cs_db);
580+
std::unique_lock<CCriticalSection> lock(cs_db);
581+
m_db_in_use.wait(lock, [this](){
582+
for (auto& count : mapFileUseCount) {
583+
if (count.second > 0) return false;
584+
}
585+
return true;
586+
});
587+
588+
std::vector<std::string> filenames;
589+
for (auto it : mapDb) {
590+
filenames.push_back(it.first);
591+
}
592+
// Close the individual Db's
593+
for (const std::string& filename : filenames) {
594+
CloseDb(filename);
595+
}
596+
// Reset the environment
597+
Flush(true); // This will flush and close the environment
598+
Reset();
599+
Open(true);
600+
}
601+
575602
bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip)
576603
{
577604
if (database.IsDummy()) {
@@ -697,7 +724,6 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
697724
if (!fMockDb) {
698725
fs::remove_all(fs::path(strPath) / "database");
699726
}
700-
g_dbenvs.erase(strPath);
701727
}
702728
}
703729
}
@@ -796,6 +822,17 @@ void BerkeleyDatabase::Flush(bool shutdown)
796822
{
797823
if (!IsDummy()) {
798824
env->Flush(shutdown);
799-
if (shutdown) env = nullptr;
825+
if (shutdown) {
826+
LOCK(cs_db);
827+
g_dbenvs.erase(env->Directory().string());
828+
env = nullptr;
829+
}
830+
}
831+
}
832+
833+
void BerkeleyDatabase::ReloadDbEnv()
834+
{
835+
if (!IsDummy()) {
836+
env->ReloadDbEnv();
800837
}
801838
}

src/wallet/db.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class BerkeleyEnvironment
3838
std::unique_ptr<DbEnv> dbenv;
3939
std::map<std::string, int> mapFileUseCount;
4040
std::map<std::string, Db*> mapDb;
41+
std::condition_variable_any m_db_in_use;
4142

4243
BerkeleyEnvironment(const fs::path& env_directory);
4344
~BerkeleyEnvironment();
@@ -75,6 +76,7 @@ class BerkeleyEnvironment
7576
void CheckpointLSN(const std::string& strFile);
7677

7778
void CloseDb(const std::string& strFile);
79+
void ReloadDbEnv();
7880

7981
DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC)
8082
{
@@ -145,6 +147,8 @@ class BerkeleyDatabase
145147

146148
void IncrementUpdateCounter();
147149

150+
void ReloadDbEnv();
151+
148152
std::atomic<unsigned int> nUpdateCounter;
149153
unsigned int nLastSeen;
150154
unsigned int nLastFlushed;

src/wallet/rpcwallet.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,7 +2121,6 @@ static UniValue encryptwallet(const JSONRPCRequest& request)
21212121
"will require the passphrase to be set prior the making these calls.\n"
21222122
"Use the walletpassphrase call for this, and then walletlock call.\n"
21232123
"If the wallet is already encrypted, use the walletpassphrasechange call.\n"
2124-
"Note that this will shutdown the server.\n"
21252124
"\nArguments:\n"
21262125
"1. \"passphrase\" (string) The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long.\n"
21272126
"\nExamples:\n"
@@ -2159,11 +2158,7 @@ static UniValue encryptwallet(const JSONRPCRequest& request)
21592158
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet.");
21602159
}
21612160

2162-
// BDB seems to have a bad habit of writing old data into
2163-
// slack space in .dat files; that is bad if the old data is
2164-
// unencrypted private keys. So:
2165-
StartShutdown();
2166-
return "wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet. The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.";
2161+
return "wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.";
21672162
}
21682163

21692164
static UniValue lockunspent(const JSONRPCRequest& request)

src/wallet/wallet.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,11 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
722722
// bits of the unencrypted private key in slack space in the database file.
723723
database->Rewrite();
724724

725+
// BDB seems to have a bad habit of writing old data into
726+
// slack space in .dat files; that is bad if the old data is
727+
// unencrypted private keys. So:
728+
database->ReloadDbEnv();
729+
725730
}
726731
NotifyStatusChanged(this);
727732

test/functional/rpc_fundrawtransaction.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -478,10 +478,8 @@ def run_test(self):
478478

479479
############################################################
480480
# locked wallet test
481-
self.stop_node(0)
482-
self.nodes[1].node_encrypt_wallet("test")
483-
self.stop_node(2)
484-
self.stop_node(3)
481+
self.nodes[1].encryptwallet("test")
482+
self.stop_nodes()
485483

486484
self.start_nodes()
487485
# This test is not meant to test fee estimation and we'd like

test/functional/test_framework/test_node.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,6 @@ def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, mat
305305
assert_msg = "bitcoind should have exited with expected error " + expected_msg
306306
self._raise_assertion_error(assert_msg)
307307

308-
def node_encrypt_wallet(self, passphrase):
309-
""""Encrypts the wallet.
310-
311-
This causes bitcoind to shutdown, so this method takes
312-
care of cleaning up resources."""
313-
self.encryptwallet(passphrase)
314-
self.wait_until_stopped()
315-
316308
def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, **kwargs):
317309
"""Add a p2p connection to the node.
318310

test/functional/wallet_bumpfee.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ def skip_test_if_missing_module(self):
4242

4343
def run_test(self):
4444
# Encrypt wallet for test_locked_wallet_fails test
45-
self.nodes[1].node_encrypt_wallet(WALLET_PASSPHRASE)
46-
self.start_node(1)
45+
self.nodes[1].encryptwallet(WALLET_PASSPHRASE)
4746
self.nodes[1].walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
4847

4948
connect_nodes_bi(self.nodes, 0, 1)

test/functional/wallet_dump.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,7 @@ def run_test(self):
132132
assert_equal(witness_addr_ret, witness_addr) # p2sh-p2wsh address added to the first key
133133

134134
#encrypt wallet, restart, unlock and dump
135-
self.nodes[0].node_encrypt_wallet('test')
136-
self.start_node(0)
135+
self.nodes[0].encryptwallet('test')
137136
self.nodes[0].walletpassphrase('test', 10)
138137
# Should be a no-op:
139138
self.nodes[0].keypoolrefill()

test/functional/wallet_encryption.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ def run_test(self):
3333
assert_equal(len(privkey), 52)
3434

3535
# Encrypt the wallet
36-
self.nodes[0].node_encrypt_wallet(passphrase)
37-
self.start_node(0)
36+
self.nodes[0].encryptwallet(passphrase)
3837

3938
# Test that the wallet is encrypted
4039
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address)

0 commit comments

Comments
 (0)