# Request and Response Flow

### Tracing a Request Through Rippled's RPC Pipeline

[← Back to Understanding XRPL(d) RPC Architecture](/core-dev-bootcamp/module06.md)

***

## Introduction

Understanding the complete **lifecycle of an RPC request**—from the moment it arrives at the server to when the response is sent back to the client—is essential for building robust custom handlers. This knowledge helps you anticipate edge cases, implement proper error handling, and optimize performance.

In this section, we'll trace the journey of a request through Rippled's RPC system, examining each stage of processing and the components involved.

***

## The Complete Request Journey

```
Client → Transport Layer → Parser → Validator → Auth → Dispatcher → Handler → Response Builder → Client
```

Let's break down each stage in detail.

***

## Stage 1: Request Reception

### HTTP Entry Point

For HTTP requests, the entry point is the **HTTP server** configured in `rippled.cfg`:

```ini
[port_rpc_admin_local]
port = 5005
ip = 127.0.0.1
admin = 127.0.0.1
protocol = http
```

**Source Location**: `src/xrpld/rpc/detail/RPCCall.cpp`

The HTTP server receives the raw request:

```http
POST / HTTP/1.1
Host: localhost:5005
Content-Type: application/json

{
    "method": "account_info",
    "params": [{
        "account": "rN7n7otQDd6FczFgLdlqtyMVrn3NnrcVXs",
        "ledger_index": "validated"
    }]
}
```

### WebSocket Entry Point

For WebSocket connections, clients establish a persistent connection:

```ini
[port_ws_admin_local]
port = 6006
ip = 127.0.0.1
admin = 127.0.0.1
protocol = ws
```

**Source Location**: `src/xrpld/rpc/detail/RPCHandler.cpp`

WebSocket messages use a slightly different format:

```json
{
    "id": 1,
    "command": "account_info",
    "account": "rN7n7otQDd6FczFgLdlqtyMVrn3NnrcVXs",
    "ledger_index": "validated"
}
```

### gRPC Entry Point

For gRPC, requests arrive as Protocol Buffer messages:

**Source Location**: `src/xrpld/app/main/GRPCServer.cpp`

```proto
message GetAccountInfoRequest {
    string account = 1;
    LedgerSpecifier ledger = 2;
}
```

***

## Stage 2: Request Parsing

The raw request is parsed into a structured format.

### JSON Parsing

```cpp
// Parse the JSON body
Json::Value request;
Json::Reader reader;

if (!reader.parse(requestBody, request)) {
    return rpcError(rpcINVALID_PARAMS, "Unable to parse JSON");
}
```

### Field Extraction

The parser extracts key fields:

```cpp
std::string method = request["method"].asString();
Json::Value params = request["params"];
unsigned int apiVersion = request.get("api_version", 1).asUInt();
```

### Protocol Normalization

Different transports use different formats, which are normalized:

**HTTP/WebSocket**:

* `method` or `command` field
* `params` array or direct parameters

**gRPC**:

* Protobuf message fields
* Converted to JSON internally

***

## Stage 3: Role Determination

Before processing the request, the system determines the **caller's role** based on the connection:

### IP-Based Role Assignment

**Source Location**: `src/xrpld/core/detail/Role.cpp`

```cpp
Role
requestRole(
    Role const& required,
    Port const& port,
    Json::Value const& params,
    beast::IP::Endpoint const& remoteIp,
    std::string_view user)
{
    if (isAdmin(port, params, remoteIp.address()))
        return Role::ADMIN;

    if (required == Role::ADMIN)
        return Role::FORBID;

    if (ipAllowed(
            remoteIp.address(),
            port.secure_gateway_nets_v4,
            port.secure_gateway_nets_v6))
    {
        if (user.size())
            return Role::IDENTIFIED;
        return Role::PROXY;
    }

    return Role::GUEST;
}
```

### Role Hierarchy

```
FORBID < GUEST < USER < IDENTIFIED < ADMIN
```

**Role descriptions**:

* **FORBID**: Blacklisted client (blocked)
* **GUEST**: Unauthenticated public access (limited commands)
* **USER**: Authenticated client (most read operations)
* **IDENTIFIED**: Trusted gateway (write operations)
* **ADMIN**: Full administrative access (all commands)

