Line data Source code
1 : // Copyright (c) 2009-2010 Satoshi Nakamoto
2 : // Copyright (c) 2009-2021 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 : namespace wallet {
15 : /* End of headers, beginning of key/value data */
16 : static const char *HEADER_END = "HEADER=END";
17 : /* End of key/value data */
18 : static const char *DATA_END = "DATA=END";
19 : typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
20 :
21 52 : static bool KeyFilter(const std::string& type)
22 : {
23 52 : return WalletBatch::IsKeyType(type) || type == DBKeys::HDCHAIN;
24 : }
25 :
26 2 : bool RecoverDatabaseFile(const ArgsManager& args, const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
27 : {
28 2 : DatabaseOptions options;
29 : DatabaseStatus status;
30 2 : ReadDatabaseArgs(args, options);
31 2 : options.require_existing = true;
32 2 : options.verify = false;
33 2 : options.require_format = DatabaseFormat::BERKELEY;
34 2 : std::unique_ptr<WalletDatabase> database = MakeDatabase(file_path, options, status, error);
35 2 : if (!database) return false;
36 :
37 2 : BerkeleyDatabase& berkeley_database = static_cast<BerkeleyDatabase&>(*database);
38 2 : std::string filename = berkeley_database.Filename();
39 2 : std::shared_ptr<BerkeleyEnvironment> env = berkeley_database.env;
40 :
41 2 : if (!env->Open(error)) {
42 0 : return false;
43 : }
44 :
45 : // Recovery procedure:
46 : // move wallet file to walletfilename.timestamp.bak
47 : // Call Salvage with fAggressive=true to
48 : // get as much data as possible.
49 : // Rewrite salvaged data to fresh wallet file
50 : // Set -rescan so any missing transactions will be
51 : // found.
52 2 : int64_t now = GetTime();
53 2 : std::string newFilename = strprintf("%s.%d.bak", filename, now);
54 :
55 4 : int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr,
56 2 : newFilename.c_str(), DB_AUTO_COMMIT);
57 2 : if (result != 0)
58 : {
59 0 : error = strprintf(Untranslated("Failed to rename %s to %s"), filename, newFilename);
60 0 : return false;
61 : }
62 :
63 : /**
64 : * Salvage data from a file. The DB_AGGRESSIVE flag is being used (see berkeley DB->verify() method documentation).
65 : * key/value pairs are appended to salvagedData which are then written out to a new wallet file.
66 : * NOTE: reads the entire database into memory, so cannot be used
67 : * for huge databases.
68 : */
69 2 : std::vector<KeyValPair> salvagedData;
70 :
71 2 : std::stringstream strDump;
72 :
73 2 : Db db(env->dbenv.get(), 0);
74 2 : result = db.verify(newFilename.c_str(), nullptr, &strDump, DB_SALVAGE | DB_AGGRESSIVE);
75 2 : if (result == DB_VERIFY_BAD) {
76 0 : warnings.push_back(Untranslated("Salvage: Database salvage found errors, all data may not be recoverable."));
77 0 : }
78 2 : if (result != 0 && result != DB_VERIFY_BAD) {
79 0 : error = strprintf(Untranslated("Salvage: Database salvage failed with result %d."), result);
80 0 : return false;
81 : }
82 :
83 : // Format of bdb dump is ascii lines:
84 : // header lines...
85 : // HEADER=END
86 : // hexadecimal key
87 : // hexadecimal value
88 : // ... repeated
89 : // DATA=END
90 :
91 2 : std::string strLine;
92 14 : while (!strDump.eof() && strLine != HEADER_END)
93 12 : getline(strDump, strLine); // Skip past header
94 :
95 2 : std::string keyHex, valueHex;
96 30 : while (!strDump.eof() && keyHex != DATA_END) {
97 28 : getline(strDump, keyHex);
98 28 : if (keyHex != DATA_END) {
99 26 : if (strDump.eof())
100 0 : break;
101 26 : getline(strDump, valueHex);
102 26 : if (valueHex == DATA_END) {
103 0 : warnings.push_back(Untranslated("Salvage: WARNING: Number of keys in data does not match number of values."));
104 0 : break;
105 : }
106 26 : salvagedData.emplace_back(ParseHex(keyHex), ParseHex(valueHex));
107 26 : }
108 : }
109 :
110 : bool fSuccess;
111 2 : if (keyHex != DATA_END) {
112 0 : warnings.push_back(Untranslated("Salvage: WARNING: Unexpected end of file while reading salvage output."));
113 0 : fSuccess = false;
114 0 : } else {
115 2 : fSuccess = (result == 0);
116 : }
117 :
118 2 : if (salvagedData.empty())
119 : {
120 0 : error = strprintf(Untranslated("Salvage(aggressive) found no records in %s."), newFilename);
121 0 : return false;
122 : }
123 :
124 2 : std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(env->dbenv.get(), 0);
125 4 : int ret = pdbCopy->open(nullptr, // Txn pointer
126 2 : filename.c_str(), // Filename
127 : "main", // Logical db name
128 : DB_BTREE, // Database type
129 : DB_CREATE, // Flags
130 : 0);
131 2 : if (ret > 0) {
132 0 : error = strprintf(Untranslated("Cannot create database file %s"), filename);
133 0 : pdbCopy->close(0);
134 0 : return false;
135 : }
136 :
137 2 : DbTxn* ptxn = env->TxnBegin();
138 2 : CWallet dummyWallet(/*chain=*/nullptr, /*coinjoin_loader=*/nullptr, "", gArgs, CreateDummyWalletDatabase());
139 2 : dummyWallet.SetupLegacyScriptPubKeyMan();
140 28 : for (KeyValPair& row : salvagedData)
141 : {
142 : /* Filter for only private key type KV pairs to be added to the salvaged wallet */
143 26 : CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
144 26 : CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
145 26 : std::string strType, strErr;
146 : bool fReadOK;
147 : {
148 : // Required in LoadKeyMetadata():
149 26 : LOCK(dummyWallet.cs_wallet);
150 26 : fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue, strType, strErr, KeyFilter);
151 26 : }
152 26 : if (!KeyFilter(strType)) {
153 24 : continue;
154 : }
155 2 : if (!fReadOK)
156 : {
157 0 : warnings.push_back(strprintf(Untranslated("WARNING: WalletBatch::Recover skipping %s: %s"), strType, strErr));
158 0 : continue;
159 : }
160 2 : Dbt datKey(row.first.data(), row.first.size());
161 2 : Dbt datValue(row.second.data(), row.second.size());
162 2 : int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
163 2 : if (ret2 > 0)
164 0 : fSuccess = false;
165 26 : }
166 2 : ptxn->commit(0);
167 2 : pdbCopy->close(0);
168 :
169 2 : return fSuccess;
170 2 : }
171 : } // namespace wallet
|