cli: Allow arguments to be both strings and json
What changed, and why it matters
This commit changes how Bitcoin Core's command-line tool (bitcoin-cli) handles certain arguments. Previously, some RPC parameters like 'hash_or_height' and 'rollback' were always treated as JSON, meaning plain strings would be rejected with a JSON parsing error. Now, for a small set of parameters, the tool accepts either a valid JSON value or a plain string. This is a usability fix, not a security patch, and it does not change the Bitcoin node itself—only the client-side argument parser.
No security action required. Treat as a normal usability improvement. Reviewers may optionally verify that the three marked parameters are intended to accept both string and numeric/block-hash values on the server side, which is the case for hash_or_height and rollback.
Security signals we found
No memory safety changes
No authentication or authorization changes
No consensus or networking changes
Client-side argument parsing only
No input validation bypass; server still validates received values
No CVE or security advisory referenced in commit
Evidence from the diff
The patch modifies src/rpc/client.cpp to add a boolean ‘also_string’ field to CRPCConvertParam. The Parse() helper now returns the raw string when JSON parsing fails if also_string is true, instead of throwing a runtime_error. The CRPCConvertTable lookups are changed from std::set to std::map to carry this flag. Three parameters are marked also_string=true: gettxoutsetinfo’s hash_or_height, dumptxoutset’s rollback, and getblockstats’s hash_or_height. A functional test regex is updated to parse the new optional field. No server-side RPC validation is altered.
Changed components
bitcoin-cli RPC argument conversion tableCRPCConvertParam / CRPCConvertTable in src/rpc/client.cpptest/functional/rpc_help.pyInspect captured patch +24 / −12
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 1fbe62d3..e462a26d 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -18,6 +18,7 @@ public:
std::string methodName; //!< method whose params want conversion
int paramIdx; //!< 0-based idx of param to convert
std::string paramName; //!< parameter name
+ bool also_string{false}; //!< The parameter is also a string
};
// clang-format off
@@ -188,10 +189,10 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "gettxout", 1, "n" },
{ "gettxout", 2, "include_mempool" },
{ "gettxoutproof", 0, "txids" },
- { "gettxoutsetinfo", 1, "hash_or_height" },
+ { "gettxoutsetinfo", 1, "hash_or_height", /*also_string=*/true },
{ "gettxoutsetinfo", 2, "use_index"},
{ "dumptxoutset", 2, "options" },
- { "dumptxoutset", 2, "rollback" },
+ { "dumptxoutset", 2, "rollback", /*also_string=*/true },
{ "lockunspent", 0, "unlock" },
{ "lockunspent", 1, "transactions" },
{ "lockunspent", 2, "persistent" },
@@ -246,7 +247,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "listdescriptors", 0, "private" },
{ "verifychain", 0, "checklevel" },
{ "verifychain", 1, "nblocks" },
- { "getblockstats", 0, "hash_or_height" },
+ { "getblockstats", 0, "hash_or_height", /*also_string=*/true },
{ "getblockstats", 1, "stats" },
{ "pruneblockchain", 0, "height" },
{ "keypoolrefill", 0, "newsize" },
@@ -318,18 +319,21 @@ static const CRPCConvertParam vRPCConvertParams[] =
// clang-format on
/** Parse string to UniValue or throw runtime_error if string contains invalid JSON */
-static UniValue Parse(std::string_view raw)
+static UniValue Parse(std::string_view raw, bool also_string)
{
UniValue parsed;
- if (!parsed.read(raw)) throw std::runtime_error(tfm::format("Error parsing JSON: %s", raw));
+ if (!parsed.read(raw)) {
+ if (!also_string) throw std::runtime_error(tfm::format("Error parsing JSON: %s", raw));
+ return raw;
+ }
return parsed;
}
class CRPCConvertTable
{
private:
- std::set<std::pair<std::string, int>> members;
- std::set<std::pair<std::string, std::string>> membersByName;
+ std::map<std::pair<std::string, int>, bool> members;
+ std::map<std::pair<std::string, std::string>, bool> membersByName;
public:
CRPCConvertTable();
@@ -337,21 +341,29 @@ public:
/** Return arg_value as UniValue, and first parse it if it is a non-string parameter */
UniValue ArgToUniValue(std::string_view arg_value, const std::string& method, int param_idx)
{
- return members.count({method, param_idx}) > 0 ? Parse(arg_value) : arg_value;
+ const auto& it = members.find({method, param_idx});
+ if (it != members.end()) {
+ return Parse(arg_value, it->second);
+ }
+ return arg_value;
}
/** Return arg_value as UniValue, and first parse it if it is a non-string parameter */
UniValue ArgToUniValue(std::string_view arg_value, const std::string& method, const std::string& param_name)
{
- return membersByName.count({method, param_name}) > 0 ? Parse(arg_value) : arg_value;
+ const auto& it = membersByName.find({method, param_name});
+ if (it != membersByName.end()) {
+ return Parse(arg_value, it->second);
+ }
+ return arg_value;
}
};
CRPCConvertTable::CRPCConvertTable()
{
for (const auto& cp : vRPCConvertParams) {
- members.emplace(cp.methodName, cp.paramIdx);
- membersByName.emplace(cp.methodName, cp.paramName);
+ members.emplace(std::make_pair(cp.methodName, cp.paramIdx), cp.also_string);
+ membersByName.emplace(std::make_pair(cp.methodName, cp.paramName), cp.also_string);
}
}
diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py
index 802a5549..02279ad2 100755
--- a/test/functional/rpc_help.py
+++ b/test/functional/rpc_help.py
@@ -32,7 +32,7 @@ def process_mapping(fname):
if line.startswith('};'):
in_rpcs = False
elif '{' in line and '"' in line:
- m = re.search(r'{ *("[^"]*"), *([0-9]+) *, *("[^"]*") *},', line)
+ m = re.search(r'{ *("[^"]*"), *([0-9]+) *, *("[^"]*") *(, \/\*also_string=\*\/true *)?},', line)
assert m, 'No match to table expression: %s' % line
name = parse_string(m.group(1))
idx = int(m.group(2))
Why this scored 23/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.