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