Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ plugins/
bin/
network_data/
local-benchmark.tar.gz
local-benchmark-with-blockscout.tar.gz
remote-benchmark.tar.gz
remote-benchmark-with-blockscout.tar.gz
blockscout/images.tar.gz
tmp/
.env
local/blockwatch
Expand Down
131 changes: 131 additions & 0 deletions blockscout/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
## Blockscout

Optional explorer for the benchmark chain. Runs as a 4-container compose stack
(Blockscout backend + frontend, Postgres, Redis) using **podman** by default
and **docker** as a fallback.

The stack can target either:

- a `local` benchmark chain started from this repo
- a `remote` benchmark chain reached over the same RPC URL you use for benchmarking

## What this builds

`scripts/blockscout-pack-images.sh` pulls the four upstream images on a build
machine with internet access and saves them into a single OCI archive at
`blockscout/images.tar.gz`. `make pack-blockscout` (in `local/` or `remote/`)
bundles that archive together with the compose file, helper scripts, and the
existing benchmark payload into one `*-with-blockscout.tar.gz`. On the target
machine, `./blockscout.sh up` runs `podman load -i blockscout/images.tar.gz`
on first call and then starts the compose stack — no registry traffic required.

The local and remote networks expose a dedicated **Archive-RPC** avalanchego
node bound to `0.0.0.0` with `--http-allowed-hosts=*` and `pruning-enabled:
false`. Blockscout queries this node so its container Host headers are
accepted and historical state lookups succeed. The bombard RPC nodes stay on
loopback with the default chain config.

## Online (dev box) flow

```bash
cd local
./bin/startnetwork --exit-on-success
./blockscout.sh up
./blockscout.sh smoke
./blockscout.sh down
```

If you want a one-step convenience flow:

```bash
cd local
./blockscout.sh up --start-local
```

For remote:

```bash
cd remote
./blockscout.sh up
./blockscout.sh smoke
./blockscout.sh down
```

The `remote` wrapper reads `network.env`, picks up the `ARCHIVE_RPC_URL` set
by `03_deploy_l1_config.sh`, and runs the same compose stack against that
endpoint.

## Air-gapped RHEL flow

The deliverable tarball ships the OCI archive inside, so the target machine
needs no registry access.

### 1. On a build machine (with internet)

```bash
# Build the image bundle once. Skip if blockscout/images.tar.gz already exists.
./scripts/blockscout-pack-images.sh

# Then bundle it with the rest of the deliverable. Pick one:
cd local && make pack-blockscout # local-benchmark-with-blockscout.tar.gz
cd remote && make pack-blockscout # remote-benchmark-with-blockscout.tar.gz
```

The image bundle defaults to `linux/amd64`. Override with
`BLOCKSCOUT_PLATFORM=linux/arm64` for ARM RHEL hosts.

### 2. On the air-gapped RHEL target

Prereqs (install from your offline RPM mirror):

```bash
sudo dnf install podman podman-compose
```

Podman 4.7+ is required (RHEL 9.3+ ships 4.7+; RHEL 9.4+ ships 5.x).

Then:

```bash
tar -xzf local-benchmark-with-blockscout.tar.gz
./bin/startnetwork --exit-on-success
./blockscout.sh up # auto-loads images from blockscout/images.tar.gz
./blockscout.sh smoke
./blockscout.sh down
```

`blockscout.sh up` is idempotent: it inspects the local image cache first and
only runs `podman load` when an image is missing.

### Default URLs

- Frontend: `http://127.0.0.1:4001`
- Backend API: `http://127.0.0.1:4000/api/v2/stats`

### Image overrides

Image pins live in [`images.conf`](./images.conf). Override via env vars at
pack time or run time:

```bash
BLOCKSCOUT_BACKEND_IMAGE=ghcr.io/your-org/blockscout-backend:<tag> \
BLOCKSCOUT_FRONTEND_IMAGE=ghcr.io/your-org/blockscout-frontend:<tag> \
./scripts/blockscout-pack-images.sh
```

### Runtime override

Force a specific runtime if both are installed:

```bash
BLOCKSCOUT_RUNTIME=docker ./blockscout.sh up
BLOCKSCOUT_RUNTIME=podman ./blockscout.sh up
```

### Shared scripts

