Status
Accepted
Context
The Hydra node represents a significant engineering asset, providing layer 1 monitoring, peer to peer consensus, durable persistence, and an isomorphic Cardano ledger. Because of this, it is being eyed as a key building block not just in Hydra based applications, but other protocols as well.
Currently the
hydra-node
uses a very basic persistence mechanism for it's internalHeadState
, that is savingStateChanged
events to file on disk and reading them back to load and re-aggregate theHeadState
upon startup.- Some production setups would benefit from storing these events to a service like Amazon Kinesis data stream instead of local files.
The
hydra-node
websocket-based API is the only available event stream right now and might not fit all purposes.- See also ADR 3 and 25
- Internally, this is realized as a single
Server
handle which cansendOutput :: ServerOutput tx -> m ()
- These
ServerOutput
s closely relate toStateChanged
events andClientEffect
s are yielded by the logic layer often together with theStateChanged
. For example:
onInitialChainAbortTx newChainState committed headId =
StateChanged HeadAborted{chainState = newChainState}
<> Effects [ClientEffect $ ServerOutput.HeadIsAborted{headId, utxo = fold committed}]Users of
hydra-node
are interested to add alternative implementations for storing, loading and consuming events of the Hydra protocol.
Decision
We create two new interfaces in the
hydra-node
architecture:data EventSource e m = EventSource { getEvents :: m [e] }
data EventSink e m = EventSink { putEvent :: e -> m () }
We realize our current
PersistenceIncremental
used for persistingStateChanged
events is both anEventSource
and anEventSink
We drop the
persistence
from the main handleHydraNode tx m
, add oneEventSource
and allow manyEventSinks
data HydraNode tx m = HydraNode
{ -- ...
, eventSource :: EventSource (StateEvent tx) m
, eventSinks :: [EventSink (StateEvent tx) m]
}
The
hydra-node
will load events andhydrate
itsHeadState
usinggetEvents
of the singleeventSource
.The
stepHydraNode
main loop does callputEvent
on alleventSinks
in sequence. Any failure will make thehydra-node
process terminate and require a restart.When loading events from
eventSource
onhydra-node
startup, it will also re-submit events viaputEvent
to alleventSinks
.The default
hydra-node
main loop does use the file-basedEventSource
and a single file-basedEventSink
(using the same file).We realize that the
EventSource
andEventSink
handles, as well as their aggregation inHydraNode
are used as an API by forks of thehydra-node
and try to minimize changes to it.
Consequences
The default operation of the
hyda-node
remains unchanged.There are other things called
Event
andEventQueue(putEvent)
right now in thehydra-node
. This is getting confusing and when we implement this, we should also rename several things first (tidying).Interface first: Implementations of
EventSink
should specify their format in a non-ambiguous and versioned way, especially when a correspondingEventSource
exists.The API
Server
can be modelled and refactored as anEventSink
.Projects forking the hydra node have dedicated extension points for producing and consuming events.
Sundae Labs can build a "Save transaction batches to S3" proof of concept
EventSink
.Sundae Labs can build a "Scrolls source"
EventSink
.Sundae Labs can build a "Amazon Kinesis"
EventSource
andEventSink
.
Out of scope / future work
Available implementations for
EventSource
andEventSink
could be- configured upon
hydra-node
startup using for example URIs:--event-source file://state
or--event-sink s3://some-bucket
- dynamically loaded as plugins without having to fork
hydra-node
.
- configured upon
The
Network
andChain
parts qualify asEventSink
s as well or shall those be triggered byEffect
s still?