One endpoint. Multiple Ethereum RPC providers.
A Rust JSON-RPC gateway with health-aware routing, provider failover, and visibility into each request.
Quick start · Architecture · Configuration · API · Development
RPCETH sits between your application and its upstream RPC nodes. It selects a provider for each request, applies chain and method policies, and tries another provider when an attempt fails. The core library is separate from the Axum HTTP server, so routing and provider management can also be used from Rust.
| Capability | What is in the code |
|---|---|
| Provider selection | Weighted random or round-robin selection, with health scores and method policy inputs |
| Multiple chains | Separate provider pools, named chains, aliases, and a default chain |
| Method policies | Glob-based overrides, read/write groups, timeouts, weights, and attempt limits |
| Failure handling | Provider exclusion between attempts, optional exponential backoff, and circuit breakers |
| Observability | Prometheus metrics, OpenTelemetry export, structured logs, and admin endpoints |
flowchart LR
app([Your application]) request@--> policy["RPCETH<br/>Chain + method policy"]
policy route@--> pool{"Choose an<br/>eligible provider"}
pool selected@-->|this attempt| healthy["Healthy node"]
healthy result@--> response([JSON-RPC response])
pool -. skip .-> failed["Unavailable node<br/>Circuit open"]
pool -. alternative .-> standby["Another eligible node"]
health["Health scores + weights"] -.-> pool
request@{ animation: slow }
route@{ animation: slow }
selected@{ animation: slow }
result@{ animation: slow }
classDef entry fill:#f6f8fa,stroke:#57606a,color:#1f2328;
classDef core fill:#eaf1ff,stroke:#2457d6,stroke-width:2px,color:#172554;
classDef healthy fill:#e9f7ef,stroke:#238636,color:#14532d;
classDef failed fill:#fff0ee,stroke:#cf3f30,color:#8b241b;
classDef muted fill:#f6f8fa,stroke:#8c959f,stroke-dasharray:4 4,color:#57606a;
classDef active stroke:#2457d6,stroke-width:2px;
class app,response entry;
class policy,pool core;
class healthy healthy;
class failed failed;
class standby,health muted;
class request,route,selected,result active;
One provider per attempt. The moving path illustrates one selected route. Other nodes are alternatives, not broadcast targets. A failed attempt can try a different provider within the configured attempt budget; an open circuit makes a provider unavailable.
See the failover sequence and implementation details.
Use a Rust toolchain that supports Edition 2024. Docker Compose is optional for the included monitoring stack.
git clone https://github.com/umgbhalla/rpceth.git
cd rpcethEdit config/proxy.yaml: set auth.api_key, replace the example upstream URLs with nodes you operate or are authorized to use, and select the chains you need. The checked-in provider list is an example, not a guarantee that those endpoints remain available.
For local monitoring, start the collector and dashboards before the proxy:
docker compose -f docker/docker-compose.yaml up -dRun the server from the repository root. -j 2 limits Cargo's build parallelism.
RUST_LOG=info cargo run -j 2 -p proxy-serverThe binary reads config/proxy.yaml, listens on 0.0.0.0:3000, and exports OTLP telemetry to http://localhost:4317. Those locations are currently set in main.rs.
Send a request using the API key you configured:
curl --request POST 'http://localhost:3000/ethereum?apikey=YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'Stop the monitoring stack with docker compose -f docker/docker-compose.yaml down.
This minimal example routes Ethereum traffic between two local upstream nodes. Replace config/proxy.yaml with it only if those nodes are available on your machine.
strategy: weighted_random
auth:
api_key: replace-with-your-own-key
providers:
- id: primary
url: http://127.0.0.1:8545
base_weight: 700
timeout_ms: 3000
- id: secondary
url: http://127.0.0.1:8546
base_weight: 300
timeout_ms: 3000
chains:
ethereum:
providers: [primary, secondary]
aliases: ["eth", "1"]
default_chain: ethereumWeights express selection preferences, not guaranteed traffic percentages. Health and method policies also affect selection.
| Setting | Behavior |
|---|---|
strategy |
weighted_random or round_robin |
chains |
Maps chain names to provider IDs and aliases |
default_chain |
Selects the pool used by POST / |
methods.groups / methods.overrides |
Applies policies to method groups and glob patterns |
default_tolerance |
Strict, Balanced, or Relaxed |
max_retries |
Currently used as the total attempt limit, with at least one attempt |
circuit_breaker |
Configures failure threshold, reset interval, and half-open probes |
See the full example, Chainlist example, and configuration types for more options. Restart the process after configuration changes; hot reload is not implemented.
| Method | Path | Purpose |
|---|---|---|
POST |
/?apikey=... |
Forward through the default chain |
POST |
/{chain_id}?apikey=... |
Forward through a named chain or alias |
GET |
/healthz |
Process liveness |
GET |
/readyz |
Readiness based on provider circuit availability |
GET |
/admin/providers |
Provider status and health details |
GET |
/admin/config |
Configuration summary |
GET |
/metrics |
Prometheus metrics |
RPC requests can include provider_id in the query string to select a particular provider in the chain. Authentication currently uses the apikey query parameter. Admin and metrics routes are outside that authentication middleware; keep them on a trusted network or protect them at your ingress. Query strings and provider URLs can appear in logs, so configure log redaction when using credentials.
The Compose stack includes an OpenTelemetry Collector, Prometheus, Grafana, and Jaeger. It provides local development defaults, including Grafana's admin / admin login.
| Service | Local address |
|---|---|
| Proxy | http://localhost:3000 |
| Grafana | http://localhost:3001 |
| Prometheus | http://localhost:9090 |
| Jaeger | http://localhost:16686 |
| OTLP gRPC collector | http://localhost:4317 |
Dashboards live in docker/grafana/dashboards/. The Prometheus configuration uses host.docker.internal to reach the host process; adapt that target for other Docker network setups.
# Inspect the workspace without compiling dependencies.
cargo metadata --no-deps --format-version 1
# Run the core library's integration tests with limited build parallelism.
cargo test -j 2 -p proxy-core --testsCore tests cover configuration, method routing, health scoring, and provider selection. They run locally without contacting upstream services.
crates/
proxy-core/ Configuration, routing, health, circuit breakers
proxy-server/ Axum gateway, admin endpoints, telemetry
config/ Provider and chain configuration examples
docker/ Local observability stack and dashboards
docs/ Architecture and operational details
- Architecture and current boundaries
- Credential handling and security checks
- Core library reference This repository does not currently include a checked-in CI workflow.
