Skip to content

Commit 520e435

Browse files
committed
Merge #18918: wallet: Move salvagewallet into wallettool
84ae057 Add release notes about salvage changes (Andrew Chow) ea337f2 Move RecoverKeysOnlyFilter into RecoverDataBaseFile (Andrew Chow) 9ea2d25 Move RecoverDatabaseFile and RecoverKeysOnlyFilter into salvage.{cpp/h} (Andrew Chow) b426c77 Make BerkeleyBatch::Recover and WalletBatch::RecoverKeysOnlyFilter standalone (Andrew Chow) 2741774 Expose a version of ReadKeyValue and use it in RecoverKeysOnlyFilter (Andrew Chow) ced95d0 Move BerkeleyEnvironment::Salvage into BerkeleyBatch::Recover (Andrew Chow) 07250b8 walletdb: remove fAggressive from Salvage (Andrew Chow) 8ebcbc8 walletdb: don't automatically salvage when corruption is detected (Andrew Chow) d321046 wallet: remove -salvagewallet (Andrew Chow) cdd955e Add basic test for bitcoin-wallet salvage (Andrew Chow) c877709 wallettool: Add a salvage command (Andrew Chow) Pull request description: Removes the `-salvagewallet` startup option and adds a `salvage` command to the `bitcoin-wallet` tool. As such, `-salvagewallet` is removed. Additionally, the automatic salvage that is done if the wallet file fails to load is removed. Lastly the salvage code entirely is moved out entirely into `bitcoin-wallet` from `walletdb.{cpp/h}` and `db.{cpp/h}`. ACKs for top commit: jonatack: ACK 84ae057 feedback taken, and compared to my previous review, the bitcoin-wallet salvage command now seems to run and it exits without raising. The new test passes at both 9454105 and 84ae057 so as a sanity check I'd agree there is room for improvement, if possible. MarcoFalke: re-ACK 84ae057 🏉 Empact: Code Review ACK bitcoin/bitcoin@84ae057 ryanofsky: Code review ACK 84ae057. Lot of small changes since previous review: added verify step before salvage, added basic test in new commit, removed unused scanstate variable and warnings parameter, tweaked various comments and strings, moved fsuccess variable declaration meshcollider: Concept / light code review ACK 84ae057 Tree-SHA512: 05be116b56ecade1c58faca1728c8fe4b78f0a082dbc2544a3f7507dd155f1f4f39070bd1fe90053444384337bc48b97149df5c1010230d78f8ecc08e69d93af
2 parents 4af01b3 + 84ae057 commit 520e435

18 files changed

+237
-273
lines changed

doc/release-notes-18918.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Wallet
2+
3+
The `-salvagewallet` startup option has been removed. A new `salvage` command has been added to the `bitcoin-wallet` tool which performs the salvage operations that `-salvagewallet` did.

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ BITCOIN_CORE_H = \
248248
wallet/ismine.h \
249249
wallet/load.h \
250250
wallet/rpcwallet.h \
251+
wallet/salvage.h \
251252
wallet/scriptpubkeyman.h \
252253
wallet/wallet.h \
253254
wallet/walletdb.h \
@@ -356,6 +357,7 @@ libbitcoin_wallet_a_SOURCES = \
356357
wallet/load.cpp \
357358
wallet/rpcdump.cpp \
358359
wallet/rpcwallet.cpp \
360+
wallet/salvage.cpp \
359361
wallet/scriptpubkeyman.cpp \
360362
wallet/wallet.cpp \
361363
wallet/walletdb.cpp \

src/bitcoin-wallet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ static void SetupWalletToolArgs()
3131

