Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

2 stars

Watchers

3 watching

Forks

Repository files navigation

leafage-evm

中文文档

leafage-evm is a lightweight EVM executor built with alloy and revm. It focuses on state queries (eth_call, estimateGas, etc.) and does not store transaction data. State updates are received via Kafka + S3, rather than P2P synchronization.

Features

  • Two Node Modes:
    • State Node: Only retains recent block states (default 64 blocks), ~90GB for ETH mainnet (as of 2025.1)
    • Archive Node: Retains complete historical state, ~360GB for ETH mainnet (as of 2025.1)
  • Multi-chain Support: Ethereum mainnet, Optimism, BSC, Cosmos EVM
  • Multiple Database Backends: RocksDB (default), MDBX
  • Database Tooling: Bulk archive initialization, RocksDB/MDBX migration, compaction and rewind

Supported Write Node Repositories

Any EVM-compatible chain can potentially be supported. The following chains are currently supported:

Chain Repository
ETH, Linea Chaintable/go-ethereum
AVAX Chaintable/coreth
OP Stack op-geth: OP, opBNB, Celo, B2, BOB, DBK, Hemi, Katana, Manta, Mantle, Mode, Orderly, Soneium, Unichain, X Layer, Swell Chaintable/op-geth
Base Chaintable/base-reth
OP Stack op-reth: HSK, Ink, Lisk, Zora, Cyber Chaintable/optimism
Arb, Gravity, Plume, Hood Chaintable/nitro
Gnosis Chaintable/erigon
Bitlayer Chaintable/bitlayer-l2
Oasys Chaintable/oasys-validator
Kava Chaintable/kava
IoTeX Chaintable/iotex-core-x
RSK (Rootstock) Chaintable/rskj
Scrl Chaintable/go-ethereum-scrl
Bera Chaintable/bera-geth
Story Chaintable/story-geth
Tac Chaintable/tacchain
Mitosis Chaintable/reth-mitosis
XDC Chaintable/XDPoSChain
Citrea Chaintable/citrea
ZKsync: Lens, Era, Abstract, Sophon Chaintable/zksync-era @ debank
Cronos zkEVM (Croze) Chaintable/zksync-era @ chain/croze
Fraxtal Chaintable/frax-op-reth
Ronin Chaintable/conduit-op-reth
World Chain Chaintable/world-chain
Plasma, Botanix Chaintable/reth-x
BSC Chaintable/bsc-x
Core Chaintable/core
Chiliz Chaintable/chiliz-chain-v2
Morph Chaintable/go-ethereum-morph-x
Taiko Chaintable/taiko-geth
Metis Chaintable/mvm-x
0G Chaintable/0g-geth
Immutable zkEVM Chaintable/immutable-geth
Kite Chaintable/subnet-evm-kite
Merlin Chaintable/cdk-erigon
Flare Chaintable/go-flare-x
Moonbeam / Moonriver Chaintable/moonbeam-x
Conflux Chaintable/conflux-rust-x
Kaia (Klaytn) Chaintable/kaia
WEMIX Chaintable/go-wemix
Polygon PoS Chaintable/bor
Sonic Chaintable/sonic
Monad Chaintable/monad-bft-x

Supported JSON-RPC Methods

eth_*

Method Description
eth_call Execute a contract call
eth_multiCall Batch contract calls
eth_blockNumber Get current block number
eth_getBalance Get account balance
eth_getBlockByNumber Get block by number
eth_getBlockByHash Get block by hash
eth_getCode Get contract code
eth_getStorageAt Get storage slot data
eth_getTransactionCount Get account nonce
eth_chainId Get chain ID
eth_baseFee Get base fee

DeBankApi (no namespace prefix)

Method Description
version Get version info
getAddressNonce Get account nonce
getAddressBalance Get account balance
getAddressCode Get contract code
getStorageAt Get storage slot data
contractMultiCall Batch contract calls
simulateTransactions Simulate transaction execution
estimateGas Estimate gas
getLatestBlock Get latest block
getBlockByHeight Get block by height
getBlockById Get block by hash
blockIsValid Validate block

