An indexer, an explorer or a wallet backend needs to know when a block arrives, when it is safe, and when a reorg removes it. An xOneRS node on main offers four ways: WebSocket and IPC subscriptions, a transaction lifecycle stream, a Kafka exporter, and hooks for code that runs in the node's process. All four put the node first: a slow consumer is dropped or loses events, and block import never waits for it. Most of this came in pull requests merged on 2026-11-15, so it is not in v0.1.0. We found one compatibility problem in the Kafka exporter and filed it as issue #1148.
Which eth_subscribe kinds does xOne serve?
From parse_kind in crates/xone-rpc/src/subscriptions.rs, over WebSocket and IPC:
| Kind | Sends | Since |
|---|---|---|
newHeads |
A header each time a block becomes canonical | earlier |
newPersistedHeads (alias persisted) |
The same header once its store commit is durable | PR #1088 |
newFinalizedHeads (alias finalized) |
The same header when the QC-committed (finalized) number advances, once per number | PR #1088 |
logs |
Logs that match a filter (address, topics) |
earlier |
newPendingTransactions |
Hashes, or full transactions with true |
earlier |
txLifecycle |
Pool events per transaction (next section) | PR #1089 |
syncing |
geth's sync status object | earlier |
newFinalizedHeads is the one to use when a block must not be reversed. On XDPoS v2 a block is final once the 3-chain QC rule commits it. See finality for exchanges and bridges.
A subscriber whose queue is full is dropped, never waited for. Closing the connection removes its subscriptions, and there is no replay of events sent while a client was away (exposing RPC safely).
What does the txLifecycle stream show?
One event per change of a pooled transaction, with the hash and a reason, never the body (PR #1089):
- admission to the queued or the pending set;
replaced, with the newhashand theoldHash;- eviction;
- mined, when a new head includes it;
- reinjection after a reorg;
- permanent invalidation.
The pool buffers at most 1,024 events. When the buffer is full, the oldest is dropped and counted. Admission never waits for a subscriber. A wallet backend can use this to tell a user why a transaction is not mined. For what happens to transactions in a reorg, see reorg reinjection.
How does the Kafka exporter work?
--kafka.brokers host:port,... starts it; --kafka.topic (default xone.chain) and --kafka.checkpoint (default <datadir>/xone/kafka-checkpoint) set where records go and where progress is saved (PR #1066, crates/xone-node/src/kafka.rs).
- Record kinds:
block,receipts,logs,tracesandrevert. A reorg is exported asrevertrecords for the removed blocks, then the new branch. - Each record has a key
{kind}:{hash}. Delivery is at least once: after a restart the exporter resumes from the checkpoint, and a consumer removes duplicates by key. - Import never waits for the broker. Records go through a bounded queue; when it is full, records are dropped and counted.
- Credentials in the broker address are refused.
Broker versions. The exporter speaks Kafka Produce version 0 with message format 0, one record per request on partition 0. Apache Kafka 4.0 removed Produce versions 0 to 2 (KIP-896), so a 4.x broker refuses these requests. We found this by reading the code and did not test it against a broker. Until #1148 is fixed, expect the exporter to work only with brokers that still accept Produce v0.
What are in-process hooks?
For code built into the node, xone_node::hooks::LiveHooks delivers every canonical block, with header, transactions and receipts, and every reorg removal, in order (PR #1082). Import does not wait for a hook, and a hook cannot change committed state. Blocks imported before the hook was registered are not replayed. A NodeBuilder for library use came in PR #1083.
How do reads stay consistent during a reorg?
Since PR #1086, eth_getLogs and eth_feeHistory read their whole range from one snapshot of the chain: head, finalized tag, store generation and canonical hashes. If a commit, unwind or prune changes the store during the scan, the method answers canonical chain changed during the query; retry rather than mixing two branches. The xOneRS repository has a resumable event consumer that handles disconnects, reorgs and history refusals (PR #978).
Which way should an indexer use?
| Need | Use |
|---|---|
| A light client or a UI that shows new blocks | newHeads over WebSocket |
| Data that must not be reversed | newFinalizedHeads, then fetch the block |
| A durable copy of blocks, receipts and logs in a data platform | The Kafka exporter, after #1148, with dedupe on the key |
| Custom indexing inside the node, with no network hop | LiveHooks |
| Transaction status for users | txLifecycle |
In every case, read the block again by hash after a reconnect: none of the streams replays missed events.
Frequently asked questions
How do I get only finalized blocks from an XDC node?
Subscribe to newFinalizedHeads (or finalized) over WebSocket on an xOneRS build from main after PR #1088.
Does xOne replay events I missed while disconnected?
No. Subscriptions do not replay. Read the missing range with eth_getBlockByNumber and eth_getLogs, or use the Kafka exporter's checkpoint.
Does the xOne Kafka exporter work with Kafka 4?
Probably not yet. It sends Produce version 0, which Kafka 4.0 removed. Issue #1148 tracks the fix.
Can a slow subscriber slow down block import?
No. A slow subscriber is dropped, the lifecycle buffer drops its oldest event, and the Kafka queue drops records; import never waits.
Sources
This article describes the source and evidence at its review date. Check the linked repository for current implementation and qualification status.
Explore the engineering evidenceEdit this page on GitHub opens the Markdown source (content/blog/xone-chain-events-indexers/index.md). GitHub may ask you to sign in. Report a correction opens a prefilled GitHub issue for this page.