Public RPC Node
The internet-facing role: serves wallets, dApps and explorers over HTTPS/WSS, and acts as a bootnode for the network. It runs no consensus and holds no keys.
Docker, a GHCR token, and a domain. See Installation — and Running a Node if you are still choosing a role.
testnet/rpc/ in node-deploy ships
the compose file, the Caddy config and the chain spec, and its
docs/rpc-node.md is the step-by-step walkthrough. This page explains why
each piece is configured the way it is.
Configuration Reference
The testnet flags are set for you in the compose files. The reference below documents what each node type actually runs.
RPC / bootnode (testnet/rpc/)
--chain /chain-specs/testnet-spec.json
--name "${RPC_NAME}"
--base-path /data
--listen-addr /ip4/0.0.0.0/tcp/30333
--node-key ${RPC_NODE_KEY} # must derive the PeerId baked into bootNodes
--rpc-port 9944
--rpc-cors all
--rpc-external
--rpc-methods Safe # read-only — recommended for public endpoints
--rpc-max-connections 1000
--state-pruning archive # full historical state (explorers/indexers)
--blocks-pruning archive
--no-mdns
--no-private-ipv4
--prometheus-external
--prometheus-port 9615
RPC nodes use host networking and sit behind Caddy + Cloudflare (see RPC Endpoint Protection).
The active tree stores internal Merkle nodes on-chain, not just leaves, so that proofs are served in O(depth) rather than recomputed from scratch. Budget for state that grows with every commitment, not only with block count — and more steeply on archive nodes.
Once a tree fills and seals, an on_idle sweep prunes ~99.8% of its internal MerkleNodes; a proof
for a note in a sealed tree recomputes the pruned siblings from the leaves on demand. The sealed
root stays a permanent anchor that keeps older notes spendable — only the redundant internal nodes
are reclaimed, so per-tree storage drops sharply after sealing.
Public RPC nodes run --rpc-methods Safe. Never expose --rpc-methods Unsafe (which enables author_*) through Cloudflare. If a backend needs unsafe methods (e.g. a faucet), run a separate node reachable only over the private network / loopback.
Monitoring and Observability
Prometheus Metrics
Orbinum exposes Substrate and custom metrics on the --prometheus-port (default: 9615).
Key metrics to monitor:
| Metric | Description | Alert Threshold |
|---|---|---|
substrate_block_height{status="best"} | Latest block received | - |
substrate_block_height{status="finalized"} | Latest finalized block | Lag > 100 blocks |
substrate_sub_libp2p_peers_count | Connected peers | < 3 peers |
substrate_sub_txpool_validations_finished | Processed transactions | - |
process_cpu_seconds_total | CPU usage | > 80% sustained |
process_resident_memory_bytes | RAM usage | > 90% of available |
Prometheus scrape configuration:
scrape_configs:
- job_name: 'orbinum-node'
scrape_interval: 15s
static_configs:
- targets: ['localhost:9615']
labels:
instance: 'testnet-rpc-1'
Health Check Endpoints
System health:
curl -H "Content-Type: application/json" \
-d '{"id":1, "jsonrpc":"2.0", "method": "system_health"}' \
http://localhost:9944
Expected response:
{
"jsonrpc": "2.0",
"result": {
"isSyncing": false,
"peers": 23,
"shouldHavePeers": true
},
"id": 1
}
Sync status:
curl -H "Content-Type: application/json" \
-d '{"id":1, "jsonrpc":"2.0", "method": "system_syncState"}' \
http://localhost:9944
The four rules worth having — node offline, low peers, finality stalled, high memory — are written out in Validator Operations. They apply unchanged here; only the scrape target differs.
Maintenance Operations
Database Management
Pruning
The public RPC / bootnode runs in archive mode (--state-pruning archive --blocks-pruning archive) so explorers and indexers can query full history. Validators use the chain spec defaults.
Check database size (inside the container):
docker exec orbinum-rpc-node du -sh /data/chains/orbinum_testnet/db/
Backups
Chain data lives in a named Docker volume (rpc-node-data) and can always be
resynced from the network, so an RPC node needs no backup of its own — it holds no
key material that matters.
A validator's keystore is the one thing it cannot regenerate. See Validator Operations.
Software Updates
Watchtower ships in the compose stack and rolling-restarts the node when a new image is published — same mechanism as every other role, documented in Validator Operations. The only difference is the directory:
cd node-deploy/testnet/rpc
docker compose pull && docker compose up -d
Troubleshooting
Node Won't Start
Symptoms: Container exits immediately after docker compose up
Diagnosis:
docker compose logs --tail 50 orbinum-rpc-node
# Common errors:
# - "Address already in use" → P2P port 30333 conflict on the host
# - chain-spec mount errors → testnet-spec.json not mounted / wrong path
Solutions:
# Free port 30333 (find the conflicting process)
sudo ss -ltnp | grep 30333
# Recreate after editing .env / compose
docker compose up -d --force-recreate
Sync Stalled or No Peers
Symptoms: Block height not increasing, peers: 0 for an extended period
Diagnosis (RPC node — RPC is reachable on loopback):
curl -s http://127.0.0.1:9944 -H "Content-Type: application/json" \
-d '{"id":1,"jsonrpc":"2.0","method":"system_health"}' | jq '.result.peers'
Solutions:
# Confirm P2P port 30333 is reachable
nc -zv <PUBLIC_OR_PRIVATE_IP> 30333
# Force database rebuild (LAST RESORT — wipes the chain data volume)
docker compose down -v
docker compose up -d
High Memory Usage
Symptoms: Node OOM-killed under query load
The RPC node is capped via mem_limit / cpus in testnet/rpc/docker-compose.yml (defaults RPC_MEM_LIMIT=6g, RPC_CPUS=3) so a query flood can't starve Caddy and the OS.
Diagnosis:
docker stats orbinum-rpc-node --no-stream
Solutions:
# Tune the caps in .env, then recreate. Leave ~2 GB RAM + 1 core for OS + Caddy.
RPC_MEM_LIMIT=12g
RPC_CPUS=6
Security Hardening
A public RPC endpoint is an attack surface in a way a validator is not: it accepts requests from anyone, by design. The layers below are what keep that from becoming a liability.
A validator publishes no RPC at all. Its security model — closed ports, keystore handling, key hygiene — is covered in Validator Operations.
RPC Endpoint Protection
The public RPC endpoint is served behind Cloudflare, with Caddy on the origin terminating TLS using a Cloudflare Origin Certificate (origin.pem / origin.key) — not Let's Encrypt/certbot, not nginx. The custom Caddy image bundles the caddy-ratelimit plugin. Full walkthrough: docs/rpc-node.md in node-deploy.
Defense-in-depth layers:
| Layer | What it does |
|---|---|
| Cloudflare proxy (orange-cloud DNS) | L3/L4 + L7 DDoS protection in front of the origin |
| Cloudflare rate limit | 100 requests / 10s per IP (primary) |
| Cloudflare WAF skip rule | Skips managed bot challenges on the RPC host — otherwise non-browser clients (indexers, wallets, server-side dApps) get JS-challenged and fail |
| Caddy rate limit | 100 / 10s keyed on CF-Connecting-IP (backstop for direct-IP hits) |
| Origin firewall | 80/443 allowed only from Cloudflare IP ranges; 9944 / 9615 denied |
| Docker resource limits | mem_limit / cpus (defaults RPC_MEM_LIMIT=6g, RPC_CPUS=3) so a flood can't starve the host |
Origin firewall — allow Cloudflare only, deny RPC/metrics:
sudo ufw allow 22/tcp # SSH
sudo ufw allow 30333/tcp # P2P — public (this node is a bootnode)
# 80/443 only from Cloudflare's edge ranges
for ip in $(curl -s https://www.cloudflare.com/ips-v4); do
sudo ufw allow from "$ip" to any port 443 proto tcp
sudo ufw allow from "$ip" to any port 80 proto tcp
done
sudo ufw deny 9944 # RPC — Caddy reaches the node over loopback
sudo ufw deny 9615 # Prometheus — not public
sudo ufw enable
Caddyfile (origin TLS + per-IP rate limit):
{$RPC_DOMAIN:rpc-1.testnet.orbinum.io} {
tls /etc/caddy/origin.pem /etc/caddy/origin.key
rate_limit {
zone rpc {
key {http.request.header.CF-Connecting-IP}
events 100
window 10s
}
}
reverse_proxy localhost:9944
}
In Cloudflare, set SSL/TLS → Full (Strict), the RPC DNS records to Proxied, and add the WAF skip + rate-limiting rules described in docs/rpc-node.md in node-deploy.
Learn More
- Installation → - Initial setup and dependencies
- Running a Development Node → — local development
- Becoming a Validator → — join the validator set
- Indexer Archive Node → — the loopback-only archive role
- Consensus Mechanism → - Understanding Orbinum consensus