Note: Block query methods (eth_getBlockByNumber, eth_getBlockByHash, getLatestBlock, getBlockByHeight, getBlockById) return header only - transactions and uncles are always empty. leafage-evm does not store transaction data.

pre_*

Method Description
pre_traceCall Pre-execute call trace
pre_traceMany Batch pre-execute traces

Build

Requirements: Rust 1.79+

cargo build --release

Docker build:

docker build -t leafage-evm .

Usage

Start Server

RUST_LOG=info ./target/release/leafage-evm standalone \
  --db-path /path/to/db \
  --listen-addr 0.0.0.0:8545 \
  --rpc-addr http://geth:8545 \
  --evm-type mainnet \
  --chain-cfg 1

Main Parameters

Parameter Default Description
--db-path - Database path (required)
--listen-addr 0.0.0.0:8545 RPC listen address
--rpc-addr - Geth RPC address (for HTTP mode state updates)
--evm-type mainnet EVM type: mainnet/op/bsc/cosmos
--chain-cfg 1 Chain ID
--db-type rocksdb Database type: rocksdb/mdbx
--db-cache 2048 Database cache size (MB)
--diff-depth-limit 64 Block diff depth retained in memory
--catchup-safe-depth 0 S3 catch-up reorg buffer: blocks below the Kafka head backfilled via the exact parent-hash chain instead of the by-number index. 0 disables it (legacy behavior); set above the chain's max reorg depth (e.g. 64 for Moonriver)
--bundle-range-size 32 MiB Target size for combining multiple StateDiff entries into one S3 Range request. A larger single entry is still read alone. Also available on archive-init
--archive false Enable archive mode
--prometheus-addr - Prometheus metrics address
--kafka-s3-config - Path to Kafka + S3 config JSON file
--max-connections 5000 Maximum concurrent RPC connections
--rpc-timeout 10000 RPC request timeout (ms)
--iterator-timeout-secs 0 Iterator timeout for archive mode (0 = disabled)
--historical-rpc - Historical RPC endpoint for pre-fork queries (e.g., OP pre-bedrock)
--historical-height - Fork height threshold for historical RPC forwarding

Kafka + S3 Configuration

When using Kafka + S3 mode, provide a JSON config file:

{
  "topic": "block-notifications",
  "brokers": "kafka1:9092,kafka2:9092",
  "partition": 0,
  "bucket_name": "state-diffs-bucket",
  "bundle_bucket_name": "compacted-state-diffs-bucket",
  "outer_bucket_name": "block-info-bucket",
  "offset_dir": "/path/to/offset",
  "s3_chain_id": "1",
  "version": "v1",
  "s3_read_timeout_secs": 20
}

bundle_bucket_name is optional; omit it or leave it empty to keep the legacy per-block S3 path.

Both standalone and archive-init accept the optional --statediff-key flag with values state-root or block-hash. For example, use standalone --statediff-key block-hash when consuming Arb Classic data produced with block-hash keys, or archive-init --statediff-key block-hash for archive initialization with the same source bucket, S3 chain ID and version. This is a top-level CLI option, outside the Kafka + S3 JSON configuration. The choice applies to genesis, live consumption, catch-up, restart and parent-hash backfill. Kafka messages are unchanged.

When omitted, the historical default is preserved: S3 chain ID "999" uses block hashes; all others use state roots. Explicit values override this default, including on chain 999. The resolved strategy is logged at startup. Match the producer's setting and keep it consistent when resuming the same dataset; chain ID 42161 does not distinguish Classic from Nitro.

Block-hash mode requires a source diff for every block, including genesis and blocks with unchanged/zero state roots. Missing or malformed objects fail the read without falling back to a state-root key. Bundle entries marked as synthesized are rejected before applying any block in the requested range; disable bundle reads to read the original per-block objects, or regenerate the bundle with real diffs. Roots inside each diff remain state roots.

s3_read_timeout_secs is a positive integer, defaulting to 20. It sets the AWS SDK operation timeout for GET and LIST calls, including bundle GET/range requests and SDK retries. Response body consumption after send() returns is outside this timeout; the SDK's existing stalled-stream protection remains unchanged. Errors propagate to the existing caller handling. archive-init uses the same default 20-second operation timeout.

