utxo-indexer¶
In-process address→UTxO indexer daemon. Follows the chain via
Node-to-Client (N2C) ChainSync from a single Cardano relay, maintains
an indexed view (in-memory or RocksDB-backed), and exposes three read
primitives over a Unix-domain NDJSON socket: ready,
utxos_at <addr>, and await <txin> [timeout_seconds].
CLI¶
utxo-indexer \
--relay-socket /path/to/cardano-node/node.sock \
--listen /tmp/idx.sock \
--network-magic 42 \
--byron-epoch-slots 86400 \
[--ready-threshold-slots 60] \
[--security-param-k 2160] \
[--db-path /tmp/idx-db] \
[--reconnect-initial-ms 1000] \
[--reconnect-max-ms 30000] \
[--reconnect-reset-threshold-ms 30000] \
[--node-ready-timeout-ms <ms>]
| Flag | Default | Purpose |
|---|---|---|
--relay-socket |
— | Unix socket of the upstream cardano-node relay. |
--listen |
— | Path the indexer will bind for NDJSON consumers. |
--network-magic |
— | Cardano network magic (mainnet=764824073, preprod=1, preview=2, antithesis testnet=42). |
--byron-epoch-slots |
— | Byron-era epoch length (mainnet=21600, antithesis testnet=86400). |
--ready-threshold-slots |
60 |
slotsBehind ≤ this ⇒ ready=true. |
--security-param-k |
2160 |
Cardano security parameter k; caps the rollback log. |
--db-path |
(in-memory) | If set, RocksDB at this path. State survives process restart. |
--reconnect-initial-ms |
1000 |
Base of the supervisor's full-jitter exponential backoff. |
--reconnect-max-ms |
30000 |
Cap of the supervisor's backoff window. |
--reconnect-reset-threshold-ms |
30000 |
Healthy-run duration that resets the supervisor's failure counter. |
--node-ready-timeout-ms |
unset | Total cap on the LSQ tip probe. Unset = wait forever for the upstream node's ChainDB to load. Set explicitly for CI scenarios that want to fail fast. |
All flags are parsed in app/utxo-indexer/Main.hs; required flags are
--relay-socket, --listen, --network-magic, and
--byron-epoch-slots.
Read wire (NDJSON)¶
One request line per connection, one response line, then EOF:
REQ: {"ready": null}
RESP: {"ready":<bool>, "tipSlot":<int|null>,
"processedSlot":<int|null>, "slotsBehind":<int|null>, ...}
REQ: {"utxos_at": "<hex-of-address-bytes>"}
RESP: {"utxos": [{"txin":"<txid>#<ix>", "txout":"<base16-cbor>"}, ...]}
REQ: {"await": "<txid_hex>#<ix>"} # optional trailing timeout
Address bytes go on the wire as hex; bech32 parsing lives in the
consumer. On upstream disconnect the ready response carries an
upstream object naming the reason, attempt counter, and
elapsed-since-disconnect time, while utxos_at and await continue
to be served from cached state.
Reconnect behaviour (issue #97)¶
The daemon survives upstream relay disconnects (e.g. relay container restart) without exiting:
- the listen socket keeps accepting consumer connections;
readyreturnsready=falsewith theupstreamreason while disconnected;utxos_atandawaitkeep serving cached state — consumer connections are never EOFed because of an upstream disconnect;- before each reconnect attempt the supervisor probes the relay via LocalStateQuery (acquire volatile tip + read tip); ChainSync is attached only once the probe sees a non-Origin tip;
- reconnect retries use full-jitter exponential backoff (defaults
1 s → 30 s) via
Control.Retry.
Operators no longer need orchestrator-level restart: always on the
indexer container to recover from peer flapping.
Stderr trace stream¶
Every lifecycle transition emits one line on stderr:
2026-04-30T12:34:56.789Z INFO indexer event=started socketPath=... dbPath=...
2026-04-30T12:35:42.103Z INFO indexer event=disconnected reason=bearer-closed
2026-04-30T12:35:42.380Z INFO indexer event=node-replaying attempt=1 elapsedMs=275
2026-04-30T12:35:43.500Z INFO indexer event=reconnecting attempt=1 waitMs=312
2026-04-30T12:35:44.815Z INFO indexer event=reconnected resumeSlot=12345 elapsedMs=2712
2026-04-30T12:40:00.001Z INFO indexer event=stopped reason=normal
grep '^.*indexer ' is the operator-friendly filter. Counting
event=disconnected vs event=reconnected per peer-restart cycle is
the recommended fault-injection assertion; event=node-replaying
lines reveal that the upstream relay is alive but its ChainDB hasn't
finished loading.
Embedded use¶
Embedders that want to route N2CEvents into their own tracer can
pass a custom Tracer IO N2CEvent to
Cardano.Node.Client.UTxOIndexer.Daemon.runDaemon's first argument
instead of defaultStderrTracer. To own the IndexerHandle (RocksDB
is single-writer) and run the follower without the NDJSON server, use
Cardano.Node.Client.UTxOIndexer.Follower.withChainSyncFollower. The
cardano-tx-generator daemon in
lambdasistemi/cardano-tx-tools
embeds the indexer this way.