> ## 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-evm

> 轻量 EVM 执行器：消费状态变更并提供 eth_call 等状态查询

leafage-evm 是查询节点。它不做 P2P 同步、不执行区块、不维护 Merkle Patricia Trie、不存交易数据——只维护账户状态，并用 revm 执行只读调用。

| 项    | 值                                                                                                             |
| ---- | ------------------------------------------------------------------------------------------------------------- |
| 仓库   | [Chaintable/leafage-evm](https://github.com/Chaintable/leafage-evm)                                           |
| 语言   | Rust 1.79+                                                                                                    |
| 许可证  | Apache-2.0                                                                                                    |
| 核心依赖 | [revm](https://github.com/bluealloy/revm)、[alloy](https://github.com/alloy-rs/alloy)、RocksDB / MDBX、jsonrpsee |

## Crate 结构

```text theme={null}
leafage-evm/
├── bin/leafage-evm/           # CLI 与运行时装配
│   ├── runner.rs              # 子命令定义
│   ├── standalone.rs          # 服务启动、链配置解析
│   ├── updater/               # kafka_updater.rs / http_updater.rs
│   ├── initializer/           # 启动时的状态初始化
│   ├── register/              # etcd 自注册
│   ├── warm/                  # 启动预热
│   └── utils.rs               # S3 键构造与读取
└── crates/
    ├── leafage-evm-types/     # 基础类型、BlockStorageDiff、RPC 类型
    ├── leafage-evm-storage/   # StateDB trait、StateTree、RocksDB/MDBX 后端
    ├── leafage-evm-rpc/       # JSON-RPC 定义与各链执行器实现
    └── leafage-evm-chains/    # 链特定预编译与硬分叉规则
```

## 状态管理

状态分两层：最近的区块以差异层链表保存在内存，更早的区块落盘。

```text theme={null}
Block N ──► Block N-1 ──► ... ──► Block N-63 ──► CacheDiskLayer ──► RocksDB
(DiffLayer)  (DiffLayer)          (DiffLayer)      (读缓存)         (已终结状态)
```

| 结构               | 作用                                                        |
| ---------------- | --------------------------------------------------------- |
| `StateTree`      | 持有 `latest` 指针、`hash_diff_map`（含分叉块）、`num_diff_map`（仅规范链） |
| `DiffLayer`      | 单个区块相对父层的变更集                                              |
| `CacheDiskLayer` | 磁盘之上的读缓存（账户 / 存储 / 代码）                                    |
| `HybridStateDB`  | 把内存层与磁盘层组合成 revm 可用的 `DatabaseRef`                        |

**查询**：从最新层向下遍历差异层，命中即返回；全部未命中则读磁盘。绝大多数请求指向 `latest` 或接近链头的区块，因此通常是纯内存操作。

**终结化**：区块深度超过 `--diff-depth-limit`（默认 64）时，最老的层被刷入数据库并从内存移除。

**分叉**：分叉块进入 `hash_diff_map` 但不进入 `num_diff_map`。按哈希查询能访问分叉状态，按高度查询始终走规范链。

细节见仓库的 [`docs/StateManage.md`](https://github.com/Chaintable/leafage-evm/blob/main/docs/StateManage.md)。

## 两种节点模式

<Tabs>
  <Tab title="State 节点（默认）">
    只保留最新状态，ETH 主网约 90GB。

    | 数据 | RocksDB 键           |
    | -- | ------------------- |
    | 账户 | `address`           |
    | 存储 | `address \|\| slot` |

    查询走直接 `get()`。请求的高度超出内存窗口时返回 `-39006`，由 nodex-proxy 转发到 Archive 节点。
  </Tab>

  <Tab title="Archive 节点（--archive）">
    保留全部历史状态，ETH 主网约 360GB。

    | 数据 | RocksDB 键                          |
    | -- | ---------------------------------- |
    | 账户 | `address \|\| block_num`           |
    | 存储 | `address \|\| slot \|\| block_num` |

    采用双写：历史版本按高度后缀写入，同时以 `u64::MAX` 后缀写一份最新值作为快速路径。历史查询用 `seek_for_prev` 定位不大于目标高度的最近版本，配合按列族调优的 prefix extractor（账户 32 字节、存储 64 字节前缀）。

    迭代器有超时跟踪机制（`--iterator-timeout-secs`），避免长时间持有的迭代器阻塞 compaction。
  </Tab>
</Tabs>

列族布局和调优参数见 [`docs/Database.md`](https://github.com/Chaintable/leafage-evm/blob/main/docs/Database.md)。

## 状态更新

启动时按参数选择更新器：配置了 `--kafka-s3-config` 用 Kafka + S3，否则配置了 `--rpc-addr` 用 HTTP 轮询，都没有则状态静止。

### Kafka + S3（生产）

```json theme={null}
{
  "topic": "nodex_pipeline_1",
  "brokers": "kafka1:9092,kafka2:9092",
  "partition": 0,
  "bucket_name": "nodex-internal",
  "outer_bucket_name": "chaintable-pipeline",
  "offset_dir": "/nodex-eth/offset",
  "s3_chain_id": "1",
  "version": ""
}
```

* `bucket_name` 是内部桶（Header + StateDiff），`outer_bucket_name` 是外部桶（BlockFile，用于按高度索引和预热）
* 显式 `assign` 指定分区并关闭自动提交，offset 落盘到 `offset_dir`，因此每个副本独立消费全量消息
* 父块与当前块 state root 相同时跳过 diff 拉取

**追赶逻辑**：启动时读取本地 offset，若早于 Kafka 最低水位则先从 S3 逐块回补，再从最新位置消费。回补时用外部桶的 `{chainID}/{height}/` 前缀把高度解析成规范哈希。

`--catchup-safe-depth` 用于抑制追赶期的分叉误判：链头附近这些区块改为沿 Kafka 通知里的 parent-hash 链逐块回补，而不是按高度索引查，避免选中错误分支。取值应大于目标链的最大 reorg 深度，`0` 表示禁用。

### HTTP 轮询（开发 / 回退）

轮询写节点的 `trace_debankBlock`。父块不在 StateTree 中时向前回溯寻找共同祖先，然后按顺序应用。适合本地开发和没有 Kafka 的环境。

细节见 [`docs/StateUpdater.md`](https://github.com/Chaintable/leafage-evm/blob/main/docs/StateUpdater.md)。

## RPC 接口

### `eth` 命名空间

`call`、`multiCall`、`blockNumber`、`getBalance`、`getCode`、`getStorageAt`、`getTransactionCount`、`getBlockByNumber`、`getBlockByHash`、`chainId`、`baseFee`。

<Warning>
  区块查询只返回 header，`transactions` 和 `uncles` 恒为空数组——leafage-evm 不存交易数据。需要交易的消费者请读 S3 外部桶。

  另外，gas 估算方法叫 `estimateGas`（无命名空间前缀），不是 `eth_estimateGas`。
</Warning>

### DeBank 命名空间（无前缀）

`version`、`getAddressNonce`、`getAddressBalance`、`getAddressCode`、`getStorageAt`、`contractMultiCall`、`simulateTransactions`、`estimateGas`、`getLatestBlock`、`getBlockByHeight`、`getBlockById`、`blockIsValid`。

### 其他

| 方法                                | 说明                     |
| --------------------------------- | ---------------------- |
| `pre_traceCall` / `pre_traceMany` | 调用追踪，不需要全节点 debug API  |
| `blockx_stateReadBatch`           | 内部批量状态读取，二进制载荷，供内部服务使用 |

完整参数见 [RPC 参考](/reference/rpc)。

## 多链执行器

`--evm-type` 选择执行器实现：

| 取值                                                                           | 说明                               |
| ---------------------------------------------------------------------------- | -------------------------------- |
| `mainnet`                                                                    | 标准以太坊                            |
| `op` / `base` / `mantlev2`                                                   | OP Stack 系，L2 gas 计算与 OVM 预编译    |
| `arbitrum`                                                                   | Nitro                            |
| `bsc`                                                                        | Parlia 验证者、tendermint / IAVL 预编译 |
| `cosmos`                                                                     | bech32 地址、p256 签名、原生代币处理         |
| `polygon` / `moonbeam` / `moonriver` / `iotex` / `citrea` / `tempo` / `hemi` | 各链特定硬分叉与预编译                      |

新增链需要实现 `EvmExecutor` trait，链特定逻辑放在 `leafage-evm-chains` crate。

`--historical-rpc` 与 `--historical-height` 用于没有 block diff 的历史区间（如 OP pre-bedrock）：低于阈值的请求转发到外部 RPC。

## 命令行

```bash theme={null}
RUST_LOG=info ./target/release/leafage-evm standalone \
  --db-path /nodex-eth \
  --listen-addr 0.0.0.0:8659 \
  --chain-cfg 1 \
  --evm-type mainnet \
  --kafka-s3-config /etc/leafage/kafka_s3.json
```

| 子命令                         | 用途                                               |
| --------------------------- | ------------------------------------------------ |
| `standalone`                | 启动服务                                             |
| `archive-init`              | 从 S3 + RPC 初始化 Archive 数据库                       |
| `db-migrate`                | 数据库迁移（RocksDB ↔ MDBX、Archive → State）            |
| `compact` / `force-compact` | 压缩数据库；`force-compact` 用于修复缺失 bloom / 索引的批量导入 SST |
| `rewind`                    | 把已提交的链头回退到更早的区块，从 S3 重新同步                        |
| `archive-scan`              | 只读扫描某个列族，用于排查                                    |

常用参数见[配置参考](/reference/configuration#leafage-evm)。

## 服务注册

启动时把自己写入 etcd 的 `{chain_id}[/{version}]/nodes/{ip}_{port}`，初始 `stateType` 为 `2`（落后）。之后由 consistency-checker 根据轮询结果改写状态，nodex-proxy watch 这些键更新节点池。

注册使用周期性事务（仅在键不存在时写入），不覆盖 checker 写入的状态；进程退出时删除自己的键。

## 指标

`--prometheus-addr` 开启后暴露：

| 指标                                                                 | 说明            |
| ------------------------------------------------------------------ | ------------- |
| `leafage_rpc_call_time` / `leafage_rpc_call_status`                | RPC 耗时与状态     |
| `leafage_storage_read_account_latency` 等                           | 各类读取延迟        |
| `leafage_storage_latest_commit_block`                              | 最新已落盘高度       |
| `leafage_storage_active_iterators` / `timed_out_iterators`         | Archive 迭代器状态 |
| `leafage_state_batch_latency_seconds` / `leafage_state_batch_size` | 批量读取          |
| `pipeline_block_num` / `pipeline_block_time`                       | 内存中最新区块       |

## 开发

```bash theme={null}
cargo build --release
cargo test
cargo clippy --all-targets -- -D warnings
```

性能对比工具 `leafage-bench` 用于比较 leafage-evm 与 geth 的 `eth_call` 表现：

```bash theme={null}
cargo build --release -p leafage-bench
git lfs pull   # 测试语料库通过 Git LFS 管理

./target/release/leafage-bench run \
  --corpus bin/leafage-bench/corpus/corpus.json \
  --target http://leafage-evm:8545 \
  --compare http://geth:8545
```

## 相关文档

仓库内的设计文档：[`Architecture.md`](https://github.com/Chaintable/leafage-evm/blob/main/docs/Architecture.md)、[`StateManage.md`](https://github.com/Chaintable/leafage-evm/blob/main/docs/StateManage.md)、[`StateUpdater.md`](https://github.com/Chaintable/leafage-evm/blob/main/docs/StateUpdater.md)、[`Database.md`](https://github.com/Chaintable/leafage-evm/blob/main/docs/Database.md)、[`DataSpec.md`](https://github.com/Chaintable/leafage-evm/blob/main/docs/DataSpec.md)。
