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