Bitcoin Core  31.0.0
P2P Digital Currency
dump.cpp
Go to the documentation of this file.
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 
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.
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;
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
std::unique_ptr< WalletDatabase > MakeDatabase(const fs::path &path, const DatabaseOptions &options, DatabaseStatus &status, bilingual_str &error)
Definition: walletdb.cpp:1298
static path absolute(const path &p)
Definition: fs.h:88
std::optional< DatabaseFormat > require_format
Definition: db.h:175
int ret
void ReadDatabaseArgs(const ArgsManager &args, DatabaseOptions &options)
Definition: db.cpp:153
std::vector< Byte > ParseHex(std::string_view hex_str)
Like TryParseHex, but returns an empty vector on invalid input.
Definition: strencodings.h:69
Bilingual messages:
Definition: translation.h:24
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1172
static constexpr unsigned int size()
Definition: uint256.h:106
bilingual_str Untranslated(std::string original)
Mark a bilingual_str as untranslated.
Definition: translation.h:82
bool IsHex(std::string_view str)
void format(std::ostream &out, FormatStringCheck< sizeof...(Args)> fmt, const Args &... args)
Format list of arguments to the stream according to given format string.
Definition: tinyformat.h:1079
consteval auto _(util::TranslatedLiteral str)
Definition: translation.h:79
constexpr unsigned char * begin()
Definition: uint256.h:100
static bool exists(const path &p)
Definition: fs.h:95
ArgsManager & args
Definition: bitcoind.cpp:277
bool DumpWallet(const ArgsManager &args, WalletDatabase &db, bilingual_str &error)
Definition: dump.cpp:24
const char * name
Definition: rest.cpp:48
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:132
A writer stream (for serialization) that computes a 256-bit hash.
Definition: hash.h:100
static void WalletToolReleaseWallet(CWallet *wallet)
Definition: dump.cpp:115
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:309
uint32_t DUMP_VERSION
Definition: dump.cpp:22
static const std::string DUMP_MAGIC
Definition: dump.cpp:21
constexpr bool IsNull() const
Definition: uint256.h:48
DatabaseStatus
Definition: db.h:186
256-bit opaque blob.
Definition: uint256.h:195
bool CreateFromDump(const ArgsManager &args, const std::string &name, const fs::path &wallet_path, bilingual_str &error, std::vector< bilingual_str > &warnings)
Definition: dump.cpp:122
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
Definition: args.cpp:461
static std::string PathToString(const path &path)
Convert path object to a byte string.
Definition: fs.h:157
virtual std::string Format()=0
static path PathFromString(const std::string &string)
Convert byte string to path object.
Definition: fs.h:180
virtual std::unique_ptr< DatabaseBatch > MakeBatch()=0
Make a DatabaseBatch connected to this database.
Path class wrapper to block calls to the fs::path(std::string) implicit constructor and the fs::path:...
Definition: fs.h:33
std::string HexStr(const std::span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
Definition: hex_base.cpp:30
An instance of this class represents one database.
Definition: db.h:129
std::filesystem::path & std_path()
Definition: fs.h:39