Branch data 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 : 0 :
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
|