veriRPC is a small JSON-RPC gateway for Ethereum execution RPC providers.
It forwards each incoming request to multiple upstream providers, waits for a quorum of matching responses, and returns the agreed result. For selected methods, it also performs proof-based verification before returning the response.
- Accepts JSON-RPC 2.0 requests over HTTP.
- Fans requests out to multiple Ethereum RPC providers.
- Requires a strict-majority consensus on the response payload.
- Verifies
eth_getBalanceagainst the Ethereum state trie usingeth_getProof. - Exposes health and metrics endpoints.
eth_getBalance: verified.eth_getTransactionReceipt: not yet verified.- All other methods: consensus only, no proof verification.
For each JSON-RPC request:
- The gateway optionally resolves any
"latest"block tags to a concrete block number by asking providers foreth_blockNumber. - The dispatcher sends the request to all configured providers in parallel.
- Responses are bucketed by compacted JSON
resultorerror. - As soon as
consensus.min_agreeproviders return the same payload, that payload becomes the agreed response. - If the method is supported by the verifier, the verifier checks the agreed result before it is returned.
For eth_getBalance, the verifier:
- Reads the agreed balance from the JSON-RPC response.
- Fetches the block header:
- from
verifier.trusted_header_providerif configured - otherwise through the same provider consensus path
- from
- Fetches an account proof from one of the agreeing providers.
- Verifies the Merkle-Patricia trie proof against the block
stateRoot. - Compares the balance in the proof with the agreed balance.
- Go
1.26.1 - At least 3 Ethereum execution RPC providers
go build -o verirpc .go test ./...- Copy and edit the example config:
cp example.config.toml config.toml- Start the server:
go run . -config config.tomlOr run the built binary:
./verirpc -config config.tomlThe default listen port is 8547.
Configuration is loaded from a TOML file.
[gateway]
port = 8547
read_timeout = "5s"
write_timeout = "10s"
[dispatcher]
timeout = "3s"
[consensus]
min_agree = 2
[verifier]
trusted_header_provider = "infura"
[[providers]]
name = "infura"
url = "https://mainnet.infura.io/v3/..."
[[providers]]
name = "alchemy"
url = "https://eth-mainnet.g.alchemy.com/v2/..."
[[providers]]
name = "ankr"
url = "https://rpc.ankr.com/eth"[gateway]
port: HTTP listen port. Default:8547read_timeout: HTTP server read timeout. Default:5swrite_timeout: HTTP server write timeout. Default:10s
[dispatcher]
timeout: timeout applied to each upstream provider request. Default:3s
[consensus]
min_agree: minimum number of providers that must return the same result. Must be a strict majority of configured providers. If omitted, it defaults tolen(providers)/2 + 1.
[verifier]
trusted_header_provider: optional provider name used only for block header lookups during verification. If omitted, header lookups use provider consensus instead.
[[providers]]
name: provider identifierurl: upstream JSON-RPC endpoint
- At least 3 providers must be configured.
consensus.min_agreemust be a strict majority.consensus.min_agreecannot exceed the number of providers.- If
verifier.trusted_header_provideris set, it must match one of the configured provider names.
Ethereum JSON-RPC entrypoint.
Request body must be a single JSON-RPC 2.0 request object.
Batch requests are not supported.
Example:
curl -s http://127.0.0.1:8547/ \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"eth_getBalance",
"params":["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045","latest"]
}'Simple health check.
Example:
curl -s http://127.0.0.1:8547/healthReturns an in-memory metrics snapshot.
Example:
curl -s http://127.0.0.1:8547/metricsIncoming requests must follow this shape:
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getBalance",
"params": ["0x...", "latest"]
}Fields:
jsonrpc: must be"2.0"id: any valid JSON-RPC id valuemethod: Ethereum JSON-RPC method nameparams: JSON array of method parameters
Successful responses:
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x0"
}Error responses:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32603,
"message": "proof verification failed"
}
}Notes:
- HTTP status is
200 OKfor JSON-RPC errors. - Parse errors use
id: null. - If no provider quorum is reached, the gateway returns:
code: -32603message: "no consensus among providers"
- If proof verification fails, the gateway returns:
code: -32603message: "proof verification failed"
{
"status": "ok"
}GET /metrics returns:
{
"dispatches": 10,
"agreements": 9,
"disagreements": 1,
"providers": {
"infura": {
"requests": 10,
"errors": 1,
"error_rate": 0.1,
"avg_latency_ms": 120.5,
"agreed": 8,
"agreement_rate": 0.8
}
}
}Fields:
dispatches: total number of dispatched JSON-RPC requestsagreements: number of requests that reached quorumdisagreements: number of requests that failed to reach quorumproviders: per-provider counters and derived rates
Per-provider fields:
requests: upstream requests senterrors: upstream requests that failederror_rate:errors / requestsavg_latency_ms: average upstream latency in millisecondsagreed: number of successful dispatches where the provider was in the agreeing setagreement_rate:agreed / dispatches
Get a balance through veriRPC:
curl -s http://127.0.0.1:8547/ \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"eth_getBalance",
"params":["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045","0x10"]
}'Get the current health status:
curl -s http://127.0.0.1:8547/healthGet the metrics snapshot:
curl -s http://127.0.0.1:8547/metrics- Only single JSON-RPC requests are supported.
- Only
eth_getBalanceis currently proof-verified. eth_getTransactionReceiptis not yet verified.- Verification depends on provider support for
eth_getProof. - Metrics are in-memory only and reset on process restart.