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