3232
gArgs.AddArg("info", "Get wallet info", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
3333
gArgs.AddArg("create", "Create new wallet file", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
34+
gArgs.AddArg("salvage", "Attempt to recover private keys from a corrupt wallet", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
3435
}
3536

3637
static bool WalletAppInit(int argc, char* argv[])

src/wallet/db.cpp

Lines changed: 5 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -268,21 +268,14 @@ BerkeleyEnvironment::BerkeleyEnvironment()
268268
fMockDb = true;
269269
}
270270

271-
BerkeleyEnvironment::VerifyResult BerkeleyEnvironment::Verify(const std::string& strFile, recoverFunc_type recoverFunc, std::string& out_backup_filename)
271+
bool BerkeleyEnvironment::Verify(const std::string& strFile)
272272
{
273273
LOCK(cs_db);
274274
assert(mapFileUseCount.count(strFile) == 0);
275275

276276
Db db(dbenv.get(), 0);
277277
int result = db.verify(strFile.c_str(), nullptr, nullptr, 0);
278-
if (result == 0)
279-
return VerifyResult::VERIFY_OK;
280-
else if (recoverFunc == nullptr)
281-
return VerifyResult::RECOVER_FAIL;
282-
283-
// Try to recover:
284-
bool fRecovered = (*recoverFunc)(fs::path(strPath) / strFile, out_backup_filename);
285-
return (fRecovered ? VerifyResult::RECOVER_OK : VerifyResult::RECOVER_FAIL);
278+
return result == 0;
286279
}
287280

288281
BerkeleyBatch::SafeDbt::SafeDbt()
@@ -324,75 +317,6 @@ BerkeleyBatch::SafeDbt::operator Dbt*()
324317
return &m_dbt;
325318
}
326319

327-
bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
328-
{
329-
std::string filename;
330-
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
331-
332-
// Recovery procedure:
333-
// move wallet file to walletfilename.timestamp.bak
334-
// Call Salvage with fAggressive=true to
335-
// get as much data as possible.
336-
// Rewrite salvaged data to fresh wallet file
337-
// Set -rescan so any missing transactions will be
338-
// found.
339-
int64_t now = GetTime();
340-
newFilename = strprintf("%s.%d.bak", filename, now);
341-
342-
int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr,
343-
newFilename.c_str(), DB_AUTO_COMMIT);
344-
if (result == 0)
345-
LogPrintf("Renamed %s to %s\n", filename, newFilename);
346-
else
347-
{
348-
LogPrintf("Failed to rename %s to %s\n", filename, newFilename);
349-
return false;
350-
}
351-
352-
std::vector<BerkeleyEnvironment::KeyValPair> salvagedData;
353-
bool fSuccess = env->Salvage(newFilename, true, salvagedData);
354-
if (salvagedData.empty())
355-
{
356-
LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename);
357-
return false;
358-
}
359-
LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size());
360-
361-
std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env->dbenv.get(), 0);
362-
int ret = pdbCopy->open(nullptr, // Txn pointer
363-
filename.c_str(), // Filename
364-
"main", // Logical db name
365-
DB_BTREE, // Database type
366-
DB_CREATE, // Flags
367-
0);
368-
if (ret > 0) {
369-
LogPrintf("Cannot create database file %s\n", filename);
370-
pdbCopy->close(0);
371-
return false;
372-
}
373-
374-
DbTxn* ptxn = env->TxnBegin();
375-
for (BerkeleyEnvironment::KeyValPair& row : salvagedData)
376-
{
377-
if (recoverKVcallback)
378-
{
379-
CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
380-
CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
381-
if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue))
382-
continue;
383-
}
384-
Dbt datKey(&row.first[0], row.first.size());
385-
Dbt datValue(&row.second[0], row.second.size());
386-
int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
387-
if (ret2 > 0)
388-
fSuccess = false;
389-
}
390-
ptxn->commit(0);
391-
pdbCopy->close(0);
392-
393-
return fSuccess;
394-
}
395-
396320
bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, bilingual_str& errorStr)
397321
{
398322
std::string walletFile;
@@ -410,100 +334,23 @@ bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, bilingual_str&
410334
return true;
411335
}
412336

413-
bool BerkeleyBatch::VerifyDatabaseFile(const fs::path& file_path, std::vector<bilingual_str>& warnings, bilingual_str& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc)
337+
bool BerkeleyBatch::VerifyDatabaseFile(const fs::path& file_path, bilingual_str& errorStr)
414338
{
415339
std::string walletFile;
416340
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile);
417341
fs::path walletDir = env->Directory();
418342

