Read OSS

三种客户端,一个接口:Base 技术栈中的 Reth vs. Geth vs. Nethermind

高级

前置知识

  • 第 1-2 篇:架构与启动流程
  • 具备区块链执行客户端的基本知识
  • 了解多阶段 Docker 构建

三种客户端,一个接口:Base 技术栈中的 Reth vs. Geth vs. Nethermind

客户端多样性是区块链网络的生存策略。一旦某个实现出现严重漏洞,运行其他客户端的节点仍能维持网络正常运转。Base 支持三种执行客户端——Reth(Rust)、Geth(Go)和 Nethermind(.NET)——base/node 仓库为每种客户端提供了遵循相同接口规范的 Dockerfile 和入口脚本。然而,统一的接口背后,三者在构建复杂度、功能范围,尤其是共识客户端兼容性上存在显著差异。

本文将全面对比三种客户端的 Dockerfile 与入口脚本,揭示其中影响性能、安全性和运维灵活性的设计决策。

构建流程对比

每个客户端的 Dockerfile 都采用多阶段构建模式,但各阶段的内容和工具链差异明显。以下是横向对比:

维度 Reth Geth Nethermind
语言 Rust Go .NET (C#)
Dockerfile reth/Dockerfile geth/Dockerfile nethermind/Dockerfile
构建阶段数 4(Go + Rust 基础 + Reth 构建 + 运行时) 3(Go op-node + Go geth + 运行时) 3(Go op-node + .NET 构建 + 运行时)
链接器 mold(含 SHA256 校验) 系统默认 系统默认
构建配置 --profile maxperf 默认(静态构建) release 配置
输出二进制文件 base-reth-node + base-consensus + op-node geth + op-node nethermind(目录)+ op-node
运行时基础镜像 ubuntu:24.04 ubuntu:24.04 mcr.microsoft.com/dotnet/aspnet:10.0-noble
构建耗时 约 30-60 分钟(Rust 编译) 约 5-10 分钟 约 5-10 分钟

Reth 的构建开销远高于另外两者。它需要安装带有逐架构 SHA256 校验的 mold 链接器,并使用 Cargo 的 maxperf profile 进行编译——该优化级别会开启激进的 LTO(链接时优化)及其他性能关键的编译器选项。这正是 Reth 的 Docker 构建需要 30-60 分钟,而 Geth 和 Nethermind 只需 5-10 分钟的原因。

flowchart TD
    subgraph "Reth Build (4 stages)"
        R1["Stage 1: golang:1.24<br/>Build op-node"] --> R4
        R2["Stage 2: rust-builder-base<br/>Install mold linker + deps"] --> R3
        R3["Stage 3: reth-base<br/>cargo build --profile maxperf<br/>→ base-reth-node + base-consensus"] --> R4
        R4["Stage 4: ubuntu:24.04<br/>Runtime image"]
    end
    subgraph "Geth Build (3 stages)"
        G1["Stage 1: golang:1.24<br/>Build op-node"] --> G3
        G2["Stage 2: golang:1.24<br/>Build geth (static)"] --> G3
        G3["Stage 3: ubuntu:24.04<br/>Runtime image"]
    end
    subgraph "Nethermind Build (3 stages)"
        N1["Stage 1: golang:1.24<br/>Build op-node (via just)"] --> N3
        N2["Stage 2: dotnet/sdk:10.0<br/>dotnet publish"] --> N3
        N3["Stage 3: dotnet/aspnet:10.0<br/>Runtime image"]
    end

提示: Reth 的构建代价较高。如果你只是在调整配置,请避免不必要地执行 docker compose build --no-cache,只有在确实变更了版本时才需要完整重建。仅修改入口脚本时,重建会很快,因为 Docker 层缓存会保留已编译的二进制文件。

Dockerfile 中的供应链安全验证

三个 Dockerfile 都实现了第 1 篇中介绍的相同供应链安全模式:锁定 tag 进行克隆,然后验证 commit hash。下面来看各客户端的具体实现。

Reth(第 51-53 行):

RUN . /tmp/versions.env && git clone $BASE_RETH_NODE_REPO . && \
    git checkout tags/$BASE_RETH_NODE_TAG && \
    bash -c '[ "$(git rev-parse HEAD)" = "$BASE_RETH_NODE_COMMIT" ]' || (echo "Commit hash verification failed" && exit 1)

Geth(第 22-24 行):

RUN . /tmp/versions.env && git clone $OP_GETH_REPO --branch $OP_GETH_TAG --single-branch . && \
    git switch -c branch-$OP_GETH_TAG && \
    bash -c '[ "$(git rev-parse HEAD)" = "$OP_GETH_COMMIT" ]'

Nethermind(第 25-27 行):

RUN . /tmp/versions.env && git clone $NETHERMIND_REPO --branch $NETHERMIND_TAG --single-branch . && \
    git switch -c $NETHERMIND_TAG && \
    bash -c '[ "$(git rev-parse HEAD)" = "$NETHERMIND_COMMIT" ]'
flowchart LR
    A[versions.env] --> B["git clone --branch TAG"]
    B --> C["git rev-parse HEAD"]
    C --> D{"== expected COMMIT?"}
    D -->|Yes| E["Proceed to build"]
    D -->|No| F["FAIL: tag-moving attack?"]

三者模式相同,但细节上有所差异。Reth 使用 git checkout tags/ 而非 --branch,且克隆完整的仓库历史(不加 --single-branch)。Geth 和 Nethermind 则通过 --single-branch 加快克隆速度。此外,Reth 在验证失败时会输出明确的错误信息("Commit hash verification failed"),而另外两者仅以非零退出码静默失败。

还有一个有趣的细节:三者都需要从 Optimism monorepo 构建 op-node。但 Geth 的 Dockerfile 使用 make 构建(第 14 行),Nethermind 则使用 just第 14 行)。两者调用的是相同的构建目标——这处不一致很可能会在未来被统一。

