Skip to main content
Only two kinds of nodes take part in producing and consuming block data in Leafage: the write node executes blocks and exports data, and the query node applies that data and answers RPC requests. The other components (consistency-checker, nodex-proxy) hold no state; they only observe and schedule these two kinds of nodes.

Write node

The write node is the chain’s original execution client (a go-ethereum fork for ETH, the respective fork for other chains) with the pipeline library compiled in. Leafage does not replace its sync or execution logic; it only attaches tracing hooks to the execution path. The write node’s RPC port is not for application clients. It serves two callers: leafage-evm in HTTP polling mode, and engineers comparing a block’s StateDiff while debugging.

Leader and standby nodes

A chain can run several write nodes. All of them sync, execute, and upload to S3; S3 objects are keyed by block hash and state root, so repeated uploads are idempotent. There is exactly one difference: only the Leader publishes notifications to Kafka. A new Leader waits grace_period (default 10 seconds) before it starts publishing, giving the previous Leader time to finish.

Query node

The query node is the leafage-evm process. The most precise way to define it is to list what it does not do: It does exactly two things: apply state diffs in notification order, and run read-only calls with revm on local state (eth_call, batch calls, gas estimation, call tracing).
Because it stores no transactions, eth_getBlockByNumber and eth_getBlockByHash return only the block header, and transactions and uncles are always empty arrays. This is a design trade-off, not a defect.

Three query node types

leafage-evm splits into State and Archive nodes by startup flags. A Native node is a full node of the original chain, registered in etcd manually by operators as a fallback. A State node returns -39006 BlockNotFound for heights outside its window; an Archive node returns -39007 InvalidBlockID for blocks it does not have. The former makes nodex-proxy retry on an Archive node; the latter does not. See Block context and routing for the routing rules across the three types, and State and state diffs for the storage layout.

Node lifecycle

Whether a query node receives traffic is not its own decision. Three components take turns on the etcd key {chainID}[/{version}]/nodes/{ip}_{port}:
1

leafage-evm registers itself

On startup it writes its address and nodeType, with stateType set to 2 (lagging). Registration uses a “write only if the key does not exist” transaction, so it never overwrites state written by others; on exit it deletes its own key.
2

consistency-checker decides

For every block it processes, it polls eth_blockNumber on all nodes and writes the result back: 1 caught up with the chain head, 2 lagging, 3 offline. An offline node without a lease has its key deleted outright.
3

nodex-proxy admits

When it sees a new node, it runs a health check first (getLatestBlock) and only then adds the node to the load-balancing pool; a delete event removes the node immediately.
When investigating “why does this node get no traffic”, start from consistency-checker’s polling results, not from nodex-proxy.

Summary

  • The write node is the original execution client + pipeline and the only block executor; all write nodes upload to S3, only the Leader publishes to Kafka.
  • The query node does not sync, execute, maintain an MPT, or store transactions; it only applies state diffs and runs read-only calls.
  • Query nodes come as State / Archive / Native, distinguished by how much state they keep; error codes -39006 and -39008 drive the proxy’s fallback between them.
  • Whether a node receives traffic is decided by the stateType that consistency-checker writes to etcd; leafage-evm only registers, nodex-proxy only consumes.
Continue reading: