dApp Docs/Docker本地开发环境
Development reference. Not independently verified for production.

MSG Chain Docker 本地开发环境完整指南

数据来源:MSG Chain 代码库核实

主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。


目录

  1. 概述
  2. 快速开始
  3. Dockerfile 设计
  4. Docker Compose:单节点
  5. Docker Compose:多节点
  6. Docker Compose:完整开发栈
  7. CosmWasm 合约开发容器
  8. 前端开发环境
  9. 数据持久化
  10. 网络配置
  11. 监控与日志
  12. 生产部署考量
  13. 排错指南

一、概述

1.1 为什么使用 Docker

MSG Chain 的本地开发环境涉及多个组件——节点二进制(Go)、CosmWasm 合约编译器(Rust + wasm32)、前端开发服务器(Node.js)、索引器(Python/Go)、浏览器(TypeScript)、PostgreSQL、Redis、Nginx 等。使用 Docker 容器化带来的核心优势:

优势 说明
环境一致性 消除"在我机器上能跑"的问题,团队共享完全相同的开发环境
依赖隔离 Go 1.24.6+、Rust 1.75+、Node.js 18+、libwasmvm 等依赖通过分层管理
一键启动 docker compose up 即可启动完整的开发栈
状态可重置 区块链数据存储在 Docker volumes 中,随时可以 docker compose down -v 重置
合约构建可重现 使用多阶段构建确保 WASM 合约的确定性编译
跨平台 在 Linux、macOS(M1/M2)、Windows 上一致运行

1.2 类似 Ganache / Hardhat 的本地测试体验

MSG Chain Docker 环境对标以太坊生态中的 Ganache 和 Hardhat 本地网络:

以太坊生态 MSG Chain 生态
Ganache / Hardhat Network MSG Chain 单节点 Docker 容器
Hardhat 部署脚本 msgd tx wasm store + instantiate
ethers.js / web3.js CosmJS (@cosmjs/cosmwasm-stargate)
MetaMask Keplr 钱包
Solidity + EVM Rust + CosmWasm (WASM)
Hardhat 控制台 docker exec -it msgd-node bash
The Graph 索引器 MSG Chain Indexer (local-only)
Etherscan MSG Chain Explorer (local-only)

1.3 技术栈概览

┌─────────────────────────────────────────────────────────────┐
│                  开发容器 (可选)                              │
│  ┌──────────┐  ┌──────────────┐  ┌──────────────────┐      │
│  │ Node.js  │  │ Rust/CosmWasm│  │ PostgREST/API    │      │
│  │ dApp 前端 │  │ 合约编译器    │  │ 客户端           │      │
│  └──────────┘  └──────────────┘  └──────────────────┘      │
├─────────────────────────────────────────────────────────────┤
│                  核心服务                                     │
│  ┌──────────────────┐  ┌──────────┐  ┌─────────────┐       │
│  │ MSG Chain 节点    │  │ Indexer  │  │ Explorer    │       │
│  │ (msgd, 5s 出块)   │  │ (local)  │  │ (local)     │       │
│  └──────────────────┘  └──────────┘  └─────────────┘       │
├─────────────────────────────────────────────────────────────┤
│                  数据层                                       │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌───────────┐  │
│  │ BadgerDB │  │PostgreSQL│  │  Redis   │  │  Volumes  │  │
│  └──────────┘  └──────────┘  └──────────┘  └───────────┘  │
├─────────────────────────────────────────────────────────────┤
│                  Docker 基础设施                              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌───────────┐  │
│  │ 网络     │  │ Compose  │  │ Health   │  │ 反向代理   │  │
│  │ overlay  │  │ 编排     │  │ 检查     │  │ (Nginx)   │  │
│  └──────────┘  └──────────┘  └──────────┘  └───────────┘  │
└─────────────────────────────────────────────────────────────┘

1.4 核心参数

参数 值
Chain ID msg-chain-1
节点二进制 msgd
编译命令 make build-linux → bin/genesis_node_linux + bin/quantum_node_linux
Bech32 前缀 msg
币种类型 (BIP44) 118
MSG 精度 18 位小数
Gas 价格 1000000000umsg
出块时间 5 秒
共识算法 Round-Robin + DAR (Dilithium-AR)
签名方案 Dilithium-5 (后量子)
状态存储 BadgerDB
默认 home ~/.msgchain

1.5 端口规划

容器端口 映射端口 协议 用途
26656 26656 TCP P2P 通信
26657 26657 TCP/WS RPC / WebSocket
1317 1317 TCP REST API
9090 9090 TCP gRPC
9091 9091 TCP gRPC-web
26660 26660 TCP Prometheus 指标

1.6 项目目录结构

MSG Chain Docker 相关文件应组织为:

msgchain/
├── docker/
│   ├── .env                        # 环境变量
│   ├── .dockerignore               # Docker 忽略文件
│   ├── msgd/
│   │   ├── Dockerfile              # 多阶段节点镜像
│   │   ├── entrypoint.sh           # 容器入口脚本
│   │   └── healthcheck.sh          # 健康检查脚本
│   ├── contract-builder/
│   │   └── Dockerfile              # 合约编译器镜像
│   ├── indexer/
│   │   └── Dockerfile              # 索引器镜像 (local)
│   ├── explorer/
│   │   └── Dockerfile              # 浏览器镜像 (local)
│   ├── frontend/
│   │   └── Dockerfile              # 前端开发镜像
│   ├── postgres/
│   │   └── init.sql                # PostgreSQL 初始化
│   ├── nginx/
│   │   ├── nginx.conf              # Nginx 配置
│   │   └── conf.d/
│   │       └── msgchain.conf       # 路由配置
│   ├── prometheus/
│   │   └── prometheus.yml          # 监控配置
│   ├── grafana/
│   │   ├── dashboards/             # Grafana 仪表板
│   │   └── datasources/            # 数据源配置
│   ├── docker-compose.single.yml   # 单节点配置
│   ├── docker-compose.multi.yml    # 多节点配置
│   ├── docker-compose.full.yml     # 完整开发栈
│   ├── docker-compose.contracts.yml # 合约开发环境
│   ├── docker-compose.frontend.yml # 前端开发环境
│   └── docker-compose.monitoring.yml # 监控栈
├── scripts/
│   ├── start-single.sh             # 单节点启动脚本
│   ├── init-multi.sh               # 多节点初始化
│   ├── deploy-contract.sh          # 合约部署脚本
│   ├── new-contract.sh             # 创建新合约
│   ├── backup.sh                   # 备份脚本
│   ├── restore.sh                  # 恢复脚本
│   ├── reset-state.sh              # 状态重置
│   └── snapshot.sh                 # 快照脚本
├── contracts/                      # 合约源码目录
├── frontend/                       # 前端源码目录
├── Makefile                        # MSG Chain 构建文件
└── go.mod                          # Go 模块定义

二、快速开始

2.1 前提条件

# 安装 Docker
# Ubuntu/Debian
sudo apt-get update && sudo apt-get install -y docker.io docker-compose-plugin

# macOS: 从 https://docs.docker.com/desktop/ 下载 Docker Desktop

# Windows: 从 https://docs.docker.com/desktop/ 下载 Docker Desktop (WSL2 后端)

# 验证安装
docker --version
docker compose version

# Clone MSG Chain 源码
git clone https://github.com/msgchain/msgchain
cd msgchain

2.2 一条命令启动(推荐)

# 使用提供 docker-compose 单节点配置
docker compose -f docker/docker-compose.single.yml up -d

# 等待节点就绪(约 30-60 秒,取决于首次构建时间)
docker compose -f docker/docker-compose.single.yml logs -f

2.3 验证节点运行

# 查询节点状态
curl -s http://localhost:26657/status | jq '.result.sync_info'

# 预期输出:
# {
#   "latest_block_height": "15",
#   "latest_block_time": "2026-07-05T12:00:50Z",
#   "catching_up": false
# }

# 查询账户余额
docker exec msgd-node msgd query bank balances \
  $(docker exec msgd-node msgd keys show validator -a --keyring-backend test) \
  --node tcp://localhost:26657

# 查看区块高度变化
watch -n 2 'curl -s http://localhost:26657/status | jq ".result.sync_info.latest_block_height"'

2.4 连接 Keplr 钱包

在浏览器中打开 Keplr 钱包,按照第 4.5 节的配置添加 MSG Chain 本地网络。

2.5 停止和清理

# 停止容器(保留数据)
docker compose -f docker/docker-compose.single.yml down

# 停止并删除数据卷(完全重置)
docker compose -f docker/docker-compose.single.yml down -v

# 查看容器状态
docker compose -f docker/docker-compose.single.yml ps

三、Dockerfile 设计

3.1 .dockerignore

# .dockerignore
.git/
.gitignore
.github/

# Build artifacts
build/
target/
dist/

# Dependencies
node_modules/
vendor/

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS files
.DS_Store
Thumbs.db

# Docker files
docker/
*.md

# Local data
data/
*.log

3.2 多阶段 Dockerfile

# docker/msgd/Dockerfile
# ============================================================
# MSG Chain 节点 Docker 镜像 — 多阶段构建
# ============================================================

# ============================================================
# Stage 1: 构建阶段
# ============================================================
FROM golang:1.21.13-bullseye AS builder

LABEL stage="msgchain-builder"
LABEL description="MSG Chain Node Builder Stage"

# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    git \
    curl \
    wget \
    jq \
    lz4 \
    ca-certificates \
    pkg-config \
    libusb-1.0-0-dev \
    && rm -rf /var/lib/apt/lists/*

# 安装 Rust (用于 CosmWasm 依赖编译)
ENV RUSTUP_HOME=/usr/local/rustup \
    CARGO_HOME=/usr/local/cargo \
    PATH=/usr/local/cargo/bin:$PATH

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
    && rustup target add wasm32-unknown-unknown \
    && rustup default 1.75.0 \
    && rustc --version

# 设置 Go 模块缓存
ENV GOPATH=/go \
    PATH=/go/bin:/usr/local/go/bin:$PATH

WORKDIR /src

# 先复制 go.mod 和 go.sum 以利用 Docker 构建缓存
COPY go.mod go.sum ./
RUN go mod download 2>/dev/null || true

# 复制 Makefile 和源代码
COPY Makefile .
COPY cmd/ ./cmd/
COPY x/ ./x/
COPY app/ ./app/
COPY lib/ ./lib/
COPY proto/ ./proto/
COPY scripts/ ./scripts/
COPY contracts/ ./contracts/

# 运行 make deps 安装 CosmWasm 依赖
RUN make deps

# 构建 MSG Chain 二进制 (生成 bin/genesis_node_linux + bin/quantum_node_linux)
RUN make build-linux

# 验证构建产物
RUN ls -la /src/build/ && /src/build/msgd version || true

# ============================================================
# Stage 2: 运行时阶段 (最小镜像)
# ============================================================
FROM debian:bullseye-slim AS runtime

LABEL description="MSG Chain Node Runtime"
LABEL chain-id="msg-chain-1"

# 安装运行时依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates \
    curl \
    jq \
    lz4 \
    libwasmvm.so.1 \
    && rm -rf /var/lib/apt/lists/*

# 从构建阶段复制二进制文件
COPY --from=builder /src/build/msgd /usr/local/bin/msgd

# 验证二进制
RUN msgd version || true

# 创建数据目录
RUN mkdir -p /home/msgchain/.msgchain/config \
              /home/msgchain/.msgchain/data \
              /home/msgchain/.msgchain/keyring-test \
    && chmod -R 755 /home/msgchain/.msgchain

# 容器健康检查脚本
COPY docker/msgd/healthcheck.sh /usr/local/bin/healthcheck.sh
RUN chmod +x /usr/local/bin/healthcheck.sh

# 入口点脚本
COPY docker/msgd/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

# 暴露端口
EXPOSE 26656 26657 1317 9090 9091 26660

# 设置工作目录
WORKDIR /home/msgchain

# 健康检查
HEALTHCHECK --interval=15s --timeout=5s --start-period=30s --retries=3 \
    CMD healthcheck.sh

# 入口点
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["start"]

# ============================================================
# Stage 3: CosmWasm 合约编译器
# ============================================================
FROM rust:1.75.0-bullseye AS contract-builder

LABEL description="MSG Chain CosmWasm Contract Builder"

# 安装 wasm32 目标
RUN rustup target add wasm32-unknown-unknown

# 安装 wasm-opt (Binaryen)
RUN apt-get update && apt-get install -y --no-install-recommends \
    binaryen \
    clang \
    && rm -rf /var/lib/apt/lists/*

# 安装 cargo 工具
RUN cargo install cargo-wasm wasm-opt

# 设置工作目录
WORKDIR /contracts

# 默认命令:编译当前目录下 合约
ENTRYPOINT ["/bin/bash", "-c"]
CMD ["RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown && \
      wasm-opt -Os -o /output/optimized.wasm \
      target/wasm32-unknown-unknown/release/*.wasm"]

3.3 健康检查脚本

#!/bin/bash
# docker/msgd/healthcheck.sh — MSG Chain 节点健康检查
set -euo pipefail

# 检查 msgd 进程是否在运行
if ! pgrep -x msgd > /dev/null 2>&1; then
    echo "ERROR: msgd process not running"
    exit 1
fi

# 检查 RPC 端点是否响应
if ! curl -sf http://localhost:26657/health > /dev/null 2>&1; then
    echo "ERROR: RPC endpoint not responding"
    exit 1
fi

# 检查区块是否持续增长
LATEST_HEIGHT=$(curl -sf http://localhost:26657/status 2>/dev/null | \
    jq -r '.result.sync_info.latest_block_height // "0"' 2>/dev/null || echo "0")

if [ "$LATEST_HEIGHT" = "0" ] || [ -z "$LATEST_HEIGHT" ]; then
    echo "WARNING: Cannot determine block height"
    exit 1
fi

echo "OK: Node healthy at height $LATEST_HEIGHT"
exit 0

3.4 入口点脚本

#!/bin/bash
# docker/msgd/entrypoint.sh — MSG Chain 节点入口点
set -euo pipefail

MSG_CHAIN_HOME="/home/msgchain/.msgchain"
CHAIN_ID="msg-chain-1"
MONIKER="${MONIKER:-docker-msg-node}"

# 如果节点未初始化,执行初始化流程
if [ ! -f "$MSG_CHAIN_HOME/config/genesis.json" ]; then
    echo "=== Initializing MSG Chain node ==="

    # 初始化节点
    msgd init --chain-id="$CHAIN_ID" --moniker="$MONIKER"

    # 创建验证者密钥 (keyring-backend test 用于开发环境)
    echo "y" | msgd keys add validator \
        --keyring-backend test \
        --home "$MSG_CHAIN_HOME" 2>/dev/null || true

    VALIDATOR_ADDR=$(msgd keys show validator -a \
        --keyring-backend test \
        --home "$MSG_CHAIN_HOME" 2>/dev/null)

    if [ -n "$VALIDATOR_ADDR" ]; then
        echo "Validator address: $VALIDATOR_ADDR"

        # 添加创世账户 (1000000 MSG)
        msgd add-genesis-account "$VALIDATOR_ADDR" \
            1000000000000000000umsg \
            --keyring-backend test \
            --home "$MSG_CHAIN_HOME"

        # 创建验证者交易
        msgd gentx validator 500000000000000000umsg \
            --chain-id="$CHAIN_ID" \
            --keyring-backend test \
            --home "$MSG_CHAIN_HOME"

        # 收集创世交易
        msgd collect-gentxs --home "$MSG_CHAIN_HOME"

        # 验证创世文件
        msgd validate-genesis --home "$MSG_CHAIN_HOME"
    fi

    # 配置 Gas 价格
    sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "1000000000umsg"/' \
        "$MSG_CHAIN_HOME/config/app.toml"

    # 配置 CORS (允许 Keplr 连接)
    sed -i 's/laddr = "tc[未公开路径]"/laddr = "tc[未公开路径]"/' \
        "$MSG_CHAIN_HOME/config/config.toml"
    sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = ["*"]/' \
        "$MSG_CHAIN_HOME/config/config.toml"

    # 启用 REST API
    sed -i 's/enable = false/enable = true/' \
        "$MSG_CHAIN_HOME/config/app.toml"
    sed -i 's/address = "tc[未公开路径]"/address = "tc[未公开路径]"/' \
        "$MSG_CHAIN_HOME/config/app.toml"
    sed -i 's/enabled-unsafe-cors = false/enabled-unsafe-cors = true/' \
        "$MSG_CHAIN_HOME/config/app.toml"

    # 启用 gRPC
    sed -i 's/address = "localhost:9090"/address = "0.0.0.0:9090"/' \
        "$MSG_CHAIN_HOME/config/app.toml"

    # 启用 Prometheus
    sed -i 's/prometheus = false/prometheus = true/' \
        "$MSG_CHAIN_HOME/config/config.toml"

    # 启用 DAR 共识
    cat >> "$MSG_CHAIN_HOME/config/config.toml" << 'CONFIG'

[consensus]
timeout_propose = "3s"
timeout_commit = "5s"
round_robin_proposer = true
dar_enabled = true
create_empty_blocks = true

[dilithium]
signature-algorithm = "dilithium5"

[dilithium.dar]
enable = true
rating-weight = 70
aggregation-threshold = 66
CONFIG

    echo "=== Initialization complete ==="
fi

echo "=== Starting MSG Chain node ==="
echo "Chain ID: $CHAIN_ID"
echo "Moniker: $MONIKER"
echo "Home: $MSG_CHAIN_HOME"

# 根据传入的命令启动节点
case "${1:-start}" in
    start)
        exec msgd start \
            --home "$MSG_CHAIN_HOME" \
            --log_level "${LOG_LEVEL:-info}"
        ;;
    init-only)
        echo "Node initialized, exiting."
        exit 0
        ;;
    *)
        exec "$@"
        ;;
esac

3.5 构建镜像

# 在 MSG Chain 项目根目录执行

# 构建节点镜像 (runtime stage)
docker build -f docker/msgd/Dockerfile \
    --target runtime \
    -t msgchain/node:latest .

# 构建合约编译器镜像
docker build -f docker/msgd/Dockerfile \
    --target contract-builder \
    -t msgchain/contract-builder:latest .

# 仅构建阶段 (用于调试)
docker build -f docker/msgd/Dockerfile \
    --target builder \
    -t msgchain/builder:latest .

# 查看镜像
docker images | grep msgchain

# 导出镜像 (离线部署)
docker save msgchain/node:latest -o msgchain-node.tar
docker save msgchain/contract-builder:latest -o msgchain-contract-builder.tar

四、Docker Compose:单节点

4.1 .env 配置文件

# docker/.env
# MSG Chain Docker 环境变量

# 节点配置
MONIKER=docker-msg-node
CHAIN_ID=msg-chain-1
LOG_LEVEL=info

# 创世账户余额 (umsg)
GENESIS_BALANCE=1000000000000000000

# 验证者质押数量 (umsg)
VALIDATOR_STAKE=500000000000000000

# Gas 配置
MINIMUM_GAS_PRICES=1000000000umsg

# 网络配置
P2P_PORT=26656
RPC_PORT=26657
REST_PORT=1317
GRPC_PORT=9090
GRPC_WEB_PORT=9091
PROMETHEUS_PORT=26660

# Keplr 配置
KEPLR_CHAIN_NAME=MSG Chain Local

# Docker 资源限制
CPU_LIMIT=2
MEMORY_LIMIT=4G

4.2 单节点配置

# docker/docker-compose.single.yml
# MSG Chain 单节点 Docker Compose 配置
# 类似 Ganache / Hardhat 的本地开发环境,一键启动

version: "3.9"

services:
  msgd-node:
    container_name: msgd-node
    image: msgchain/node:latest
    build:
      context: ..
      dockerfile: docker/msgd/Dockerfile
      target: runtime
    ports:
      # P2P 通信
      - "${P2P_PORT:-26656}:26656/tcp"
      # RPC (包含 WebSocket)
      - "${RPC_PORT:-26657}:26657/tcp"
      # REST API
      - "${REST_PORT:-1317}:1317/tcp"
      # gRPC
      - "${GRPC_PORT:-9090}:9090/tcp"
      # gRPC-web
      - "${GRPC_WEB_PORT:-9091}:9091/tcp"
      # Prometheus
      - "${PROMETHEUS_PORT:-26660}:26660/tcp"
    environment:
      - MONIKER=${MONIKER:-docker-msg-node}
      - CHAIN_ID=${CHAIN_ID:-msg-chain-1}
      - LOG_LEVEL=${LOG_LEVEL:-info}
    volumes:
      # 链数据持久化
      - msgd-data:/home/msgchain/.msgchain/data
      - msgd-config:/home/msgchain/.msgchain/config
      - msgd-keyring:/home/msgchain/.msgchain/keyring-test
    networks:
      - msgnet
    healthcheck:
      test: ["CMD", "healthcheck.sh"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 30s
    restart: unless-stopped
    # 资源限制 (开发环境)
    deploy:
      resources:
        limits:
          cpus: "${CPU_LIMIT:-2}"
          memory: "${MEMORY_LIMIT:-4G}"
        reservations:
          cpus: "1"
          memory: "2G"
    # 日志配置
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

volumes:
  msgd-data:
    name: msgd-data
  msgd-config:
    name: msgd-config
  msgd-keyring:
    name: msgd-keyring

networks:
  msgnet:
    name: msgnet
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

4.3 一键启动脚本

#!/bin/bash
# docker/start-single.sh — MSG Chain 单节点一键启动脚本
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"

echo "=========================================="
echo "  MSG Chain 单节点开发环境启动"
echo "=========================================="

# 检查 Docker
if ! command -v docker &> /dev/null; then
    echo "错误: Docker 未安装。请先安装 Docker。"
    exit 1
fi

# 加载环境变量
if [ -f "$SCRIPT_DIR/.env" ]; then
    set -a
    source "$SCRIPT_DIR/.env"
    set +a
fi

# 构建镜像 (如需强制重建,添加 --build 参数)
BUILD_FLAG=""
if [ "${1:-}" = "--build" ]; then
    BUILD_FLAG="--build"
    echo ">> 强制重建 Docker 镜像..."
fi

# 启动容器
echo ">> 启动 MSG Chain 节点..."
docker compose -f "$SCRIPT_DIR/docker-compose.single.yml" up -d $BUILD_FLAG

# 等待节点就绪
echo ">> 等待节点就绪..."
sleep 5

# 检查健康状态
for i in $(seq 1 30); do
    HEALTH=$(docker inspect --format='{{.State.Health.Status}}' msgd-node 2>/dev/null || echo "starting")
    if [ "$HEALTH" = "healthy" ]; then
        echo ">> 节点已就绪!"
        break
    fi
    echo "  等待中... ($i/30)"
    sleep 2
done

# 显示节点信息
echo ""
echo "=========================================="
echo "  MSG Chain 节点已启动!"
echo "=========================================="
echo "  RPC:       http://localhost:${RPC_PORT:-26657}"
echo "  REST:      http://localhost:${REST_PORT:-1317}"
echo "  gRPC:      localhost:${GRPC_PORT:-9090}"
echo "  WebSocket: ws://localhost:${RPC_PORT:-26657}/websocket"
echo "  P2P:       localhost:${P2P_PORT:-26656}"
echo "  Prometheus: http://localhost:${PROMETHEUS_PORT:-26660}/metrics"
echo "------------------------------------------"

# 获取验证者地址
VALIDATOR_ADDR=$(docker exec msgd-node msgd keys show validator -a \
    --keyring-backend test 2>/dev/null || echo "N/A")
echo "  验证者地址: $VALIDATOR_ADDR"
echo "------------------------------------------"

# 获取区块高度
BLOCK_HEIGHT=$(curl -sf http://localhost:${RPC_PORT:-26657}/status 2>/dev/null | \
    jq -r '.result.sync_info.latest_block_height' 2>/dev/null || echo "N/A")
echo "  当前区块高度: $BLOCK_HEIGHT"
echo "=========================================="
echo ""
echo "使用以下命令停止:"
echo "  docker compose -f $SCRIPT_DIR/docker-compose.single.yml down"
echo "重置数据:"
echo "  docker compose -f $SCRIPT_DIR/docker-compose.single.yml down -v"
echo ""

4.4 实用操作命令

# 进入容器
docker exec -it msgd-node bash

# 创建新密钥
docker exec msgd-node msgd keys add mykey --keyring-backend test

# 查看所有密钥
docker exec msgd-node msgd keys list --keyring-backend test

# 转账
docker exec msgd-node msgd tx bank send \
  $(docker exec msgd-node msgd keys show validator -a --keyring-backend test) \
  msg1destination... \
  1000000000000000000umsg \
  --from validator \
  --gas auto \
  --gas-prices 1000000000umsg \
  --chain-id msg-chain-1 \
  --keyring-backend test \
  --node tcp://localhost:26657 \
  -y

# 查询余额
docker exec msgd-node msgd query bank balances \
  $(docker exec msgd-node msgd keys show validator -a --keyring-backend test)

# 查看日志
docker logs msgd-node -f

# 查看节点状态
curl -s http://localhost:26657/status | jq

# 查看网络对等节点
curl -s http://localhost:26657/net_info | jq '.result.n_peers'

# 上传合约 (WASM 文件在容器内)
docker cp ./contract.wasm msgd-node:/tmp/contract.wasm
docker exec msgd-node msgd tx wasm store /tmp/contract.wasm \
  --from validator --gas auto --gas-prices 1000000000umsg \
  --chain-id msg-chain-1 --keyring-backend test \
  --node tcp://localhost:26657 -y

4.5 Keplr 钱包配置

将以下配置添加到 Keplr 钱包(开发环境自动添加,或手动在 Keplr 中导入):

{
  "chainId": "msg-chain-1",
  "chainName": "MSG Chain Local",
  "rpc": "http://localhost:26657",
  "rest": "http://localhost:1317",
  "bip44": { "coinType": 118 },
  "bech32Config": {
    "bech32PrefixAccAddr": "msg",
    "bech32PrefixAccPub": "msgpub",
    "bech32PrefixValAddr": "msgvaloper",
    "bech32PrefixValPub": "msgvaloperpub",
    "bech32PrefixConsAddr": "msgvalcons",
    "bech32PrefixConsPub": "msgvalconspub"
  },
  "currencies": [{ "coinDenom": "MSG", "coinMinimalDenom": "umsg", "coinDecimals": 18 }],
  "feeCurrencies": [{ "coinDenom": "MSG", "coinMinimalDenom": "umsg", "coinDecimals": 18, "gasPriceStep": { "low": 1e9, "average": 1e9, "high": 1e9 } }],
  "stakeCurrency": { "coinDenom": "MSG", "coinMinimalDenom": "umsg", "coinDecimals": 18 }
}

通过 JavaScript 自动添加(在浏览器控制台执行):

await window.keplr.experimentalSuggestChain({
  chainId: "msg-chain-1",
  chainName: "MSG Chain Local",
  rpc: "http://localhost:26657",
  rest: "http://localhost:1317",
  bip44: { coinType: 118 },
  bech32Config: {
    bech32PrefixAccAddr: "msg",
    bech32PrefixAccPub: "msgpub",
    bech32PrefixValAddr: "msgvaloper",
    bech32PrefixValPub: "msgvaloperpub",
    bech32PrefixConsAddr: "msgvalcons",
    bech32PrefixConsPub: "msgvalconspub",
  },
  currencies: [{ coinDenom: "MSG", coinMinimalDenom: "umsg", coinDecimals: 18 }],
  feeCurrencies: [{ coinDenom: "MSG", coinMinimalDenom: "umsg", coinDecimals: 18, gasPriceStep: { low: 1e9, average: 1e9, high: 1e9 } }],
  stakeCurrency: { coinDenom: "MSG", coinMinimalDenom: "umsg", coinDecimals: 18 },
});

五、Docker Compose:多节点

5.1 3 节点验证者集群

# docker/docker-compose.multi.yml
# MSG Chain 3 节点验证者集群 — 用于测试共识、网络延迟、DAR 评分等场景

version: "3.9"

services:
  # ============================================================
  # 种子节点 (用于节点发现)
  # ============================================================
  seed-node:
    container_name: msgd-seed
    image: msgchain/node:latest
    build:
      context: ..
      dockerfile: docker/msgd/Dockerfile
      target: runtime
    ports:
      - "26656:26656/tcp"
      - "26657:26657/tcp"
    environment:
      - MONIKER=msgd-seed
      - CHAIN_ID=msg-chain-1
      - NODE_ROLE=seed
      - LOG_LEVEL=info
    volumes:
      - msgd-seed-config:/home/msgchain/.msgchain/config
      - msgd-seed-data:/home/msgchain/.msgchain/data
    networks:
      msgnet:
        ipv4_address: 172.20.0.2
    healthcheck:
      test: ["CMD", "healthcheck.sh"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 30s
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: "2G"

  # ============================================================
  # 验证者节点 1
  # ============================================================
  validator-1:
    container_name: msgd-validator-1
    image: msgchain/node:latest
    build:
      context: ..
      dockerfile: docker/msgd/Dockerfile
      target: runtime
    ports:
      - "26661:26656/tcp"
      - "26662:26657/tcp"
      - "1318:1317/tcp"
    environment:
      - MONIKER=validator-1
      - CHAIN_ID=msg-chain-1
      - NODE_ROLE=validator
      - LOG_LEVEL=info
    volumes:
      - msgd-validator1-config:/home/msgchain/.msgchain/config
      - msgd-validator1-data:/home/msgchain/.msgchain/data
      - msgd-validator1-keyring:/home/msgchain/.msgchain/keyring-test
    networks:
      msgnet:
        ipv4_address: 172.20.0.11
    depends_on:
      seed-node:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "healthcheck.sh"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 30s
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: "2G"

  # ============================================================
  # 验证者节点 2
  # ============================================================
  validator-2:
    container_name: msgd-validator-2
    image: msgchain/node:latest
    build:
      context: ..
      dockerfile: docker/msgd/Dockerfile
      target: runtime
    ports:
      - "26663:26656/tcp"
      - "26664:26657/tcp"
      - "1319:1317/tcp"
    environment:
      - MONIKER=validator-2
      - CHAIN_ID=msg-chain-1
      - NODE_ROLE=validator
      - LOG_LEVEL=info
    volumes:
      - msgd-validator2-config:/home/msgchain/.msgchain/config
      - msgd-validator2-data:/home/msgchain/.msgchain/data
      - msgd-validator2-keyring:/home/msgchain/.msgchain/keyring-test
    networks:
      msgnet:
        ipv4_address: 172.20.0.12
    depends_on:
      seed-node:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "healthcheck.sh"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 30s
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: "2G"

  # ============================================================
  # 验证者节点 3
  # ============================================================
  validator-3:
    container_name: msgd-validator-3
    image: msgchain/node:latest
    build:
      context: ..
      dockerfile: docker/msgd/Dockerfile
      target: runtime
    ports:
      - "26665:26656/tcp"
      - "26666:26657/tcp"
      - "1320:1317/tcp"
    environment:
      - MONIKER=validator-3
      - CHAIN_ID=msg-chain-1
      - NODE_ROLE=validator
      - LOG_LEVEL=info
    volumes:
      - msgd-validator3-config:/home/msgchain/.msgchain/config
      - msgd-validator3-data:/home/msgchain/.msgchain/data
      - msgd-validator3-keyring:/home/msgchain/.msgchain/keyring-test
    networks:
      msgnet:
        ipv4_address: 172.20.0.13
    depends_on:
      seed-node:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "healthcheck.sh"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 30s
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: "2G"

volumes:
  msgd-seed-config:
  msgd-seed-data:
  msgd-validator1-config:
  msgd-validator1-data:
  msgd-validator1-keyring:
  msgd-validator2-config:
  msgd-validator2-data:
  msgd-validator2-keyring:
  msgd-validator3-config:
  msgd-validator3-data:
  msgd-validator3-keyring:

networks:
  msgnet:
    name: msgnet
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

5.2 多节点初始化脚本

#!/bin/bash
# docker/init-multi.sh — MSG Chain 多节点集群初始化脚本
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
MSG_CHAIN_HOME="/home/msgchain/.msgchain"
CHAIN_ID="msg-chain-1"

echo "=== MSG Chain 多节点集群初始化 ==="

# Step 1: 运行种子节点
echo "[1/6] 启动种子节点..."
docker compose -f "$SCRIPT_DIR/docker-compose.multi.yml" up -d seed-node
sleep 10

# 获取种子节点 ID
SEED_ID=$(docker exec msgd-seed msgd tendermint show-node-id 2>/dev/null)
echo "  Seed Node ID: $SEED_ID"
echo "  Seed Address: ${SEED_ID}@172.20.0.2:26656"

# Step 2: 初始化验证者节点
echo "[2/6] 初始化验证者节点..."
for i in 1 2 3; do
    MONIKER="validator-$i"
    echo "  初始化 $MONIKER..."
    docker run --rm -v "msgd-validator${i}-config:$MSG_CHAIN_HOME/config" \
        msgchain/node:latest msgd init --chain-id="$CHAIN_ID" --moniker="$MONIKER"
done
echo "  所有验证者节点初始化完成!"

# Step 3: 创建验证者密钥和创世交易
echo "[3/6] 创建验证者密钥和创世交易..."
for i in 1 2 3; do
    MONIKER="validator-$i"
    echo "  创建 $MONIKER 密钥..."
    docker run --rm \
        -v "msgd-validator${i}-config:$MSG_CHAIN_HOME/config" \
        -v "msgd-validator${i}-keyring:$MSG_CHAIN_HOME/keyring-test" \
        msgchain/node:latest bash -c "
            echo 'y' | msgd keys add validator --keyring-backend test --home $MSG_CHAIN_HOME 2>/dev/null
        "
done

# 在 validator-1 上创建创世账户
docker run --rm \
    -v "msgd-validator1-config:$MSG_CHAIN_HOME/config" \
    -v "msgd-validator1-keyring:$MSG_CHAIN_HOME/keyring-test" \
    msgchain/node:latest bash -c "
        VALIDATOR_ADDR=\$(msgd keys show validator -a --keyring-backend test --home $MSG_CHAIN_HOME)
        msgd add-genesis-account \$VALIDATOR_ADDR 1000000000000000000umsg \
            --keyring-backend test --home $MSG_CHAIN_HOME
        msgd gentx validator 500000000000000000umsg \
            --chain-id=$CHAIN_ID --keyring-backend test --home $MSG_CHAIN_HOME
        msgd collect-gentxs --home $MSG_CHAIN_HOME
    "

# 同步 genesis 到其他验证者
echo "  同步 genesis 文件..."
docker run --rm \
    -v "msgd-validator1-config:/src/config" \
    -v "msgd-validator2-config:/dst1/config" \
    -v "msgd-validator3-config:/dst2/config" \
    alpine:latest sh -c "
        cp /src/config/genesis.json /dst1/config/genesis.json
        cp /src/config/genesis.json /dst2/config/genesis.json
    "

# Step 4: 配置 persistent peers
echo "[4/6] 配置节点连接..."
VALIDATOR_IDS=()
for i in 1 2 3; do
    ID=$(docker run --rm \
        -v "msgd-validator${i}-config:$MSG_CHAIN_HOME/config" \
        msgchain/node:latest bash -c "msgd tendermint show-node-id --home $MSG_CHAIN_HOME" 2>/dev/null)
    VALIDATOR_IDS+=("$ID")
    echo "  Validator-$i Node ID: $ID"
done

# 构建 persistent peers
PEERS=""
for i in 1 2 3; do
    IP="172.20.0.1${i}"
    [ -n "$PEERS" ] && PEERS="${PEERS},"
    PEERS="${PEERS}${VALIDATOR_IDS[$((i-1))]}@${IP}:26656"
done
echo "  Persistent Peers: $PEERS"

for i in 1 2 3; do
    docker run --rm \
        -v "msgd-validator${i}-config:$MSG_CHAIN_HOME/config" \
        msgchain/node:latest bash -c "
            sed -i 's/seeds = \"\"/seeds = \"${SEED_ID}@172.20.0.2:26656\"/' \
                $MSG_CHAIN_HOME/config/config.toml
            sed -i 's/persistent_peers = \"\"/persistent_peers = \"${PEERS}\"/' \
                $MSG_CHAIN_HOME/config/config.toml
            sed -i 's/laddr = \"tc[未公开路径]"/laddr = \"tc[未公开路径]"/' \
                $MSG_CHAIN_HOME/config/config.toml
            sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = [\"*\"]/' \
                $MSG_CHAIN_HOME/config/config.toml
            sed -i 's/minimum-gas-prices = \"\"/minimum-gas-prices = \"1000000000umsg\"/' \
                $MSG_CHAIN_HOME/config/app.toml
            sed -i 's/prometheus = false/prometheus = true/' \
                $MSG_CHAIN_HOME/config/config.toml
        "
    echo "  验证者 $i 配置已完成"
done

# Step 5: 启动所有验证者
echo "[5/6] 启动验证者节点..."
docker compose -f "$SCRIPT_DIR/docker-compose.multi.yml" up -d validator-1 validator-2 validator-3

# Step 6: 验证集群
echo "[6/6] 验证集群状态..."
sleep 15
for i in 1 2 3; do
    PORT=$((26661 + (i-1) * 2))
    STATUS=$(curl -sf "http://localhost:${PORT}/status" 2>/dev/null || echo '{"error":"not ready"}')
    HEIGHT=$(echo "$STATUS" | jq -r '.result.sync_info.latest_block_height // "N/A"' 2>/dev/null)
    echo "  Validator-$i (端口 $PORT): 区块高度=$HEIGHT"
done

echo ""
echo "=== 多节点集群初始化完成! ==="
echo "种子节点 P2P: localhost:26656"
echo "Validator-1 RPC: http://localhost:26662"
echo "Validator-2 RPC: http://localhost:26664"
echo "Validator-3 RPC: http://localhost:26666"

5.3 DAR 共识在多节点中的配置

# 在每个验证者上启用 DAR 共识 (由 entrypoint.sh 自动完成)

# 手动验证 DAR 配置
docker exec msgd-validator-1 grep -E "dar_enabled|round_robin_proposer" \
    /home/msgchain/.msgchain/config/config.toml

# 预期输出:
# round_robin_proposer = true
# dar_enabled = true

# 查看 DAR 评分
curl -X POST http://localhost:26662 \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","id":1,"method":"/msgchain.dar.v1.Query/DarRatings","params":{}}' | jq

5.4 多节点测试场景

# 场景 1: 模拟节点离线
docker stop msgd-validator-2
sleep 60
# 观察共识是否继续 (Round-Robin + DAR 应自动跳过离线节点)
curl -s http://localhost:26662/status | jq '.result.sync_info.latest_block_height'

# 场景 2: 恢复离线节点
docker start msgd-validator-2
sleep 30
curl -s http://localhost:26664/status | jq '.result.sync_info.catching_up'

# 场景 3: 查看 DAR 评分
curl -X POST http://localhost:26662 \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","id":1,"method":"/msgchain.dar.v1.Query/DarRatings","params":{}}' | jq

# 场景 4: 网络分区测试
docker network disconnect msgnet msgd-validator-3
sleep 30
docker network connect msgnet msgd-validator-3

六、Docker Compose:完整开发栈

6.1 完整开发栈架构

                    浏览器 (Keplr + dApp)
                           |
                        Nginx (反向代理)
                        /    |    \
                       /     |     \
              MSG Chain     Indexer    Explorer
                节点        (local)     (local)
                 |            |           |
              BadgerDB    PostgreSQL    Redis

6.2 完整栈 Docker Compose

# docker/docker-compose.full.yml
# MSG Chain 完整开发栈 — 节点 + 索引器 + 浏览器 + 数据库 + 缓存 + 反向代理

version: "3.9"

services:
  # ============================================================
  # MSG Chain 节点
  # ============================================================
  msgd-node:
    container_name: msgd-node
    image: msgchain/node:latest
    build:
      context: ..
      dockerfile: docker/msgd/Dockerfile
      target: runtime
    ports:
      - "26656:26656/tcp"
      - "26657:26657/tcp"
      - "26660:26660/tcp"
    expose:
      - "1317"
      - "9090"
      - "9091"
    environment:
      - MONIKER=full-stack-node
      - CHAIN_ID=msg-chain-1
      - LOG_LEVEL=info
    volumes:
      - msgd-data:/home/msgchain/.msgchain/data
      - msgd-config:/home/msgchain/.msgchain/config
      - msgd-keyring:/home/msgchain/.msgchain/keyring-test
    networks:
      backend:
        aliases:
          - msgd-node
    healthcheck:
      test: ["CMD", "healthcheck.sh"]
      interval: 15s
      timeout: 5s
      retries: 5
      start_period: 30s
    restart: unless-stopped

  # ============================================================
  # PostgreSQL (索引器存储)
  # ============================================================
  postgres:
    container_name: msgd-postgres
    image: postgres:16-alpine
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: msgchain_indexer
      POSTGRES_USER: msgchain
      POSTGRES_PASSWORD: msgchain_dev_password
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    networks:
      backend:
        aliases:
          - postgres
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U msgchain -d msgchain_indexer"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: "1G"

  # ============================================================
  # Redis (缓存层)
  # ============================================================
  redis:
    container_name: msgd-redis
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    networks:
      backend:
        aliases:
          - redis
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "0.5"
          memory: "512M"

  # ============================================================
  # Indexer (本地开发版本 — 部分实现状态)
  # 当前状态: local_preflight_only=true
  # 生产 retention/SLO 未关闭
  # ============================================================
  indexer:
    container_name: msgd-indexer
    image: msgchain/indexer:local
    build:
      context: ..
      dockerfile: docker/indexer/Dockerfile
    ports:
      - "8081:8080"
    environment:
      - INDEXER_MODE=local_preflight_only
      - INDEXER_CHAIN_ID=msg-chain-1
      - INDEXER_RPC_ENDPOINT=http://msgd-node:26657
      - INDEXER_REST_ENDPOINT=http://msgd-node:1317
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_NAME=msgchain_indexer
      - DB_USER=msgchain
      - DB_PASSWORD=msgchain_dev_password
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - INDEXER_ENABLE_TXS=true
      - INDEXER_ENABLE_CONTRACTS=true
      - INDEXER_ENABLE_MEMOS=true
      - INDEXER_RETENTION_PROOF=false
      - INDEXER_SLO_TRACKING=false
    volumes:
      - indexer-data:/data
    networks:
      backend:
        aliases:
          - indexer
    depends_on:
      msgd-node:
        condition: service_healthy
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-sf", "http://localhost:8080/api/v1/indexer/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: "1G"

  # ============================================================
  # Explorer (本地开发版本 — 部分实现状态)
  # 已关闭的子网关:
  #   - contract source metadata
  #   - canonical source query
  #   - selected-contract event query
  #   - WS logs filter
  #   - hash search committed-index fail-closed
  # ============================================================
  explorer:
    container_name: msgd-explorer
    image: msgchain/explorer:local
    build:
      context: ..
      dockerfile: docker/explorer/Dockerfile
    ports:
      - "8082:80"
    environment:
      - EXPLORER_MODE=local_preflight
      - EXPLORER_CHAIN_ID=msg-chain-1
      - EXPLORER_CHAIN_NAME=MSG Chain Local
      - EXPLORER_RPC_ENDPOINT=http://msgd-node:26657
      - EXPLORER_REST_ENDPOINT=http://msgd-node:1317
      - EXPLORER_INDEXER_ENDPOINT=http://indexer:8080
      - EXPLORER_ENABLE_TX_SEARCH=true
      - EXPLORER_ENABLE_CONTRACT_VERIFICATION=false
      - EXPLORER_ENABLE_WS_LOGS=false
      - EXPLORER_ENABLE_HASH_SEARCH_COMMITTED_INDEX=false
      - EXPLORER_ENABLE_CONTRACT_SOURCE_METADATA=false
      - EXPLORER_BECH32_PREFIX=msg
      - EXPLORER_COIN_DECIMALS=18
    volumes:
      - explorer-data:/data
    networks:
      backend:
        aliases:
          - explorer
    depends_on:
      msgd-node:
        condition: service_healthy
      indexer:
        condition: service_healthy
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "0.5"
          memory: "512M"

  # ============================================================
  # Nginx 反向代理
  # ============================================================
  nginx:
    container_name: msgd-nginx
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - nginx-cache:/var/cache/nginx
    networks:
      backend:
      frontend:
        aliases:
          - api.msgchain.local
          - explorer.msgchain.local
    depends_on:
      msgd-node:
        condition: service_healthy
      indexer:
        condition: service_started
      explorer:
        condition: service_started
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "0.5"
          memory: "256M"

volumes:
  msgd-data:
  msgd-config:
  msgd-keyring:
  postgres-data:
  redis-data:
  indexer-data:
  explorer-data:
  nginx-cache:

networks:
  backend:
    name: msgd-backend
    driver: bridge
    internal: true
    ipam:
      config:
        - subnet: 172.21.0.0/16
  frontend:
    name: msgd-frontend
    driver: bridge
    ipam:
      config:
        - subnet: 172.22.0.0/16

6.3 PostgreSQL 初始化脚本

-- docker/postgres/init.sql
-- MSG Chain Indexer PostgreSQL 初始化

CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS hstore;

CREATE TABLE IF NOT EXISTS blocks (
    height          BIGINT PRIMARY KEY,
    hash            VARCHAR(64) NOT NULL UNIQUE,
    proposer        VARCHAR(64),
    num_txs         INTEGER DEFAULT 0,
    total_gas       BIGINT DEFAULT 0,
    timestamp       TIMESTAMP WITH TIME ZONE NOT NULL,
    chain_id        VARCHAR(32) DEFAULT 'msg-chain-1',
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS transactions (
    tx_hash         VARCHAR(64) PRIMARY KEY,
    height          BIGINT NOT NULL REFERENCES blocks(height),
    index           INTEGER NOT NULL,
    sender          VARCHAR(64),
    gas_wanted      BIGINT,
    gas_used        BIGINT,
    gas_fee         NUMERIC(78, 0),
    status          VARCHAR(16) DEFAULT 'success',
    memo            TEXT,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS messages (
    id              SERIAL PRIMARY KEY,
    tx_hash         VARCHAR(64) NOT NULL REFERENCES transactions(tx_hash),
    msg_type        VARCHAR(64) NOT NULL,
    msg_content     JSONB,
    contract_address VARCHAR(64),
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS contracts (
    address         VARCHAR(64) PRIMARY KEY,
    code_id         BIGINT NOT NULL,
    creator         VARCHAR(64),
    tx_hash         VARCHAR(64) REFERENCES transactions(tx_hash),
    label           VARCHAR(255),
    admin           VARCHAR(64),
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS contract_codes (
    code_id         BIGINT PRIMARY KEY,
    creator         VARCHAR(64),
    tx_hash         VARCHAR(64) REFERENCES transactions(tx_hash),
    data_hash       VARCHAR(64),
    wasm_size       BIGINT,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS accounts (
    address         VARCHAR(64) PRIMARY KEY,
    balance_umsg    NUMERIC(78, 0) DEFAULT 0,
    nonce           BIGINT DEFAULT 0,
    first_seen      TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    last_active     TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS memos (
    id              SERIAL PRIMARY KEY,
    tx_hash         VARCHAR(64) NOT NULL REFERENCES transactions(tx_hash),
    memo_text       TEXT NOT NULL,
    sender          VARCHAR(64),
    height          BIGINT NOT NULL,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_memos_text ON memos USING gin(to_tsvector('simple', memo_text));
CREATE INDEX IF NOT EXISTS idx_memos_sender ON memos(sender);
CREATE INDEX IF NOT EXISTS idx_memos_height ON memos(height DESC);
CREATE INDEX IF NOT EXISTS idx_tx_height ON transactions(height DESC);
CREATE INDEX IF NOT EXISTS idx_tx_sender ON transactions(sender);
CREATE INDEX IF NOT EXISTS idx_blocks_timestamp ON blocks(timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_messages_type ON messages(msg_type);
CREATE INDEX IF NOT EXISTS idx_messages_contract ON messages(contract_address);
CREATE INDEX IF NOT EXISTS idx_contracts_code_id ON contracts(code_id);

CREATE TABLE IF NOT EXISTS indexer_meta (
    key             VARCHAR(64) PRIMARY KEY,
    value           TEXT NOT NULL,
    updated_at      TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

INSERT INTO indexer_meta (key, value) VALUES
    ('version', '0.1.0'),
    ('chain_id', 'msg-chain-1'),
    ('mode', 'local_preflight_only'),
    ('last_synced_height', '0'),
    ('retention_proof_enabled', 'false'),
    ('slo_tracking_enabled', 'false')
ON CONFLICT (key) DO NOTHING;

CREATE USER IF NOT EXISTS indexer_user WITH PASSWORD 'indexer_dev_password';
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO indexer_user;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO indexer_user;

6.4 Nginx 反向代理配置

# docker/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    multi_accept on;
    use epoll;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    client_max_body_size 100M;

    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/octet-stream;

    include /etc/nginx/conf.d/*.conf;
}
# docker/nginx/conf.d/msgchain.conf
# MSG Chain 反向代理路由配置

upstream msgd_rpc {
    server msgd-node:26657;
    keepalive 32;
}

upstream msgd_rest {
    server msgd-node:1317;
    keepalive 32;
}

upstream msgd_indexer {
    server indexer:8080;
    keepalive 16;
}

upstream msgd_explorer {
    server explorer:80;
    keepalive 16;
}

server {
    listen 80;
    server_name rpc.msgchain.local;

    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
    add_header Access-Control-Allow-Headers "Content-Type, Authorization";
    add_header Access-Control-Expose-Headers "Content-Type, Content-Length";

    if ($request_method = OPTIONS) { return 204; }

    location / {
        proxy_pass http://msgd_rpc;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

server {
    listen 80;
    server_name rest.msgchain.local;

    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
    add_header Access-Control-Allow-Headers "Content-Type, Authorization";

    if ($request_method = OPTIONS) { return 204; }

    location / {
        proxy_pass http://msgd_rest;
        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;
    }
}

server {
    listen 80;
    server_name indexer.msgchain.local;

    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
    add_header Access-Control-Allow-Headers "Content-Type, Authorization";

    if ($request_method = OPTIONS) { return 204; }

    location / {
        proxy_pass http://msgd_indexer;
        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;
    }
}

server {
    listen 80;
    server_name explorer.msgchain.local;

    location / {
        proxy_pass http://msgd_explorer;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
    }
}

server {
    listen 80 default_server;
    root /usr/share/nginx/html;
    index index.html;
    location / {
        return 200 'MSG Chain 开发环境\n\n可用服务:\n  RPC:       http://rpc.msgchain.local\n  REST:      http://rest.msgchain.local\n  Indexer:   http://indexer.msgchain.local\n  Explorer:  http://explorer.msgchain.local\n';
        add_header Content-Type text/plain;
    }
}

6.5 Indexer Dockerfile (本地开发)

# docker/indexer/Dockerfile
# MSG Chain Indexer — 本地开发版本
# 当前状态: 部分实现 (local_preflight_only=true)

FROM node:18-bullseye AS builder

WORKDIR /src
COPY indexer/package*.json ./
RUN npm install
COPY indexer/ ./

FROM node:18-alpine AS runtime

RUN apk add --no-cache curl jq

WORKDIR /app
COPY --from=builder /src /app

HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD curl -sf http://localhost:8080/api/v1/indexer/health || exit 1

EXPOSE 8080

ENV INDEXER_MODE=local_preflight_only

CMD ["node", "src/server.js"]

6.6 Explorer Dockerfile (本地开发)

# docker/explorer/Dockerfile
# MSG Chain Explorer — 本地开发版本
# 当前状态: 部分实现
# 已关闭: contract source metadata, canonical source query,
#          selected-contract event query, WS logs filter,
#          hash search committed-index fail-closed

FROM node:18-alpine AS builder

WORKDIR /src
COPY explorer/package*.json ./
RUN npm ci
COPY explorer/ ./

ARG CHAIN_ID=msg-chain-1
ARG BECH32_PREFIX=msg
ENV NEXT_PUBLIC_CHAIN_ID=$CHAIN_ID
ENV NEXT_PUBLIC_BECH32_PREFIX=$BECH32_PREFIX

RUN npm run build

FROM nginx:1.25-alpine AS runtime

COPY --from=builder /src/out /usr/share/nginx/html
COPY explorer/nginx.conf /etc/nginx/conf.d/default.conf
COPY explorer/inject-config.sh /docker-entrypoint.d/inject-config.sh
RUN chmod +x /docker-entrypoint.d/inject-config.sh

EXPOSE 80

HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
    CMD wget -qO- http://localhost:80/ || exit 1

CMD ["nginx", "-g", "daemon off;"]

6.7 完整栈启动

# 启动完整开发栈
docker compose -f docker/docker-compose.full.yml up -d

# 查看所有服务状态
docker compose -f docker/docker-compose.full.yml ps

# 查看日志
docker compose -f docker/docker-compose.full.yml logs -f

# 验证 Indexer 健康 (部分实现)
curl http://localhost:8081/api/v1/indexer/health
# 预期: {"status":"ok","mode":"local_preflight_only","chain_id":"msg-chain-1"}

# 验证 Indexer 能力
curl http://localhost:8081/api/v1/indexer/capabilities
# 预期: {"txs":true,"contracts":true,"memos":true,"retention_proof":false,"slo_tracking":false}

# 查询备忘录 (Indexer 部分实现功能)
curl http://localhost:8081/api/v1/indexer/memos?limit=10

# 搜索备忘录
curl "http://localhost:8081/api/v1/indexer/memos/search?q=hello&limit=10"

# 获取备忘录类型
curl http://localhost:8081/api/v1/indexer/memos/types

# 停止完整栈
docker compose -f docker/docker-compose.full.yml down

# 完全重置 (删除所有数据)
docker compose -f docker/docker-compose.full.yml down -v

七、CosmWasm 合约开发容器

7.1 合约编译器 Dockerfile

# docker/contract-builder/Dockerfile
# MSG Chain CosmWasm 合约编译器 — 用于构建和优化 WASM 合约

FROM rust:1.75.0-bullseye AS base

RUN rustup target add wasm32-unknown-unknown

RUN apt-get update && apt-get install -y --no-install-recommends \
    binaryen \
    clang \
    libclang-dev \
    libssl-dev \
    pkg-config \
    curl \
    jq \
    && rm -rf /var/lib/apt/lists/*

RUN cargo install wasm-opt

WORKDIR /contracts

7.2 docker-compose 合约开发服务

# docker/docker-compose.contracts.yml
# MSG Chain CosmWasm 合约开发环境

version: "3.9"

services:
  # ============================================================
  # 合约编译器
  # ============================================================
  contract-builder:
    container_name: msgd-contract-builder
    build:
      context: ..
      dockerfile: docker/contract-builder/Dockerfile
    volumes:
      - ../contracts:/contracts
      - ../build/wasm:/output
      - cargo-cache:/usr/local/cargo/registry
      - cargo-target:/contracts/target
    environment:
      - RUSTFLAGS=-C link-arg=-s
      - CARGO_NET_RETRY=3
    working_dir: /contracts
    entrypoint: ["/bin/bash"]
    command: ["-c", "for dir in */; do if [ -f \"${dir}Cargo.toml\" ]; then echo \"Building ${dir}...\"; cd \"$dir\"; RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown 2>&1; cd /contracts; fi; done; echo 'All contracts built.'"]
    profiles:
      - contracts
    restart: "no"

  # ============================================================
  # WASM 优化器
  # ============================================================
  wasm-optimizer:
    container_name: msgd-wasm-opt
    build:
      context: ..
      dockerfile: docker/contract-builder/Dockerfile
    volumes:
      - ../build/wasm:/wasm
    entrypoint: ["/bin/bash"]
    command: ["-c", "for f in /wasm/*.wasm; do echo \"Optimizing $(basename $f)...\"; wasm-opt -Os -o \"${f%.wasm}_optimized.wasm\" \"$f\" 2>&1; done; echo 'Optimization complete.'; ls -lh /wasm/"]
    profiles:
      - contracts
    restart: "no"

  # ============================================================
  # 合约部署工具
  # ============================================================
  contract-deployer:
    container_name: msgd-contract-deployer
    image: msgchain/node:latest
    build:
      context: ..
      dockerfile: docker/msgd/Dockerfile
      target: runtime
    volumes:
      - ../build/wasm:/wasm
      - ../scripts/deploy-contract.sh:/deploy-contract.sh:ro
    environment:
      - CHAIN_ID=msg-chain-1
      - NODE=tcp://msgd-node:26657
      - KEYRING_BACKEND=test
      - FROM=validator
      - GAS_PRICES=1000000000umsg
      - GAS_ADJUSTMENT=1.3
    entrypoint: ["/bin/bash"]
    command: ["/deploy-contract.sh"]
    profiles:
      - contracts
    depends_on:
      msgd-node:
        condition: service_healthy
    networks:
      - msgnet
    restart: "no"

