salvage.cpp raw
1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Copyright (c) 2009-2021 The Limenka 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 <streams.h>
7 #include <util/fs.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 #include <db_cxx.h>
15
16 namespace wallet {
17 /* End of headers, beginning of key/value data */
18 static const char *HEADER_END = "HEADER=END";
19 /* End of key/value data */
20 static const char *DATA_END = "DATA=END";
21 typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
22
23 class DummyCursor : public DatabaseCursor
24 {
25 Status Next(DataStream& key, DataStream& value) override { return Status::FAIL; }
26 };
27
28 /** RAII class that provides access to a DummyDatabase. Never fails. */
29 class DummyBatch : public DatabaseBatch
30 {
31 private:
32 bool ReadKey(DataStream&& key, DataStream& value) override { return true; }
33 bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite=true) override { return true; }
34 bool EraseKey(DataStream&& key) override { return true; }
35 bool HasKey(DataStream&& key) override { return true; }
36 bool ErasePrefix(Span<const std::byte> prefix) override { return true; }
37
38 public:
39 void Flush() override {}
40 void Close() override {}
41
42 std::unique_ptr<DatabaseCursor> GetNewCursor() override { return std::make_unique<DummyCursor>(); }
43 std::unique_ptr<DatabaseCursor> GetNewPrefixCursor(Span<const std::byte> prefix) override { return GetNewCursor(); }
44 bool TxnBegin() override { return true; }
45 bool TxnCommit() override { return true; }
46 bool TxnAbort() override { return true; }
47 bool HasActiveTxn() override { return false; }
48 };
49
50 /** A dummy WalletDatabase that does nothing and never fails. Only used by salvage.
51 **/
52 class DummyDatabase : public WalletDatabase
53 {
54 public:
55 void Open() override {};
56 void AddRef() override {}
57 void RemoveRef() override {}
58 bool Rewrite(const char* pszSkip=nullptr) override { return true; }
59 bool Backup(const std::string& strDest) const override { return true; }
60 void Close() override {}
61 void Flush() override {}
62 bool PeriodicFlush() override { return true; }
63 void IncrementUpdateCounter() override { ++nUpdateCounter; }
64 void ReloadDbEnv() override {}
65 std::string Filename() override { return "dummy"; }
66 std::vector<fs::path> Files() override { return {}; }
67 std::string Format() override { return "dummy"; }
68 std::unique_ptr<DatabaseBatch> MakeBatch(bool flush_on_close = true) override { return std::make_unique<DummyBatch>(); }
69 };
70
71 bool RecoverDatabaseFile(const ArgsManager& args, const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
72 {
73 DatabaseOptions options;
74 DatabaseStatus status;
75 ReadDatabaseArgs(args, options);
76 options.require_existing = true;
77 options.verify = false;
78 options.require_format = DatabaseFormat::BERKELEY;
79 std::unique_ptr<WalletDatabase> database = MakeDatabase(file_path, options, status, error);
80 if (!database) return false;
81
82 BerkeleyDatabase& berkeley_database = static_cast<BerkeleyDatabase&>(*database);
83 std::string filename = berkeley_database.Filename();
84 std::shared_ptr<BerkeleyEnvironment> env = berkeley_database.env;
85
86 if (!env->Open(error)) {
87 return false;
88 }
89
90 // Recovery procedure:
91 // move wallet file to walletfilename.timestamp.bak
92 // Call Salvage with fAggressive=true to
93 // get as much data as possible.
94 // Rewrite salvaged data to fresh wallet file
95 // Rescan so any missing transactions will be
96 // found.
97 int64_t now = GetTime();
98 std::string newFilename = strprintf("%s.%d.bak", filename, now);
99
100 int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr,
101 newFilename.c_str(), DB_AUTO_COMMIT);
102 if (result != 0)
103 {
104 error = Untranslated(strprintf("Failed to rename %s to %s", filename, newFilename));
105 return false;
106 }
107
108 /**
109 * Salvage data from a file. The DB_AGGRESSIVE flag is being used (see berkeley DB->verify() method documentation).
110 * key/value pairs are appended to salvagedData which are then written out to a new wallet file.
111 * NOTE: reads the entire database into memory, so cannot be used
112 * for huge databases.
113 */
114 std::vector<KeyValPair> salvagedData;
115
116 std::stringstream strDump;
117
118 Db db(env->dbenv.get(), 0);
119 result = db.verify(newFilename.c_str(), nullptr, &strDump, DB_SALVAGE | DB_AGGRESSIVE);
120 if (result == DB_VERIFY_BAD) {
121 warnings.emplace_back(Untranslated("Salvage: Database salvage found errors, all data may not be recoverable."));
122 }
123 if (result != 0 && result != DB_VERIFY_BAD) {
124 error = Untranslated(strprintf("Salvage: Database salvage failed with result %d.", result));
125 return false;
126 }
127
128 // Format of bdb dump is ascii lines:
129 // header lines...
130 // HEADER=END
131 // hexadecimal key
132 // hexadecimal value
133 // ... repeated
134 // DATA=END
135
136 std::string strLine;
137 while (!strDump.eof() && strLine != HEADER_END)
138 getline(strDump, strLine); // Skip past header
139
140 std::string keyHex, valueHex;
141 while (!strDump.eof() && keyHex != DATA_END) {
142 getline(strDump, keyHex);
143 if (keyHex != DATA_END) {
144 if (strDump.eof())
145 break;
146 getline(strDump, valueHex);
147 if (valueHex == DATA_END) {
148 warnings.emplace_back(Untranslated("Salvage: WARNING: Number of keys in data does not match number of values."));
149 break;
150 }
151 salvagedData.emplace_back(ParseHex(keyHex), ParseHex(valueHex));
152 }
153 }
154
155 bool fSuccess;
156 if (keyHex != DATA_END) {
157 warnings.emplace_back(Untranslated("Salvage: WARNING: Unexpected end of file while reading salvage output."));
158 fSuccess = false;
159 } else {
160 fSuccess = (result == 0);
161 }
162
163 if (salvagedData.empty())
164 {
165 error = Untranslated(strprintf("Salvage(aggressive) found no records in %s.", newFilename));
166 return false;
167 }
168
169 std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(env->dbenv.get(), 0);
170 int ret = pdbCopy->open(nullptr, // Txn pointer
171 filename.c_str(), // Filename
172 "main", // Logical db name
173 DB_BTREE, // Database type
174 DB_CREATE, // Flags
175 0);
176 if (ret > 0) {
177 error = Untranslated(strprintf("Cannot create database file %s", filename));
178 pdbCopy->close(0);
179 return false;
180 }
181
182 DbTxn* ptxn = env->TxnBegin(DB_TXN_WRITE_NOSYNC);
183 CWallet dummyWallet(nullptr, "", std::make_unique<DummyDatabase>());
184 for (KeyValPair& row : salvagedData)
185 {
186 /* Filter for only private key type KV pairs to be added to the salvaged wallet */
187 DataStream ssKey{row.first};
188 DataStream ssValue(row.second);
189 std::string strType, strErr;
190
191 // We only care about KEY, MASTER_KEY, CRYPTED_KEY, and HDCHAIN types
192 ssKey >> strType;
193 bool fReadOK = false;
194 if (strType == DBKeys::KEY) {
195 fReadOK = LoadKey(&dummyWallet, ssKey, ssValue, strErr);
196 } else if (strType == DBKeys::CRYPTED_KEY) {
197 fReadOK = LoadCryptedKey(&dummyWallet, ssKey, ssValue, strErr);
198 } else if (strType == DBKeys::MASTER_KEY) {
199 fReadOK = LoadEncryptionKey(&dummyWallet, ssKey, ssValue, strErr);
200 } else if (strType == DBKeys::HDCHAIN) {
201 fReadOK = LoadHDChain(&dummyWallet, ssValue, strErr);
202 } else {
203 continue;
204 }
205
206 if (!fReadOK)
207 {
208 warnings.push_back(Untranslated(strprintf("WARNING: WalletBatch::Recover skipping %s: %s", strType, strErr)));
209 continue;
210 }
211 Dbt datKey(row.first.data(), row.first.size());
212 Dbt datValue(row.second.data(), row.second.size());
213 int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
214 if (ret2 > 0)
215 fSuccess = false;
216 }
217 ptxn->commit(0);
218 pdbCopy->close(0);
219
220 return fSuccess;
221 }
222 } // namespace wallet
223