/Users/eugenesiegel/btc/bitcoin/src/wallet/dump.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2020-present The Bitcoin Core developers |
2 | | // Distributed under the MIT software license, see the accompanying |
3 | | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | | |
5 | | #include <wallet/dump.h> |
6 | | |
7 | | #include <common/args.h> |
8 | | #include <util/fs.h> |
9 | | #include <util/translation.h> |
10 | | #include <wallet/wallet.h> |
11 | | #include <wallet/walletdb.h> |
12 | | |
13 | | #include <algorithm> |
14 | | #include <fstream> |
15 | | #include <memory> |
16 | | #include <string> |
17 | | #include <utility> |
18 | | #include <vector> |
19 | | |
20 | | namespace wallet { |
21 | | static const std::string DUMP_MAGIC = "BITCOIN_CORE_WALLET_DUMP"; |
22 | | uint32_t DUMP_VERSION = 1; |
23 | | |
24 | | bool DumpWallet(const ArgsManager& args, WalletDatabase& db, bilingual_str& error) |
25 | 0 | { |
26 | | // Get the dumpfile |
27 | 0 | std::string dump_filename = args.GetArg("-dumpfile", ""); |
28 | 0 | if (dump_filename.empty()) { |
29 | 0 | error = _("No dump file provided. To use dump, -dumpfile=<filename> must be provided."); |
30 | 0 | return false; |
31 | 0 | } |
32 | | |
33 | 0 | fs::path path = fs::PathFromString(dump_filename); |
34 | 0 | path = fs::absolute(path); |
35 | 0 | if (fs::exists(path)) { |
36 | 0 | error = strprintf(_("File %s already exists. If you are sure this is what you want, move it out of the way first."), fs::PathToString(path)); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
37 | 0 | return false; |
38 | 0 | } |
39 | 0 | std::ofstream dump_file; |
40 | 0 | dump_file.open(path); |
41 | 0 | if (dump_file.fail()) { |
42 | 0 | error = strprintf(_("Unable to open %s for writing"), fs::PathToString(path)); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
43 | 0 | return false; |
44 | 0 | } |
45 | | |
46 | 0 | HashWriter hasher{}; |
47 | |
|
48 | 0 | std::unique_ptr<DatabaseBatch> batch = db.MakeBatch(); |
49 | |
|
50 | 0 | bool ret = true; |
51 | 0 | std::unique_ptr<DatabaseCursor> cursor = batch->GetNewCursor(); |
52 | 0 | if (!cursor) { |
53 | 0 | error = _("Error: Couldn't create cursor into database"); |
54 | 0 | ret = false; |
55 | 0 | } |
56 | | |
57 | | // Write out a magic string with version |
58 | 0 | std::string line = strprintf("%s,%u\n", DUMP_MAGIC, DUMP_VERSION); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
59 | 0 | dump_file.write(line.data(), line.size()); |
60 | 0 | hasher << std::span{line}; |
61 | | |
62 | | // Write out the file format |
63 | 0 | std::string format = db.Format(); |
64 | | // BDB files that are opened using BerkeleyRODatabase have it's format as "bdb_ro" |
65 | | // We want to override that format back to "bdb" |
66 | 0 | if (format == "bdb_ro") { |
67 | 0 | format = "bdb"; |
68 | 0 | } |
69 | 0 | line = strprintf("%s,%s\n", "format", format); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
70 | 0 | dump_file.write(line.data(), line.size()); |
71 | 0 | hasher << std::span{line}; |
72 | |
|
73 | 0 | if (ret) { |
74 | | |
75 | | // Read the records |
76 | 0 | while (true) { |
77 | 0 | DataStream ss_key{}; |
78 | 0 | DataStream ss_value{}; |
79 | 0 | DatabaseCursor::Status status = cursor->Next(ss_key, ss_value); |
80 | 0 | if (status == DatabaseCursor::Status::DONE) { |
81 | 0 | ret = true; |
82 | 0 | break; |
83 | 0 | } else if (status == DatabaseCursor::Status::FAIL) { |
84 | 0 | error = _("Error reading next record from wallet database"); |
85 | 0 | ret = false; |
86 | 0 | break; |
87 | 0 | } |
88 | 0 | std::string key_str = HexStr(ss_key); |
89 | 0 | std::string value_str = HexStr(ss_value); |
90 | 0 | line = strprintf("%s,%s\n", key_str, value_str); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
91 | 0 | dump_file.write(line.data(), line.size()); |
92 | 0 | hasher << std::span{line}; |
93 | 0 | } |
94 | 0 | } |
95 | |
|
96 | 0 | cursor.reset(); |
97 | 0 | batch.reset(); |
98 | |
|
99 | 0 | if (ret) { |
100 | | // Write the hash |
101 | 0 | tfm::format(dump_file, "checksum,%s\n", HexStr(hasher.GetHash())); |
102 | 0 | dump_file.close(); |
103 | 0 | } else { |
104 | | // Remove the dumpfile on failure |
105 | 0 | dump_file.close(); |
106 | 0 | fs::remove(path); |
107 | 0 | } |
108 | |
|
109 | 0 | return ret; |
110 | 0 | } |
111 | | |
112 | | // The standard wallet deleter function blocks on the validation interface |
113 | | // queue, which doesn't exist for the bitcoin-wallet. Define our own |
114 | | // deleter here. |
115 | | static void WalletToolReleaseWallet(CWallet* wallet) |
116 | 0 | { |
117 | 0 | wallet->WalletLogPrintf("Releasing wallet\n"); |
118 | 0 | wallet->Close(); |
119 | 0 | delete wallet; |
120 | 0 | } |
121 | | |
122 | | bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings) |
123 | 0 | { |
124 | | // Get the dumpfile |
125 | 0 | std::string dump_filename = args.GetArg("-dumpfile", ""); |
126 | 0 | if (dump_filename.empty()) { |
127 | 0 | error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided."); |
128 | 0 | return false; |
129 | 0 | } |
130 | | |
131 | 0 | fs::path dump_path = fs::PathFromString(dump_filename); |
132 | 0 | dump_path = fs::absolute(dump_path); |
133 | 0 | if (!fs::exists(dump_path)) { |
134 | 0 | error = strprintf(_("Dump file %s does not exist."), fs::PathToString(dump_path)); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
135 | 0 | return false; |
136 | 0 | } |
137 | 0 | std::ifstream dump_file{dump_path}; |
138 | | |
139 | | // Compute the checksum |
140 | 0 | HashWriter hasher{}; |
141 | 0 | uint256 checksum; |
142 | | |
143 | | // Check the magic and version |
144 | 0 | std::string magic_key; |
145 | 0 | std::getline(dump_file, magic_key, ','); |
146 | 0 | std::string version_value; |
147 | 0 | std::getline(dump_file, version_value, '\n'); |
148 | 0 | if (magic_key != DUMP_MAGIC) { |
149 | 0 | error = strprintf(_("Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."), magic_key, DUMP_MAGIC); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
150 | 0 | dump_file.close(); |
151 | 0 | return false; |
152 | 0 | } |
153 | | // Check the version number (value of first record) |
154 | 0 | uint32_t ver; |
155 | 0 | if (!ParseUInt32(version_value, &ver)) { |
156 | 0 | error =strprintf(_("Error: Unable to parse version %u as a uint32_t"), version_value); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
157 | 0 | dump_file.close(); |
158 | 0 | return false; |
159 | 0 | } |
160 | 0 | if (ver != DUMP_VERSION) { |
161 | 0 | error = strprintf(_("Error: Dumpfile version is not supported. This version of bitcoin-wallet only supports version 1 dumpfiles. Got dumpfile with version %s"), version_value); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
162 | 0 | dump_file.close(); |
163 | 0 | return false; |
164 | 0 | } |
165 | 0 | std::string magic_hasher_line = strprintf("%s,%s\n", magic_key, version_value); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
166 | 0 | hasher << std::span{magic_hasher_line}; |
167 | | |
168 | | // Get the stored file format |
169 | 0 | std::string format_key; |
170 | 0 | std::getline(dump_file, format_key, ','); |
171 | 0 | std::string format_value; |
172 | 0 | std::getline(dump_file, format_value, '\n'); |
173 | 0 | if (format_key != "format") { |
174 | 0 | error = strprintf(_("Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), format_key); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
175 | 0 | dump_file.close(); |
176 | 0 | return false; |
177 | 0 | } |
178 | | // Make sure that the dump was created from a sqlite database only as that is the only |
179 | | // type of database that we still support. |
180 | | // Other formats such as BDB should not be loaded into a sqlite database since they also |
181 | | // use a different type of wallet entirely which is no longer compatible with this software. |
182 | 0 | if (format_value != "sqlite") { |
183 | 0 | error = strprintf(_("Error: Dumpfile specifies an unsupported database format (%s). Only sqlite database dumps are supported"), format_value); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
184 | 0 | return false; |
185 | 0 | } |
186 | 0 | std::string format_hasher_line = strprintf("%s,%s\n", format_key, format_value); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
187 | 0 | hasher << std::span{format_hasher_line}; |
188 | |
|
189 | 0 | DatabaseOptions options; |
190 | 0 | DatabaseStatus status; |
191 | 0 | ReadDatabaseArgs(args, options); |
192 | 0 | options.require_create = true; |
193 | 0 | options.require_format = DatabaseFormat::SQLITE; |
194 | 0 | std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error); |
195 | 0 | if (!database) return false; |
196 | | |
197 | | // dummy chain interface |
198 | 0 | bool ret = true; |
199 | 0 | std::shared_ptr<CWallet> wallet(new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet); |
200 | 0 | { |
201 | 0 | LOCK(wallet->cs_wallet); Line | Count | Source | 257 | 0 | #define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) Line | Count | Source | 11 | 0 | #define UNIQUE_NAME(name) PASTE2(name, __COUNTER__) Line | Count | Source | 9 | 0 | #define PASTE2(x, y) PASTE(x, y) Line | Count | Source | 8 | 0 | #define PASTE(x, y) x ## y |
|
|
|
|
202 | 0 | DBErrors load_wallet_ret = wallet->LoadWallet(); |
203 | 0 | if (load_wallet_ret != DBErrors::LOAD_OK) { |
204 | 0 | error = strprintf(_("Error creating %s"), name); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
205 | 0 | return false; |
206 | 0 | } |
207 | | |
208 | | // Get the database handle |
209 | 0 | WalletDatabase& db = wallet->GetDatabase(); |
210 | 0 | std::unique_ptr<DatabaseBatch> batch = db.MakeBatch(); |
211 | 0 | batch->TxnBegin(); |
212 | | |
213 | | // Read the records from the dump file and write them to the database |
214 | 0 | while (dump_file.good()) { |
215 | 0 | std::string key; |
216 | 0 | std::getline(dump_file, key, ','); |
217 | 0 | std::string value; |
218 | 0 | std::getline(dump_file, value, '\n'); |
219 | |
|
220 | 0 | if (key == "checksum") { |
221 | 0 | std::vector<unsigned char> parsed_checksum = ParseHex(value); |
222 | 0 | if (parsed_checksum.size() != checksum.size()) { |
223 | 0 | error = Untranslated("Error: Checksum is not the correct size"); |
224 | 0 | ret = false; |
225 | 0 | break; |
226 | 0 | } |
227 | 0 | std::copy(parsed_checksum.begin(), parsed_checksum.end(), checksum.begin()); |
228 | 0 | break; |
229 | 0 | } |
230 | | |
231 | 0 | std::string line = strprintf("%s,%s\n", key, value); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
232 | 0 | hasher << std::span{line}; |
233 | |
|
234 | 0 | if (key.empty() || value.empty()) { |
235 | 0 | continue; |
236 | 0 | } |
237 | | |
238 | 0 | if (!IsHex(key)) { |
239 | 0 | error = strprintf(_("Error: Got key that was not hex: %s"), key); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
240 | 0 | ret = false; |
241 | 0 | break; |
242 | 0 | } |
243 | 0 | if (!IsHex(value)) { |
244 | 0 | error = strprintf(_("Error: Got value that was not hex: %s"), value); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
245 | 0 | ret = false; |
246 | 0 | break; |
247 | 0 | } |
248 | | |
249 | 0 | std::vector<unsigned char> k = ParseHex(key); |
250 | 0 | std::vector<unsigned char> v = ParseHex(value); |
251 | 0 | if (!batch->Write(std::span{k}, std::span{v})) { |
252 | 0 | error = strprintf(_("Error: Unable to write record to new wallet")); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
253 | 0 | ret = false; |
254 | 0 | break; |
255 | 0 | } |
256 | 0 | } |
257 | |
|
258 | 0 | if (ret) { |
259 | 0 | uint256 comp_checksum = hasher.GetHash(); |
260 | 0 | if (checksum.IsNull()) { |
261 | 0 | error = _("Error: Missing checksum"); |
262 | 0 | ret = false; |
263 | 0 | } else if (checksum != comp_checksum) { |
264 | 0 | error = strprintf(_("Error: Dumpfile checksum does not match. Computed %s, expected %s"), HexStr(comp_checksum), HexStr(checksum)); Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
265 | 0 | ret = false; |
266 | 0 | } |
267 | 0 | } |
268 | |
|
269 | 0 | if (ret) { |
270 | 0 | batch->TxnCommit(); |
271 | 0 | } else { |
272 | 0 | batch->TxnAbort(); |
273 | 0 | } |
274 | |
|
275 | 0 | batch.reset(); |
276 | |
|
277 | 0 | dump_file.close(); |
278 | 0 | } |
279 | 0 | wallet.reset(); // The pointer deleter will close the wallet for us. |
280 | | |
281 | | // Remove the wallet dir if we have a failure |
282 | 0 | if (!ret) { |
283 | 0 | fs::remove_all(wallet_path); |
284 | 0 | } |
285 | |
|
286 | 0 | return ret; |
287 | 0 | } |
288 | | } // namespace wallet |