> ## Documentation Index
> Fetch the complete documentation index at: https://docs.leafage.chaintable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 写节点与查询节点

> Leafage 两侧角色的边界：写节点做什么、查询节点不做什么、三种查询节点类型，以及节点在 etcd 中的生命周期。

Leafage 里只有两种节点参与区块数据的生产和消费：**写节点**执行区块并导出数据，**查询节点**应用这些数据并回答 RPC 请求。其余组件（consistency-checker、nodex-proxy）不持有状态，只观察和调度这两种节点。

## 写节点

写节点就是这条链原本的执行客户端（ETH 用 go-ethereum 分叉，其他链用各自的分叉），加上编译进去的 pipeline 库。Leafage 没有替换它的同步和执行逻辑，只在执行路径上挂了追踪钩子。

| 写节点做的事                                             | 由谁完成                             |
| -------------------------------------------------- | -------------------------------- |
| P2P 同步与区块执行                                        | 执行客户端本身                          |
| 采集调用追踪、事件、状态差异                                     | pipeline tracer（`tracing.Hooks`） |
| 把 Header、StateDiff、BlockFile、BlockValidation 上传 S3 | pipeline，在 `OnCommit` 中并发上传      |
| 确定新的 canonical head 后发布区块变更通知                      | 执行客户端调用 pipeline，仅 Leader        |
| 按需返回单个区块的完整执行输出（`trace_debankBlock`）               | 执行客户端的 `trace` 命名空间              |

写节点的 RPC 端口不面向应用客户端。它服务两类调用方：leafage-evm 的 HTTP 轮询模式，以及调试时比对某个区块的 StateDiff。

### Leader 与备节点

一条链可以运行多个写节点。它们都同步、都执行、都上传 S3——S3 对象以区块哈希和 state root 为键，重复上传是幂等的。区别只有一个：**只有 Leader 向 Kafka 发布通知**。

| 模式        | 配置                                 | 行为                                                   |
| --------- | ---------------------------------- | ---------------------------------------------------- |
| etcd 自动选举 | 配置 `etcd_endpoints`，`is_backup` 留空 | 抢占 `{chainID}[/{version}]/writers/leader`，键消失后随机退避再抢 |
| 手动指定      | 配置 `is_backup` 为 `true` / `false`  | 角色固定，不需要 etcd                                        |

新 Leader 会先等待 `grace_period`（默认 10 秒）再开始发布，给上一任留出收尾时间。

## 查询节点

查询节点是 leafage-evm 进程。定义它最准确的方式是列出它**不做**的事：

| 不做                       | 原因                               |
| ------------------------ | -------------------------------- |
| 不参与 P2P 同步               | 区块由写节点执行，通知经 Kafka 到达            |
| 不执行区块                    | 只把写节点导出的状态差异应用到本地状态              |
| 不维护 Merkle Patricia Trie | 不需要出块、不需要证明 state root，扁平的键值存储就够 |
| 不存交易、收据、日志               | `eth_call` 只需要账户状态；交易数据在 S3 外部桶  |

它做的事只有两件：按通知顺序应用状态差异，以及在本地状态上用 revm 执行只读调用（`eth_call`、批量调用、gas 估算、调用追踪）。

<Note>
  因为不存交易，`eth_getBlockByNumber` 和 `eth_getBlockByHash` 只返回区块头，`transactions` 与 `uncles` 恒为空数组。这是设计取舍，不是缺陷。
</Note>

## 三种查询节点类型

leafage-evm 按启动参数分成 State 和 Archive 两种；Native 节点是原链的全节点，由运维手动注册到 etcd 作为兜底。

| 类型         | 启动方式        | 磁盘上保留           | 能回答的查询                    | etcd 中的标识           |
| ---------- | ----------- | --------------- | ------------------------- | ------------------- |
| State 节点   | 默认          | 每个账户和存储槽的最新值    | `latest` 及内存差异窗口内的高度      | `nodeType: 1`       |
| Archive 节点 | `--archive` | 每个账户和存储槽在每个高度的值 | 任意历史高度                    | `nodeType: 2`       |
| Native 节点  | 原链客户端       | 原链全节点数据         | 触及 leafage-evm 不支持的预编译的调用 | 单独的 `nativeNodes` 键 |

State 节点收到超出窗口的高度会返回 `-39006 BlockNotFound`；Archive 节点收到不存在的区块返回 `-39007 InvalidBlockID`。前者会触发 nodex-proxy 改选 Archive 节点重试，后者不会。

三种类型的路由规则见[区块上下文与路由](/concepts/block-context)，存储布局见[状态与状态差异](/concepts/state)。

## 节点的生命周期

查询节点是否接收流量，不由它自己决定。三个组件在 etcd 的 `{chainID}[/{version}]/nodes/{ip}_{port}` 键上接力：

<Steps>
  <Step title="leafage-evm 自注册">
    启动时写入自己的地址和 `nodeType`，`stateType` 置为 `2`（落后）。注册用“键不存在才写入”的事务，不覆盖别人写入的状态；进程退出时删除自己的键。
  </Step>

  <Step title="consistency-checker 判定">
    每处理一个区块都轮询所有节点的 `eth_blockNumber`，把结果写回：`1` 已追上链头，`2` 落后，`3` 离线。离线且没有租约的节点直接删键。
  </Step>

  <Step title="nodex-proxy 接纳">
    watch 到新节点后先做健康检查（`getLatestBlock`），通过后才加入负载均衡池；watch 到删除事件立即移出。
  </Step>
</Steps>

| `stateType` | 含义    | 是否接收流量 |
| ----------- | ----- | ------ |
| `1`         | 已追上链头 | 是      |
| `2`         | 落后    | 否      |
| `3`         | 离线    | 否      |

排查“某个节点为什么没有流量”时，从 consistency-checker 的轮询结果查起，而不是从 nodex-proxy。

## 小结

* **写节点**是原链执行客户端 + pipeline，唯一执行区块；多个写节点都上传 S3，只有 **Leader** 发 Kafka。
* **查询节点**不同步、不执行、不维护 MPT、不存交易，只应用状态差异并执行只读调用。
* 查询节点分 **State / Archive / Native**，按保留的状态范围区分，错误码 `-39006` 与 `-39008` 驱动 proxy 在三者之间兜底。
* 节点是否接收流量由 **consistency-checker** 写入 etcd 的 `stateType` 决定，leafage-evm 只负责注册，nodex-proxy 只负责消费。

继续阅读：

* [状态与状态差异](/concepts/state)：查询节点内部如何组织状态。
* [区块上下文与路由](/concepts/block-context)：nodex-proxy 如何在三种节点之间选择。
* [架构总览](/architecture/overview#节点类型)：各类型节点在 ETH 主网上的存储规模。
