Run a Node
Shape is an OP Stack rollup. Running a Shape node requires two clients that talk over the Engine API and authenticate with a shared JWT secret:
- Execution client (op-geth) runs the EVM and stores chain state.
- Consensus client (op-node) derives the L2 chain from L1 data and feeds blocks to the execution client.
Prerequisites
Hardware requirements
| Component | Full node | Archive node |
|---|---|---|
| CPU | Reasonably modern, multi-core | Same |
| RAM | 16 GB minimum | 16 GB minimum |
| Storage | 700 GB+ SSD (grows ~100 GB / 6 months) | 14 TB+ NVMe SSD (grows ~3.5 TB / 6 months) |
Archive nodes should use NVMe SSDs. op-geth’s --datadir.ancient flag can offload older data to a cheaper HDD.
Software dependencies
- Go 1.21+
- just (op-node build tool)
- Make (op-geth build tool)
- Git
- An L1 Ethereum RPC endpoint (e.g., Alchemy , Infura, or your own node)
- An L1 Beacon (consensus) endpoint (required since Ecotone for blob retrieval)
Network ports
Expose the following ports for P2P sync:
| Port | Protocol | Purpose |
|---|---|---|
| 30303 | TCP + UDP | op-geth peer discovery and block propagation |
| 9222 | TCP + UDP | op-node peer discovery |
Choose paths
The commands below assume these shell variables:
export OP_GETH_DIR=$HOME/op-geth
export OPTIMISM_DIR=$HOME/optimism
export OP_NODE_DIR=$OPTIMISM_DIR/op-node
export DATADIR=$HOME/.shape
export CONFIG_DIR=$HOME/.shape-config
mkdir -p "$DATADIR/geth" "$CONFIG_DIR"Adjust them if you want different locations.
Build the clients
Build both binaries first. You need the same op-geth and op-node binaries whether you sync from a snapshot or from genesis.
1. Build op-geth
git clone https://github.com/ethereum-optimism/op-geth.git "$OP_GETH_DIR"
cd "$OP_GETH_DIR"
git checkout <required-release-branch>
make gethCheck op-geth releases for the required release branch.
2. Build op-node
git clone https://github.com/ethereum-optimism/optimism.git "$OPTIMISM_DIR"
cd "$OPTIMISM_DIR"
git checkout <required-release-branch>
cd "$OP_NODE_DIR"
justCheck optimism releases for the required release branch.
Option A: Sync from snapshot (fastest)
Alchemy snapshots let you skip the full sync by downloading a compressed chaindata archive and starting near the chain tip.
1. Download the snapshot
Visit alchemy.com/snapshots/shape and download the latest .tar.lz4 snapshot for your network (mainnet or Sepolia).
2. Install lz4 and decompress
# macOS
brew install lz4
# Ubuntu/Debian
sudo apt-get install -y lz4If you have existing data, stop your node and clear the old chaindata first:
rm -rf "$DATADIR/geth/chaindata"
rm -rf "$DATADIR/geth/lightchaindata"Decompress the snapshot:
lz4 -c -d <snapshot-file>.tar.lz4 | tar -x -C "$DATADIR/geth/"3. Download the rollup config
Save the rollup.json for your network to $CONFIG_DIR/rollup.json:
Shape Mainnet:
Shape Sepolia:
4. Continue to Configure and run
Your node should be in sync within minutes of starting.
Option B: Sync from genesis
1. Download genesis and rollup configs
Save the files for your network to $CONFIG_DIR/rollup.json and $CONFIG_DIR/genesis.json:
Shape Mainnet:
Shape Sepolia:
2. Initialize the datadir
"$OP_GETH_DIR/build/bin/geth" init --datadir="$DATADIR" "$CONFIG_DIR/genesis.json"3. Continue to Configure and run
Configure and run
1. Generate a JWT secret
op-geth and op-node authenticate over the Engine API with a shared secret. Save it to $CONFIG_DIR/jwt.txt:
openssl rand -hex 32 > "$CONFIG_DIR/jwt.txt"2. Start op-geth
Shape Mainnet:
"$OP_GETH_DIR/build/bin/geth" \
--datadir="$DATADIR" \
--http \
--http.port=8545 \
--http.addr=0.0.0.0 \
--http.api=eth,net,web3 \
--ws \
--ws.port=8546 \
--ws.addr=0.0.0.0 \
--ws.api=eth,net,web3 \
--authrpc.addr=localhost \
--authrpc.port=8551 \
--authrpc.jwtsecret="$CONFIG_DIR/jwt.txt" \
--authrpc.vhosts=* \
--verbosity=3 \
--rollup.sequencerhttp=https://mainnet.shape.network \
--rollup.disabletxpoolgossip=true \
--syncmode=snapShape Sepolia: Replace --rollup.sequencerhttp with https://sepolia.shape.network.
For archive mode: Add --gcmode=archive and change --syncmode=full.
3. Start op-node
Set your L1 endpoints:
export L1_RPC_URL=<your-l1-ethereum-rpc> # e.g., https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
export L1_RPC_KIND=<provider-type> # alchemy, quicknode, infura, basic, etc.
export L1_BEACON_URL=<your-l1-beacon-endpoint> # e.g., https://ethereum-beacon-api.publicnode.comShape Mainnet:
"$OP_NODE_DIR/bin/op-node" \
--l1=$L1_RPC_URL \
--l1.rpckind=$L1_RPC_KIND \
--l1.beacon=$L1_BEACON_URL \
--l2=ws://localhost:8551 \
--l2.jwt-secret="$CONFIG_DIR/jwt.txt" \
--rollup.config="$CONFIG_DIR/rollup.json" \
--l2.enginekind=geth \
--syncmode=execution-layer \
--override.granite=1727370000 \
--override.holocene=1739880000 \
--p2p.listen.tcp=9222 \
--p2p.listen.udp=9222 \
--rpc.addr=127.0.0.1 \
--rpc.port=9545 \
--metrics.enabled \
--metrics.port=7300Shape Sepolia:
"$OP_NODE_DIR/bin/op-node" \
--l1=$L1_RPC_URL \
--l1.rpckind=$L1_RPC_KIND \
--l1.beacon=$L1_BEACON_URL \
--l2=ws://localhost:8551 \
--l2.jwt-secret="$CONFIG_DIR/jwt.txt" \
--rollup.config="$CONFIG_DIR/rollup.json" \
--l2.enginekind=geth \
--syncmode=execution-layer \
--override.ecotone=0 \
--override.fjord=1721732400 \
--override.granite=1727197200 \
--override.holocene=1739880000 \
--p2p.listen.tcp=9222 \
--p2p.listen.udp=9222 \
--rpc.addr=127.0.0.1 \
--rpc.port=9545 \
--metrics.enabled \
--metrics.port=7300For Sepolia, use a Sepolia L1 RPC for L1_RPC_URL.
Do not expose the op-node RPC publicly. The
--rpc.addrshould be127.0.0.1(localhost) unless you have a specific reason and a firewall in place.
Sync modes
| Mode | op-node flag | op-geth flag | Use case |
|---|---|---|---|
| Snap sync | --syncmode=execution-layer | --syncmode=snap (default) | Downloads state at chain tip via P2P. Fastest for full nodes. |
| Full sync | --syncmode=execution-layer | --syncmode=full | Executes every block. Required for archive nodes. |
Verifying your node
After startup, look for:
- op-geth logs showing
Syncing beacon headersas it downloads headers from peers. - op-geth logs showing state download progress (percentage and data volumes).
- Once caught up, both clients log new blocks every 2 seconds.
Check sync status via JSON-RPC:
curl -s -X POST http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}'Returns false when fully synced.
Monitoring
op-node exposes Prometheus metrics at http://localhost:7300/metrics when started with --metrics.enabled.
| Metric | What it tells you |
|---|---|
op_node_default_refs_number | Current L1/L2 block heights. If these stall, sync has stopped. |
op_node_default_p2p_peer_count | Connected peers. Zero means the node falls back to slow L1-only derivation. |
op_node_default_rpc_client_request_duration_seconds | RPC call latency. Spikes usually point to your L1 provider. |
Blob archiver
Since Ecotone, batch data is posted as EIP-4844 blobs . Standard beacon nodes prune blobs after ~18 days.
You need a blob archiver if your node may go offline for 18+ days or you are syncing from genesis without a recent snapshot.
Options:
- Run an L1 beacon node with pruning disabled (e.g., Lighthouse with
--prune-blobs=false) - Use a third-party beacon endpoint that retains blobs beyond the 18-day window
Point op-node at a fallback endpoint that has the old blobs:
--l1.beacon-fallbacks=<archiver-endpoint>Troubleshooting
Node stalling or can’t find peers
Pass the bootnode enodes directly to op-node with --p2p.bootnodes:
Shape Mainnet:
--p2p.bootnodes=enode://ae31be7c6596f4bc3657f90b65a1569c7b0db10c976649f06fe5cfb26139064d78d22b41aede293aa7358fc336d086051ac7fce78f063dce89e1da19c7b88aee@34.228.226.23:0?discport=30301,enode://0bea78f02c9bee58326b3a612dae63dc5a0193fb3dfc9be687c9757d25606405ef9f87810157598fce35688bdb5b3834dc6283afab6dc5774abbd6c5a7b17cea@3.85.27.162:0?discport=30301Shape Sepolia:
--p2p.bootnodes=enode://34fc07c447b843f3915f72cae27daeda7f2f8c3ea4b9132aed188387d3208d1d5cd7eeea58d30d038c14f985a5b108ad6b2172815ed8b757c9de9f609741b2a6@34.228.9.78:0?discport=30301,enode://61841b7682f808f5ce8222f15d7de7f118a2c2c3e342fe42ea41e987ae22fb90e49ef2b5f394897e791c33c6fc59d7ad064851ee653badb2d6ece7a94040944b@54.91.75.177:0?discport=30301401 Unauthorized: signature invalid
The JWT secret doesn’t match between op-node and op-geth. Ensure both point to the same jwt.txt file.
403 Forbidden: invalid host specified
Add --authrpc.vhosts=* to op-geth, or set it to the specific hostname op-node uses to connect.
P2P directory permission denied
op-node can’t write to its P2P discovery directory. Either grant write access or redirect with:
--p2p.discovery.path=/path/with/write/access
--p2p.peerstore.path=/path/with/write/accessWrong chain / mismatched genesis
Verify that:
L1_RPC_URLpoints to the correct L1 network (Ethereum mainnet for Shape, Sepolia for Shape Sepolia)rollup.jsonmatches the network you intend to run- op-geth was initialized with the correct
genesis.json
Unclean shutdown detected
op-geth prints this warning on startup after a crash or kill. It usually recovers on its own. If the database is unrecoverable, re-initialize from genesis or restore from a snapshot .