首页
/ Union Devnet 可复现网络配置:基于 Nix + Arion 的多链本地环境构建指南

Union Devnet 可复现网络配置:基于 Nix + Arion 的多链本地环境构建指南

2026-09-04 19:29:44作者:曹令琨Iris

本文围绕 networks/README.md 所描述的 Union 网络配置体系展开,覆盖 devnet / testnet / mainnet 三级网络模型、genesis/services/ 目录的职责划分,以及如何用 Nix 服务生成函数 + Arion(docker-compose 的 Nix 封装)组合出一条可复现的本地端到端开发网络。读完后你可以掌握 Union 单节点网络的完整 genesis 构建流水线、四条 Cosmos 开发链与本地以太坊的接线方式,以及 devnet.nix 中各 arion spec 的组装逻辑与启动方式。

1. 三级网络模型:devnet、testnet、mainnet

networks/README.md 开篇明确了项目对"网络"的定义,这是整个 networks/ 目录组织结构的出发点:

网络 定义 运行位置
devnet 开发者在本机运行的、模拟完整端到端网络拓扑的环境 本地机器
testnet Union 团队在自己的节点上运行的、用于测试类主网环境的网络 Union 节点集群
mainnet 运行在公共 Union 主网上的生产环境 公共主网

这三者共享同一套"genesis + 服务生成函数"的抽象,区别只在于注入的配置与运行载体。仓库中的对应物很直观:networks/genesis/ 下既存在面向本地的 devnet-eth/,也存在多个真实 testnet 目录(union-testnet-2union-testnet-10,每个目录含 genesis.json 与各验证人的 gentx/ 文件,例如 gentx 目录示例),即 testnet 的创世状态同样被版本化管理。

2. networks/ 目录总览

按照 README 的划分,目录承担两类职责:

  • genesis/:各网络(network)的创世配置。当前包含 devnet-eth/(本地以太坊,含 genesis.jsondev-jwt.prvdev-key0.prv~dev-key7.prv 共 8 个开发私钥)与 union-testnet-* 系列;
  • services/:所有服务生成函数。它们被定义为 Nix 函数,以便按需注入依赖与网络相关配置,生成产物随后被包含进 arion spec。

services/ 下的实际内容(见 services 目录):

文件 / 目录 提供的外部服务
geth.nix 本地以太坊执行节点(archive 模式)
lodestar.nix 以太坊信标链节点
forge.nix Foundry 工具链(部署/交互用)
postgres.nix PostgreSQL(Voyager 队列等依赖)
voyager.nix Union 消息中继组件 voyager + 其 postgres
unionvisor.nix unionvisor 本地链运行时
blockscout/ Blockscout 浏览器全家桶:backend、frontend、db、redis、sc-verifier、sig-provider、stats-db、stats、visualizer、proxy

这些 .nix 文件本身不直接定义容器,而是返回 { image, service } 形式的 arion 模块片段。例如 voyager.nix 返回的 service 指定了 network_mode = "host"、健康检查命令 voyager rpc info,以及 depends_on.postgres.condition = "service_healthy" 这样的依赖声明——这正是 README 所说"依赖与网络特定配置按需注入"的具体体现:同一份生成函数可以被不同网络复用,只是传入的参数不同。

这些模块被 flake.nix 统一挂入 flake 的模块系统(约 219–233 行处列出了 networks/e2e-setup.nixnetworks/devnet.nixnetworks/stargaze.nixnetworks/osmosis.nixnetworks/atomone.nixnetworks/babylon.nixnetworks/services/voyager.nix 等),因此 devnetConfigdevnet-eth-config 等包都从 flake 顶层可寻址。

3. Cosmos 开发链的 Genesis 构建流水线:mkCosmosDevnet.nix

devnet.nix 中每一条 Cosmos 开发链都是对 mkCosmosDevnet.nix 的一次参数化调用。该生成函数的签名为:

{
  node,                # 节点二进制(如 self'.packages.uniond / starsd / osmosisd / simd)
  chainId,             # 例:union-devnet-1
  chainName,           # 例:union
  denom,               # 例:au
  keyType,             # 例:bn254(Union)或 ed25519(其他链)
  validatorCount,      # 验证人数量,默认 4
  portIncrease,       # 端口偏移,避免多链同机冲突
  genesisOverwrites ? { },       # 任意 genesis 深度合并覆盖
  startCommandOverwrite ? null,  # 可选的启动命令整体替换
  extraPackages ? [ ],          # 注入容器镜像的额外包
  sdkVersion ? 50,              # 仅接受 47 / 50 / 52(assertOneOf 断言)
  sdkPatchVersion ? 0,
}

