mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-09-12 21:52:53 +02:00
89af67d79ftests: Add some fuzz test coverage for command-specific args (Anthony Towns)92df785859tests: Add some test coverage for ArgsManager::AddCommand (Anthony Towns)33c8090be9ArgsManager: automate checking for correct command options (Anthony Towns)186354a0d8bitcoin-wallet: use command-specific options (Anthony Towns)d21e82b7d6ArgsManager: support command-specific options (Anthony Towns) Pull request description: Adds the ability to link particular options to one or more (`OptionsCategory::COMMANDS`) commands, and uses this feature in `bitcoin-wallet`. Separates out the help information for these command-specific options (duplicating it if an option applies to multiple commands), and provides a function for checking at runtime if some options have been specified by the user that only apply to other commands. #### Motivation Currently, `ArgsManager` supports commands like `bitcoin-wallet dump` but while some of the options are command-specific (like `-dumpfile`), `ArgsManager` itself doesn't know that. As a result, `-dumpfile` is listed in the global help rather than under the relevant commands, and if you use `-dumpfile` with a different command that doesn't support it, `ArgsManager` cannot automatically report that as an error, resulting in the commands that don't support the option having to have error-handling specific to all the options they don't support. #### Changes Help output moves command-specific options under their associated commands: Before: ``` Options: -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. ... Commands: createfromdump Create new wallet file from dumped records dump Print out all of the wallet key-value records ``` After: ``` Commands: createfromdump Create new wallet file from dumped records -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. dump Print out all of the wallet key-value records -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. ``` Error messages are now generated automatically by `ArgsManager` rather than ad-hoc wallet code for each option: Before: ```c++ if (args.IsArgSet("-dumpfile") && command != "dump" && command != "createfromdump") { tfm::format(std::cerr, "The -dumpfile option can only be used with the \"dump\" and \"createfromdump\" commands.\n"); return false; } ``` After: ```c++ std::vector<std::string> details; if (!args.CheckCommandOptions(command, &details)) { tfm::format(std::cerr, "Error: Invalid arguments provided:\n%s\n", util::MakeUnorderedList(details)); return false; } ``` #### Limitations - If an option applies to multiple commands, it shares the same help text. There's no way to provide per-command descriptions. - Option parsing rules are unchanged — options still cannot appear after the command. ACKs for top commit: achow101: ACK89af67d79fsedited: Re-ACK89af67d79fryanofsky: Code review ACK89af67d79f. Since last review: rebase, integration with ClearArgs and fuzz test, and Assume -> Assert switch Tree-SHA512: 7ae7c3b74d0c8c4db8459e9f0b9c7498b2fa4758954ec49983decbba177877b039779f0f7b55e60c3a0ed74c5e9e4ac4734ba9e049bf3a7743280ef8300869fa
183 lines
6.6 KiB
C++
183 lines
6.6 KiB
C++
// Copyright (c) 2016-present The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <wallet/wallettool.h>
|
|
|
|
#include <common/args.h>
|
|
#include <util/check.h>
|
|
#include <util/fs.h>
|
|
#include <util/translation.h>
|
|
#include <wallet/dump.h>
|
|
#include <wallet/wallet.h>
|
|
#include <wallet/walletutil.h>
|
|
|
|
namespace wallet {
|
|
namespace WalletTool {
|
|
|
|
// 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;
|
|
}
|
|
|
|
static void WalletCreate(CWallet* wallet_instance, uint64_t wallet_creation_flags)
|
|
{
|
|
LOCK(wallet_instance->cs_wallet);
|
|
|
|
wallet_instance->InitWalletFlags(wallet_creation_flags);
|
|
|
|
Assert(wallet_instance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
|
|
wallet_instance->SetupDescriptorScriptPubKeyMans();
|
|
|
|
tfm::format(std::cout, "Topping up keypool...\n");
|
|
wallet_instance->TopUpKeyPool();
|
|
}
|
|
|
|
static std::shared_ptr<CWallet> MakeWallet(const std::string& name, const fs::path& path, DatabaseOptions options)
|
|
{
|
|
DatabaseStatus status;
|
|
bilingual_str error;
|
|
std::vector<bilingual_str> warnings;
|
|
std::unique_ptr<WalletDatabase> database = MakeDatabase(path, options, status, error);
|
|
if (!database) {
|
|
tfm::format(std::cerr, "%s\n", error.original);
|
|
return nullptr;
|
|
}
|
|
|
|
// dummy chain interface
|
|
std::shared_ptr<CWallet> wallet_instance{new CWallet(/*chain=*/nullptr, name, std::move(database)), WalletToolReleaseWallet};
|
|
DBErrors load_wallet_ret;
|
|
try {
|
|
load_wallet_ret = wallet_instance->PopulateWalletFromDB(error, warnings);
|
|
} catch (const std::runtime_error&) {
|
|
tfm::format(std::cerr, "Error loading %s. Is wallet being used by another process?\n", name);
|
|
return nullptr;
|
|
}
|
|
|
|
if (!error.empty()) {
|
|
tfm::format(std::cerr, "%s", error.original);
|
|
}
|
|
|
|
for (const auto &warning : warnings) {
|
|
tfm::format(std::cerr, "%s", warning.original);
|
|
}
|
|
|
|
if (load_wallet_ret != DBErrors::LOAD_OK && load_wallet_ret != DBErrors::NONCRITICAL_ERROR && load_wallet_ret != DBErrors::NEED_RESCAN) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (options.require_create) WalletCreate(wallet_instance.get(), options.create_flags);
|
|
|
|
return wallet_instance;
|
|
}
|
|
|
|
static void WalletShowInfo(CWallet* wallet_instance)
|
|
{
|
|
LOCK(wallet_instance->cs_wallet);
|
|
|
|
tfm::format(std::cout, "Wallet info\n===========\n");
|
|
tfm::format(std::cout, "Name: %s\n", wallet_instance->GetName());
|
|
tfm::format(std::cout, "Format: %s\n", wallet_instance->GetDatabase().Format());
|
|
tfm::format(std::cout, "Descriptors: %s\n", wallet_instance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) ? "yes" : "no");
|
|
tfm::format(std::cout, "Encrypted: %s\n", wallet_instance->HasEncryptionKeys() ? "yes" : "no");
|
|
tfm::format(std::cout, "HD (hd seed available): %s\n", wallet_instance->IsHDEnabled() ? "yes" : "no");
|
|
tfm::format(std::cout, "Keypool Size: %u\n", wallet_instance->GetKeyPoolSize());
|
|
tfm::format(std::cout, "Transactions: %zu\n", wallet_instance->mapWallet.size());
|
|
tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->m_address_book.size());
|
|
}
|
|
|
|
bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
|
|
{
|
|
{
|
|
std::vector<std::string> details;
|
|
if (!args.CheckCommandOptions(command, &details)) {
|
|
tfm::format(std::cerr, "Error: Invalid arguments provided:\n%s\n", util::MakeUnorderedList(details));
|
|
return false;
|
|
}
|
|
}
|
|
if ((command == "create" || command == "createfromdump") && !args.IsArgSet("-wallet")) {
|
|
tfm::format(std::cerr, "Wallet name must be provided when creating a new wallet.\n");
|
|
return false;
|
|
}
|
|
const std::string name = args.GetArg("-wallet", "");
|
|
util::Result<fs::path> path_res = GetWalletPath(name);
|
|
if (!path_res) {
|
|
tfm::format(std::cerr, "%s\n", util::ErrorString(path_res).original);
|
|
return false;
|
|
}
|
|
const fs::path& path = *path_res;
|
|
|
|
if (command == "create") {
|
|
if (name.empty()) {
|
|
tfm::format(std::cerr, "Wallet name cannot be empty\n");
|
|
return false;
|
|
}
|
|
DatabaseOptions options;
|
|
ReadDatabaseArgs(args, options);
|
|
options.require_create = true;
|
|
options.create_flags |= WALLET_FLAG_DESCRIPTORS;
|
|
options.require_format = DatabaseFormat::SQLITE;
|
|
|
|
const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
|
|
if (wallet_instance) {
|
|
WalletShowInfo(wallet_instance.get());
|
|
wallet_instance->Close();
|
|
}
|
|
} else if (command == "info") {
|
|
DatabaseOptions options;
|
|
ReadDatabaseArgs(args, options);
|
|
options.require_existing = true;
|
|
const std::shared_ptr<CWallet> wallet_instance = MakeWallet(name, path, options);
|
|
if (!wallet_instance) return false;
|
|
WalletShowInfo(wallet_instance.get());
|
|
wallet_instance->Close();
|
|
} else if (command == "dump") {
|
|
DatabaseOptions options;
|
|
ReadDatabaseArgs(args, options);
|
|
options.require_existing = true;
|
|
DatabaseStatus status;
|
|
|
|
if (IsBDBFile(BDBDataFile(path))) {
|
|
options.require_format = DatabaseFormat::BERKELEY_RO;
|
|
}
|
|
|
|
bilingual_str error;
|
|
std::unique_ptr<WalletDatabase> database = MakeDatabase(path, options, status, error);
|
|
if (!database) {
|
|
tfm::format(std::cerr, "%s\n", error.original);
|
|
return false;
|
|
}
|
|
|
|
bool ret = DumpWallet(args, *database, error);
|
|
if (!ret && !error.empty()) {
|
|
tfm::format(std::cerr, "%s\n", error.original);
|
|
return ret;
|
|
}
|
|
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(args, 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;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} // namespace WalletTool
|
|
} // namespace wallet
|