rpc: Run type check against RPCArgs

This commit is contained in:
MarcoFalke 2022-12-12 14:30:14 +01:00
parent faf96721a6
commit fa9f6d7bcd
No known key found for this signature in database
GPG Key ID: CE2B75697E69A548
12 changed files with 83 additions and 153 deletions

View File

@ -444,11 +444,6 @@ static RPCHelpMan getblockfrompeer()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {
UniValue::VSTR, // blockhash
UniValue::VNUM, // peer_id
});
const NodeContext& node = EnsureAnyNodeContext(request.context); const NodeContext& node = EnsureAnyNodeContext(request.context);
ChainstateManager& chainman = EnsureChainman(node); ChainstateManager& chainman = EnsureChainman(node);
PeerManager& peerman = EnsurePeerman(node); PeerManager& peerman = EnsurePeerman(node);
@ -654,7 +649,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", "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"}, {"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", RPCResult{"for verbosity = 0",
@ -872,7 +868,11 @@ static RPCHelpMan gettxoutsetinfo()
"Note this call may take some time if you are not using coinstatsindex.\n", "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_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."}, {"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."},
}, },
RPCResult{ RPCResult{
@ -1742,7 +1742,11 @@ static RPCHelpMan getblockstats()
"\nCompute per block statistics for a given window. All amounts are in satoshis.\n" "\nCompute per block statistics for a given window. All amounts are in satoshis.\n"
"It won't work for some heights with pruning.\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)", {"stats", RPCArg::Type::ARR, RPCArg::DefaultHint{"all values"}, "Values to plot (see result below)",
{ {
{"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"}, {"height", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Selected statistic"},
@ -2144,8 +2148,6 @@ static RPCHelpMan scantxoutset()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR});
UniValue result(UniValue::VOBJ); UniValue result(UniValue::VOBJ);
if (request.params[0].get_str() == "status") { if (request.params[0].get_str() == "status") {
CoinsViewScanReserver reserver; CoinsViewScanReserver reserver;

View File

@ -63,8 +63,6 @@ static RPCHelpMan estimatesmartfee()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR});
CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
const NodeContext& node = EnsureAnyNodeContext(request.context); const NodeContext& node = EnsureAnyNodeContext(request.context);
const CTxMemPool& mempool = EnsureMemPool(node); const CTxMemPool& mempool = EnsureMemPool(node);
@ -155,8 +153,6 @@ static RPCHelpMan estimaterawfee()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true);
CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context); CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);

View File

@ -61,11 +61,6 @@ static RPCHelpMan sendrawtransaction()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {
UniValue::VSTR,
UniValueType(), // VNUM or VSTR, checked inside AmountFromValue()
});
CMutableTransaction mtx; CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) { 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."); 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 [&](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(); const UniValue raw_transactions = request.params[0].get_array();
if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) { if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
throw JSONRPCError(RPC_INVALID_PARAMETER, throw JSONRPCError(RPC_INVALID_PARAMETER,
@ -800,9 +791,6 @@ static RPCHelpMan submitpackage()
if (!Params().IsMockableChain()) { if (!Params().IsMockableChain()) {
throw std::runtime_error("submitpackage is for regression testing (-regtest mode) only"); 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(); const UniValue raw_transactions = request.params[0].get_array();
if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) { if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
throw JSONRPCError(RPC_INVALID_PARAMETER, throw JSONRPCError(RPC_INVALID_PARAMETER,

View File

@ -362,7 +362,6 @@ static RPCHelpMan addconnection()
throw std::runtime_error("addconnection is for regression testing (-regtest mode) only."); 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 address = request.params[0].get_str();
const std::string conn_type_in{TrimString(request.params[1].get_str())}; const std::string conn_type_in{TrimString(request.params[1].get_str())};
ConnectionType conn_type{}; ConnectionType conn_type{};

View File

@ -52,7 +52,6 @@ static RPCHelpMan setmocktime()
// ensure all call sites of GetTime() are accessing this safely. // ensure all call sites of GetTime() are accessing this safely.
LOCK(cs_main); LOCK(cs_main);
RPCTypeCheck(request.params, {UniValue::VNUM});
const int64_t time{request.params[0].getInt<int64_t>()}; const int64_t time{request.params[0].getInt<int64_t>()};
if (time < 0) { if (time < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time)); throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Mocktime cannot be negative: %s.", time));
@ -106,8 +105,6 @@ static RPCHelpMan mockscheduler()
throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only"); 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>(); int64_t delta_seconds = request.params[0].getInt<int64_t>();
if (delta_seconds <= 0 || delta_seconds > 3600) { if (delta_seconds <= 0 || delta_seconds > 3600) {
throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)"); throw std::runtime_error("delta_time must be between 1 and 3600 seconds (1 hr)");
@ -295,18 +292,18 @@ static RPCHelpMan echo(const std::string& name)
"\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n" "\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 " "\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.", "bitcoin-cli and the GUI. There is no server-side difference.",
{ {
{"arg0", 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, ""}, {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}},
{"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}},
{"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}},
{"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}},
{"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}},
{"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}},
{"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}},
{"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}},
{"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""}, {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "", RPCArgOptions{.skip_type_check = true}},
}, },
RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"}, RPCResult{RPCResult::Type::ANY, "", "Returns whatever was passed in"},
RPCExamples{""}, RPCExamples{""},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue

View File

@ -195,8 +195,6 @@ static RPCHelpMan getdescriptorinfo()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VSTR});
FlatSigningProvider provider; FlatSigningProvider provider;
std::string error; std::string error;
auto desc = Parse(request.params[0].get_str(), provider, 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 [&](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(); const std::string desc_str = request.params[0].get_str();
int64_t range_begin = 0; int64_t range_begin = 0;

View File

@ -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"}, {"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" {"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."}, "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.", "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"}, {"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"}, {"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 [&](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; std::optional<bool> rbf;
if (!request.params[3].isNull()) { if (!request.params[3].isNull()) {
rbf = request.params[3].get_bool(); rbf = request.params[3].get_bool();
@ -397,8 +390,6 @@ static RPCHelpMan decoderawtransaction()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL});
CMutableTransaction mtx; CMutableTransaction mtx;
bool try_witness = request.params[1].isNull() ? true : request.params[1].get_bool(); 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 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VSTR});
UniValue r(UniValue::VOBJ); UniValue r(UniValue::VOBJ);
CScript script; CScript script;
if (request.params[0].get_str().size() > 0){ if (request.params[0].get_str().size() > 0){
@ -702,8 +691,6 @@ static RPCHelpMan signrawtransactionwithkey()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true);
CMutableTransaction mtx; CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) { 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."); 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 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VSTR});
// Unserialize the transactions // Unserialize the transactions
PartiallySignedTransaction psbtx; PartiallySignedTransaction psbtx;
std::string error; std::string error;
@ -1395,8 +1380,6 @@ static RPCHelpMan combinepsbt()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VARR}, true);
// Unserialize the transactions // Unserialize the transactions
std::vector<PartiallySignedTransaction> psbtxs; std::vector<PartiallySignedTransaction> psbtxs;
UniValue txs = request.params[0].get_array(); UniValue txs = request.params[0].get_array();
@ -1450,8 +1433,6 @@ static RPCHelpMan finalizepsbt()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true);
// Unserialize the transactions // Unserialize the transactions
PartiallySignedTransaction psbtx; PartiallySignedTransaction psbtx;
std::string error; std::string error;
@ -1499,14 +1480,6 @@ static RPCHelpMan createpsbt()
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](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; std::optional<bool> rbf;
if (!request.params[3].isNull()) { if (!request.params[3].isNull()) {
rbf = request.params[3].get_bool(); rbf = request.params[3].get_bool();
@ -1560,8 +1533,6 @@ static RPCHelpMan converttopsbt()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true);
// parse hex string from parameter // parse hex string from parameter
CMutableTransaction tx; CMutableTransaction tx;
bool permitsigdata = request.params[1].isNull() ? false : request.params[1].get_bool(); 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 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true);
// Unserialize the transactions // Unserialize the transactions
PartiallySignedTransaction psbtx; PartiallySignedTransaction psbtx;
std::string error; std::string error;
@ -1714,8 +1683,6 @@ static RPCHelpMan joinpsbts()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VARR}, true);
// Unserialize the transactions // Unserialize the transactions
std::vector<PartiallySignedTransaction> psbtxs; std::vector<PartiallySignedTransaction> psbtxs;
UniValue txs = request.params[0].get_array(); UniValue txs = request.params[0].get_array();
@ -1842,8 +1809,6 @@ static RPCHelpMan analyzepsbt()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {UniValue::VSTR});
// Unserialize the transaction // Unserialize the transaction
PartiallySignedTransaction psbtx; PartiallySignedTransaction psbtx;
std::string error; std::string error;

View File

@ -30,23 +30,6 @@ std::string GetAllOutputTypes()
return Join(ret, ", "); 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) void RPCTypeCheckArgument(const UniValue& value, const UniValueType& typeExpected)
{ {
if (!typeExpected.typeAny && value.type() != typeExpected.type) { if (!typeExpected.typeAny && value.type() != typeExpected.type) {
@ -579,6 +562,9 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) { if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) {
throw std::runtime_error(ToString()); 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); UniValue ret = m_fun(*this, request);
if (gArgs.GetBoolArg("-rpcdoccheck", DEFAULT_RPC_DOC_CHECK)) { 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); })); CHECK_NONFATAL(std::any_of(m_results.m_results.begin(), m_results.m_results.end(), [&ret](const RPCResult& res) { return res.MatchesType(ret); }));
@ -677,6 +663,44 @@ UniValue RPCHelpMan::GetArgMap() const
return arr; 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 std::string RPCArg::GetFirstName() const
{ {
return m_names.substr(0, m_names.find("|")); return m_names.substr(0, m_names.find("|"));

View File

@ -62,13 +62,6 @@ struct UniValueType {
UniValue::VType type; 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. * Type-check one argument; throws JSONRPCError if wrong type given.
*/ */
@ -138,6 +131,7 @@ enum class OuterType {
}; };
struct RPCArgOptions { 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::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. 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 bool hidden{false}; //!< For testing only
@ -217,6 +211,9 @@ struct RPCArg {
bool IsOptional() const; bool IsOptional() const;
/** Check whether the request JSON type matches. */
void MatchesType(const UniValue& request) const;
/** Return the first of all aliases */ /** Return the first of all aliases */
std::string GetFirstName() const; std::string GetFirstName() const;

View File

@ -1330,8 +1330,6 @@ RPCHelpMan importmulti()
// the user could have gotten from another RPC command prior to now // the user could have gotten from another RPC command prior to now
wallet.BlockUntilSyncedToCurrentChain(); wallet.BlockUntilSyncedToCurrentChain();
RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ});
EnsureLegacyScriptPubKeyMan(*pwallet, true); EnsureLegacyScriptPubKeyMan(*pwallet, true);
const UniValue& requests = mainRequest.params[0]; const UniValue& requests = mainRequest.params[0];
@ -1652,8 +1650,6 @@ RPCHelpMan importdescriptors()
throw JSONRPCError(RPC_WALLET_ERROR, "importdescriptors is not available for non-descriptor wallets"); throw JSONRPCError(RPC_WALLET_ERROR, "importdescriptors is not available for non-descriptor wallets");
} }
RPCTypeCheck(main_request.params, {UniValue::VARR, UniValue::VOBJ});
WalletRescanReserver reserver(*pwallet); WalletRescanReserver reserver(*pwallet);
if (!reserver.reserve()) { if (!reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");

View File

@ -317,7 +317,11 @@ RPCHelpMan sendmany()
"\nSend multiple times. Amounts are double-precision floating point numbers." + "\nSend multiple times. Amounts are double-precision floating point numbers." +
HELP_REQUIRING_PASSPHRASE, HELP_REQUIRING_PASSPHRASE,
{ {
{"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, "Must be set to \"\" for backwards compatibility.", RPCArgOptions{.oneline_description="\"\""}}, {"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, "Must be set to \"\" for backwards compatibility.",
RPCArgOptions{
.skip_type_check = true,
.oneline_description = "\"\"",
}},
{"amounts", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::NO, "The addresses and amounts", {"amounts", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::NO, "The addresses and amounts",
{ {
{"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value"}, {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value"},
@ -778,7 +782,10 @@ RPCHelpMan fundrawtransaction()
}, },
}, },
FundTxDoc()), FundTxDoc()),
RPCArgOptions{.oneline_description="options"}}, RPCArgOptions{
.skip_type_check = true,
.oneline_description = "options",
}},
{"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n" {"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n"
"If iswitness is not present, heuristic tests will be used in decoding.\n" "If iswitness is not present, heuristic tests will be used in decoding.\n"
"If true, only witness deserialization will be tried.\n" "If true, only witness deserialization will be tried.\n"
@ -810,8 +817,6 @@ RPCHelpMan fundrawtransaction()
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return UniValue::VNULL; if (!pwallet) return UniValue::VNULL;
RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL});
// parse hex string from parameter // parse hex string from parameter
CMutableTransaction tx; CMutableTransaction tx;
bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool(); bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool();
@ -900,8 +905,6 @@ RPCHelpMan signrawtransactionwithwallet()
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return UniValue::VNULL; if (!pwallet) return UniValue::VNULL;
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true);
CMutableTransaction mtx; CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) { 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."); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
@ -1001,7 +1004,6 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
throw JSONRPCError(RPC_WALLET_ERROR, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead."); throw JSONRPCError(RPC_WALLET_ERROR, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead.");
} }
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ});
uint256 hash(ParseHashV(request.params[0], "txid")); uint256 hash(ParseHashV(request.params[0], "txid"));
CCoinControl coin_control; CCoinControl coin_control;
@ -1135,7 +1137,7 @@ RPCHelpMan send()
}, },
}, },
}, },
}, RPCArgOptions{.skip_type_check = true}},
{"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
{"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n"
"\"" + FeeModes("\"\n\"") + "\""}, "\"" + FeeModes("\"\n\"") + "\""},
@ -1205,15 +1207,6 @@ RPCHelpMan send()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {
UniValueType(), // outputs (ARR or OBJ, checked later)
UniValue::VNUM, // conf_target
UniValue::VSTR, // estimate_mode
UniValueType(), // fee_rate, will be checked by AmountFromValue() in SetFeeEstimateMode()
UniValue::VOBJ, // options
}, true
);
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return UniValue::VNULL; if (!pwallet) return UniValue::VNULL;
@ -1314,15 +1307,6 @@ RPCHelpMan sendall()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
RPCTypeCheck(request.params, {
UniValue::VARR, // recipients
UniValue::VNUM, // conf_target
UniValue::VSTR, // estimate_mode
UniValueType(), // fee_rate, will be checked by AmountFromValue() in SetFeeEstimateMode()
UniValue::VOBJ, // options
}, true
);
std::shared_ptr<CWallet> const pwallet{GetWalletForJSONRPCRequest(request)}; std::shared_ptr<CWallet> const pwallet{GetWalletForJSONRPCRequest(request)};
if (!pwallet) return UniValue::VNULL; if (!pwallet) return UniValue::VNULL;
// Make sure the results are valid at least up to the most recent block // Make sure the results are valid at least up to the most recent block
@ -1518,8 +1502,6 @@ RPCHelpMan walletprocesspsbt()
// the user could have gotten from another RPC command prior to now // the user could have gotten from another RPC command prior to now
wallet.BlockUntilSyncedToCurrentChain(); wallet.BlockUntilSyncedToCurrentChain();
RPCTypeCheck(request.params, {UniValue::VSTR});
// Unserialize the transaction // Unserialize the transaction
PartiallySignedTransaction psbtx; PartiallySignedTransaction psbtx;
std::string error; std::string error;
@ -1594,7 +1576,7 @@ RPCHelpMan walletcreatefundedpsbt()
}, },
}, },
}, },
}, RPCArgOptions{.skip_type_check = true}},
{"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"}, {"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
Cat<std::vector<RPCArg>>( Cat<std::vector<RPCArg>>(
@ -1645,15 +1627,6 @@ RPCHelpMan walletcreatefundedpsbt()
// the user could have gotten from another RPC command prior to now // the user could have gotten from another RPC command prior to now
wallet.BlockUntilSyncedToCurrentChain(); wallet.BlockUntilSyncedToCurrentChain();
RPCTypeCheck(request.params, {
UniValue::VARR,
UniValueType(), // ARR or OBJ, checked later
UniValue::VNUM,
UniValue::VOBJ,
UniValue::VBOOL
}, true
);
UniValue options{request.params[3].isNull() ? UniValue::VOBJ : request.params[3]}; UniValue options{request.params[3].isNull() ? UniValue::VOBJ : request.params[3]};
CAmount fee; CAmount fee;

View File

@ -568,8 +568,6 @@ static RPCHelpMan upgradewallet()
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return UniValue::VNULL; if (!pwallet) return UniValue::VNULL;
RPCTypeCheck(request.params, {UniValue::VNUM}, true);
EnsureWalletIsUnlocked(*pwallet); EnsureWalletIsUnlocked(*pwallet);
int version = 0; int version = 0;
@ -637,8 +635,6 @@ RPCHelpMan simulaterawtransaction()
if (!rpc_wallet) return UniValue::VNULL; if (!rpc_wallet) return UniValue::VNULL;
const CWallet& wallet = *rpc_wallet; const CWallet& wallet = *rpc_wallet;
RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VOBJ}, true);
LOCK(wallet.cs_wallet); LOCK(wallet.cs_wallet);
UniValue include_watchonly(UniValue::VNULL); UniValue include_watchonly(UniValue::VNULL);