feat: Experimental attempt at RPC validation framework

This commit is contained in:
Mayukha Vadari
2026-05-13 12:08:20 -04:00
parent 4ad94ae2ff
commit a5a7f1c3f6
4 changed files with 113 additions and 8 deletions

View File

@@ -3,6 +3,7 @@
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/Role.h>
#include <xrpld/rpc/handlers/Handlers.h>
#include <xrpld/rpc/handlers/account/AccountInfo.h>
#include <xrpld/rpc/handlers/ledger/Ledger.h>
#include <xrpld/rpc/handlers/server_info/Version.h>
@@ -48,6 +49,16 @@ handle(JsonContext& context, Object& object)
context.apiVersion >= HandlerImpl::minApiVer &&
context.apiVersion <= HandlerImpl::maxApiVer,
"xrpl::RPC::handle : valid API version");
if constexpr (requires { HandlerImpl::requestFields; })
{
if (auto status = validateFieldSpecs(context.params, HandlerImpl::requestFields))
{
status.inject(object);
return status;
}
}
HandlerImpl handler(context);
auto status = handler.check();
@@ -78,10 +89,6 @@ handlerFrom()
Handler const kHANDLER_ARRAY[]{
// Some handlers not specified here are added to the table via addHandler()
// Request-response methods
{.name = "account_info",
.valueMethod = byRef(&doAccountInfo),
.role = Role::USER,
.condition = Condition::NoCondition},
{.name = "account_currencies",
.valueMethod = byRef(&doAccountCurrencies),
.role = Role::USER,
@@ -410,6 +417,7 @@ private:
}
// This is where the new-style handlers are added.
addHandler<AccountInfoHandler>();
addHandler<LedgerHandler>();
addHandler<VersionHandler>();
}

View File

@@ -6,8 +6,11 @@
#include <xrpld/rpc/detail/Tuning.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/server/NetworkOPs.h>
#include <span>
namespace json {
class Object;
} // namespace json
@@ -36,9 +39,37 @@ struct Handler
unsigned maxApiVer = kAPI_MAXIMUM_VALID_VERSION;
};
enum class FieldRequirement { Optional, Required };
struct FieldSpec
{
json::StaticString name;
FieldRequirement requirement;
json::ValueType type;
};
Handler const*
getHandler(unsigned int version, bool betaEnabled, std::string const&);
inline Status
validateFieldSpecs(json::Value const& params, std::span<FieldSpec const> fields)
{
for (auto const& field : fields)
{
if (!params.isMember(field.name))
{
if (field.requirement == FieldRequirement::Required)
return {RpcInvalidParams, missingFieldMessage(std::string(field.name))};
continue;
}
if (params[field.name].type() != field.type)
return {RpcInvalidParams, invalidFieldMessage(field.name)};
}
return Status::kOK;
}
/** Return a json::ValueType::Object with a single entry. */
template <class Value>
json::Value

View File

@@ -1,3 +1,5 @@
#include <xrpld/rpc/handlers/account/AccountInfo.h>
#include <xrpld/app/main/Application.h>
#include <xrpld/app/misc/TxQ.h>
#include <xrpld/rpc/Context.h>
@@ -91,14 +93,10 @@ doAccountInfo(RPC::JsonContext& context)
std::string strIdent;
if (params.isMember(jss::account))
{
if (!params[jss::account].isString())
return RPC::invalidFieldError(jss::account);
strIdent = params[jss::account].asString();
}
else if (params.isMember(jss::ident))
{
if (!params[jss::ident].isString())
return RPC::invalidFieldError(jss::ident);
strIdent = params[jss::ident].asString();
}
else
@@ -340,4 +338,24 @@ doAccountInfo(RPC::JsonContext& context)
return result;
}
namespace RPC {
AccountInfoHandler::AccountInfoHandler(JsonContext& context) : context_(context)
{
}
Status
AccountInfoHandler::check()
{
return Status::kOK;
}
void
AccountInfoHandler::writeResult(json::Value& value)
{
value = doAccountInfo(context_);
}
} // namespace RPC
} // namespace xrpl

View File

@@ -0,0 +1,48 @@
#pragma once
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/Role.h>
#include <xrpld/rpc/Status.h>
#include <xrpld/rpc/detail/Handler.h>
#include <xrpl/protocol/ApiVersion.h>
#include <xrpl/protocol/jss.h>
#include <array>
namespace xrpl::RPC {
class AccountInfoHandler
{
public:
explicit AccountInfoHandler(JsonContext&);
Status
check();
void
writeResult(json::Value&);
// NOLINTBEGIN(readability-identifier-naming)
static constexpr char name[] = "account_info";
static constexpr unsigned minApiVer = RPC::kAPI_MINIMUM_SUPPORTED_VERSION;
static constexpr unsigned maxApiVer = RPC::kAPI_MAXIMUM_VALID_VERSION;
static constexpr Role role = Role::USER;
static constexpr Condition condition = Condition::NoCondition;
static constexpr std::array requestFields = {
FieldSpec{jss::account, FieldRequirement::Optional, json::ValueType::String},
FieldSpec{jss::ident, FieldRequirement::Optional, json::ValueType::String},
FieldSpec{jss::queue, FieldRequirement::Optional, json::ValueType::Boolean},
};
// NOLINTEND(readability-identifier-naming)
private:
JsonContext& context_;
};
} // namespace xrpl::RPC