/ 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      if (name.empty()) {
125          tfm::format(std::cerr, "Wallet name cannot be empty\n");
126          return false;
127      }
128  
129      // Get the dumpfile
130      std::string dump_filename = args.GetArg("-dumpfile", "");
131      if (dump_filename.empty()) {
132          error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.");
133          return false;
134      }
135  
136      fs::path dump_path = fs::PathFromString(dump_filename);
137      dump_path = fs::absolute(dump_path);
138      if (!fs::exists(dump_path)) {
139          error = strprintf(_("Dump file %s does not exist."), fs::PathToString(dump_path));
140          return false;
141      }
142      std::ifstream dump_file{dump_path.std_path()};
143  
144      // Compute the checksum
145      HashWriter hasher{};
146      uint256 checksum;
147  
148      // Check the magic and version
149      std::string magic_key;
150      std::getline(dump_file, magic_key, ',');
151      std::string version_value;
152      std::getline(dump_file, version_value, '\n');
153      if (magic_key != DUMP_MAGIC) {
154          error = strprintf(_("Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."), magic_key, DUMP_MAGIC);
155          dump_file.close();
156          return false;
157      }
158      // Check the version number (value of first record)
159      const auto ver{ToIntegral<uint32_t>(version_value)};
160      if (!ver) {
161          error = strprintf(_("Error: Unable to parse version %u as a uint32_t"), version_value);
162          dump_file.close();
163          return false;
164      }
165      if (*ver != DUMP_VERSION) {
166          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);
167          dump_file.close();
168          return false;
169      }
170      std::string magic_hasher_line = strprintf("%s,%s\n", magic_key, version_value);
171      hasher << std::span{magic_hasher_line};
172  
173      // Get the stored file format
174      std::string format_key;
175      std::getline(dump_file, format_key, ',');
176      std::string format_value;
177      std::getline(dump_file, format_value, '\n');
178      if (format_key != "format") {
179          error = strprintf(_("Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), format_key);
180          dump_file.close();
181          return false;
182      }
183      // Make sure that the dump was created from a sqlite database only as that is the only
184      // type of database that we still support.
185      // Other formats such as BDB should not be loaded into a sqlite database since they also
186      // use a different type of wallet entirely which is no longer compatible with this software.
187      if (format_value != "sqlite") {
188          error = strprintf(_("Error: Dumpfile specifies an unsupported database format (%s). Only sqlite database dumps are supported"), format_value);
189          return false;
190      }
191      std::string format_hasher_line = strprintf("%s,%s\n", format_key, format_value);
192      hasher << std::span{format_hasher_line};
193  
194      DatabaseOptions options;
195      DatabaseStatus status;
196      ReadDatabaseArgs(args, options);
197      options.require_create = true;
198      options.require_format = DatabaseFormat::SQLITE;
199      std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error);
200      if (!database) return false;
201  
202      // dummy chain interface
203      bool ret = true;
204      std::shared_ptr<CWallet> wallet(new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet);
205      {
206          // Get the database handle
207          WalletDatabase& db = wallet->GetDatabase();
208          std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
209          batch->TxnBegin();
210  
211          // Read the records from the dump file and write them to the database
212          while (dump_file.good()) {
213              std::string key;
214              std::getline(dump_file, key, ',');
215              std::string value;
216              std::getline(dump_file, value, '\n');
217  
218              if (key == "checksum") {
219                  std::vector<unsigned char> parsed_checksum = ParseHex(value);
220                  if (parsed_checksum.size() != checksum.size()) {
221                      error = Untranslated("Error: Checksum is not the correct size");
222                      ret = false;
223                      break;
224                  }
225                  std::copy(parsed_checksum.begin(), parsed_checksum.end(), checksum.begin());
226                  break;
227              }
228  
229              std::string line = strprintf("%s,%s\n", key, value);
230              hasher << std::span{line};
231  
232              if (key.empty() || value.empty()) {
233                  continue;
234              }
235  
236              if (!IsHex(key)) {
237                  error = strprintf(_("Error: Got key that was not hex: %s"), key);
238                  ret = false;
239                  break;
240              }
241              if (!IsHex(value)) {
242                  error = strprintf(_("Error: Got value that was not hex: %s"), value);
243                  ret = false;
244                  break;
245              }
246  
247              std::vector<unsigned char> k = ParseHex(key);
248              std::vector<unsigned char> v = ParseHex(value);
249              if (!batch->Write(std::span{k}, std::span{v})) {
250                  error = strprintf(_("Error: Unable to write record to new wallet"));
251                  ret = false;
252                  break;
253              }
254          }
255  
256          if (ret) {
257              uint256 comp_checksum = hasher.GetHash();
258              if (checksum.IsNull()) {
259                  error = _("Error: Missing checksum");
260                  ret = false;
261              } else if (checksum != comp_checksum) {
262                  error = strprintf(_("Error: Dumpfile checksum does not match. Computed %s, expected %s"), HexStr(comp_checksum), HexStr(checksum));
263                  ret = false;
264              }
265          }
266  
267          if (ret) {
268              batch->TxnCommit();
269          } else {
270              batch->TxnAbort();
271          }
272  
273          batch.reset();
274  
275          dump_file.close();
276      }
277      // On failure, gather the paths to remove
278      std::vector<fs::path> paths_to_remove = wallet->GetDatabase().Files();
279      if (!name.empty()) paths_to_remove.push_back(wallet_path);
280  
281      wallet.reset(); // The pointer deleter will close the wallet for us.
282  
283      // Remove the wallet dir if we have a failure
284      if (!ret) {
285          for (const auto& p : paths_to_remove) {
286              fs::remove(p);
287          }
288      }
289  
290      return ret;
291  }
292  } // namespace wallet