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