- `scripts/blockscout-pack-images.sh` — pull + save images for offline use
- `scripts/blockscout-up.sh` — bring the stack up (also accepts `--rpc URL`)
- `scripts/blockscout-down.sh` — tear down + cleanup
- `scripts/blockscout-smoke.sh` — basic API/frontend reachability check
- `scripts/_blockscout_runtime.sh` — sourced helper, picks the runtime + compose CLI
152 changes: 152 additions & 0 deletions blockscout/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
services:
db:
image: ${BLOCKSCOUT_POSTGRES_IMAGE}
restart: unless-stopped
command: postgres -c max_connections=500
environment:
POSTGRES_DB: ${BLOCKSCOUT_DB_NAME}
POSTGRES_USER: ${BLOCKSCOUT_DB_USER}
POSTGRES_PASSWORD: ${BLOCKSCOUT_DB_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${BLOCKSCOUT_DB_USER} -d ${BLOCKSCOUT_DB_NAME}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- blockscout

redis:
image: ${BLOCKSCOUT_REDIS_IMAGE}
restart: unless-stopped
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
- redis-data:/data
networks:
- blockscout

backend:
image: ${BLOCKSCOUT_BACKEND_IMAGE}
restart: unless-stopped
command:
- /bin/sh
- -c
- bin/blockscout eval "Elixir.Explorer.ReleaseTasks.create_and_migrate()" && bin/blockscout start
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
extra_hosts:
- "${BLOCKSCOUT_EXTRA_HOST}"
environment:
DATABASE_URL: postgresql://${BLOCKSCOUT_DB_USER}:${BLOCKSCOUT_DB_PASSWORD}@db:5432/${BLOCKSCOUT_DB_NAME}
SECRET_KEY_BASE: ${BLOCKSCOUT_SECRET_KEY_BASE}
ETHEREUM_JSONRPC_VARIANT: geth
ETHEREUM_JSONRPC_HTTP_URL: ${BLOCKSCOUT_RPC_URL_INTERNAL}
ETHEREUM_JSONRPC_WS_URL: ${BLOCKSCOUT_WS_URL_INTERNAL}
# TRACE_URL is intentionally unset for subnet-evm: the chain only allows
# tracing finalized blocks, which races against the realtime indexer
# and causes Blockscout to mark the URL unavailable. We keep
# INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=true so trace isn't
# actually needed for indexing.
ETHEREUM_JSONRPC_HTTP_TIMEOUT: "60"
NETWORK: ${BLOCKSCOUT_CHAIN_NAME}
SUBNETWORK: ${BLOCKSCOUT_CHAIN_SHORT_NAME}
CHAIN_ID: ${BLOCKSCOUT_CHAIN_ID}
COIN: ${BLOCKSCOUT_COIN_SYMBOL}
COIN_NAME: ${BLOCKSCOUT_COIN_NAME}
PORT: "4000"
POOL_SIZE: "50"
ECTO_USE_SSL: "false"
# Indexer config tuned for subnet-evm:
# - No internal-tx / pending-tx fetchers (subnet-evm doesn't expose
# debug_traceTransaction on unfinalized data)
# - FIRST_BLOCK=0 forces catchup to scan from genesis even when the
# realtime indexer hasn't yet reported a chain head
# - INDEXER_REALTIME_FETCHER_USE_HTTP=true polls for new blocks via
# HTTP instead of relying solely on WS newHeads subscription
INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER: "true"
INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER: "true"
INDEXER_REALTIME_FETCHER_USE_HTTP: "true"
FIRST_BLOCK: "0"
# Force the catchup indexer to scan a fixed range. This bypasses
# head-detection failures we've seen with subnet-evm where the realtime
# indexer can't establish a chain head, leaving block_catchup idle. Once
# initial backfill works we can switch this back to head-driven catchup.
INDEXER_BLOCK_RANGES: "0..latest"
BLOCK_TRANSFORMER: base
DISABLE_EXCHANGE_RATES: "true"
DISABLE_WRITE_API: "true"
DISABLE_INDEXER: "false"
DISABLE_REALTIME_INDEXER: "false"
ENABLE_TXS_STATS: "false"
API_RATE_LIMIT_DISABLED: "true"
API_RATE_LIMIT: "50"
API_RATE_LIMIT_BY_IP: "true"
CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD: 1h
CACHE_ADDRESS_TRANSACTIONS_COUNTER_PERIOD: 1h
ACCOUNT_ENABLED: "false"
SHOW_TESTNET_LABEL: "true"
TESTNET_LABEL_TEXT: ${BLOCKSCOUT_CHAIN_SHORT_NAME}
HEALTHY_BLOCKS_PERIOD: "60"
HISTORY_FETCH_INTERVAL: "30"
TXS_HISTORIAN_INIT_LAG: "0"
TXS_STATS_DAYS_TO_COMPILE_AT_INIT: "10"
LOG_LEVEL: info
ports:
- "${BLOCKSCOUT_API_PORT}:4000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000/api/v2/stats"]
interval: 30s
timeout: 10s
retries: 5
start_period: 120s
networks:
- blockscout

