How it works
Oura is a streaming pipeline. It connects to a Cardano node, reads the chain one block at a time, runs each event through a chain of filters, and hands the result to a sink. Every stage is independent and runs on its own thread, so data flows through continuously rather than in batches.
The pipeline
A pipeline is just four pieces, wired together in your daemon.toml:
- Source — connects to the chain and produces a stream of events. It can
read from a local node (
N2C), a remote relay (N2N), a UTxO RPC endpoint, and more. - Intersect — tells the source where on the chain to start. The default is the current tip; you can also start from the origin or a specific point to backfill history.
- Filters — a sequence of transformations applied to each event, in order. Filters decode the raw block, split it into transactions, keep only what matches a pattern, reshape it into JSON, and so on.
- Sink — the destination. It takes the final event and sends it somewhere: your terminal, a file, a message broker, a database, an HTTP endpoint, a cloud service.
Events and rollbacks
The Cardano chain isn’t append-only — the tip can reorganize, abandoning recently-seen blocks in favor of a competing fork. Oura surfaces this directly: every event carries an action describing what happened to the block.
apply— a block was added to the chain. The normal case.undo— a block was rolled back. If your sink already acted on it, you need to reverse that.reset— the pipeline jumped to a new position (for example, on startup or after recovering across a gap). It carries apoint, not a record.
This is the one concept worth getting right before you build a consumer, because how you
handle undo is up to you. There are three common approaches:
- Handle it — react to
undoevents and reverse whatever the matchingapplydid. Most precise, but your downstream logic has to support it. - Buffer it away — add the RollbackBuffer filter to hold
blocks until they’re deep enough to be unlikely to roll back, so your sink rarely sees an
undoat all (it trades a little latency for simplicity). - Stay idempotent — design the sink so reprocessing the same block is harmless, and combine it with a cursor so a restart resumes cleanly instead of re-reading from scratch.
For the exact JSON shape of an event — the event / point / record envelope — see the
Data Dictionary.
How records change shape
The record inside each event isn’t fixed — its shape depends on which filters ran before the
sink. A source emits the raw block as CBOR; filters progressively refine it:
CborBlock ──SplitBlock──▶ CborTx ──ParseCbor──▶ parsed Tx ──IntoJson──▶ JSONSo a sink reading straight from a source sees raw block CBOR, while one sitting after
ParseCbor sees a structured transaction. You compose the chain to produce whatever shape your
destination needs. The canonical pipeline — SplitBlock → ParseCbor → Select → sink — is a good
default starting point.
See the Filters overview for the full set, and the record-variant table in the Data Dictionary for exactly which filter produces which shape.