Union Devnet 可复现网络配置:基于 Nix + Arion 的多链本地环境构建指南
本文围绕 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-2 至 union-testnet-10,每个目录含 genesis.json 与各验证人的 gentx/ 文件,例如 gentx 目录示例),即 testnet 的创世状态同样被版本化管理。
2. networks/ 目录总览
按照 README 的划分,目录承担两类职责:
genesis/:各网络(network)的创世配置。当前包含 devnet-eth/(本地以太坊,含genesis.json、dev-jwt.prv及dev-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.nix、networks/devnet.nix、networks/stargaze.nix、networks/osmosis.nix、networks/atomone.nix、networks/babylon.nix、networks/services/voyager.nix 等),因此 devnetConfig、devnet-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,
}
值得注意的细节是 sdkVersion 被 pkgs.lib.assertOneOf 限制在 [ 47 50 52 ] 三种取值,并在生成 shell 命令时处理 SDK 版本差异:gentx、add-genesis-account、collect-gentxs 这三个子命令在 SDK v50(或 v47 的 patch ≥ 8)中被移到了 genesis 子命令下,脚本里据此决定是否加 genesis 前缀(见 mkCosmosDevnet.nix 第 115–129 行)。
3.1 确定性密钥与验证人 genesis
整条流水线是纯函数式的 runCommand 折叠链(pkgs.lib.foldl),保证同一输入永远产出同一 genesis:
- 助记词层:
devKeyMnemonics内置了 11 个固定助记词(alice、bob、charlie、…、jake),其中validatorCount个分配给验证人(valoper-0…),其余作为开发账户(addDevKeyToKeyringAndGenesis)。N 个验证人的私钥则由devnet-utils keygen mnemonic以sha256(idx)为种子派生,即 mkNodeMnemonic 实现的确定性 keygen——任何人重建 Nix store 都能得到完全相同的密钥。 - initHome:以助记词
--recover方式node init testnet(SDK ≥ 52 时追加--consensus-key-algo ${keyType}),并用sed把 genesis 里的默认"stake"替换为目标denom。 - addDevKeyToKeyringAndGenesis / addValoperKeyToKeyringAndGenesis:给开发账户写入
10000000000000000000000000${denom}(10^25)余额,验证人账户写入同样量级余额。 - mkValGentx:每个验证人生成 gentx,质押量
1000000000000000000000${denom}(10^21),公钥类型使用链的keyType(Union 为bn254),并带--keyring-backend test、--ip "0.0.0.0"等固定参数。 - applyGenesisOverwrites:把调用方传入的
genesisOverwrites序列化成 JSON,用jq -s '.[0] * .[1]'做深度合并覆盖进genesis.json。 - setValidatorPubkey:写入
.consensus.params.validator.pub_key_types = ["${keyType}"],这一步是 Union 使用 BLS(bn254)验证人公钥的关键。 - enablePBTS:设置
consensus.params.feature.vote_extensions_enable_height = "0"与pbts_enable_height = "1",即从创世起启用 CometBFT 的并行区块时间流(PBTS)与投票扩展特性。
3.2 组装可启动的 devnet-home 与验证人节点
devnet-home(mkCosmosDevnet.nix 第 288–330 行)把上面产出的 genesisHome 复制进 home 目录,把 N 个 gentx 放进 config/gentx/,执行 collect-gentxs,随后按 SDK 版本调用 genesis validate / genesis validate-genesis 校验,并做三处 sed 修补:client.toml 写入 chain-id、config.toml 把 max_body_bytes 提到 100000000、max_tx_bytes 提到 10485760、CORS 放开为 *。
mkValidatorHome idx 则复制 devnet-home 并换入第 idx 个验证人的 priv_validator_key.json 与 node_key.json,把 persistent_peers 统一指向 node-0(所有节点只连 0 号节点,保证 P2P 拓扑确定)。最终 mkNodeService idx 生成容器描述:
- 镜像内容:
coreutils、curl、节点二进制、该验证人的 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_fee(10000000 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.nix 用 pkgs.pkgsStatic.buildGo123Module 从 inputs.osmosis 源码构建 osmosisd,Linux 下用 musl + libwasmvm-2_1_3 静态链接;stargaze.nix 同法构建 starsd(libwasmvm 2.1.4);atomone.nix、babylon.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 行):
- geth:geth.nix 先
geth init --state.scheme=hash导入 genesis/devnet-eth/genesis.json 与dev-key0.prv、dev-key1.prv两个账户,然后以--gcmode=archive --syncmode=full --authrpc.jwtsecret=<dev-jwt.prv>启动,暴露8545(HTTP)、8546(WS)、8551(Auth-RPC);健康检查为向 8545 发一个eth_getBlockByNumberJSON-RPC 请求; - lodestar:本地信标链,其验证人数量取自
devnetConfig.ethereum.beacon.validatorCount(flake.nix 中为 128); - forge:注入
evm-sources、evm-contracts包,供部署/调用 evm/contracts 等合约使用。
genesis 文件本身被打包成 devnet-eth-config 这个 linkFarm(devnet.nix 第 348–363 行),把 genesis.json、dev-jwt.prv 与 dev-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 行):
mkNamedModule name:{ project.name = name; services = services.${name}; };modules:除各命名模块外,还定义了组合模块full-dev-setup(devnet-eth // devnet-union // postgres);voyager-queuespec 只挂modules.postgres;mkNamedSpec name:{ modules = [ modules.${name} ]; };mkNamedBuild name:arion.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 行):ensureAtRepositoryRoot检查当前目录存在flake.nix(该检查函数定义在 flake.nix 第 442–451 行);- 清理并重建
.devnet/homes/,把四个devnet-*-home包分别拷入union/、osmosis/、stargaze/、simd/,并chmod -R +w修复密钥文件写权限; - 运行 devnet-compose(
tools侧的 Rust 程序,见 devnet-compose)生成 process-compose 编排文件; - 以 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.nix(starsd + libwasmvm 2.1.4)、atomone.nix(atomoned,Go 1.26 模块)与 babylon.nix 中。静态链接的目的与 services/geth.nix 等容器化服务的假设一致:镜像里不依赖发行版动态库。
6. Genesis 目录的内容与 testnet 对照
genesis/ 目录按网络命名,当前内容(见 genesis 目录):
devnet-eth/:genesis.json+dev-jwt.prv+dev-key0.prv…dev-key7.prv。它是唯一把私钥直接提交进仓库的目录——因为整条 devnet 追求"任何人一条命令得到同一张网",密钥是确定性的、无资金价值的开发密钥,geth.nix 中--password /dev/null导入账户也印证了这一点;union-testnet-2/…union-testnet-10/:真实 testnet 的genesis.json与gentx/目录(各验证人的生成交易 JSON)。这些文件说明了 testnet 的创世同样受版本控制:每次 testnet 升级/重新出网都新增一个编号目录。
Cosmos 开发链的 genesis 则不提交成品,而是由 mkCosmosDevnet.nix 在构建期确定性生成(第 3 节所述流水线),二者策略不同但都服从"可复现网络"这一总目标。
7. 小结:这套体系的可复现性来自哪里
结合 networks/README.md 的四段式结构与源码可以归纳:
- 网络抽象:devnet / testnet / mainnet 三级,
genesis/管"创世状态",services/管"运行形态",两者正交; - 确定性:助记词硬编码 +
sha256(idx)种子 keygen +jq深度合并 overwrite +collect-gentxs前校验,使 genesis 成为构建产物而非手工产物; - 注入式组合:
mkCosmosDevnet { ... }与import ./services/geth.nix { inherit pkgs; config = ...; }都是纯 Nix 函数,链参数(chainId、denom、keyType、端口偏移、SDK 版本差异)全部外部注入; - 可执行化:
arion.build产出 prebuilt compose,writeShellApplication再包一层固定参数的arion up,开发者只需按 flake 包名(devnet、devnet-union、full-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.nix(devnetConfig 定义处)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00