networks:
  msgnet:
    name: msgnet
    external: true

volumes:
  cargo-cache:
  cargo-target:

7.3 合约部署脚本

#!/bin/bash
# scripts/deploy-contract.sh — 自动部署 CosmWasm 合约到 MSG Chain
set -euo pipefail

CHAIN_ID="${CHAIN_ID:-msg-chain-1}"
NODE="${NODE:-tcp://localhost:26657}"
FROM="${FROM:-validator}"
KEYRING_BACKEND="${KEYRING_BACKEND:-test}"
GAS_PRICES="${GAS_PRICES:-1000000000umsg}"
GAS_ADJUSTMENT="${GAS_ADJUSTMENT:-1.3}"

WASM_DIR="/wasm"

echo "=== MSG Chain 合约部署工具 ==="
echo "Chain ID: $CHAIN_ID"
echo "Node: $NODE"
echo ""

WASM_FILES=($(find "$WASM_DIR" -name "*.wasm" -type f 2>/dev/null))

if [ ${#WASM_FILES[@]} -eq 0 ]; then
    echo "错误: 在 $WASM_DIR 中未找到 .wasm 文件"
    exit 1
fi

echo "找到 ${#WASM_FILES[@]} 个 WASM 文件:"
for f in "${WASM_FILES[@]}"; do
    SIZE=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f" 2>/dev/null || echo "?")
    echo "  $(basename $f) ($SIZE bytes)"
done
echo ""

for WASM_PATH in "${WASM_FILES[@]}"; do
    WASM_FILE=$(basename "$WASM_PATH")
    CONTRACT_NAME="${WASM_FILE%.wasm}"
    CONTRACT_NAME="${CONTRACT_NAME%_optimized}"

    echo "--- 部署合约: $CONTRACT_NAME ---"

    echo "[1/3] 上传合约代码..."
    STORE_OUTPUT=$(msgd tx wasm store "$WASM_PATH" \
        --from "$FROM" \
        --gas auto \
        --gas-prices "$GAS_PRICES" \
        --gas-adjustment "$GAS_ADJUSTMENT" \
        --chain-id "$CHAIN_ID" \
        --keyring-backend "$KEYRING_BACKEND" \
        --node "$NODE" \
        -y 2>&1)

    echo "$STORE_OUTPUT"

    CODE_ID=$(echo "$STORE_OUTPUT" | grep -oP 'code_id: \K\d+' || true)
    if [ -z "$CODE_ID" ]; then
        echo "错误: 无法获取 Code ID,跳过"
        continue
    fi
    echo ">> Code ID: $CODE_ID"

    echo "[2/3] 实例化合约..."
    INIT_MSG='{}'
    INSTANTIATE_OUTPUT=$(msgd tx wasm instantiate "$CODE_ID" "$INIT_MSG" \
        --from "$FROM" \
        --label "$CONTRACT_NAME" \
        --gas auto \
        --gas-prices "$GAS_PRICES" \
        --gas-adjustment "$GAS_ADJUSTMENT" \
        --chain-id "$CHAIN_ID" \
        --keyring-backend "$KEYRING_BACKEND" \
        --node "$NODE" \
        -y 2>&1)

    echo "$INSTANTIATE_OUTPUT"

    CONTRACT_ADDR=$(echo "$INSTANTIATE_OUTPUT" | grep -oP 'contract_address: \K\S+' || true)
    if [ -n "$CONTRACT_ADDR" ]; then
        echo ">> 合约地址: $CONTRACT_ADDR"

        echo "[3/3] 验证合约..."
        sleep 2
        QUERY_RESULT=$(msgd query wasm contract-state smart "$CONTRACT_ADDR" '{}' \
            --node "$NODE" 2>&1 || true)
        echo ">> 查询结果: $QUERY_RESULT"

        echo "$CONTRACT_NAME | Code: $CODE_ID | Address: $CONTRACT_ADDR" >> /wasm/deployed_contracts.txt
    fi

    echo "--- 完成: $CONTRACT_NAME ---"
    echo ""
done

echo "=== 合约部署完成! ==="
cat /wasm/deployed_contracts.txt 2>/dev/null || true

7.4 合约开发工作流

# 1. 启动 MSG Chain 节点
docker compose -f docker/docker-compose.single.yml up -d

# 2. 构建所有合约
docker compose -f docker/docker-compose.contracts.yml run --rm contract-builder

# 3. 优化 WASM 文件
docker compose -f docker/docker-compose.contracts.yml run --rm wasm-optimizer

# 4. 部署合约到本地节点
docker compose -f docker/docker-compose.contracts.yml run --rm contract-deployer

# 5. 与合约交互
docker exec msgd-node msgd query wasm contract-state smart \
  msg1contract_address... \
  '{"get_count":{}}' \
  --node tcp://localhost:26657

docker exec msgd-node msgd tx wasm execute \
  msg1contract_address... \
  '{"increment":{}}' \
  --from validator \
  --gas auto \
  --gas-prices 1000000000umsg \
  --chain-id msg-chain-1 \
  --keyring-backend test \
  --node tcp://localhost:26657 \
  -y

八、前端开发环境

8.1 前端开发容器

# docker/frontend/Dockerfile
# MSG Chain 前端开发环境 — 支持 Keplr 钱包集成、CosmJS 客户端、热重载

FROM node:18-bullseye AS deps

WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:18-bullseye AS dev

WORKDIR /app
RUN npm install -g create-react-app next vite typescript
RUN npm install -g \
    @cosmjs/cosmwasm-stargate \
    @cosmjs/proto-signing \
    @cosmjs/encoding \
    @cosmjs/stargate \
    @cosmjs/amino

ENV REACT_APP_CHAIN_ID=msg-chain-1
ENV REACT_APP_RPC_ENDPOINT=http://localhost:26657
ENV REACT_APP_REST_ENDPOINT=http://localhost:1317
ENV REACT_APP_BECH32_PREFIX=msg
ENV REACT_APP_GAS_PRICE=1000000000umsg

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
    CMD curl -sf http://localhost:3000/ || exit 1

CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

8.2 docker-compose 前端服务

# docker/docker-compose.frontend.yml
# MSG Chain 前端开发环境

version: "3.9"

services:
  frontend-dev:
    container_name: msgd-frontend-dev
    build:
      context: ..
      dockerfile: docker/frontend/Dockerfile
      target: dev
    ports:
      - "3000:3000"
    volumes:
      - ../frontend:/app
      - /app/node_modules
    environment:
      - REACT_APP_CHAIN_ID=msg-chain-1
      - REACT_APP_RPC_ENDPOINT=http://localhost:26657
      - REACT_APP_REST_ENDPOINT=http://localhost:1317
      - REACT_APP_BECH32_PREFIX=msg
      - REACT_APP_GAS_PRICE=1000000000umsg
      - REACT_APP_IS_LOCAL=true
      - REACT_APP_EXPLORER_URL=http://localhost:8082
      - REACT_APP_INDEXER_URL=http://localhost:8081
      - NODE_ENV=development
      - REACT_APP_KEPLR_USE_HTTP=true
    networks:
      - msgnet
    depends_on:
      msgd-node:
        condition: service_healthy
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: "2G"
    profiles:
      - frontend

networks:
  msgnet:
    name: msgnet
    external: true

8.3 开发代理配置

// frontend/vite.config.ts — Vite 开发环境代理配置
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    host: '0.0.0.0',
    port: 3000,
    proxy: {
      '/cosmos': {
        target: 'http://msgd-node:26657',
        changeOrigin: true,
      },
      '/api': {
        target: 'http://msgd-node:1317',
        changeOrigin: true,
      },
      '/indexer': {
        target: 'http://indexer:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/indexer/, '/api/v1/indexer'),
      },
      '/websocket': {
        target: 'ws://msgd-node:26657',
        ws: true,
        changeOrigin: true,
      },
    },
  },
});

