Skip to content

Commit e1e7a90

Browse files
committed
wallettool: Add dump command
Adds a new dump command to bitcoin-wallet which prints out all of the wallet's records in hex.
1 parent ad3d4b3 commit e1e7a90

File tree

5 files changed

+154
-22
lines changed

5 files changed

+154
-22
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ BITCOIN_CORE_H = \
249249
wallet/context.h \
250250
wallet/crypter.h \
251251
wallet/db.h \
252+
wallet/dump.h \
252253
wallet/feebumper.h \
253254
wallet/fees.h \
254255
wallet/ismine.h \
@@ -361,6 +362,7 @@ libbitcoin_wallet_a_SOURCES = \
361362
wallet/context.cpp \
362363
wallet/crypter.cpp \
363364
wallet/db.cpp \
365+
wallet/dump.cpp \
364366
wallet/feebumper.cpp \
365367
wallet/fees.cpp \
366368
wallet/interfaces.cpp \

src/bitcoin-wallet.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ static void SetupWalletToolArgs(ArgsManager& argsman)
2727
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
2828
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
2929
argsman.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS);
30+
argsman.AddArg("-dumpfile=<file name>", "When used with 'dump', writes out the records to this file.", ArgsManager::ALLOW_STRING, OptionsCategory::OPTIONS);
3031
argsman.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
3132
argsman.AddArg("-descriptors", "Create descriptors wallet. Only for create", ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS);
3233
argsman.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
3334

