Files
clio/src/rpc
2026-09-23 13:29:57 +01:00
..

RPC subsystem

@page "rpc" RPC subsystem

The RPC subsystem is where the common framework for handling incoming JSON requests is implemented.

Request validation and deserialisation are not defined here. They live in the shared xrpl-rpc-spec library, consumed as <rpcspec/...>, so that one spec per RPC method can be shared verbatim between Clio and xrpld. Clio's job is to supply the handler body and the response shape.

Components

See the common subfolder.

  • AnyHandler: The type-erased wrapper that allows for storing different handlers in one map/vector.
  • Concepts: The SomeHandler concept and its parts, which define what a handler must provide.
  • HandlerRegistry: The table mapping a method name to its handler factory, plus whether the method is Clio-only (and therefore never forwarded to xrpld).

From the spec library:

  • rpc::spec::HandlerFor<Input>: Base class supplying the static parseInput and spec entry points a handler needs. It resolves the versioned spec for Input through ADL, so a handler names only its Input type.
  • rpcspec/handlers/<method>/Types.hpp: The strongly-typed Input struct for a method (xrpl::AccountID, xrpl::uint256, LedgerSpecifier, ... rather than std::string).
  • rpcspec/handlers/<method>/Spec.hpp: The consteval spec declaring that method's fields, their validators and their converters.

Implementing a handler

See the existing handlers in src/rpc/handlers for examples; NFTInfo is a small one.

Handlers need to fulfil the requirements specified by the SomeHandler concept (see rpc/common/Concepts.hpp):

  • Derive from rpc::spec::HandlerFor<rpc::spec::handlers::<method>::Input>. This supplies:

    • Input — the strongly-typed input struct, owned by the spec library rather than declared here

    • static parseInput(boost::json::value const&, uint32_t apiVersion) — validates and deserialises in one pass, returning std::expected<Input, Status>

    • static spec(uint32_t apiVersion) — returns a type-erased rpc::spec::RpcSpecView

    If the method takes no input at all, skip the base class and expose only process(Context const&).

  • Expose an Output POD struct which acts as output of a valid handler invocation.

  • Have a process(Input const&, Context const&) member function returning HandlerReturnType<Output>. Cross-field checks that cannot be expressed in the spec belong here.

  • Implement value_from support for Output using tag_invoke as per boost::json documentation. A value_to for Input is not needed — the spec deserialises it.

  • Register the method in rpc/common/impl/HandlerRegistry.cpp.

If the method has no spec yet, add Types.hpp and Spec.hpp for it in the spec library first.

Important

Do not hand-write template struct rpc::spec::HandlerFor<...>;, and do not include <rpcspec/HandlerForDefs.hpp> or <rpcspec/handlers/*/Spec.hpp> from a handler. The explicit instantiations are generated by rpcspec_generate_instantiations() in src/rpc/CMakeLists.txt, one translation unit per method that has a Spec.hpp. A useful side effect is that every spec is compiled by Clio's build, so spec-side mistakes surface here.

Error messages

A method's error codes and messages are part of its wire contract: clients parse them, so an altered error, error_code or error_message is a breaking change. If a test disagrees with the code, fix the spec rather than the expectation.

Clio and xrpld do not always word the same failure identically. The spec library expresses those differences per server rather than unifying early — ifServerClio() / ifServerXrpld() for whole validators, and the RPCSPEC_IS_CLIO / RPCSPEC_IS_XRPLD macros for anything finer.