diff --git a/selfhost/.env.example b/selfhost/.env.example new file mode 100644 index 00000000..4674e4f6 --- /dev/null +++ b/selfhost/.env.example @@ -0,0 +1,95 @@ +# ── Core ────────────────────────────────────────────────────────────────────── +# Directory on the host where all persistent data is stored +DATA_DIR=./data + +# Snapshots speed up initial sync by pre-populating data directories. +# You can provide one or both. start.sh downloads and extracts them before +# starting any containers. Each is skipped if its target directory is non-empty. + +# URL to a snapshot archive (.tar or .tar.gz) for the mchain data directory +# MCHAIN_SNAPSHOT_URL=https:// + +# URL to a snapshot archive (.tar or .tar.gz) for the Nitro data directory +# NITRO_SNAPSHOT_URL=https:// + +# Your appchain's chain ID +APPCHAIN_CHAIN_ID= + +# Image versions (override to pin to a specific release) +# SYND_APPCHAINS_VERSION=v1.0.12 +# SYND_NITRO_VERSION=eigenda-v3.7.6 + +# ── Ingestors (WebSocket) ────────────────────────────────────────────────────── +# WebSocket RPC URL(s) for the sequencing chain (comma-separated for redundancy) +SEQUENCING_INGESTOR_WS_URLS=wss:// + +# Block number to start ingesting from on the sequencing chain +SEQUENCING_INGESTOR_START_BLOCK=0 + +# WebSocket RPC URL(s) for the settlement chain (comma-separated for redundancy) +SETTLEMENT_INGESTOR_WS_URLS=wss:// + +# Block number to start ingesting from on the settlement chain +SETTLEMENT_INGESTOR_START_BLOCK=0 + +# ── Parent Chains Contracts ───────────────────────────────────────────────── +# Address of the sequencing contract on the sequencing chain (used by translator & batch sequencer) +SEQUENCING_CONTRACT_ADDRESS=0x + +# Arbitrum bridge and inbox contract addresses on the settlement chain +ARBITRUM_BRIDGE_ADDRESS=0x +ARBITRUM_INBOX_ADDRESS=0x + +# ── Batch Sequencer ──────────────────────────────────────────────────────────── +# Private key of the wallet that signs and submits transaction batches +BATCHER_PRIVATE_KEY=0x + +# HTTP RPC URL(s) for the sequencing chain (comma-separated) +SEQUENCING_RPC_URLS=https:// + +# ── Nitro ────────────────────────────────────────────────────────────────────── +# Chain info JSON blob consumed by the Nitro node (NITRO__ double-underscore prefix notation) +NITRO_CHAIN_INFO__JSON= + +# ── Translator ───────────────────────────────────────────────────────────────────── +TRANSLATOR_SETTLEMENT_START_BLOCK=0 +TRANSLATOR_SEQUENCING_START_BLOCK=0 + +# ── Proposer ─────────────────────────────────────────────────────────────────── +# Private key used by the proposer to submit state commitments on-chain (WITHOUT 0x prefix) +PROPOSER_PRIVATE_KEY= + +# Ethereum L1 RPC URL (used by the proposer to verify attestations) +ETHEREUM_RPC_URL=https:// + +# Settlement chain RPC URL and chain ID +SETTLEMENT_RPC_URL=https:// +SETTLEMENT_CHAIN_ID= + +# HTTP RPC URL for the sequencing chain node (used by the proposer) +SEQUENCING_RPC_URL=https:// + +# Beacon chain RPC URL (used for blob fetching) +BEACON_RPC_URL=https:// + +# URL of the TEE enclave that produces signed state commitments +ENCLAVE_RPC_URL=https:// + +# Contract addresses on the settlement chain +TEE_MODULE_CONTRACT_ADDRESS=0x +APPCHAIN_BRIDGE_ADDRESS=0x +SEQUENCING_BRIDGE_ADDRESS=0x + + +# ── MChain ───────────────────────────────────────────────────────────────────── +# ── Migration (only needed for chains migrated from vanilla orbit) ────────── +# MCHAIN_GENESIS_CONFIG= +# MIGRATED_SETTLEMENT_START_BLOCK=0 +# MIGRATED_BEFORE_BATCH_ACC=0x +# MIGRATED_BATCH_ACC=0x +# MIGRATED_BATCH_COUNT=0 +# MIGRATED_DELAYED_MSGS_ACC=0x +# MIGRATED_DELAYED_MSGS_COUNT=0 + +# ── Logging ──────────────────────────────────────────────────────────────────── +RUST_LOG=info diff --git a/selfhost/.gitignore b/selfhost/.gitignore new file mode 100644 index 00000000..0c54f604 --- /dev/null +++ b/selfhost/.gitignore @@ -0,0 +1,2 @@ +data/ +.env diff --git a/selfhost/docker-compose.yml b/selfhost/docker-compose.yml new file mode 100644 index 00000000..81d28b8d --- /dev/null +++ b/selfhost/docker-compose.yml @@ -0,0 +1,167 @@ +services: + + valkey: + image: valkey/valkey:9.0.3-alpine + container_name: valkey + command: ["valkey-server", "--save", "60", "1", "--loglevel", "warning"] + volumes: + - ${DATA_DIR:-./data}/valkey:/data + restart: unless-stopped + + maestro: + image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-maestro:${SYND_APPCHAINS_VERSION:-v1.0.12} + container_name: maestro + environment: + - VALKEY_URL=redis://valkey:6379 + - CHAIN_RPC_URLS={"${APPCHAIN_CHAIN_ID}":["http://nitro:8547"]} + - SKIP_BALANCE_CHECK=true + - RUST_LOG=${RUST_LOG:-info} + depends_on: + - valkey + restart: unless-stopped + + batch_sequencer: + image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-batch-sequencer:${SYND_APPCHAINS_VERSION:-v1.0.12} + container_name: batch_sequencer + environment: + - VALKEY_URL=redis://valkey:6379 + - CHAIN_ID=${APPCHAIN_CHAIN_ID} + - SEQUENCING_ADDRESS=${SEQUENCING_CONTRACT_ADDRESS} + - BATCHER_PRIVATE_KEY=${BATCHER_PRIVATE_KEY} + - SEQUENCING_RPC_URLS=${SEQUENCING_RPC_URLS} + - RUST_LOG=${RUST_LOG:-info} + depends_on: + - valkey + - maestro + restart: unless-stopped + + settlement_ingestor: + image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-chain-ingestor:${SYND_APPCHAINS_VERSION:-v1.0.12} + container_name: settlement_ingestor + command: + - "--db-file=/data/settlement.db" + - "--start-block=${SETTLEMENT_INGESTOR_START_BLOCK:-0}" + - "--ws-urls=${SETTLEMENT_INGESTOR_WS_URLS}" + environment: + - RUST_LOG=${RUST_LOG:-info} + volumes: + - ${DATA_DIR:-./data}/settlement:/data + restart: unless-stopped + + sequencing_ingestor: + image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-chain-ingestor:${SYND_APPCHAINS_VERSION:-v1.0.12} + container_name: sequencing_ingestor + command: + - "--db-file=/data/sequencing.db" + - "--start-block=${SEQUENCING_INGESTOR_START_BLOCK:-0}" + - "--ws-urls=${SEQUENCING_INGESTOR_WS_URLS}" + environment: + - RUST_LOG=${RUST_LOG:-info} + volumes: + - ${DATA_DIR:-./data}/sequencing:/data + restart: unless-stopped + + mchain: + image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-mchain:${SYND_APPCHAINS_VERSION:-v1.0.12} + container_name: mchain + command: + - "--datadir=/data" + environment: + - APPCHAIN_CHAIN_ID=${APPCHAIN_CHAIN_ID} + - GENESIS_CONFIG + - SNAPSHOT_URL + - SETTLEMENT_START_BLOCK + - MIGRATED_BEFORE_BATCH_ACC + - MIGRATED_BATCH_ACC + - MIGRATED_BATCH_COUNT + - MIGRATED_DELAYED_MSGS_ACC + - MIGRATED_DELAYED_MSGS_COUNT + - RUST_LOG=${RUST_LOG:-info} + volumes: + - ${DATA_DIR:-./data}/mchain:/data + restart: unless-stopped + + translator: + image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-translator:${SYND_APPCHAINS_VERSION:-v1.0.12} + container_name: translator + environment: + - SETTLEMENT_WS_URL=ws://settlement_ingestor:8545 + - SEQUENCING_WS_URL=ws://sequencing_ingestor:8545 + - APPCHAIN_CHAIN_ID=${APPCHAIN_CHAIN_ID} + - SEQUENCING_CONTRACT_ADDRESS=${SEQUENCING_CONTRACT_ADDRESS} + - ARBITRUM_BRIDGE_ADDRESS=${ARBITRUM_BRIDGE_ADDRESS} + - ARBITRUM_INBOX_ADDRESS=${ARBITRUM_INBOX_ADDRESS} + - SETTLEMENT_START_BLOCK=${TRANSLATOR_SETTLEMENT_START_BLOCK:-} + - SEQUENCING_START_BLOCK=${TRANSLATOR_SEQUENCING_START_BLOCK:-} + - SETTLEMENT_DELAY=60 + - MCHAIN_WS_URL=ws://mchain:8545 + - RUST_LOG=${RUST_LOG:-info} + depends_on: + - settlement_ingestor + - sequencing_ingestor + - mchain + restart: unless-stopped + + proposer: + image: ghcr.io/syndicateprotocol/syndicate-appchains/synd-proposer:${SYND_APPCHAINS_VERSION:-v1.0.12} + container_name: proposer + environment: + - APPCHAIN_RPC_URL=http://nitro:8547 + - PRIVATE_KEY=${PROPOSER_PRIVATE_KEY} + - ETHEREUM_RPC_URL=${ETHEREUM_RPC_URL} + - SETTLEMENT_RPC_URL=${SETTLEMENT_RPC_URL} + - SETTLEMENT_CHAIN_ID=${SETTLEMENT_CHAIN_ID} + - SEQUENCING_RPC_URL=${SEQUENCING_RPC_URL} + - BEACON_RPC_URL=${BEACON_RPC_URL:-} + - ENCLAVE_RPC_URL=${ENCLAVE_RPC_URL} + - TEE_MODULE_CONTRACT_ADDRESS=${TEE_MODULE_CONTRACT_ADDRESS} + - APPCHAIN_BRIDGE_ADDRESS=${APPCHAIN_BRIDGE_ADDRESS} + - SEQUENCING_BRIDGE_ADDRESS=${SEQUENCING_BRIDGE_ADDRESS} + - SEQUENCING_CONTRACT_ADDRESS=${SEQUENCING_CONTRACT_ADDRESS} + - MTLS_ENABLED_ENCLAVE=false + - EIGEN_RPC_URL="noop.com" + depends_on: + - nitro + restart: unless-stopped + + nitro: + image: ghcr.io/syndicateprotocol/nitro/nitro:${SYND_NITRO_VERSION:-eigenda-v3.7.6} + container_name: nitro + init: true + restart: unless-stopped + environment: + - NITRO_CHAIN_INFO__JSON=${NITRO_CHAIN_INFO__JSON} + command: + - "--conf.env-prefix=NITRO" + - "--init.validate-checksum=false" + - "--parent-chain.connection.url=ws://mchain:8545" + - "--parent-chain.id=511000" + - "--chain.id=${APPCHAIN_CHAIN_ID}" + - "--http.api=arb,eth,net,web3,txpool,arbtrace,debug,synd" + - "--ws.api=arb,eth,net,web3,txpool,arbtrace,debug,synd" + - "--http.addr=0.0.0.0" + - "--http.port=8547" + - "--http.vhosts=*" + - "--http.corsdomain=*" + - "--ws.addr=0.0.0.0" + - "--ws.port=8548" + - "--ws.origins=*" + - "--node.dangerous.disable-blob-reader=true" + - "--node.inbox-reader.check-delay=100ms" + - "--node.staker.enable=false" + - "--node.parent-chain-reader.poll-interval=100ms" + - "--node.parent-chain-reader.old-header-timeout=2540400h" + - "--execution.parent-chain-reader.old-header-timeout=2540400h" + - "--execution.caching.archive=true" + - "--execution.forwarding-target=http://maestro:8080" + - "--ensure-rollup-deployment=false" + - "--persistent.chain=/home/user/data/" + # uncomment to test the setup on arm64 MacOs machine + # - "--execution.stylus-target.amd64=x86_64-linux-unknown+sse4.2+lzcnt" + ports: + - "8545:8547" + - "8548:8548" + volumes: + - ${DATA_DIR:-./data}/nitro:/home/user/data + depends_on: + - mchain diff --git a/selfhost/readme.md b/selfhost/readme.md new file mode 100644 index 00000000..185a3a1b --- /dev/null +++ b/selfhost/readme.md @@ -0,0 +1,103 @@ +# Self-Hosting a Syndicate Appchain Node + +Run your own Syndicate appchain RPC node with Docker Compose. + +## Prerequisites + +- [Docker](https://docs.docker.com/get-docker/) with the Compose plugin (v2.20+) +- `curl` and `tar` (for snapshot download) +- Disk space: 1 TB+ recommended +- RAM: 16 GB+ recommended +- CPU: 4+ cores recommended +- Ports `8545` (HTTP RPC) and `8548` (WebSocket RPC) available on the host +- WebSocket RPC access to your sequencing chain and settlement chain (e.g. via Alchemy, Infura, or a self-hosted node) + +> [!NOTE] +> For synd-TEE withdrawals to be functional, you'll need to set up a synd-enclave on an AWS Nitro TEE instance and provide that URL as `ENCLAVE_RPC_URL`. See the [TEE enclave setup guide](https://docs.syndicate.io/en/docs/syndicate-stack/guides/run-withdrawals-infra#run-synd-enclave-in-aws-tee) for instructions. + +## Setup + + +### 1. Copy the env template + +```bash +cp .env.example .env +``` + +### 2. Fill in `.env` + +Open `.env` and supply your chain-specific values. +All initial values for your specific appchain can be provided by the Syndicate team. + + +> [!WARNING] +> `BATCHER_PRIVATE_KEY` and `PROPOSER_PRIVATE_KEY` must have funds on the sequencing / settlement chains respectively. Additionally, the BATCHER must be authorized to sequence on the sequencing contract. + +To change the location where data is persisted, set `DATA_DIR` in your `.env` before running `start.sh`. + +### 3. Start + +```bash +bash start.sh +``` + +The script will: +1. Create local data directories under `DATA_DIR` (default: `./data`) +2. Download and extract the nitro snapshot if `NITRO_SNAPSHOT_URL` is set +3. Start all services with `docker compose` (the mchain container handles its own snapshot download via `MCHAIN_SNAPSHOT_URL` if set) + +## Verify + +Check that all containers are running: + +```bash +docker compose ps +``` + +Wait for the mchain and nitro to finish syncing, then test the RPC: + +```bash +curl -s -X POST http://localhost:8545 \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' +``` + +WebSocket RPC is also available at `ws://localhost:8548`. + +## Common commands + +```bash +# Follow logs for all services +docker compose logs -f + +# Follow logs for a specific service +docker compose logs -f nitro + +# Stop all services +docker compose down + +# Stop and remove all persisted data (bind-mounted) +docker compose down && rm -rf ${DATA_DIR:-./data} + +# Restart a single service +docker compose restart mchain +``` + +## Data directory layout + +``` +./data/ +├── mchain/ # RocksDB state for the intermediate chain node +├── nitro/ # Nitro node state +├── sequencing/ # filesystem cache for the sequencing ingestor +├── settlement/ # filesystem cache for the settlement ingestor +└── valkey/ # Valkey (Redis-compatible) persistence +``` + +## Summary + +At this point you should have a functional synd-stack rollup deriving state from the parent chains. +You can assert that the rollup node is synced by checking the `eth_blockNumber` rpc call result. +You should also be able to send new transactions by calling `eth_sendRawTransaction` on the rollup node. +Withdrawals should also be functional, you can assert this by monitoring the `TEEModule` contract for `assertionPosted` and `closeChallengeWindow` events ([example](https://basescan.org/address/0xA61C573986bf21D1B93010c8D50909a6c313Dd61#events)) + diff --git a/selfhost/start.sh b/selfhost/start.sh new file mode 100755 index 00000000..c1e462ab --- /dev/null +++ b/selfhost/start.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Resolve the directory this script lives in so it works from any call site +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# ── Preflight ────────────────────────────────────────────────────────────────── +if [ ! -f .env ]; then + echo "Error: .env file not found." + echo " cp .env.example .env" + echo " # fill in your values, then re-run this script" + exit 1 +fi + +source .env + +DATA_DIR="${DATA_DIR:-./data}" + +# ── Data directories ─────────────────────────────────────────────────────────── +echo "Creating data directories under $DATA_DIR ..." +mkdir -p \ + "$DATA_DIR/valkey" \ + "$DATA_DIR/settlement" \ + "$DATA_DIR/sequencing" \ + "$DATA_DIR/mchain" \ + "$DATA_DIR/nitro/nitro" + +# ── Snapshots ───────────────────────────────────────────────────────────────── +download_snapshot() { + local url="$1" + local dest="$2" + local label="$3" + local marker="$dest/.snapshot_downloaded" + + [ -z "$url" ] && return 0 + + if [ -f "$marker" ]; then + echo "Skipping $label snapshot: already downloaded" + return 0 + fi + + echo "Downloading $label snapshot from $url ..." + curl -L --progress-bar "$url" | tar -xf - -C "$dest" + touch "$marker" + echo "$label snapshot extracted to $dest" +} + +download_snapshot "${NITRO_SNAPSHOT_URL:-}" "$DATA_DIR/nitro/nitro" "nitro" + +# ── Forward optional env vars ────────────────────────────────────────────────── +[ -n "${MCHAIN_GENESIS_CONFIG:-}" ] && export GENESIS_CONFIG="$MCHAIN_GENESIS_CONFIG" +[ -n "${MCHAIN_SNAPSHOT_URL:-}" ] && export SNAPSHOT_URL="$MCHAIN_SNAPSHOT_URL" + +# ── Start services ───────────────────────────────────────────────────────────── +echo "Starting services ..." +docker compose --env-file .env up -d + +echo "" +echo "All services started." +echo "Appchain JSON-RPC will be available at http://localhost:8545 once nitro is ready." +echo "Run 'docker compose logs -f' to follow logs."