Status
Superseded by ADR 23 and ADR 26
Context
- Currently the
hydra-node
maintains two pieces of state during the life-cycle of a Hydra Head:- A
HeadState tx
provided by theHydraHead tx m
handle interface and part of theHydra.Node
module. It provides the basis for the mainhydra-node
business logic inHydra.Node.processNextEvent
andHydra.HeadLogic.update
Creation, Usage SomeOnChainHeadState
is kept in theHydra.Chain.Direct
to keep track of the latest known head state, including notable transaction outputs and information how to spend it (e.g. scripts and datums) Code, Usage 1, Usage 2, Usage 3 (There are other unrelated things kept in memory like the event history in the API server or a peer map in the network heartbeat component.)
- A
- The interface between the
Hydra.Node
and aHydra.Chain
component consists of- constructing certain Head protocol transactions given a description of it (
PostChainTx tx
):postTx :: MonadThrow m => PostChainTx tx -> m ()
- a callback function when the
Hydra.Chain
component observed a new Head protocol transaction described byOnChainTx tx
:type ChainCallback tx m = OnChainTx tx -> m ()
- constructing certain Head protocol transactions given a description of it (
- Given by the usage sites above, the
Hydra.Chain.Direct
module requires additional info to do both, construct protocol transactions withpostTx
as well as observe potentialOnChainTx
(here). Hence we see that, operation of theHydra.Chain.Direct
component (and likely any implementing the interface fully) is inherently stateful. - We are looking at upcoming features to handle rollbacks and dealing with persisting the head state.
- Both could benefit from the idea, that the
HeadState
is just a result of pureEvent
processing (a.k.a event sourcing). - Right now the
HeadState
kept inHydra.Node
alone, is not enough to fully describe the state of thehydra-node
. Hence it would not be enough to just persist all theEvent
s and replaying them to achieve persistence, nor resetting to some previousHeadState
in the presence of a rollback.
- Both could benefit from the idea, that the
Decision
We define and keep a "blackbox"
ChainStateType tx
in theHeadState tx
- It shall not be introspectable to the business logic in
HeadLogic
- It shall contain chain-specific information about the current Hydra Head, which will naturally need to evolve once we have multiple Heads in our feature scope
- For example:
data HeadState tx
= IdleState
| InitialState
{ chainState :: ChainStateType tx
-- ...
}
| OpenState
{ chainState :: ChainStateType tx
-- ...
}
| ClosedState
{ chainState :: ChainStateType tx
-- ...
}- It shall not be introspectable to the business logic in
We provide the latest
ChainStateType tx
topostTx
:postTx :: ChainStateType tx -> PostChainTx tx -> m ()
We change the
ChainEvent tx
data type and callback interface ofChain
to:data ChainEvent tx
= Observation
{ observedTx :: OnChainTx tx
, newChainState :: ChainStateType tx
}
| Rollback ChainSlot
| Tick UTCTime
type ChainCallback tx m = (ChainStateType tx -> Maybe (ChainEvent tx)) -> m ()with the meaning, that invocation of the callback indicates receival of a transaction which is
Maybe
observing a relevantChainEvent tx
, where anObservation
may include anewChainState
.We also decide to extend
OnChainEffect
with aChainState tx
to explicitly thread the usedchainState
in theHydra.HeadLogic
.
Consequences
- We need to change the construction of
Chain
handles and the call sites ofpostTx
- We need to extract the state handling (similar to the event queue) out of the
HydraNode
handle and shuffle the main ofhydra-node
a bit to be able to provide the latestChainState
to the chain callback as a continuation. - We need to make the
ChainState
serializable (ToJSON
,FromJSON
) as it will be part of theHeadState
. - We can drop the
TVar
of keepingOnChainHeadState
in theHydra.Chain.Direct
module. - We need to update
DirectChainSpec
andBehaviorSpec
test suites to mock/implement the callback & state handling. - We might be able to simplify the
ChainState tx
to be just aUTxOType tx
later. - As
OnChainEffect
andObservation
values will contain aChainStateType tx
, traces will automatically include the fullChainState
, which might be helpful but also possible big.
Alternative
- We could extend
PostChainTx
(likeObservation
) withChainState
and keep the signatures:
postTx :: MonadThrow m => PostChainTx tx -> m ()
type ChainCallback tx m = (ChainState tx -> Maybe (ChainEvent tx) -> m ()
- Not implemented as it is less clear on the need for a
ChainState
in the signatures.