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