Skip to main content
Every Leafage component works around the same block stream: the write node publishes block change notifications in execution order, and query nodes and consistency-checker consume them in that same order. Three things in this stream need to be kept apart: the notification itself, the canonical chain versus fork blocks, and published versus confirmed.

Block change notification

Each time the write node sets a new canonical head, it publishes one BlockChangeNotification to the Kafka internal topic:
The notification carries metadata only, no payload. Consumers take the hash and fetch the Header and StateDiff from S3 themselves. Kafka is therefore responsible only for ordering and low latency, while large objects go through S3 and can be fetched in parallel.
The S3 upload and the Kafka publish are not one atomic operation. By the time a consumer receives the notification the S3 objects are usually there, but consumers must tolerate a brief 404 and retry.

Canonical chain and fork blocks

The canonical chain is the chain that contains the write node’s current canonical head. Before publishing, the write node compares the last published block with the new head by finding their common ancestor:
With only new blocks, changeType is 1 and newBlocks is ascending by height; when a branch is dropped, changeType is 2 and both dropBlocks and newBlocks are present. The dropped blocks are fork blocks. They do not vanish from the system: The same height may have several objects under the external bucket prefix {chainID}[/{version}]/{height}/; is_fork is the only way to tell them apart. A query node catching up from a cold start relies on it to resolve a height to its canonical hash.

Published versus confirmed

At the moment the write node publishes a notification, query nodes have not applied the block yet. A consumer subscribed directly to the internal topic would receive the notification while the block is “not yet queryable”. consistency-checker adds a confirmation step in between:
External notifications use one structure for both new and dropped blocks, distinguished by is_fork. On a reorg, the checker publishes the drop notifications first, then the new-block notifications. External consumers therefore get one guarantee: when the notification arrives, the query cluster can already serve that block. If replicas are still not ready after check_timeout_ms (default 2000 milliseconds), the checker does not publish the block and retries the whole step.

One block, three records

Once a block is confirmed, three places along the pipeline each keep a record of it, serving different readers: Both leafage-evm and consistency-checker expose getLatestBlock / getBlockByHeight / getBlockById / blockIsValid. The method names match but the semantics differ: the former returns the node’s own current view, the latter returns confirmed blocks.

Summary

  • A block change notification carries hashes and heights only, on a single totally ordered partition, published by the Leader alone; payloads live in S3.
  • A reorg is expressed as changeType: 2 plus dropBlocks; dropped fork blocks stay in both S3 and query node memory, and consistency-checker writes back is_fork.
  • Published (internal topic) and confirmed (external topic) are two different streams; external consumers should subscribe only to the latter.
  • The confirmed height is written to etcd’s lastBlockNumber, which is what nodex-proxy uses to choose between State and Archive.
Continue reading:
  • Block context and routing: how a request says “which block”, and how the confirmed height feeds into routing.
  • Data flow: reorg handling details in the write node, leafage-evm, and the checker.
  • Interface contracts: naming, encoding, and producer settings of the two topics.