3435
argsman.AddArg("info", "Get wallet info", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
3536
argsman.AddArg("create", "Create new wallet file", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
3637
argsman.AddArg("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
38+
argsman.AddArg("dump", "Print out all of the wallet key-value records", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
3739
}
3840

3941
static bool WalletAppInit(int argc, char* argv[])

src/wallet/dump.cpp

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) 2020 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 <util/translation.h>
8+
#include <wallet/wallet.h>
9+
10+
static const std::string DUMP_MAGIC = "BITCOIN_CORE_WALLET_DUMP";
11+
uint32_t DUMP_VERSION = 1;
12+
13+
bool DumpWallet(CWallet& wallet, bilingual_str& error)
14+
{
15+
// Get the dumpfile
16+
std::string dump_filename = gArgs.GetArg("-dumpfile", "");
17+
if (dump_filename.empty()) {
18+
error = _("No dump file provided. To use dump, -dumpfile=<filename> must be provided.");
19+
return false;
20+
}
21+
22+
fs::path path = dump_filename;
23+
path = fs::absolute(path);
24+
if (fs::exists(path)) {
25+
error = strprintf(_("File %s already exists. If you are sure this is what you want, move it out of the way first."), path.string());
26+
return false;
27+
}
28+
fsbridge::ofstream dump_file;
29+
dump_file.open(path);
30+
if (dump_file.fail()) {
31+
error = strprintf(_("Unable to open %s for writing"), path.string());
32+
return false;
33+
}
34+
35+
CHashWriter hasher(0, 0);
36+
37+
WalletDatabase& db = wallet.GetDatabase();
38+
std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
39+
40+
bool ret = true;
41+
if (!batch->StartCursor()) {
42+
error = _("Error: Couldn't create cursor into database");
43+
ret = false;
44+
}
45+
46+
// Write out a magic string with version
47+
std::string line = strprintf("%s,%u\n", DUMP_MAGIC, DUMP_VERSION);
48+
dump_file.write(line.data(), line.size());
49+
hasher.write(line.data(), line.size());
50+
51+
// Write out the file format
52+
line = strprintf("%s,%s\n", "format", db.Format());
53+
dump_file.write(line.data(), line.size());
54+
hasher.write(line.data(), line.size());
55+
56+
if (ret) {
57+
58+
// Read the records
59+
while (true) {
60+
CDataStream ss_key(SER_DISK, CLIENT_VERSION);
61+
CDataStream ss_value(SER_DISK, CLIENT_VERSION);
62+
bool complete;
63+
ret = batch->ReadAtCursor(ss_key, ss_value, complete);
64+
if (complete) {
65+
ret = true;
66+
break;
67+
} else if (!ret) {
68+
error = _("Error reading next record from wallet database");
69+
break;
70+
}
71+
std::string key_str = HexStr(ss_key);
72+
std::string value_str = HexStr(ss_value);
73+
line = strprintf("%s,%s\n", key_str, value_str);
74+
dump_file.write(line.data(), line.size());
75+
hasher.write(line.data(), line.size());
76+
}
77+
}
78+
79+
batch->CloseCursor();
80+
batch.reset();
81+
82+
// Close the wallet after we're done with it. The caller won't be doing this
83+
wallet.Close();
84+
85+
if (ret) {
86+
// Write the hash
87+
tfm::format(dump_file, "checksum,%s\n", HexStr(hasher.GetHash()));
88+
dump_file.close();
89+
} else {
90+
// Remove the dumpfile on failure
91+
dump_file.close();
92+
fs::remove(path);
93+
}
94+
95+
return ret;
96+
}

src/wallet/dump.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) 2020 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+
#ifndef BITCOIN_WALLET_DUMP_H
6+
#define BITCOIN_WALLET_DUMP_H
7+
8+
class CWallet;
9+
10+
struct bilingual_str;
11+
12+
bool DumpWallet(CWallet& wallet, bilingual_str& error);
13+
14+
#endif // BITCOIN_WALLET_DUMP_H

src/wallet/wallettool.cpp

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <fs.h>
66
#include <util/system.h>
77
#include <util/translation.h>
8+
#include <wallet/dump.h>
89
#include <wallet/salvage.h>
910
#include <wallet/wallet.h>
1011
#include <wallet/walletutil.h>
@@ -106,6 +107,12 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
106107
{
107108
fs::path path = fs::absolute(name, GetWalletDir());
108109

110+
// -dumpfile is only allowed with dump and createfromdump. Disallow it for all other commands.
111+
if (gArgs.IsArgSet("-dumpfile") && command != "dump" && command != "createfromdump") {
112+
tfm::format(std::cerr, "The -dumpfile option can only be used with the \"dump\" and \"createfromdump\" commands.\n");
113+
return false;
114+
}
115+
109116
if (command == "create") {
110117
DatabaseOptions options;
111118
options.require_create = true;
@@ -119,33 +126,44 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
119126
WalletShowInfo(wallet_instance.get());
120127
wallet_instance->Close();
121128
}
122-
} else if (command == "info" || command == "salvage") {
123-
if (command == "info") {
124-
DatabaseOptions options;
125-
options.require_existing = true;
126-
std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
127-
if (!wallet_instance) return false;
128-
WalletShowInfo(wallet_instance.get());
129-
wallet_instance->Close();
130-
} else if (command == "salvage") {
129+
} else if (command == "info") {
130+
DatabaseOptions options;
131+
options.require_existing = true;
132+
std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
133+
if (!wallet_instance) return false;
134+
WalletShowInfo(wallet_instance.get());
135+
wallet_instance->Close();
136+
} else if (command == "salvage") {
131137
#ifdef USE_BDB
132-
bilingual_str error;
133-
std::vector<bilingual_str> warnings;
134-
bool ret = RecoverDatabaseFile(path, error, warnings);
135-
if (!ret) {
136-
for (const auto& warning : warnings) {
137-
tfm::format(std::cerr, "%s\n", warning.original);
138-
}
139-
if (!error.empty()) {
140-
tfm::format(std::cerr, "%s\n", error.original);
141-
}
138+
bilingual_str error;
139+
std::vector<bilingual_str> warnings;
140+
bool ret = RecoverDatabaseFile(path, error, warnings);
141+
if (!ret) {
142+
for (const auto& warning : warnings) {
143+
tfm::format(std::cerr, "%s\n", warning.original);
142144
}
143-
return ret;
145+
if (!error.empty()) {
146+
tfm::format(std::cerr, "%s\n", error.original);
147+
}
148+
}
149+
return ret;
144150
#else
145-
tfm::format(std::cerr, "Salvage command is not available as BDB support is not compiled");
146-
return false;
151+
tfm::format(std::cerr, "Salvage command is not available as BDB support is not compiled");
152+
return false;
147153
#endif
154+
} else if (command == "dump") {
155+
DatabaseOptions options;
156+
options.require_existing = true;
157+
std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
158+
if (!wallet_instance) return false;
159+
bilingual_str error;
160+
bool ret = DumpWallet(*wallet_instance, error);
161+
if (!ret && !error.empty()) {
162+
tfm::format(std::cerr, "%s\n", error.original);
163+
return ret;
148164
}
165+
tfm::format(std::cout, "The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n");
166+
return ret;
149167
} else {
150168
tfm::format(std::cerr, "Invalid command: %s\n", command);
151169
return false;

0 commit comments

Comments
 (0)