8.4 Keplr 集成

// frontend/src/config/chain.ts
export const msgChainConfig = {
  chainId: "msg-chain-1",
  chainName: "MSG Chain Local",
  rpc: process.env.REACT_APP_RPC_ENDPOINT || "http://localhost:26657",
  rest: process.env.REACT_APP_REST_ENDPOINT || "http://localhost:1317",
  bip44: { coinType: 118 },
  bech32Config: {
    bech32PrefixAccAddr: "msg",
    bech32PrefixAccPub: "msgpub",
    bech32PrefixValAddr: "msgvaloper",
    bech32PrefixValPub: "msgvaloperpub",
    bech32PrefixConsAddr: "msgvalcons",
    bech32PrefixConsPub: "msgvalconspub",
  },
  currencies: [{ coinDenom: "MSG", coinMinimalDenom: "umsg", coinDecimals: 18 }],
  feeCurrencies: [{ coinDenom: "MSG", coinMinimalDenom: "umsg", coinDecimals: 18, gasPriceStep: { low: 1e9, average: 1e9, high: 1e9 } }],
  stakeCurrency: { coinDenom: "MSG", coinMinimalDenom: "umsg", coinDecimals: 18 },
};

8.5 前端开发工作流

# 1. 启动 MSG Chain 节点
docker compose -f docker/docker-compose.single.yml up -d

