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