Bitcoin Core  24.1.0
P2P Digital Currency
dump.cpp
Go to the documentation of this file.
1 // Copyright (c) 2020-2021 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 <fs.h>
8 #include <util/translation.h>
9 #include <wallet/wallet.h>
10 
11 #include <algorithm>
12 #include <fstream>
13 #include <memory>
14 #include <string>
15 #include <utility>
16 #include <vector>
17 
18 namespace wallet {
19 static const std::string DUMP_MAGIC = "BITCOIN_CORE_WALLET_DUMP";
20 uint32_t DUMP_VERSION = 1;
21 
23 {
24  // Get the dumpfile
25  std::string dump_filename = args.GetArg("-dumpfile", "");
26  if (dump_filename.empty()) {
27  error = _("No dump file provided. To use dump, -dumpfile=<filename> must be provided.");
28  return false;
29  }
30 
31  fs::path path = fs::PathFromString(dump_filename);
32  path = fs::absolute(path);
33  if (fs::exists(path)) {
34  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));
35  return false;
36  }
37  std::ofstream dump_file;
38  dump_file.open(path);
39  if (dump_file.fail()) {
40  error = strprintf(_("Unable to open %s for writing"), fs::PathToString(path));
41  return false;
42  }
43 
44  HashWriter hasher{};
45 
46  WalletDatabase& db = wallet.GetDatabase();
47  std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
48 
49  bool ret = true;
50  if (!batch->StartCursor()) {
51  error = _("Error: Couldn't create cursor into database");
52  ret = false;
53  }
54 
55  // Write out a magic string with version
56  std::string line = strprintf("%s,%u\n", DUMP_MAGIC, DUMP_VERSION);
57  dump_file.write(line.data(), line.size());
58  hasher.write(MakeByteSpan(line));
59 
60  // Write out the file format
61  line = strprintf("%s,%s\n", "format", db.Format());
62  dump_file.write(line.data(), line.size());
63  hasher.write(MakeByteSpan(line));
64 
65  if (ret) {
66 
67  // Read the records
68  while (true) {
71  bool complete;
72  ret = batch->ReadAtCursor(ss_key, ss_value, complete);
73  if (complete) {
74  ret = true;
75  break;
76  } else if (!ret) {
77  error = _("Error reading next record from wallet database");
78  break;
79  }
80  std::string key_str = HexStr(ss_key);
81  std::string value_str = HexStr(ss_value);
82  line = strprintf("%s,%s\n", key_str, value_str);
83  dump_file.write(line.data(), line.size());
84  hasher.write(MakeByteSpan(line));
85  }
86  }
87 
88  batch->CloseCursor();
89  batch.reset();
90 
91  // Close the wallet after we're done with it. The caller won't be doing this
92  wallet.Close();
93 
94  if (ret) {
95  // Write the hash
96  tfm::format(dump_file, "checksum,%s\n", HexStr(hasher.GetHash()));
97  dump_file.close();
98  } else {
99  // Remove the dumpfile on failure
100  dump_file.close();
101  fs::remove(path);
102  }
103 
104  return ret;
105 }
106 
107 // The standard wallet deleter function blocks on the validation interface
108 // queue, which doesn't exist for the bitcoin-wallet. Define our own
109 // deleter here.
111 {
112  wallet->WalletLogPrintf("Releasing wallet\n");
113  wallet->Close();
114  delete wallet;
115 }
116 
117 bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
118 {
119  // Get the dumpfile
120  std::string dump_filename = args.GetArg("-dumpfile", "");
121  if (dump_filename.empty()) {
122  error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.");
123  return false;
124  }
125 
126  fs::path dump_path = fs::PathFromString(dump_filename);
127  dump_path = fs::absolute(dump_path);
128  if (!fs::exists(dump_path)) {
129  error = strprintf(_("Dump file %s does not exist."), fs::PathToString(dump_path));
130  return false;
131  }
132  std::ifstream dump_file{dump_path};
133 
134  // Compute the checksum
135  HashWriter hasher{};
136  uint256 checksum;
137 
138  // Check the magic and version
139  std::string magic_key;
140  std::getline(dump_file, magic_key, ',');
141  std::string version_value;
142  std::getline(dump_file, version_value, '\n');
143  if (magic_key != DUMP_MAGIC) {
144  error = strprintf(_("Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."), magic_key, DUMP_MAGIC);
145  dump_file.close();
146  return false;
147  }
148  // Check the version number (value of first record)
149  uint32_t ver;
150  if (!ParseUInt32(version_value, &ver)) {
151  error =strprintf(_("Error: Unable to parse version %u as a uint32_t"), version_value);
152  dump_file.close();
153  return false;
154  }
155  if (ver != DUMP_VERSION) {
156  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);
157  dump_file.close();
158  return false;
159  }
160  std::string magic_hasher_line = strprintf("%s,%s\n", magic_key, version_value);
161  hasher.write(MakeByteSpan(magic_hasher_line));
162 
163  // Get the stored file format
164  std::string format_key;
165  std::getline(dump_file, format_key, ',');
166  std::string format_value;
167  std::getline(dump_file, format_value, '\n');
168  if (format_key != "format") {
169  error = strprintf(_("Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), format_key);
170  dump_file.close();
171  return false;
172  }
173  // Get the data file format with format_value as the default
174  std::string file_format = args.GetArg("-format", format_value);
175  if (file_format.empty()) {
176  error = _("No wallet file format provided. To use createfromdump, -format=<format> must be provided.");
177  return false;
178  }
179  DatabaseFormat data_format;
180  if (file_format == "bdb") {
181  data_format = DatabaseFormat::BERKELEY;
182  } else if (file_format == "sqlite") {
183  data_format = DatabaseFormat::SQLITE;
184  } else {
185  error = strprintf(_("Unknown wallet file format \"%s\" provided. Please provide one of \"bdb\" or \"sqlite\"."), file_format);
186  return false;
187  }
188  if (file_format != format_value) {
189  warnings.push_back(strprintf(_("Warning: Dumpfile wallet format \"%s\" does not match command line specified format \"%s\"."), format_value, file_format));
190  }
191  std::string format_hasher_line = strprintf("%s,%s\n", format_key, format_value);
192  hasher.write(MakeByteSpan(format_hasher_line));
193 
194  DatabaseOptions options;
195  DatabaseStatus status;
196  ReadDatabaseArgs(args, options);
197  options.require_create = true;
198  options.require_format = data_format;
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(nullptr /* chain */, name, gArgs, std::move(database)), WalletToolReleaseWallet);
205  {
206  LOCK(wallet->cs_wallet);
207  DBErrors load_wallet_ret = wallet->LoadWallet();
208  if (load_wallet_ret != DBErrors::LOAD_OK) {
209  error = strprintf(_("Error creating %s"), name);
210  return false;
211  }
212 
213  // Get the database handle
214  WalletDatabase& db = wallet->GetDatabase();
215  std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
216  batch->TxnBegin();
217 
218  // Read the records from the dump file and write them to the database
219  while (dump_file.good()) {
220  std::string key;
221  std::getline(dump_file, key, ',');
222  std::string value;
223  std::getline(dump_file, value, '\n');
224 
225  if (key == "checksum") {
226  std::vector<unsigned char> parsed_checksum = ParseHex(value);
227  if (parsed_checksum.size() != checksum.size()) {
228  error = Untranslated("Error: Checksum is not the correct size");
229  ret = false;
230  break;
231  }
232  std::copy(parsed_checksum.begin(), parsed_checksum.end(), checksum.begin());
233  break;
234  }
235 
236  std::string line = strprintf("%s,%s\n", key, value);
237  hasher.write(MakeByteSpan(line));
238 
239  if (key.empty() || value.empty()) {
240  continue;
241  }
242 
243  if (!IsHex(key)) {
244  error = strprintf(_("Error: Got key that was not hex: %s"), key);
245  ret = false;
246  break;
247  }
248  if (!IsHex(value)) {
249  error = strprintf(_("Error: Got value that was not hex: %s"), value);
250  ret = false;
251  break;
252  }
253 
254  std::vector<unsigned char> k = ParseHex(key);
255  std::vector<unsigned char> v = ParseHex(value);
256 
258  CDataStream ss_value(v, SER_DISK, CLIENT_VERSION);
259 
260  if (!batch->Write(ss_key, ss_value)) {
261  error = strprintf(_("Error: Unable to write record to new wallet"));
262  ret = false;
263  break;
264  }
265  }
266 
267  if (ret) {
268  uint256 comp_checksum = hasher.GetHash();
269  if (checksum.IsNull()) {
270  error = _("Error: Missing checksum");
271  ret = false;
272  } else if (checksum != comp_checksum) {
273  error = strprintf(_("Error: Dumpfile checksum does not match. Computed %s, expected %s"), HexStr(comp_checksum), HexStr(checksum));
274  ret = false;
275  }
276  }
277 
278  if (ret) {
279  batch->TxnCommit();
280  } else {
281  batch->TxnAbort();
282  }
283 
284  batch.reset();
285 
286  dump_file.close();
287  }
288  wallet.reset(); // The pointer deleter will close the wallet for us.
289 
290  // Remove the wallet dir if we have a failure
291  if (!ret) {
292  fs::remove_all(wallet_path);
293  }
294 
295  return ret;
296 }
297 } // namespace wallet
std::unique_ptr< WalletDatabase > MakeDatabase(const fs::path &path, const DatabaseOptions &options, DatabaseStatus &status, bilingual_str &error)
Definition: walletdb.cpp:1156
std::optional< DatabaseFormat > require_format
Definition: db.h:208
static path PathFromString(const std::string &string)
Convert byte string to path object.
Definition: fs.h:173
int ret
void ReadDatabaseArgs(const ArgsManager &args, DatabaseOptions &options)
Definition: db.cpp:142
ArgsManager gArgs
Definition: system.cpp:86
Bilingual messages:
Definition: translation.h:18
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1164
static constexpr unsigned int size()
Definition: uint256.h:81
bilingual_str Untranslated(std::string original)
Mark a bilingual_str as untranslated.
Definition: translation.h:48
bool IsHex(std::string_view str)
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:185
unsigned char * begin()
Definition: uint256.h:61
bool IsNull() const
Definition: uint256.h:34
ArgsManager args
bool ParseUInt32(std::string_view str, uint32_t *out)
Convert decimal string to unsigned 32-bit integer with strict parse error feedback.
std::vector< Byte > ParseHex(std::string_view str)
Parse the hex string into bytes (uint8_t or std::byte).
static std::string PathToString(const path &path)
Convert path object to a byte string.
Definition: fs.h:150
std::string HexStr(const Span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
#define LOCK(cs)
Definition: sync.h:261
const char * name
Definition: rest.cpp:46
bilingual_str _(const char *psz)
Translation function.
Definition: translation.h:65
A writer stream (for serialization) that computes a 256-bit hash.
Definition: hash.h:100
void format(std::ostream &out, const char *fmt, const Args &... args)
Format list of arguments to the stream according to given format string.
Definition: tinyformat.h:1062
static void WalletToolReleaseWallet(CWallet *wallet)
Definition: dump.cpp:110
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:235
uint32_t DUMP_VERSION
Definition: dump.cpp:20
static const std::string DUMP_MAGIC
Definition: dump.cpp:19
Definition: node.h:39
DatabaseStatus
Definition: db.h:219
256-bit opaque blob.
Definition: uint256.h:119
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:117
DatabaseFormat
Definition: db.h:200
bool DumpWallet(const ArgsManager &args, CWallet &wallet, bilingual_str &error)
Definition: dump.cpp:22
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
Definition: system.cpp:604
Span< const std::byte > MakeByteSpan(V &&v) noexcept
Definition: span.h:264
DBErrors
Error statuses for the wallet database.
Definition: walletdb.h:45
virtual std::unique_ptr< DatabaseBatch > MakeBatch(bool flush_on_close=true)=0
Make a DatabaseBatch connected to this database.
virtual std::string Format()=0
std::shared_ptr< CWallet > wallet
static bool exists(const path &p)
Definition: fs.h:88
Path class wrapper to block calls to the fs::path(std::string) implicit constructor and the fs::path:...
Definition: fs.h:30
static const int CLIENT_VERSION
bitcoind-res.rc includes this file, but it cannot cope with real c++ code.
Definition: clientversion.h:33
static path absolute(const path &p)
Definition: fs.h:81
bool error(const char *fmt, const Args &... args)
Definition: system.h:48
An instance of this class represents one database.
Definition: db.h:105