“满堂彩”:Lighthouse + Reth 集成于单一二进制文件

本文档记录了一个关于 Lighthouse-Reth 集成想法的小实验,目标是使用单一二进制文件同时运行共识层 (CL) 和执行层 (EL),从而简化以太坊节点的运行。实验结果表明,这种集成在用户体验、资源使用和可观察性方面具有潜在优势,但也存在维护、版本协调和编译时间等方面的挑战。

fullhouse-logs截图:“Fullhouse”正在同步。

这篇文章记录了一个关于 Lighthouse-Reth 集成想法的小实验,这个想法很久以前就出现了,但一直没有真正进展。随着 gas 限制和 blob 数量的增加,共识层(CL)和执行层(EL)之间流动的数据也越来越多,而当前 HTTP + JSON 路径可能会变得越来越昂贵。Engine API 中的 SSZ 支持会有所帮助,但仍然有一些 UX 方面的优势值得探索 - 所以我用 Claude 做了个短期的、有时间限制的实验,看看单二进制设置会是什么样子。

这个想法是拥有一个运行共识层和执行层的单一二进制文件 - 我们称之为 "Fullhouse" - 运行以太坊节点可以像这样简单:

fullhouse --checkpoint-sync-url https://checkpoint.sigp.io

背景

CL 和 EL 通过 Engine API (HTTP) 进行交互

┌─────────────┐    HTTP (JSON-RPC)  ┌──────────────┐
│ Lighthouse  │ ←─────────────────→ │     Reth     |
│ (Consensus) │                     │ (Execution)  │
└─────────────┘                     └──────────────┘

通过直接调用或通道在进程内

┌─────────────────────────────────────────────────┐
│           Single Lighthouse-Reth Binary         │
│  ┌─────────────┐          ┌──────────────┐      │
│  │ Lighthouse  │ ←──────→ │     Reth     │      │
│  │ (Consensus) │  Direct  │ (Execution)  │      │
│  └─────────────┘  Calls   └──────────────┘      │
└─────────────────────────────────────────────────┘

CL + EL 在单个二进制文件中的潜在优势:

  • 为普通用户提供更好的 UX:只需下载和运行一个二进制文件。
  • 更低的资源使用率:消除了 HTTP 和 JSON-serde 的开销,随着 payload 大小和 blob 数量的增加,这可能会变得非常重要。
  • 降低延迟:直接函数调用降低了区块生产和区块处理等关键路径中的延迟,这有助于提高节点和验证者的性能。
  • 端到端可观察性:可以实现端到端客户端跟踪,而无需实现分布式跟踪。

概念验证

本节将介绍原型的技术细节。如果你不关心内部原理,请随时跳到下一节。

这是一个有时间限制的 POC,因为我知道这个兔子洞可能非常深。Claude 帮助生成了大部分初始代码,目标是在最短的时间内让一些东西能够工作,所以代码非常粗糙,但重点是看到一些东西能够工作,并了解可行性和所需的工作量。

我想以最小的更改来解决这个问题,因为我认为如果这种集成增加太多的维护或依赖开销,就不值得了。所以我考虑只在 Lighthouse 的 Engine 结构体中,将 HttpJsonRpc 替换为 RethEngineApi。如果我实现了所有的接口,这应该就可以工作了。

pub struct Engine {
    pub api: RethEngineApi,
    pub json_api: HttpJsonRpc,
    payload_id_cache: Mutex<LruCache<PayloadIdCacheKey, PayloadId>>,
    state: RwLock<State>,
    latest_forkchoice_state: RwLock<Option<ForkchoiceState>>,
    executor: TaskExecutor,
}

感谢 Reth 的模块化设计,我能够相当快地在 Lighthouse 中运行 Reth,并获得了 Reth 的 ConsensusEngineHandle 的Handle,它公开了 CL 驱动 EL 的函数(forkchoice_updatednew_payload)。

我意识到 ConsensusEngineHandle 不够用,因为它没有公开完整的 engine 方法集 - 然后我找到了 EngineApi 结构体,它是要使用的正确接口,并公开了所有的 engine 方法。启动 Reth 并获取 EngineApi 的Handle相当简单:

  debug!("Launching Reth node");

  let engine_api = EngineApiExt::new(
      BasicEngineApiBuilder::<EthereumEngineValidatorBuilder>::default(),
      move |api| {
          info!("Reth node started, extracting engine API handle");
          let _ = handle_tx.send(Ok(api));
          debug!("Extracted engine api handle");
      },
  );

  let tasks = TaskManager::current();
  let node_builder = NodeBuilder::new(node_config)
      .with_database(db)
      .with_launch_context(tasks.executor())
      .with_types::<EthereumNode>()
      .with_components(EthereumNode::components())
      .with_add_ons(EthereumAddOns::default().with_engine_api(engine_api));

  match node_builder.launch().await {

就这样 -- 我们有了 CL 向 EL 发送新块和分叉选择更新的工作同步!🥳

fullhouse-syncing

该分支可以在这里找到:https://github.com/jimmygchen/lighthouse/tree/lighthouse-reth

  • 已完成的 engine 方法集成:
  • forkchoice_updated
  • new_payload
  • get_payload
  • get_blobs_v1
  • get_blobs_v2
  • 其余的 engine/RPC 方法没有实现,所以 CL 仍然使用 HTTP 进行其他所有操作。
  • 目前传递给 reth 的唯一 CLI 标志是 networkdatadir
  • 顺便提一下:在 Lighthouse 类型和 Reth 类型之间进行转换目前需要手动字段映射。使用 JSON-RPC 时,这不需要,因为一切都通过 JSON 流动。这不是一个障碍,但这是一些额外的粘合代码,在真正的实现中需要清理。

测试和指标

该实现没有优化,但如果没有一些测试结果,这篇文章就不完整了!

我使用单独的 Lighthouse 和 Reth 二进制文件与 Hoodi 测试网上的 Fullhouse 进行了比较。这是我使用的命令和下面的指标。

fullhouse bn --network hoodi \
    --checkpoint-sync-url https://hoodi.beaconstate.info \
    --http --metrics --telemetry-collector-url http://localhost:4317

fetch_and_process_engine_blobs

这个 span 指标显示了从 EL 获取 blob(engine_getBlobs 方法)并将它们转换为数据列的 p95 时间。Fullhouse 在这里表现明显更好。

Lighthouse + Reth Fullhouse
fetch-blobs-1 fetch-blobs-2

execution_layer_request_times

这个指标显示了调用 EL 的 p95 持续时间。

  • forkchoice_updated:两者在这里表现相似。
  • new_payload:Fullhouse 在这里较慢。
Lighthouse + Reth Fullhouse
el-times-1 el-times-2

beacon_block_processing_seconds

这个指标衡量了区块处理的 p95 总运行时间,包括执行区块,可能包括将区块和数据列导入数据库的时间。正如预期的那样,Fullhouse 在这里表现更差,因为大部分时间都花在了 EL payload 验证(上面的 new_payload 方法)上。

Lighthouse + Reth Fullhouse
block-proc-1 block-proc-2

更快的 get_blobs_v2 响应时间似乎没有太大帮助,因为关键路径是 payload 执行。请参见下面的区块处理跟踪示例:

block-proc-3

我之前提到了潜在的可观察性优势,这个想法是,如果我们检测 EL 中的函数,我们将能够看到所有关键步骤,轻松识别出哪个步骤花费的时间最长,以及所采用的代码路径,所有这些都在一个简单的跟踪中!

beacon_block_delay_attestable_slot_start

这个指标显示了区块变得可证明的时间和 slot 开始之间的时间间隔。结果非常相似,对于两者来说,平均区块可证明时间约为 2 秒。

Lighthouse + Reth Fullhouse
block-delay-1 block-delay-2

produce_block_v3

我运行了一个连接到信标节点的 blockdreamer 实例,以在每个 slot 中触发区块生产。Fullhouse 在这里表现明显更差。不幸的是,我在关闭测试实例之前没有捕获正确的屏幕截图,但在测试期间,p95 持续时间似乎慢了大约 2 倍。

总的来说,Fullhouse POC:

  • 总体上使用了稍微多一点的 CPU,内存使用量相似。
  • 在 CPU 密集型任务(如 new_payloadget_payload)上表现更差。我怀疑这与任务调度或争用相同的 Tokio 和/或 Rayon 线程池有关,当在单个进程和 Tokio 运行时中运行 Lighthouse 和 Reth 时。这很可能是一个可以解决的问题。
  • 对于大型 JSON 响应(如 get_blobs_v2)表现更好。

结论

能够用一个命令运行整个以太坊节点非常简洁:

fullhouse bn --checkpoint-sync-url https://checkpoint.sigp.io

注意bn 子命令仅在此处,因为 Lighthouse 期望它 - 在组合二进制文件中删除它很简单。

性能结果不是主要重点,但看到它工作令人非常满意!我在短时间内从这个 POC 中学到了很多东西,这是一个有趣的实验。代码是快速拼凑起来的(大部分由 Claude 完成),非常粗糙,但这就是目标 - 只是一个快速实验,以了解挑战和可行性。我确实遇到了一些挑战,并设法解决了一些,我认为这个想法是可行的,并且可能值得付出努力,并具有前面提到的好处:更好的 UX、更低的资源开销、改进的性能和可观察性。

在潜在的缺点方面:

  • 需要持续的维护工作,例如依赖管理、API 更新
  • 协调版本和发布
  • 编译时间长

我考虑过在一个单独的 repo 中实现这个,但是让依赖项正确非常耗时(Claude 也为此苦苦挣扎),所以我选择了更快的替代方案,将其嵌入到 Lighthouse 中,以便用于本次实验。这并不意味着它是一个正确或首选的方法 - 引入像 Reth 这样的大型代码库作为依赖项并不理想 - 但对于本次有时间限制的 POC 来说,对 Claude 和我来说都更容易。这也会减慢编译时间,因此在真正的实现中,将其隔离在 feature gate 后面非常重要。

很想听听你的想法,欢迎随时提出任何反馈或想法。感谢阅读!

感谢 Reth 团队 - 他们的模块化设计使本次实验比其他情况更顺利,感谢 Lighthouse 团队审查这篇文章并提供早期反馈。

  • 原文链接: blog.sigmaprime.io/fullh...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
sigmaprime
sigmaprime
江湖只有他的大名,没有他的介绍。