dump.cpp raw
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 {
26 // Get the dumpfile
27 std::string dump_filename = args.GetArg("-dumpfile", "");
28 if (dump_filename.empty()) {
29 error = _("No dump file provided. To use dump, -dumpfile=<filename> must be provided.");
30 return false;
31 }
32
33 fs::path path = fs::PathFromString(dump_filename);
34 path = fs::absolute(path);
35 if (fs::exists(path)) {
36 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));
37 return false;
38 }
39 std::ofstream dump_file;
40 dump_file.open(path.std_path());
41 if (dump_file.fail()) {
42 error = strprintf(_("Unable to open %s for writing"), fs::PathToString(path));
43 return false;
44 }
45
46 HashWriter hasher{};
47
48 std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
49
50 bool ret = true;
51 std::unique_ptr<DatabaseCursor> cursor = batch->GetNewCursor();
52 if (!cursor) {
53 error = _("Error: Couldn't create cursor into database");
54 ret = false;
55 }
56
57 // Write out a magic string with version
58 std::string line = strprintf("%s,%u\n", DUMP_MAGIC, DUMP_VERSION);
59 dump_file.write(line.data(), line.size());
60 hasher << std::span{line};
61
62 // Write out the file format
63 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 if (format == "bdb_ro") {
67 format = "bdb";
68 }
69 line = strprintf("%s,%s\n", "format", format);
70 dump_file.write(line.data(), line.size());
71 hasher << std::span{line};
72
73 if (ret) {
74
75 // Read the records
76 while (true) {
77 DataStream ss_key{};
78 DataStream ss_value{};
79 DatabaseCursor::Status status = cursor->Next(ss_key, ss_value);
80 if (status == DatabaseCursor::Status::DONE) {
81 ret = true;
82 break;
83 } else if (status == DatabaseCursor::Status::FAIL) {
84 error = _("Error reading next record from wallet database");
85 ret = false;
86 break;
87 }
88 std::string key_str = HexStr(ss_key);
89 std::string value_str = HexStr(ss_value);
90 line = strprintf("%s,%s\n", key_str, value_str);
91 dump_file.write(line.data(), line.size());
92 hasher << std::span{line};
93 }
94 }
95
96 cursor.reset();
97 batch.reset();
98
99 if (ret) {
100 // Write the hash
101 tfm::format(dump_file, "checksum,%s\n", HexStr(hasher.GetHash()));
102 dump_file.close();
103 } else {
104 // Remove the dumpfile on failure
105 dump_file.close();
106 fs::remove(path);
107 }
108
109 return ret;
110 }
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 {
117 wallet->WalletLogPrintf("Releasing wallet\n");
118 wallet->Close();
119 delete wallet;
120 }
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 {
124 if (name.empty()) {
125 tfm::format(std::cerr, "Wallet name cannot be empty\n");
126 return false;
127 }
128
129 // Get the dumpfile
130 std::string dump_filename = args.GetArg("-dumpfile", "");
131 if (dump_filename.empty()) {
132 error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.");
133 return false;
134 }
135
136 fs::path dump_path = fs::PathFromString(dump_filename);
137 dump_path = fs::absolute(dump_path);
138 if (!fs::exists(dump_path)) {
139 error = strprintf(_("Dump file %s does not exist."), fs::PathToString(dump_path));
140 return false;
141 }
142 std::ifstream dump_file{dump_path.std_path()};
143
144 // Compute the checksum
145 HashWriter hasher{};
146 uint256 checksum;
147
148 // Check the magic and version
149 std::string magic_key;
150 std::getline(dump_file, magic_key, ',');
151 std::string version_value;
152 std::getline(dump_file, version_value, '\n');
153 if (magic_key != DUMP_MAGIC) {
154 error = strprintf(_("Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."), magic_key, DUMP_MAGIC);
155 dump_file.close();
156 return false;
157 }
158 // Check the version number (value of first record)
159 const auto ver{ToIntegral<uint32_t>(version_value)};
160 if (!ver) {
161 error = strprintf(_("Error: Unable to parse version %u as a uint32_t"), version_value);
162 dump_file.close();
163 return false;
164 }
165 if (*ver != DUMP_VERSION) {
166 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);
167 dump_file.close();
168 return false;
169 }
170 std::string magic_hasher_line = strprintf("%s,%s\n", magic_key, version_value);
171 hasher << std::span{magic_hasher_line};
172
173 // Get the stored file format
174 std::string format_key;
175 std::getline(dump_file, format_key, ',');
176 std::string format_value;
177 std::getline(dump_file, format_value, '\n');
178 if (format_key != "format") {
179 error = strprintf(_("Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), format_key);
180 dump_file.close();
181 return false;
182 }
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 if (format_value != "sqlite") {
188 error = strprintf(_("Error: Dumpfile specifies an unsupported database format (%s). Only sqlite database dumps are supported"), format_value);
189 return false;
190 }
191 std::string format_hasher_line = strprintf("%s,%s\n", format_key, format_value);
192 hasher << std::span{format_hasher_line};
193
194 DatabaseOptions options;
195 DatabaseStatus status;
196 ReadDatabaseArgs(args, options);
197 options.require_create = true;
198 options.require_format = DatabaseFormat::SQLITE;
199 std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error);
200 if (!database) return false;
201
202 // dummy chain interface
203 bool ret = true;
204 std::shared_ptr<CWallet> wallet(new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet);
205 {
206 // Get the database handle
207 WalletDatabase& db = wallet->GetDatabase();
208 std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
209 batch->TxnBegin();
210
211 // Read the records from the dump file and write them to the database
212 while (dump_file.good()) {
213 std::string key;
214 std::getline(dump_file, key, ',');
215 std::string value;
216 std::getline(dump_file, value, '\n');
217
218 if (key == "checksum") {
219 std::vector<unsigned char> parsed_checksum = ParseHex(value);
220 if (parsed_checksum.size() != checksum.size()) {
221 error = Untranslated("Error: Checksum is not the correct size");
222 ret = false;
223 break;
224 }
225 std::copy(parsed_checksum.begin(), parsed_checksum.end(), checksum.begin());
226 break;
227 }
228
229 std::string line = strprintf("%s,%s\n", key, value);
230 hasher << std::span{line};
231
232 if (key.empty() || value.empty()) {
233 continue;
234 }
235
236 if (!IsHex(key)) {
237 error = strprintf(_("Error: Got key that was not hex: %s"), key);
238 ret = false;
239 break;
240 }
241 if (!IsHex(value)) {
242 error = strprintf(_("Error: Got value that was not hex: %s"), value);
243 ret = false;
244 break;
245 }
246
247 std::vector<unsigned char> k = ParseHex(key);
248 std::vector<unsigned char> v = ParseHex(value);
249 if (!batch->Write(std::span{k}, std::span{v})) {
250 error = strprintf(_("Error: Unable to write record to new wallet"));
251 ret = false;
252 break;
253 }
254 }
255
256 if (ret) {
257 uint256 comp_checksum = hasher.GetHash();
258 if (checksum.IsNull()) {
259 error = _("Error: Missing checksum");
260 ret = false;
261 } else if (checksum != comp_checksum) {
262 error = strprintf(_("Error: Dumpfile checksum does not match. Computed %s, expected %s"), HexStr(comp_checksum), HexStr(checksum));
263 ret = false;
264 }
265 }
266
267 if (ret) {
268 batch->TxnCommit();
269 } else {
270 batch->TxnAbort();
271 }
272
273 batch.reset();
274
275 dump_file.close();
276 }
277 // On failure, gather the paths to remove
278 std::vector<fs::path> paths_to_remove = wallet->GetDatabase().Files();
279 if (!name.empty()) paths_to_remove.push_back(wallet_path);
280
281 wallet.reset(); // The pointer deleter will close the wallet for us.
282
283 // Remove the wallet dir if we have a failure
284 if (!ret) {
285 for (const auto& p : paths_to_remove) {
286 fs::remove(p);
287 }
288 }
289
290 return ret;
291 }
292 } // namespace wallet
293