Skip to content
云间札记
Go back

Blockscout L2 浏览器部署:OP Stack 链上数据可视化

Updated:

背景

为 OP Stack L2 Rollup 部署区块链浏览器,提供交易查询、区块浏览、合约验证等功能。

服务器配置

主机配置角色
prod-explorer-10-169-20-308c/16g/200gBlockscout 浏览器

数据库准备

安装 psql 客户端

apt install postgresql-client -y

数据库操作

# 连接数据库
psql -h pgm-3ns8716n6k923d80.pg.rds.aliyuncs.com -p 5432 -U blockscout -d blockscout
# 密码: V3K76N4gS18K

常用操作:

-- 查询当前用户
SELECT current_user;

-- 查询所有数据库
SELECT datname FROM pg_database;

-- 创建数据库
CREATE DATABASE blockscout;

-- 授权
GRANT ALL PRIVILEGES ON DATABASE blockscout TO blockscout;

-- 查询数据库大小
SELECT pg_size_pretty(pg_database_size('blockscout')) AS size;

-- 查询所有表大小
SELECT
    relname AS "Table",
    pg_size_pretty(pg_total_relation_size(relid)) AS "Size"
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC;

-- 终止会话(重建前)
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'blockscout';

-- 重命名数据库
ALTER DATABASE blockscout RENAME TO old_blockscout;

源码部署

下载源码

mkdir -pv /data/op-explorer/
cd /data/op-explorer/

# 克隆指定分支
git clone https://github.com/blockscout/blockscout.git -b production-optimism
cd blockscout/docker-compose

清理重建

cd /data/op-explorer/blockscout/docker-compose/services
# 删除旧数据
rm -rf redis-data logs stats-db-data

Nginx 配置

cd /data/op-explorer/blockscout/docker-compose/proxy

# 备份
cp default.conf.template default.conf.template_backup

# 写入配置
cat > default.conf.template <<'EOF'
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

upstream back-proxy-pass {
    server ${BACK_PROXY_PASS};
}
upstream front-proxy-pass {
    server ${FRONT_PROXY_PASS};
}
upstream stats-proxy-pass {
    server stats:8050;
}

server {
    listen       80;
    server_name  scan.nal.network;
    proxy_http_version 1.1;
    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Allow-Methods' '*';

    location ~ ^/(api|socket|sitemap.xml|auth/auth0|auth/auth0/callback|auth/logout) {
        add_header Content-Security-Policy "upgrade-insecure-requests;connect-src *";
        proxy_pass            http://back-proxy-pass;
        proxy_http_version    1.1;
        proxy_set_header      Host "$host";
        proxy_set_header      X-Real-IP "$remote_addr";
        proxy_set_header      X-Forwarded-For "$proxy_add_x_forwarded_for";
        proxy_set_header      X-Forwarded-Proto "$scheme";
        proxy_set_header      Upgrade "$http_upgrade";
        proxy_set_header      Connection $connection_upgrade;
        proxy_cache_bypass    $http_upgrade;
    }
    location / {
        add_header Content-Security-Policy "upgrade-insecure-requests;connect-src *";
        proxy_pass           http://front-proxy-pass;
        proxy_http_version    1.1;
        proxy_set_header      Host "$host";
        proxy_set_header      X-Real-IP "$remote_addr";
        proxy_set_header      X-Forwarded-For "$proxy_add_x_forwarded_for";
        proxy_set_header      X-Forwarded-Proto "$scheme";
        proxy_set_header      Upgrade "$http_upgrade";
        proxy_set_header      Connection $connection_upgrade;
        proxy_cache_bypass    $http_upgrade;
    }
}

server {
    listen       80;
    server_name  stats.nal.network;
    proxy_http_version 1.1;

    location / {
        add_header 'Access-Control-Allow-Origin' *;
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' '*';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
        add_header Content-Security-Policy "upgrade-insecure-requests;connect-src *";
        if ($request_method = 'OPTIONS') {
                add_header 'Access-Control-Max-Age' 1728000;
                add_header 'Content-Type' 'text/plain; charset=utf-8';
                add_header 'Content-Length' 0;
                return 204;
        }
        proxy_pass            http://stats-proxy-pass;
        proxy_http_version    1.1;
        proxy_set_header      Host "$host";
        proxy_set_header      X-Real-IP "$remote_addr";
        proxy_set_header      X-Forwarded-For "$proxy_add_x_forwarded_for";
        proxy_set_header      X-Forwarded-Proto "$scheme";
        proxy_set_header      Upgrade "$http_upgrade";
        proxy_set_header      Connection $connection_upgrade;
        proxy_cache_bypass    $http_upgrade;
    }
}