值得注意的细节是 sdkVersionpkgs.lib.assertOneOf 限制在 [ 47 50 52 ] 三种取值,并在生成 shell 命令时处理 SDK 版本差异:gentxadd-genesis-accountcollect-gentxs 这三个子命令在 SDK v50(或 v47 的 patch ≥ 8)中被移到了 genesis 子命令下,脚本里据此决定是否加 genesis 前缀(见 mkCosmosDevnet.nix 第 115–129 行)。

3.1 确定性密钥与验证人 genesis

整条流水线是纯函数式的 runCommand 折叠链(pkgs.lib.foldl),保证同一输入永远产出同一 genesis:

  1. 助记词层devKeyMnemonics 内置了 11 个固定助记词(alicebobcharlie、…、jake),其中 validatorCount 个分配给验证人(valoper-0…),其余作为开发账户(addDevKeyToKeyringAndGenesis)。N 个验证人的私钥则由 devnet-utils keygen mnemonicsha256(idx) 为种子派生,即 mkNodeMnemonic 实现的确定性 keygen——任何人重建 Nix store 都能得到完全相同的密钥。
  2. initHome:以助记词 --recover 方式 node init testnet(SDK ≥ 52 时追加 --consensus-key-algo ${keyType}),并用 sed 把 genesis 里的默认 "stake" 替换为目标 denom
  3. addDevKeyToKeyringAndGenesis / addValoperKeyToKeyringAndGenesis:给开发账户写入 10000000000000000000000000${denom}(10^25)余额,验证人账户写入同样量级余额。
  4. mkValGentx:每个验证人生成 gentx,质押量 1000000000000000000000${denom}(10^21),公钥类型使用链的 keyType(Union 为 bn254),并带 --keyring-backend test--ip "0.0.0.0" 等固定参数。
  5. applyGenesisOverwrites:把调用方传入的 genesisOverwrites 序列化成 JSON,用 jq -s '.[0] * .[1]'深度合并覆盖进 genesis.json
  6. setValidatorPubkey:写入 .consensus.params.validator.pub_key_types = ["${keyType}"],这一步是 Union 使用 BLS(bn254)验证人公钥的关键。
  7. enablePBTS:设置 consensus.params.feature.vote_extensions_enable_height = "0"pbts_enable_height = "1",即从创世起启用 CometBFT 的并行区块时间流(PBTS)与投票扩展特性。

3.2 组装可启动的 devnet-home 与验证人节点

devnet-homemkCosmosDevnet.nix 第 288–330 行)把上面产出的 genesisHome 复制进 home 目录,把 N 个 gentx 放进 config/gentx/,执行 collect-gentxs,随后按 SDK 版本调用 genesis validate / genesis validate-genesis 校验,并做三处 sed 修补:client.toml 写入 chain-idconfig.tomlmax_body_bytes 提到 100000000、max_tx_bytes 提到 10485760、CORS 放开为 *

mkValidatorHome idx 则复制 devnet-home 并换入第 idx 个验证人的 priv_validator_key.jsonnode_key.json,把 persistent_peers 统一指向 node-0(所有节点只连 0 号节点,保证 P2P 拓扑确定)。最终 mkNodeService idx 生成容器描述:

  • 镜像内容coreutilscurl、节点二进制、该验证人的 home 及 extraPackages
  • 端口映射(含 portIncrease 偏移):26657(CometBFT JSON-RPC)、9090(gRPC)、1317(REST)、6060(pprof);
  • 启动命令:默认执行 node start,带 --api.enabled-unsafe-cors--minimum-gas-prices "0${denom}"--log_level rpc-server:warn,x/wasm:debug,*:info 等开发友好参数;
  • 健康检查:每 5 秒 curl http://127.0.0.1:26657/block?height=2 --fail,即链出到第 2 块即视为就绪。

4. devnet.nix:把 genesis 与服务生成函数注入 arion spec

这是 networks/README.md 中"Arion"一节的落地文件。devnet.nix 的核心思路是:先用 mkCosmosDevnet 把各链的服务生成出来,再用 arion.build specs.<name> 编译成 prebuilt compose 文件,最后导出可执行包

4.1 四条 Cosmos 开发链的参数