### Configuration Example

```ini
[rpc_admin]
admin = 127.0.0.1, ::1

[secure_gateway]
ip = 192.168.1.100
```

***

## Stage 4: Handler Lookup

The dispatcher searches the handler table for the requested command:

```cpp
// Look up the handler
auto const it = handlerTable.find(method);

if (it == handlerTable.end()) {
    return rpcError(rpcUNKNOWN_COMMAND, "Unknown method");
}

HandlerInfo const& handlerInfo = it->second;
```

### Version Matching

If API versioning is in use:

```cpp
if (apiVersion < handlerInfo.version_min ||
    apiVersion > handlerInfo.version_max)
{
    return rpcError(rpcINVALID_API_VERSION);
}
```

***

## Stage 5: Permission Verification

The system checks if the caller has sufficient permissions:

```cpp
if (context.role < handlerInfo.role) {
    return rpcError(rpcNO_PERMISSION,
        "You don't have permission for this command");
}
```

**Example**: A `GUEST` client attempting to call `submit` (requires `USER` role) would be rejected here.

***

## Stage 6: Condition Validation

Handlers may require specific runtime conditions:

### Ledger Availability Check

```cpp
if (handlerInfo.condition & RPC::NEEDS_CURRENT_LEDGER) {
    if (!context.ledgerMaster.haveLedger()) {
        return rpcError(rpcNO_CURRENT,
            "Current ledger is not available");
    }
}
```

### Network Connectivity Check

```cpp
if (handlerInfo.condition & RPC::NEEDS_NETWORK_CONNECTION) {
    if (context.netOps.getOperatingMode() < NetworkOPs::omSYNCING) {
        return rpcError(rpcNO_NETWORK,
            "Not connected to network");
    }
}
```

### Closed Ledger Check

```cpp
if (handlerInfo.condition & RPC::NEEDS_CLOSED_LEDGER) {
    if (!context.ledgerMaster.getValidatedLedger()) {
        return rpcError(rpcNO_CLOSED,
            "No validated ledger available");
    }
}
```

***

## Stage 7: Context Construction

A `JsonContext` object is built with all necessary information:

```cpp
RPC::JsonContext context {
    .params = params,
    .app = app,
    .consumer = consumer,
    .role = role,
    .ledger = ledger,
    .netOps = app.getOPs(),
    .ledgerMaster = app.getLedgerMaster(),
    .apiVersion = apiVersion
};
```

**Context provides**:

* **Request parameters** (`params`)
* **Application services** (`app`)
* **Resource tracking** (`consumer`)
* **Permission level** (`role`)
* **Ledger access** (`ledger`, `ledgerMaster`)
* **Network operations** (`netOps`)

***

## Stage 8: Resource Charging

The system tracks API usage to prevent abuse:

```cpp
// Charge the client for this request
context.consumer.charge(Resource::feeReferenceRPC);

// Check if client has exceeded limits
if (context.consumer.isUnlimited() == false &&
    context.consumer.balance() <= 0)
{
    return rpcError(rpcSLOW_DOWN,
        "You are making requests too frequently");
}
```

**Resource limits** are configured per client and prevent DoS attacks.

***

## Stage 9: Handler Invocation

The handler function is called with the constructed context:

```cpp
Json::Value result;

try {
    result = handlerInfo.handler(context);
} catch (std::exception const& ex) {
    return rpcError(rpcINTERNAL, ex.what());
}
```

**Error handling**: Any uncaught exceptions are converted to `rpcINTERNAL` errors.

***

## Stage 10: Response Construction

### Success Response

For successful requests:

```json
{
    "result": {
        "status": "success",
        "account_data": {
            "Account": "rN7n7otQDd6FczFgLdlqtyMVrn3NnrcVXs",
            "Balance": "1000000000",
            ...
        },
        "ledger_index": 12345
    }
}
```

### Error Response

For failed requests:

```json
{
    "result": {
        "error": "actNotFound",
        "error_code": 19,
        "error_message": "Account not found.",
        "status": "error",
        "request": {
            "command": "account_info",
            "account": "rInvalidAccount"
        }
    }
}
```

***

## Stage 11: Response Serialization

