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