frontend:
image: ${BLOCKSCOUT_FRONTEND_IMAGE}
restart: unless-stopped
depends_on:
backend:
condition: service_healthy
environment:
NEXT_PUBLIC_APP_HOST: 127.0.0.1
NEXT_PUBLIC_API_HOST: 127.0.0.1
NEXT_PUBLIC_API_PORT: ${BLOCKSCOUT_API_PORT}
NEXT_PUBLIC_API_PROTOCOL: http
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL: ws
NEXT_PUBLIC_API_BASE_PATH: /
NEXT_PUBLIC_NETWORK_NAME: ${BLOCKSCOUT_CHAIN_NAME}
NEXT_PUBLIC_NETWORK_SHORT_NAME: ${BLOCKSCOUT_CHAIN_SHORT_NAME}
NEXT_PUBLIC_NETWORK_ID: ${BLOCKSCOUT_EVM_CHAIN_ID}
NEXT_PUBLIC_NETWORK_CURRENCY_NAME: ${BLOCKSCOUT_COIN_NAME}
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL: ${BLOCKSCOUT_COIN_SYMBOL}
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS: "18"
NEXT_PUBLIC_NETWORK_RPC_URL: ${BLOCKSCOUT_RPC_URL_PUBLIC}
NEXT_PUBLIC_IS_TESTNET: "true"
NEXT_PUBLIC_API_SPEC_URL: https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_HOMEPAGE_CHARTS: "[]"
NEXT_PUBLIC_FOOTER_LINKS: "[]"
NEXT_PUBLIC_AD_BANNER_PROVIDER: none
NEXT_PUBLIC_AD_TEXT_PROVIDER: none
ports:
- "${BLOCKSCOUT_FRONTEND_PORT}:3000"
networks:
- blockscout

networks:
blockscout:
driver: bridge

volumes:
postgres-data:
redis-data:
21 changes: 21 additions & 0 deletions blockscout/images.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Blockscout image pins. Sourced by scripts/blockscout-{up,pack-images}.sh.
#
# These images are pulled on the build machine and saved into
# blockscout/images.tar.gz so the air-gapped target can `podman load -i`
# them without internet access.
#
# Override via env vars at pack time or run time, e.g.:
# BLOCKSCOUT_BACKEND_IMAGE=avaplatform/blockscout-backend:<tag> \
# BLOCKSCOUT_FRONTEND_IMAGE=avaplatform/blockscout-frontend:<tag> \
# ./scripts/blockscout-pack-images.sh

# Fully-qualified registry names. Required for podman, which doesn't fall back
# to docker.io for short names by default on stock RHEL/Rocky installs.
BLOCKSCOUT_BACKEND_IMAGE="${BLOCKSCOUT_BACKEND_IMAGE:-docker.io/blockscout/blockscout:7.0.2.commit.900ef697}"
BLOCKSCOUT_FRONTEND_IMAGE="${BLOCKSCOUT_FRONTEND_IMAGE:-ghcr.io/blockscout/frontend:v2.3.1}"
BLOCKSCOUT_POSTGRES_IMAGE="${BLOCKSCOUT_POSTGRES_IMAGE:-docker.io/library/postgres:15-alpine}"
BLOCKSCOUT_REDIS_IMAGE="${BLOCKSCOUT_REDIS_IMAGE:-docker.io/library/redis:7-alpine}"

# Target platform for image pulls. RHEL targets are typically linux/amd64;
# override for ARM hosts.
BLOCKSCOUT_PLATFORM="${BLOCKSCOUT_PLATFORM:-linux/amd64}"
Loading
Loading