# 2. 创建前端项目
mkdir -p frontend && cd frontend
docker run --rm -v $(pwd):/app -w /app node:18 bash -c "
    npm create vite@latest . -- --template react-ts && \
    npm install && \
    npm install @cosmjs/cosmwasm-stargate @cosmjs/proto-signing \
        @cosmjs/encoding @cosmjs/stargate @cosmjs/amino && \
    npm install --save-dev @keplr-wallet/types
"
cd ..

# 3. 启动前端服务器
docker compose -f docker/docker-compose.frontend.yml up -d

# 4. 访问 http://localhost:3000

# 5. 停止前端
docker compose -f docker/docker-compose.frontend.yml down

九、数据持久化

9.1 Volume 策略

msgd-data       → 区块链状态数据 (BadgerDB)
  ├── blockstore.db/   区块存储
  ├── cs.wal/          共识 WAL
  ├── evidence.db/     证据存储
  ├── state.db/        应用状态
  └── tx_index.db/     交易索引

msgd-config     → 节点配置文件
  ├── genesis.json
  ├── app.toml
  ├── config.toml
  └── client.toml

msgd-keyring    → 测试密钥环

postgres-data   → 索引器数据库
redis-data      → 缓存数据
indexer-data    → 索引器状态
explorer-data   → 浏览器缓存

