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