The JSON response is serialized back to the client's format:

### HTTP Response

```http
HTTP/1.1 200 OK
Content-Type: application/json

{
    "result": { ... }
}
```

### WebSocket Response

```json
{
    "id": 1,
    "status": "success",
    "type": "response",
    "result": { ... }
}
```

### gRPC Response

```proto
GetAccountInfoResponse {
    account_data: { ... }
}
```

***

## Stage 12: Response Delivery

The response is sent back to the client over the same transport:

* **HTTP**: Single request-response cycle completes
* **WebSocket**: Response is pushed to the persistent connection
* **gRPC**: Streamed response or unary response returned

***

## Timing and Performance

Each stage has associated latency:

| Stage                 | Typical Time | Notes               |
| --------------------- | ------------ | ------------------- |
| **Reception**         | < 1 ms       | Network overhead    |
| **Parsing**           | < 1 ms       | JSON parsing        |
| **Lookup**            | < 0.1 ms     | Hash table lookup   |
| **Permission Check**  | < 0.1 ms     | Simple comparison   |
| **Condition Check**   | < 1 ms       | Ledger availability |
| **Handler Execution** | 1-100 ms     | Varies by handler   |
| **Serialization**     | < 1 ms       | JSON encoding       |
| **Delivery**          | < 1 ms       | Network overhead    |

**Total typical latency**: 5-105 ms

***

## Error Handling at Each Stage

Different errors can occur at each stage:

### Parsing Errors

```cpp
rpcINVALID_PARAMS  // Malformed JSON
rpcBAD_SYNTAX      // Invalid structure
```

### Lookup Errors

```cpp
rpcUNKNOWN_COMMAND // Command not found
rpcINVALID_API_VERSION // Version mismatch
```

### Permission Errors

```cpp
rpcNO_PERMISSION   // Insufficient role
rpcFORBIDDEN       // Blacklisted client
```

### Condition Errors

```cpp
rpcNO_CURRENT      // No current ledger
rpcNO_NETWORK      // Not connected
rpcNO_CLOSED       // No validated ledger
```

### Handler Errors

```cpp
rpcACT_NOT_FOUND   // Account not found
rpcLGR_NOT_FOUND   // Ledger not found
rpcINTERNAL        // Unexpected error
```

***

## Real-World Example: Tracing an account\_info Request

Let's trace a complete request:

### 1. Client Request

```json
{
    "method": "account_info",
    "params": [{
        "account": "rN7n7otQDd6FczFgLdlqtyMVrn3NnrcVXs",
        "ledger_index": "validated"
    }]
}
```

### 2. Reception (HTTP)

```
POST / HTTP/1.1
Host: localhost:5005
```

### 3. Parsing

```cpp
method = "account_info"
params = { "account": "rN7n...", "ledger_index": "validated" }
```

### 4. Role Determination

```cpp
remoteIP = 127.0.0.1 → Role::ADMIN
```

### 5. Handler Lookup

```cpp
handler = doAccountInfo
required_role = Role::USER
condition = NEEDS_CURRENT_LEDGER
```

### 6. Permission Check

```cpp
ADMIN >= USER → PASS
```

### 7. Condition Check

```cpp
haveLedger() == true → PASS
```

### 8. Context Construction

```cpp
context.params = params
context.role = ADMIN
context.ledger = currentLedger
```

### 9. Handler Invocation

```cpp
result = doAccountInfo(context)
```

### 10. Response

```json
{
    "result": {
        "status": "success",
        "account_data": {
            "Account": "rN7n7otQDd6FczFgLdlqtyMVrn3NnrcVXs",
            "Balance": "1000000000"
        }
    }
}
```

***

### Conclusion

The RPC request-response flow demonstrates Rippled's carefully orchestrated pipeline for handling API calls. From initial reception across multiple transport protocols, through parsing, role determination, permission checks, and condition validation, to handler invocation and response formatting—each stage serves a specific purpose. This multi-stage design enables early rejection of invalid requests, consistent error handling, proper resource management, and transport-agnostic processing. Mastering this flow is crucial for debugging RPC issues and understanding how custom handlers integrate into the system.

***


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.xrpl-commons.org/core-dev-bootcamp/module06/request-response-flow.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
