Development reference. Not independently verified for production.
MSG Chain Docker 本地开发环境完整指南
数据来源:MSG Chain 代码库核实
主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。
目录
- 概述
- 快速开始
- Dockerfile 设计
- Docker Compose:单节点
- Docker Compose:多节点
- Docker Compose:完整开发栈
- CosmWasm 合约开发容器
- 前端开发环境
- 数据持久化
- 网络配置
- 监控与日志
- 生产部署考量
- 排错指南
一、概述
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 |
内存限制 |