RPC configs are kept separate from the main cortensord states.
Folders:
salt/rpc/(state stubs for L1/L2/L3)pillar/rpc/(RPC pillar data)pillar/rpc/common.sls(shared defaults by layer:l1,l2,l3)
Overrides:
Put shared defaults under rpc_common.l1, rpc_common.l2, and rpc_common.l3
in pillar/rpc/common.sls, then override on a per‑network basis in
pillar/rpc/l1*.sls, pillar/rpc/l2*.sls, or pillar/rpc/l3*.sls.
The current RPC states are stubs; when automation is added, it should merge
rpc_common into the specific layer config (layer values win).
Mainnet placeholders are available:
pillar/rpc/l1-mainnet.slspillar/rpc/l2-mainnet.slspillar/rpc/l3-mainnet.sls
Mainnet setup uses the same flow as testnet, but with mainnet chain IDs,
endpoints, and snapshots. Copy the testnet examples below and swap in the
mainnet values, or fill in the *-mainnet.sls placeholders.
These RPC states are not wired into top.sls. Apply them explicitly on RPC hosts:
sudo salt 'rpc-server-01' state.apply rpc.l1
sudo salt 'rpc-server-01' state.apply rpc.l2
sudo salt 'rpc-server-01' state.apply rpc.l3If you want Salt to run Docker Compose upgrades, set compose_dir in
pillar/rpc/common.sls (or per‑layer overrides in pillar/rpc/l*.sls) and run:
sudo salt 'rpc-server-01' state.apply rpc.upgraderpc.l2 and rpc.l3 also generate docker-compose.yml in the configured
compose_dir (defaults: /opt/rpc/l2, /opt/rpc/l3) along with the data dirs.
These use the same compose_dir values and run only if a compose file exists:
sudo salt 'rpc-server-01' state.apply rpc.pull
sudo salt 'rpc-server-01' state.apply rpc.start
sudo salt 'rpc-server-01' state.apply rpc.stopWhen you upgrade RPC images, do a controlled stop/pull/start:
# L1 (in the L1 compose folder)
docker compose down
docker compose pull
docker compose up -d
# L2 (in the L2 compose folder)
docker compose down
docker compose pull
docker compose up -d
# L3 (in the L3 compose folder)
docker compose down
docker compose pull
docker compose up -dOptional: verify logs after restart
docker compose logs -fn 200Estimated storage: 1.1–1.5 TB
Setup:
curl -sL https://raw.githubusercontent.com/cerberus-node/aztec-network/refs/heads/main/auto-setup-sepolia.sh -o auto-setup-sepolia.sh && chmod +x auto-setup-sepolia.sh && bash auto-setup-sepolia.shChoose: Prysm
Verify:
curl -s -X POST http://localhost:8545 \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}'curl -s http://localhost:3500/eth/v1/node/syncing | jqCommands:
docker compose up -d
docker compose down
docker compose logs -fn 1000
docker compose logs -fn 1000 geth
docker compose logs -fn 1000 prysmEstimated storage: 1.1–1.5 TB (first setup may need ~2x snapshot space)
Setup:
- Create folders:
mkdir -p /opt/rpc/l2/arbitrum-sepolia-rpc-data
cd /opt/rpc/l2- Download snapshot from https://www.publicnode.com/snapshots into
arbitrum-sepolia-rpc-data. - Create
docker-compose.yml:
services:
arbitrum-sepolia-rpc:
container_name: arbitrum-sepolia-rpc
image: offchainlabs/nitro-node:v3.9.3-8bc5554
user: 0:0
restart: unless-stopped
stop_grace_period: 5m
command:
--chain.id=421614
--parent-chain.connection.url=http://geth:8545
--parent-chain.blob-client.beacon-url=http://prysm:3500
--http.api=net,web3,eth
--http.corsdomain=*
--http.addr=0.0.0.0
--http.vhosts=*
--http.port=22557
--ws.addr 0.0.0.0
--ws.port=22558
--ws.origins=*
volumes:
- ./arbitrum-sepolia-rpc-data:/root/.arbitrum
ports:
- 22557:22557 # json-rpc endpoint
- 22558:22558 # ws endpoint
networks:
- rpc
networks:
rpc:
external: trueCommands:
docker compose up -d
docker compose down
docker compose logs -fn 1000With Salt (if compose_dir is set, default /opt/rpc/l2):
sudo salt 'rpc-server-01' state.apply rpc.pull # docker compose pull
sudo salt 'rpc-server-01' state.apply rpc.start # docker compose up -d
sudo salt 'rpc-server-01' state.apply rpc.stop # docker compose downNginx example (reverse proxy with limits): see block below.
Nginx (optional):
upstream backend_sepolia_arb_rpc {
server 127.0.0.1:22557; #agnc
#server 209.209.9.159:22557 weight=1 max_fails=10 fail_timeout=30m; #backup-0
#server 104.152.209.8:22557 weight=1 max_fails=10 fail_timeout=30m; #backup-1
}
server
{
listen 80;
listen 443 ssl http2 ;
server_name sepolia-arb-rpc.agnc.my.id;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/sepolia-arb-rpc.agnc.my.id;
include /www/server/panel/vhost/nginx/extension/sepolia-arb-rpc.agnc.my.id/*.conf;
limit_req zone=rpc_limit burst=2000 nodelay;
limit_conn conn_limit 10000;
#CERT-APPLY-CHECK--START
include /www/server/panel/vhost/nginx/well-known/sepolia-arb-rpc.agnc.my.id.conf;
#CERT-APPLY-CHECK--END
#SSL-START
ssl_certificate /www/server/panel/vhost/cert/sepolia-arb-rpc.agnc.my.id/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/sepolia-arb-rpc.agnc.my.id/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_tickets on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
error_page 497 https://$host$request_uri;
#SSL-END
error_page 404 /404.html;
error_page 502 /502.html;
location ~ /purge(/.*) {
proxy_cache_purge cache_one $host$1$is_args$args;
}
include /www/server/panel/vhost/nginx/proxy/sepolia-arb-rpc.agnc.my.id/*.conf;
include enable-php-00.conf;
include /www/server/panel/vhost/rewrite/sepolia-arb-rpc.agnc.my.id.conf;
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
{
return 404;
}
location ~ \.well-known{
allow all;
}
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
access_log /www/wwwlogs/sepolia-arb-rpc.agnc.my.id.log;
error_log /www/wwwlogs/sepolia-arb-rpc.agnc.my.id.error.log;
}location ^~ /
{
proxy_pass http://backend_sepolia_arb_rpc;
proxy_set_header Host backend_sepolia_arb_rpc;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status;
set $static_filesBL8N6tG 0;
if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
{
set $static_filesBL8N6tG 1;
expires 1m;
}
if ( $static_filesBL8N6tG = 0 )
{
add_header Cache-Control no-cache;
}
}Setup:
- Create folders:
mkdir -p /opt/rpc/l3/testnet1a-rpc-data
cd /opt/rpc/l3- Prepare
testnet1a-rpc-data(sync data as needed). - Create
docker-compose.yml:
services:
testnet1a-rpc:
container_name: testnet1a-rpc
image: offchainlabs/nitro-node:v3.9.4-7f582c3
user: 0:0
restart: unless-stopped
stop_grace_period: 5m
command:
--chain.id=18964747554
--chain.name=COR-0
--parent-chain.connection.url=http://arbitrum-sepolia-rpc:22557
--chain.info-json="[{\"chain-id\":18964747554,\"parent-chain-id\":421614,\"parent-chain-is-arbitrum\":true,\"chain-name\":\"COR-0\",\"chain-config\":{\"chainId\":18964747554,\"homesteadBlock\":0,\"daoForkBlock\":null,\"daoForkSupport\":true,\"eip150Block\":0,\"eip150Hash\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"eip155Block\":0,\"eip158Block\":0,\"byzantiumBlock\":0,\"constantinopleBlock\":0,\"petersburgBlock\":0,\"istanbulBlock\":0,\"muirGlacierBlock\":0,\"berlinBlock\":0,\"londonBlock\":0,\"clique\":{\"period\":0,\"epoch\":0},\"arbitrum\":{\"EnableArbOS\":true,\"AllowDebugPrecompiles\":false,\"DataAvailabilityCommittee\":true,\"InitialArbOSVersion\":40,\"InitialChainOwner\":\"0xD6b9d395c3368B5412b3a7D2fb0D7327a83Ed792\",\"GenesisBlockNum\":0,\"MaxCodeSize\":24576,\"MaxInitCodeSize\":49152}},\"rollup\":{\"bridge\":\"0x974d0762eBD0883411A75de8BF41a4c7eA93c03d\",\"inbox\":\"0x08a6C4Bbf77A2387D7DDAD41Fd55193467B6e2b6\",\"sequencer-inbox\":\"0xeeF64Fe40F04aB1aB55016A242D4C278721237FD\",\"rollup\":\"0x39F772DfF00756bec951b691778f58d10a753a57\",\"validator-wallet-creator\":\"0x2c37dCBCE3fbe32c9Ba62892F1E41DbB023BB62b\",\"stake-token\":\"0x980B62Da83eFf3D4576C647993b0c1D7faf17c73\",\"deployed-at\":229017936}}]"
--node.feed.input.url=wss://feed.testnet1a.cortensor.org
--execution.forwarding-target=https://sequencer.testnet1a.cortensor.org
--node.data-availability.enable
--node.data-availability.rest-aggregator.enable
--node.data-availability.rest-aggregator.online-url-list=https://testnet1a-das-servers.cortensor.org
--http.api=net,web3,eth,debug
--http.corsdomain=*
--http.addr=0.0.0.0
--http.vhosts=*
--http.port=10001
--ws.addr=0.0.0.0
--ws.port=10002
--ws.origins=*
volumes:
- ./testnet1a-rpc-data:/root/.arbitrum
ports:
- 10001:10001 # JSON-RPC endpoint
- 10002:10002 # WebSocket endpoint
networks:
- rpc
networks:
rpc:
external: trueCommands:
docker compose up -d
docker compose down
docker compose logs -fn 100With Salt (if compose_dir is set, default /opt/rpc/l3):
sudo salt 'rpc-server-01' state.apply rpc.pull # docker compose pull
sudo salt 'rpc-server-01' state.apply rpc.start # docker compose up -d
sudo salt 'rpc-server-01' state.apply rpc.stop # docker compose downMain server block:
upstream backend_sepolia_arb_rpc {
server 127.0.0.1:22557; #primary
#server 209.209.9.159:22557 weight=1 max_fails=10 fail_timeout=30m; #backup-0
#server 104.152.209.8:22557 weight=1 max_fails=10 fail_timeout=30m; #backup-1
}
server {
listen 80;
listen 443 ssl http2;
server_name sepolia-arb-rpc.example.com;
root /www/wwwroot/sepolia-arb-rpc.example.com;
include /www/server/panel/vhost/nginx/extension/sepolia-arb-rpc.example.com/*.conf;
limit_req zone=rpc_limit burst=2000 nodelay;
limit_conn conn_limit 10000;
# SSL certificate paths
ssl_certificate /www/server/panel/vhost/cert/sepolia-arb-rpc.example.com/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/sepolia-arb-rpc.example.com/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
error_page 497 https://$host$request_uri;
error_page 404 /404.html;
error_page 502 /502.html;
location ~ /purge(/.*) {
proxy_cache_purge cache_one $host$1$is_args$args;
}
include /www/server/panel/vhost/nginx/proxy/sepolia-arb-rpc.example.com/*.conf;
include enable-php-00.conf;
include /www/server/panel/vhost/rewrite/sepolia-arb-rpc.example.com.conf;
location ~ ^/(\\.user.ini|\\.htaccess|\\.git|\\.env|\\.svn|\\.project|LICENSE|README.md) { return 404; }
location ~ \\.well-known { allow all; }
if ( $uri ~ "^/\\.well-known/.*\\.(php|jsp|py|js|css|lua|ts|go|zip|tar\\.gz|rar|7z|sql|bak)$" ) { return 403; }
access_log /www/wwwlogs/sepolia-arb-rpc.example.com.log;
error_log /www/wwwlogs/sepolia-arb-rpc.example.com.error.log;
}Proxy block:
location ^~ / {
proxy_pass http://backend_sepolia_arb_rpc;
proxy_set_header Host backend_sepolia_arb_rpc;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status;
set $static_files 0;
if ( $uri ~* "\\.(gif|png|jpg|css|js|woff|woff2)$" ) { set $static_files 1; expires 1m; }
if ( $static_files = 0 ) { add_header Cache-Control no-cache; }
}Nginx (optional):
limit_req_zone $binary_remote_addr zone=rpc_limit:50m rate=5000r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:50m;
server
{
listen 80;
listen 443 ssl http2 ;
server_name cor-1.testnet-rpc.agnc.my.id;
index index.php index.html index.htm default.php default.htm default.html;
root /www/wwwroot/cor-1.testnet-rpc.agnc.my.id;
include /www/server/panel/vhost/nginx/extension/cor-1.testnet-rpc.agnc.my.id/*.conf;
limit_req zone=rpc_limit burst=2000 nodelay;
limit_conn conn_limit 10000;
#CERT-APPLY-CHECK--START
include /www/server/panel/vhost/nginx/well-known/cor-1.testnet-rpc.agnc.my.id.conf;
#CERT-APPLY-CHECK--END
#SSL-START
ssl_certificate /www/server/panel/vhost/cert/cor-1.testnet-rpc.agnc.my.id/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/cor-1.testnet-rpc.agnc.my.id/privkey.pem;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_tickets on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000";
error_page 497 https://$host$request_uri;
#SSL-END
#ERROR-PAGE-START
error_page 404 /404.html;
error_page 502 /502.html;
#ERROR-PAGE-END
#PHP-INFO-START
#Clear cache
location ~ /purge(/.*) {
proxy_cache_purge cache_one $host$1$is_args$args;
}
include /www/server/panel/vhost/nginx/proxy/cor-1.testnet-rpc.agnc.my.id/*.conf;
include enable-php-00.conf;
#REWRITE-START
include /www/server/panel/vhost/rewrite/cor-1.testnet-rpc.agnc.my.id.conf;
#REWRITE-END
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md)
{
return 404;
}
location ~ \.well-known{
allow all;
}
if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) {
return 403;
}
access_log /www/wwwlogs/cor-1.testnet-rpc.agnc.my.id.log;
error_log /www/wwwlogs/cor-1.testnet-rpc.agnc.my.id.error.log;
}location ^~ /
{
proxy_pass http://localhost:20001;
proxy_set_header Host localhost;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status;
set $static_fileK370sGqr 0;
if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
{
set $static_fileK370sGqr 1;
expires 1m;
}
if ( $static_fileK370sGqr = 0 )
{
add_header Cache-Control no-cache;
}
}