From a5a7f1c3f6033f54cb8b959f569e07fafe2ca41c Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 13 May 2026 12:08:20 -0400 Subject: [PATCH] feat: Experimental attempt at RPC validation framework --- src/xrpld/rpc/detail/Handler.cpp | 16 +++++-- src/xrpld/rpc/detail/Handler.h | 31 ++++++++++++ .../rpc/handlers/account/AccountInfo.cpp | 26 ++++++++-- src/xrpld/rpc/handlers/account/AccountInfo.h | 48 +++++++++++++++++++ 4 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 src/xrpld/rpc/handlers/account/AccountInfo.h diff --git a/src/xrpld/rpc/detail/Handler.cpp b/src/xrpld/rpc/detail/Handler.cpp index 938e2e645d..b2e7765be7 100644 --- a/src/xrpld/rpc/detail/Handler.cpp +++ b/src/xrpld/rpc/detail/Handler.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -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(); addHandler(); addHandler(); } diff --git a/src/xrpld/rpc/detail/Handler.h b/src/xrpld/rpc/detail/Handler.h index 5140f5d6be..298770a6b3 100644 --- a/src/xrpld/rpc/detail/Handler.h +++ b/src/xrpld/rpc/detail/Handler.h @@ -6,8 +6,11 @@ #include #include +#include #include +#include + 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 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 json::Value diff --git a/src/xrpld/rpc/handlers/account/AccountInfo.cpp b/src/xrpld/rpc/handlers/account/AccountInfo.cpp index ece73c9aee..f8ef98ab01 100644 --- a/src/xrpld/rpc/handlers/account/AccountInfo.cpp +++ b/src/xrpld/rpc/handlers/account/AccountInfo.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -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 diff --git a/src/xrpld/rpc/handlers/account/AccountInfo.h b/src/xrpld/rpc/handlers/account/AccountInfo.h new file mode 100644 index 0000000000..d59661377a --- /dev/null +++ b/src/xrpld/rpc/handlers/account/AccountInfo.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include + +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