Node Setup
This guide sets up a podseq sequencer node: Reth, Walrus access, a signer key, and p2p identity.
Prerequisites#
- Rust stable (
rust-toolchain.tomlpins it) - Reth running in L2 mode (Engine API on
localhost:8551) - Access to a Walrus publisher and aggregator (Testnet endpoints are built in)
- A funded Sui address for settlement (SUI for gas)
1. Reth (execution layer)#
Generate a shared JWT secret:
openssl rand -hex 32 > jwt.hexStart Reth with --authrpc.jwtsecret jwt.hex on its Engine API port (8551). Both
Reth and podseq must use the same jwt.hex.
2. Keys#
Signer key#
A single Ed25519 suiprivkey (Bech32) serves double duty: it signs settlement
transactions on Sui and block headers. Generate it with the Sui CLI:
sui keytool generate ed25519Or use podseq:
podseq keyring generate-key --out sequencer.keySave the suiprivkey... string to a file (e.g. sequencer.key). The corresponding
address needs SUI for gas.
P2P identity key#
Authenticated peer connections use a Commonware ed25519 key.
Bridge relayer key (optional)#
The enshrined bridge (bridge) mints on L2 for Sui deposits and releases on Sui for L2 burns. It needs a separate secp256k1 EVM key (the L2 uses EVM signatures; Sui uses Ed25519 so they can't share a key). Generate it with podseq:
podseq keyring generate-evm-key --out relayer.keyThe printed address must be set as the L2 Bridge relayer (via the contract's
one-time initialize) and funded with L2 gas.
3. podseq configuration#
Generate a starting config:
podseq init config --out podseq.tomlEdit it:
[reth]
engine_url = "http://localhost:8551"
# rpc_url = "http://localhost:8545" # mempool queries (defaults to 8545)
jwt_path = "jwt.hex"
[walrus]
publisher_url = "https://publisher.walrus-testnet.walrus.space"
aggregator_url = "https://aggregator.walrus-testnet.walrus.space"
# epochs = 53 # storage lifetime; default = max (53 ≈ 2 years)
# batch_size_bytes = 65536 # flush a DA blob once blocks reach this size
[sui]
rpc_url = "https://fullnode.testnet.sui.io:443"
# settlement_package_id is only needed by the sequencer (for committing blocks).
[p2p]
key_path = "p2p.key"
listen_addr = "0.0.0.0:9000"
# bootstrap_peers = ["abc123@1.2.3.4:9000"] # optional
[signer]
# Sequencer mode: private key (suiprivkey) that signs settlement + block headers.
key_path = "sequencer.key"
# Full node mode: the sequencer's ed25519 public key (hex) to verify blocks.
# Required in full node mode; ignored in sequencer mode.
# sequencer_pubkey = "0x..."
# Bridge (optional, sequencer mode only). On first start, if cap_id/vault_id are
# unset, the sequencer calls bridge::initialize and writes them back here.
# [bridge]
# enabled = true
# l2_relayer_key_path = "relayer.key"A minimal sequencer config only needs [reth] jwt_path and [signer] key_path.
Everything else defaults to Testnet. Full node mode requires
[signer] sequencer_pubkey instead of key_path (see Full node).
4. Run#
podseq start --config podseq.tomlThe node loads the JWT, connects to Reth, builds the Sui-layer client, starts the p2p network, and begins the sequencer loop. On startup the sequencer prints its derived address and public key in a banner:
╔════════════════════════════════════════════════════════════════╗
║ SEQUENCER ADDRESS: 0x...
║ sequencer pubkey: 0x...
╚════════════════════════════════════════════════════════════════╝Copy the sequencer pubkey value into the signer.sequencer_pubkey field of any
full node that should verify this sequencer's blocks.
Full node#
A full node reconstructs the chain from DA + settlement, verifies every block signature against the sequencer's public key, and re-executes blocks against a local Reth. Only the sequencer produces blocks.
Set mode = "full" in the config.
Minimal full node config#
mode = "full"
[reth]
engine_url = "http://localhost:8551"
jwt_path = "jwt.hex"
[signer]
# The sequencer's ed25519 public key (hex).
sequencer_pubkey = "0x..."
[sui]
rpc_url = "https://fullnode.testnet.sui.io:443"
registry_id = "0x..." # the shared Registry objectThe node refuses to start if sequencer_pubkey or registry_id is missing.
Full-node mode only needs [reth], [signer] sequencer_pubkey, and
[sui] registry_id — signer.key_path, sui.settler_cap_id,
sui.settlement_package_id, and walrus.publisher_url are sequencer-only.
Fast-sync (optional)#
Add a [p2p] section and point bootstrap_peers at the
sequencer to receive block announcements ahead of DA finality:
[p2p]
key_path = "p2p.key"
listen_addr = "0.0.0.0:9001"
bootstrap_peers = ["<sequencer-pubkey-hex>@<seq-host>:9000"]p2p blocks are applied as an optimistic head only; they are never marked
safe or finalized. Applying and finalizing are separate flows: a block's
payload is submitted to Reth once (via p2p, or via DA if there was no p2p), and
finality only advances the safe/finalized fork-choice pointers. When DA
settles a height already applied via p2p, the block is just finalized (the DA
block hash is checked against Reth's block at that height; only a mismatch
re-applies and reorgs). Without p2p the node still syncs purely from DA +
settlement, at finality lag.
Sync behavior#
- The DA sync cursor (
synced_height) and the last DA-verified fork-choice hashes (head/safe/finalized) are persisted todata_dirand resumed on restart, so the node skips re-reading the head from Reth. On first run (no state yet) it discovers the head from Reth instead. Only DA-settled blocks advance the cursor or the persisted state; optimistic p2p heads are not persisted. - A block whose signature fails verification is rejected and sync halts with an error.
Docker Compose#
The recommended way to run a local dev node is with Docker Compose:
docker compose \
-f docker-compose.yml -f docker-compose.testnet.yml up -dThis starts Reth, podseq, a Blockscout block explorer (http://localhost:4000),
and an optional development faucet (http://localhost:8080, requires FAUCET_PRIVATE_KEY).
Both are preconfigured for local development. To disable either, comment out the
relevant services in docker-compose.yml (explorer) or docker-compose.testnet.yml
(faucet).
Troubleshooting#
| Symptom | Fix |
|---|---|
loading JWT secret ... failed |
jwt.hex must be a 32-byte hex string shared with Reth. |
no signer key configured |
Add signer.key_path (a suiprivkey file) in sequencer mode. |
sequencer_pubkey is required ... |
Full node mode needs signer.sequencer_pubkey (from the sequencer banner). |
sui.registry_id is required ... |
Full node mode needs the shared Registry object ID (from the sequencer config). |
no settlement_package_id configured |
Sequencer mode requires settlement: deploy the Move package and set sui.settlement_package_id, sui.settler_cap_id, and sui.registry_id (or leave all three unset for first-start auto-deploy). |
p2p is not configured |
Add a [p2p] section to config. The identity key is auto-generated on first start. |