diff --git a/src/Makefile.am b/src/Makefile.am index feed4a0061cd..743e0101c3cc 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -136,6 +136,7 @@ BITCOIN_CORE_H = \ clientversion.h \ coins.h \ common/args.h \ + common/argsregister.h \ common/bloom.h \ common/init.h \ common/run_command.h \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 539578085b5a..75f02c7ed648 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -21,82 +22,144 @@ #include #include -void ReadSigNetArgs(const ArgsManager& args, CChainParams::SigNetOptions& options) + +namespace { +class SigNetArgsRegister { - if (args.IsArgSet("-signetseednode")) { - options.seeds.emplace(args.GetArgs("-signetseednode")); - } - if (args.IsArgSet("-signetchallenge")) { - const auto signet_challenge = args.GetArgs("-signetchallenge"); - if (signet_challenge.size() != 1) { +public: + using T = CChainParams::SigNetOptions; + + static inline void GetChallenge(std::optional>& challenge, const std::vector& arg_challenges) + { + if (arg_challenges.size() != 1) { throw std::runtime_error("-signetchallenge cannot be multiple values."); } - const auto val{TryParseHex(signet_challenge[0])}; + + const auto val{TryParseHex(arg_challenges[0])}; if (!val) { - throw std::runtime_error(strprintf("-signetchallenge must be hex, not '%s'.", signet_challenge[0])); + throw std::runtime_error(strprintf("-signetchallenge must be hex, not '%s'.", arg_challenges[0])); } - options.challenge.emplace(*val); + challenge.emplace(*val); } -} -void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& options) + template + static inline void Register(Op& op) + { + return C::Do(op, + C::Defn(&T::challenge, "-signetchallenge", "", GetChallenge, + "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), + C::Defn(&T::seeds, "-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) + ); + } +}; + +class RegTestArgsRegister { - if (auto value = args.GetBoolArg("-fastprune")) options.fastprune = *value; +public: + using T = CChainParams::RegTestOptions; - for (const std::string& arg : args.GetArgs("-testactivationheight")) { - const auto found{arg.find('@')}; - if (found == std::string::npos) { - throw std::runtime_error(strprintf("Invalid format (%s) for -testactivationheight=name@height.", arg)); - } - const auto value{arg.substr(found + 1)}; - int32_t height; - if (!ParseInt32(value, &height) || height < 0 || height >= std::numeric_limits::max()) { - throw std::runtime_error(strprintf("Invalid height value (%s) for -testactivationheight=name@height.", arg)); - } + static inline void GetActivationHeights(std::unordered_map& activation_heights, const std::vector& args) + { + for (const std::string& arg : args) { + const auto found{arg.find('@')}; + if (found == std::string::npos) { + throw std::runtime_error(strprintf("Invalid format (%s) for -testactivationheight=name@height.", arg)); + } - const auto deployment_name{arg.substr(0, found)}; - if (const auto buried_deployment = GetBuriedDeployment(deployment_name)) { - options.activation_heights[*buried_deployment] = height; - } else { - throw std::runtime_error(strprintf("Invalid name (%s) for -testactivationheight=name@height.", arg)); + const auto value{arg.substr(found + 1)}; + int32_t height; + if (!ParseInt32(value, &height) || height < 0 || height >= std::numeric_limits::max()) { + throw std::runtime_error(strprintf("Invalid height value (%s) for -testactivationheight=name@height.", arg)); + } + + const auto deployment_name{arg.substr(0, found)}; + if (const auto buried_deployment = GetBuriedDeployment(deployment_name)) { + activation_heights[*buried_deployment] = height; + } else { + throw std::runtime_error(strprintf("Invalid name (%s) for -testactivationheight=name@height.", arg)); + } } } - if (!args.IsArgSet("-vbparams")) return; - - for (const std::string& strDeployment : args.GetArgs("-vbparams")) { - std::vector vDeploymentParams = SplitString(strDeployment, ':'); - if (vDeploymentParams.size() < 3 || 4 < vDeploymentParams.size()) { - throw std::runtime_error("Version bits parameters malformed, expecting deployment:start:end[:min_activation_height]"); - } - CChainParams::VersionBitsParameters vbparams{}; - if (!ParseInt64(vDeploymentParams[1], &vbparams.start_time)) { - throw std::runtime_error(strprintf("Invalid nStartTime (%s)", vDeploymentParams[1])); - } - if (!ParseInt64(vDeploymentParams[2], &vbparams.timeout)) { - throw std::runtime_error(strprintf("Invalid nTimeout (%s)", vDeploymentParams[2])); - } - if (vDeploymentParams.size() >= 4) { - if (!ParseInt32(vDeploymentParams[3], &vbparams.min_activation_height)) { - throw std::runtime_error(strprintf("Invalid min_activation_height (%s)", vDeploymentParams[3])); + static inline void GetVBParams(std::unordered_map& version_bits_parameters, const std::vector& args) + { + for (const std::string& deployment : args) { + std::vector vDeploymentParams = SplitString(deployment, ':'); + if (vDeploymentParams.size() < 3 || 4 < vDeploymentParams.size()) { + throw std::runtime_error("Version bits parameters malformed, expecting deployment:start:end[:min_activation_height]"); } - } else { - vbparams.min_activation_height = 0; - } - bool found = false; - for (int j=0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { - if (vDeploymentParams[0] == VersionBitsDeploymentInfo[j].name) { - options.version_bits_parameters[Consensus::DeploymentPos(j)] = vbparams; - found = true; - LogPrintf("Setting version bits activation parameters for %s to start=%ld, timeout=%ld, min_activation_height=%d\n", vDeploymentParams[0], vbparams.start_time, vbparams.timeout, vbparams.min_activation_height); - break; + CChainParams::VersionBitsParameters vbparams{}; + if (!ParseInt64(vDeploymentParams[1], &vbparams.start_time)) { + throw std::runtime_error(strprintf("Invalid nStartTime (%s)", vDeploymentParams[1])); + } + if (!ParseInt64(vDeploymentParams[2], &vbparams.timeout)) { + throw std::runtime_error(strprintf("Invalid nTimeout (%s)", vDeploymentParams[2])); + } + if (vDeploymentParams.size() >= 4) { + if (!ParseInt32(vDeploymentParams[3], &vbparams.min_activation_height)) { + throw std::runtime_error(strprintf("Invalid min_activation_height (%s)", vDeploymentParams[3])); + } + } else { + vbparams.min_activation_height = 0; + } + bool found = false; + for (int j=0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { + if (vDeploymentParams[0] == VersionBitsDeploymentInfo[j].name) { + version_bits_parameters[Consensus::DeploymentPos(j)] = vbparams; + found = true; + LogPrintf("Setting version bits activation parameters for %s to start=%ld, timeout=%ld, min_activation_height=%d\n", vDeploymentParams[0], vbparams.start_time, vbparams.timeout, vbparams.min_activation_height); + break; + } + } + if (!found) { + throw std::runtime_error(strprintf("Invalid deployment (%s)", vDeploymentParams[0])); } - } - if (!found) { - throw std::runtime_error(strprintf("Invalid deployment (%s)", vDeploymentParams[0])); } } + + template + static inline void Register(Op& op) + { + return C::Do(op, + C::Defn(&T::version_bits_parameters, "-vbparams", "=deployment:start:end[:min_activation_height]", GetVBParams, + "Use given start/end times and min_activation_height for specified version bits deployment (regtest-only)", + ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, + OptionsCategory::DEBUG_TEST), + C::Defn(&T::activation_heights, "-testactivationheight", "=name@height.", GetActivationHeights, + "Set the activation height of 'name' (segwit, bip34, dersig, cltv, csv). (regtest-only)", + ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, + OptionsCategory::DEBUG_TEST), + C::Defn(&T::fastprune, "-fastprune", "", + "Use smaller block files and lower minimum prune height for testing purposes", + ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, + OptionsCategory::DEBUG_TEST) + ); + } +}; + +} // anon namespace + +void SetupChainParamsOptions(ArgsManager& argsman) +{ + ArgsRegister::Register(argsman); + ArgsRegister::Register(argsman); +} + + +void ReadSigNetArgs(const ArgsManager& args, CChainParams::SigNetOptions& options) +{ + ArgsRegister::Update(args, options); +} + +void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& options) +{ + ArgsRegister::Update(args, options); } static std::unique_ptr globalChainParams; diff --git a/src/chainparams.h b/src/chainparams.h index 4743e022dbbb..6c2f9bfb3bb4 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -12,6 +12,11 @@ class ArgsManager; +/** + * Setup configuration options + */ +void SetupChainParamsOptions(ArgsManager& argsman); + /** * Creates and returns a std::unique_ptr of the chosen chain. */ diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 8cbf9e85e0ca..9bd4aac9ec9d 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -16,12 +16,8 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman) argsman.AddArg("-chain=", "Use the chain (default: main). Allowed values: main, test, signet, regtest", 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("-testnet", "Use the test chain. Equivalent to -chain=test.", 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("-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); } static std::unique_ptr globalChainBaseParams; diff --git a/src/common/args.cpp b/src/common/args.cpp index 643838399fc5..f043294e070c 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -489,6 +489,28 @@ std::optional ArgsManager::GetIntArg(const std::string& strArg) const return SettingToInt(value); } +template<> +std::optional ArgsManager::Get(const std::string& arg) const +{ + return GetArg(arg); +} + +template<> +std::optional ArgsManager::Get(const std::string& arg) const +{ + return GetBoolArg(arg); +} + +template<> +std::optional> ArgsManager::Get>(const std::string& arg) const +{ + std::optional> result{GetArgs(arg)}; + if (result.has_value() && result.value().empty()) { + result.reset(); + } + return result; +} + std::optional SettingToInt(const common::SettingsValue& value) { if (value.isNull()) return std::nullopt; diff --git a/src/common/args.h b/src/common/args.h index ae3ed02bc7df..e7ccce22c5a5 100644 --- a/src/common/args.h +++ b/src/common/args.h @@ -260,6 +260,15 @@ class ArgsManager */ bool IsArgNegated(const std::string& strArg) const; + /** + * Return typed argument if present + * + * @param arg Argument to get (e.g. "-foo") + * @return command-line argument or nullopt + */ + template + std::optional Get(const std::string& arg) const; + /** * Return string argument or default value * @@ -343,6 +352,17 @@ class ArgsManager */ void AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat); + /** + * Add typed argument + */ + template + void AddTypedArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat) + { + // able to asssociate type information with argument directly in future, + // eg specialise AddTypedArg> to call AddArg(name, help, flags|ALLOW_LIST, cat). + AddArg(name, help, flags, cat); + } + /** * Add subcommand */ diff --git a/src/common/argsregister.h b/src/common/argsregister.h new file mode 100644 index 000000000000..80d67dcd75fc --- /dev/null +++ b/src/common/argsregister.h @@ -0,0 +1,151 @@ +// Copyright (c) 2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_COMMON_ARGSREGISTER_H +#define BITCOIN_COMMON_ARGSREGISTER_H + +#include + +#include + +/** Typesafe args registration. */ + +/** Example usage: + +constexpr bool DEFAULT_FOO_A{true}; + +struct FooOpts { + bool a{DEFAULT_FOO_A}; + std::optional b; + std::vector c; + Custom d; +}; + +class FooRegister +{ +public: + using T = CChainParams::SigNetOptions; + + static inline void GetD(Custom& d, const std::string& arg_d); + + template + static inline void Register(Op& op) + { + return C::Do(op, + C::Defn(&T::a, "-fooa", "", + "Description of option A", + ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, + OptionsCategory::DEBUG_TEST), + C::Defn(&T::b, "-foob", "=bar", + "Description of option B", + ArgsManager::ALLOW_ANY, + OptionsCategory::DEBUG_TEST), + C::Defn(&T::c, "-foob", "=bar", + "Description of option B", + ArgsManager::ALLOW_ANY, + OptionsCategory::DEBUG_TEST), + C::Defn(&T::d, "-foob", "=bar", GetD, + "Description of option B", + ArgsManager::ALLOW_ANY, + OptionsCategory::DEBUG_TEST) + ); + } +}; + +void SetupFooOptions(ArgsManager& argsman) +{ + ArgsRegister::Register(argsman); +} + +void ReadFooArgs(const ArgsManager& args, FooOpts& options) +{ + ArgsRegister::Update(args, options); +} + +**/ + +template +class ArgsRegister +{ +public: + using STRUCT = typename REG::T; + + static inline void Register(ArgsManager& args) + { + auto l = [&](const auto& sd) { + using AT = typename std::remove_reference_t::ArgType; + args.AddTypedArg(sd.name+sd.params, sd.desc, sd.flags, sd.cat); + }; + (REG::template Register<_Do>)(l); + } + + static inline void Update(const ArgsManager& args, STRUCT& options) + { + auto l = [&](const auto& sd) { + using AT = typename std::remove_reference_t::ArgType; + auto arg = args.Get(sd.name); + if (arg.has_value()) { + sd.cvt(options.*(sd.field), arg.value()); + } + }; + (REG::template Register<_Do>)(l); + } + +private: + template + struct ArgDefn + { + using FieldType = FT; + using ArgType = AT; + + void (&cvt)(FieldType&, const ArgType&); + FieldType STRUCT::* field; + std::string name; + std::string params; // "=blah" or "" + std::string desc; + unsigned int flags; + OptionsCategory cat; + }; + + class _Do + { + private: + template + static inline void Do(Op& op) { } + + template + static inline void set_directly(T& dst, const T& src) { dst = src; } + + template + static inline void set_optional(std::optional& dst, const T& src) { dst = src; } + + public: + template + static inline ArgDefn, T> Defn(std::optional STRUCT::* field, const std::string& name, const std::string& params, const std::string& desc, Ts... ts) + { + return { set_optional, field, name, params, desc, ts... }; + } + + template + static inline ArgDefn Defn(T STRUCT::* field, const std::string& name, const std::string& params, const std::string& desc, Ts... ts) + { + return { set_directly, field, name, params, desc, ts... }; + } + + template + static inline ArgDefn Defn(FT STRUCT::* field, const std::string& name, const std::string& params, void(&cvt)(FT&,const AT&), const std::string& desc, Ts... ts) + { + return { cvt, field, name, params, desc, ts... }; + } + + template + static inline void Do(Op& op, const ArgDefn& sd, Ts... ts) + { + op(sd); + Do(op, ts...); + } + }; +}; + +#endif // BITCOIN_COMMON_ARGSREGISTER_H diff --git a/src/init.cpp b/src/init.cpp index 864c2e278b18..459f3c857bb5 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -436,7 +436,6 @@ void SetupServerArgs(ArgsManager& argsman) #endif argsman.AddArg("-assumevalid=", strprintf("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s, signet: %s)", defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex(), signetChainParams->GetConsensus().defaultAssumeValid.GetHex()), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-blocksdir=", "Specify directory to hold blocks subdirectory for *.dat files (default: )", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - argsman.AddArg("-fastprune", "Use smaller block files and lower minimum prune height for testing purposes", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); #if HAVE_SYSTEM argsman.AddArg("-blocknotify=", "Execute command when the best block changes (%s in cmd is replaced by block hash)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #endif @@ -584,6 +583,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-uacomment=", "Append comment to the user agent string", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); SetupChainParamsBaseOptions(argsman); + SetupChainParamsOptions(argsman); argsman.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", !testnetChainParams->RequireStandard()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); argsman.AddArg("-incrementalrelayfee=", strprintf("Fee rate (in %s/kvB) used to define cost of relay, used for mempool limiting and replacement policy. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);