Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/bitcoin-wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ 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 | ArgsManager::DISALLOW_NEGATION, 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. When used with 'createfromdump', loads the records into a new wallet.", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, 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_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::COMMAND_OPTIONS);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a future improvement/ follow-up could be specifying the help message description for each command:

Suggested change
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_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::COMMAND_OPTIONS);
argsman.AddArg("-dumpfile=<file name>", "{'dump':'Writes out the records to this file.','createfromdump':'Loads the records into a new wallet.'}", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::COMMAND_OPTIONS);```

argsman.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
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.AddCommand("info", "Get wallet info");
argsman.AddCommand("create", "Create a new descriptor wallet file");
argsman.AddCommand("dump", "Print out all of the wallet key-value records");
argsman.AddCommand("createfromdump", "Create new wallet file from dumped records");
argsman.AddCommand("dump", "Print out all of the wallet key-value records", {"-dumpfile"});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are there any other such relations between command and arg that we could check this way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not currently, I think

argsman.AddCommand("createfromdump", "Create new wallet file from dumped records", {"-dumpfile"});
}

static std::optional<int> WalletAppInit(ArgsManager& args, int argc, char* argv[])
Expand Down
108 changes: 90 additions & 18 deletions src/common/args.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,31 @@ std::optional<const ArgsManager::Command> ArgsManager::GetCommand() const
return ret;
}

bool ArgsManager::CheckCommandOptions(const std::string& command, std::vector<std::string>* errors) const
{
LOCK(cs_args);

auto command_options = m_available_args.find(OptionsCategory::COMMAND_OPTIONS);
if (command_options == m_available_args.end()) return true;

const auto command_args = m_command_args.find(command);
auto is_valid_opt = [&](const auto& opt) EXCLUSIVE_LOCKS_REQUIRED(cs_args) -> bool {
if (command_args == m_command_args.end()) return false;
return command_args->second.contains(opt);
};

bool ok = true;
for (const auto& [arg, _] : command_options->second) {
if (!IsArgSet(arg)) continue;
if (is_valid_opt(arg)) continue;
if (errors != nullptr) {
errors->emplace_back(strprintf("The %s option cannot be used with the '%s' command.", arg, command));
ok = false;
}
}
return ok;
}

std::vector<std::string> ArgsManager::GetArgs(const std::string& strArg) const
{
std::vector<std::string> result;
Expand Down Expand Up @@ -553,7 +578,7 @@ void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strV
m_settings.forced_settings[SettingName(strArg)] = strValue;
}

void ArgsManager::AddCommand(const std::string& cmd, const std::string& help)
void ArgsManager::AddCommand(const std::string& cmd, const std::string& help, std::set<std::string>&& options)
{
Assert(cmd.find('=') == std::string::npos);
Assert(cmd.at(0) != '-');
Expand All @@ -562,6 +587,9 @@ void ArgsManager::AddCommand(const std::string& cmd, const std::string& help)
m_accept_any_command = false; // latch to false
std::map<std::string, Arg>& arg_map = m_available_args[OptionsCategory::COMMANDS];
auto ret = arg_map.emplace(cmd, Arg{"", help, ArgsManager::COMMAND});
if (!options.empty()) {
m_command_args.try_emplace(cmd, std::move(options));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "ArgsManager: support command-specific options" (e2debfa)

Should the options list be validated against known options? Seems like it could be easy to make a typo and pass a bad list of options to this function, causing help output to be incomplete without it being obvious.

}
Assert(ret.second); // Fail on duplicate commands
}

Expand Down Expand Up @@ -618,14 +646,46 @@ void ArgsManager::CheckMultipleCLIArgs() const
}
}

namespace {
/** Helper class for iterating over COMMAND_OPTIONS applicable to a given command */
template <typename T>
class CommandOptionsGetter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "ArgsManager: support command-specific options" (e2debfa)

Any plans to reuse this class? It would seem simpler to inline:

diff

--- a/src/common/args.cpp
+++ b/src/common/args.cpp
@@ -646,35 +646,6 @@ void ArgsManager::CheckMultipleCLIArgs() const
     }
 }
 
-namespace {
-/** Helper class for iterating over COMMAND_OPTIONS applicable to a given command */
-template <typename T>
-class CommandOptionsGetter
-{
-private:
-    const typename T::const_iterator m_end;
-    const typename T::const_iterator m_iter;
-public:
-    CommandOptionsGetter(const T& available_args)
-    : m_end{available_args.end()},
-      m_iter{available_args.find(OptionsCategory::COMMAND_OPTIONS)}
-    {
-    }
-
-    template <typename Fn>
-    void Iterate(const std::set<std::string>& select, bool with_debug, Fn&& fn) const
-    {
-        if (select.empty()) return;
-        if (m_iter == m_end) return;
-        for (const auto& [cmdopt_name, cmdopt_info] : m_iter->second) {
-            if (!with_debug && (cmdopt_info.m_flags & ArgsManager::DEBUG_ONLY)) continue;
-            if (!select.contains(cmdopt_name)) continue;
-            fn(cmdopt_name, cmdopt_info);
-        }
-    }
-};
-} // anonymous namespace
-
 std::string ArgsManager::GetHelpMessage() const
 {
     const bool show_debug = GetBoolArg("-help-debug", false);
@@ -682,7 +653,7 @@ std::string ArgsManager::GetHelpMessage() const
     std::string usage;
     LOCK(cs_args);
 
-    const auto command_options = CommandOptionsGetter(m_available_args);
+    const auto command_options = m_available_args.find(OptionsCategory::COMMAND_OPTIONS);
 
     for (const auto& [category, category_args] : m_available_args) {
         switch(category) {
@@ -748,10 +719,12 @@ std::string ArgsManager::GetHelpMessage() const
 
                 if (category == OptionsCategory::COMMANDS) {
                     const auto cmd_args = m_command_args.find(arg_name);
-                    if (cmd_args != m_command_args.end()) {
-                        command_options.Iterate(cmd_args->second, show_debug, [&](const auto& cmdopt_name, const auto& cmdopt_info) {
+                    if (cmd_args != m_command_args.end() && command_options != m_available_args.end()) {
+                        for (const auto& [cmdopt_name, cmdopt_info] : command_options->second) {
+                            if (!show_debug && (cmdopt_info.m_flags & ArgsManager::DEBUG_ONLY)) continue;
+                            if (!cmd_args->second.contains(cmdopt_name)) continue;
                             usage += HelpMessageSubOpt(cmdopt_name, cmdopt_info.m_help_param, cmdopt_info.m_help_text);
-                        });
+                        }
                     }
                 }
             }

{
private:
const typename T::const_iterator m_end;
const typename T::const_iterator m_iter;
public:
CommandOptionsGetter(const T& available_args)
: m_end{available_args.end()},
m_iter{available_args.find(OptionsCategory::COMMAND_OPTIONS)}
{
}

template <typename Fn>
void Iterate(const std::set<std::string>& select, bool with_debug, Fn&& fn) const
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the new code is completely uncovered: https://corecheck.dev/bitcoin/bitcoin/pulls/28802

Adding temporary exceptions inside indicates that tool_wallet.py and wallet_encryption.py cover some of the functions here.
I understand that argsman_tests.cpp is already very weak coverage-wise, but would be great to add some coverage for the new code at least.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some unit test coverage

{
if (select.empty()) return;
if (m_iter == m_end) return;
for (const auto& [cmdopt_name, cmdopt_info] : m_iter->second) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: when is it opt and when cmdopt?

if (!with_debug && (cmdopt_info.m_flags & ArgsManager::DEBUG_ONLY)) continue;
if (!select.contains(cmdopt_name)) continue;
fn(cmdopt_name, cmdopt_info);
}
}
};
} // anonymous namespace

std::string ArgsManager::GetHelpMessage() const
{
const bool show_debug = GetBoolArg("-help-debug", false);

std::string usage;
LOCK(cs_args);
for (const auto& arg_map : m_available_args) {
switch(arg_map.first) {

const auto command_options = CommandOptionsGetter(m_available_args);

for (const auto& [category, category_args] : m_available_args) {
switch(category) {
case OptionsCategory::OPTIONS:
usage += HelpMessageGroup("Options:");
break;
Expand Down Expand Up @@ -671,22 +731,29 @@ std::string ArgsManager::GetHelpMessage() const
case OptionsCategory::CLI_COMMANDS:
usage += HelpMessageGroup("CLI Commands:");
break;
case OptionsCategory::COMMAND_OPTIONS:
break;
default:
break;
}

if (category == OptionsCategory::COMMAND_OPTIONS) continue;

// When we get to the hidden options, stop
if (arg_map.first == OptionsCategory::HIDDEN) break;

for (const auto& arg : arg_map.second) {
if (show_debug || !(arg.second.m_flags & ArgsManager::DEBUG_ONLY)) {
std::string name;
if (arg.second.m_help_param.empty()) {
name = arg.first;
} else {
name = arg.first + arg.second.m_help_param;
if (category == OptionsCategory::HIDDEN) break;

for (const auto& [arg_name, arg_info] : category_args) {
if (show_debug || !(arg_info.m_flags & ArgsManager::DEBUG_ONLY)) {
usage += HelpMessageOpt(arg_name, arg_info.m_help_param, arg_info.m_help_text);

if (category == OptionsCategory::COMMANDS) {
const auto cmd_args = m_command_args.find(arg_name);
if (cmd_args != m_command_args.end()) {
command_options.Iterate(cmd_args->second, show_debug, [&](const auto& cmdopt_name, const auto& cmdopt_info) {
usage += HelpMessageSubOpt(cmdopt_name, cmdopt_info.m_help_param, cmdopt_info.m_help_text);
});
}
}
usage += HelpMessageOpt(name, arg.second.m_help_text);
}
}
}
Expand All @@ -712,11 +779,11 @@ std::string HelpMessageGroup(const std::string &message) {
return std::string(message) + std::string("\n\n");
}

std::string HelpMessageOpt(const std::string &option, const std::string &message) {
return std::string(optIndent,' ') + std::string(option) +
std::string("\n") + std::string(msgIndent,' ') +
FormatParagraph(message, screenWidth - msgIndent, msgIndent) +
std::string("\n\n");
std::string HelpMessageOpt(std::string_view option, std::string_view help_param, std::string_view message, int indent)
{
return strprintf("%*s%s%s\n%*s%s\n\n",
(optIndent + indent), "", option, help_param,
(msgIndent + indent), "", FormatParagraph(message, screenWidth - msgIndent - indent, msgIndent + indent));
}

const std::vector<std::string> TEST_OPTIONS_DOC{
Expand All @@ -733,6 +800,11 @@ bool HasTestOption(const ArgsManager& args, const std::string& test_option)
});
}

std::string HelpMessageSubOpt(std::string_view option, std::string_view help_param, std::string_view message)
{
return HelpMessageOpt(option, help_param, message, msgIndent - optIndent);
}

fs::path GetDefaultDataDir()
{
// Windows:
Expand Down
29 changes: 25 additions & 4 deletions src/common/args.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <variant>
#include <vector>

Expand Down Expand Up @@ -66,6 +67,8 @@ enum class OptionsCategory {
CLI_COMMANDS,
IPC,

COMMAND_OPTIONS, // Specific to one or more commands
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems safe, just have to make sure: is it safe to add in the middle of this enum (e.g. is its index persisted anywhere?).
If the placement doesn't matter, can we add the options closer to the command values (before IPC)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Placement doesn't matter, but I don't think moving closer to COMMANDS, REGISTER_COMMANDS or CLI_COMMANDS really makes sense -- COMMAND_OPTIONS relates to argsman.AddCommand() commands (used by bitcoin-wallet and bitcoin-util), which differ from COMMANDS (used by bitcoin-tx), REGISTER_COMMANDS (also used by bitcoin-tx), and CLI_COMMANDS (used by bitcoin-cli for its non-rpc commands, -generate, -netinfo, -addrinfo, -getinfo).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "ArgsManager: support command-specific options" (e2debfa)

Might be worth noting that options with this category will be excluded from help output unless they are referenced by an AddCommand call.

Relatedly it might be good to add a check that every option in the COMMAND_OPTIONS category is actually referenced by an AddCommand call.


HIDDEN // Always the last option to avoid printing these in the help
};

Expand Down Expand Up @@ -138,6 +141,7 @@ class ArgsManager
std::set<std::string> m_network_only_args GUARDED_BY(cs_args);
std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args);
std::optional<unsigned int> m_default_flags GUARDED_BY(cs_args){};
std::map<std::string, std::set<std::string>> m_command_args GUARDED_BY(cs_args);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line seems to conflict with latest master.

nit: is it important for the set to be sorted here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's looked up by the key in CheckCommandOptions, so sorting seems fine to me?

bool m_accept_any_command GUARDED_BY(cs_args){true};
std::list<SectionInfo> m_config_sections GUARDED_BY(cs_args);
std::optional<fs::path> m_config_path GUARDED_BY(cs_args);
Expand Down Expand Up @@ -213,6 +217,11 @@ class ArgsManager
*/
std::optional<const Command> GetCommand() const;

/**
* Check for invalid command options
*/
bool CheckCommandOptions(const std::string& command, std::vector<std::string>* errors = nullptr) const;

/**
* Get blocks directory path
*
Expand Down Expand Up @@ -348,9 +357,9 @@ class ArgsManager
void AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat);

/**
* Add subcommand
* Add command
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment still useful now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a docstring, so shows up in the doxygen docs https://doxygen.bitcoincore.org/class_args_manager.html#ab71d531303f7e6fe3bbf67683a414a2e ; maybe it's not that useful, but doesn't seem worth removing outright

*/
void AddCommand(const std::string& cmd, const std::string& help);
void AddCommand(const std::string& cmd, const std::string& help, std::set<std::string>&& options={});

/**
* Add many hidden arguments
Expand Down Expand Up @@ -474,10 +483,22 @@ std::string HelpMessageGroup(const std::string& message);
/**
* Format a string to be used as option description in help messages
*
* @param option Option message (e.g. "-rpcuser=<user>")
* @param option Option name (e.g. "-rpcuser")
* @param help_param Help parameter (e.g. "=<user>" or "")
* @param message Option description (e.g. "Username for JSON-RPC connections")
* @param indent Additional indentation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "ArgsManager: support command-specific options" (e2debfa)

Giving this function an indent parameter doesn't seem right semantically since the other parameters are higher-level parameters unrelated to formatting, and the function is still hardcoding most other formatting value internally. This also increases code complexity by requiring a separate HelpMessageSubOpt function, because the caller doesn't have access to the formatting constants. Would suggest keeping this high-level and simplifying code with a change like:

diff

--- a/src/common/args.cpp
+++ b/src/common/args.cpp
@@ -750,7 +750,7 @@ std::string ArgsManager::GetHelpMessage() const
                     const auto cmd_args = m_command_args.find(arg_name);
                     if (cmd_args != m_command_args.end()) {
                         command_options.Iterate(cmd_args->second, show_debug, [&](const auto& cmdopt_name, const auto& cmdopt_info) {
-                            usage += HelpMessageSubOpt(cmdopt_name, cmdopt_info.m_help_param, cmdopt_info.m_help_text);
+                            usage += HelpMessageOpt(cmdopt_name, cmdopt_info.m_help_param, cmdopt_info.m_help_text, /*subopt=*/true);
                         });
                     }
                 }
@@ -779,8 +779,9 @@ std::string HelpMessageGroup(const std::string &message) {
     return std::string(message) + std::string("\n\n");
 }
 
-std::string HelpMessageOpt(std::string_view option, std::string_view help_param, std::string_view message, int indent)
+std::string HelpMessageOpt(std::string_view option, std::string_view help_param, std::string_view message, bool subopt)
 {
+    const int indent{subopt ? msgIndent - optIndent : 0};
     return strprintf("%*s%s%s\n%*s%s\n\n",
            (optIndent + indent), "", option, help_param,
            (msgIndent + indent), "", FormatParagraph(message, screenWidth - msgIndent - indent, msgIndent + indent));
@@ -800,11 +801,6 @@ bool HasTestOption(const ArgsManager& args, const std::string& test_option)
     });
 }
 
-std::string HelpMessageSubOpt(std::string_view option, std::string_view help_param, std::string_view message)
-{
-    return HelpMessageOpt(option, help_param, message, msgIndent - optIndent);
-}
-
 fs::path GetDefaultDataDir()
 {
     // Windows:
--- a/src/common/args.h
+++ b/src/common/args.h
@@ -486,19 +486,9 @@ std::string HelpMessageGroup(const std::string& message);
  * @param option Option name (e.g. "-rpcuser")
  * @param help_param Help parameter (e.g. "=<user>" or "")
  * @param message Option description (e.g. "Username for JSON-RPC connections")
- * @param indent Additional indentation
+ * @param subopt True if this is suboption, instead of a top level option.
  * @return the formatted string
  */
-std::string HelpMessageOpt(std::string_view option, std::string_view help_param, std::string_view message, int indent=0);
-
-/**
- * Same as HelpMessageOpt, but indents for command-specific options
- *
- * @param option Option name (e.g. "-rpcuser")
- * @param help_param Help parameter (e.g. "=<user>" or "")
- * @param message Option description (e.g. "Username for JSON-RPC connections")
- * @return the formatted string
- */
-std::string HelpMessageSubOpt(std::string_view option, std::string_view help_param, std::string_view message);
+std::string HelpMessageOpt(std::string_view option, std::string_view help_param, std::string_view message, bool subopt=false);
 
 #endif // BITCOIN_COMMON_ARGS_H

* @return the formatted string
*/
std::string HelpMessageOpt(std::string_view option, std::string_view help_param, std::string_view message, int indent=0);

/**
* Same as HelpMessageOpt, but indents for command-specific options
*
* @param option Option name (e.g. "-rpcuser")
* @param help_param Help parameter (e.g. "=<user>" or "")
* @param message Option description (e.g. "Username for JSON-RPC connections")
* @return the formatted string
*/
std::string HelpMessageOpt(const std::string& option, const std::string& message);
std::string HelpMessageSubOpt(std::string_view option, std::string_view help_param, std::string_view message);

#endif // BITCOIN_COMMON_ARGS_H
45 changes: 45 additions & 0 deletions src/test/argsman_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,51 @@ BOOST_AUTO_TEST_CASE(util_GetArg)
BOOST_CHECK_EQUAL(testArgs.GetArg("pritest4", "default"), "b");
}

BOOST_AUTO_TEST_CASE(util_AddCommand)
{
enum TestFail { SUCCESS, PARSE_FAIL, PARSE_ERROR, NO_COMMAND, COMMAND_OPTS };

auto testfn = [&](const auto& argv) -> TestFail {
TestArgsManager test_args;
test_args.AddArg("-opt1=<file name>", "Opt 1", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::COMMAND_OPTIONS);
test_args.AddArg("-opt2=<file name>", "Opt 2", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::COMMAND_OPTIONS);
test_args.AddArg("-opt3=<file name>", "Opt 3", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);

test_args.AddCommand("cmd1", "No specific options");
test_args.AddCommand("cmd2", "Opt 1", {"-opt1"});
test_args.AddCommand("cmd3", "Opt 1 or 2", {"-opt1", "-opt2"});

std::string error;
if (!test_args.ParseParameters(argv.size(), argv.data(), error)) return PARSE_FAIL;
if (!error.empty()) return PARSE_ERROR;
const auto command = test_args.GetCommand();
if (!command) return NO_COMMAND;
std::vector<std::string> details;
if (!test_args.CheckCommandOptions(command->command, &details)) return COMMAND_OPTS;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "tests: Add some test coverage for ArgsManager::AddCommand" (cce7eba)

Might be good to check details is empty on success, nonempty on failure

return SUCCESS;
};

BOOST_CHECK_EQUAL(COMMAND_OPTS, testfn(std::array{"x", "-opt1=foo", "cmd1"}));
BOOST_CHECK_EQUAL(SUCCESS, testfn(std::array{"x", "cmd1", "-opt1=foo"})); // things after the command are "args" and left unparsed, not options

BOOST_CHECK_EQUAL(SUCCESS, testfn(std::array{"x", "-opt1=foo", "cmd2"}));
BOOST_CHECK_EQUAL(SUCCESS, testfn(std::array{"x", "-opt1=foo", "cmd3"}));
BOOST_CHECK_EQUAL(COMMAND_OPTS, testfn(std::array{"x", "-opt2=foo", "cmd1"}));
BOOST_CHECK_EQUAL(COMMAND_OPTS, testfn(std::array{"x", "-opt2=foo", "cmd2"}));
BOOST_CHECK_EQUAL(SUCCESS, testfn(std::array{"x", "-opt2=foo", "cmd3"}));
BOOST_CHECK_EQUAL(SUCCESS, testfn(std::array{"x", "-opt3=foo", "cmd1"}));
BOOST_CHECK_EQUAL(SUCCESS, testfn(std::array{"x", "-opt3=foo", "cmd2"}));
BOOST_CHECK_EQUAL(SUCCESS, testfn(std::array{"x", "-opt3=foo", "cmd3"}));

BOOST_CHECK_EQUAL(COMMAND_OPTS, testfn(std::array{"x", "-opt1=foo", "-opt3=bar", "-opt2=baz", "cmd1"}));
BOOST_CHECK_EQUAL(COMMAND_OPTS, testfn(std::array{"x", "-opt1=foo", "-opt3=bar", "-opt2=baz", "cmd2"}));
BOOST_CHECK_EQUAL(SUCCESS, testfn(std::array{"x", "-opt1=foo", "-opt3=bar", "-opt2=baz", "cmd3"}));

BOOST_CHECK_EQUAL(PARSE_FAIL, testfn(std::array{"x", "cmd4"}));
BOOST_CHECK_EQUAL(NO_COMMAND, testfn(std::array{"x", "-opt3=foo"}));
BOOST_CHECK_EQUAL(PARSE_FAIL, testfn(std::array{"x", "-opt4=foo"}));
}

BOOST_AUTO_TEST_CASE(util_GetChainTypeString)
{
TestArgsManager test_args;
Expand Down
3 changes: 2 additions & 1 deletion src/test/fuzz/string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ FUZZ_TARGET(string)
(void)HelpExampleCli(random_string_1, random_string_2);
(void)HelpExampleRpc(random_string_1, random_string_2);
(void)HelpMessageGroup(random_string_1);
(void)HelpMessageOpt(random_string_1, random_string_2);
(void)HelpMessageOpt(random_string_1, "", random_string_2);
(void)HelpMessageOpt(random_string_1, random_string_2, "");
(void)IsDeprecatedRPCEnabled(random_string_1);
(void)Join(random_string_vector, random_string_1);
(void)JSONRPCError(fuzzed_data_provider.ConsumeIntegral<int>(), random_string_1);
Expand Down
9 changes: 6 additions & 3 deletions src/wallet/wallettool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,12 @@ static void WalletShowInfo(CWallet* wallet_instance)

bool ExecuteWalletToolFunc(const ArgsManager& args, const std::string& command)
{
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;
{
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" && !args.IsArgSet("-wallet")) {
tfm::format(std::cerr, "Wallet name must be provided when creating a new wallet.\n");
Expand Down
1 change: 1 addition & 0 deletions test/functional/tool_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ def test_no_create_legacy(self):
assert not (self.nodes[0].wallets_path / "legacy").exists()
self.assert_raises_tool_error("Invalid parameter -descriptors", "-wallet=legacy", "-descriptors=false", "create")
assert not (self.nodes[0].wallets_path / "legacy").exists()
self.assert_raises_tool_error("The -dumpfile option cannot be used with the 'create' command.", "-wallet=legacy", "-dumpfile=wallet.dump", "create")

def run_test(self):
self.wallet_path = self.nodes[0].wallets_path / self.default_wallet_name / self.wallet_data_filename
Expand Down
Loading