server {
    listen       8081;
    server_name  scan.nal.network;
    proxy_http_version 1.1;
    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Allow-Methods' '*';

    location / {
        proxy_pass            http://visualizer:8050/;
        proxy_http_version    1.1;
        proxy_buffering       off;
        proxy_set_header      Host "$host";
        proxy_set_header      X-Real-IP "$remote_addr";
        proxy_connect_timeout 30m;
        proxy_read_timeout    30m;
        proxy_send_timeout    30m;
        proxy_set_header      X-Forwarded-For "$proxy_add_x_forwarded_for";
        proxy_set_header      X-Forwarded-Proto "$scheme";
        proxy_set_header      Upgrade "$http_upgrade";
        proxy_set_header      Connection $connection_upgrade;
        proxy_cache_bypass    $http_upgrade;
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' '*' always;
            add_header 'Access-Control-Allow-Credentials' 'true' always;
            add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always;
            add_header 'Access-Control-Allow-Headers' 'DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,x-csrf-token' always;
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }
    }
}
EOF

Docker 配置

修改数据目录

# AWS 使用 snapd 管理 docker,需迁移到独立目录
sudo snap stop docker
sudo systemctl stop snapd.socket
sudo systemctl stop snapd.service

# 创建新目录
mkdir -vp /data/docker/workspace
mkdir -vp /etc/docker/

# 修改配置
cat > /etc/docker/daemon.json <<'EOF'
{
  "data-root": "/data/docker/workspace"
}
EOF

# 迁移数据
rsync -aP /var/lib/docker/ /data/docker/workspace

# 重启
sudo systemctl daemon-reload
sudo systemctl restart docker.service

# 验证
docker info | grep "Docker Root Dir"

Nginx 服务配置

cat > /data/op-explorer/blockscout/docker-compose/services/nginx.yml <<'EOF'
version: '3.9'

services:
  proxy:
    image: nginx
    container_name: proxy
    extra_hosts:
      - 'host.docker.internal:host-gateway'
    volumes:
      - "../proxy:/etc/nginx/templates"
    environment:
      BACK_PROXY_PASS: ${BACK_PROXY_PASS:-backend:4000}
      FRONT_PROXY_PASS: ${FRONT_PROXY_PASS:-frontend:3000}
    ports:
      - target: 80
        published: 80
      - target: 8080
        published: 8080
      - target: 8081
        published: 8081
EOF

环境变量配置

common-blockscout.env

cd /data/op-explorer/blockscout/docker-compose/envs

# 备份
cp common-blockscout.env common-blockscout.env_back

cat > common-blockscout.env <<'EOF'
# RPC 配置
ETHEREUM_JSONRPC_HTTP_URL=http://10.169.20.40:8545/
ETHEREUM_JSONRPC_FALLBACK_HTTP_URL=http://10.169.20.40:8545/
ETHEREUM_JSONRPC_TRACE_URL=http://10.169.20.40:8545/
ETHEREUM_JSONRPC_FALLBACK_TRACE_URL=http://10.169.20.40:8545/
ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL=http://10.169.20.40:8545/
ETHEREUM_JSONRPC_ETH_CALL_URL=http://10.169.20.40:8545/

# 数据库
DATABASE_URL=postgresql://blockscout:V3K76N4gS18K@pgm-3ns8716n6k923d80.pg.rds.aliyuncs.com:5432/blockscout

# 链配置
DOCKER_REPO=blockscout-optimism
DOCKER_TAG=6.6.0-postrelease-c886b77c
CHAIN_TYPE=optimism
NETWORK=NAL
SUBNETWORK=NAL
COIN=ETH
CHAIN_ID=328527

# L1 配置
INDEXER_BEACON_RPC_URL=http://10.169.21.21:3500
INDEXER_OPTIMISM_L1_RPC=http://10.169.21.21:8545
INDEXER_OPTIMISM_L1_BATCH_START_BLOCK=20180082
INDEXER_OPTIMISM_L1_BATCH_INBOX=0xff00000000000000000000000000000000328527
INDEXER_OPTIMISM_L1_BATCH_SUBMITTER=0xB1b676357de100c5bd846299CF6C85436803e839
INDEXER_OPTIMISM_L1_PORTAL_CONTRACT=0x872902b91fB2aa95147fCDc346a567B7970DBe47
INDEXER_OPTIMISM_L1_OUTPUT_ROOTS_START_BLOCK=20180082
INDEXER_OPTIMISM_L1_OUTPUT_ORACLE_CONTRACT=0xaE25ea4Cc185585Fa6abf344F3354bf8207Cd7D1
INDEXER_OPTIMISM_L1_WITHDRAWALS_START_BLOCK=20180082
INDEXER_OPTIMISM_L2_WITHDRAWALS_START_BLOCK=1
INDEXER_OPTIMISM_L2_MESSAGE_PASSER_CONTRACT=0x4200000000000000000000000000000000000016
INDEXER_OPTIMISM_L1_DEPOSITS_START_BLOCK=20180082
INDEXER_OPTIMISM_L1_BATCH_BLOCKSCOUT_BLOBS_API_URL=https://eth.blockscout.com/api/v2/blobs
INDEXER_OPTIMISM_L2_BATCH_GENESIS_BLOCK_NUMBER=0

