English | 中文
A high-performance blockchain JSON-RPC proxy server with load balancing, health checking, rate limiting, and observability. It sits between clients and blockchain nodes, distributing requests across a pool of nodes while providing advanced traffic management capabilities.
- Multi-Chain Support — Manage multiple blockchain chains simultaneously with per-chain configuration
- Load Balancing — Random (with weighted gateway support) and Round-Robin strategies
- Node Health Checking — Automatic health verification with configurable timeouts and retries before adding nodes to the pool
- Rate Limiting — Per-method RPC request rate limiting (token bucket algorithm)
- Request Mirroring — Duplicate requests to mirror targets for analysis/monitoring
- Method Routing — Route specific RPC methods to specific node subsets based on inclusion/exclusion rules
- Block Range Query Limiting — Restrict block range queries to prevent excessive resource consumption
- Method Validation — Regex-based validation and deny list for RPC method names
- Observability — OpenTelemetry tracing, Prometheus metrics, structured logging with sampling
- Service Discovery — etcd-based dynamic node registration and configuration
- Version Routing — Support for versioned chain endpoints for multi-version deployments
- Batch Request Support — Handle JSON-RPC batch requests with unified timeout
┌──────────────────┐
│ HTTP Clients │
└────────┬─────────┘
│
┌────────▼─────────┐
│ Hertz HTTP │
│ Server (:8663) │
└────────┬─────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌───────────┐ ┌───────────┐
│ Pre- │ │ Processor │ │ Post- │
│Processor │ │ Pipeline │ │Processor │
│(Validate)│ │ (Handle) │ │ (Log) │
└────┬─────┘ └───────────┘ └───────────┘
│
▼
┌───────────────────────────────────┐
│ Load Balancer │
│ ┌─────────────────────────────┐ │
│ │ Node Selector Strategy │ │
│ │ (Random / Round-Robin) │ │
│ ├─────────────────────────────┤ │
│ │ Gateway (Weights/Routes) │ │
│ └─────────────────────────────┘ │
└───────────────┬───────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐
│ Archive │ │ State │ │ Native │
│ Nodes │ │ Nodes │ │ Nodes │
└─────────┘ └──────────┘ └──────────┘
etcd ──── Service Discovery & Config ────►
Prometheus ◄──── Metrics (:8664) ────
For component boundaries, request lifecycle, dynamic configuration, and operational design details, see the Design Architecture.
Node selection logic:
latest/pendingblock → State nodes- Within 64 blocks of head → State nodes
- More than 64 blocks behind → Archive nodes
- Explicit native request → Native nodes
For comprehensive deployment instructions (Docker Compose, Kubernetes, systemd, production tuning), see the Deployment Guide.
- Go 1.22+
- etcd v3 cluster (for service discovery)
go build -o node-proxy cmd/proxy/main.godocker build -t nodex-proxy:latest .
docker run -p 8663:8663 -p 8664:8664 nodex-proxy:latest -config /path/to/config.yaml./node-proxy -config config/config.example.yaml -listen 8663Flags:
| Flag | Description |
|---|---|
-config |
Path to YAML configuration file |
-listen |
Override RPC listen port (takes precedence over config) |
Ports:
| Port | Description |
|---|---|
8663 |
RPC server (configurable via -listen) |
8664 |
Prometheus metrics endpoint |
See config/config.example.yaml for a full example.
listen: "8663"
metric_listen: "8664"
etcd_endpoints:
- "http://127.0.0.1:2379"
log_level: "info"
# Optional; an empty list disables usage reporting.
usage:
kafka_brokers:
- "kafka-1:9092"
kafka_topic: "leafage-usage"
report_interval: 5s
proxy_config:
service_name: "jrpcx"
native_node_url: "http://127.0.0.1:8545"
default_rpc_timeout: 5000 # milliseconds
connection_pool_size: 2000
node_select_strategy: "random" # "random" or "round_robin"
etcd_prefix: ""When enabled, RPC duration is aggregated in memory by client-id for service
leafage and resource type read, then written to the configured Kafka topic.
A batch is flushed as soon as it reaches 10,000 aggregation keys or the
configured report_interval elapses (default 5s). A missing or blank
client-id is reported as unknown. usage is the aggregated duration in
milliseconds and is reported as at least 1.
jrpcx_usage_aggregation_keys reports the current number of aggregation keys
held in memory, including batches being written. Delivery is best-effort: the
final in-memory batch is sent during graceful shutdown, but data can be lost on
process crashes or Kafka failures.
{
"id": "3c9d1b7e-52aa-4f0e-8d21-77b4e0c9a1f2",
"client_id": "instance:019f45e26c307c86bd45ab350bb52ca8",
"service": "leafage",
"resource_type": "read",
"usage": 123,
"timestamp": 1783568373000
}proxy_config:
processor:
# Slow request logging
observability_log:
enable: true
enable_error_log: true
slow_threshold:
default: 500 # ms
rpc_methods:
eth_call: 1000
eth_getLogs: 2000
# Per-method rate limiting (requests per second)
rate_limiter:
rpc_methods:
eth_call: 100
eth_getLogs: 50
# Block range query restriction
block_range_query_limit:
enable: false
recent_blocks: 0
rewrite_to_latest: false
# Request mirroring
request_mirror:
enable: false
# Method name validation
method_name_checker:
enable: false
regexp: "^[a-zA-Z_][a-zA-Z0-9_]*$"
# Denied RPC methods
method_denied:
- eth_newPendingTransactionFilter
- txpool_content
- txpool_inspect
- txpool_contentFrom
- txpool_statusproxy_config:
observability:
trace:
enable: false
otlp_endpoint: ""
sampling_ratio: 0.1
log:
sampling:
enable: true
initial: 100
thereafter: 100
static_resource:
service.name: "nodex-proxy"
service.version: "1.0.0"Nodes and configuration are managed through etcd with the following key structure:
| Key Pattern | Description |
|---|---|
{prefix}/{chainId}/nodes/{nodeKey} |
State/Archive nodes |
{prefix}/{chainId}/nativeNodes/{nodeKey} |
Native fallback nodes |
{prefix}/{chainId}/lastBlockNumber |
Current chain block height |
{prefix}/{chainId}/gateway |
Gateway config (weights, method routes) |
{prefix}/{chainId}/mirror/{addrKey} |
Mirror targets |
{prefix}/{chainId}/version |
Chain version info |
{prefix}/{chainId}/{version}/nodes/{nodeKey} |
Versioned nodes |
The proxy watches for etcd PUT and DELETE events to dynamically add/remove nodes and update configurations at runtime.
When a new node is discovered via etcd:
- An
eth_blockNumberRPC call is sent to verify the node is reachable - On failure, retries every 5 seconds up to a configurable max wait time (default: 300s)
- Only after a successful health check is the node added to the load balancer pool
Prometheus metrics are exposed at http://<host>:8664/metrics.
| Metric | Type | Description |
|---|---|---|
jrpcx_rpc_calls_started |
Counter | RPC calls initiated |
jrpcx_rpc_calls_finished |
Counter | RPC calls completed |
jrpcx_rpc_calls_failed |
Counter | Failed RPC calls |
jrpcx_rpc_calls_time |
Histogram | RPC latency (ms) |
jrpcx_rpc_calls_cache_hits |
Counter | Cache hit count |
jrpcx_rpc_request_payload_sizes |
Histogram | Request payload size |
jrpcx_rpc_response_payload_sizes |
Histogram | Response payload size |
jrpcx_rpc_batch_calls_finished |
Counter | Batch request count |
jrpcx_rpc_batch_calls_time |
Histogram | Batch request latency |
jrpcx_rpc_http_status_code |
Counter | HTTP status codes |
Common labels include host, target, chain_id, and chain_version. Method metrics add method; jrpcx_rpc_calls_started also adds sourcedapp; failures add status_code, upstream_related, and reason.
| Dependency | Purpose |
|---|---|
| cloudwego/hertz | HTTP framework |
| ethereum/go-ethereum | Ethereum types & RPC |
| etcd/client/v3 | Service discovery |
| opentelemetry | Distributed tracing |
| uber/zap | Structured logging |
| bytedance/sonic | High-performance JSON |
| prometheus/client_golang | Metrics |
MIT License
Copyright (c) 2022 DeBank Inc. admin@debank.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.