mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-22 15:04:44 +01:00
wallettool: Add createfromdump command
Creates a new wallet file using the dump file produced by the dump command
This commit is contained in:
parent
e1e7a90d5f
commit
a88c320041
4 changed files with 208 additions and 1 deletions
|
@ -27,15 +27,17 @@ static void SetupWalletToolArgs(ArgsManager& argsman)
|
|||
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-dumpfile=<file name>", "When used with 'dump', writes out the records to this file.", ArgsManager::ALLOW_STRING, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-dumpfile=<file name>", "When used with 'dump', writes out the records to this file. When used with 'createfromdump', loads the records into a new wallet.", ArgsManager::ALLOW_STRING, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
|
||||
argsman.AddArg("-descriptors", "Create descriptors wallet. Only for create", ArgsManager::ALLOW_BOOL, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-format=<format>", "The format of the wallet file to create. Either \"bdb\" or \"sqlite\". Only used with 'createfromdump'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
|
||||
|
||||
argsman.AddArg("info", "Get wallet info", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
|
||||
argsman.AddArg("create", "Create new wallet file", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
|
||||
argsman.AddArg("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
|
||||
argsman.AddArg("dump", "Print out all of the wallet key-value records", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
|
||||
argsman.AddArg("createfromdump", "Create new wallet file from dumped records", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
|
||||
}
|
||||
|
||||
static bool WalletAppInit(int argc, char* argv[])
|
||||
|
|
|
@ -94,3 +94,189 @@ bool DumpWallet(CWallet& wallet, bilingual_str& error)
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// The standard wallet deleter function blocks on the validation interface
|
||||
// queue, which doesn't exist for the bitcoin-wallet. Define our own
|
||||
// deleter here.
|
||||
static void WalletToolReleaseWallet(CWallet* wallet)
|
||||
{
|
||||
wallet->WalletLogPrintf("Releasing wallet\n");
|
||||
wallet->Close();
|
||||
delete wallet;
|
||||
}
|
||||
|
||||
bool CreateFromDump(const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
|
||||
{
|
||||
// Get the dumpfile
|
||||
std::string dump_filename = gArgs.GetArg("-dumpfile", "");
|
||||
if (dump_filename.empty()) {
|
||||
error = _("No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.");
|
||||
return false;
|
||||
}
|
||||
|
||||
fs::path dump_path = dump_filename;
|
||||
dump_path = fs::absolute(dump_path);
|
||||
if (!fs::exists(dump_path)) {
|
||||
error = strprintf(_("Dump file %s does not exist."), dump_path.string());
|
||||
return false;
|
||||
}
|
||||
fsbridge::ifstream dump_file(dump_path);
|
||||
|
||||
// Compute the checksum
|
||||
CHashWriter hasher(0, 0);
|
||||
uint256 checksum;
|
||||
|
||||
// Check the magic and version
|
||||
std::string magic_key;
|
||||
std::getline(dump_file, magic_key, ',');
|
||||
std::string version_value;
|
||||
std::getline(dump_file, version_value, '\n');
|
||||
if (magic_key != DUMP_MAGIC) {
|
||||
error = strprintf(_("Error: Dumpfile identifier record is incorrect. Got \"%s\", expected \"%s\"."), magic_key, DUMP_MAGIC);
|
||||
dump_file.close();
|
||||
return false;
|
||||
}
|
||||
// Check the version number (value of first record)
|
||||
uint32_t ver;
|
||||
if (!ParseUInt32(version_value, &ver)) {
|
||||
error =strprintf(_("Error: Unable to parse version %u as a uint32_t"), version_value);
|
||||
dump_file.close();
|
||||
return false;
|
||||
}
|
||||
if (ver != DUMP_VERSION) {
|
||||
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);
|
||||
dump_file.close();
|
||||
return false;
|
||||
}
|
||||
std::string magic_hasher_line = strprintf("%s,%s\n", magic_key, version_value);
|
||||
hasher.write(magic_hasher_line.data(), magic_hasher_line.size());
|
||||
|
||||
// Get the stored file format
|
||||
std::string format_key;
|
||||
std::getline(dump_file, format_key, ',');
|
||||
std::string format_value;
|
||||
std::getline(dump_file, format_value, '\n');
|
||||
if (format_key != "format") {
|
||||
error = strprintf(_("Error: Dumpfile format record is incorrect. Got \"%s\", expected \"format\"."), format_key);
|
||||
dump_file.close();
|
||||
return false;
|
||||
}
|
||||
// Get the data file format with format_value as the default
|
||||
std::string file_format = gArgs.GetArg("-format", format_value);
|
||||
if (file_format.empty()) {
|
||||
error = _("No wallet file format provided. To use createfromdump, -format=<format> must be provided.");
|
||||
return false;
|
||||
}
|
||||
DatabaseFormat data_format;
|
||||
if (file_format == "bdb") {
|
||||
data_format = DatabaseFormat::BERKELEY;
|
||||
} else if (file_format == "sqlite") {
|
||||
data_format = DatabaseFormat::SQLITE;
|
||||
} else {
|
||||
error = strprintf(_("Unknown wallet file format \"%s\" provided. Please provide one of \"bdb\" or \"sqlite\"."), file_format);
|
||||
return false;
|
||||
}
|
||||
if (file_format != format_value) {
|
||||
warnings.push_back(strprintf(_("Warning: Dumpfile wallet format \"%s\" does not match command line specified format \"%s\"."), format_value, file_format));
|
||||
}
|
||||
std::string format_hasher_line = strprintf("%s,%s\n", format_key, format_value);
|
||||
hasher.write(format_hasher_line.data(), format_hasher_line.size());
|
||||
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
options.require_create = true;
|
||||
options.require_format = data_format;
|
||||
std::unique_ptr<WalletDatabase> database = MakeDatabase(wallet_path, options, status, error);
|
||||
if (!database) return false;
|
||||
|
||||
// dummy chain interface
|
||||
bool ret = true;
|
||||
std::shared_ptr<CWallet> wallet(new CWallet(nullptr /* chain */, name, std::move(database)), WalletToolReleaseWallet);
|
||||
{
|
||||
LOCK(wallet->cs_wallet);
|
||||
bool first_run = true;
|
||||
DBErrors load_wallet_ret = wallet->LoadWallet(first_run);
|
||||
if (load_wallet_ret != DBErrors::LOAD_OK) {
|
||||
error = strprintf(_("Error creating %s"), name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the database handle
|
||||
WalletDatabase& db = wallet->GetDatabase();
|
||||
std::unique_ptr<DatabaseBatch> batch = db.MakeBatch();
|
||||
batch->TxnBegin();
|
||||
|
||||
// Read the records from the dump file and write them to the database
|
||||
while (dump_file.good()) {
|
||||
std::string key;
|
||||
std::getline(dump_file, key, ',');
|
||||
std::string value;
|
||||
std::getline(dump_file, value, '\n');
|
||||
|
||||
if (key == "checksum") {
|
||||
std::vector<unsigned char> parsed_checksum = ParseHex(value);
|
||||
std::copy(parsed_checksum.begin(), parsed_checksum.end(), checksum.begin());
|
||||
break;
|
||||
}
|
||||
|
||||
std::string line = strprintf("%s,%s\n", key, value);
|
||||
hasher.write(line.data(), line.size());
|
||||
|
||||
if (key.empty() || value.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsHex(key)) {
|
||||
error = strprintf(_("Error: Got key that was not hex: %s"), key);
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
if (!IsHex(value)) {
|
||||
error = strprintf(_("Error: Got value that was not hex: %s"), value);
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> k = ParseHex(key);
|
||||
std::vector<unsigned char> v = ParseHex(value);
|
||||
|
||||
CDataStream ss_key(k, SER_DISK, CLIENT_VERSION);
|
||||
CDataStream ss_value(v, SER_DISK, CLIENT_VERSION);
|
||||
|
||||
if (!batch->Write(ss_key, ss_value)) {
|
||||
error = strprintf(_("Error: Unable to write record to new wallet"));
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
uint256 comp_checksum = hasher.GetHash();
|
||||
if (checksum.IsNull()) {
|
||||
error = _("Error: Missing checksum");
|
||||
ret = false;
|
||||
} else if (checksum != comp_checksum) {
|
||||
error = strprintf(_("Error: Dumpfile checksum does not match. Computed %s, expected %s"), HexStr(comp_checksum), HexStr(checksum));
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
batch->TxnCommit();
|
||||
} else {
|
||||
batch->TxnAbort();
|
||||
}
|
||||
|
||||
batch.reset();
|
||||
|
||||
dump_file.close();
|
||||
}
|
||||
wallet.reset(); // The pointer deleter will close the wallet for us.
|
||||
|
||||
// Remove the wallet dir if we have a failure
|
||||
if (!ret) {
|
||||
fs::remove_all(wallet_path);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
#ifndef BITCOIN_WALLET_DUMP_H
|
||||
#define BITCOIN_WALLET_DUMP_H
|
||||
|
||||
#include <fs.h>
|
||||
|
||||
class CWallet;
|
||||
|
||||
struct bilingual_str;
|
||||
|
||||
bool DumpWallet(CWallet& wallet, bilingual_str& error);
|
||||
bool CreateFromDump(const std::string& name, const fs::path& wallet_path, bilingual_str& error, std::vector<bilingual_str>& warnings);
|
||||
|
||||
#endif // BITCOIN_WALLET_DUMP_H
|
||||
|
|
|
@ -107,6 +107,11 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
|
|||
{
|
||||
fs::path path = fs::absolute(name, GetWalletDir());
|
||||
|
||||
// -format is only allowed with createfromdump. Disallow it for all other commands.
|
||||
if (gArgs.IsArgSet("-format") && command != "createfromdump") {
|
||||
tfm::format(std::cerr, "The -format option can only be used with the \"createfromdump\" command.\n");
|
||||
return false;
|
||||
}
|
||||
// -dumpfile is only allowed with dump and createfromdump. Disallow it for all other commands.
|
||||
if (gArgs.IsArgSet("-dumpfile") && command != "dump" && command != "createfromdump") {
|
||||
tfm::format(std::cerr, "The -dumpfile option can only be used with the \"dump\" and \"createfromdump\" commands.\n");
|
||||
|
@ -164,6 +169,17 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
|
|||
}
|
||||
tfm::format(std::cout, "The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n");
|
||||
return ret;
|
||||
} else if (command == "createfromdump") {
|
||||
bilingual_str error;
|
||||
std::vector<bilingual_str> warnings;
|
||||
bool ret = CreateFromDump(name, path, error, warnings);
|
||||
for (const auto& warning : warnings) {
|
||||
tfm::format(std::cout, "%s\n", warning.original);
|
||||
}
|
||||
if (!ret && !error.empty()) {
|
||||
tfm::format(std::cerr, "%s\n", error.original);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
tfm::format(std::cerr, "Invalid command: %s\n", command);
|
||||
return false;
|
||||
|
|
Loading…
Add table
Reference in a new issue