Bitcoin Core 22.99.0
P2P Digital Currency
salvage.cpp
Go to the documentation of this file.
1// Copyright (c) 2009-2010 Satoshi Nakamoto
2// Copyright (c) 2009-2020 The Bitcoin Core developers
3// Distributed under the MIT software license, see the accompanying
4// file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
6#include <fs.h>
7#include <streams.h>
8#include <util/translation.h>
9#include <wallet/bdb.h>
10#include <wallet/salvage.h>
11#include <wallet/wallet.h>
12#include <wallet/walletdb.h>
13
14/* End of headers, beginning of key/value data */
15static const char *HEADER_END = "HEADER=END";
16/* End of key/value data */
17static const char *DATA_END = "DATA=END";
18typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
19
20static bool KeyFilter(const std::string& type)
21{
22 return WalletBatch::IsKeyType(type) || type == DBKeys::HDCHAIN;
23}
24
25bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
26{
27 DatabaseOptions options;
28 DatabaseStatus status;
29 options.require_existing = true;
30 options.verify = false;
32 std::unique_ptr<WalletDatabase> database = MakeDatabase(file_path, options, status, error);
33 if (!database) return false;
34
35 BerkeleyDatabase& berkeley_database = static_cast<BerkeleyDatabase&>(*database);
36 std::string filename = berkeley_database.Filename();
37 std::shared_ptr<BerkeleyEnvironment> env = berkeley_database.env;
38
39 if (!env->Open(error)) {
40 return false;
41 }
42
43 // Recovery procedure:
44 // move wallet file to walletfilename.timestamp.bak
45 // Call Salvage with fAggressive=true to
46 // get as much data as possible.
47 // Rewrite salvaged data to fresh wallet file
48 // Rescan so any missing transactions will be
49 // found.
50 int64_t now = GetTime();
51 std::string newFilename = strprintf("%s.%d.bak", filename, now);
52
53 int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr,
54 newFilename.c_str(), DB_AUTO_COMMIT);
55 if (result != 0)
56 {
57 error = strprintf(Untranslated("Failed to rename %s to %s"), filename, newFilename);
58 return false;
59 }
60
67 std::vector<KeyValPair> salvagedData;
68
69 std::stringstream strDump;
70
71 Db db(env->dbenv.get(), 0);
72 result = db.verify(newFilename.c_str(), nullptr, &strDump, DB_SALVAGE | DB_AGGRESSIVE);
73 if (result == DB_VERIFY_BAD) {
74 warnings.push_back(Untranslated("Salvage: Database salvage found errors, all data may not be recoverable."));
75 }
76 if (result != 0 && result != DB_VERIFY_BAD) {
77 error = strprintf(Untranslated("Salvage: Database salvage failed with result %d."), result);
78 return false;
79 }
80
81 // Format of bdb dump is ascii lines:
82 // header lines...
83 // HEADER=END
84 // hexadecimal key
85 // hexadecimal value
86 // ... repeated
87 // DATA=END
88
89 std::string strLine;
90 while (!strDump.eof() && strLine != HEADER_END)
91 getline(strDump, strLine); // Skip past header
92
93 std::string keyHex, valueHex;
94 while (!strDump.eof() && keyHex != DATA_END) {
95 getline(strDump, keyHex);
96 if (keyHex != DATA_END) {
97 if (strDump.eof())
98 break;
99 getline(strDump, valueHex);
100 if (valueHex == DATA_END) {
101 warnings.push_back(Untranslated("Salvage: WARNING: Number of keys in data does not match number of values."));
102 break;
103 }
104 salvagedData.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex)));
105 }
106 }
107
108 bool fSuccess;
109 if (keyHex != DATA_END) {
110 warnings.push_back(Untranslated("Salvage: WARNING: Unexpected end of file while reading salvage output."));
111 fSuccess = false;
112 } else {
113 fSuccess = (result == 0);
114 }
115
116 if (salvagedData.empty())
117 {
118 error = strprintf(Untranslated("Salvage(aggressive) found no records in %s."), newFilename);
119 return false;
120 }
121
122 std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(env->dbenv.get(), 0);
123 int ret = pdbCopy->open(nullptr, // Txn pointer
124 filename.c_str(), // Filename
125 "main", // Logical db name
126 DB_BTREE, // Database type
127 DB_CREATE, // Flags
128 0);
129 if (ret > 0) {
130 error = strprintf(Untranslated("Cannot create database file %s"), filename);
131 pdbCopy->close(0);
132 return false;
133 }
134
135 DbTxn* ptxn = env->TxnBegin();
136 CWallet dummyWallet(nullptr, "", CreateDummyWalletDatabase());
137 for (KeyValPair& row : salvagedData)
138 {
139 /* Filter for only private key type KV pairs to be added to the salvaged wallet */
140 CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
141 CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
142 std::string strType, strErr;
143 bool fReadOK;
144 {
145 // Required in LoadKeyMetadata():
146 LOCK(dummyWallet.cs_wallet);
147 fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, strType, strErr, KeyFilter);
148 }
149 if (!KeyFilter(strType)) {
150 continue;
151 }
152 if (!fReadOK)
153 {
154 warnings.push_back(strprintf(Untranslated("WARNING: WalletBatch::Recover skipping %s: %s"), strType, strErr));
155 continue;
156 }
157 Dbt datKey(row.first.data(), row.first.size());
158 Dbt datValue(row.second.data(), row.second.size());
159 int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
160 if (ret2 > 0)
161 fSuccess = false;
162 }
163 ptxn->commit(0);
164 pdbCopy->close(0);
165
166 return fSuccess;
167}
An instance of this class represents one database.
Definition: bdb.h:95
std::shared_ptr< BerkeleyEnvironment > env
Pointer to shared database environment.
Definition: bdb.h:156
std::string Filename() override
Return path to main database filename.
Definition: bdb.h:144
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:205
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:229
RecursiveMutex cs_wallet
Main wallet lock.
Definition: wallet.h:345
static bool IsKeyType(const std::string &strType)
Definition: walletdb.cpp:755
Path class wrapper to prepare application code for transition from boost::filesystem library to std::...
Definition: fs.h:34
static const int CLIENT_VERSION
bitcoind-res.rc includes this file, but it cannot cope with real c++ code.
Definition: clientversion.h:33
std::unique_ptr< WalletDatabase > MakeDatabase(const fs::path &path, const DatabaseOptions &options, DatabaseStatus &status, bilingual_str &error)
Definition: walletdb.cpp:1103
DatabaseStatus
Definition: db.h:212
const std::string HDCHAIN
Definition: walletdb.cpp:40
std::pair< std::vector< unsigned char >, std::vector< unsigned char > > KeyValPair
Definition: salvage.cpp:18
static const char * HEADER_END
Definition: salvage.cpp:15
static const char * DATA_END
Definition: salvage.cpp:17
bool RecoverDatabaseFile(const fs::path &file_path, bilingual_str &error, std::vector< bilingual_str > &warnings)
Definition: salvage.cpp:25
static bool KeyFilter(const std::string &type)
Definition: salvage.cpp:20
@ SER_DISK
Definition: serialize.h:139
std::vector< unsigned char > ParseHex(const char *psz)
bool verify
Definition: db.h:209
std::optional< DatabaseFormat > require_format
Definition: db.h:206
bool require_existing
Definition: db.h:204
Bilingual messages:
Definition: translation.h:16
#define LOCK(cs)
Definition: sync.h:226
bool error(const char *fmt, const Args &... args)
Definition: system.h:49
int64_t GetTime()
DEPRECATED Use either GetTimeSeconds (not mockable) or GetTime<T> (mockable)
Definition: time.cpp:26
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1164
bilingual_str Untranslated(std::string original)
Mark a bilingual_str as untranslated.
Definition: translation.h:46
static bool ReadKeyValue(CWallet *pwallet, CDataStream &ssKey, CDataStream &ssValue, CWalletScanState &wss, std::string &strType, std::string &strErr, const KeyFilterFn &filter_fn=nullptr) EXCLUSIVE_LOCKS_REQUIRED(pwallet -> cs_wallet)
Definition: walletdb.cpp:321
std::unique_ptr< WalletDatabase > CreateDummyWalletDatabase()
Return object for accessing dummy database with no read/write capabilities.
Definition: walletdb.cpp:1183