I’m a fan of the Reth execution client, and recommend it for anyone looking to set up an archive node.
Reth is modular, so the same code base can target different ecosystems. I’ve been exploring the Base ecosystem lately, which is built on the Optimism stack.
Optimism is an open source layer two (L2) specification that anyone can use to launch a rollup chain of their own.
This is quite similar to Arbitrum, which is in itself an optimistic rollup. However Arbitrum does not implement the Optimism protocol. I wrote a guide to running the Arbitrum node software (Nitro) last year.
The Reth documentation includes a basic guide to deploying an Optimism node using their op-reth
variant.
However it’s not a very approachable guide unless you’re already familiar with running a Reth node, and it leaves out some required configuration items.
So this guide is feature-complete, provides the missing options, and provides additional context where I think it’s useful.
Optimism Stack Architecture
The Optimism stack in general requires two pieces of software: an execution client and a rollup client. The rollup client is equivalent to the consensus client on Ethereum, but is stateless and only uses to maintain sync with the live chain.
For this example, the execution client is op-reth
and the rollup client is op-node
.
However since Optimism is a rollup, it needs to track the state of the L1 below it. And to track the state of the L1, it needs access to state data from an L1 execution client and beacon data from an L1 consensus client.
So to properly run an L2 node you need four things!
If you’re already running an Ethereum node, you have access to the L1 execution and consensus clients. But if not, you can use some of the free public endpoints. The example below uses public L1 services, but you’re welcome to point these to your internal services if you have them.
I run multiple nodes, so the configuration below has alternative ports for both op-reth
and op-node
to avoid conflicts.
Both op-reth
and op-node
run in Docker containers, defined by a compose file. The Reth node’s data is held in a named Docker volume called base_reth_data
, which will be stored in /var/lib/docker/volumes
unless you have overridden this location. Alternatively you can specify a file system path, which will be attached to the container using a bind mount.
Compose
docker-compose.yml
services:
execution_client:
image: ghcr.io/paradigmxyz/op-reth:latest
container_name: base_reth
restart: always
command: >
node
--full
--chain base
--datadir /data
--rollup.sequencer-http https://mainnet-sequencer.base.org
--rollup.disable-tx-pool-gossip
--port 30305
--discovery.port 30305
--enable-discv5-discovery
--discovery.v5.addr 0.0.0.0
--discovery.v5.port 30306
--discovery.v5.port.ipv6 30306
--http
--http.addr 0.0.0.0
--http.port 8547
--http.corsdomain "*"
--http.api all
--ws
--ws.addr 0.0.0.0
--ws.port 8548
--ws.origins "*"
--ws.api all
--authrpc.jwtsecret /jwt/jwt.hex
--authrpc.addr 0.0.0.0
--authrpc.port 9551
ports:
- 8547:8547/tcp
- 8548:8548/tcp
- 30305:30305/tcp
- 30305:30305/udp
- 30306:30306/tcp
- 30306:30306/udp
volumes:
- base_reth_data:/data
- base_reth_jwt:/jwt
rollup_client:
image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:latest
container_name: base_rollup
restart: always
command: >
op-node
--network=base-mainnet
--syncmode=execution-layer
--l1=wss://eth.merkle.io
--l1.trustrpc
--l1.beacon=https://ethereum-beacon-api.publicnode.com
--l2=http://execution_client:9551
--l2.jwt-secret=/jwt/jwt.hex
--p2p.listen.tcp=9004
--p2p.listen.udp=9004
ports:
- 9004:9004/tcp
- 9004:9004/udp
volumes:
- base_reth_jwt:/jwt:ro
volumes:
base_reth_data:
base_reth_jwt:
After creating this file, bring the clients online with docker compose up
and observe that both containers are created and begin to sync with the network. Once you are satisfied that both are working correctly, stop them with CTRL+C and bring them online in detached mode using docker compose up -d
.
Notes, Caveats and Bugs
As an optimization, Reth does not support storage proofs for historical blocks. Therefore a rollup client that requests a storage proof will fail, since it cannot verify that the L1 client is sending accurate information.
The --l1.trustrpc
option to op-node
can skip this storage check, but you might not be willing to make this tradeoff with a public L1. Since I run my own L1 infrastructure, I have no issue using this option. But if you don’t, or prefer a more conservative approach, consider using a different L1 client.
During testing, I discovered that the current release of op-reth
(v0.2.0-beta.9) has a bug where it will not forward transactions to the main Base sequencer. I filed an issue on the Reth Github, which the team promptly fixed. However they have not published a new release with that fix. I expect it will be published soon, but be aware that beta.9 and earlier will not pass transactions to the sequencer as you’d expect. I have tagged the image above with latest
, so that update will be picked up when you run docker compose pull
and the container will be recreated with docker compose up -d
.
I have specified the --full
option to op-reth
, which configures it as a full (non-archive) node. If you want access to all historical blocks and storage states, remove that option.
Sync Time and Storage Use
The node took roughly a day to sync, and the data directory is using ~310 GB of storage.
Hi, this is my first comment here. I want to ask the question - Is it possible to build MEV bot in L2 OP-stack chains like BASE? From my quick research, it is impossible to MEV on L2 OP-stack, due to missing the mempool accessibility. But I wonder if we can work around, by listening Sequencer queue and mev upon arb opportunities?