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