rpc: refactor: use more (Maybe)Arg<std::string_view>
What changed, and why it matters
This is a routine internal cleanup of how Bitcoin Core's RPC (remote procedure call) code reads string and integer arguments. It replaces older manual 'is this parameter null?' checks with a newer helper called (Maybe)Arg<std::string_view>. The commit is explicitly titled as a refactor, reduces duplicated default values, and makes no changes to the actual rules that RPC commands enforce. There is no indication this fixes a security bug or introduces a vulnerability.
No security action required. Treat as normal code-quality refactor during review/merge.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors RPC argument handling across nine files to use the self.Arg
Changed components
src/rpc/blockchain.cppsrc/rpc/fees.cppsrc/rpc/mempool.cppsrc/rpc/net.cppsrc/rpc/node.cppsrc/rpc/output_script.cppsrc/rpc/server.cppsrc/rpc/server.hsrc/rpc/util.cppInspect captured patch +69 / −79
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index e960a5ed..f4b12211 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -40,6 +40,7 @@
#include <serialize.h>
#include <streams.h>
#include <sync.h>
+#include <tinyformat.h>
#include <txdb.h>
#include <txmempool.h>
#include <undo.h>
@@ -60,6 +61,7 @@
#include <memory>
#include <mutex>
#include <optional>
+#include <string>
#include <string_view>
#include <vector>
@@ -937,7 +939,7 @@ static RPCHelpMan pruneblockchain()
};
}
-CoinStatsHashType ParseHashType(const std::string& hash_type_input)
+CoinStatsHashType ParseHashType(std::string_view hash_type_input)
{
if (hash_type_input == "hash_serialized_3") {
return CoinStatsHashType::HASH_SERIALIZED;
@@ -1039,7 +1041,7 @@ static RPCHelpMan gettxoutsetinfo()
UniValue ret(UniValue::VOBJ);
const CBlockIndex* pindex{nullptr};
- const CoinStatsHashType hash_type{request.params[0].isNull() ? CoinStatsHashType::HASH_SERIALIZED : ParseHashType(request.params[0].get_str())};
+ const CoinStatsHashType hash_type{ParseHashType(self.Arg<std::string_view>("hash_type"))};
bool index_requested = request.params[2].isNull() || request.params[2].get_bool();
NodeContext& node = EnsureAnyNodeContext(request.context);
@@ -2516,7 +2518,8 @@ static RPCHelpMan scanblocks()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
UniValue ret(UniValue::VOBJ);
- if (request.params[0].get_str() == "status") {
+ auto action{self.Arg<std::string_view>("action")};
+ if (action == "status") {
BlockFiltersScanReserver reserver;
if (reserver.reserve()) {
// no scan in progress
@@ -2525,7 +2528,7 @@ static RPCHelpMan scanblocks()
ret.pushKV("progress", g_scanfilter_progress.load());
ret.pushKV("current_height", g_scanfilter_progress_height.load());
return ret;
- } else if (request.params[0].get_str() == "abort") {
+ } else if (action == "abort") {
BlockFiltersScanReserver reserver;
if (reserver.reserve()) {
// reserve was possible which means no scan was running
@@ -2534,12 +2537,12 @@ static RPCHelpMan scanblocks()
// set the abort flag
g_scanfilter_should_abort_scan = true;
return true;
- } else if (request.params[0].get_str() == "start") {
+ } else if (action == "start") {
BlockFiltersScanReserver reserver;
if (!reserver.reserve()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
}
- const std::string filtertype_name{request.params[4].isNull() ? "basic" : request.params[4].get_str()};
+ auto filtertype_name{self.Arg<std::string_view>("filtertype")};
BlockFilterType filtertype;
if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
@@ -2551,7 +2554,7 @@ static RPCHelpMan scanblocks()
BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
if (!index) {
- throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name);
+ throw JSONRPCError(RPC_MISC_ERROR, tfm::format("Index is not enabled for filtertype %s", filtertype_name));
}
NodeContext& node = EnsureAnyNodeContext(request.context);
@@ -2650,9 +2653,8 @@ static RPCHelpMan scanblocks()
ret.pushKV("to_height", start_index->nHeight); // start_index is always the last scanned block here
ret.pushKV("relevant_blocks", std::move(blocks));
ret.pushKV("completed", completed);
- }
- else {
- throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid action '%s'", request.params[0].get_str()));
+ } else {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, tfm::format("Invalid action '%s'", action));
}
return ret;
},
@@ -2917,10 +2919,7 @@ static RPCHelpMan getblockfilter()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
uint256 block_hash = ParseHashV(request.params[0], "blockhash");
- std::string filtertype_name = BlockFilterTypeName(BlockFilterType::BASIC);
- if (!request.params[1].isNull()) {
- filtertype_name = request.params[1].get_str();
- }
+ auto filtertype_name{self.Arg<std::string_view>("filtertype")};
BlockFilterType filtertype;
if (!BlockFilterTypeByName(filtertype_name, filtertype)) {
@@ -2929,7 +2928,7 @@ static RPCHelpMan getblockfilter()
BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
if (!index) {
- throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name);
+ throw JSONRPCError(RPC_MISC_ERROR, tfm::format("Index is not enabled for filtertype %s", filtertype_name));
}
const CBlockIndex* block_index;
@@ -3075,10 +3074,10 @@ static RPCHelpMan dumptxoutset()
}
const ArgsManager& args{EnsureAnyArgsman(request.context)};
- const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str()));
+ const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(self.Arg<std::string_view>("path")));
// Write to a temporary path and then move into `path` on completion
// to avoid confusion due to an interruption.
- const fs::path temppath = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str() + ".incomplete"));
+ const fs::path temppath = path + ".incomplete";
if (fs::exists(path)) {
throw JSONRPCError(
diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp
index 92c7a922..ceb341e4 100644
--- a/src/rpc/fees.cpp
+++ b/src/rpc/fees.cpp
@@ -21,6 +21,7 @@
#include <array>
#include <cmath>
#include <string>
+#include <string_view>
using common::FeeModeFromString;
using common::FeeModesDetail;
@@ -67,18 +68,15 @@ static RPCHelpMan estimatesmartfee()
CHECK_NONFATAL(mempool.m_opts.signals)->SyncWithValidationInterfaceQueue();
unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
- bool conservative = false;
- if (!request.params[1].isNull()) {
- FeeEstimateMode fee_mode;
- if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
- }
- if (fee_mode == FeeEstimateMode::CONSERVATIVE) conservative = true;
+ FeeEstimateMode fee_mode;
+ if (!FeeModeFromString(self.Arg<std::string_view>("estimate_mode"), fee_mode)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
}
UniValue result(UniValue::VOBJ);
UniValue errors(UniValue::VARR);
FeeCalculation feeCalc;
+ bool conservative{fee_mode == FeeEstimateMode::CONSERVATIVE};
CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)};
if (feeRate != CFeeRate(0)) {
CFeeRate min_mempool_feerate{mempool.GetMinFee()};
diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp
index 147af369..1fda2a23 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -28,6 +28,7 @@
#include <util/time.h>
#include <util/vector.h>
+#include <string_view>
#include <utility>
using node::DumpMempool;
@@ -768,7 +769,7 @@ static RPCHelpMan importmempool()
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Can only import the mempool after the block download and sync is done.");
}
- const fs::path load_path{fs::u8path(request.params[0].get_str())};
+ const fs::path load_path{fs::u8path(self.Arg<std::string_view>("filepath"))};
const UniValue& use_current_time{request.params[1]["use_current_time"]};
const UniValue& apply_fee_delta{request.params[1]["apply_fee_delta_priority"]};
const UniValue& apply_unbroadcast{request.params[1]["apply_unbroadcast_set"]};
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 4c88caca..c97d4c75 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -36,11 +36,11 @@
#include <optional>
#include <stdexcept>
#include <string>
+#include <string_view>
#include <vector>
using node::NodeContext;
using util::Join;
-using util::TrimString;
const std::vector<std::string> CONNECTION_TYPE_DOC{
"outbound-full-relay (default automatic connections)",
@@ -402,7 +402,7 @@ static RPCHelpMan addconnection()
}
const std::string address = request.params[0].get_str();
- const std::string conn_type_in{TrimString(request.params[1].get_str())};
+ auto conn_type_in{util::TrimStringView(self.Arg<std::string_view>("connection_type"))};
ConnectionType conn_type{};
if (conn_type_in == "outbound-full-relay") {
conn_type = ConnectionType::OUTBOUND_FULL_RELAY;
@@ -462,16 +462,15 @@ static RPCHelpMan disconnectnode()
CConnman& connman = EnsureConnman(node);
bool success;
- const UniValue &address_arg = request.params[0];
- const UniValue &id_arg = request.params[1];
+ auto address{self.MaybeArg<std::string_view>("address")};
+ auto node_id{self.MaybeArg<int64_t>("nodeid")};
- if (!address_arg.isNull() && id_arg.isNull()) {
+ if (address && !node_id) {
/* handle disconnect-by-address */
- success = connman.DisconnectNode(address_arg.get_str());
- } else if (!id_arg.isNull() && (address_arg.isNull() || (address_arg.isStr() && address_arg.get_str().empty()))) {
+ success = connman.DisconnectNode(*address);
+ } else if (node_id && (!address || address->empty())) {
/* handle disconnect-by-id */
- NodeId nodeid = (NodeId) id_arg.getInt<int64_t>();
- success = connman.DisconnectNode(nodeid);
+ success = connman.DisconnectNode(*node_id);
} else {
throw JSONRPCError(RPC_INVALID_PARAMS, "Only one of address and nodeid should be provided.");
}
@@ -523,10 +522,10 @@ static RPCHelpMan getaddednodeinfo()
std::vector<AddedNodeInfo> vInfo = connman.GetAddedNodeInfo(/*include_connected=*/true);
- if (!request.params[0].isNull()) {
+ if (auto node{self.MaybeArg<std::string_view>("node")}) {
bool found = false;
for (const AddedNodeInfo& info : vInfo) {
- if (info.m_params.m_added_node == request.params[0].get_str()) {
+ if (info.m_params.m_added_node == *node) {
vInfo.assign(1, info);
found = true;
break;
@@ -754,10 +753,8 @@ static RPCHelpMan setban()
},
[&](const RPCHelpMan& help, const JSONRPCRequest& request) -> UniValue
{
- std::string strCommand;
- if (!request.params[1].isNull())
- strCommand = request.params[1].get_str();
- if (strCommand != "add" && strCommand != "remove") {
+ auto command{help.Arg<std::string_view>("command")};
+ if (command != "add" && command != "remove") {
throw std::runtime_error(help.ToString());
}
NodeContext& node = EnsureAnyNodeContext(request.context);
@@ -765,25 +762,23 @@ static RPCHelpMan setban()
CSubNet subNet;
CNetAddr netAddr;
- bool isSubnet = false;
-
- if (request.params[0].get_str().find('/') != std::string::npos)
- isSubnet = true;
+ std::string subnet_arg{help.Arg<std::string_view>("subnet")};
+ const bool isSubnet{subnet_arg.find('/') != subnet_arg.npos};
if (!isSubnet) {
- const std::optional<CNetAddr> addr{LookupHost(request.params[0].get_str(), false)};
+ const std::optional<CNetAddr> addr{LookupHost(subnet_arg, false)};
if (addr.has_value()) {
netAddr = static_cast<CNetAddr>(MaybeFlipIPv6toCJDNS(CService{addr.value(), /*port=*/0}));
}
+ } else {
+ subNet = LookupSubNet(subnet_arg);
}
- else
- subNet = LookupSubNet(request.params[0].get_str());
- if (! (isSubnet ? subNet.IsValid() : netAddr.IsValid()) )
+ if (! (isSubnet ? subNet.IsValid() : netAddr.IsValid()) ) {
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Invalid IP/Subnet");
+ }
- if (strCommand == "add")
- {
+ if (command == "add") {
if (isSubnet ? banman.IsBanned(subNet) : banman.IsBanned(netAddr)) {
throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP/Subnet already banned");
}
@@ -809,9 +804,7 @@ static RPCHelpMan setban()
node.connman->DisconnectNode(netAddr);
}
}
- }
- else if(strCommand == "remove")
- {
+ } else if(command == "remove") {
if (!( isSubnet ? banman.Unban(subNet) : banman.Unban(netAddr) )) {
throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Unban failed. Requested address/subnet was not previously manually banned.");
}
@@ -1051,11 +1044,11 @@ static RPCHelpMan sendmsgtopeer()
HelpExampleCli("sendmsgtopeer", "0 \"addr\" \"ffffff\"") + HelpExampleRpc("sendmsgtopeer", "0 \"addr\" \"ffffff\"")},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
const NodeId peer_id{request.params[0].getInt<int64_t>()};
- const std::string& msg_type{request.params[1].get_str()};
+ const auto msg_type{self.Arg<std::string_view>("msg_type")};
if (msg_type.size() > CMessageHeader::MESSAGE_TYPE_SIZE) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Error: msg_type too long, max length is %i", CMessageHeader::MESSAGE_TYPE_SIZE));
}
- auto msg{TryParseHex<unsigned char>(request.params[2].get_str())};
+ auto msg{TryParseHex<unsigned char>(self.Arg<std::string_view>("msg"))};
if (!msg.has_value()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Error parsing input for msg");
}
diff --git a/src/rpc/node.cpp b/src/rpc/node.cpp
index a5da8785..c922b27f 100644
--- a/src/rpc/node.cpp
+++ b/src/rpc/node.cpp
@@ -21,6 +21,7 @@
#include <rpc/server_util.h>
#include <rpc/util.h>
#include <scheduler.h>
+#include <tinyformat.h>
#include <univalue.h>
#include <util/any.h>
#include <util/check.h>
@@ -30,6 +31,7 @@
#ifdef HAVE_MALLOC_INFO
#include <malloc.h>
#endif
+#include <string_view>
using node::NodeContext;
@@ -176,7 +178,7 @@ static RPCHelpMan getmemoryinfo()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str();
+ auto mode{self.Arg<std::string_view>("mode")};
if (mode == "stats") {
UniValue obj(UniValue::VOBJ);
obj.pushKV("locked", RPCLockedMemoryInfo());
@@ -188,7 +190,7 @@ static RPCHelpMan getmemoryinfo()
throw JSONRPCError(RPC_INVALID_PARAMETER, "mallocinfo mode not available");
#endif
} else {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode);
+ throw JSONRPCError(RPC_INVALID_PARAMETER, tfm::format("unknown mode %s", mode));
}
},
};
@@ -385,7 +387,7 @@ static RPCHelpMan getindexinfo()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
UniValue result(UniValue::VOBJ);
- const std::string index_name = request.params[0].isNull() ? "" : request.params[0].get_str();
+ const std::string index_name{self.MaybeArg<std::string_view>("index_name").value_or("")};
if (g_txindex) {
result.pushKVs(SummaryToJSON(g_txindex->GetSummary(), index_name));
diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp
index 43624c98..5c1151f0 100644
--- a/src/rpc/output_script.cpp
+++ b/src/rpc/output_script.cpp
@@ -22,6 +22,7 @@
#include <memory>
#include <optional>
#include <string>
+#include <string_view>
#include <tuple>
#include <vector>
@@ -130,20 +131,17 @@ static RPCHelpMan createmultisig()
}
// Get the output type
- OutputType output_type = OutputType::LEGACY;
- if (!request.params[2].isNull()) {
- std::optional<OutputType> parsed = ParseOutputType(request.params[2].get_str());
- if (!parsed) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str()));
- } else if (parsed.value() == OutputType::BECH32M) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses");
- }
- output_type = parsed.value();
+ auto address_type{self.Arg<std::string_view>("address_type")};
+ auto output_type{ParseOutputType(address_type)};
+ if (!output_type) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, tfm::format("Unknown address type '%s'", address_type));
+ } else if (output_type.value() == OutputType::BECH32M) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses");
}
FlatSigningProvider keystore;
CScript inner;
- const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner);
+ const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type.value(), keystore, inner);
// Make the descriptor
std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), keystore);
@@ -154,7 +152,7 @@ static RPCHelpMan createmultisig()
result.pushKV("descriptor", descriptor->ToString());
UniValue warnings(UniValue::VARR);
- if (descriptor->GetOutputType() != output_type) {
+ if (descriptor->GetOutputType() != output_type.value()) {
// Only warns if the user has explicitly chosen an address type we cannot generate
warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present.");
}
@@ -198,7 +196,7 @@ static RPCHelpMan getdescriptorinfo()
{
FlatSigningProvider provider;
std::string error;
- auto descs = Parse(request.params[0].get_str(), provider, error);
+ auto descs = Parse(self.Arg<std::string_view>("descriptor"), provider, error);
if (descs.empty()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
}
@@ -303,7 +301,7 @@ static RPCHelpMan deriveaddresses()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- const std::string desc_str = request.params[0].get_str();
+ auto desc_str{self.Arg<std::string_view>("descriptor")};
int64_t range_begin = 0;
int64_t range_end = 0;
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index 2edf13fd..5fd733d0 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -26,6 +26,7 @@
#include <chrono>
#include <memory>
#include <mutex>
+#include <string_view>
#include <unordered_map>
using util::SplitString;
@@ -65,7 +66,7 @@ struct RPCCommandExecution
}
};
-std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest& helpreq) const
+std::string CRPCTable::help(std::string_view strCommand, const JSONRPCRequest& helpreq) const
{
std::string strRet;
std::string category;
@@ -130,16 +131,13 @@ static RPCHelpMan help()
RPCExamples{""},
[&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue
{
- std::string strCommand;
- if (jsonRequest.params.size() > 0) {
- strCommand = jsonRequest.params[0].get_str();
- }
- if (strCommand == "dump_all_command_conversions") {
+ auto command{self.MaybeArg<std::string_view>("command")};
+ if (command == "dump_all_command_conversions") {
// Used for testing only, undocumented
return tableRPC.dumpArgMap(jsonRequest);
}
- return tableRPC.help(strCommand, jsonRequest);
+ return tableRPC.help(command.value_or(""), jsonRequest);
},
};
}
diff --git a/src/rpc/server.h b/src/rpc/server.h
index 84accccb..cb68c1de 100644
--- a/src/rpc/server.h
+++ b/src/rpc/server.h
@@ -89,7 +89,7 @@ private:
std::map<std::string, std::vector<const CRPCCommand*>> mapCommands;
public:
CRPCTable();
- std::string help(const std::string& name, const JSONRPCRequest& helpreq) const;
+ std::string help(std::string_view name, const JSONRPCRequest& helpreq) const;
/**
* Execute a method.
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index dbe90d93..4813324c 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -724,6 +724,7 @@ static void CheckRequiredOrDefault(const RPCArg& param)
TMPL_INST(nullptr, const UniValue*, maybe_arg;);
TMPL_INST(nullptr, std::optional<double>, maybe_arg ? std::optional{maybe_arg->get_real()} : std::nullopt;);
TMPL_INST(nullptr, std::optional<bool>, maybe_arg ? std::optional{maybe_arg->get_bool()} : std::nullopt;);
+TMPL_INST(nullptr, std::optional<int64_t>, maybe_arg ? std::optional{maybe_arg->getInt<int64_t>()} : std::nullopt;);
TMPL_INST(nullptr, std::optional<std::string_view>, maybe_arg ? std::optional<std::string_view>{maybe_arg->get_str()} : std::nullopt;);
// Required arg or optional arg with default value.
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.