Reth:功能最丰富的路径

Reth 的入口脚本位于 reth/reth-entrypoint,是三者中功能最强大的,提供了其他客户端所不具备的特性。

Flashblocks 支持第 24-29 行)通过连接 Flashblocks WebSocket 端点,实现亚区块级延迟。设置 RETH_FB_WEBSOCKET_URL 后,节点可在区块最终确认前接收预确认数据:

if [[ -n "${RETH_FB_WEBSOCKET_URL:-}" ]]; then
    ADDITIONAL_ARGS="$ADDITIONAL_ARGS --websocket-url=$RETH_FB_WEBSOCKET_URL"
    echo "Enabling Flashblocks support with endpoint: $RETH_FB_WEBSOCKET_URL"
fi

裁剪配置第 70-73 行)支持精细控制需要保留的历史数据范围。RETH_PRUNING_ARGS 变量可接受 --prune.receipts.distance=50000 等参数,让运维人员在磁盘空间与历史查询能力之间灵活权衡。

历史证明——即第 2 篇中介绍的多阶段初始化流程——是 Reth 独有的功能,用于为 L1 验证提供历史状态证明服务。

第 133-158 行的最终 exec 命令通过 HTTP 和 WebSocket 同时暴露了一组完整的 API,包括 web3debugethnettxpoolminer

Geth:经过验证的默认选择

尽管 Geth 是默认客户端(.envCLIENT=geth),其入口脚本 geth/geth-entrypoint 的设计重心在于内存管理调优,提供了一套不同的配置旋钮。

以下五个缓存参数值得重点关注:

变量 默认值 含义
GETH_CACHE 20480(MB) 缓存池总大小
GETH_CACHE_DATABASE 20(%) LevelDB 读写缓存
GETH_CACHE_GC 12(%) 垃圾回收缓存
GETH_CACHE_SNAPSHOT 24(%) 快照生成缓存
GETH_CACHE_TRIE 44(%) Trie 节点内存缓存

这些百分比加起来恰好等于缓存池的 100%。Trie 缓存占比最高(44%,默认设置下约为 9GB),反映了 L2 读密集型的状态访问模式——Trie 遍历是执行开销的主要来源。

Geth 还支持其他客户端所没有的若干条件特性:

  • 非保护交易(通过 OP_GETH_ALLOW_UNPROTECTED_TXS)——适用于遗留交易格式
  • 状态存储方案选择(通过 OP_GETH_STATE_SCHEME)——可在哈希型和路径型状态存储之间切换
  • Rollup 停机保护(通过 --rollup.halt=major,见第 78 行)——在遇到主版本不兼容时自动停止节点

Nethermind:最简洁的路径

Nethermind 的入口脚本位于 nethermind/nethermind-entrypoint,是三者中最精简的。其优势来自 Nethermind 内置的配置体系:

exec ./nethermind \
    --config="$OP_NODE_NETWORK" \

第 47 行--config 标志指向内置的网络配置(例如 base-mainnetbase-sepolia)。这一个参数就能替代 Geth 和 Reth 需要逐一传入的数十个链相关参数。

