diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp index e4619c69d..c1444be23 100644 --- a/src/wallet/salvage.cpp +++ b/src/wallet/salvage.cpp @@ -1,166 +1,171 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2020 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include /* End of headers, beginning of key/value data */ static const char *HEADER_END = "HEADER=END"; /* End of key/value data */ static const char *DATA_END = "DATA=END"; typedef std::pair, std::vector> KeyValPair; -bool RecoverDatabaseFile(const fs::path &file_path) { +bool RecoverDatabaseFile(const fs::path &file_path, bilingual_str &error, + std::vector &warnings) { std::string filename; std::shared_ptr env = GetWalletEnv(file_path, filename); - bilingual_str open_err; - if (!env->Open(open_err)) { - tfm::format(std::cerr, "%s\n", open_err.original); + if (!env->Open(error)) { return false; } // Recovery procedure: // move wallet file to walletfilename.timestamp.bak // Call Salvage with fAggressive=true to // get as much data as possible. // Rewrite salvaged data to fresh wallet file // Set -rescan so any missing transactions will be // found. int64_t now = GetTime(); std::string newFilename = strprintf("%s.%d.bak", filename, now); int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr, newFilename.c_str(), DB_AUTO_COMMIT); - if (result == 0) { - LogPrintf("Renamed %s to %s\n", filename, newFilename); - } else { - LogPrintf("Failed to rename %s to %s\n", filename, newFilename); + if (result != 0) { + error = strprintf(Untranslated("Failed to rename %s to %s"), filename, + newFilename); return false; } /** * Salvage data from a file. The DB_AGGRESSIVE flag is being used (see * berkeley DB->verify() method documentation). key/value pairs are appended * to salvagedData which are then written out to a new wallet file. NOTE: * reads the entire database into memory, so cannot be used for huge * databases. */ std::vector salvagedData; std::stringstream strDump; Db db(env->dbenv.get(), 0); result = db.verify(newFilename.c_str(), nullptr, &strDump, DB_SALVAGE | DB_AGGRESSIVE); if (result == DB_VERIFY_BAD) { - LogPrintf("Salvage: Database salvage found errors, all data may not be " - "recoverable.\n"); + warnings.push_back( + Untranslated("Salvage: Database salvage found errors, all data may " + "not be recoverable.")); } if (result != 0 && result != DB_VERIFY_BAD) { - LogPrintf("Salvage: Database salvage failed with result %d.\n", result); + error = strprintf( + Untranslated("Salvage: Database salvage failed with result %d."), + result); return false; } // Format of bdb dump is ascii lines: // header lines... // HEADER=END // hexadecimal key // hexadecimal value // ... repeated // DATA=END std::string strLine; while (!strDump.eof() && strLine != HEADER_END) { getline(strDump, strLine); // Skip past header } std::string keyHex, valueHex; while (!strDump.eof() && keyHex != DATA_END) { getline(strDump, keyHex); if (keyHex != DATA_END) { if (strDump.eof()) { break; } getline(strDump, valueHex); if (valueHex == DATA_END) { - LogPrintf("Salvage: WARNING: Number of keys in data does not " - "match number of values.\n"); + warnings.push_back( + Untranslated("Salvage: WARNING: Number of keys in data " + "does not match number of values.")); break; } salvagedData.push_back( make_pair(ParseHex(keyHex), ParseHex(valueHex))); } } bool fSuccess; if (keyHex != DATA_END) { - LogPrintf("Salvage: WARNING: Unexpected end of file while reading " - "salvage output.\n"); + warnings.push_back(Untranslated("Salvage: WARNING: Unexpected end of " + "file while reading salvage output.")); fSuccess = false; } else { fSuccess = (result == 0); } if (salvagedData.empty()) { - LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename); + error = strprintf( + Untranslated("Salvage(aggressive) found no records in %s."), + newFilename); return false; } - LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size()); std::unique_ptr pdbCopy = std::make_unique(env->dbenv.get(), 0); int ret = pdbCopy->open(nullptr, // Txn pointer filename.c_str(), // Filename "main", // Logical db name DB_BTREE, // Database type DB_CREATE, // Flags 0); if (ret > 0) { - LogPrintf("Cannot create database file %s\n", filename); + error = + strprintf(Untranslated("Cannot create database file %s"), filename); pdbCopy->close(0); return false; } DbTxn *ptxn = env->TxnBegin(); CWallet dummyWallet(nullptr, WalletLocation(), CreateDummyWalletDatabase()); for (KeyValPair &row : salvagedData) { /* Filter for only private key type KV pairs to be added to the salvaged * wallet */ CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION); CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION); std::string strType, strErr; bool fReadOK; { // Required in LoadKeyMetadata(): LOCK(dummyWallet.cs_wallet); fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, strType, strErr); } if (!WalletBatch::IsKeyType(strType) && strType != DBKeys::HDCHAIN) { continue; } if (!fReadOK) { - LogPrintf("WARNING: WalletBatch::Recover skipping %s: %s\n", - strType, strErr); + warnings.push_back(strprintf( + Untranslated("WARNING: WalletBatch::Recover skipping %s: %s"), + strType, strErr)); continue; } Dbt datKey(&row.first[0], row.first.size()); Dbt datValue(&row.second[0], row.second.size()); int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE); if (ret2 > 0) { fSuccess = false; } } ptxn->commit(0); pdbCopy->close(0); return fSuccess; } diff --git a/src/wallet/salvage.h b/src/wallet/salvage.h index 79a387fe3..209bc4699 100644 --- a/src/wallet/salvage.h +++ b/src/wallet/salvage.h @@ -1,14 +1,17 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2020 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLET_SALVAGE_H #define BITCOIN_WALLET_SALVAGE_H #include #include -bool RecoverDatabaseFile(const fs::path &file_path); +struct bilingual_str; + +bool RecoverDatabaseFile(const fs::path &file_path, bilingual_str &error, + std::vector &warnings); #endif // BITCOIN_WALLET_SALVAGE_H diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index 2cd00ee33..88ceece72 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -1,160 +1,171 @@ // Copyright (c) 2016-2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include namespace WalletTool { // The standard wallet deleter function blocks on the validation interface // queue, which doesn't exist for the bitcoin-wallet. Define our own // deleter here. static void WalletToolReleaseWallet(CWallet *wallet) { wallet->WalletLogPrintf("Releasing wallet\n"); wallet->Flush(true); delete wallet; } static std::shared_ptr CreateWallet(const std::string &name, const fs::path &path) { if (fs::exists(path)) { tfm::format(std::cerr, "Error: File exists already\n"); return nullptr; } // dummy chain interface std::shared_ptr wallet_instance( new CWallet(nullptr /* chain */, WalletLocation(name), CreateWalletDatabase(path)), WalletToolReleaseWallet); LOCK(wallet_instance->cs_wallet); bool first_run = true; DBErrors load_wallet_ret = wallet_instance->LoadWallet(first_run); if (load_wallet_ret != DBErrors::LOAD_OK) { tfm::format(std::cerr, "Error creating %s", name); return nullptr; } wallet_instance->SetMinVersion(FEATURE_HD_SPLIT); // generate a new HD seed auto spk_man = wallet_instance->GetOrCreateLegacyScriptPubKeyMan(); CPubKey seed = spk_man->GenerateNewSeed(); spk_man->SetHDSeed(seed); tfm::format(std::cout, "Topping up keypool...\n"); wallet_instance->TopUpKeyPool(); return wallet_instance; } static std::shared_ptr LoadWallet(const std::string &name, const fs::path &path) { if (!fs::exists(path)) { tfm::format(std::cerr, "Error: Wallet files does not exist\n"); return nullptr; } // dummy chain interface std::shared_ptr wallet_instance( new CWallet(nullptr /* chain */, WalletLocation(name), CreateWalletDatabase(path)), WalletToolReleaseWallet); DBErrors load_wallet_ret; try { bool first_run; load_wallet_ret = wallet_instance->LoadWallet(first_run); } catch (const std::runtime_error &) { tfm::format( std::cerr, "Error loading %s. Is wallet being used by another process?\n", name); return nullptr; } if (load_wallet_ret != DBErrors::LOAD_OK) { wallet_instance = nullptr; if (load_wallet_ret == DBErrors::CORRUPT) { tfm::format(std::cerr, "Error loading %s: Wallet corrupted", name); return nullptr; } else if (load_wallet_ret == DBErrors::NONCRITICAL_ERROR) { tfm::format( std::cerr, "Error reading %s! All keys read correctly, but transaction " "data or address book entries might be missing or incorrect.", name); } else if (load_wallet_ret == DBErrors::TOO_NEW) { tfm::format(std::cerr, "Error loading %s: Wallet requires newer version of %s", name, PACKAGE_NAME); return nullptr; } else if (load_wallet_ret == DBErrors::NEED_REWRITE) { tfm::format(std::cerr, "Wallet needed to be rewritten: restart %s to complete", PACKAGE_NAME); return nullptr; } else { tfm::format(std::cerr, "Error loading %s", name); return nullptr; } } return wallet_instance; } static void WalletShowInfo(CWallet *wallet_instance) { LOCK(wallet_instance->cs_wallet); tfm::format(std::cout, "Wallet info\n===========\n"); tfm::format(std::cout, "Encrypted: %s\n", wallet_instance->IsCrypted() ? "yes" : "no"); tfm::format(std::cout, "HD (hd seed available): %s\n", wallet_instance->IsHDEnabled() ? "yes" : "no"); tfm::format(std::cout, "Keypool Size: %u\n", wallet_instance->GetKeyPoolSize()); tfm::format(std::cout, "Transactions: %zu\n", wallet_instance->mapWallet.size()); tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->m_address_book.size()); } bool ExecuteWalletToolFunc(const std::string &command, const std::string &name) { fs::path path = fs::absolute(name, GetWalletDir()); if (command == "create") { std::shared_ptr wallet_instance = CreateWallet(name, path); if (wallet_instance) { WalletShowInfo(wallet_instance.get()); wallet_instance->Flush(true); } } else if (command == "info" || command == "salvage") { if (!fs::exists(path)) { tfm::format(std::cerr, "Error: no wallet file at %s\n", name); return false; } if (command == "info") { std::shared_ptr wallet_instance = LoadWallet(name, path); if (!wallet_instance) { return false; } WalletShowInfo(wallet_instance.get()); wallet_instance->Flush(true); } else if (command == "salvage") { - return RecoverDatabaseFile(path); + bilingual_str error; + std::vector warnings; + bool ret = RecoverDatabaseFile(path, error, warnings); + if (!ret) { + for (const auto &warning : warnings) { + tfm::format(std::cerr, "%s\n", warning.original); + } + if (!error.empty()) { + tfm::format(std::cerr, "%s\n", error.original); + } + } + return ret; } } else { tfm::format(std::cerr, "Invalid command: %s\n", command); return false; } return true; } } // namespace WalletTool