9.2 备份与恢复

#!/bin/bash
# scripts/backup.sh — MSG Chain Docker 数据备份
set -euo pipefail

BACKUP_DIR="${BACKUP_DIR:-./backups}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_PATH="$BACKUP_DIR/msgchain_docker_$TIMESTAMP"

echo "=== MSG Chain Docker 数据备份 ==="
mkdir -p "$BACKUP_PATH"

# 备份链数据 (BadgerDB)
if docker volume inspect msgd-data &>/dev/null; then
    docker run --rm -v msgd-data:/source -v "$BACKUP_PATH:/backup" \
        alpine:latest tar -czf /backup/chain-data.tar.gz -C /source .
    echo "  链数据已备份"
fi

# 备份配置
if docker volume inspect msgd-config &>/dev/null; then
    docker run --rm -v msgd-config:/source -v "$BACKUP_PATH:/backup" \
        alpine:latest tar -czf /backup/chain-config.tar.gz -C /source .
    echo "  配置已备份"
fi

# 备份 PostgreSQL
if docker container inspect msgd-postgres &>/dev/null; then
    docker exec msgd-postgres pg_dump -U msgchain msgchain_indexer \
        > "$BACKUP_PATH/postgres_dump.sql" 2>/dev/null || true
    [ -s "$BACKUP_PATH/postgres_dump.sql" ] && gzip "$BACKUP_PATH/postgres_dump.sql" \
        && echo "  PostgreSQL 已备份" || rm -f "$BACKUP_PATH/postgres_dump.sql"
fi

echo "备份位置: $BACKUP_PATH"
echo "文件列表:"
ls -lh "$BACKUP_PATH/"
#!/bin/bash
# scripts/restore.sh — MSG Chain Docker 数据恢复
set -euo pipefail

BACKUP_DIR="${1:-}"
[ -z "$BACKUP_DIR" ] && echo "用法: $0 <备份目录>" && exit 1

echo "=== MSG Chain Docker 数据恢复 ==="

docker stop msgd-node 2>/dev/null || true
docker stop msgd-postgres 2>/dev/null || true

[ -f "$BACKUP_DIR/chain-data.tar.gz" ] && \
    docker run --rm -v msgd-data:/target -v "$BACKUP_DIR:/backup" \
        alpine:latest sh -c "rm -rf /target/* && tar -xzf /backup/chain-data.tar.gz -C /target" \
    && echo "链数据已恢复"

[ -f "$BACKUP_DIR/chain-config.tar.gz" ] && \
    docker run --rm -v msgd-config:/target -v "$BACKUP_DIR:/backup" \
        alpine:latest sh -c "rm -rf /target/* && tar -xzf /backup/chain-config.tar.gz -C /target" \
    && echo "配置已恢复"

[ -f "$BACKUP_DIR/postgres_dump.sql.gz" ] && \
    gunzip -c "$BACKUP_DIR/postgres_dump.sql.gz" | docker exec -i msgd-postgres psql -U msgchain msgchain_indexer \
    && echo "PostgreSQL 已恢复"

docker start msgd-node 2>/dev/null || true
docker start msgd-postgres 2>/dev/null || true
echo "=== 恢复完成 ==="

9.3 状态重置和快照

#!/bin/bash
# scripts/reset-state.sh — 状态重置
echo "=== MSG Chain 状态重置 ==="
echo "将删除所有区块数据,保留配置和密钥"
read -p "确认? (RESET): " CONFIRM
[ "$CONFIRM" != "RESET" ] && echo "已取消" && exit 0

docker stop msgd-node 2>/dev/null || true
docker run --rm -v msgd-data:/data alpine:latest sh -c "rm -rf /data/*"
docker start msgd-node 2>/dev/null || true
echo "状态已重置,节点将自动重新初始化"
#!/bin/bash
# scripts/snapshot.sh — 创建快照
SNAPSHOT_DIR="${SNAPSHOT_DIR:-./snapshots}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "$SNAPSHOT_DIR"

BLOCK_HEIGHT=$(curl -sf http://localhost:26657/status 2>/dev/null | \
    jq -r '.result.sync_info.latest_block_height' 2>/dev/null || echo "unknown")
