aboutsummaryrefslogtreecommitdiff
path: root/src/rpc
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc')
-rw-r--r--src/rpc/blockchain.cpp22
-rw-r--r--src/rpc/client.cpp32
-rw-r--r--src/rpc/fees.cpp4
-rw-r--r--src/rpc/mempool.cpp12
-rw-r--r--src/rpc/net.cpp53
-rw-r--r--src/rpc/node.cpp27
-rw-r--r--src/rpc/output_script.cpp3
-rw-r--r--src/rpc/rawtransaction.cpp41
-rw-r--r--src/rpc/util.cpp183
-rw-r--r--src/rpc/util.h96
10 files changed, 237 insertions, 236 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index fade2ad504..2b39580043 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -445,11 +445,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);
@@ -655,7 +650,8 @@ static RPCHelpMan getblock()
"If verbosity is 3, returns an Object with information about block <hash> 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",
@@ -873,7 +869,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).", RPCArgOptions{.type_str={"", "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{
@@ -1743,7 +1743,11 @@ static RPCHelpMan getblockstats()
"\nCompute per block statistics for a given window. All amounts are in satoshis.\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", RPCArgOptions{.type_str={"", "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"},
@@ -2145,8 +2149,6 @@ 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") {
CoinsViewScanReserver reserver;
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index b046e1a17b..5fe914f0a1 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -227,11 +227,16 @@ private:
public:
CRPCConvertTable();
- bool convert(const std::string& method, int idx) {
- return (members.count(std::make_pair(method, idx)) > 0);
+ /** Return arg_value as UniValue, and first parse it if it is a non-string parameter */
+ UniValue ArgToUniValue(const std::string& arg_value, const std::string& method, int param_idx)
+ {
+ return members.count(std::make_pair(method, param_idx)) > 0 ? ParseNonRFCJSONValue(arg_value) : arg_value;
}
- bool convert(const std::string& method, const std::string& name) {
- return (membersByName.count(std::make_pair(method, name)) > 0);
+
+ /** Return arg_value as UniValue, and first parse it if it is a non-string parameter */
+ UniValue ArgToUniValue(const std::string& arg_value, const std::string& method, const std::string& param_name)
+ {
+ return membersByName.count(std::make_pair(method, param_name)) > 0 ? ParseNonRFCJSONValue(arg_value) : arg_value;
}
};
@@ -263,14 +268,7 @@ UniValue RPCConvertValues(const std::string &strMethod, const std::vector<std::s
for (unsigned int idx = 0; idx < strParams.size(); idx++) {
const std::string& strVal = strParams[idx];
-
- if (!rpcCvtTable.convert(strMethod, idx)) {
- // insert string value directly
- params.push_back(strVal);
- } else {
- // parse string as JSON, insert bool/number/object/etc. value
- params.push_back(ParseNonRFCJSONValue(strVal));
- }
+ params.push_back(rpcCvtTable.ArgToUniValue(strVal, strMethod, idx));
}
return params;
@@ -284,7 +282,7 @@ UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<s
for (const std::string &s: strParams) {
size_t pos = s.find('=');
if (pos == std::string::npos) {
- positional_args.push_back(rpcCvtTable.convert(strMethod, positional_args.size()) ? ParseNonRFCJSONValue(s) : s);
+ positional_args.push_back(rpcCvtTable.ArgToUniValue(s, strMethod, positional_args.size()));
continue;
}
@@ -294,13 +292,7 @@ UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<s
// Intentionally overwrite earlier named values with later ones as a
// convenience for scripts and command line users that want to merge
// options.
- if (!rpcCvtTable.convert(strMethod, name)) {
- // insert string value directly
- params.pushKV(name, value);
- } else {
- // parse string as JSON, insert bool/number/object/etc. value
- params.pushKV(name, ParseNonRFCJSONValue(value));
- }
+ params.pushKV(name, rpcCvtTable.ArgToUniValue(value, strMethod, name));
}
if (!positional_args.empty()) {
diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp
index cd2f6390e5..62396d4c58 100644
--- a/src/rpc/fees.cpp
+++ b/src/rpc/fees.cpp
@@ -63,8 +63,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 44bff55f96..44f7435a26 100644
--- a/src/rpc/mempool.cpp
+++ b/src/rpc/mempool.cpp
@@ -61,11 +61,6 @@ static RPCHelpMan sendrawtransaction()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- RPCTypeCheck(request.params, {
- UniValue::VSTR,
- UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
- });
-
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.");
@@ -147,10 +142,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,
@@ -800,9 +791,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/net.cpp b/src/rpc/net.cpp
index d430087358..f0e5b90509 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -114,7 +114,7 @@ static RPCHelpMan getpeerinfo()
{
{RPCResult::Type::STR, "SERVICE_NAME", "the service name if it is recognised"}
}},
- {RPCResult::Type::BOOL, "relaytxes", /*optional=*/true, "Whether we relay transactions to this peer"},
+ {RPCResult::Type::BOOL, "relaytxes", "Whether we relay transactions to this peer"},
{RPCResult::Type::NUM_TIME, "lastsend", "The " + UNIX_EPOCH_TIME + " of the last send"},
{RPCResult::Type::NUM_TIME, "lastrecv", "The " + UNIX_EPOCH_TIME + " of the last receive"},
{RPCResult::Type::NUM_TIME, "last_transaction", "The " + UNIX_EPOCH_TIME + " of the last valid transaction received from this peer"},
@@ -131,17 +131,17 @@ static RPCHelpMan getpeerinfo()
{RPCResult::Type::BOOL, "inbound", "Inbound (true) or Outbound (false)"},
{RPCResult::Type::BOOL, "bip152_hb_to", "Whether we selected peer as (compact blocks) high-bandwidth peer"},
{RPCResult::Type::BOOL, "bip152_hb_from", "Whether peer selected us as (compact blocks) high-bandwidth peer"},
- {RPCResult::Type::NUM, "startingheight", /*optional=*/true, "The starting height (block) of the peer"},
- {RPCResult::Type::NUM, "presynced_headers", /*optional=*/true, "The current height of header pre-synchronization with this peer, or -1 if no low-work sync is in progress"},
- {RPCResult::Type::NUM, "synced_headers", /*optional=*/true, "The last header we have in common with this peer"},
- {RPCResult::Type::NUM, "synced_blocks", /*optional=*/true, "The last block we have in common with this peer"},
- {RPCResult::Type::ARR, "inflight", /*optional=*/true, "",
+ {RPCResult::Type::NUM, "startingheight", "The starting height (block) of the peer"},
+ {RPCResult::Type::NUM, "presynced_headers", "The current height of header pre-synchronization with this peer, or -1 if no low-work sync is in progress"},
+ {RPCResult::Type::NUM, "synced_headers", "The last header we have in common with this peer"},
+ {RPCResult::Type::NUM, "synced_blocks", "The last block we have in common with this peer"},
+ {RPCResult::Type::ARR, "inflight", "",
{
{RPCResult::Type::NUM, "n", "The heights of blocks we're currently asking from this peer"},
}},
- {RPCResult::Type::BOOL, "addr_relay_enabled", /*optional=*/true, "Whether we participate in address relay with this peer"},
- {RPCResult::Type::NUM, "addr_processed", /*optional=*/true, "The total number of addresses processed, excluding those dropped due to rate limiting"},
- {RPCResult::Type::NUM, "addr_rate_limited", /*optional=*/true, "The total number of addresses dropped due to rate limiting"},
+ {RPCResult::Type::BOOL, "addr_relay_enabled", "Whether we participate in address relay with this peer"},
+ {RPCResult::Type::NUM, "addr_processed", "The total number of addresses processed, excluding those dropped due to rate limiting"},
+ {RPCResult::Type::NUM, "addr_rate_limited", "The total number of addresses dropped due to rate limiting"},
{RPCResult::Type::ARR, "permissions", "Any special permissions that have been granted to this peer",
{
{RPCResult::Type::STR, "permission_type", Join(NET_PERMISSIONS_DOC, ",\n") + ".\n"},
@@ -205,12 +205,10 @@ static RPCHelpMan getpeerinfo()
if (stats.m_mapped_as != 0) {
obj.pushKV("mapped_as", uint64_t(stats.m_mapped_as));
}
- ServiceFlags services{fStateStats ? statestats.their_services : ServiceFlags::NODE_NONE};
+ ServiceFlags services{statestats.their_services};
obj.pushKV("services", strprintf("%016x", services));
obj.pushKV("servicesnames", GetServicesNames(services));
- if (fStateStats) {
- obj.pushKV("relaytxes", statestats.m_relay_txs);
- }
+ obj.pushKV("relaytxes", statestats.m_relay_txs);
obj.pushKV("lastsend", count_seconds(stats.m_last_send));
obj.pushKV("lastrecv", count_seconds(stats.m_last_recv));
obj.pushKV("last_transaction", count_seconds(stats.m_last_tx_time));
@@ -225,7 +223,7 @@ static RPCHelpMan getpeerinfo()
if (stats.m_min_ping_time < std::chrono::microseconds::max()) {
obj.pushKV("minping", Ticks<SecondsDouble>(stats.m_min_ping_time));
}
- if (fStateStats && statestats.m_ping_wait > 0s) {
+ if (statestats.m_ping_wait > 0s) {
obj.pushKV("pingwait", Ticks<SecondsDouble>(statestats.m_ping_wait));
}
obj.pushKV("version", stats.nVersion);
@@ -236,26 +234,24 @@ static RPCHelpMan getpeerinfo()
obj.pushKV("inbound", stats.fInbound);
obj.pushKV("bip152_hb_to", stats.m_bip152_highbandwidth_to);
obj.pushKV("bip152_hb_from", stats.m_bip152_highbandwidth_from);
- if (fStateStats) {
- obj.pushKV("startingheight", statestats.m_starting_height);
- obj.pushKV("presynced_headers", statestats.presync_height);
- obj.pushKV("synced_headers", statestats.nSyncHeight);
- obj.pushKV("synced_blocks", statestats.nCommonHeight);
- UniValue heights(UniValue::VARR);
- for (const int height : statestats.vHeightInFlight) {
- heights.push_back(height);
- }
- obj.pushKV("inflight", heights);
- obj.pushKV("addr_relay_enabled", statestats.m_addr_relay_enabled);
- obj.pushKV("addr_processed", statestats.m_addr_processed);
- obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited);
+ obj.pushKV("startingheight", statestats.m_starting_height);
+ obj.pushKV("presynced_headers", statestats.presync_height);
+ obj.pushKV("synced_headers", statestats.nSyncHeight);
+ obj.pushKV("synced_blocks", statestats.nCommonHeight);
+ UniValue heights(UniValue::VARR);
+ for (const int height : statestats.vHeightInFlight) {
+ heights.push_back(height);
}
+ obj.pushKV("inflight", heights);
+ obj.pushKV("addr_relay_enabled", statestats.m_addr_relay_enabled);
+ obj.pushKV("addr_processed", statestats.m_addr_processed);
+ obj.pushKV("addr_rate_limited", statestats.m_addr_rate_limited);
UniValue permissions(UniValue::VARR);
for (const auto& permission : NetPermissions::ToStrings(stats.m_permission_flags)) {
permissions.push_back(permission);
}
obj.pushKV("permissions", permissions);
- obj.pushKV("minfeefilter", fStateStats ? ValueFromAmount(statestats.m_fee_filter_received) : 0);
+ obj.pushKV("minfeefilter", ValueFromAmount(statestats.m_fee_filter_received));
UniValue sendPerMsgType(UniValue::VOBJ);
for (const auto& i : stats.mapSendBytesPerMsgType) {
@@ -362,7 +358,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 ab1bc6615f..79b8277968 100644
--- a/src/rpc/node.cpp
+++ b/src/rpc/node.cpp
@@ -53,7 +53,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<int64_t>()};
if (time < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time));
@@ -107,8 +106,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<int64_t>();
if (delta_seconds <= 0 || delta_seconds > 3600) {
throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)");
@@ -296,18 +293,18 @@ static RPCHelpMan echo(const std::string& name)
"\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n"
"\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in "
"bitcoin-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{""},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp
index 2ac6d6d76f..911c769e61 100644
--- a/src/rpc/output_script.cpp
+++ b/src/rpc/output_script.cpp
@@ -195,8 +195,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);
@@ -247,7 +245,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 c712536b91..981dead3b8 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -162,7 +162,7 @@ static std::vector<RPCArg> CreateTxDoc()
},
},
},
- },
+ RPCArgOptions{.skip_type_check = true}},
{"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
{"replaceable", RPCArg::Type::BOOL, RPCArg::Default{true}, "Marks this transaction as BIP125-replaceable.\n"
"Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."},
@@ -185,7 +185,8 @@ static RPCHelpMan getrawtransaction()
"If verbosity is 2, returns a JSON Object with information about the transaction, including fee and prevout information.",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
- {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{0}, "0 for hex-encoded data, 1 for a JSON object, and 2 for JSON object with fee and prevout"},
+ {"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{0}, "0 for hex-encoded data, 1 for a JSON object, and 2 for JSON object with fee and prevout",
+ RPCArgOptions{.skip_type_check = true}},
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "The block in which to look for the transaction"},
},
{
@@ -354,14 +355,6 @@ static RPCHelpMan createrawtransaction()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- RPCTypeCheck(request.params, {
- UniValue::VARR,
- UniValueType(), // ARR or OBJ, checked later
- UniValue::VNUM,
- UniValue::VBOOL
- }, true
- );
-
std::optional<bool> rbf;
if (!request.params[3].isNull()) {
rbf = request.params[3].get_bool();
@@ -397,8 +390,6 @@ static RPCHelpMan decoderawtransaction()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL});
-
CMutableTransaction mtx;
bool try_witness = request.params[1].isNull() ? true : request.params[1].get_bool();
@@ -451,8 +442,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){
@@ -702,8 +691,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.");
@@ -981,8 +968,6 @@ static RPCHelpMan decodepsbt()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- RPCTypeCheck(request.params, {UniValue::VSTR});
-
// Unserialize the transactions
PartiallySignedTransaction psbtx;
std::string error;
@@ -1395,8 +1380,6 @@ static RPCHelpMan combinepsbt()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- RPCTypeCheck(request.params, {UniValue::VARR}, true);
-
// Unserialize the transactions
std::vector<PartiallySignedTransaction> psbtxs;
UniValue txs = request.params[0].get_array();
@@ -1450,8 +1433,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;
@@ -1499,14 +1480,6 @@ static RPCHelpMan createpsbt()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- RPCTypeCheck(request.params, {
- UniValue::VARR,
- UniValueType(), // ARR or OBJ, checked later
- UniValue::VNUM,
- UniValue::VBOOL,
- }, true
- );
-
std::optional<bool> rbf;
if (!request.params[3].isNull()) {
rbf = request.params[3].get_bool();
@@ -1560,8 +1533,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();
@@ -1623,8 +1594,6 @@ static RPCHelpMan utxoupdatepsbt()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true);
-
// Unserialize the transactions
PartiallySignedTransaction psbtx;
std::string error;
@@ -1714,8 +1683,6 @@ static RPCHelpMan joinpsbts()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- RPCTypeCheck(request.params, {UniValue::VARR}, true);
-
// Unserialize the transactions
std::vector<PartiallySignedTransaction> psbtxs;
UniValue txs = request.params[0].get_array();
@@ -1842,8 +1809,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/util.cpp b/src/rpc/util.cpp
index 53785b1941..9c8b0b5642 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <clientversion.h>
#include <consensus/amount.h>
#include <key_io.h>
#include <outputtype.h>
@@ -30,23 +31,6 @@ std::string GetAllOutputTypes()
return Join(ret, ", ");
}
-void RPCTypeCheck(const UniValue& params,
- const std::list<UniValueType>& 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) {
@@ -405,6 +389,7 @@ struct Sections {
const auto indent = std::string(current_indent, ' ');
const auto indent_next = std::string(current_indent + 2, ' ');
const bool push_name{outer_type == OuterType::OBJ}; // Dictionary keys must have a name
+ const bool is_top_level_arg{outer_type == OuterType::NONE}; // True on the first recursion
switch (arg.m_type) {
case RPCArg::Type::STR_HEX:
@@ -413,7 +398,7 @@ struct Sections {
case RPCArg::Type::AMOUNT:
case RPCArg::Type::RANGE:
case RPCArg::Type::BOOL: {
- if (outer_type == OuterType::NONE) return; // Nothing more to do for non-recursive types on first recursion
+ if (is_top_level_arg) return; // Nothing more to do for non-recursive types on first recursion
auto left = indent;
if (arg.m_opts.type_str.size() != 0 && push_name) {
left += "\"" + arg.GetName() + "\": " + arg.m_opts.type_str.at(0);
@@ -421,12 +406,12 @@ struct Sections {
left += push_name ? arg.ToStringObj(/*oneline=*/false) : arg.ToString(/*oneline=*/false);
}
left += ",";
- PushSection({left, arg.ToDescriptionString()});
+ PushSection({left, arg.ToDescriptionString(/*is_named_arg=*/push_name)});
break;
}
case RPCArg::Type::OBJ:
case RPCArg::Type::OBJ_USER_KEYS: {
- const auto right = outer_type == OuterType::NONE ? "" : arg.ToDescriptionString();
+ const auto right = is_top_level_arg ? "" : arg.ToDescriptionString(/*is_named_arg=*/push_name);
PushSection({indent + (push_name ? "\"" + arg.GetName() + "\": " : "") + "{", right});
for (const auto& arg_inner : arg.m_inner) {
Push(arg_inner, current_indent + 2, OuterType::OBJ);
@@ -434,20 +419,20 @@ struct Sections {
if (arg.m_type != RPCArg::Type::OBJ) {
PushSection({indent_next + "...", ""});
}
- PushSection({indent + "}" + (outer_type != OuterType::NONE ? "," : ""), ""});
+ PushSection({indent + "}" + (is_top_level_arg ? "" : ","), ""});
break;
}
case RPCArg::Type::ARR: {
auto left = indent;
left += push_name ? "\"" + arg.GetName() + "\": " : "";
left += "[";
- const auto right = outer_type == OuterType::NONE ? "" : arg.ToDescriptionString();
+ const auto right = is_top_level_arg ? "" : arg.ToDescriptionString(/*is_named_arg=*/push_name);
PushSection({left, right});
for (const auto& arg_inner : arg.m_inner) {
Push(arg_inner, current_indent + 2, OuterType::ARR);
}
PushSection({indent_next + "...", ""});
- PushSection({indent + "]" + (outer_type != OuterType::NONE ? "," : ""), ""});
+ PushSection({indent + "]" + (is_top_level_arg ? "" : ","), ""});
break;
}
} // no default case, so the compiler can warn about missing cases
@@ -579,9 +564,31 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) {
throw std::runtime_error(ToString());
}
+ for (size_t i{0}; i < m_args.size(); ++i) {
+ m_args.at(i).MatchesType(request.params[i]);
+ }
UniValue ret = m_fun(*this, request);
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); }));
+ UniValue mismatch{UniValue::VARR};
+ for (const auto& res : m_results.m_results) {
+ UniValue match{res.MatchesType(ret)};
+ if (match.isTrue()) {
+ mismatch.setNull();
+ break;
+ }
+ mismatch.push_back(match);
+ }
+ if (!mismatch.isNull()) {
+ std::string explain{
+ mismatch.empty() ? "no possible results defined" :
+ mismatch.size() == 1 ? mismatch[0].write(4) :
+ mismatch.write(4)};
+ throw std::runtime_error{
+ strprintf("Internal bug detected: RPC call \"%s\" returned incorrect type:\n%s\n%s %s\nPlease report this issue here: %s\n",
+ m_name, explain,
+ PACKAGE_NAME, FormatFullVersion(),
+ PACKAGE_BUGREPORT)};
+ }
}
return ret;
}
@@ -641,7 +648,7 @@ std::string RPCHelpMan::ToString() const
if (i == 0) ret += "\nArguments:\n";
// Push named argument name and description
- sections.m_sections.emplace_back(::ToString(i + 1) + ". " + arg.GetFirstName(), arg.ToDescriptionString());
+ sections.m_sections.emplace_back(::ToString(i + 1) + ". " + arg.GetFirstName(), arg.ToDescriptionString(/*is_named_arg=*/true));
sections.m_max_pad = std::max(sections.m_max_pad, sections.m_sections.back().m_left.size());
// Recursively push nested args
@@ -677,6 +684,44 @@ UniValue RPCHelpMan::GetArgMap() const
return arr;
}
+void RPCArg::MatchesType(const UniValue& request) const
+{
+ if (m_opts.skip_type_check) return;
+ if (IsOptional() && request.isNull()) return;
+ switch (m_type) {
+ case Type::STR_HEX:
+ case Type::STR: {
+ RPCTypeCheckArgument(request, UniValue::VSTR);
+ return;
+ }
+ case Type::NUM: {
+ RPCTypeCheckArgument(request, UniValue::VNUM);
+ return;
+ }
+ case Type::AMOUNT: {
+ // VNUM or VSTR, checked inside AmountFromValue()
+ return;
+ }
+ case Type::RANGE: {
+ // VNUM or VARR, checked inside ParseRange()
+ return;
+ }
+ case Type::BOOL: {
+ RPCTypeCheckArgument(request, UniValue::VBOOL);
+ return;
+ }
+ case Type::OBJ:
+ case Type::OBJ_USER_KEYS: {
+ RPCTypeCheckArgument(request, UniValue::VOBJ);
+ return;
+ }
+ case Type::ARR: {
+ RPCTypeCheckArgument(request, UniValue::VARR);
+ return;
+ }
+ } // no default case, so the compiler can warn about missing cases
+}
+
std::string RPCArg::GetFirstName() const
{
return m_names.substr(0, m_names.find("|"));
@@ -697,7 +742,7 @@ bool RPCArg::IsOptional() const
}
}
-std::string RPCArg::ToDescriptionString() const
+std::string RPCArg::ToDescriptionString(bool is_named_arg) const
{
std::string ret;
ret += "(";
@@ -743,14 +788,12 @@ std::string RPCArg::ToDescriptionString() const
ret += ", optional, default=" + std::get<RPCArg::Default>(m_fallback).write();
} else {
switch (std::get<RPCArg::Optional>(m_fallback)) {
+ case RPCArg::Optional::OMITTED_NAMED_ARG: // Deprecated alias for OMITTED, can be removed
case RPCArg::Optional::OMITTED: {
+ if (is_named_arg) ret += ", optional"; // Default value is "null" in dicts. Otherwise,
// nothing to do. Element is treated as if not present and has no default value
break;
}
- case RPCArg::Optional::OMITTED_NAMED_ARG: {
- ret += ", optional"; // Default value is "null"
- break;
- }
case RPCArg::Optional::NO: {
ret += ", required";
break;
@@ -860,53 +903,77 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
NONFATAL_UNREACHABLE();
}
-bool RPCResult::MatchesType(const UniValue& result) const
+static const std::optional<UniValue::VType> ExpectedType(RPCResult::Type type)
{
- if (m_skip_type_check) {
- return true;
- }
- switch (m_type) {
+ using Type = RPCResult::Type;
+ switch (type) {
case Type::ELISION:
case Type::ANY: {
- return true;
+ return std::nullopt;
}
case Type::NONE: {
- return UniValue::VNULL == result.getType();
+ return UniValue::VNULL;
}
case Type::STR:
case Type::STR_HEX: {
- return UniValue::VSTR == result.getType();
+ return UniValue::VSTR;
}
case Type::NUM:
case Type::STR_AMOUNT:
case Type::NUM_TIME: {
- return UniValue::VNUM == result.getType();
+ return UniValue::VNUM;
}
case Type::BOOL: {
- return UniValue::VBOOL == result.getType();
+ return UniValue::VBOOL;
}
case Type::ARR_FIXED:
case Type::ARR: {
- if (UniValue::VARR != result.getType()) return false;
+ return UniValue::VARR;
+ }
+ case Type::OBJ_DYN:
+ case Type::OBJ: {
+ return UniValue::VOBJ;
+ }
+ } // no default case, so the compiler can warn about missing cases
+ NONFATAL_UNREACHABLE();
+}
+
+UniValue RPCResult::MatchesType(const UniValue& result) const
+{
+ if (m_skip_type_check) {
+ return true;
+ }
+
+ const auto exp_type = ExpectedType(m_type);
+ if (!exp_type) return true; // can be any type, so nothing to check
+
+ if (*exp_type != result.getType()) {
+ return strprintf("returned type is %s, but declared as %s in doc", uvTypeName(result.getType()), uvTypeName(*exp_type));
+ }
+
+ if (UniValue::VARR == result.getType()) {
+ UniValue errors(UniValue::VOBJ);
for (size_t i{0}; i < result.get_array().size(); ++i) {
// If there are more results than documented, re-use the last doc_inner.
const RPCResult& doc_inner{m_inner.at(std::min(m_inner.size() - 1, i))};
- if (!doc_inner.MatchesType(result.get_array()[i])) return false;
+ UniValue match{doc_inner.MatchesType(result.get_array()[i])};
+ if (!match.isTrue()) errors.pushKV(strprintf("%d", i), match);
}
- return true; // empty result array is valid
+ if (errors.empty()) return true; // empty result array is valid
+ return errors;
}
- case Type::OBJ_DYN:
- case Type::OBJ: {
- if (UniValue::VOBJ != result.getType()) return false;
+
+ if (UniValue::VOBJ == result.getType()) {
if (!m_inner.empty() && m_inner.at(0).m_type == Type::ELISION) return true;
+ UniValue errors(UniValue::VOBJ);
if (m_type == Type::OBJ_DYN) {
const RPCResult& doc_inner{m_inner.at(0)}; // Assume all types are the same, randomly pick the first
for (size_t i{0}; i < result.get_obj().size(); ++i) {
- if (!doc_inner.MatchesType(result.get_obj()[i])) {
- return false;
- }
+ UniValue match{doc_inner.MatchesType(result.get_obj()[i])};
+ if (!match.isTrue()) errors.pushKV(result.getKeys()[i], match);
}
- return true; // empty result obj is valid
+ if (errors.empty()) return true; // empty result obj is valid
+ return errors;
}
std::set<std::string> doc_keys;
for (const auto& doc_entry : m_inner) {
@@ -916,7 +983,7 @@ bool RPCResult::MatchesType(const UniValue& result) const
result.getObjMap(result_obj);
for (const auto& result_entry : result_obj) {
if (doc_keys.find(result_entry.first) == doc_keys.end()) {
- return false; // missing documentation
+ errors.pushKV(result_entry.first, "key returned that was not in doc");
}
}
@@ -924,18 +991,18 @@ bool RPCResult::MatchesType(const UniValue& result) const
const auto result_it{result_obj.find(doc_entry.m_key_name)};
if (result_it == result_obj.end()) {
if (!doc_entry.m_optional) {
- return false; // result is missing a required key
+ errors.pushKV(doc_entry.m_key_name, "key missing, despite not being optional in doc");
}
continue;
}
- if (!doc_entry.MatchesType(result_it->second)) {
- return false; // wrong type
- }
+ UniValue match{doc_entry.MatchesType(result_it->second)};
+ if (!match.isTrue()) errors.pushKV(doc_entry.m_key_name, match);
}
- return true;
+ if (errors.empty()) return true;
+ return errors;
}
- } // no default case, so the compiler can warn about missing cases
- NONFATAL_UNREACHABLE();
+
+ return true;
}
void RPCResult::CheckInnerDoc() const
diff --git a/src/rpc/util.h b/src/rpc/util.h
index 387fee34d3..27363eeb50 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -63,13 +63,6 @@ struct UniValueType {
};
/**
- * Type-check arguments; throws JSONRPCError if wrong type given. Does not check that
- * the right number of arguments are passed, just that any passed are the correct type.
- */
-void RPCTypeCheck(const UniValue& params,
- const std::list<UniValueType>& typesExpected, bool fAllowNull=false);
-
-/**
* Type-check one argument; throws JSONRPCError if wrong type given.
*/
void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected);
@@ -138,6 +131,7 @@ enum class OuterType {
};
struct RPCArgOptions {
+ bool skip_type_check{false};
std::string oneline_description{}; //!< Should be empty unless it is supposed to override the auto-generated summary line
std::vector<std::string> type_str{}; //!< Should be empty unless it is supposed to override the auto-generated type strings. Vector length is either 0 or 2, m_opts.type_str.at(0) will override the type of the value in a key-value pair, m_opts.type_str.at(1) will override the type in the argument description.
bool hidden{false}; //!< For testing only
@@ -160,21 +154,24 @@ struct RPCArg {
/** Required arg */
NO,
/**
+ * The arg is optional for one of two reasons:
+ *
* Optional arg that is a named argument and has a default value of
- * `null`. When possible, the default value should be specified.
- */
- OMITTED_NAMED_ARG,
- /**
+ * `null`.
+ *
* Optional argument with default value omitted because they are
- * implicitly clear. That is, elements in an array or object may not
+ * implicitly clear. That is, elements in an array may not
* exist by default.
* When possible, the default value should be specified.
*/
OMITTED,
+ OMITTED_NAMED_ARG, // Deprecated alias for OMITTED, can be removed
};
+ /** Hint for default value */
using DefaultHint = std::string;
+ /** Default constant value */
using Default = UniValue;
- using Fallback = std::variant<Optional, /* hint for default value */ DefaultHint, /* default constant value */ Default>;
+ using Fallback = std::variant<Optional, DefaultHint, Default>;
const std::string m_names; //!< The name of the arg (can be empty for inner args, can contain multiple aliases separated by | for named request arguments)
const Type m_type;
@@ -184,10 +181,10 @@ struct RPCArg {
const RPCArgOptions m_opts;
RPCArg(
- const std::string name,
- const Type type,
- const Fallback fallback,
- const std::string description,
+ std::string name,
+ Type type,
+ Fallback fallback,
+ std::string description,
RPCArgOptions opts = {})
: m_names{std::move(name)},
m_type{std::move(type)},
@@ -199,11 +196,11 @@ struct RPCArg {
}
RPCArg(
- const std::string name,
- const Type type,
- const Fallback fallback,
- const std::string description,
- const std::vector<RPCArg> inner,
+ std::string name,
+ Type type,
+ Fallback fallback,
+ std::string description,
+ std::vector<RPCArg> inner,
RPCArgOptions opts = {})
: m_names{std::move(name)},
m_type{std::move(type)},
@@ -217,6 +214,9 @@ struct RPCArg {
bool IsOptional() const;
+ /** Check whether the request JSON type matches. */
+ void MatchesType(const UniValue& request) const;
+
/** Return the first of all aliases */
std::string GetFirstName() const;
@@ -237,7 +237,7 @@ struct RPCArg {
* Return the description string, including the argument type and whether
* the argument is required.
*/
- std::string ToDescriptionString() const;
+ std::string ToDescriptionString(bool is_named_arg) const;
};
struct RPCResult {
@@ -266,12 +266,12 @@ struct RPCResult {
const std::string m_cond;
RPCResult(
- const std::string cond,
- const Type type,
- const std::string m_key_name,
- const bool optional,
- const std::string description,
- const std::vector<RPCResult> inner = {})
+ std::string cond,
+ Type type,
+ std::string m_key_name,
+ bool optional,
+ std::string description,
+ std::vector<RPCResult> inner = {})
: m_type{std::move(type)},
m_key_name{std::move(m_key_name)},
m_inner{std::move(inner)},
@@ -285,19 +285,19 @@ struct RPCResult {
}
RPCResult(
- const std::string cond,
- const Type type,
- const std::string m_key_name,
- const std::string description,
- const std::vector<RPCResult> inner = {})
- : RPCResult{cond, type, m_key_name, false, description, inner} {}
+ std::string cond,
+ Type type,
+ std::string m_key_name,
+ std::string description,
+ std::vector<RPCResult> inner = {})
+ : RPCResult{std::move(cond), type, std::move(m_key_name), /*optional=*/false, std::move(description), std::move(inner)} {}
RPCResult(
- const Type type,
- const std::string m_key_name,
- const bool optional,
- const std::string description,
- const std::vector<RPCResult> inner = {},
+ Type type,
+ std::string m_key_name,
+ bool optional,
+ std::string description,
+ std::vector<RPCResult> inner = {},
bool skip_type_check = false)
: m_type{std::move(type)},
m_key_name{std::move(m_key_name)},
@@ -311,12 +311,12 @@ struct RPCResult {
}
RPCResult(
- const Type type,
- const std::string m_key_name,
- const std::string description,
- const std::vector<RPCResult> inner = {},
+ Type type,
+ std::string m_key_name,
+ std::string description,
+ std::vector<RPCResult> inner = {},
bool skip_type_check = false)
- : RPCResult{type, m_key_name, false, description, inner, skip_type_check} {}
+ : RPCResult{type, std::move(m_key_name), /*optional=*/false, std::move(description), std::move(inner), skip_type_check} {}
/** Append the sections of the result. */
void ToSections(Sections& sections, OuterType outer_type = OuterType::NONE, const int current_indent = 0) const;
@@ -324,8 +324,10 @@ struct RPCResult {
std::string ToStringObj() const;
/** Return the description string, including the result type. */
std::string ToDescriptionString() const;
- /** Check whether the result JSON type matches. */
- bool MatchesType(const UniValue& result) const;
+ /** Check whether the result JSON type matches.
+ * Returns true if type matches, or object describing error(s) if not.
+ */
+ UniValue MatchesType(const UniValue& result) const;
private:
void CheckInnerDoc() const;