Skip to Content

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

ComponentFull nodeArchive node
CPUReasonably modern, multi-coreSame
RAM16 GB minimum16 GB minimum
Storage700 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:

PortProtocolPurpose
30303TCP + UDPop-geth peer discovery and block propagation
9222TCP + UDPop-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 geth

Check 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" just

Check 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 lz4

If 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=snap

Shape 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.com

Shape 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=7300

Shape 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=7300

For Sepolia, use a Sepolia L1 RPC for L1_RPC_URL.

Do not expose the op-node RPC publicly. The --rpc.addr should be 127.0.0.1 (localhost) unless you have a specific reason and a firewall in place.

Sync modes

Modeop-node flagop-geth flagUse 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=fullExecutes every block. Required for archive nodes.

Verifying your node

After startup, look for:

  1. op-geth logs showing Syncing beacon headers as it downloads headers from peers.
  2. op-geth logs showing state download progress (percentage and data volumes).
  3. 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.

MetricWhat it tells you
op_node_default_refs_numberCurrent L1/L2 block heights. If these stall, sync has stopped.
op_node_default_p2p_peer_countConnected peers. Zero means the node falls back to slow L1-only derivation.
op_node_default_rpc_client_request_duration_secondsRPC 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:

  1. Run an L1 beacon node with pruning disabled (e.g., Lighthouse with --prune-blobs=false)
  2. 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=30301

Shape Sepolia:

--p2p.bootnodes=enode://34fc07c447b843f3915f72cae27daeda7f2f8c3ea4b9132aed188387d3208d1d5cd7eeea58d30d038c14f985a5b108ad6b2172815ed8b757c9de9f609741b2a6@34.228.9.78:0?discport=30301,enode://61841b7682f808f5ce8222f15d7de7f118a2c2c3e342fe42ea41e987ae22fb90e49ef2b5f394897e791c33c6fc59d7ad064851ee653badb2d6ece7a94040944b@54.91.75.177:0?discport=30301

401 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/access

Wrong chain / mismatched genesis

Verify that:

  • L1_RPC_URL points to the correct L1 network (Ethereum mainnet for Shape, Sepolia for Shape Sepolia)
  • rollup.json matches 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 .

References

Last updated on