# 性能优化
ETHEREUM_JSONRPC_GETH_TRACE_BY_BLOCK=true
EOF

common-frontend.env

cat > common-frontend.env <<'EOF'
NEXT_PUBLIC_API_HOST=scan.nal.network
NEXT_PUBLIC_API_PROTOCOL=https
NEXT_PUBLIC_STATS_API_HOST=https://stats.nal.network
NEXT_PUBLIC_NETWORK_NAME=NAL
NEXT_PUBLIC_NETWORK_SHORT_NAME=Nal chain
NEXT_PUBLIC_NETWORK_ID=328527
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=ETH
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_APP_HOST=scan.nal.network
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_VISUALIZE_API_HOST=https://scan.nal.network:8081
NEXT_PUBLIC_IS_TESTNET=false
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_ROLLUP_TYPE=optimistic
NEXT_PUBLIC_ROLLUP_L1_BASE_URL=https://eth.blockscout.com/
NEXT_PUBLIC_ROLLUP_L2_WITHDRAWAL_URL=https://bridge.nal.network/withdrawal
NEXT_PUBLIC_AD_TEXT_PROVIDER=none
NEXT_PUBLIC_AD_BANNER_PROVIDER=none
NEXT_PUBLIC_GAS_TRACKER_ENABLED=true
NEXT_PUBLIC_GAS_TRACKER_UNITS=['gwei']
NEXT_PUBLIC_WEB3_WALLETS=['metamask']
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_HIDE_INDEXING_ALERT_BLOCKS=true
NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS=true
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true
NEXT_PUBLIC_NETWORK_ICON=https://qa-e-chain.obs.cn-north-219.jrzq.huaweicloud.com/nal-icon/nal.svg
NEXT_PUBLIC_NETWORK_LOGO=https://qa-e-chain.obs.cn-north-219.jrzq.huaweicloud.com/nal-icon/nal.svg
NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND=radial-gradient(103.03% 103.03% at 0% 0%, rgba(28,100,175,0.8) 0%, rgba(46,35,136,0.8) 100%), var(--chakra-colors-blue-300)
FAVICON_GENERATOR_API_KEY=c91723cf3aa4406388ba51b4552fd004d95c114d
FAVICON_MASTER_URL=https://i.postimg.cc/7ZjL5Jsd/nal.png
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=b6a7972b75af75d28a8aeb35a6af7871
NEXT_PUBLIC_NETWORK_RPC_URL=https://rpc.nal.network
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=false
EOF

common-stats.env

cat > common-stats.env <<'EOF'
STATS__CREATE_DATABASE=true
STATS__RUN_MIGRATIONS=true
EOF

stats.yml

cat > /data/op-explorer/blockscout/docker-compose/services/stats.yml <<'EOF'
STATS__BLOCKSCOUT_DB_URL=${STATS__BLOCKSCOUT_DB_URL:-postgresql://blockscout:V3K76N4gS18K@pgm-3ns8716n6k923d80.pg.rds.aliyuncs.com:5432/blockscout}
EOF

backend.yml

cat > /data/op-explorer/blockscout/docker-compose/services/backend.yml <<'EOF'
# 修改镜像
image: blockscout/blockscout-optimism:6.7.2
EOF

frontend.yml

cat > /data/op-explorer/blockscout/docker-compose/services/frontend.yml <<'EOF'
version: '3.9'

services:
  frontend:
    image: ghcr.io/blockscout/frontend:v1.32.0
    pull_policy: always
    platform: linux/amd64
    restart: always
    container_name: 'frontend'
    env_file:
      -  ../envs/common-frontend.env
EOF

启动服务

cd /data/op-explorer/blockscout/docker-compose

# 启动
docker compose -f external-db.yml up -d

# 停止
docker compose -f external-db.yml down

镜像更新

# 修改镜像标签
docker tag 5fa06fa31aed blockscout/blockscout-optimism:newtag

# 验证
docker images

# 修改 backend.yml 使用新镜像
image: blockscout/blockscout-optimism:newtag

复盘

问题根因:Docker 默认目录在系统盘,日志增长导致磁盘占满

解决:迁移 Docker 数据目录到 /data 独立磁盘

改进措施

本文首发于 wr.mrchi.cn,转载请注明出处。



Previous Post
运维资产管理系统设计与实现:多云环境下的资源治理与堡垒机一体化
Next Post
以太坊主网自建节点部署:Geth 执行客户端 + Prysm 共识客户端