diff --git a/doc/release-notes-17493.md b/doc/release-notes-17493.md new file mode 100644 index 00000000000..263225f9809 --- /dev/null +++ b/doc/release-notes-17493.md @@ -0,0 +1,8 @@ +Configuration file +------------------ + +Repeated assignments of the same setting in the same section of the config file +which were previously ignored will now trigger errors on startup that look +like: "Multiple values specified for -setting in same section of config file." +Settings like `-debug=` and `-rpcallowip=` which are meant to +be repeated are not affected by this change and will not trigger errors. diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 060d519d920..54d03e3017e 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -16,13 +16,13 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman) argsman.AddArg("-chain=", "Use the chain (default: main). Allowed values: " LIST_CHAIN_NAMES, ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. " "This is intended for regression testing tools and app development. Equivalent to -chain=regtest.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); - argsman.AddArg("-testactivationheight=name@height.", "Set the activation height of 'name' (segwit, bip34, dersig, cltv, csv). (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-testactivationheight=name@height.", "Set the activation height of 'name' (segwit, bip34, dersig, cltv, csv). (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-testnet", "Use the testnet3 chain. Equivalent to -chain=test. Support for testnet3 is deprecated and will be removed in an upcoming release. Consider moving to testnet4 now by using -testnet4.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-testnet4", "Use the testnet4 chain. Equivalent to -chain=testnet4.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); - argsman.AddArg("-vbparams=deployment:start:end[:min_activation_height]", "Use given start/end times and min_activation_height for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-vbparams=deployment:start:end[:min_activation_height]", "Use given start/end times and min_activation_height for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-signet", "Use the signet chain. Equivalent to -chain=signet. Note that the network is defined by the -signetchallenge parameter", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); - argsman.AddArg("-signetchallenge", "Blocks must satisfy the given script to be considered valid (only for signet networks; defaults to the global default signet test network challenge)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::CHAINPARAMS); - argsman.AddArg("-signetseednode", "Specify a seed node for the signet network, in the hostname[:port] format, e.g. sig.net:1234 (may be used multiple times to specify multiple seed nodes; defaults to the global default signet test network seed node(s))", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-signetchallenge", "Blocks must satisfy the given script to be considered valid (only for signet networks; defaults to the global default signet test network challenge)", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST | ArgsManager::DISALLOW_NEGATION, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-signetseednode", "Specify a seed node for the signet network, in the hostname[:port] format, e.g. sig.net:1234 (may be used multiple times to specify multiple seed nodes; defaults to the global default signet test network seed node(s))", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST | ArgsManager::DISALLOW_NEGATION, OptionsCategory::CHAINPARAMS); } static std::unique_ptr globalChainBaseParams; diff --git a/src/common/args.cpp b/src/common/args.cpp index 833a0b28bd2..32f149d96cb 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -101,11 +101,43 @@ KeyInfo InterpretKey(std::string key) * @param[in] flags ArgsManager registered argument flags * @param[out] error Error description if settings value is not valid * - * @return parsed settings value if it is valid, otherwise nullopt accompanied + * @return parsed settings value if it is valid, otherwise `nullopt` accompanied * by a descriptive error string + * + * @note By design, the \ref InterpretValue function does mostly lossless + * conversions of command line arguments and configuration file values to JSON + * `common::SettingsValue` values, so higher level application code and GetArg + * helper methods can unambiguously determine original configuration strings + * from the JSON values, and flexibly interpret settings and provide good error + * feedback. Specifically: + * \n + * - JSON `null` value is never returned and is reserved for settings that were + * not configured at all. + * + * - JSON `false` value is returned for negated settings like `-nosetting` or + * `-nosetting=1`. `false` is also returned for boolean-only settings that + * have the ALLOW_BOOL flag and false values like `setting=0`. + * + * - JSON `true` value is returned for settings that have the ALLOW_BOOL flag + * and are specified on the command line without a value like `-setting`. + * `true` is also returned for boolean-only settings that have the ALLOW_BOOL + * flag and true values like `setting=1`. `true` is also returned for untyped + * legacy settings (see \ref IsTypedArg) that use double negation like + * `-nosetting=0`. + * + * - JSON `""` empty string value is returned for settings like `-setting=` + * that specify empty values. `""` is also returned for untyped legacy + * settings (see \ref IsTypedArg) that are specified on the command line + * without a value like `-setting`. + * + * - JSON strings like `"abc"` are returned for settings like `-setting=abc` if + * the setting has the ALLOW_STRING flag or is an untyped legacy setting. + * + * - JSON numbers like `123` are returned for settings like `-setting=123` if + * the setting enables integer parsing with the ALLOW_INT flag. */ std::optional InterpretValue(const KeyInfo& key, const std::string* value, - unsigned int flags, std::string& error) + unsigned int flags, std::string& error) { // Return negated settings as false values. if (key.negated) { @@ -113,6 +145,16 @@ std::optional InterpretValue(const KeyInfo& key, const st error = strprintf("Negating of -%s is meaningless and therefore forbidden", key.name); return std::nullopt; } + if (IsTypedArg(flags)) { + // If argument is typed, only allow negation with no value or with + // literal "1" value. Avoid calling InterpretBool and accepting + // other values which could be ambiguous. + if (value && *value != "1") { + error = strprintf("Cannot negate -%s at the same time as setting a value ('%s').", key.name, *value); + return std::nullopt; + } + return false; + } // Double negatives like -nofoo=0 are supported (but discouraged) if (value && !InterpretBool(*value)) { LogPrintf("Warning: parsed potentially confusing double-negative -%s=%s\n", key.name, *value); @@ -120,11 +162,63 @@ std::optional InterpretValue(const KeyInfo& key, const st } return false; } - if (!value && (flags & ArgsManager::DISALLOW_ELISION)) { - error = strprintf("Can not set -%s with no value. Please specify value with -%s=value.", key.name, key.name); - return std::nullopt; + if (value) { + if ((flags & ArgsManager::ALLOW_STRING) || !IsTypedArg(flags) || value->empty()) return *value; + if (flags & ArgsManager::ALLOW_INT) { + if (auto parsed_int = ToIntegral(*value)) return *parsed_int; + } + if (flags & ArgsManager::ALLOW_BOOL) { + if (*value == "0") return false; + if (*value == "1") return true; + } + error = strprintf("Cannot set -%s value to '%s'.", key.name, *value); + } else { + if (flags & ArgsManager::ALLOW_BOOL) return true; + if (!(flags & ArgsManager::DISALLOW_ELISION) && !IsTypedArg(flags)) return ""; + error = strprintf("Cannot set -%s with no value. Please specify value with -%s=value.", key.name, key.name); } - return value ? *value : ""; + if (flags & ArgsManager::ALLOW_STRING) { + error = strprintf("%s %s", error, "It must be set to a string."); + } else if (flags & ArgsManager::ALLOW_INT) { + error = strprintf("%s %s", error, "It must be set to an integer."); + } else if (flags & ArgsManager::ALLOW_BOOL) { + error = strprintf("%s %s", error, "It must be set to 0 or 1."); + } + return std::nullopt; +} + +//! Return string if setting is a nonempty string or number (-setting=abc, +//! -setting=123), "" if setting is false (-nosetting), otherwise return +//! nullopt. For legacy untyped args, coerce bool settings to strings as well. +static inline std::optional ConvertToString(const common::SettingsValue& value, bool typed_arg) +{ + if (value.isStr() && !value.get_str().empty()) return value.get_str(); + if (value.isNum()) return value.getValStr(); + if (typed_arg && value.isFalse()) return ""; + if (!typed_arg && !value.isNull()) { + if (value.isBool()) return value.get_bool() ? "1" : "0"; + return value.get_str(); + } + return {}; +} + +//! Return int64 if setting is a number or bool, otherwise return nullopt. For +//! legacy untyped args, coerce string settings as well. +static inline std::optional ConvertToInt(const common::SettingsValue& value, bool typed_arg) +{ + if (value.isNum()) return value.getInt(); + if (value.isBool()) return value.get_bool(); + if (!typed_arg && !value.isNull()) return LocaleIndependentAtoi(value.get_str()); + return {}; +} + +//! Return bool if setting is a bool, otherwise return nullopt. For legacy +//! untyped args, coerce string settings as well. +static inline std::optional ConvertToBool(const common::SettingsValue& value, bool typed_arg) +{ + if (value.isBool()) return value.get_bool(); + if (!typed_arg && !value.isNull()) return InterpretBool(value.get_str()); + return {}; } // Define default constructor and destructor that are not inline, so code instantiating this class doesn't need to @@ -269,6 +363,30 @@ std::optional ArgsManager::GetArgFlags(const std::string& name) co return std::nullopt; } +/** + * Check that arg has the right flags for use in a given context. Raises + * logic_error if this isn't the case, indicating the argument was registered + * with bad AddArg flags. + * + * Returns true if the arg is registered and has type checking enabled. Returns + * false if the arg was never registered or is untyped. + */ +bool ArgsManager::CheckArgFlags(const std::string& name, + uint32_t require, + uint32_t forbid, + const char* context) const +{ + std::optional flags = GetArgFlags(name); + if (!flags) return false; + if (!IsTypedArg(*flags)) require &= ~(ALLOW_BOOL | ALLOW_INT | ALLOW_STRING); + if ((*flags & require) != require || (*flags & forbid) != 0) { + throw std::logic_error( + strprintf("Bug: Can't call %s on arg %s registered with flags 0x%08x (requires 0x%x, disallows 0x%x)", + context, name, *flags, require, forbid)); + } + return IsTypedArg(*flags); +} + fs::path ArgsManager::GetPathArg(std::string arg, const fs::path& default_value) const { if (IsArgNegated(arg)) return fs::path{}; @@ -361,9 +479,10 @@ std::optional ArgsManager::GetCommand() const std::vector ArgsManager::GetArgs(const std::string& strArg) const { + bool typed_arg = CheckArgFlags(strArg, /*require=*/ ALLOW_STRING | ALLOW_LIST, /*forbid=*/ 0, __func__); std::vector result; for (const common::SettingsValue& value : GetSettingsList(strArg)) { - result.push_back(value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str()); + result.push_back(ConvertToString(value, typed_arg).value_or("")); } return result; } @@ -461,22 +580,13 @@ std::string ArgsManager::GetArg(const std::string& strArg, const std::string& st std::optional ArgsManager::GetArg(const std::string& strArg) const { - const common::SettingsValue value = GetSetting(strArg); - return SettingToString(value); -} - -std::optional SettingToString(const common::SettingsValue& value) -{ - if (value.isNull()) return std::nullopt; - if (value.isFalse()) return "0"; - if (value.isTrue()) return "1"; - if (value.isNum()) return value.getValStr(); - return value.get_str(); + bool typed_arg = CheckArgFlags(strArg, /*require=*/ ALLOW_STRING, /*forbid=*/ ALLOW_LIST, __func__); + return ConvertToString(GetSetting(strArg), typed_arg); } std::string SettingToString(const common::SettingsValue& value, const std::string& strDefault) { - return SettingToString(value).value_or(strDefault); + return ConvertToString(value, /*typed_arg=*/false).value_or(strDefault); } int64_t ArgsManager::GetIntArg(const std::string& strArg, int64_t nDefault) const @@ -486,22 +596,13 @@ int64_t ArgsManager::GetIntArg(const std::string& strArg, int64_t nDefault) cons std::optional ArgsManager::GetIntArg(const std::string& strArg) const { - const common::SettingsValue value = GetSetting(strArg); - return SettingToInt(value); -} - -std::optional SettingToInt(const common::SettingsValue& value) -{ - if (value.isNull()) return std::nullopt; - if (value.isFalse()) return 0; - if (value.isTrue()) return 1; - if (value.isNum()) return value.getInt(); - return LocaleIndependentAtoi(value.get_str()); + bool typed_arg = CheckArgFlags(strArg, /*require=*/ ALLOW_INT, /*forbid=*/ ALLOW_LIST, __func__); + return ConvertToInt(GetSetting(strArg), typed_arg); } int64_t SettingToInt(const common::SettingsValue& value, int64_t nDefault) { - return SettingToInt(value).value_or(nDefault); + return ConvertToInt(value, /*typed_arg=*/false).value_or(nDefault); } bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const @@ -511,20 +612,13 @@ bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const std::optional ArgsManager::GetBoolArg(const std::string& strArg) const { - const common::SettingsValue value = GetSetting(strArg); - return SettingToBool(value); -} - -std::optional SettingToBool(const common::SettingsValue& value) -{ - if (value.isNull()) return std::nullopt; - if (value.isBool()) return value.get_bool(); - return InterpretBool(value.get_str()); + bool typed_arg = CheckArgFlags(strArg, /*require=*/ ALLOW_BOOL, /*forbid=*/ ALLOW_LIST, __func__); + return ConvertToBool(GetSetting(strArg), typed_arg); } bool SettingToBool(const common::SettingsValue& value, bool fDefault) { - return SettingToBool(value).value_or(fDefault); + return ConvertToBool(value, /*typed_arg=*/false).value_or(fDefault); } bool ArgsManager::SoftSetArg(const std::string& strArg, const std::string& strValue) @@ -537,15 +631,17 @@ bool ArgsManager::SoftSetArg(const std::string& strArg, const std::string& strVa bool ArgsManager::SoftSetBoolArg(const std::string& strArg, bool fValue) { - if (fValue) - return SoftSetArg(strArg, std::string("1")); - else - return SoftSetArg(strArg, std::string("0")); + LOCK(cs_args); + CheckArgFlags(strArg, /*require=*/ ALLOW_BOOL, /*forbid=*/ ALLOW_LIST, __func__); + if (IsArgSet(strArg)) return false; + m_settings.forced_settings[SettingName(strArg)] = fValue; + return true; } void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strValue) { LOCK(cs_args); + CheckArgFlags(strArg, /*require=*/ ALLOW_STRING, /*forbid=*/ 0, __func__); m_settings.forced_settings[SettingName(strArg)] = strValue; } @@ -580,6 +676,20 @@ void ArgsManager::AddArg(const std::string& name, const std::string& help, unsig if (flags & ArgsManager::NETWORK_ONLY) { m_network_only_args.emplace(arg_name); } + + // Disallow flag combinations that would result in nonsensical behavior or a bad UX. + if ((flags & ALLOW_ANY) && (flags & (ALLOW_BOOL | ALLOW_INT | ALLOW_STRING))) { + throw std::logic_error(strprintf("Bug: bad %s flags. ALLOW_{BOOL|INT|STRING} flags are incompatible with " + "ALLOW_ANY (typed arguments need to be type checked)", arg_name)); + } + if ((flags & ALLOW_BOOL) && (flags & DISALLOW_ELISION)) { + throw std::logic_error(strprintf("Bug: bad %s flags. ALLOW_BOOL flag is incompatible with DISALLOW_ELISION " + "(boolean arguments should not require argument values)", arg_name)); + } + if ((flags & ALLOW_INT) && (flags & ALLOW_STRING)) { + throw std::logic_error(strprintf("Bug: bad %s flags. ALLOW_INT flag is incompatible with ALLOW_STRING " + "(any valid integer is also a valid string)", arg_name)); + } } void ArgsManager::AddHiddenArgs(const std::vector& names) @@ -793,7 +903,7 @@ std::variant ArgsManager::GetChainArg() const /* ignore_default_section_config= */ false, /*ignore_nonpersistent=*/false, /* get_chain_type= */ true); - return value.isNull() ? false : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); + return ConvertToBool(value, /*typed_arg=*/false).value_or(false); }; const bool fRegTest = get_net("-regtest"); diff --git a/src/common/args.h b/src/common/args.h index 8d9daf5f65d..689621c5762 100644 --- a/src/common/args.h +++ b/src/common/args.h @@ -87,27 +87,99 @@ struct SectionInfo { }; std::string SettingToString(const common::SettingsValue&, const std::string&); -std::optional SettingToString(const common::SettingsValue&); - int64_t SettingToInt(const common::SettingsValue&, int64_t); -std::optional SettingToInt(const common::SettingsValue&); - bool SettingToBool(const common::SettingsValue&, bool); -std::optional SettingToBool(const common::SettingsValue&); class ArgsManager { public: /** - * Flags controlling how config and command line arguments are validated and - * interpreted. + * Flags controlling how config and command line arguments are parsed. + * + * The flags below provide very basic type checking, designed to catch + * obvious configuration mistakes and provide helpful error messages. + * Specifying these flags is not a substitute for actually validating + * setting values that are parsed and making sure they are legitimate. + * + * Summary of recommended flags: + * + * - For most settings, just use standalone ALLOW_BOOL, ALLOW_INT, or + * ALLOW_STRING flags. + * + * - If your setting accepts multiple values and you want to read all the + * values, not just the last value, add | ALLOW_LIST to the flags. + * + * - If your setting causes a new action to be performed, and does not + * require a value, add | ALLOW_BOOL to the flags. Adding it just allows + * the setting to be specified alone on the command line without a value, + * as "-foo" instead of "-foo=value". + * + * - Only use the DISALLOW_NEGATION flag if your setting really cannot + * function without a value, so the command line interface will generally + * support negation and be more consistent. + * + * Detailed description of flags: + * + * The ALLOW_STRING, ALLOW_INT, and ALLOW_BOOL flags control what syntaxes are + * accepted, according to the following chart: + * + * | Syntax | STRING | INT | BOOL | STRING\|BOOL | INT\|BOOL | + * | -------- | :----: | :-: | :--: | :----------: | :-------: | + * | -foo=abc | X | | | X | | + * | -foo=123 | X | X | | X | X | + * | -foo=0 | X | X | X | X | X | + * | -foo=1 | X | X | X | X | X | + * | -foo | | | X | X | X | + * | -foo= | X | X | X | X | X | + * | -nofoo | X | X | X | X | X | + * | -nofoo=1 | X | X | X | X | X | + * + * Once validated, settings can be retrieved by called GetSetting(), + * GetArg(), GetIntArg(), and GetBoolArg(). GetSetting() is the most general + * way to access settings, returning them as JSON values. The other + * functions just wrap GetSetting() for convenience. + * + * As can be seen in the chart, the default behavior of the flags is not + * very restrictive, although it can be restricted further. It tries to + * accommodate parsing command lines and configuration files written by + * human beings, not just machines, understanding that users may have + * different configuration styles and debugging needs. So the flags do not + * mandate one way to configure things or try to prevent every possible + * error, but instead catch the most common and blatant errors, and allow + * application code to impose additional restrictions, since application + * code needs to parse settings and reject invalid values anyway. + * + * Specifically, by default: + * + * - All settings can be specified multiple times, not just ALLOW_LIST + * settings. This allows users to override the config file from the + * command line, and override earlier command line settings with later + * ones. Application code can disable this behavior by calling the + * GetArgs() function and raising an error if more than one value is + * returned. + * + * - All settings can be negated. This provides a consistent command line + * interface where settings support -nofoo syntax when meaningful. + * GetSetting() returns a false JSON value for negated settings, and + * GetArg(), GetIntArg(), and GetBoolArg() return "", 0, and false + * respectively. Application code can disallow negation by specifying the + * DISALLOW_NEGATION flag, or just handling "", 0, and false values and + * rejecting them if they do not make sense. + * + * - All settings can be empty. Since all settings are optional, it is + * useful to have a way to set them, and a way to unset them. It is also + * unambiguous in most cases to treat empty -foo= syntax as not setting a + * value, so by default this syntax is allowed and causes GetSetting() to + * return JSON "", and GetArg(), GetIntArg() and GetBoolArg() to return + * std::nullopt. Application code can override this behavior by rejecting + * these values. */ enum Flags : uint32_t { - ALLOW_ANY = 0x01, //!< disable validation - // ALLOW_BOOL = 0x02, //!< unimplemented, draft implementation in #16545 - // ALLOW_INT = 0x04, //!< unimplemented, draft implementation in #16545 - // ALLOW_STRING = 0x08, //!< unimplemented, draft implementation in #16545 - // ALLOW_LIST = 0x10, //!< unimplemented, draft implementation in #16545 + ALLOW_ANY = 0x01, //!< allow any argument value (no type checking) + ALLOW_BOOL = 0x02, //!< allow -foo=1, -foo=0, -foo, -nofoo, -nofoo=1, and -foo= + ALLOW_INT = 0x04, //!< allow -foo=123, -nofoo, -nofoo=1, and -foo= + ALLOW_STRING = 0x08, //!< allow -foo=abc, -nofoo, -nofoo=1, and -foo= + ALLOW_LIST = 0x10, //!< allow multiple -foo=bar -foo=baz values DISALLOW_NEGATION = 0x20, //!< disallow -nofoo syntax DISALLOW_ELISION = 0x40, //!< disallow -foo syntax that doesn't assign any value @@ -154,12 +226,73 @@ protected: bool UseDefaultSection(const std::string& arg) const EXCLUSIVE_LOCKS_REQUIRED(cs_args); public: + ArgsManager(); + ~ArgsManager(); + + /** + * @name GetArg Functions + * + * GetArg functions are an easy way to access most settings. They are + * wrappers around the lower-level GetSetting() function that provide + * greater convenience. + * + * Examples: + * + * GetArg("-foo") // returns "abc" if -foo=abc was specified, or nullopt if unset + * GetIntArg("-foo") // returns 123 if -foo=123 was specified, or nullopt if unset + * GetBoolArg("-foo") // returns true if -foo was specified, or nullopt if unset + * GetBoolArg("-foo") // returns false if -nofoo was specified, or nullopt if unset + * + * If no type flags (ALLOW_STRING, ALLOW_INT, or ALLOW_BOOL) are set, GetArg + * functions do many type coercions and can have surprising behaviors which + * legacy code relies on, like parsing -nofoo as string "0" or -foo=true as + * boolean false. + * + * If any type flags are set, then: + * + * - Only GetArg functions with types matching the flags can be called. For + * example, it is an error to call GetIntArg() if ALLOW_INT is not set. + * + * - GetArg functions act like std::get_if(), returning null if the + * requested type is not available or the setting is unspecified or empty. + * + * - "Widening" type conversions from smaller to bigger types are done if + * unambiguous (bool -> int -> string). For example, if settings.json + * contains {"foo":123}, GetArg("-foo") will return "123". If it contains + * {"foo":true}, GetIntArg("-foo") will return 1. + * + * - "Narrowing" type conversions in the other direction are not done even + * when they would be unambiguous. This makes it possible to distinguish + * values by type by checking for narrow types first. For example, to + * handle boolean settings.json or command line values (-foo -nofoo) + * differently than string values (-foo=abc), you can write: + * + * if (auto foo{args.GetBoolArg("-foo")}) { + * // handle -foo or -nofoo bool in foo.value() + * } else if (auto foo{args.GetArg("-foo")}) { + * // handle -foo=abc string in foo.value() + * } else { + * // handle unset setting + * } + * + * More examples of GetArg function usage can be found in the + * @ref example_options::ReadOptions() function in + * @ref argsman_tests.cpp + *@{*/ + std::optional GetArg(const std::string& strArg) const; + std::optional GetIntArg(const std::string& strArg) const; + std::optional GetBoolArg(const std::string& strArg) const; + /**@}*/ + /** * Get setting value. * - * Result will be null if setting was unset, true if "-setting" argument was passed - * false if "-nosetting" argument was passed, and a string if a "-setting=value" - * argument was passed. + * Result will be null if setting was unspecified, true if `-setting` + * argument was passed, false if `-nosetting` argument was passed, and will + * be a string, integer, or boolean if a `-setting=value` argument was + * passed (which of the three depends on ALLOW_STRING, ALLOW_INT, and + * ALLOW_BOOL flags). See \ref InterpretValue for a full description of how + * command line and configuration strings map to JSON values. */ common::SettingsValue GetSetting(const std::string& arg) const; @@ -168,9 +301,6 @@ protected: */ std::vector GetSettingsList(const std::string& arg) const; - ArgsManager(); - ~ArgsManager(); - /** * Select the network in use */ @@ -271,7 +401,6 @@ protected: * @return command-line argument or default value */ std::string GetArg(const std::string& strArg, const std::string& strDefault) const; - std::optional GetArg(const std::string& strArg) const; /** * Return path argument or default value @@ -293,7 +422,6 @@ protected: * @return command-line argument (0 if invalid number) or default value */ int64_t GetIntArg(const std::string& strArg, int64_t nDefault) const; - std::optional GetIntArg(const std::string& strArg) const; /** * Return boolean argument or default value @@ -303,7 +431,6 @@ protected: * @return command-line argument or default value */ bool GetBoolArg(const std::string& strArg, bool fDefault) const; - std::optional GetBoolArg(const std::string& strArg) const; /** * Set an argument if it doesn't already have a value @@ -423,6 +550,8 @@ protected: void LogArgs() const; private: + bool CheckArgFlags(const std::string& name, uint32_t require, uint32_t forbid, const char* context) const; + /** * Get data directory path * @@ -446,6 +575,13 @@ private: const std::map>& args) const; }; +//! Whether the type of the argument has been specified and extra validation +//! rules should apply. +inline bool IsTypedArg(uint32_t flags) +{ + return flags & (ArgsManager::ALLOW_BOOL | ArgsManager::ALLOW_INT | ArgsManager::ALLOW_STRING); +} + extern ArgsManager gArgs; /** diff --git a/src/common/config.cpp b/src/common/config.cpp index fac4aa314c5..0fbe84996ae 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -102,6 +102,10 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file std::optional flags = GetArgFlags('-' + key.name); if (!IsConfSupported(key, error)) return false; if (flags) { + if (!(*flags & ALLOW_LIST) && m_settings.ro_config[key.section].count(key.name)) { + error = strprintf("Multiple values specified for -%s in same section of config file.", key.name); + return false; + } std::optional value = InterpretValue(key, &option.second, *flags, error); if (!value) { return false; diff --git a/src/init.cpp b/src/init.cpp index 7fdbf75dc66..e95b4ec9f04 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -489,9 +489,9 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-datadir=", "Specify data directory", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS); argsman.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); argsman.AddArg("-dbcache=", strprintf("Maximum database cache size MiB (minimum %d, default: %d). Make sure you have enough RAM. In addition, unused memory allocated to the mempool is shared with this cache (see -maxmempool).", MIN_DB_CACHE >> 20, DEFAULT_DB_CACHE >> 20), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-includeconf=", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-includeconf=", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::OPTIONS); argsman.AddArg("-allowignoredconf", strprintf("For backwards compatibility, treat an unused %s file in the datadir as a warning, not an error.", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-loadblock=", "Imports blocks from external file on startup", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-loadblock=", "Imports blocks from external file on startup", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::OPTIONS); argsman.AddArg("-maxmempool=", strprintf("Keep the transaction memory pool below megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE_MB), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-maxorphantx=", strprintf("Keep at most unconnectable transactions in memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-mempoolexpiry=", strprintf("Do not keep transactions in the mempool longer than hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY_HOURS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -513,24 +513,24 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-settings=", strprintf("Specify path to dynamic settings data file. Can be disabled with -nosettings. File is written at runtime and not meant to be edited by users (use %s instead for custom settings). Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME, BITCOIN_SETTINGS_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #if HAVE_SYSTEM argsman.AddArg("-startupnotify=", "Execute command on startup.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-shutdownnotify=", "Execute command immediately before beginning shutdown. The need for shutdown may be urgent, so be careful not to delay it long (if the command doesn't require interaction with the server, consider having it fork into the background).", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-shutdownnotify=", "Execute command immediately before beginning shutdown. The need for shutdown may be urgent, so be careful not to delay it long (if the command doesn't require interaction with the server, consider having it fork into the background).", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::OPTIONS); #endif argsman.AddArg("-txindex", strprintf("Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)", DEFAULT_TXINDEX), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-blockfilterindex=", strprintf("Maintain an index of compact filters by block (default: %s, values: %s).", DEFAULT_BLOCKFILTERINDEX, ListBlockFilterTypes()) + " If is not supplied or if = 1, indexes for all known types are enabled.", - ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + ArgsManager::ALLOW_BOOL | ArgsManager::ALLOW_STRING | ArgsManager::ALLOW_LIST, OptionsCategory::OPTIONS); - argsman.AddArg("-addnode=", strprintf("Add a node to connect to and attempt to keep the connection open (see the addnode RPC help for more info). This option can be specified multiple times to add multiple nodes; connections are limited to %u at a time and are counted separately from the -maxconnections limit.", MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + argsman.AddArg("-addnode=", strprintf("Add a node to connect to and attempt to keep the connection open (see the addnode RPC help for more info). This option can be specified multiple times to add multiple nodes; connections are limited to %u at a time and are counted separately from the -maxconnections limit.", MAX_ADDNODE_CONNECTIONS), ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-asmap=", strprintf("Specify asn mapping used for bucketing of the peers (default: %s). Relative paths will be prefixed by the net-specific datadir location.", DEFAULT_ASMAP_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-bantime=", strprintf("Default duration (in seconds) of manually configured bans (default: %u)", DEFAULT_MISBEHAVING_BANTIME), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-bind=[:][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet3: 127.0.0.1:%u=onion, testnet4: 127.0.0.1:%u=onion, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultChainParams->GetDefaultPort() + 1, testnetChainParams->GetDefaultPort() + 1, testnet4ChainParams->GetDefaultPort() + 1, signetChainParams->GetDefaultPort() + 1, regtestChainParams->GetDefaultPort() + 1), ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + argsman.AddArg("-bind=[:][=onion]", strprintf("Bind to given address and always listen on it (default: 0.0.0.0). Use [host]:port notation for IPv6. Append =onion to tag any incoming connections to that address and port as incoming Tor connections (default: 127.0.0.1:%u=onion, testnet3: 127.0.0.1:%u=onion, testnet4: 127.0.0.1:%u=onion, signet: 127.0.0.1:%u=onion, regtest: 127.0.0.1:%u=onion)", defaultChainParams->GetDefaultPort() + 1, testnetChainParams->GetDefaultPort() + 1, testnet4ChainParams->GetDefaultPort() + 1, signetChainParams->GetDefaultPort() + 1, regtestChainParams->GetDefaultPort() + 1), ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-cjdnsreachable", "If set, then this host is configured for CJDNS (connecting to fc00::/8 addresses would lead us to the CJDNS network, see doc/cjdns.md) (default: 0)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-connect=", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); + argsman.AddArg("-connect=", "Connect only to the specified node; -noconnect disables automatic connections (the rules for this peer are the same as for -addnode). This option can be specified multiple times to connect to multiple nodes.", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST | ArgsManager::NETWORK_ONLY, OptionsCategory::CONNECTION); argsman.AddArg("-discover", "Discover own IP addresses (default: 1 when listening and no -externalip or -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-dns", strprintf("Allow DNS lookups for -addnode, -seednode and -connect (default: %u)", DEFAULT_NAME_LOOKUP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-dnsseed", strprintf("Query for peer addresses via DNS lookup, if low on addresses (default: %u unless -connect used or -maxconnections=0)", DEFAULT_DNSSEED), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-externalip=", "Specify your own public address", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-externalip=", "Specify your own public address", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::CONNECTION); argsman.AddArg("-fixedseeds", strprintf("Allow fixed seeds if DNS seeds don't provide peers (default: %u)", DEFAULT_FIXEDSEEDS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-forcednsseed", strprintf("Always query for peer addresses via DNS lookup (default: %u)", DEFAULT_FORCEDNSSEED), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-listen", strprintf("Accept connections from outside (default: %u if no -proxy, -connect or -maxconnections=0)", DEFAULT_LISTEN), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -546,7 +546,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) #endif argsman.AddArg("-i2psam=", "I2P SAM proxy to reach I2P peers and accept I2P connections (default: none)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-i2pacceptincoming", strprintf("Whether to accept inbound I2P connections (default: %i). Ignored if -i2psam is not set. Listening for inbound I2P connections is done through the SAM proxy, not by binding to a local address and port.", DEFAULT_I2P_ACCEPT_INCOMING), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-onlynet=", "Make automatic outbound connections only to network (" + Join(GetNetworkNames(), ", ") + "). Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-onlynet=", "Make automatic outbound connections only to network (" + Join(GetNetworkNames(), ", ") + "). Inbound and manual connections are not affected by this option. It can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::CONNECTION); argsman.AddArg("-v2transport", strprintf("Support v2 transport (default: %u)", DEFAULT_V2_TRANSPORT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -558,7 +558,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-proxy=", "Connect through SOCKS5 proxy, set -noproxy to disable (default: disabled)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_ELISION, OptionsCategory::CONNECTION); #endif argsman.AddArg("-proxyrandomize", strprintf("Randomize credentials for every proxy connection. This enables Tor stream isolation (default: %u)", DEFAULT_PROXYRANDOMIZE), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); - argsman.AddArg("-seednode=", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes. During startup, seednodes will be tried before dnsseeds.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + argsman.AddArg("-seednode=", "Connect to a node to retrieve peer addresses, and disconnect. This option can be specified multiple times to connect to multiple nodes. During startup, seednodes will be tried before dnsseeds.", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::CONNECTION); argsman.AddArg("-networkactive", "Enable all P2P network activity (default: 1). Can be changed by the setnetworkactive RPC command", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-timeout=", strprintf("Specify socket connection timeout in milliseconds. If an initial attempt to connect is unsuccessful after this amount of time, drop it (minimum: 1, default: %d)", DEFAULT_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-peertimeout=", strprintf("Specify a p2p connection timeout delay in seconds. After connecting to a peer, wait this amount of time before considering disconnection based on inactivity (minimum: 1, default: %d)", DEFAULT_PEER_CONNECT_TIMEOUT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CONNECTION); @@ -569,22 +569,22 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-natpmp", strprintf("Use PCP or NAT-PMP to map the listening port (default: %u)", DEFAULT_NATPMP), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); argsman.AddArg("-whitebind=<[permissions@]addr>", "Bind to the given address and add permission flags to the peers connecting to it. " "Use [host]:port notation for IPv6. Allowed permissions: " + Join(NET_PERMISSIONS_DOC, ", ") + ". " - "Specify multiple permissions separated by commas (default: download,noban,mempool,relay). Can be specified multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + "Specify multiple permissions separated by commas (default: download,noban,mempool,relay). Can be specified multiple times.", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::CONNECTION); argsman.AddArg("-whitelist=<[permissions@]IP address or network>", "Add permission flags to the peers using the given IP address (e.g. 1.2.3.4) or " "CIDR-notated network (e.g. 1.2.3.0/24). Uses the same permissions as " "-whitebind. " "Additional flags \"in\" and \"out\" control whether permissions apply to incoming connections and/or manual (default: incoming only). " - "Can be specified multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); + "Can be specified multiple times.", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::CONNECTION); g_wallet_init_interface.AddWalletOptions(argsman); #ifdef ENABLE_ZMQ - argsman.AddArg("-zmqpubhashblock=
", "Enable publish hash block in
", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); - argsman.AddArg("-zmqpubhashtx=
", "Enable publish hash transaction in
", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); - argsman.AddArg("-zmqpubrawblock=
", "Enable publish raw block in
", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); - argsman.AddArg("-zmqpubrawtx=
", "Enable publish raw transaction in
", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); - argsman.AddArg("-zmqpubsequence=
", "Enable publish hash block and tx sequence in
", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + argsman.AddArg("-zmqpubhashblock=
", "Enable publish hash block in
", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::ZMQ); + argsman.AddArg("-zmqpubhashtx=
", "Enable publish hash transaction in
", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::ZMQ); + argsman.AddArg("-zmqpubrawblock=
", "Enable publish raw block in
", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::ZMQ); + argsman.AddArg("-zmqpubrawtx=
", "Enable publish raw transaction in
", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::ZMQ); + argsman.AddArg("-zmqpubsequence=
", "Enable publish hash block and tx sequence in
", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubhashblockhwm=", strprintf("Set publish hash block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubhashtxhwm=", strprintf("Set publish hash transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubrawblockhwm=", strprintf("Set publish raw block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); @@ -609,14 +609,14 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) argsman.AddArg("-checkaddrman=", strprintf("Run addrman consistency checks every operations. Use 0 to disable. (default: %u)", DEFAULT_ADDRMAN_CONSISTENCY_CHECKS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-checkmempool=", strprintf("Run mempool consistency checks every transactions. Use 0 to disable. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-checkpoints", strprintf("Enable rejection of any forks from the known historical chain until block %s (default: %u)", defaultChainParams->Checkpoints().GetHeight(), DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); - argsman.AddArg("-deprecatedrpc=", "Allows deprecated RPC method(s) to be used", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-deprecatedrpc=", "Allows deprecated RPC method(s) to be used", ArgsManager::ALLOW_ANY | ArgsManager::ALLOW_LIST | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", DEFAULT_STOPAFTERBLOCKIMPORT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-stopatheight", strprintf("Stop running after reaching the given height in the main chain (default: %u)", DEFAULT_STOPATHEIGHT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-limitancestorcount=", strprintf("Do not accept transactions if number of in-mempool ancestors is or more (default: %u)", DEFAULT_ANCESTOR_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-limitancestorsize=", strprintf("Do not accept transactions whose size with all in-mempool ancestors exceeds kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT_KVB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-limitdescendantcount=", strprintf("Do not accept transactions if any ancestor would have or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-limitdescendantsize=", strprintf("Do not accept transactions if any ancestor would have more than kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT_KVB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); - argsman.AddArg("-test=