node 包 chainId denom keyType 端口偏移 要点
devnet-union uniond(本仓库 uniond 节点) union-devnet-1 au bn254 +0 SDK 50;覆盖共识参数 max_bytes = "10485760"max_gas = "200000000";治理 max_deposit_period = "12s"voting_period = "30s"expedited_voting_period = "6s"unbonding_time = "2m"signed_blocks_window = "10"feemarket.distribute_fees = true(见 devnet.nix 第 25–58 行
devnet-stargaze starsd stargaze-devnet-1 ustars ed25519 +100 默认参数
devnet-osmosis osmosisd osmosis-devnet-1 uosmo ed25519 +200 SDK 47;开启 tokenfactory denom_creation_fee10000000 uosmo
devnet-simd simd simd-devnet-1 stake ed25519 +300 SDK 50.8(sdkPatchVersion = 8

每条链默认 4 个验证人(validatorCount = 4),与 flake.nix 中注入的 devnetConfig.validatorCount = 4 一致。各链的节点二进制通过独立的 Nix 模块构建:如 osmosis.nixpkgs.pkgsStatic.buildGo123Moduleinputs.osmosis 源码构建 osmosisd,Linux 下用 musl + libwasmvm-2_1_3 静态链接;stargaze.nix 同法构建 starsd(libwasmvm 2.1.4);atomone.nixbabylon.nix 则分别构建 atomoned 等其他 Cosmos 节点。

另有一个特殊条目 union-v1:使用 inputs.v1_0_0 这个历史版本 flake 编译出的 uniond(chainId union-minimal-devnet-1、denom muno),并把启动命令整体替换为经 unionvisor 运行(unionvisor init + unionvisor run,含 --poll-interval 1000--minimum-gas-prices "0muno" 等参数),同时额外注入 bundle-union-1-next 包——可以推断这是用于验证 v1.0.0 旧版链与当前主开发分支互操作/升级场景的对照环境。

4.2 以太坊侧与支撑服务

services.devnet-eth 聚合了三个函数式服务(devnet.nix 第 174–189 行):

  • gethgeth.nixgeth init --state.scheme=hash 导入 genesis/devnet-eth/genesis.jsondev-key0.prvdev-key1.prv 两个账户,然后以 --gcmode=archive --syncmode=full --authrpc.jwtsecret=<dev-jwt.prv> 启动,暴露 8545(HTTP)、8546(WS)、8551(Auth-RPC);健康检查为向 8545 发一个 eth_getBlockByNumber JSON-RPC 请求;
  • lodestar:本地信标链,其验证人数量取自 devnetConfig.ethereum.beacon.validatorCountflake.nix 中为 128);
  • forge:注入 evm-sourcesevm-contracts 包,供部署/调用 evm/contracts 等合约使用。

genesis 文件本身被打包成 devnet-eth-config 这个 linkFarmdevnet.nix 第 348–363 行),把 genesis.jsondev-jwt.prvdev-key0..7.prv 汇成一个目录供上述服务引用(源码中标注了 FIXME,说明作者也认为这更应放在 genesis 管理模块里)。

Blockscout 一组服务只在 x86_64 且未设置 NO_BLOCKSCOUT 环境变量时启用,源码注释给出了原因:"blockscout backend segfault on non-x86 arch"(devnet.nix 第 190–192 行)。services.postgres 则单独导出 postgres 模块,因为 voyager 需要它作为消息队列。

4.3 spec、build 与可执行包的组装

组装逻辑由四个小函数完成(devnet.nix 第 238–304 行):

  1. mkNamedModule name{ project.name = name; services = services.${name}; }
  2. modules:除各命名模块外,还定义了组合模块 full-dev-setupdevnet-eth // devnet-union // postgres);voyager-queue spec 只挂 modules.postgres
  3. mkNamedSpec name{ modules = [ modules.${name} ]; }
  4. mkNamedBuild namearion.build specs.${name},把 spec 编译成 prebuilt docker-compose 文件。

最终 packages 输出:

  • devnet-union / devnet-simd / devnet-stargaze / devnet-osmosis / devnet-eth / union-v1 / full-dev-setup / voyager-queue:每个都是一个名为 <name> 的 shell 应用,内容统一为

    arion --prebuilt-file <build.<name>> up --build --force-recreate -V --always-recreate-deps --remove-orphans
    

    mkCi (system == "x86_64-linux") 条件导出——除 voyager-queue(始终导出)外,仅 x86_64 Linux 可用;

  • devnet-union-home 等四个 *-home:各链 devnet-home 的可分发产物,供非 arion 的编排方式(如 process-compose)直接使用;

  • devnet(脚本名 union-full-devnet):一条完整的本地端到端网络入口脚本,流程为(devnet.nix 第 309–333 行):

    1. ensureAtRepositoryRoot 检查当前目录存在 flake.nix(该检查函数定义在 flake.nix 第 442–451 行);
    2. 清理并重建 .devnet/homes/,把四个 devnet-*-home 包分别拷入 union/osmosis/stargaze/simd/,并 chmod -R +w 修复密钥文件写权限;
    3. 运行 devnet-composetools 侧的 Rust 程序,见 devnet-compose)生成 process-compose 编排文件;
    4. 以 bash 为 shell 启动 process-compose --theme="One Dark" 管理全部子进程。

    对应的 devnet-logs 包则是 lnav ./.devnet/logs/,用 lnav 聚合查看各服务日志。

