diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 04652688dbef..06de1e2237dc 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -489,11 +489,14 @@ SECONDARY: $(QT_QM) $(srcdir)/qt/dashstrings.cpp: FORCE @test -n $(XGETTEXT) || echo "xgettext is required for updating translations" - $(AM_V_GEN) cd $(srcdir); XGETTEXT=$(XGETTEXT) COPYRIGHT_HOLDERS="$(COPYRIGHT_HOLDERS)" $(PYTHON) ../share/qt/extract_strings_qt.py $(libbitcoin_node_a_SOURCES) $(libbitcoin_wallet_a_SOURCES) $(libbitcoin_common_a_SOURCES) $(libbitcoin_zmq_a_SOURCES) $(libbitcoin_consensus_a_SOURCES) $(libbitcoin_util_a_SOURCES) + $(AM_V_GEN) cd $(srcdir); XGETTEXT=$(XGETTEXT) COPYRIGHT_HOLDERS="$(COPYRIGHT_HOLDERS)" $(PYTHON) ../share/qt/extract_strings_qt.py \ + $(libbitcoin_node_a_SOURCES) $(libbitcoin_wallet_a_SOURCES) $(libbitcoin_common_a_SOURCES) \ + $(libbitcoin_zmq_a_SOURCES) $(libbitcoin_consensus_a_SOURCES) $(libbitcoin_util_a_SOURCES) \ + $(BITCOIN_QT_BASE_CPP) $(BITCOIN_QT_WINDOWS_CPP) $(BITCOIN_QT_WALLET_CPP) $(BITCOIN_QT_H) $(BITCOIN_MM) # The resulted dash_en.xlf source file should follow Transifex requirements. # See: https://docs.transifex.com/formats/xliff#how-to-distinguish-between-a-source-file-and-a-translation-file -translate: $(srcdir)/qt/dashstrings.cpp $(QT_FORMS_UI) $(QT_FORMS_UI) $(BITCOIN_QT_BASE_CPP) qt/bitcoin.cpp $(BITCOIN_QT_WINDOWS_CPP) $(BITCOIN_QT_WALLET_CPP) $(BITCOIN_QT_H) $(BITCOIN_MM) +translate: $(srcdir)/qt/dashstrings.cpp $(QT_FORMS_UI) $(QT_FORMS_UI) $(BITCOIN_QT_BASE_CPP) $(BITCOIN_QT_WINDOWS_CPP) $(BITCOIN_QT_WALLET_CPP) $(BITCOIN_QT_H) $(BITCOIN_MM) @test -n $(LUPDATE) || echo "lupdate is required for updating translations" $(AM_V_GEN) QT_SELECT=$(QT_SELECT) $(LUPDATE) -no-obsolete -I $(srcdir) -locations relative $^ -ts $(srcdir)/qt/locale/dash_en.ts @test -n $(LCONVERT) || echo "lconvert is required for updating translations" diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 1803294f7b3c..443b23dbbac2 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -500,11 +500,6 @@ static RPCHelpMan getblockfrompeer() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValue::VSTR, // blockhash - UniValue::VNUM, // peer_id - }); - const NodeContext& node = EnsureAnyNodeContext(request.context); ChainstateManager& chainman = EnsureChainman(node); PeerManager& peerman = EnsurePeerman(node); @@ -929,7 +924,8 @@ static RPCHelpMan getblock() "If verbosity is 3, returns an Object with information about block and information about each transaction, including prevout information for inputs (only for unpruned blocks in the current best chain).\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The block hash"}, - {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1}, "0 for hex-encoded data, 1 for a JSON object, 2 for JSON object with transaction data, and 3 for JSON object with transaction data including prevout information for inputs"}, + {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{1}, "0 for hex-encoded data, 1 for a JSON object, 2 for JSON object with transaction data, and 3 for JSON object with transaction data including prevout information for inputs", + RPCArgOptions{.skip_type_check = true}}, }, { RPCResult{"for verbosity = 0", @@ -1146,7 +1142,11 @@ static RPCHelpMan gettxoutsetinfo() "Note this call may take some time if you are not using coinstatsindex.\n", { {"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized_2"}, "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'muhash', 'none'."}, - {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", "", {"", "string or numeric"}}, + {"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", + RPCArgOptions{ + .skip_type_check = true, + .type_str = {"", "string or numeric"}, + }}, {"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."}, }, RPCResult{ @@ -2019,13 +2019,17 @@ static RPCHelpMan getblockstats() "\nCompute per block statistics for a given window. All amounts are in duffs.\n" "It won't work for some heights with pruning.\n", { - {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", "", {"", "string or numeric"}}, + {"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block hash or height of the target block", + RPCArgOptions{ + .skip_type_check = true, + .type_str = {"", "string or numeric"}, + }}, {"stats", RPCArg::Type::ARR, RPCArg::DefaultHint{"all values"}, "Values to plot (see result below)", { {"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, {"time", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, }, - "stats"}, + RPCArgOptions{.oneline_description="stats"}}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -2457,7 +2461,7 @@ static RPCHelpMan scantxoutset() }, }, }, - "[scanobjects,...]"}, + RPCArgOptions{.oneline_description="[scanobjects,...]"}}, }, { RPCResult{"when action=='start'; only returns after scan completes", RPCResult::Type::OBJ, "", "", { @@ -2497,10 +2501,9 @@ static RPCHelpMan scantxoutset() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); - UniValue result(UniValue::VOBJ); - if (request.params[0].get_str() == "status") { + const auto action{self.Arg("action")}; + if (action == "status") { CoinsViewScanReserver reserver; if (reserver.reserve()) { // no scan in progress @@ -2508,7 +2511,7 @@ static RPCHelpMan scantxoutset() } result.pushKV("progress", g_scan_progress.load()); return result; - } else if (request.params[0].get_str() == "abort") { + } else if (action == "abort") { CoinsViewScanReserver reserver; if (reserver.reserve()) { // reserve was possible which means no scan was running @@ -2517,7 +2520,7 @@ static RPCHelpMan scantxoutset() // set the abort flag g_should_abort_scan = true; return true; - } else if (request.params[0].get_str() == "start") { + } else if (action == "start") { CoinsViewScanReserver reserver; if (!reserver.reserve()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\""); @@ -2586,7 +2589,7 @@ static RPCHelpMan scantxoutset() result.pushKV("unspents", unspents); result.pushKV("total_amount", ValueFromAmount(total_in)); } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command"); + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", action)); } return result; }, diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index 735f524e808b..c2e7f9d8a1f4 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -91,14 +91,16 @@ static RPCArg GetRpcArg(const std::string& strParamName) "Can be set to an empty string, which will require a ProUpServTx afterwards.", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""}, - }} + }, + RPCArgOptions{.skip_type_check = true}} }, {"coreP2PAddrs_update", {"coreP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO, "Array of addresses in the form \"ADDR:PORT\". Must be unique on the network.", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""}, - }} + }, + RPCArgOptions{.skip_type_check = true}} }, {"operatorKey", {"operatorKey", RPCArg::Type::STR, RPCArg::Optional::NO, @@ -187,7 +189,8 @@ static RPCArg GetRpcArg(const std::string& strParamName) "Must be unique on the network. Can be set to an empty string, which will require a ProUpServTx afterwards.", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""}, - }} + }, + RPCArgOptions{.skip_type_check = true}} }, {"platformP2PAddrs_update", {"platformP2PAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO, @@ -195,7 +198,8 @@ static RPCArg GetRpcArg(const std::string& strParamName) "Must be unique on the network.", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""}, - }} + }, + RPCArgOptions{.skip_type_check = true}} }, {"platformHTTPSAddrs", {"platformHTTPSAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO, @@ -203,7 +207,8 @@ static RPCArg GetRpcArg(const std::string& strParamName) "Must be unique on the network. Can be set to an empty string, which will require a ProUpServTx afterwards.", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""}, - }} + }, + RPCArgOptions{.skip_type_check = true}} }, {"platformHTTPSAddrs_update", {"platformHTTPSAddrs", RPCArg::Type::ARR, RPCArg::Optional::NO, @@ -211,7 +216,8 @@ static RPCArg GetRpcArg(const std::string& strParamName) "Must be unique on the network.", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, ""}, - }} + }, + RPCArgOptions{.skip_type_check = true}} }, }; @@ -1408,7 +1414,8 @@ static RPCHelpMan protx_list() " This will also include ProTx which failed PoSe verification.\n" #endif }, - {"detailed", RPCArg::Type::BOOL, RPCArg::Default{false}, "If not specified, only the hashes of the ProTx will be returned."}, + {"detailed", RPCArg::Type::BOOL, RPCArg::Default{false}, "If not specified, only the hashes of the ProTx will be returned.", + RPCArgOptions{.skip_type_check = true}}, {"height", RPCArg::Type::NUM, RPCArg::DefaultHint{"current chain-tip"}, ""}, }, RPCResult{ @@ -1604,17 +1611,12 @@ static const CBlockIndex* ParseBlockIndex(const UniValue& v, const ChainstateMan { AssertLockHeld(::cs_main); - try { - const auto hash{ParseBlock(v, chainman, strName)}; - const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash); - if (!pindex) { - throw std::runtime_error(strprintf("Block %s with hash %s not found", strName, v.getValStr())); - } - return pindex; - } catch (...) { - // Same phrasing as ParseBlock() as it can parse heights - throw std::runtime_error(strprintf("%s must be a block hash or chain height and not %s", strName, v.getValStr())); + const auto hash{ParseBlock(v, chainman, strName)}; + const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash); + if (!pindex) { + throw std::runtime_error(strprintf("Block %s with hash %s not found", strName, v.getValStr())); } + return pindex; } static RPCHelpMan protx_diff() @@ -1622,8 +1624,10 @@ static RPCHelpMan protx_diff() return RPCHelpMan{"protx diff", "\nCalculates a diff between two deterministic masternode lists. The result also contains proof data.\n", { - {"baseBlock", RPCArg::Type::STR, RPCArg::Optional::NO, "The starting block hash or height."}, - {"block", RPCArg::Type::STR, RPCArg::Optional::NO, "The ending block hash or height."}, + {"baseBlock", RPCArg::Type::STR, RPCArg::Optional::NO, "The starting block hash or height.", + RPCArgOptions{.skip_type_check = true}}, + {"block", RPCArg::Type::STR, RPCArg::Optional::NO, "The ending block hash or height.", + RPCArgOptions{.skip_type_check = true}}, {"extended", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Show additional fields."}, }, CSimplifiedMNListDiff::GetJsonHelp(/*key=*/"", /*optional=*/false), @@ -1663,8 +1667,10 @@ static RPCHelpMan protx_listdiff() return RPCHelpMan{"protx listdiff", "\nCalculate a full MN list diff between two masternode lists.\n", { - {"baseBlock", RPCArg::Type::STR, RPCArg::Optional::NO, "The starting block hash or height."}, - {"block", RPCArg::Type::STR, RPCArg::Optional::NO, "The ending block hash or height."}, + {"baseBlock", RPCArg::Type::STR, RPCArg::Optional::NO, "The starting block hash or height.", + RPCArgOptions{.skip_type_check = true}}, + {"block", RPCArg::Type::STR, RPCArg::Optional::NO, "The ending block hash or height.", + RPCArgOptions{.skip_type_check = true}}, }, RPCResult { RPCResult::Type::OBJ, "", "", @@ -1839,8 +1845,10 @@ static RPCHelpMan evodb_verify() "This is a read-only operation that does not modify the database.\n" "If no heights are specified, defaults to the full range from DIP0003 activation to chain tip.\n", { - {"startBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The starting block hash or height (defaults to DIP0003 activation height)."}, - {"stopBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The ending block hash or height (defaults to current chain tip)."}, + {"startBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The starting block hash or height (defaults to DIP0003 activation height).", + RPCArgOptions{.skip_type_check = true}}, + {"stopBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The ending block hash or height (defaults to current chain tip).", + RPCArgOptions{.skip_type_check = true}}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -1877,8 +1885,10 @@ static RPCHelpMan evodb_repair() "If verification fails, recalculates diffs from blockchain data and replaces corrupted records.\n" "If no heights are specified, defaults to the full range from DIP0003 activation to chain tip.\n", { - {"startBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The starting block hash or height (defaults to DIP0003 activation height)."}, - {"stopBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The ending block hash or height (defaults to current chain tip)."}, + {"startBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The starting block hash or height (defaults to DIP0003 activation height).", + RPCArgOptions{.skip_type_check = true}}, + {"stopBlock", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The ending block hash or height (defaults to current chain tip).", + RPCArgOptions{.skip_type_check = true}}, }, RPCResult{ RPCResult::Type::OBJ, "", "", diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp index 0835759b3e75..a4b15f0dc4aa 100644 --- a/src/rpc/fees.cpp +++ b/src/rpc/fees.cpp @@ -64,8 +64,6 @@ static RPCHelpMan estimatesmartfee() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR}); - CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); const NodeContext& node = EnsureAnyNodeContext(request.context); const CTxMemPool& mempool = EnsureMemPool(node); @@ -155,8 +153,6 @@ static RPCHelpMan estimaterawfee() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true); - CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 8315c6a96db3..4c5f97938496 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -60,13 +60,6 @@ RPCHelpMan sendrawtransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValue::VSTR, - UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() - UniValue::VBOOL, - UniValue::VBOOL, - }); - CMutableTransaction mtx; if (!DecodeHexTx(mtx, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input."); @@ -145,10 +138,6 @@ static RPCHelpMan testmempoolaccept() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() - }); const UniValue raw_transactions = request.params[0].get_array(); if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) { throw JSONRPCError(RPC_INVALID_PARAMETER, @@ -809,9 +798,6 @@ static RPCHelpMan submitpackage() if (!Params().IsMockableChain()) { throw std::runtime_error("submitpackage is for regression testing (-regtest mode) only"); } - RPCTypeCheck(request.params, { - UniValue::VARR, - }); const UniValue raw_transactions = request.params[0].get_array(); if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) { throw JSONRPCError(RPC_INVALID_PARAMETER, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 385240ee1e93..fb224e3701f1 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -116,7 +116,7 @@ static RPCHelpMan getnetworkhashps() ChainstateManager& chainman = EnsureAnyChainman(request.context); LOCK(cs_main); - return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].getInt() : 120, !request.params[1].isNull() ? request.params[1].getInt() : -1, chainman.ActiveChain()); + return GetNetworkHashPS(self.Arg("nblocks"), self.Arg("height"), chainman.ActiveChain()); }, }; } @@ -228,12 +228,12 @@ static RPCHelpMan generatetodescriptor() "\nGenerate 11 blocks to mydesc\n" + HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")}, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const int num_blocks{request.params[0].getInt()}; - const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].getInt()}; + const auto num_blocks{self.Arg("num_blocks")}; + const auto max_tries{self.Arg("maxtries")}; CScript coinbase_script; std::string error; - if (!getScriptFromDescriptor(request.params[1].get_str(), coinbase_script, error)) { + if (!getScriptFromDescriptor(self.Arg("descriptor"), coinbase_script, error)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } @@ -498,7 +498,7 @@ static RPCHelpMan prioritisetransaction() { LOCK(cs_main); - uint256 hash(ParseHashV(request.params[0].get_str(), "txid")); + uint256 hash(ParseHashV(request.params[0], "txid")); CAmount nAmount = request.params[1].getInt(); EnsureAnyMemPool(request.context).PrioritiseTransaction(hash, nAmount); @@ -552,15 +552,13 @@ static RPCHelpMan getblocktemplate() {"capabilities", RPCArg::Type::ARR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "A list of strings", { {"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "client side supported feature, 'longpoll', 'coinbasevalue', 'proposal', 'serverlist', 'workid'"}, - }, - }, + }}, {"rules", RPCArg::Type::ARR, RPCArg::Optional::NO, "A list of strings", { {"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "client side supported softfork deployment"}, - }, - }, + }}, }, - "\"template_request\""}, + RPCArgOptions{.oneline_description="\"template_request\""}}, }, { RPCResult{"If the proposal was accepted with mode=='proposal'", RPCResult::Type::NONE, "", ""}, diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 2e75a19f3bcc..e8f3f61acc0a 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -334,7 +334,7 @@ static RPCHelpMan addnode() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - const std::string command{request.params[1].get_str()}; + const auto command{self.Arg("command")}; if (command != "onetry" && command != "add" && command != "remove") { throw std::runtime_error( self.ToString()); @@ -343,9 +343,9 @@ static RPCHelpMan addnode() const NodeContext& node = EnsureAnyNodeContext(request.context); CConnman& connman = EnsureConnman(node); - const std::string node_arg = request.params[0].get_str(); + const auto node_arg{self.Arg("node")}; bool node_v2transport = connman.GetLocalServices() & NODE_P2P_V2; - bool use_v2transport = request.params[2].isNull() ? node_v2transport : request.params[2].get_bool(); + bool use_v2transport = self.MaybeArg("v2transport").value_or(node_v2transport); if (use_v2transport && !node_v2transport) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Error: v2transport requested but not enabled (see -v2transport)"); @@ -401,7 +401,6 @@ static RPCHelpMan addconnection() throw std::runtime_error("addconnection is for regression testing (-regtest mode) only."); } - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VSTR}); const std::string address = request.params[0].get_str(); const std::string conn_type_in{TrimString(request.params[1].get_str())}; ConnectionType conn_type{}; diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp index a095d2f42a54..bf962589f158 100644 --- a/src/rpc/node.cpp +++ b/src/rpc/node.cpp @@ -262,7 +262,6 @@ static RPCHelpMan setmocktime() // ensure all call sites of GetTime() are accessing this safely. LOCK(cs_main); - RPCTypeCheck(request.params, {UniValue::VNUM}); const int64_t time{request.params[0].getInt()}; if (time < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time)); @@ -388,7 +387,7 @@ static RPCHelpMan getaddressmempool() { {"address", RPCArg::Type::STR, RPCArg::Default{""}, "The base58check encoded address"}, }, - }, + RPCArgOptions{.skip_type_check = true}}, }, RPCResult{ RPCResult::Type::ARR, "", "", @@ -461,7 +460,7 @@ static RPCHelpMan getaddressutxos() { {"address", RPCArg::Type::STR, RPCArg::Default{""}, "The base58check encoded address"}, }, - }, + RPCArgOptions{.skip_type_check = true}}, }, RPCResult{ RPCResult::Type::ARR, "", "", @@ -532,7 +531,7 @@ static RPCHelpMan getaddressdeltas() { {"address", RPCArg::Type::STR, RPCArg::Default{""}, "The base58check encoded address"}, }, - }, + RPCArgOptions{.skip_type_check = true}}, }, RPCResult{ RPCResult::Type::ARR, "", "", @@ -619,7 +618,7 @@ static RPCHelpMan getaddressbalance() { {"address", RPCArg::Type::STR, RPCArg::Default{""}, "The base58check encoded address"}, }, - }, + RPCArgOptions{.skip_type_check = true}}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -696,7 +695,7 @@ static RPCHelpMan getaddresstxids() { {"address", RPCArg::Type::STR, RPCArg::Default{""}, "The base58check encoded address"}, }, - }, + RPCArgOptions{.skip_type_check = true}}, }, RPCResult{ RPCResult::Type::ARR, "", "", @@ -835,8 +834,6 @@ static RPCHelpMan mockscheduler() throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only"); } - // check params are valid values - RPCTypeCheck(request.params, {UniValue::VNUM}); int64_t delta_seconds = request.params[0].getInt(); if (delta_seconds <= 0 || delta_seconds > 3600) { throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)"); @@ -1030,16 +1027,16 @@ static RPCHelpMan echo(const std::string& name) "\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in " "dash-cli and the GUI. There is no server-side difference.", { - {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, - {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, + {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, + {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}}, }, RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"}, RPCExamples{""}, diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp index 2ad98f709d38..97209a5d0fee 100644 --- a/src/rpc/output_script.cpp +++ b/src/rpc/output_script.cpp @@ -158,9 +158,6 @@ static RPCHelpMan getdescriptorinfo() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - - RPCTypeCheck(request.params, {UniValue::VSTR}); - FlatSigningProvider provider; std::string error; auto desc = Parse(request.params[0].get_str(), provider, error); @@ -209,8 +206,6 @@ static RPCHelpMan deriveaddresses() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - - RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later const std::string desc_str = request.params[0].get_str(); int64_t range_begin = 0; diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 7a0dd1f71e05..8b6aa0fff410 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -216,7 +216,7 @@ static std::vector CreateTxDoc() }, }, }, - }, + RPCArgOptions{.skip_type_check = true}}, {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, }; } @@ -726,7 +726,6 @@ static RPCHelpMan getassetunlockstatuses() std::optional poolCL{std::nullopt}; std::optional poolOnTip{std::nullopt}; std::optional nSpecificCoreHeight{std::nullopt}; - if (!request.params[1].isNull()) { nSpecificCoreHeight = request.params[1].getInt(); if (nSpecificCoreHeight.value() < 0 || nSpecificCoreHeight.value() > chainman.ActiveChain().Height()) { @@ -820,13 +819,6 @@ static RPCHelpMan createrawtransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // ARR or OBJ, checked later - UniValue::VNUM, - }, true - ); - CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2]); return EncodeHexTx(CTransaction(rawTx)); @@ -851,8 +843,6 @@ static RPCHelpMan decoderawtransaction() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR}); - CMutableTransaction mtx; if (!DecodeHexTx(mtx, request.params[0].get_str())) @@ -889,8 +879,6 @@ static RPCHelpMan decodescript() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR}); - UniValue r(UniValue::VOBJ); CScript script; if (request.params[0].get_str().size() > 0){ @@ -1062,8 +1050,6 @@ static RPCHelpMan signrawtransactionwithkey() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); - CMutableTransaction mtx; if (!DecodeHexTx(mtx, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input."); @@ -1253,8 +1239,6 @@ static RPCHelpMan decodepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR}); - // Unserialize the transactions PartiallySignedTransaction psbtx; std::string error; @@ -1533,8 +1517,6 @@ static RPCHelpMan combinepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VARR}, true); - // Unserialize the transactions std::vector psbtxs; UniValue txs = request.params[0].get_array(); @@ -1588,8 +1570,6 @@ static RPCHelpMan finalizepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); - // Unserialize the transactions PartiallySignedTransaction psbtx; std::string error; @@ -1637,13 +1617,6 @@ static RPCHelpMan createpsbt() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // ARR or OBJ, checked later - UniValue::VNUM, - }, true - ); - CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2]); // Make a blank psbt @@ -1686,8 +1659,6 @@ static RPCHelpMan converttopsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); - // parse hex string from parameter CMutableTransaction tx; bool permitsigdata = request.params[1].isNull() ? false : request.params[1].get_bool(); @@ -1744,9 +1715,6 @@ static RPCHelpMan utxoupdatepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true); - - // Parse descriptors, if any. FlatSigningProvider provider; if (!request.params[1].isNull()) { @@ -1788,8 +1756,6 @@ static RPCHelpMan joinpsbts() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VARR}, true); - // Unserialize the transactions std::vector psbtxs; UniValue txs = request.params[0].get_array(); @@ -1893,8 +1859,6 @@ static RPCHelpMan analyzepsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR}); - // Unserialize the transaction PartiallySignedTransaction psbtx; std::string error; diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 3889e09aa9e2..d864e4f04d3f 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -183,7 +183,7 @@ static RPCHelpMan stop() // to the client (intended for testing) "\nRequest a graceful shutdown of " PACKAGE_NAME ".", { - {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", "", {}, /*hidden=*/true}, + {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "how long to wait in ms", RPCArgOptions{.hidden=true}}, }, RPCResult{RPCResult::Type::STR, "", "A string with the content '" + RESULT + "'"}, RPCExamples{""}, diff --git a/src/rpc/signmessage.cpp b/src/rpc/signmessage.cpp index 58c682a65c9b..13d0b67e7d12 100644 --- a/src/rpc/signmessage.cpp +++ b/src/rpc/signmessage.cpp @@ -39,9 +39,9 @@ static RPCHelpMan verifymessage() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - std::string strAddress = request.params[0].get_str(); - std::string strSign = request.params[1].get_str(); - std::string strMessage = request.params[2].get_str(); + std::string strAddress = self.Arg("address"); + std::string strSign = self.Arg("signature"); + std::string strMessage = self.Arg("message"); switch (MessageVerify(strAddress, strSign, strMessage)) { case MessageVerificationResult::ERR_INVALID_ADDRESS: diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 738a5dea162e..0a9334a8eba7 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -18,6 +18,11 @@ #include #include +#include +#include +#include +#include + const std::string UNIX_EPOCH_TIME = "UNIX epoch time"; const std::string EXAMPLE_ADDRESS[2] = {"XunLY9Tf7Zsef8gMGL2fhWA9ZmMjt4KPw0", "XwQQkwA4FYkq2XERzMY2CiAZhJTEDAbtc0"}; @@ -31,23 +36,6 @@ std::string GetAllOutputTypes() return Join(ret, ", "); } -void RPCTypeCheck(const UniValue& params, - const std::list& typesExpected, - bool fAllowNull) -{ - unsigned int i = 0; - for (const UniValueType& t : typesExpected) { - if (params.size() <= i) - break; - - const UniValue& v = params[i]; - if (!(fAllowNull && v.isNull())) { - RPCTypeCheckArgument(v, t); - } - i++; - } -} - void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected) { if (!typeExpected.typeAny && value.type() != typeExpected.type) { @@ -361,8 +349,8 @@ struct Sections { case RPCArg::Type::BOOL: { if (is_top_level_arg) return; // Nothing more to do for non-recursive types on first recursion auto left = indent; - if (arg.m_type_str.size() != 0 && push_name) { - left += "\"" + arg.GetName() + "\": " + arg.m_type_str.at(0); + if (arg.m_opts.type_str.size() != 0 && push_name) { + left += "\"" + arg.GetName() + "\": " + arg.m_opts.type_str.at(0); } else { left += push_name ? arg.ToStringObj(/*oneline=*/false) : arg.ToString(/*oneline=*/false); } @@ -525,13 +513,71 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) { throw std::runtime_error(ToString()); } + UniValue arg_mismatch{UniValue::VOBJ}; + for (size_t i{0}; i < m_args.size(); ++i) { + const auto& arg{m_args.at(i)}; + UniValue match{arg.MatchesType(request.params[i])}; + if (!match.isTrue()) { + arg_mismatch.pushKV(strprintf("Position %s (%s)", i + 1, arg.m_names), std::move(match)); + } + } + if (!arg_mismatch.empty()) { + throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Wrong type passed:\n%s", arg_mismatch.write(4))); + } + CHECK_NONFATAL(m_req == nullptr); + m_req = &request; UniValue ret = m_fun(*this, request); + m_req = nullptr; if (gArgs.GetBoolArg("-rpcdoccheck", DEFAULT_RPC_DOC_CHECK)) { CHECK_NONFATAL(std::any_of(m_results.m_results.begin(), m_results.m_results.end(), [&ret](const RPCResult& res) { return res.MatchesType(ret); })); } return ret; } +using CheckFn = void(const RPCArg&); +static const UniValue* DetailMaybeArg(CheckFn* check, const std::vector& params, const JSONRPCRequest* req, size_t i) +{ + CHECK_NONFATAL(i < params.size()); + const UniValue& arg{CHECK_NONFATAL(req)->params[i]}; + const RPCArg& param{params.at(i)}; + if (check) check(param); + + if (!arg.isNull()) return &arg; + if (!std::holds_alternative(param.m_fallback)) return nullptr; + return &std::get(param.m_fallback); +} + +static void CheckRequiredOrDefault(const RPCArg& param) +{ + // Must use `Arg(i)` to get the argument or its default value. + const bool required{ + std::holds_alternative(param.m_fallback) && RPCArg::Optional::NO == std::get(param.m_fallback), + }; + CHECK_NONFATAL(required || std::holds_alternative(param.m_fallback)); +} + +#define TMPL_INST(check_param, ret_type, return_code) \ + template <> \ + ret_type RPCHelpMan::ArgValue(size_t i) const \ + { \ + const UniValue* maybe_arg{ \ + DetailMaybeArg(check_param, m_args, m_req, i), \ + }; \ + return return_code \ + } \ + void force_semicolon(ret_type) + +// Optional arg (without default). Can also be called on required args, if needed. +TMPL_INST(nullptr, std::optional, maybe_arg ? std::optional{maybe_arg->get_real()} : std::nullopt;); +TMPL_INST(nullptr, std::optional, maybe_arg ? std::optional{maybe_arg->get_bool()} : std::nullopt;); +TMPL_INST(nullptr, const std::string*, maybe_arg ? &maybe_arg->get_str() : nullptr;); + +// Required arg or optional arg with default value. +TMPL_INST(CheckRequiredOrDefault, bool, CHECK_NONFATAL(maybe_arg)->get_bool();); +TMPL_INST(CheckRequiredOrDefault, int, CHECK_NONFATAL(maybe_arg)->getInt();); +TMPL_INST(CheckRequiredOrDefault, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt();); +TMPL_INST(CheckRequiredOrDefault, const std::string&, CHECK_NONFATAL(maybe_arg)->get_str();); + bool RPCHelpMan::IsValidNumArgs(size_t num_args) const { size_t num_required_args = 0; @@ -553,6 +599,16 @@ std::vector RPCHelpMan::GetArgNames() const return ret; } +size_t RPCHelpMan::GetParamIndex(std::string_view key) const +{ + auto it{std::find_if( + m_args.begin(), m_args.end(), [&key](const auto& arg) { return arg.GetName() == key;} + )}; + + CHECK_NONFATAL(it != m_args.end()); // TODO: ideally this is checked at compile time + return std::distance(m_args.begin(), it); +} + std::string RPCHelpMan::ToString() const { std::string ret; @@ -561,7 +617,7 @@ std::string RPCHelpMan::ToString() const ret += m_name; bool was_optional{false}; for (const auto& arg : m_args) { - if (arg.m_hidden) break; // Any arg that follows is also hidden + if (arg.m_opts.hidden) break; // Any arg that follows is also hidden const bool optional = arg.IsOptional(); ret += " "; if (optional) { @@ -582,7 +638,7 @@ std::string RPCHelpMan::ToString() const Sections sections; for (size_t i{0}; i < m_args.size(); ++i) { const auto& arg = m_args.at(i); - if (arg.m_hidden) break; // Any arg that follows is also hidden + if (arg.m_opts.hidden) break; // Any arg that follows is also hidden if (i == 0) ret += "\nArguments:\n"; @@ -623,6 +679,52 @@ UniValue RPCHelpMan::GetArgMap() const return arr; } +static std::optional ExpectedType(RPCArg::Type type) +{ + using Type = RPCArg::Type; + switch (type) { + case Type::STR_HEX: + case Type::STR: { + return UniValue::VSTR; + } + case Type::NUM: { + return UniValue::VNUM; + } + case Type::AMOUNT: { + // VNUM or VSTR, checked inside AmountFromValue() + return std::nullopt; + } + case Type::RANGE: { + // VNUM or VARR, checked inside ParseRange() + return std::nullopt; + } + case Type::BOOL: { + return UniValue::VBOOL; + } + case Type::OBJ: + case Type::OBJ_USER_KEYS: { + return UniValue::VOBJ; + } + case Type::ARR: { + return UniValue::VARR; + } + } // no default case, so the compiler can warn about missing cases + NONFATAL_UNREACHABLE(); +} + +UniValue RPCArg::MatchesType(const UniValue& request) const +{ + if (m_opts.skip_type_check) return true; + if (IsOptional() && request.isNull()) return true; + const auto exp_type{ExpectedType(m_type)}; + if (!exp_type) return true; // nothing to check + + if (*exp_type != request.getType()) { + return strprintf("JSON value of type %s is not of expected type %s", uvTypeName(request.getType()), uvTypeName(*exp_type)); + } + return true; +} + std::string RPCArg::GetFirstName() const { return m_names.substr(0, m_names.find("|")); @@ -647,8 +749,8 @@ std::string RPCArg::ToDescriptionString(bool is_named_arg) const { std::string ret; ret += "("; - if (m_type_str.size() != 0) { - ret += m_type_str.at(1); + if (m_opts.type_str.size() != 0) { + ret += m_opts.type_str.at(1); } else { switch (m_type) { case Type::STR_HEX: @@ -890,7 +992,7 @@ std::string RPCArg::ToStringObj(const bool oneline) const std::string RPCArg::ToString(const bool oneline) const { - if (oneline && !m_oneline_description.empty()) return m_oneline_description; + if (oneline && !m_opts.oneline_description.empty()) return m_opts.oneline_description; switch (m_type) { case Type::STR_HEX: diff --git a/src/rpc/util.h b/src/rpc/util.h index cbbd8831b283..c9f384f77b94 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_RPC_UTIL_H #define BITCOIN_RPC_UTIL_H +#include #include #include #include @@ -13,14 +14,30 @@ #include