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

# Write nodes and query nodes

> The boundary between Leafage's two sides: what the write node does, what the query node does not do, the three query node types, and the node lifecycle in etcd.

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.

| What the write node does                                                         | Done by                                            |
| -------------------------------------------------------------------------------- | -------------------------------------------------- |
| P2P sync and block execution                                                     | The execution client itself                        |
| Collecting call traces, events, and state diffs                                  | pipeline tracer (`tracing.Hooks`)                  |
| Uploading Header, StateDiff, BlockFile, and BlockValidation to S3                | pipeline, concurrently inside `OnCommit`           |
| Publishing the block change notification once a new canonical head is set        | The execution client calling pipeline; Leader only |
| Returning the full execution output of one block on demand (`trace_debankBlock`) | The execution client's `trace` namespace           |

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**.

| Mode          | Configuration                                 | Behavior                                                                                                    |
| ------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| etcd election | Set `etcd_endpoints`, leave `is_backup` unset | Claims `{chainID}[/{version}]/writers/leader`; when the key disappears, backs off randomly and claims again |
| Manual        | Set `is_backup` to `true` / `false`           | Fixed role, no etcd needed                                                                                  |

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:

| Does not                              | Reason                                                                                |
| ------------------------------------- | ------------------------------------------------------------------------------------- |
| Join P2P sync                         | Blocks are executed by the write node; notifications arrive through Kafka             |
| Execute blocks                        | It only applies the state diffs exported by the write node to local state             |
| Maintain a Merkle Patricia Trie       | It never produces blocks or proves a state root, so a flat key-value store is enough  |
| Store transactions, receipts, or logs | `eth_call` only needs account state; transaction data lives in the S3 external bucket |

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).

<Note>
  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.
</Note>

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

| Type         | How it starts         | Kept on disk                                    | Queries it can answer                                     | Identity in etcd             |
| ------------ | --------------------- | ----------------------------------------------- | --------------------------------------------------------- | ---------------------------- |
| State node   | Default               | Latest value of every account and storage slot  | `latest` and heights inside the in-memory diff window     | `nodeType: 1`                |
| Archive node | `--archive`           | Value of every account and slot at every height | Any historical height                                     | `nodeType: 2`                |
| Native node  | Original chain client | Original full-node data                         | Calls that touch precompiles leafage-evm does not support | A separate `nativeNodes` key |

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](/en/concepts/block-context) for the routing rules across the three types, and [State and state diffs](/en/concepts/state) 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}`:

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

| `stateType` | Meaning                       | Receives traffic |
| ----------- | ----------------------------- | ---------------- |
| `1`         | Caught up with the chain head | Yes              |
| `2`         | Lagging                       | No               |
| `3`         | Offline                       | No               |

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:

* [State and state diffs](/en/concepts/state): how a query node organizes state internally.
* [Block context and routing](/en/concepts/block-context): how nodex-proxy chooses among the three node types.
* [Architecture overview](/en/architecture/overview#node-types): storage footprint of each node type on ETH mainnet.