因此开发者在本机获得完整环境的两条路径是:devnet 包(process-compose 单进程入口,含四条 Cosmos 链)以及 full-dev-setup 等 arion 包(docker-compose 形态,可只起 Union 或只起以太坊一侧)。

5. 外部 Cosmos 节点的二进制构建模块

README 提到服务函数"依赖与网络特定配置可以按需注入",而节点二进制的可注入性由 networks/ 下的一组 flake 模块保证。以 osmosis.nix 为例:

osmosisd = pkgs.pkgsStatic.buildGo123Module {
  name = "osmosisd";
  src = inputs.osmosis;                 # 依赖通过 flake input 注入
  vendorHash = "sha256-Sgggqfem3a5KuFP9Z05a8Xtpgl00lNBVc8+encmRHs4=";
  subPackages = [ "./cmd/osmosisd" ];
  tags = [ "netgo" "muslc" ];
  env.GOWORK = "off";
} // {
  # Linux 下 musl + libwasmvm 静态链接(CosmWasm 链必需)
  nativeBuildInputs = [ pkgs.musl libwasmvm-2_1_3 ];
  ldflags = [ "-linkmode external" "-extldflags '-Wl,-z,muldefs -z noexecstack -static ...'" ];
};

同一模式出现在 stargaze.nixstarsd + libwasmvm 2.1.4)、atomone.nixatomoned,Go 1.26 模块)与 babylon.nix 中。静态链接的目的与 services/geth.nix 等容器化服务的假设一致:镜像里不依赖发行版动态库。

6. Genesis 目录的内容与 testnet 对照

genesis/ 目录按网络命名,当前内容(见 genesis 目录):

  • devnet-eth/genesis.json + dev-jwt.prv + dev-key0.prvdev-key7.prv。它是唯一把私钥直接提交进仓库的目录——因为整条 devnet 追求"任何人一条命令得到同一张网",密钥是确定性的、无资金价值的开发密钥,geth.nix--password /dev/null 导入账户也印证了这一点;
  • union-testnet-2/union-testnet-10/:真实 testnet 的 genesis.jsongentx/ 目录(各验证人的生成交易 JSON)。这些文件说明了 testnet 的创世同样受版本控制:每次 testnet 升级/重新出网都新增一个编号目录。

Cosmos 开发链的 genesis 则不提交成品,而是由 mkCosmosDevnet.nix 在构建期确定性生成(第 3 节所述流水线),二者策略不同但都服从"可复现网络"这一总目标。

7. 小结:这套体系的可复现性来自哪里

结合 networks/README.md 的四段式结构与源码可以归纳:

  1. 网络抽象:devnet / testnet / mainnet 三级,genesis/ 管"创世状态",services/ 管"运行形态",两者正交;
  2. 确定性:助记词硬编码 + sha256(idx) 种子 keygen + jq 深度合并 overwrite + collect-gentxs 前校验,使 genesis 成为构建产物而非手工产物;
  3. 注入式组合mkCosmosDevnet { ... }import ./services/geth.nix { inherit pkgs; config = ...; } 都是纯 Nix 函数,链参数(chainId、denom、keyType、端口偏移、SDK 版本差异)全部外部注入;
  4. 可执行化arion.build 产出 prebuilt compose,writeShellApplication 再包一层固定参数的 arion up,开发者只需按 flake 包名(devnetdevnet-unionfull-dev-setup 等)运行即可获得与 Union 团队完全一致的本地环境;非 x86_64 平台则通过 mkCi 条件天然禁用受影响的服务。

若你想进一步深入,建议的源码入口是:genesis 流水线 networks/mkCosmosDevnet.nix、spec 组装 networks/devnet.nix、EVM 侧接线 networks/services/geth.nix 与消息中继 networks/services/voyager.nix,以及 flake 层的参数注入点 flake.nixdevnetConfig 定义处)。

登录后查看全文
热门项目推荐
相关项目推荐