419343
if (fs::exists(walletDir / walletFile))
420344
{
421-
std::string backup_filename;
422-
BerkeleyEnvironment::VerifyResult r = env->Verify(walletFile, recoverFunc, backup_filename);
423-
if (r == BerkeleyEnvironment::VerifyResult::RECOVER_OK)
424-
{
425-
warnings.push_back(strprintf(_("Warning: Wallet file corrupt, data salvaged!"
426-
" Original %s saved as %s in %s; if"
427-
" your balance or transactions are incorrect you should"
428-
" restore from a backup."),
429-
walletFile, backup_filename, walletDir));
430-
}
431-
if (r == BerkeleyEnvironment::VerifyResult::RECOVER_FAIL)
432-
{
433-
errorStr = strprintf(_("%s corrupt, salvage failed"), walletFile);
345+
if (!env->Verify(walletFile)) {
346+
errorStr = strprintf(_("%s corrupt. Try using the wallet tool bitcoin-wallet to salvage or restoring a backup."), walletFile);
434347
return false;
435348
}
436349
}
437350
// also return true if files does not exists
438351
return true;
439352
}
440353

441-
/* End of headers, beginning of key/value data */
442-
static const char *HEADER_END = "HEADER=END";
443-
/* End of key/value data */
444-
static const char *DATA_END = "DATA=END";
445-
446-
bool BerkeleyEnvironment::Salvage(const std::string& strFile, bool fAggressive, std::vector<BerkeleyEnvironment::KeyValPair>& vResult)
447-
{
448-
LOCK(cs_db);
449-
assert(mapFileUseCount.count(strFile) == 0);
450-
451-
u_int32_t flags = DB_SALVAGE;
452-
if (fAggressive)
453-
flags |= DB_AGGRESSIVE;
454-
455-
std::stringstream strDump;
456-
457-
Db db(dbenv.get(), 0);
458-
int result = db.verify(strFile.c_str(), nullptr, &strDump, flags);
459-
if (result == DB_VERIFY_BAD) {
460-
LogPrintf("BerkeleyEnvironment::Salvage: Database salvage found errors, all data may not be recoverable.\n");
461-
if (!fAggressive) {
462-
LogPrintf("BerkeleyEnvironment::Salvage: Rerun with aggressive mode to ignore errors and continue.\n");
463-
return false;
464-
}
465-
}
466-
if (result != 0 && result != DB_VERIFY_BAD) {
467-
LogPrintf("BerkeleyEnvironment::Salvage: Database salvage failed with result %d.\n", result);
468-
return false;
469-
}
470-
471-
// Format of bdb dump is ascii lines:
472-
// header lines...
473-
// HEADER=END
474-
// hexadecimal key
475-
// hexadecimal value
476-
// ... repeated
477-
// DATA=END
478-
479-
std::string strLine;
480-
while (!strDump.eof() && strLine != HEADER_END)
481-
getline(strDump, strLine); // Skip past header
482-
483-
std::string keyHex, valueHex;
484-
while (!strDump.eof() && keyHex != DATA_END) {
485-
getline(strDump, keyHex);
486-
if (keyHex != DATA_END) {
487-
if (strDump.eof())
488-
break;
489-
getline(strDump, valueHex);
490-
if (valueHex == DATA_END) {
491-
LogPrintf("BerkeleyEnvironment::Salvage: WARNING: Number of keys in data does not match number of values.\n");
492-
break;
493-
}
494-
vResult.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex)));
495-
}
496-
}
497-
498-
if (keyHex != DATA_END) {
499-
LogPrintf("BerkeleyEnvironment::Salvage: WARNING: Unexpected end of file while reading salvage output.\n");
500-
return false;
501-
}
502-
503-
return (result == 0);
504-
}
505-
506-
507354
void BerkeleyEnvironment::CheckpointLSN(const std::string& strFile)
508355
{
509356
dbenv->txn_checkpoint(0, 0, 0);

src/wallet/db.h

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,26 +66,7 @@ class BerkeleyEnvironment
6666
bool IsDatabaseLoaded(const std::string& db_filename) const { return m_databases.find(db_filename) != m_databases.end(); }
6767
fs::path Directory() const { return strPath; }
6868

69-
/**
70-
* Verify that database file strFile is OK. If it is not,
71-
* call the callback to try to recover.
72-
* This must be called BEFORE strFile is opened.
73-
* Returns true if strFile is OK.
74-
*/
75-
enum class VerifyResult { VERIFY_OK,
76-
RECOVER_OK,
77-
RECOVER_FAIL };
78-
typedef bool (*recoverFunc_type)(const fs::path& file_path, std::string& out_backup_filename);
79-
VerifyResult Verify(const std::string& strFile, recoverFunc_type recoverFunc, std::string& out_backup_filename);
80-
/**
81-
* Salvage data from a file that Verify says is bad.
82-
* fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method documentation).
83-
* Appends binary key/value pairs to vResult, returns true if successful.
84-
* NOTE: reads the entire database into memory, so cannot be used
85-
* for huge databases.
86-
*/
87-
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
88-
bool Salvage(const std::string& strFile, bool fAggressive, std::vector<KeyValPair>& vResult);
69+
bool Verify(const std::string& strFile);
8970

9071
bool Open(bool retry);
9172
void Close();
@@ -245,15 +226,14 @@ class BerkeleyBatch
245226

246227
void Flush();
247228
void Close();
248-
static bool Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename);
249229

