aboutsummaryrefslogtreecommitdiff
path: root/src/rpc
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc')
-rw-r--r--src/rpc/blockchain.cpp84
-rw-r--r--src/rpc/blockchain.h2
-rw-r--r--src/rpc/client.cpp33
-rw-r--r--src/rpc/fees.cpp4
-rw-r--r--src/rpc/mempool.cpp12
-rw-r--r--src/rpc/mining.cpp6
-rw-r--r--src/rpc/net.cpp53
-rw-r--r--src/rpc/node.cpp34
-rw-r--r--src/rpc/output_script.cpp5
-rw-r--r--src/rpc/rawtransaction.cpp52
-rw-r--r--src/rpc/server.cpp2
-rw-r--r--src/rpc/txoutproof.cpp6
-rw-r--r--src/rpc/util.cpp206
-rw-r--r--src/rpc/util.h106
14 files changed, 327 insertions, 278 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 784fb64d36..8bee066ab8 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -25,6 +25,7 @@
#include <net_processing.h>
#include <node/blockstorage.h>
#include <node/context.h>
+#include <node/transaction.h>
#include <node/utxo_snapshot.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
@@ -444,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);
@@ -464,7 +460,7 @@ static RPCHelpMan getblockfrompeer()
// Fetching blocks before the node has syncing past their height can prevent block files from
// being pruned, so we avoid it if the node is in prune mode.
- if (node::fPruneMode && index->nHeight > WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()->nHeight)) {
+ if (chainman.m_blockman.IsPruneMode() && index->nHeight > WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()->nHeight)) {
throw JSONRPCError(RPC_MISC_ERROR, "In prune mode, only blocks that the node has already synced previously can be fetched from a peer");
}
@@ -569,7 +565,7 @@ static RPCHelpMan getblockheader()
if (!fVerbose)
{
- CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION);
+ DataStream ssBlock{};
ssBlock << pblockindex->GetBlockHeader();
std::string strHex = HexStr(ssBlock);
return strHex;
@@ -654,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",
@@ -778,10 +775,11 @@ static RPCHelpMan pruneblockchain()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- if (!node::fPruneMode)
+ ChainstateManager& chainman = EnsureAnyChainman(request.context);
+ if (!chainman.m_blockman.IsPruneMode()) {
throw JSONRPCError(RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode.");
+ }
- ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
Chainstate& active_chainstate = chainman.ActiveChainstate();
CChain& active_chain = active_chainstate.m_chain;
@@ -872,7 +870,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{
@@ -1265,15 +1267,15 @@ RPCHelpMan getblockchaininfo()
obj.pushKV("initialblockdownload", active_chainstate.IsInitialBlockDownload());
obj.pushKV("chainwork", tip.nChainWork.GetHex());
obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage());
- obj.pushKV("pruned", node::fPruneMode);
- if (node::fPruneMode) {
+ obj.pushKV("pruned", chainman.m_blockman.IsPruneMode());
+ if (chainman.m_blockman.IsPruneMode()) {
obj.pushKV("pruneheight", chainman.m_blockman.GetFirstStoredBlock(tip)->nHeight);
// if 0, execution bypasses the whole if block.
bool automatic_pruning{args.GetIntArg("-prune", 0) != 1};
obj.pushKV("automatic_pruning", automatic_pruning);
if (automatic_pruning) {
- obj.pushKV("prune_target_size", node::nPruneTarget);
+ obj.pushKV("prune_target_size", chainman.m_blockman.GetPruneTarget());
}
}
@@ -1742,7 +1744,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"},
@@ -2144,8 +2150,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;
@@ -2268,17 +2272,47 @@ public:
}
};
+static bool CheckBlockFilterMatches(BlockManager& blockman, const CBlockIndex& blockindex, const GCSFilter::ElementSet& needles)
+{
+ const CBlock block{GetBlockChecked(blockman, &blockindex)};
+ const CBlockUndo block_undo{GetUndoChecked(blockman, &blockindex)};
+
+ // Check if any of the outputs match the scriptPubKey
+ for (const auto& tx : block.vtx) {
+ if (std::any_of(tx->vout.cbegin(), tx->vout.cend(), [&](const auto& txout) {
+ return needles.count(std::vector<unsigned char>(txout.scriptPubKey.begin(), txout.scriptPubKey.end())) != 0;
+ })) {
+ return true;
+ }
+ }
+ // Check if any of the inputs match the scriptPubKey
+ for (const auto& txundo : block_undo.vtxundo) {
+ if (std::any_of(txundo.vprevout.cbegin(), txundo.vprevout.cend(), [&](const auto& coin) {
+ return needles.count(std::vector<unsigned char>(coin.out.scriptPubKey.begin(), coin.out.scriptPubKey.end())) != 0;
+ })) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
static RPCHelpMan scanblocks()
{
return RPCHelpMan{"scanblocks",
- "\nReturn relevant blockhashes for given descriptors.\n"
+ "\nReturn relevant blockhashes for given descriptors (requires blockfilterindex).\n"
"This call may take several minutes. Make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)",
{
scan_action_arg_desc,
scan_objects_arg_desc,
RPCArg{"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "Height to start to scan from"},
RPCArg{"stop_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"chain tip"}, "Height to stop to scan"},
- RPCArg{"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"}
+ RPCArg{"filtertype", RPCArg::Type::STR, RPCArg::Default{BlockFilterTypeName(BlockFilterType::BASIC)}, "The type name of the filter"},
+ RPCArg{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
+ {
+ {"filter_false_positives", RPCArg::Type::BOOL, RPCArg::Default{false}, "Filter false positives (slower and may fail on pruned nodes). Otherwise they may occur at a rate of 1/M"},
+ },
+ RPCArgOptions{.oneline_description="\"options\""}},
},
{
scan_result_status_none,
@@ -2338,6 +2372,9 @@ static RPCHelpMan scanblocks()
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unknown filtertype");
}
+ UniValue options{request.params[5].isNull() ? UniValue::VOBJ : request.params[5]};
+ bool filter_false_positives{options.exists("filter_false_positives") ? options["filter_false_positives"].get_bool() : false};
+
BlockFilterIndex* index = GetBlockFilterIndex(filtertype);
if (!index) {
throw JSONRPCError(RPC_MISC_ERROR, "Index is not enabled for filtertype " + filtertype_name);
@@ -2408,6 +2445,15 @@ static RPCHelpMan scanblocks()
for (const BlockFilter& filter : filters) {
// compare the elements-set with each filter
if (filter.GetFilter().MatchAny(needle_set)) {
+ if (filter_false_positives) {
+ // Double check the filter matches by scanning the block
+ const CBlockIndex& blockindex = *CHECK_NONFATAL(WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(filter.GetBlockHash())));
+
+ if (!CheckBlockFilterMatches(chainman.m_blockman, blockindex, needle_set)) {
+ continue;
+ }
+ }
+
blocks.push_back(filter.GetBlockHash().GetHex());
LogPrint(BCLog::RPC, "scanblocks: found match in %s\n", filter.GetBlockHash().GetHex());
}
diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h
index 2a802f7fee..9ccb87b78a 100644
--- a/src/rpc/blockchain.h
+++ b/src/rpc/blockchain.h
@@ -16,8 +16,6 @@
#include <stdint.h>
#include <vector>
-extern RecursiveMutex cs_main;
-
class CBlock;
class CBlockIndex;
class Chainstate;
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index e6ce188a56..5fe914f0a1 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -86,6 +86,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "scanblocks", 1, "scanobjects" },
{ "scanblocks", 2, "start_height" },
{ "scanblocks", 3, "stop_height" },
+ { "scanblocks", 5, "options" },
{ "scantxoutset", 1, "scanobjects" },
{ "addmultisigaddress", 0, "nrequired" },
{ "addmultisigaddress", 1, "keys" },
@@ -226,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;
}
};
@@ -262,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;
@@ -283,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;
}
@@ -293,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/mining.cpp b/src/rpc/mining.cpp
index 764c4c675b..8753f845a5 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -441,7 +441,7 @@ static RPCHelpMan prioritisetransaction()
"Accepts the transaction into mined blocks at a higher (or lower) priority\n",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id."},
- {"dummy", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "API-Compatibility for previous API. Must be zero or null.\n"
+ {"dummy", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "API-Compatibility for previous API. Must be zero or null.\n"
" DEPRECATED. For forward compatibility use named arguments and omit this parameter."},
{"fee_delta", RPCArg::Type::NUM, RPCArg::Optional::NO, "The fee value (in satoshis) to add (or subtract, if negative).\n"
" Note, that this value is not a fee rate. It is a value to modify absolute fee of the TX.\n"
@@ -513,8 +513,8 @@ static RPCHelpMan getblocktemplate()
{
{"template_request", RPCArg::Type::OBJ, RPCArg::Default{UniValue::VOBJ}, "Format of the template",
{
- {"mode", RPCArg::Type::STR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"},
- {"capabilities", RPCArg::Type::ARR, /* treat as named arg */ RPCArg::Optional::OMITTED_NAMED_ARG, "A list of strings",
+ {"mode", RPCArg::Type::STR, /* treat as named arg */ RPCArg::Optional::OMITTED, "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"},
+ {"capabilities", RPCArg::Type::ARR, /* treat as named arg */ RPCArg::Optional::OMITTED, "A list of strings",
{
{"str", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "client side supported feature, 'longpoll', 'coinbasevalue', 'proposal', 'serverlist', 'workid'"},
}},
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 d8f2576b6d..5918bc6e38 100644
--- a/src/rpc/node.cpp
+++ b/src/rpc/node.cpp
@@ -12,6 +12,7 @@
#include <interfaces/echo.h>
#include <interfaces/init.h>
#include <interfaces/ipc.h>
+#include <kernel/cs_main.h>
#include <node/context.h>
#include <rpc/server.h>
#include <rpc/server_util.h>
@@ -52,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));
@@ -106,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)");
@@ -242,11 +240,11 @@ static RPCHelpMan logging()
" - \"none\", \"0\" : even if other logging categories are specified, ignore all of them.\n"
,
{
- {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to add to debug logging",
+ {"include", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The categories to add to debug logging",
{
{"include_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
}},
- {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The categories to remove from debug logging",
+ {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The categories to remove from debug logging",
{
{"exclude_category", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "the valid logging category"},
}},
@@ -295,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, "", RPCArgOptions{.skip_type_check = true}},
+ {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
+ {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
+ {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
+ {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
+ {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
+ {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
+ {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
+ {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
+ {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.skip_type_check = true}},
+ },
RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"},
RPCExamples{""},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
@@ -378,7 +376,7 @@ static RPCHelpMan getindexinfo()
return RPCHelpMan{"getindexinfo",
"\nReturns the status of one or all available indices currently running in the node.\n",
{
- {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Filter results for an index with a specific name."},
+ {"index_name", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Filter results for an index with a specific name."},
},
RPCResult{
RPCResult::Type::OBJ_DYN, "", "", {
diff --git a/src/rpc/output_script.cpp b/src/rpc/output_script.cpp
index 2ac6d6d76f..bb04f58424 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);
@@ -232,7 +230,7 @@ static RPCHelpMan deriveaddresses()
"For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n"},
{
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."},
- {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED_NAMED_ARG, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."},
+ {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in [begin,end] notation) to derive."},
},
RPCResult{
RPCResult::Type::ARR, "", "",
@@ -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..7704496c10 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -54,8 +54,11 @@ using node::PSBTAnalysis;
using node::ReadBlockFromDisk;
using node::UndoReadFromDisk;
-static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry, Chainstate& active_chainstate, const CTxUndo* txundo = nullptr, TxVerbosity verbosity = TxVerbosity::SHOW_TXID)
+static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry,
+ Chainstate& active_chainstate, const CTxUndo* txundo = nullptr,
+ TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS)
{
+ CHECK_NONFATAL(verbosity >= TxVerbosity::SHOW_DETAILS);
// Call into TxToUniv() in bitcoin-common to decode the transaction hex.
//
// Blockchain contextual information (confirmations and blocktime) is not
@@ -162,7 +165,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,8 +188,9 @@ 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"},
- {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "The block in which to look for the transaction"},
+ {"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, "The block in which to look for the transaction"},
},
{
RPCResult{"if verbosity is not set or set to 0",
@@ -354,14 +358,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 +393,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 +445,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){
@@ -650,7 +642,7 @@ static RPCHelpMan signrawtransactionwithkey()
{"privatekey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "private key in base58-encoding"},
},
},
- {"prevtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "The previous dependent transaction outputs",
+ {"prevtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The previous dependent transaction outputs",
{
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
{
@@ -702,8 +694,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 +971,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 +1383,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 +1436,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 +1483,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 +1536,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();
@@ -1607,7 +1581,7 @@ static RPCHelpMan utxoupdatepsbt()
"\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
- {"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "An array of either strings or objects", {
+ {"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of either strings or objects", {
{"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with an output descriptor and extra information", {
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
@@ -1623,8 +1597,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 +1686,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 +1812,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 9f57a56297..44d7e2676b 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -168,7 +168,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", RPCArgOptions{.hidden=true}},
+ {"wait", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "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/txoutproof.cpp b/src/rpc/txoutproof.cpp
index 8c5468634d..24b5d04115 100644
--- a/src/rpc/txoutproof.cpp
+++ b/src/rpc/txoutproof.cpp
@@ -34,7 +34,7 @@ static RPCHelpMan gettxoutproof()
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A transaction hash"},
},
},
- {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "If specified, looks for txid in the block with this hash"},
+ {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "If specified, looks for txid in the block with this hash"},
},
RPCResult{
RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof."
@@ -112,7 +112,7 @@ static RPCHelpMan gettxoutproof()
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not all transactions found in specified or retrieved block");
}
- CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
+ DataStream ssMB{};
CMerkleBlock mb(block, setTxids);
ssMB << mb;
std::string strHex = HexStr(ssMB);
@@ -138,7 +138,7 @@ static RPCHelpMan verifytxoutproof()
RPCExamples{""},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
- CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
+ DataStream ssMB{ParseHexV(request.params[0], "proof")};
CMerkleBlock merkleBlock;
ssMB >> merkleBlock;
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 53785b1941..a1020c3b2b 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,31 +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) {
- throw JSONRPCError(RPC_TYPE_ERROR,
- strprintf("JSON value of type %s is not of expected type %s", uvTypeName(value.type()), uvTypeName(typeExpected.type)));
- }
-}
-
void RPCTypeCheckObj(const UniValue& o,
const std::map<std::string, UniValueType>& typesExpected,
bool fAllowNull,
@@ -405,6 +381,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 +390,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 +398,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 +411,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 +556,39 @@ 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)));
+ }
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,52 @@ UniValue RPCHelpMan::GetArgMap() const
return arr;
}
+static std::optional<UniValue::VType> 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("|"));
@@ -697,7 +750,7 @@ bool RPCArg::IsOptional() const
}
}
-std::string RPCArg::ToDescriptionString() const
+std::string RPCArg::ToDescriptionString(bool is_named_arg) const
{
std::string ret;
ret += "(";
@@ -744,13 +797,10 @@ std::string RPCArg::ToDescriptionString() const
} else {
switch (std::get<RPCArg::Optional>(m_fallback)) {
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 +910,77 @@ void RPCResult::ToSections(Sections& sections, const OuterType outer_type, const
NONFATAL_UNREACHABLE();
}
-bool RPCResult::MatchesType(const UniValue& result) const
+static 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 +990,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 +998,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..e3783c8f76 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -62,18 +62,6 @@ struct UniValueType {
UniValue::VType type;
};
-/**
- * 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);
-
/*
Check for expected keys/value types in an Object.
*/
@@ -138,6 +126,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 +149,20 @@ struct RPCArg {
/** Required arg */
NO,
/**
- * 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,
- /**
- * Optional argument with default value omitted because they are
- * implicitly clear. That is, elements in an array or object may not
- * exist by default.
+ * Optional argument for which the default value is omitted from
+ * help text for one of two reasons:
+ * - It's a named argument and has a default value of `null`.
+ * - Its default value is implicitly clear. That is, elements in an
+ * array may not exist by default.
* When possible, the default value should be specified.
*/
OMITTED,
};
+ /** 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 +172,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 +187,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 +205,12 @@ struct RPCArg {
bool IsOptional() const;
+ /**
+ * Check whether the request JSON type matches.
+ * Returns true if type matches, or object describing error(s) if not.
+ */
+ UniValue MatchesType(const UniValue& request) const;
+
/** Return the first of all aliases */
std::string GetFirstName() const;
@@ -237,7 +231,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 +260,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 +279,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 +305,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 +318,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;