Skip to content

RPC proxy — Reference ​

The complete reference for the fusion endpoint: the request flow, the config model, route matching, caching, request forms, and the Blurt routing map. For a gentle introduction, start with the Quick guide.

The proxy is a generalized JSON-RPC reverse proxy (Saboin/temp-rpc-proxy) — Python / Sanic, config-driven, HTTP and WebSocket. It is not Blurt-specific; Blurt supplies a routing config that fuses blurtd, nexus-go, and the wallet indexer behind one endpoint.

Request flow ​

text
  client ──► POST /  (JSON-RPC 2.0)
          ├─ parse method  ("api.method" or the legacy "call" form)
          ├─ match the first route whose regex matches the method
          ├─ cache hit? return cached response          (Redis, per-route TTL)
          └─ else forward to the route's target, cache, and return

A request is a JSON-RPC 2.0 object (jsonrpc, method, params, id) sent to POST /. WebSocket connections are served on the same endpoint with the same routing.

Configuration model ​

Routing is a single JSON file with four top-level keys:

jsonc
{
  "targets":  { "<name>": "<backend URL>", ... },   // named backends
  "timeouts": { "<name>": <seconds>, ... },         // per-target request timeout
  "routes":   { "<method regex>": { "target": "<name>", "cache": <sec>, "translate_to_app_base": <bool> }, ... },
  "default-cache": <seconds>                          // fallback TTL (0 = no cache)
}
  • targets — a name → backend-URL map. The URL scheme selects the transport (http(s)://, ws(s)://, or sock://).
  • timeouts — per-target request timeout in seconds.
  • routes — an ordered map of method regex → route config.
  • default-cache — TTL applied when a route sets no cache.

Route config ​

KeyMeaning
targetwhich named target to forward to
cacheresponse TTL in seconds for this route (omitted → default-cache)
translate_to_app_baserewrite the call to the node's legacy call form before forwarding (see Request forms)

Route matching ​

The proxy takes the JSON-RPC method (e.g. bridge.get_ranked_posts) and matches it against the routes regexes in declaration order; the first match wins. Because matching is first-wins, specific routes must come before catch-alls. A typical namespace ends with a catch-all so nothing is unrouted:

jsonc
"^condenser_api.get_content(.*)$": { "target": "nexus",  "cache": 3 },  // specific → social
"^condenser_api.broadcast_(.*)$":  { "target": "blurtd"              },  // specific → node
"^condenser_api.(.*)$":            { "target": "blurtd"              }   // catch-all → node

Request forms ​

The proxy accepts both JSON-RPC call styles and normalizes them:

jsonc
// modern: dotted method
{ "jsonrpc": "2.0", "id": 1, "method": "condenser_api.get_content", "params": [...] }

// legacy: "call" with [api, method, params]
{ "jsonrpc": "2.0", "id": 1, "method": "call", "params": ["condenser_api", "get_content", [...]] }

translate_to_app_base handles the reverse: for routes that set it, the proxy rewrites a dotted request into the legacy call form ("method": "call", "params": ["condenser_api", <method>, <params>]) that the node expects for certain namespaces.

Caching ​

Responses are cached in Redis (via aiocache) with a per-route TTL in seconds. default-cache (usually 0) applies when a route omits cache. Read methods that are hot and tolerate slight staleness (feeds, blogs, trending, witness lists) carry a small TTL; broadcast and consensus-critical methods carry no cache. Caching shields the backends from repeated identical reads without changing correctness beyond the TTL window.

Transports ​

The target URL scheme selects how the proxy reaches the backend:

SchemeTransport
http:// / https://HTTP
ws:// / wss://WebSocket
sock://Unix socket

Clients may connect over HTTP POST or WebSocket; routing is identical.

The Blurt routing map ​

Blurt's config fuses three backends — the node (blurtd), the social layer (nexus-go, as nexus), and the wallet indexer (wallet_indexer). The routing follows one principle: consensus and broadcast go to the node; social reads go to nexus-go; wallet history goes to the wallet indexer.

Method patternTargetWhy
bridge.*nexus-gothe social API (feeds, communities, profiles, …)
wallet_history.*wallet indexeraccount history and reward screens
tags_api.*, follow_api.*, hive.*nexus-gosocial/discovery reads
condenser_api.get_content*, get_discussions_by*, get_blog*, get_followers/following/follow_countnexus-gothe social subset of the classic API
condenser_api.broadcast_*, network_broadcast_api.*blurtdwrites to the chain
condenser_api.get_account_history, account_history_api.*blurtdchain history (the wallet indexer accelerates the wallet's slice)
condenser_api.* (catch-all), database_api.*, block_api.*, chain_api.*, witness/key APIsblurtdconsensus and chain state

This is exactly the split the wallet indexer and nexus-go pages describe from the other side: a front-end uses one RPC URL, and the proxy sends each method to the service built for it.

Adding a backend is a config change

Fronting a new service (as Blurt did for the wallet indexer) is just a new targets entry plus a route above the catch-alls — no client change, since the front-end keeps using the same single URL.

Configuration (runtime) ​

VariableDefaultPurpose
HOST127.0.0.1bind address (front with a web server for TLS)
PORT5002listen port
WORKERS4Sanic worker processes
DEBUGFalsedebug mode

Redis must be reachable (default 127.0.0.1:6379) for the cache. The routing config is passed with --config. Deployment specifics (service unit, TLS termination, backend addresses) are operator-specific and live with the deployment; the repository ships a systemd unit and example configs.

Released under the MIT License.