Crate layout
State management
State is kept in two tiers: recent blocks are held in memory as a linked list of diff layers, and older blocks are flushed to disk.
Queries: walk down the diff layers from the latest layer and return on the first hit; if every layer misses, read from disk. The vast majority of requests target
latest or blocks near the chain head, so they are usually pure in-memory operations.
Finalization: when a block’s depth exceeds --diff-depth-limit (default 64), the oldest layer is flushed to the database and removed from memory.
Forks: fork blocks enter hash_diff_map but not num_diff_map. Queries by hash can reach fork state; queries by height always follow the canonical chain.
See the repository’s docs/StateManage.md for details.
Two node modes
- State node (default)
- Archive node (--archive)
Keeps only the latest state; about 90GB for ETH mainnet.
Queries use a direct
get(). When the requested height falls outside the in-memory window, it returns -39006, and nodex-proxy forwards the request to an Archive node.docs/Database.md for the column family layout and tuning parameters.
State updates
The updater is chosen by parameters at startup: if--kafka-s3-config is set, Kafka + S3 is used; otherwise, if --rpc-addr is set, HTTP polling is used; if neither is set, state stays static.
Kafka + S3 (production)
bucket_nameis the internal bucket (Header + StateDiff);outer_bucket_nameis the external bucket (BlockFile, used for indexing by height and warmup)- The partition is set with an explicit
assignand auto-commit is disabled; the offset is written tooffset_dir, so every replica consumes the full message stream independently - Diff fetching is skipped when the parent block and the current block have the same state root
{chainID}/{height}/ prefix is used to resolve a height to the canonical hash.
--catchup-safe-depth suppresses fork misjudgment during catch-up: this many blocks near the chain head are instead backfilled block by block along the parent-hash chain in the Kafka notifications, rather than looked up by height index, to avoid selecting the wrong branch. The value should be greater than the target chain’s maximum reorg depth; 0 disables it.
HTTP polling (development / fallback)
Polls the write node’strace_debankBlock. When the parent block is not in the StateTree, it walks backwards to find the common ancestor, then applies blocks in order. Suitable for local development and environments without Kafka.
See docs/StateUpdater.md for details.
RPC API
eth namespace
call, multiCall, blockNumber, getBalance, getCode, getStorageAt, getTransactionCount, getBlockByNumber, getBlockByHash, chainId, baseFee.
DeBank namespace (no prefix)
version, getAddressNonce, getAddressBalance, getAddressCode, getStorageAt, contractMultiCall, simulateTransactions, estimateGas, getLatestBlock, getBlockByHeight, getBlockById, blockIsValid.
Others
See the RPC reference for the full parameters.
Multi-chain executors
--evm-type selects the executor implementation:
Adding a new chain requires implementing the
EvmExecutor trait, with the chain-specific logic placed in the leafage-evm-chains crate.
--historical-rpc and --historical-height are for historical ranges without block diffs (such as OP pre-bedrock): requests below the threshold are forwarded to an external RPC.
Command line
See the configuration reference for common parameters.
Service registration
At startup, the node writes itself into etcd at{chain_id}[/{version}]/nodes/{ip}_{port} with an initial stateType of 2 (lagging). After that, consistency-checker rewrites the state based on polling results, and nodex-proxy watches these keys to update the node pool.
Registration uses a periodic transaction (writing only when the key does not exist), so it does not overwrite the state written by the checker; the node deletes its own key on process exit.
Metrics
Exposed when--prometheus-addr is enabled:
Development
leafage-bench compares eth_call performance between leafage-evm and geth:
Related documents
Design documents in the repository:Architecture.md— overall architectureStateManage.md— state management: diff layers, finalization, forksStateUpdater.md— state updater: Kafka + S3 and HTTP pollingDatabase.md— database storage: column family layout and tuning parametersDataSpec.md— data specification