echo "当前区块高度: $BLOCK_HEIGHT"

docker pause msgd-node 2>/dev/null || docker stop msgd-node 2>/dev/null || true

docker run --rm -v msgd-data:/source -v "$(pwd)/$SNAPSHOT_DIR:/snapshot" \
    alpine:latest tar -czf "/snapshot/msgchain_snapshot_$TIMESTAMP.tar.gz" \
        -C /source --exclude=cs.wal --exclude=*.sst.tmp .

docker unpause msgd-node 2>/dev/null || docker start msgd-node 2>/dev/null || true

echo "快照文件: $SNAPSHOT_DIR/msgchain_snapshot_$TIMESTAMP.tar.gz"

9.4 Volume 管理

# 列出 volumes
docker volume ls | grep msgd

# 查看 volume 详情
docker volume inspect msgd-data

# 手动备份 volume
docker run --rm -v msgd-data:/source -v $(pwd):/backup alpine tar -czf /backup/msgd-data.tar.gz -C /source .

# 手动恢复 volume
docker run --rm -v msgd-data:/target -v $(pwd):/backup alpine sh -c "rm -rf /target/* && tar -xzf /backup/msgd-data.tar.gz -C /target"

# 删除 volumes
docker volume rm msgd-data msgd-config msgd-keyring

# 查看 volume 使用量
docker run --rm -v msgd-data:/data alpine du -sh /data

十、网络配置

10.1 Docker 网络拓扑

                  主机 (Host)
                     |
              msgd-nginx (80, 443)
                     |
             frontend 网络 (172.22.0.0/16)
                     |
             backend 网络 (internal, 172.21.0.0/16)
                     |
   msgd-node  indexer  explorer  postgres  redis
   .2         .3       .4        .5        .6

10.2 内部网络配置

# 内部网络 (backend) — 服务间通信,不对外暴露
networks:
  backend:
    name: msgd-backend
    driver: bridge
    internal: true
    ipam:
      config:
        - subnet: 172.21.0.0/16
  frontend:
    name: msgd-frontend
    driver: bridge
    ipam:
      config:
        - subnet: 172.22.0.0/16

10.3 端口暴露策略

服务 内部端口 主机端口 范围 说明
msgd-node:26657 26657 26657 主机 RPC (Keplr 直连)
msgd-node:1317 1317 1317 主机 REST API
msgd-node:9090 9090 - 内部 gRPC
msgd-node:26656 26656 26656 主机 P2P
nginx:80 80 80 主机 HTTP 代理
postgres:5432 5432 5432 主机 数据库管理
redis:6379 6379 6379 主机 缓存管理
indexer:8080 8080 8081 主机 Indexer API
explorer:80 80 8082 主机 Explorer UI

10.4 CORS 配置

# Keplr 需要 CORS 支持 (在 entrypoint.sh 已自动配置)
sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = ["*"]/' \
    /home/msgchain/.msgchain/config/config.toml

# 更安全的 CORS (指定域名)
# cors_allowed_origins = [
#   "http://localhost:3000",
#   "chrome-extension://*"
# ]

10.5 WebSocket 支持

// 前端 WebSocket 连接
export function connectWebSocket() {
  const ws = new WebSocket("ws://localhost:26657/websocket");

  ws.onopen = () => {
    ws.send(JSON.stringify({
      jsonrpc: "2.0",
      method: "subscribe",
      id: 1,
      params: { query: "tm.event='NewBlock'" },
    }));
    ws.send(JSON.stringify({
      jsonrpc: "2.0",
      method: "subscribe",
      id: 2,
      params: { query: "tm.event='Tx'" },
    }));
  };

  ws.onmessage = (event) => {
    console.log("WebSocket 消息:", JSON.parse(event.data));
  };

  ws.onclose = () => {
    setTimeout(connectWebSocket, 3000); // 自动重连
  };

  return ws;
}

10.6 跨容器服务发现

# 容器内通过服务名互相访问
curl http://msgd-node:26657/status
curl http://msgd-node:1317/cosmos/base/tendermint/v1beta1/node_info
curl http://indexer:8080/api/v1/indexer/health

# 测试连通性
docker exec msgd-indexer ping msgd-node
docker exec msgd-explorer curl -sf http://msgd-node:26657/health

十一、监控与日志

11.1 Prometheus + Grafana 监控栈

# docker/docker-compose.monitoring.yml
# MSG Chain 监控栈 — Prometheus + Grafana + Loki

version: "3.9"

services:
  prometheus:
    container_name: msgd-prometheus
    image: prom/prometheus:v2.49.0
    ports:
      - "9092:9090"
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.path=/prometheus"
      - "--storage.tsdb.retention.time=30d"
    networks:
      - msgnet
    restart: unless-stopped

  grafana:
    container_name: msgd-grafana
    image: grafana/grafana:10.2.0
    ports:
      - "3001:3000"
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_AUTH_ANONYMOUS_ENABLED=true
    volumes:
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
      - ./grafana/datasources:/etc/grafana/provisioning/datasources:ro
      - grafana-data:/var/lib/grafana
    networks:
      - msgnet
    depends_on:
      - prometheus
    restart: unless-stopped

  loki:
    container_name: msgd-loki
    image: grafana/loki:2.9.0
    ports:
      - "3100:3100"
    volumes:
      - ./loki/loki.yml:/etc/loki/loki.yml:ro
      - loki-data:/loki
    command: -config.file=/etc/loki/loki.yml
    networks:
      - msgnet
    restart: unless-stopped

  promtail:
    container_name: msgd-promtail
    image: grafana/promtail:2.9.0
    volumes:
      - ./promtail/promtail.yml:/etc/promtail/promtail.yml:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
    command: -config.file=/etc/promtail/promtail.yml
    networks:
      - msgnet
    depends_on:
      - loki
    restart: unless-stopped

  node-exporter:
    container_name: msgd-node-exporter
    image: prom/node-exporter:v1.6.0
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - "--path.procfs=/host/proc"
      - "--path.sysfs=/host/sys"
      - "--path.rootfs=/rootfs"
    networks:
      - msgnet
    restart: unless-stopped

volumes:
  prometheus-data:
  grafana-data:
  loki-data:

networks:
  msgnet:
    name: msgnet
    external: true

11.2 Prometheus 配置

# docker/prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s
  external_labels:
    cluster: "msg-chain-local"
    environment: "development"

scrape_configs:
  - job_name: "msgd-node"
    scrape_interval: 10s
    metrics_path: "/metrics"
    static_configs:
      - targets: ["msgd-node:26660"]
        labels:
          service: "msgd-node"
          chain_id: "msg-chain-1"

  - job_name: "node-exporter"
    scrape_interval: 30s
    static_configs:
      - targets: ["node-exporter:9100"]

  - job_name: "docker"
    scrape_interval: 30s
    static_configs:
      - targets: ["host.docker.internal:9323"]

11.3 Grafana 仪表板

{
  "dashboard": {
    "title": "MSG Chain 节点监控 (Docker)",
    "tags": ["msg-chain", "docker", "development"],
    "panels": [
      {
        "title": "区块高度",
        "type": "stat",
        "targets": [{"expr": "msgd_tendermint_block_height"}]
      },
      {
        "title": "对等节点数",
        "type": "stat",
        "targets": [{"expr": "msgd_tendermint_p2p_peers"}],
        "thresholds": [
          {"color": "red", "value": null},
          {"color": "yellow", "value": 1},
          {"color": "green", "value": 3}
        ]
      },
      {
        "title": "出块速率",
        "type": "timeseries",
        "targets": [{"expr": "rate(msgd_tendermint_block_height[5m])"}]
      },
      {
        "title": "Mempool 大小",
        "type": "timeseries",
        "targets": [{"expr": "msgd_tendermint_mempool_size"}]
      },
      {
        "title": "CPU 使用率",
        "type": "timeseries",
        "targets": [{"expr": "rate(container_cpu_usage_seconds_total{container_label_com_docker_compose_service=\"msgd-node\"}[1m]) * 100"}]
      },
      {
        "title": "内存使用 (MB)",
        "type": "timeseries",
        "targets": [{"expr": "container_memory_usage_bytes{container_label_com_docker_compose_service=\"msgd-node\"} / 1024 / 1024"}]
      },
      {
        "title": "Gas 使用",
        "type": "timeseries",
        "targets": [{"expr": "rate(msgd_tendermint_total_gas[5m])"}]
      }
    ]
  }
}

11.4 Loki 日志配置

# docker/loki/loki.yml
auth_enabled: false
server:
  http_listen_port: 3100
common:
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
schema_config:
  configs:
    - from: 2020-10-24
      store: boltdb-shipper
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 24h
# docker/promtail/promtail.yml
server:
  http_listen_port: 9080
  grpc_listen_port: 0
positions:
  filename: /tmp/positions.yaml
clients:
  - url: http://loki:3100/loki/api/v1/push
scrape_configs:
  - job_name: docker
    pipeline_stages:
      - docker: {}
    static_configs:
      - labels:
          job: "msgd-node"
          __path__: /var/lib/docker/containers/*/*-json.log

11.5 健康检查

# 查看容器健康状态
docker inspect --format='{{.State.Health.Status}}' msgd-node

# 等待节点健康
while [ "$(docker inspect --format='{{.State.Health.Status}}' msgd-node)" != "healthy" ]; do
  sleep 2
done

# 查看健康检查日志
docker inspect --format='{{json .State.Health}}' msgd-node | jq

十二、生产部署考量

12.1 当前边界

重要提示: MSG Chain 目前处于开发阶段,以下尚未达到生产就绪:

组件 状态 说明
公共测试网 未部署 仅支持本地 Docker 开发环境
Explorer 部分实现 多项功能未完成,仅本地验证
Indexer 部分实现 local_preflight_only=true,生产 SLO 未关闭

12.2 Docker Swarm 部署

# docker/docker-stack.yml — Docker Swarm 部署
version: "3.9"

services:
  msgd-node:
    image: msgchain/node:latest
    ports:
      - target: 26656
        published: 26656
        protocol: tcp
        mode: host
      - target: 26657
        published: 26657
        protocol: tcp
        mode: host
    environment:
      - MONIKER={{.Node.Hostname}}
      - CHAIN_ID=msg-chain-1
      - LOG_LEVEL=info
    volumes:
      - msgd-data:/home/msgchain/.msgchain/data
    networks:
      - msgnet
    deploy:
      mode: replicated
      replicas: 1
      resources:
        limits:
          cpus: "4"
          memory: "8G"
      restart_policy:
        condition: any
        delay: 5s
        max_attempts: 3
      update_config:
        parallelism: 1
        delay: 30s
        order: start-first

volumes:
  msgd-data:
    driver: local

networks:
  msgnet:
    driver: overlay
    attachable: true

12.3 Kubernetes 部署 (参考)

# k8s/msgd-statefulset.yaml — Kubernetes 部署参考
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: msgd-node
  labels:
    app: msgd-node
    chain: msg-chain-1
spec:
  serviceName: msgd-node
  replicas: 1
  selector:
    matchLabels:
      app: msgd-node
  template:
    metadata:
      labels:
        app: msgd-node
    spec:
      containers:
      - name: msgd
        image: msgchain/node:latest
        ports:
        - containerPort: 26656
          name: p2p
        - containerPort: 26657
          name: rpc
        - containerPort: 1317
          name: rest
        - containerPort: 9090
          name: grpc
        env:
        - name: MONIKER
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        - name: CHAIN_ID
          value: "msg-chain-1"
        - name: LOG_LEVEL
          value: "info"
        volumeMounts:
        - name: data
          mountPath: /home/msgchain/.msgchain/data
        - name: config
          mountPath: /home/msgchain/.msgchain/config
        resources:
          requests:
            cpu: "2"
            memory: "4Gi"
          limits:
            cpu: "4"
            memory: "8Gi"
        livenessProbe:
          httpGet:
            path: /health
            port: 26657
          initialDelaySeconds: 30
          periodSeconds: 15
        readinessProbe:
          httpGet:
            path: /status
            port: 26657
          initialDelaySeconds: 10
          periodSeconds: 5
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 100Gi
  - metadata:
      name: config
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

12.4 安全最佳实践

# 1. 使用非 root 用户运行
RUN useradd -m -s /bin/bash msgchain
USER msgchain

# 2. 密钥文件权限
chmod 600 /home/msgchain/.msgchain/config/priv_validator_key.json
chmod 600 /home/msgchain/.msgchain/config/node_key.json

# 3. 镜像签名 (Docker Content Trust)
export DOCKER_CONTENT_TRUST=1
docker pull msgchain/node:latest

# 4. 定期扫描镜像漏洞
docker scout quickview msgchain/node:latest

# 5. 资源限制
docker compose up --scale msgd-node=1 --memory=4g --cpus=2

# 6. 只读根文件系统
# docker-compose.yml 中添加:
# read_only: true
# tmpfs:
#   - /tmp

# 7. 禁止特权模式
# security_opt:
#   - no-new-privileges:true
# cap_drop:
#   - ALL

12.5 密码和密钥管理

# .env 文件 (不要提交到 Git!)
# docker/.env
POSTGRES_PASSWORD=你的强密码
INDEXER_DB_PASSWORD=你的强密码

# .gitignore
docker/.env
*.key
*.pem

# Docker Secrets (Swarm 模式)
echo "你的强密码" | docker secret create db_password -

十三、排错指南

13.1 容器无法启动

# 现象: docker compose up 后容器立即退出

# 1. 查看日志
docker logs msgd-node

# 2. 检查端口冲突
sudo lsof -i :26657
sudo lsof -i :1317
# 修改 docker-compose.yml 中的映射端口

# 3. 检查 Dockerfile 构建错误
docker compose -f docker/docker-compose.single.yml build --no-cache

# 4. 检查 volume 权限
docker run --rm -v msgd-config:/config alpine ls -la /config/

# 5. 重置状态后重试
docker compose -f docker/docker-compose.single.yml down -v
docker compose -f docker/docker-compose.single.yml up -d

13.2 端口冲突

# 现象: Address already in use

# 查找占用端口的进程
sudo lsof -i :26657
# 输出: msgd      12345  user    6u  IPv4  ...  0.0.0.0:26657

# 杀掉进程
sudo kill -9 12345

# 或修改映射端口 (docker-compose.override.yml)
services:
  msgd-node:
    ports:
      - "36657:26657"  # 改为 36657
      - "2317:1317"    # 改为 2317

13.3 Volume 权限问题

# 现象: permission denied 或 cannot create directory

# 问题: Docker 容器内用户 (root) 与主机用户 UID 不匹配

# 解决方案 1: 在 Dockerfile 中指定用户
RUN useradd -u 1000 -m msgchain
USER msgchain

# 解决方案 2: 运行时指定用户
docker compose -f docker/docker-compose.single.yml run -u $(id -u):$(id -g) msgd-node

# 解决方案 3: 修复 volume 权限
docker run --rm -v msgd-data:/data alpine chown -R 1000:1000 /data

13.4 跨平台问题

# M1/M2 Mac (ARM64) 问题

# 问题: Go 编译的二进制在 ARM64 上需要特殊处理

# 解决方案 1: 多架构构建
docker buildx build --platform linux/amd64,linux/arm64 \
  -t msgchain/node:latest .

# 解决方案 2: 模拟 AMD64
docker compose -f docker/docker-compose.single.yml run \
  --platform linux/amd64 msgd-node

# Windows (WSL2) 问题

# 问题: 文件权限和路径分隔符

# 解决方案: 确保 Docker Desktop 使用 WSL2 后端
# Docker Desktop → Settings → General → Use WSL 2 based engine

# 路径问题: 使用 [未公开路径] 而非 [未公开路径]
# 或将项目放在 WSL2 文件系统中 (推荐):
# \\wsl.localhost\Ubuntu\home\user\msgchain

13.5 容器间网络连通问题

# 现象: 容器无法相互访问

# 1. 检查网络是否存在
docker network ls | grep msgnet

# 2. 检查容器是否在同一网络
docker inspect msgd-node | jq '.[].NetworkSettings.Networks'

# 3. 测试连通性
docker exec msgd-indexer ping msgd-node
docker exec msgd-indexer curl -v http://msgd-node:26657/status

# 4. 检查 DNS 解析
docker exec msgd-indexer getent hosts msgd-node

# 5. 使用 IP 地址直连 (绕过 DNS)
docker inspect msgd-node | jq -r '.[].NetworkSettings.Networks.msgnet.IPAddress'
# 然后使用 IP: curl http://172.20.0.2:26657/status

13.6 Keplr 连接问题

# 现象: Keplr 无法添加 MSG Chain 本地网络

# 1. 确认节点 RPC 可访问
curl http://localhost:26657/status

# 2. 确认 REST API 可访问
curl http://localhost:1317/cosmos/base/tendermint/v1beta1/node_info

# 3. 确认 CORS 配置正确
docker exec msgd-node grep cors_allowed_origins \
    /home/msgchain/.msgchain/config/config.toml
# 应该输出: cors_allowed_origins = ["*"]

# 4. 检查浏览器是否阻止混合内容
# 如果 dApp 在 HTTPS,RPC 在 HTTP → 浏览器会阻止
# 解决方案: 本地开发使用 HTTP,或在 localhost 下运行

# 5. 直接通过控制台添加
# 打开浏览器开发者工具 (F12) → Console
await window.keplr.experimentalSuggestChain({
  chainId: "msg-chain-1",
  chainName: "MSG Chain Local",
  rpc: "http://localhost:26657",
  rest: "http://localhost:1317",
  bip44: { coinType: 118 },
  bech32Config: {
    bech32PrefixAccAddr: "msg",
    bech32PrefixAccPub: "msgpub",
    bech32PrefixValAddr: "msgvaloper",
    bech32PrefixValPub: "msgvaloperpub",
    bech32PrefixConsAddr: "msgvalcons",
    bech32PrefixConsPub: "msgvalconspub",
  },
  currencies: [{ coinDenom: "MSG", coinMinimalDenom: "umsg", coinDecimals: 18 }],
  feeCurrencies: [{ coinDenom: "MSG", coinMinimalDenom: "umsg", coinDecimals: 18, gasPriceStep: { low: 1e9, average: 1e9, high: 1e9 } }],
  stakeCurrency: { coinDenom: "MSG", coinMinimalDenom: "umsg", coinDecimals: 18 },
});

13.7 Docker 磁盘空间问题

# 现象: no space left on device

# 1. 清理未使用的容器和镜像
docker system prune -a -f

# 2. 查看磁盘使用
docker system df

# 3. 清理构建缓存
docker builder prune -a -f

# 4. 限制日志大小 (在 docker-compose.yml 中配置)
logging:
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

# 5. 移动 Docker 数据目录 (Linux)
# /etc/docker/daemon.json
# { "data-root": "/mnt/large-disk/docker" }
# sudo systemctl restart docker

13.8 msgd 进程崩溃

# 现象: 容器运行但 msgd 进程退出

# 1. 检查退出代码
docker inspect msgd-node --format='{{.State.ExitCode}}'

# 2. 查看 panic 日志
docker logs msgd-node --tail 100

# 3. 测试 msgd 二进制是否正常
docker exec msgd-node msgd version

# 4. 检查创世文件是否有效
docker exec msgd-node msgd validate-genesis \
    --home /home/msgchain/.msgchain

# 5. 重置状态 (保留配置)
docker exec msgd-node msgd tendermint unsafe-reset-all \
    --home /home/msgchain/.msgchain

# 6. 重启容器
docker restart msgd-node

13.9 合约编译失败

# 现象: cargo build --target wasm32-unknown-unknown 失败

# 1. 确认 Rust 版本
docker run --rm msgchain/contract-builder:latest rustc --version
# 需要 Rust 1.75+

# 2. 确认 wasm32 目标已安装
docker run --rm msgchain/contract-builder:latest rustup target list --installed

# 3. 清理缓存
docker compose -f docker/docker-compose.contracts.yml run --rm \
    -e CARGO_HOME=/usr/local/cargo contract-builder \
    bash -c "cargo clean && cargo build --release --target wasm32-unknown-unknown"

# 4. 检查 Cargo.toml 中 cosmwasm-std 版本 (需要 1.x)

# 5. 检查源代码语法
docker compose -f docker/docker-compose.contracts.yml run --rm \
    contract-builder \
    bash -c "cargo check --target wasm32-unknown-unknown"

13.10 常见错误速查表

错误 原因 解决方案
cannot find package Go 依赖缺失 运行 make deps 重新构建镜像
libwasmvm.so not found CosmWasm 运行时缺失 确保镜像安装 libwasmvm.so.1
connection refused 端口未暴露 检查 docker-compose.yml 的 ports 映射
validator set is empty 没有验证者 确保 entrypoint.sh 正确执行了 gentx
out of gas Gas 不足 提高 --gas-adjustment 或使用 --gas auto
account sequence mismatch nonce 错误 等待前一笔交易确认
signature verification failed Dilithium-5 密钥不匹配 检查 --algo dilithium5
WASM binary too large WASM > 800KB 使用 wasm-opt -Os 优化
catching up 节点在同步 等待同步完成
insufficient funds 余额不足 检查创世账户是否正确配置

附录

A. 常用 Docker 命令速查

# 容器管理
docker ps                      # 列出运行中容器
docker ps -a                   # 列出所有容器
docker logs msgd-node -f       # 跟踪日志
docker exec -it msgd-node bash # 进入容器
docker inspect msgd-node       # 查看容器详情
docker stats                   # 查看资源使用

# 镜像管理
docker images                  # 列出镜像
docker build -t msgchain/node . # 构建镜像
docker tag msgchain/node msgchain/node:v1.0  # 标记版本
docker push msgchain/node:latest  # 推送镜像

# Volume 管理
docker volume ls               # 列出 volumes
docker volume inspect msgd-data # 查看 volume 详情
docker volume rm msgd-data     # 删除 volume

# 网络管理
docker network ls              # 列出网络
docker network inspect msgnet  # 查看网络详情
docker network create msgnet   # 创建网络

# Compose
docker compose ps              # 查看服务状态
docker compose logs -f         # 跟踪所有日志
docker compose down -v         # 停止并删除 volumes
docker compose build --no-cache # 强制重建

B. MSG Chain 端口速查

端口 用途 配置 协议
26656 P2P 通信 config.toml p2p.laddr TCP
26657 RPC / WebSocket config.toml rpc.laddr TCP/WS
1317 REST API app.toml api.address HTTP
9090 gRPC app.toml grpc.address gRPC
9091 gRPC-web app.toml grpc-web.address HTTP
26660 Prometheus 指标 config.toml instrumentation HTTP

C. 目录结构速查

路径 用途 持久化
~/.msgchain/config/ 节点配置文件 是 (msgd-config)
~/.msgchain/data/ BadgerDB 链数据 是 (msgd-data)
~/.msgchain/keyring/ 密钥环 (test) 是 (msgd-keyring)
~/.msgchain/cosmovisor/ 升级管理器 可选
/usr/local/bin/msgd 节点二进制 镜像内
docker/ Docker 配置文件 源码
scripts/ 辅助脚本 源码
contracts/ CosWasm 合约源码 源码
frontend/ dApp 前端源码 源码

D. 环境变量速查

变量 默认值 说明
MONIKER docker-msg-node 节点名称
CHAIN_ID msg-chain-1 链 ID
LOG_LEVEL info 日志级别
MINIMUM_GAS_PRICES 1000000000umsg 最低 Gas 价格
GENESIS_BALANCE 1000000000000000000 创世账户余额
VALIDATOR_STAKE 500000000000000000 验证者质押量
P2P_PORT 26656 P2P 端口
RPC_PORT 26657 RPC 端口
REST_PORT 1317 REST 端口
CPU_LIMIT 2 CPU 限制
MEMORY_LIMIT 4G 内存限制