Both standalone and archive-init accept --bundle-range-size <MIB> to tune the compacted StateDiff request size. The limit applies only when grouping multiple entries; a single entry larger than the configured value is fetched by itself.

CLI Subcommands

Subcommand Purpose
standalone Start the node
archive-init Initialize an archive database in bulk from S3 and RPC
db-migrate Migrate between databases (RocksDB ↔ MDBX, archive → state)
compact Compact the database to reclaim space
force-compact Force bottommost compaction so bulk-loaded SSTs regenerate the prefix bloom / partitioned index
rewind Reset the committed head to an earlier block and resync from S3
archive-scan Read-only scan of an archive column family (forensics/debugging)

Run leafage-evm <subcommand> --help for the full parameter list.

# Initialize an archive database
RUST_LOG=info ./target/release/leafage-evm archive-init --help

# Migrate a database (e.g. archive → state)
RUST_LOG=info ./target/release/leafage-evm db-migrate \
  --src /path/to/source/db \
  --dst /path/to/leafage/db

Benchmark

leafage-bench is a CLI tool for benchmarking eth_call performance between leafage-evm and geth.

Build

cargo build --release -p leafage-bench

Corpus

The benchmark corpus (bin/leafage-bench/corpus/corpus.json) is tracked via Git LFS. Pull it after cloning:

git lfs pull

Subcommands

run — Run the benchmark

./target/release/leafage-bench run \
  --corpus bin/leafage-bench/corpus/corpus.json \
  --target http://leafage-evm:8545 \
  --compare http://geth:8545
Parameter Default Description
--corpus / -c - Path to the corpus JSON file (required)
--target - Primary RPC endpoint URL (leafage-evm) (required)
--compare - Comparison RPC endpoint URL (geth)
--label all Only run cases with this complexity label: L1, L2, L3
--concurrency 10 Number of concurrent requests per endpoint
--requests corpus size Total requests per endpoint per round
--rounds 1 Number of benchmark rounds
--seed - Shuffle seed for corpus ordering
--output-dir - Directory for export files (summary.json, verbose.json)
--verbose false Write per-request details to verbose.json (requires --output-dir)

inspect — Inspect the corpus

Print summary statistics of the corpus without running any benchmark:

./target/release/leafage-bench inspect \
  --corpus bin/leafage-bench/corpus/corpus.json

Documentation

Document Description
Architecture.md System architecture, crate structure, key traits
StateManage.md In-memory state tree, fork handling, finalization
StateUpdater.md Kafka + S3 and HTTP update modes
Database.md RocksDB storage layout for state and archive nodes
DataSpec.md Data format specification for state updates
Deploy Deployment guide with Docker Compose

Architecture

State Management

leafage-evm manages state using a linked-list structure:

Latest Block (Head)
    ↓
Block N-1 diff
    ↓
   ...
    ↓
Block N-63 diff
    ↓
Base State (RocksDB)
  • Recent 64 block diffs are kept in memory for fast access
  • State queries search from top to bottom, falling back to RocksDB
  • On new block: push new diff to head, persist oldest diff to RocksDB when exceeding depth limit

State Update

leafage-evm supports two modes for receiving state updates:

  • Kafka + S3 (Primary): Receives block change notifications via Kafka, fetches block info and state diffs from S3
  • HTTP (Fallback): Polls trace_debankBlock RPC from a modified Geth instance

Project Structure

leafage-evm/
├── bin/leafage-evm/           # CLI entry point
├── crates/
│   ├── leafage-evm-types/     # Type definitions
│   ├── leafage-evm-storage/   # Storage layer (RocksDB/MDBX, StateTree)
│   ├── leafage-evm-rpc/       # JSON-RPC implementation
│   └── leafage-evm-chains/    # Chain-specific logic (BSC/Cosmos precompiles)
└── docs/                      # Documentation

License

MIT OR Apache-2.0

Arbitrum Classic

For historical Classic calls, use --evm-type=arbitrum --evm-custom-config='{"execution_mode":"classic"}'. See Classic execution scope and verification for StateDiff configuration, supported calls and the limits of account-state-only simulation. Nitro remains the default.

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

2 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages