Appearance
IOST L2(opIOST) Node Operation Guide
If you want to build applications on opIOST, you need access to an opIOST node. You can run your own node to access the blockchain data.
This guide will walk you through setting up your own opIOST mainnet node.
IMPORTANT: BSC Lorentz Hardfork Notice
BSC Lorentz hardfork will occur on April 29th, 2025 at 05:05 AM UTC.
All IOST L2 node operators must update to compatible versions before this date to ensure proper chain synchronization after BSC block time changes from 3 seconds to 0.75 seconds.
A comprehensive node setup guide with updated instructions will be published after the successful completion of the BSC hardfork.
Hardware Requirements
Nodes must store the transaction history of opIOST and run Geth. For optimal performance, they should be powerful machines (physical or virtual) with at least 16GB RAM and an SSD drive with 500GB free space (for production network).
Fast Node
For users who only need to run a normal RPC node without debugging functions, you can run a fast node, which has faster synchronization speed and lower hardware requirements.
Fast nodes don't have MPT states and only use snapshots to sync the latest state. The security is not as good as full nodes, but it's sufficient for most users and has been validated in many production nodes. The advantage of fast nodes is faster synchronization because they don't need to calculate MPT states or store and query MPT trees.
You can start a fast node with the --allow-insecure-no-tries
flag. If using a fast node, the gc mode should not be archive.
Running with Docker
Using Docker Compose Scripts
Our docker-compose script repo link: https://github.com/alt-research/opstack-fullnode-sync
For our docker-compose script repo, please checkout to tag
v1.2.0
a. Commands:
bash# If you have not downloaded this repo before git clone https://github.com/alt-research/opstack-fullnode-sync cd opstack-fullnode-sync # If you already download this repo before git fetch # Checkout to tag v1.2.0 git checkout -b branch-v1.2.0 v1.2.0
Restart (start) the fullnode with the new
.env
bash
# geth image
GETH_IMAGE=ghcr.io/bnb-chain/op-geth:v0.5.6
# l1 rpc url
# node image
NODE_IMAGE=ghcr.io/bnb-chain/op-node:v0.5.2
L1_RPC=<L1_RPC>
# l1 rpc kind(standard/basic/quicknode/alchemy/erigon)
# https://docs.optimism.io/builders/node-operators/tutorials/node-from-docker
L1_RPC_KIND=standard
# node libp2p bootnodes
P2P_STATIC="/dns/iost-mainnet-bootnodes.altlayer.network/tcp/31851/p2p/16Uiu2HAm2Gb2TgbP1N2o5QwVfqdJdsKtCVud2rNEY8U6FrGnuvwt,/dns/iost-mainnet-bootnodes.altlayer.network/tcp/31853/p2p/16Uiu2HAmQgjpWtJpuCt4HEyktismfHnmcuYe984C2w9bHr8WSM2Z,/dns/iost-mainnet-bootnodes.altlayer.network/tcp/31855/p2p/16Uiu2HAm8Bp6PaF6qaHpSuvfQboGT3n6NQmgexTpEc6jPWHweqLy"
# rollup config file url link
ROLLUP_CONFIG_URL="https://operator-public.s3.us-west-2.amazonaws.com/iost/mainnet/rollup.json"
# tx forward endpoint
SEQUENCER_HTTP=http://l2-mainnet.iost.io
# op-l2 geth bootnodes
GETH_STATICNODES='[
"enode://15d0286e96b4ace7f1dcd572b29b5429dac5e63cbb7df8da3718602a50e55e694dab55c1556c5572783a4d9a2afc31adcf9be8f0e57c2334c6a4836010ece8e9@iost-mainnet-bootnodes.altlayer.network:31850",
"enode://95e995240594ec6e909e68d9bba3d430d40df451b0e593bc738aed88fe1380298d094223a19752f3311d946f6f7c6d6fe347e8f1404d022a07c40e3e83cc5c97@iost-mainnet-bootnodes.altlayer.network:31852",
"enode://505fcae458eb2cb8bda6a7c98c815ffaa3fd6527931515036e949f1bc30e14b1c01802e0a21904faac4cf07d106b1ab9c9ab99fbe91079483faa580edb8ed11c@iost-mainnet-bootnodes.altlayer.network:31854"
]'
# genesis file url link
GENESIS_URL="https://operator-public.s3.us-west-2.amazonaws.com/iost/mainnet/genesis.json"
# sync mode(full or snap)
SYNC_MODE=full
# gc mode(archive or full)
GC_MODE=archive
# geth http api
HTTP_API="web3,eth,net,engine"
# geth ws api
WS_API="web3,eth,net,engine"
# plasma enabled(true or false)
# beacon url
L1_BEACON="https://bsc-dataseed.bnbchain.org"
a. If it's a new node, please read README.md
to generate jwt.txt
before you start your fullnode. If you already have one, skip this step.
b. Copy the new .env
into the root directory in this repo
i. Please remember to set the value for `L1_RPC`
ii. And `L1_BEACON` if your rollup is using BSC DA. Normally we already set a default beacon endpoint in `.env`
c. Modify other parameters based on your requirements, like:
i. Sync mode: `SYNC_MODE`
ii. HTTP/WS RPC models: `HTTP_API` and `WS_API`
iii. Other configuration options as needed
d. Start fullnode:
bash
docker compose up -d
- Then our script will register new bootnodes directly and launch your fullnode.
Run with Binaries
Building op-node and op-geth
Dependencies: golang 1.21+, make, git, gcc, libc-dev
bash
export OPIOST_WORKSPACE=/data/opiost-node
mkdir -p $OPIOST_WORKSPACE
cd $OPIOST_WORKSPACE
git clone https://github.com/iost-official/opiost.git
cd opiost/op-node
git checkout v0.5.2 # Use specific version v0.5.2
make op-node
mkdir -p $OPIOST_WORKSPACE/op-node-data
cp ./bin/op-node $OPIOST_WORKSPACE/op-node-data
cd $OPIOST_WORKSPACE
git clone https://github.com/iost-official/op-geth.git
cd op-geth
git checkout v0.5.6 # Use specific version v0.5.6
make geth
mkdir -p $OPIOST_WORKSPACE/op-geth-data
cp ./build/bin/geth $OPIOST_WORKSPACE/op-geth-data/op-geth
Data Preparation
bash
cd $OPIOST_WORKSPACE
# Generate JWT key
openssl rand -hex 32 > jwt.txt
cp jwt.txt $OPIOST_WORKSPACE/op-geth-data
cp jwt.txt $OPIOST_WORKSPACE/op-node-data
# Initialize op-geth datadir
cd $OPIOST_WORKSPACE/op-geth-data
mkdir datadir
Starting Components
op-geth startup script
Create file: $OPIOST_WORKSPACE/start-geth.sh
bash
#!/bin/bash
set -e
export OPIOST_WORKSPACE="/data/opiost-node"
cd $OPIOST_WORKSPACE/op-geth-data
export SEQUENCER_HTTP="http://l2-mainnet.iost.io"
export GENESIS_URL="https://operator-public.s3.us-west-2.amazonaws.com/iost/mainnet/genesis.json"
export SYNC_MODE="full"
export GC_MODE="archive" #or full
export HTTP_API="web3,eth,net,engine"
export WS_API="web3,eth,net,engine"
export CHAIN_ID=182
if [ ! -d "./datadir/geth" ]; then
echo "Initializing geth datadir"
wget -O ./genesis.json $GENESIS_URL
./op-geth init --state.scheme=hash --datadir=./datadir ./genesis.json
else
echo "geth datadir already initialized, skipping..."
fi
./op-geth \
--datadir=./datadir \
--rollup.disabletxpoolgossip=true \
--rollup.sequencerhttp=$SEQUENCER_HTTP \
--http \
--http.corsdomain="*" \
--http.vhosts="*" \
--http.addr=0.0.0.0 \
--http.port=8545 \
--http.api=$HTTP_API \
--ws \
--ws.addr=0.0.0.0 \
--ws.port=8546 \
--ws.origins="*" \
--ws.api=$WS_API \
--authrpc.addr=0.0.0.0 \
--authrpc.vhosts="*" \
--authrpc.port=8551 \
--authrpc.jwtsecret=./jwt.txt \
--networkid=$CHAIN_ID \
--metrics \
--metrics.port=6060 \
--metrics.addr="0.0.0.0" \
--syncmode=$SYNC_MODE \
--gcmode=$GC_MODE \
--maxpeers=100 \
--config=./config.toml \
--verbosity=3
TIP
op-geth runs with PBSS (Path-Base Scheme Storage) and PebbleDB by adding the flags --state.scheme path
and --db.engine pebble
. It's recommended to start a new node with this mode, which provides better performance and less disk space usage.
To start a fast node, you can add the --allow-insecure-no-tries
flag, but the gcmode should be full.
op-node startup script
Create file: $OPIOST_WORKSPACE/start-node.sh
bash
#!/bin/bash
set -e
export OPIOST_WORKSPACE="/data/opiost-node"
cd $OPIOST_WORKSPACE/op-node-data
export L1_RPC="https://bsc-dataseed.binance.org"
export L1_RPC_KIND="standard"
export L2_RPC="http://localhost:8551"
export P2P_STATIC="/dns/iost-mainnet-bootnodes.altlayer.network/tcp/31851/p2p/16Uiu2HAm2Gb2TgbP1N2o5QwVfqdJdsKtCVud2rNEY8U6FrGnuvwt,/dns/iost-mainnet-bootnodes.altlayer.network/tcp/31853/p2p/16Uiu2HAmQgjpWtJpuCt4HEyktismfHnmcuYe984C2w9bHr8WSM2Z,/dns/iost-mainnet-bootnodes.altlayer.network/tcp/31855/p2p/16Uiu2HAm8Bp6PaF6qaHpSuvfQboGT3n6NQmgexTpEc6jPWHweqLy"
export ROLLUP_CONFIG_URL="https://operator-public.s3.us-west-2.amazonaws.com/iost/mainnet/rollup.json"
export L1_BEACON="https://bsc-dataseed.bnbchain.org"
if [ ! -f "./rollup.json" ]; then
echo "Downloading rollup config..."
wget -O ./rollup.json $ROLLUP_CONFIG_URL
fi
mkdir -p ./opnode_discovery_db
mkdir -p ./opnode_peerstore_db
mkdir -p ./safedb
./op-node \
--syncmode=execution-layer \
--rollup.config=./rollup.json \
--safedb.path=./safedb \
--l1.trustrpc \
--l1=$L1_RPC \
--l1.rpckind=$L1_RPC_KIND \
--l2=$L2_RPC \
--l2.jwt-secret=./jwt.txt \
--metrics.enabled \
--metrics.port=7300 \
--metrics.addr="0.0.0.0" \
--rpc.addr=0.0.0.0 \
--rpc.port=8547 \
--p2p.static=$P2P_STATIC \
--p2p.listen.ip=0.0.0.0 \
--p2p.listen.tcp=9003 \
--p2p.listen.udp=9003 \
--p2p.discovery.path=./opnode_discovery_db \
--p2p.peerstore.path=./opnode_peerstore_db \
--p2p.priv.path=./opnode_p2p_priv.txt \
--l1.beacon=$L1_BEACON \
--verifier.l1-confs=15 \
--sequencer.l1-confs=15 \
--log.level=info
Node Type Comparison
Parameter/Feature | Fast Node | Full Node | Archive Node |
---|---|---|---|
Key Command Parameters | --syncmode=full --allow-insecure-no-tries --gcmode=full --state.scheme=path --db.engine=pebble | --syncmode=full --gcmode=full --state.scheme=path --db.engine=pebble | --syncmode=full --gcmode=archive --state.scheme not available--db.engine=pebble optional |
State Data Storage | Current state only | Recent 128 blocks | All historical blocks |
MPT States | None | Yes | Yes |
Disk Usage | Lowest | Medium | Highest |
Sync Speed | Fastest | Medium | Slowest |
Security | Lower | High | High |
Historical State Queries | Not supported | Recent 128 blocks | Fully supported |
PBSS Compatibility | Supported | Supported | Not supported |
Use Cases | Basic RPC services High performance needs Resource-constrained environments | Validator nodes General applications Balance of resources and functionality | Block explorers Historical data analysis Complete state access |
Command Examples for Different Node Types
Fast Node Command
bash
./op-geth \
--datadir="./datadir" \
--syncmode=full \
--state.scheme=path \
--db.engine=pebble \
--allow-insecure-no-tries \
--gcmode=full \
# other parameters...
Full Node Command
bash
./op-geth \
--datadir="./datadir" \
--syncmode=full \
--state.scheme=path \
--db.engine=pebble \
--gcmode=full \
# other parameters...
Archive Node Command
bash
./op-geth \
--datadir="./datadir" \
--syncmode=full \
--gcmode=archive \
# other parameters...
Setting Up System Services
Setting script execution permissions
bash
chmod +x $OPIOST_WORKSPACE/start-geth.sh
chmod +x $OPIOST_WORKSPACE/start-node.sh
op-geth service
bash
sudo tee /etc/systemd/system/op-geth.service > /dev/null << EOF
[Unit]
Description=OPIOST Geth Service
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=10
User=$USER
ExecStart=$OPIOST_WORKSPACE/start-geth.sh
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
EOF
op-node service
bash
sudo tee /etc/systemd/system/op-node.service > /dev/null << EOF
[Unit]
Description=OPIOST Node Service
After=network.target op-geth.service
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=10
User=$USER
ExecStart=$OPIOST_WORKSPACE/start-node.sh
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
EOF
Starting services
bash
# Reload systemd
sudo systemctl daemon-reload
# Start services
sudo systemctl start op-geth
# Wait for op-geth to start for a few minutes before starting op-node
sleep 180
sudo systemctl start op-node
# Set to start on boot
sudo systemctl enable op-geth
sudo systemctl enable op-node
Checking Status
Wait for the node to sync. You'll see logs in op-geth if there are new blocks.
INFO [11-15|10:10:05.569] Syncing beacon headers downloaded=1,762,304 left=11,403,991 eta=27m1.039s
INFO [11-15|10:10:06.440] Forkchoice requested sync to new head number=13,164,499 hash=d78cb3..a2e94d finalized=unknown
You can check the block height with curl:
bash
$ curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' http://localhost:8545
Once all block headers have been downloaded, the node will start downloading blocks. You'll notice the block height increasing.
{"jsonrpc":"2.0","id":1,"result":"0x1a"}
To verify if the node has synced to the latest height, you can compare the block with the one requested from public endpoints.
bash
# Local
$ curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","id": 1, "params": ["0x1a", false]}' http://localhost:8545
# Mainnet
$ curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","id": 1, "params": ["0x1a", false]}' https://l2-mainnet.iost.io
Troubleshooting
Not synced for a long time
The default sync mechanism involves two P2P networks, the op-node network and op-geth network. If you are not connected to the op-node network, you cannot receive the latest blocks from broadcast, and can't trigger the engine sync of op-geth. If you are not connected to the op-geth network, you can receive the latest blocks from broadcast, but can't get the historical blocks from op-geth P2P network.
Check the op-geth logs.
If you can find the following logs, it means that the op-node network is connected successfully and you are receiving the latest blocks from broadcast.
INFO [11-15|10:32:02.801] Forkchoice requested sync to new head number=8,290,596 hash=1dbff3..9a306a finalized=unknown
If you can find the following logs, it means that the op-geth network is connected successfully and you are receiving the historical block headers from op-geth P2P network.
INFO [11-15|10:32:52.240] Syncing beacon headers downloaded=210,432 left=8,084,773 eta=31m39.748s
Check the op-node p2p network with the command below:
bash
$ curl -X POST -H "Content-Type: application/json" --data \
'{"method":"opp2p_peers","params":[true],"id":1,"jsonrpc":"2.0"}' \
http://localhost:8547
Check the op-geth p2p network with the command below. You have to enable admin API in op-geth to use this API.
bash
$ curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"admin_peers","params":[],"id":1}' http://localhost:8545 | jq .
The local node's chain has forked from the canonical chain
If your local node is already running and tracking blocks, the following situations may indicate that your local node's chain has forked from the canonical chain:
- The block hash at the same height obtained through the eth_getBlockByNumber method does not match the data returned by the public node.
- Your local chain consistently lags behind a fixed number of blocks and cannot catch up with the latest block height.
In this case, we recommend that you check the code version of the running node through the following steps:
bash
$ op-node -v
op-node version v0.0.0-515ebd51-1698742099
$ op-geth version
Geth
Version: 0.1.0-unstable
Git Commit: f8871fc80dbf2aa0178b504e76c20c21b890c6d5
Git Commit Date: 20231026
Upstream Version: 1.11.5-stable
Architecture: arm64
Go Version: go1.20.2
Operating System: darwin
GOPATH=
Please make sure to use the latest code version. If the code version is incorrect, please completely clear the node data and run the new node again according to this guide. You also need to check if the genesis.json and rollup.json files are up to date.
Node block is stuck and op-geth log prints: Database compacting, degraded performance
If your node suddenly gets stuck and cannot grow, and your op-geth log keeps printing: Database compacting, degraded performance database=/data/geth/chaindata, then you need to consider upgrading your machine specifications, increasing CPU, memory, and disk maximum throughput.
This is because the current OP Stack only supports the archive mode of Geth, and the disk space usage increases over time. The Leveldb that Geth relies on requires more machine resources to complete the compact process. The latest version supports PBSS and Pebble to solve this problem.
If you are an advanced user, you can also try to perform offline pruning on your nodes (Note that this is a dangerous operation and after pruning, the state data of the blocks before the target block height will no longer be available). You can follow these steps:
- Shut down your machine and make sure that op-geth prints the following log: "Blockchain stopped".
- Search for the keyword "Chain head was updated" in the logs to confirm the block height of the last inserted block before the node was shut down. For example, let's assume the block height is 16667495.
- Wait for 16667495 to become the final state on the chain, ensuring that no reorganization has occurred.
- Get the state root hash of this block height through JSON-RPC.
- To execute pruning, use the following command: geth snapshot prune-state --datadir {yourDataDir} --triesInMemory=32
- Be patient and observe the logs. The entire process may take dozens of hours.
- Restart your node after pruning is complete.
WARNING
Pruning is a very dangerous operation that may damage the node's data. This could result in having to rerun a new node. Only perform this operation if you are familiar with the process.