为什么要拆
在生产规模下,用“多部署几个 Geth”来扩展查询能力会遇到几个硬性问题:
Leafage 的答案是:只让一个节点执行区块,其余节点消费执行结果。
组件职责
写节点
执行客户端加上 pipeline 集成,在执行区块时导出状态变更、调用追踪和事件。
pipeline
分发库。序列化执行数据,双桶写 S3,Leader 节点向 Kafka 发布区块变更通知。
leafage-evm
查询节点。消费 Kafka + S3 重建状态,用 revm 执行
eth_call,不做 P2P、不存交易。consistency-checker
校验层。等待副本收敛、标记分叉块、维护 etcd 中的节点健康状态、向外部 Kafka 发布确认通知。
nodex-proxy
网关。通过 etcd 发现节点,按区块上下文路由到 State / Archive / Native 节点池。
运行拓扑
数据面与控制面
理解 Leafage 的关键是把两条通路分开看。它们使用不同的中间件,故障影响范围也不同。数据面:Kafka + S3
区块数据只走一个方向,从写节点流向消费者。- Kafka(内部 topic) 只传通知,不传数据体。消息里是区块哈希、父哈希、高度、时间戳,以及 reorg 时被丢弃的区块列表。
- S3(内部桶) 存 Header 和 StateDiff,是 leafage-evm 重建状态的数据源。
- S3(外部桶) 存 BlockFile(交易、追踪、事件)和 BlockValidation,供外部消费者和分析平台使用。
- Kafka(外部 topic) 由 consistency-checker 发布,是外部消费者应当订阅的入口,带一致性保障。
控制面:etcd
etcd 是所有运行时状态的事实来源,三个组件在上面协作:
完整键空间见接口契约。
节点类型
nodex-proxy 按请求携带的区块参数在三个节点池之间路由,规则见 nodex-proxy 组件文档。
部署单元
一条链是一个独立的部署单元,各链之间不共享 Kafka topic、S3 前缀或 etcd 前缀(都以chainID 开头)。一个典型的单链部署包含:
chainId 管理多条链的节点池,因此通常整个集群只部署一组 proxy。
多链支持
写侧和读侧分别适配:- 写侧:pipeline 需要嵌入目标链的执行客户端。已适配 40+ 条链的客户端分叉,列表见 leafage-evm README。适配方式见接入新链。
- 读侧:leafage-evm 通过
--evm-type选择执行器,当前支持mainnet、arbitrum、op、base、bsc、cosmos、mantlev2、tempo、citrea、iotex、moonbeam、moonriver、polygon、hemi。
设计取舍
leafage-evm 不存交易数据
leafage-evm 不存交易数据
eth_call 只需要账户状态(balance、nonce、code、storage),交易体、收据和日志都可以丢弃。代价是区块查询接口(eth_getBlockByNumber 等)只返回 header,transactions 和 uncles 恒为空。需要交易数据的消费者应当读 S3 外部桶或订阅外部 Kafka topic。内存里只保留 64 个区块的差异
内存里只保留 64 个区块的差异
最近 64 块的状态以差异层链表形式留在内存,超出深度的层被刷写到 RocksDB。这个窗口同时是 reorg 的处理能力上限:更深的重组需要走 S3 回补路径。窗口大小由
--diff-depth-limit 控制。一致性不由 leafage-evm 自己保证
一致性不由 leafage-evm 自己保证
所有查询节点消费同一个 Kafka 分区,按相同顺序应用相同区块,因此不存在 P2P 的非确定性。但“副本是否都追上了”这件事由 consistency-checker 判断,并只在达到
ready_ratio(默认 0.8)后才向外部通知。Kafka 只发通知,数据体走 S3
Kafka 只发通知,数据体走 S3
通知消息很小,Kafka 只承担顺序和低延迟;大体积的 StateDiff 和 BlockFile 走 S3,消费者可以并行拉取,也便于冷启动时按需回补历史。
下一步
数据链路
跟着一个区块走完执行、分发、摄入、终结化、查询五个阶段。
接口契约
Kafka、S3、etcd 和 RPC 的精确格式定义。