250230
/* flush the wallet passively (TRY_LOCK)
251231
ideal to be called periodically */
252232
static bool PeriodicFlush(BerkeleyDatabase& database);
253233
/* verifies the database environment */
254234
static bool VerifyEnvironment(const fs::path& file_path, bilingual_str& errorStr);
255235
/* verifies the database file */
256-
static bool VerifyDatabaseFile(const fs::path& file_path, std::vector<bilingual_str>& warnings, bilingual_str& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc);
236+
static bool VerifyDatabaseFile(const fs::path& file_path, bilingual_str& errorStr);
257237

258238
template <typename K, typename T>
259239
bool Read(const K& key, T& value)

src/wallet/init.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ void WalletInit::AddWalletOptions() const
5454
gArgs.AddArg("-paytxfee=<amt>", strprintf("Fee (in %s/kB) to add to transactions you send (default: %s)",
5555
CURRENCY_UNIT, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
5656
gArgs.AddArg("-rescan", "Rescan the block chain for missing wallet transactions on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
57-
gArgs.AddArg("-salvagewallet", "Attempt to recover private keys from a corrupt wallet on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
5857
gArgs.AddArg("-spendzeroconfchange", strprintf("Spend unconfirmed change when sending transactions (default: %u)", DEFAULT_SPEND_ZEROCONF_CHANGE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
5958
gArgs.AddArg("-txconfirmtarget=<n>", strprintf("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)", DEFAULT_TX_CONFIRM_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
6059
gArgs.AddArg("-wallet=<path>", "Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
@@ -89,16 +88,6 @@ bool WalletInit::ParameterInteraction() const
8988
LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -walletbroadcast=0\n", __func__);
9089
}
9190

92-
if (gArgs.GetBoolArg("-salvagewallet", false)) {
93-
if (is_multiwallet) {
94-
return InitError(strprintf(Untranslated("%s is only allowed with a single wallet file"), "-salvagewallet"));
95-
}
96-
// Rewrite just private keys: rescan to find transactions
97-
if (gArgs.SoftSetBoolArg("-rescan", true)) {
98-
LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__);
99-
}
100-
}
101-
10291
bool zapwallettxes = gArgs.GetBoolArg("-zapwallettxes", false);
10392
// -zapwallettxes implies dropping the mempool on startup
10493
if (zapwallettxes && gArgs.SoftSetBoolArg("-persistmempool", false)) {

src/wallet/load.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,6 @@ bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wal
3737

3838
chain.initMessage(_("Verifying wallet(s)...").translated);
3939

40-
// Parameter interaction code should have thrown an error if -salvagewallet
41-
// was enabled with more than wallet file, so the wallet_files size check
42-
// here should have no effect.
43-
bool salvage_wallet = gArgs.GetBoolArg("-salvagewallet", false) && wallet_files.size() <= 1;
44-
4540
// Keep track of each wallet absolute path to detect duplicates.
4641
std::set<fs::path> wallet_paths;
4742

@@ -55,7 +50,7 @@ bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wal
5550

5651
bilingual_str error_string;
5752
std::vector<bilingual_str> warnings;
58-
bool verify_success = CWallet::Verify(chain, location, salvage_wallet, error_string, warnings);
53+
bool verify_success = CWallet::Verify(chain, location, error_string, warnings);
5954
if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n")));
6055
if (!verify_success) {
6156
chain.initError(error_string);

src/wallet/load.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ class Chain;
1616
} // namespace interfaces
1717

1818
//! Responsible for reading and validating the -wallet arguments and verifying the wallet database.
19-
//! This function will perform salvage on the wallet if requested, as long as only one wallet is
20-
//! being loaded (WalletInit::ParameterInteraction() forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet).
2119
bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files);
2220

2321
//! Load wallet databases.

0 commit comments

Comments
 (0)