可选配置仅有 bootnode 和 ethstats 监控(第 33-43 行)。Nethermind 还默认启用健康检查(第 58 行--HealthChecks.Enabled=true),这是其他两个客户端所没有的。

其 Dockerfile 使用 .NET 专属的运行时基础镜像(mcr.microsoft.com/dotnet/aspnet:10.0-noble),并通过架构名称映射处理交叉编译(第 29-32 行):

RUN TARGETARCH=${TARGETARCH#linux/} && \
    arch=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \
    dotnet publish src/Nethermind/Nethermind.Runner -c $BUILD_CONFIG -a $arch -o /publish --sc false

这里将 Docker 的 TARGETARCH(amd64/arm64)映射为 .NET 的架构名称(x64/arm64),因为 .NET 使用 x64 而非 amd64

base-consensus 的不对称性

这可能是整个代码库中最关键的发现,却也最容易被忽视。来看看每个 Dockerfile 在最终镜像中都复制了哪些文件:

文件 Reth Geth Nethermind
op-node 二进制文件
base-consensus 二进制文件
base-consensus-entrypoint
op-node-entrypoint
consensus-entrypoint

看看 Reth Dockerfile 的 COPY 指令(第 66-74 行):

COPY --from=op /app/op-node/bin/op-node ./
COPY --from=reth-base /app/target/maxperf/base-consensus ./
COPY --from=reth-base /app/target/maxperf/base-reth-node ./
...
COPY base-consensus-entrypoint .

再对比 Geth(第 37-42 行):

COPY --from=op /app/op-node/bin/op-node ./
COPY --from=geth /app/build/bin/geth ./
...
# 没有 base-consensus 二进制文件或入口脚本

只有 Reth 的 Dockerfile 会构建并打包 base-consensus 二进制文件。这与默认配置之间存在一个隐蔽的冲突:

flowchart TD
    ENV[".env defaults"] --> C["CLIENT=geth"]
    ENV --> U["USE_BASE_CONSENSUS=true"]
    C --> GETH["Geth Dockerfile<br/>No base-consensus binary"]
    U --> CE["consensus-entrypoint<br/>routes to base-consensus"]
    CE --> FAIL["❌ Exit 1:<br/>Base client is not supported<br/>for this node type"]

    style FAIL fill:#ff6666

.env 文件默认将 CLIENT 设为 geth,将 USE_BASE_CONSENSUS 设为 true。但 USE_BASE_CONSENSUS=true 必须配合 Reth 镜像使用。如果用户直接沿用默认配置,共识入口脚本的分发逻辑将会失败,因为 Geth 镜像中根本不存在 base-consensus-entrypoint

实际上,这个问题由 docker-compose.yml第 17 行USE_BASE_CONSENSUS 默认为 false 来规避:

environment:
  - USE_BASE_CONSENSUS=${USE_BASE_CONSENSUS:-false}

因此,除非显式设置,有效默认值为 false——但 .env 文件确实将其设置为了 true。最终以哪个为准,取决于你所用的 Docker Compose 版本的优先级规则。这类配置边界情况往往会让人调试数小时。

提示: 如果你想运行 base-consensus,请明确指定 CLIENT=reth。如果你想运行 Geth 或 Nethermind,请确保 USE_BASE_CONSENSUS=false(或在 .env 中不设置该变量)。

功能特性总览

功能 Reth Geth Nethermind
base-consensus 支持
Flashblocks
历史证明
裁剪配置 ✅(精细控制) ✅(gcmode) 内置
缓存调优 默认 ✅(5 个参数) 默认
Snap sync ✅(实验性) ✅(通过 bootnode)
Ethstats
内置网络配置
健康检查
日志级别转换 ✅(名称 → -v 标志) 直接(verbosity 整数) 直接(名称)
构建耗时 约 30-60 分钟 约 5-10 分钟 约 5-10 分钟

由此可以得出清晰的结论:Reth 是功能最丰富的路径,独家支持 Base 的最新能力(base-consensus、Flashblocks、历史证明);Geth 是久经考验的主力选择,提供精细的缓存调优能力;Nethermind 则是最省心的方案,依托内置配置即可轻松运行。

下一篇预告

我们已经了解了 versions.json 中的版本信息如何作为锁定依赖流入 Dockerfile。那么,谁来更新 versions.json?它如何防止版本降级、处理四种不同的版本格式,并自动生成 Pull Request?在第 4 篇中,我们将深入剖析 dependency_updater——一个 Go CLI 工具,专门解决在保障供应链安全的前提下,持续追踪四个上游依赖这一出乎意料的复杂问题。