APIv2(ledger_entry): return invalidParams for bad parameters (#4630)

- Verify "check", used to retrieve a Check object, is a string.
- Verify "nft_page", used to retrieve an NFT Page, is a string.
- Verify "index", used to retrieve any type of ledger object by its
  unique ID, is a string.
- Verify "directory", used to retrieve a DirectoryNode, is a string or
  an object.

This change only impacts api_version 2 since it is a breaking change.

https://xrpl.org/ledger_entry.html

Fix #4550
This commit is contained in:
Peter Chen
2026-02-19 16:18:44 +09:00
committed by tequ
parent f3de28f9b7
commit ce09fedc2c
3 changed files with 731 additions and 488 deletions

View File

@@ -19,6 +19,7 @@
#include <ripple/protocol/Issue.h>
#include <ripple/json/json_errors.h>
#include <ripple/protocol/AccountID.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/protocol/jss.h>
@@ -78,7 +79,7 @@ issueFromJson(Json::Value const& v)
{
if (!v.isObject())
{
Throw<std::runtime_error>(
Throw<Json::error>(
"issueFromJson can only be specified with a 'object' Json value");
}
@@ -87,37 +88,34 @@ issueFromJson(Json::Value const& v)
if (!curStr.isString())
{
Throw<std::runtime_error>(
Throw<Json::error>(
"issueFromJson currency must be a string Json value");
}
auto const currency = to_currency(curStr.asString());
if (currency == badCurrency() || currency == noCurrency())
{
Throw<std::runtime_error>(
"issueFromJson currency must be a valid currency");
Throw<Json::error>("issueFromJson currency must be a valid currency");
}
if (isXRP(currency))
{
if (!issStr.isNull())
{
Throw<std::runtime_error>("Issue, XRP should not have issuer");
Throw<Json::error>("Issue, XRP should not have issuer");
}
return xrpIssue();
}
if (!issStr.isString())
{
Throw<std::runtime_error>(
"issueFromJson issuer must be a string Json value");
Throw<Json::error>("issueFromJson issuer must be a string Json value");
}
auto const issuer = parseBase58<AccountID>(issStr.asString());
if (!issuer)
{
Throw<std::runtime_error>(
"issueFromJson issuer must be a valid account");
Throw<Json::error>("issueFromJson issuer must be a valid account");
}
return Issue{currency, *issuer};

File diff suppressed because it is too large Load Diff

View File

@@ -1926,18 +1926,231 @@ public:
std::string const ledgerHash{to_string(env.closed()->info().hash)};
auto makeParams = [&apiVersion](std::function<void(Json::Value&)> f) {
Json::Value params;
params[jss::api_version] = apiVersion;
f(params);
return params;
};
// "features" is not an option supported by ledger_entry.
Json::Value jvParams;
jvParams[jss::api_version] = apiVersion;
jvParams[jss::features] = ledgerHash;
jvParams[jss::ledger_hash] = ledgerHash;
Json::Value const jrr =
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
{
auto const jvParams =
makeParams([&ledgerHash](Json::Value& jvParams) {
jvParams[jss::features] = ledgerHash;
jvParams[jss::ledger_hash] = ledgerHash;
});
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
if (apiVersion < 2u)
checkErrorValue(jrr, "unknownOption", "");
else
checkErrorValue(jrr, "invalidParams", "");
if (apiVersion < 2u)
checkErrorValue(jrr, "unknownOption", "");
else
checkErrorValue(jrr, "invalidParams", "");
}
Json::Value const injectObject = []() {
Json::Value obj(Json::objectValue);
obj[jss::account] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
obj[jss::ledger_index] = "validated";
return obj;
}();
Json::Value const injectArray = []() {
Json::Value arr(Json::arrayValue);
arr[0u] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
arr[1u] = "validated";
return arr;
}();
// invalid input for fields that can handle an object, but can't handle
// an array
for (auto const& field : {
jss::directory,
jss::escrow,
jss::offer,
jss::ticket,
jss::amm,
jss::import_vlseq,
jss::uri_token,
jss::hook,
})
{
auto const jvParams =
makeParams([&field, &injectArray](Json::Value& jvParams) {
jvParams[field] = injectArray;
});
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
if (apiVersion < 2u)
checkErrorValue(jrr, "internal", "Internal error.");
else
checkErrorValue(jrr, "invalidParams", "");
}
// Fields that can handle objects just fine
for (auto const& field : {
jss::directory,
jss::escrow,
jss::offer,
jss::ticket,
jss::amm,
jss::import_vlseq,
jss::uri_token,
})
{
auto const jvParams =
makeParams([&field, &injectObject](Json::Value& jvParams) {
jvParams[field] = injectObject;
});
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "malformedRequest", "");
}
for (auto const& inject : {injectObject, injectArray})
{
// invalid input for fields that can't handle an object or an array
for (auto const& field :
{jss::index,
jss::account_root,
jss::check,
jss::payment_channel})
{
auto const jvParams =
makeParams([&field, &inject](Json::Value& jvParams) {
jvParams[field] = inject;
});
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
if (apiVersion < 2u)
checkErrorValue(jrr, "internal", "Internal error.");
else
checkErrorValue(jrr, "invalidParams", "");
}
// directory sub-fields
for (auto const& field : {jss::dir_root, jss::owner})
{
auto const jvParams =
makeParams([&field, &inject](Json::Value& jvParams) {
jvParams[jss::directory][field] = inject;
});
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
if (apiVersion < 2u)
checkErrorValue(jrr, "internal", "Internal error.");
else
checkErrorValue(jrr, "invalidParams", "");
}
// escrow sub-fields
{
auto const jvParams =
makeParams([&inject](Json::Value& jvParams) {
jvParams[jss::escrow][jss::owner] = inject;
jvParams[jss::escrow][jss::seq] = 99;
});
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
if (apiVersion < 2u)
checkErrorValue(jrr, "internal", "Internal error.");
else
checkErrorValue(jrr, "invalidParams", "");
}
// offer sub-fields
{
auto const jvParams =
makeParams([&inject](Json::Value& jvParams) {
jvParams[jss::offer][jss::account] = inject;
jvParams[jss::offer][jss::seq] = 99;
});
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
if (apiVersion < 2u)
checkErrorValue(jrr, "internal", "Internal error.");
else
checkErrorValue(jrr, "invalidParams", "");
}
// ripple_state sub-fields
{
auto const jvParams =
makeParams([&inject](Json::Value& jvParams) {
Json::Value rs(Json::objectValue);
rs[jss::currency] = "FOO";
rs[jss::accounts] = Json::Value(Json::arrayValue);
rs[jss::accounts][0u] =
"rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
rs[jss::accounts][1u] =
"rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv";
rs[jss::currency] = inject;
jvParams[jss::ripple_state] = std::move(rs);
});
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
if (apiVersion < 2u)
checkErrorValue(jrr, "internal", "Internal error.");
else
checkErrorValue(jrr, "invalidParams", "");
}
// ticket sub-fields
{
auto const jvParams =
makeParams([&inject](Json::Value& jvParams) {
jvParams[jss::ticket][jss::account] = inject;
jvParams[jss::ticket][jss::ticket_seq] = 99;
});
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
if (apiVersion < 2u)
checkErrorValue(jrr, "internal", "Internal error.");
else
checkErrorValue(jrr, "invalidParams", "");
}
// Fields that can handle malformed inputs just fine
for (auto const& field : {jss::nft_page, jss::deposit_preauth})
{
auto const jvParams =
makeParams([&field, &inject](Json::Value& jvParams) {
jvParams[field] = inject;
});
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "malformedRequest", "");
}
// Subfields of deposit_preauth that can handle malformed inputs
// fine
for (auto const& field : {jss::owner, jss::authorized})
{
auto const jvParams =
makeParams([&field, &inject](Json::Value& jvParams) {
auto pa = Json::Value(Json::objectValue);
pa[jss::owner] = "rhigTLJJyXXSRUyRCQtqi1NoAZZzZnS4KU";
pa[jss::authorized] =
"rKssEq6pg1KbqEqAFnua5mFAL6Ggpsh2wv";
pa[field] = inject;
jvParams[jss::deposit_preauth] = std::move(pa);
});
Json::Value const jrr = env.rpc(
"json", "ledger_entry", to_string(jvParams))[jss::result];
checkErrorValue(jrr, "malformedRequest", "");
}
}
}
/// @brief ledger RPC requests as a way to drive