/root/bitcoin/src/wallet/dump.cpp
Line | Count | Source |
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.std_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 its 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 | 0 | if (name.empty()) { |
125 | 0 | tfm::format(std::cerr, "Wallet name cannot be empty\n"); |
126 | 0 | return false; |
127 | 0 | } |
128 | | |
129 | | // Get the dumpfile |
130 | 0 | std::string dump_filename = args.GetArg("-dumpfile", ""); |
131 | 0 | if (dump_filename.empty()) { |
132 | 0 | error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided."); |
133 | 0 | return false; |
134 | 0 | } |
135 | | |
136 | 0 | fs::path dump_path = fs::PathFromString(dump_filename); |
137 | 0 | dump_path = fs::absolute(dump_path); |
138 | 0 | if (!fs::exists(dump_path)) { |
139 | 0 | error = strprintf(_("Dump file %s does not exist."), fs::PathToString(dump_path));Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
140 | 0 | return false; |
141 | 0 | } |
142 | 0 | std::ifstream dump_file{dump_path.std_path()}; |
143 | | |
144 | | // Compute the checksum |
145 | 0 | HashWriter hasher{}; |
146 | 0 | uint256 checksum; |
147 | | |
148 | | // Check the magic and version |
149 | 0 | std::string magic_key; |
150 | 0 | std::getline(dump_file, magic_key, ','); |
151 | 0 | std::string version_value; |
152 | 0 | std::getline(dump_file, version_value, '\n'); |
153 | 0 | if (magic_key != DUMP_MAGIC) { |
154 | 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 |
|
155 | 0 | dump_file.close(); |
156 | 0 | return false; |
157 | 0 | } |
158 | | // Check the version number (value of first record) |
159 | 0 | const auto ver{ToIntegral<uint32_t>(version_value)}; |
160 | 0 | if (!ver) { |
161 | 0 | error = strprintf(_("Error: Unable to parse version %u as a uint32_t"), version_value);Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
162 | 0 | dump_file.close(); |
163 | 0 | return false; |
164 | 0 | } |
165 | 0 | if (*ver != DUMP_VERSION) { |
166 | 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 |
|
167 | 0 | dump_file.close(); |
168 | 0 | return false; |
169 | 0 | } |
170 | 0 | std::string magic_hasher_line = strprintf("%s,%s\n", magic_key, version_value);Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
171 | 0 | hasher << std::span{magic_hasher_line}; |
172 | | |
173 | | // Get the stored file format |
174 | 0 | std::string format_key; |
175 | 0 | std::getline(dump_file, format_key, ','); |
176 | 0 | std::string format_value; |
177 | 0 | std::getline(dump_file, format_value, '\n'); |
178 | 0 | if (format_key != "format") { |
179 | 0 | error = strprintf(_("Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), format_key);Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
180 | 0 | dump_file.close(); |
181 | 0 | return false; |
182 | 0 | } |
183 | | // Make sure that the dump was created from a sqlite database only as that is the only |
184 | | // type of database that we still support. |
185 | | // Other formats such as BDB should not be loaded into a sqlite database since they also |
186 | | // use a different type of wallet entirely which is no longer compatible with this software. |
187 | 0 | if (format_value != "sqlite") { |
188 | 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 |
|
189 | 0 | return false; |
190 | 0 | } |
191 | 0 | std::string format_hasher_line = strprintf("%s,%s\n", format_key, format_value);Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
192 | 0 | hasher << std::span{format_hasher_line}; |
193 | |
|
194 | 0 | DatabaseOptions options; |
195 | 0 | DatabaseStatus status; |
196 | 0 | ReadDatabaseArgs(args, options); |
197 | 0 | options.require_create = true; |
198 | 0 | options.require_format = DatabaseFormat::SQLITE; |
199 | 0 | std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error); |
200 | 0 | if (!database) return false; |
201 | | |
202 | | // dummy chain interface |
203 | 0 | bool ret = true; |
204 | 0 | std::shared_ptr<CWallet> wallet(new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet); |
205 | 0 | { |
206 | | // Get the database handle |
207 | 0 | WalletDatabase& db = wallet->GetDatabase(); |
208 | 0 | std::unique_ptr<DatabaseBatch> batch = db.MakeBatch(); |
209 | 0 | batch->TxnBegin(); |
210 | | |
211 | | // Read the records from the dump file and write them to the database |
212 | 0 | while (dump_file.good()) { |
213 | 0 | std::string key; |
214 | 0 | std::getline(dump_file, key, ','); |
215 | 0 | std::string value; |
216 | 0 | std::getline(dump_file, value, '\n'); |
217 | |
|
218 | 0 | if (key == "checksum") { |
219 | 0 | std::vector<unsigned char> parsed_checksum = ParseHex(value); |
220 | 0 | if (parsed_checksum.size() != checksum.size()) { |
221 | 0 | error = Untranslated("Error: Checksum is not the correct size"); |
222 | 0 | ret = false; |
223 | 0 | break; |
224 | 0 | } |
225 | 0 | std::copy(parsed_checksum.begin(), parsed_checksum.end(), checksum.begin()); |
226 | 0 | break; |
227 | 0 | } |
228 | | |
229 | 0 | std::string line = strprintf("%s,%s\n", key, value);Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
230 | 0 | hasher << std::span{line}; |
231 | |
|
232 | 0 | if (key.empty() || value.empty()) { |
233 | 0 | continue; |
234 | 0 | } |
235 | | |
236 | 0 | if (!IsHex(key)) { |
237 | 0 | error = strprintf(_("Error: Got key that was not hex: %s"), key);Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
238 | 0 | ret = false; |
239 | 0 | break; |
240 | 0 | } |
241 | 0 | if (!IsHex(value)) { |
242 | 0 | error = strprintf(_("Error: Got value that was not hex: %s"), value);Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
243 | 0 | ret = false; |
244 | 0 | break; |
245 | 0 | } |
246 | | |
247 | 0 | std::vector<unsigned char> k = ParseHex(key); |
248 | 0 | std::vector<unsigned char> v = ParseHex(value); |
249 | 0 | if (!batch->Write(std::span{k}, std::span{v})) { |
250 | 0 | error = strprintf(_("Error: Unable to write record to new wallet"));Line | Count | Source | 1172 | 0 | #define strprintf tfm::format |
|
251 | 0 | ret = false; |
252 | 0 | break; |
253 | 0 | } |
254 | 0 | } |
255 | |
|
256 | 0 | if (ret) { |
257 | 0 | uint256 comp_checksum = hasher.GetHash(); |
258 | 0 | if (checksum.IsNull()) { |
259 | 0 | error = _("Error: Missing checksum"); |
260 | 0 | ret = false; |
261 | 0 | } else if (checksum != comp_checksum) { |
262 | 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 |
|
263 | 0 | ret = false; |
264 | 0 | } |
265 | 0 | } |
266 | |
|
267 | 0 | if (ret) { |
268 | 0 | batch->TxnCommit(); |
269 | 0 | } else { |
270 | 0 | batch->TxnAbort(); |
271 | 0 | } |
272 | |
|
273 | 0 | batch.reset(); |
274 | |
|
275 | 0 | dump_file.close(); |
276 | 0 | } |
277 | | // On failure, gather the paths to remove |
278 | 0 | std::vector<fs::path> paths_to_remove = wallet->GetDatabase().Files(); |
279 | 0 | if (!name.empty()) paths_to_remove.push_back(wallet_path); |
280 | |
|
281 | 0 | wallet.reset(); // The pointer deleter will close the wallet for us. |
282 | | |
283 | | // Remove the wallet dir if we have a failure |
284 | 0 | if (!ret) { |
285 | 0 | for (const auto& p : paths_to_remove) { |
286 | 0 | fs::remove(p); |
287 | 0 | } |
288 | 0 | } |
289 | |
|
290 | 0 | return ret; |
291 | 0 | } |
292 | | } // namespace wallet |