Safe Haskell | Safe-Inferred |
---|---|
Language | GHC2021 |
Implements the Head Protocol's state machine as pure functions in an event sourced manner.
More specifically, the update
will handle Input
s (or rather "commands" in
event sourcing speak) and convert that into a list of side-Effect
s and
StateChanged
events, which in turn are aggregate
d into a single
HeadState
.
As the specification is using a more imperative way of specifying the protocl
behavior, one would find the decision logic in update
while state updates
can be found in the corresponding aggregate
branch.
Synopsis
- update :: IsChainState tx => Environment -> Ledger tx -> HeadState tx -> Input tx -> Outcome tx
- defaultTTL :: TTL
- aggregateState :: IsChainState tx => HeadState tx -> Outcome tx -> HeadState tx
- recoverChainStateHistory :: (Foldable t, IsChainState tx) => ChainStateType tx -> t (StateChanged tx) -> ChainStateHistory tx
- recoverState :: (Foldable t, IsChainState tx) => HeadState tx -> t (StateChanged tx) -> HeadState tx
- aggregate :: IsChainState tx => HeadState tx -> StateChanged tx -> HeadState tx
- onIdleClientInit :: Environment -> Outcome tx
- onIdleChainInitTx :: Environment -> ChainStateType tx -> HeadId -> HeadSeed -> HeadParameters -> [OnChainId] -> Outcome tx
- onInitialChainCommitTx :: Monoid (UTxOType tx) => InitialState tx -> ChainStateType tx -> Party -> UTxOType tx -> Outcome tx
- onInitialClientAbort :: Monoid (UTxOType tx) => InitialState tx -> Outcome tx
- onInitialChainAbortTx :: Monoid (UTxOType tx) => ChainStateType tx -> Committed tx -> HeadId -> Outcome tx
- onInitialChainCollectTx :: IsChainState tx => InitialState tx -> ChainStateType tx -> Outcome tx
- onOpenClientNewTx :: tx -> Outcome tx
- onOpenNetworkReqTx :: IsTx tx => Environment -> Ledger tx -> OpenState tx -> TTL -> tx -> Outcome tx
- onOpenNetworkReqSn :: IsTx tx => Environment -> Ledger tx -> OpenState tx -> Party -> SnapshotNumber -> [TxIdType tx] -> Outcome tx
- onOpenNetworkAckSn :: IsTx tx => Environment -> OpenState tx -> Party -> Signature (Snapshot tx) -> SnapshotNumber -> Outcome tx
- onOpenClientClose :: OpenState tx -> Outcome tx
- onOpenChainCloseTx :: OpenState tx -> ChainStateType tx -> SnapshotNumber -> UTCTime -> Outcome tx
- onClosedChainContestTx :: ClosedState tx -> ChainStateType tx -> SnapshotNumber -> UTCTime -> Outcome tx
- onClosedClientFanout :: ClosedState tx -> Outcome tx
- onClosedChainFanoutTx :: ClosedState tx -> ChainStateType tx -> Outcome tx
- data Input tx
- = ClientInput {
- clientInput :: ClientInput tx
- | NetworkInput { }
- | ChainInput {
- chainEvent :: ChainEvent tx
- = ClientInput {
- type TTL = Natural
- data LogicError tx
- = UnhandledInput {
- input :: Input tx
- currentHeadState :: HeadState tx
- | RequireFailed { }
- | NotOurHead {
- ourHeadId :: HeadId
- otherHeadId :: HeadId
- = UnhandledInput {
- data RequirementFailure tx
- = ReqSnNumberInvalid { }
- | ReqSnNotLeader { }
- | InvalidMultisignature {
- multisig :: Text
- vkeys :: [VerificationKey HydraKey]
- | SnapshotAlreadySigned { }
- | AckSnNumberInvalid { }
- | SnapshotDoesNotApply {
- requestedSn :: SnapshotNumber
- txid :: TxIdType tx
- error :: ValidationError
- data HeadState tx
- = Idle (IdleState tx)
- | Initial (InitialState tx)
- | Open (OpenState tx)
- | Closed (ClosedState tx)
- data ClosedState tx = ClosedState {}
- type Committed tx = Map Party (UTxOType tx)
- data CoordinatedHeadState tx = CoordinatedHeadState {
- localUTxO :: UTxOType tx
- localTxs :: [tx]
- allTxs :: Map (TxIdType tx) tx
- confirmedSnapshot :: ConfirmedSnapshot tx
- seenSnapshot :: SeenSnapshot tx
- newtype IdleState tx = IdleState {
- chainState :: ChainStateType tx
- data InitialState tx = InitialState {}
- data OpenState tx = OpenState {}
- type PendingCommits = Set Party
- data SeenSnapshot tx
- = NoSeenSnapshot
- | LastSeenSnapshot { }
- | RequestedSnapshot { }
- | SeenSnapshot { }
- seenSnapshotNumber :: SeenSnapshot tx -> SnapshotNumber
- setChainState :: ChainStateType tx -> HeadState tx -> HeadState tx
- data Effect tx
- = ClientEffect {
- serverOutput :: ServerOutput tx
- | NetworkEffect { }
- | OnChainEffect {
- postChainTx :: PostChainTx tx
- = ClientEffect {
- data StateChanged tx
- = HeadInitialized {
- parameters :: HeadParameters
- chainState :: ChainStateType tx
- headId :: HeadId
- headSeed :: HeadSeed
- | CommittedUTxO {
- party :: Party
- committedUTxO :: UTxOType tx
- chainState :: ChainStateType tx
- | HeadAborted {
- chainState :: ChainStateType tx
- | HeadOpened {
- chainState :: ChainStateType tx
- initialUTxO :: UTxOType tx
- | TransactionAppliedToLocalUTxO {
- tx :: tx
- newLocalUTxO :: UTxOType tx
- | SnapshotRequestDecided { }
- | SnapshotRequested {
- snapshot :: Snapshot tx
- requestedTxIds :: [TxIdType tx]
- newLocalUTxO :: UTxOType tx
- newLocalTxs :: [tx]
- | TransactionReceived {
- tx :: tx
- | PartySignedSnapshot { }
- | SnapshotConfirmed {
- snapshot :: Snapshot tx
- signatures :: MultiSignature (Snapshot tx)
- | HeadClosed { }
- | HeadContested { }
- | HeadIsReadyToFanout
- | HeadFannedOut {
- chainState :: ChainStateType tx
- | ChainRolledBack {
- chainState :: ChainStateType tx
- | TickObserved { }
- = HeadInitialized {
- data Outcome tx
- = Continue {
- stateChanges :: [StateChanged tx]
- effects :: [Effect tx]
- | Wait {
- reason :: WaitReason tx
- stateChanges :: [StateChanged tx]
- | Error {
- error :: LogicError tx
- = Continue {
- data WaitReason tx
- wait :: WaitReason tx -> Outcome tx
- cause :: Effect tx -> Outcome tx
- causes :: [Effect tx] -> Outcome tx
- newState :: StateChanged tx -> Outcome tx
- noop :: Outcome tx
- isLeader :: HeadParameters -> Party -> SnapshotNumber -> Bool
Documentation
:: IsChainState tx | |
=> Environment | |
-> Ledger tx | |
-> HeadState tx | Current HeadState to validate the command against. |
-> Input tx | Input to be processed. |
-> Outcome tx |
Handles inputs and converts them into StateChanged
events along with
Effect
s, in case it is processed succesfully. Later, the Node will
aggregate
the events, resulting in a new HeadState
.
defaultTTL :: TTL Source #
aggregateState :: IsChainState tx => HeadState tx -> Outcome tx -> HeadState tx Source #
recoverChainStateHistory :: (Foldable t, IsChainState tx) => ChainStateType tx -> t (StateChanged tx) -> ChainStateHistory tx Source #
recoverState :: (Foldable t, IsChainState tx) => HeadState tx -> t (StateChanged tx) -> HeadState tx Source #
aggregate :: IsChainState tx => HeadState tx -> StateChanged tx -> HeadState tx Source #
Reflect StateChanged
events onto the HeadState
aggregate.
onIdleClientInit :: Environment -> Outcome tx Source #
:: Environment | |
-> ChainStateType tx | New chain state. |
-> HeadId | |
-> HeadSeed | |
-> HeadParameters | |
-> [OnChainId] | |
-> Outcome tx |
Observe an init transaction, initialize parameters in an InitialState
and
notify clients that they can now commit.
Transition: IdleState
→ InitialState
onInitialChainCommitTx Source #
:: Monoid (UTxOType tx) | |
=> InitialState tx | |
-> ChainStateType tx | New chain state |
-> Party | Comitting party |
-> UTxOType tx | Committed UTxO |
-> Outcome tx |
Observe a commit transaction and record the committed UTxO in the state. Also, if this is the last commit to be observed, post a collect-com transaction on-chain.
Transition: InitialState
→ InitialState
onInitialClientAbort :: Monoid (UTxOType tx) => InitialState tx -> Outcome tx Source #
Client request to abort the head. This leads to an abort transaction on chain, reimbursing already committed UTxOs.
Transition: InitialState
→ InitialState
onInitialChainAbortTx Source #
Observe an abort transaction by switching the state and notifying clients about it.
Transition: InitialState
→ IdleState
onInitialChainCollectTx Source #
:: IsChainState tx | |
=> InitialState tx | |
-> ChainStateType tx | New chain state |
-> Outcome tx |
Observe a collectCom transaction. We initialize the OpenState
using the
head parameters from IdleState
and construct an InitialSnapshot
holding
u0
from the committed UTxOs.
Transition: InitialState
→ OpenState
:: tx | The transaction to be submitted to the head. |
-> Outcome tx |
:: IsTx tx | |
=> Environment | |
-> Ledger tx | |
-> OpenState tx | |
-> TTL | |
-> tx | The transaction to be submitted to the head. |
-> Outcome tx |
Process a transaction request (ReqTx
) from a party.
We apply this transaction to the seen utxo (ledger state). If not applicable,
we wait and retry later. If it applies, this yields an updated seen ledger
state. Then, we check whether we are the leader for the next snapshot and
emit a snapshot request ReqSn
including this transaction if needed.
:: IsTx tx | |
=> Environment | |
-> Ledger tx | |
-> OpenState tx | |
-> Party | Party which sent the ReqSn. |
-> SnapshotNumber | Requested snapshot number. |
-> [TxIdType tx] | List of transactions to snapshot. |
-> Outcome tx |
Process a snapshot request (ReqSn
) from party.
This checks that s is the next snapshot number and that the party is
responsible for leading that snapshot. Then, we potentially wait until the
previous snapshot is confirmed (no snapshot is in flight), before we apply
(or wait until applicable) the requested transactions to the last confirmed
snapshot. Only then, we start tracking this new "seen" snapshot, compute a
signature of it and send the corresponding AckSn
to all parties. Finally,
the pending transaction set gets pruned to only contain still applicable
transactions.
:: IsTx tx | |
=> Environment | |
-> OpenState tx | |
-> Party | Party which sent the AckSn. |
-> Signature (Snapshot tx) | Signature from other party. |
-> SnapshotNumber | Snapshot number of this AckSn. |
-> Outcome tx |
Process a snapshot acknowledgement (AckSn
) from a party.
We do require that the is from the last seen or next expected snapshot, and
potentially wait wait for the corresponding ReqSn
before proceeding. If the
party hasn't sent us a signature yet, we store it. Once a signature from each
party has been collected, we aggregate a multi-signature and verify it is
correct. If everything is fine, the snapshot can be considered as the latest
confirmed one. Similar to processing a ReqTx
, we check whether we are
leading the next snapshot and craft a corresponding ReqSn
if needed.
onOpenClientClose :: OpenState tx -> Outcome tx Source #
:: OpenState tx | |
-> ChainStateType tx | New chain state. |
-> SnapshotNumber | Closed snapshot number. |
-> UTCTime | Contestation deadline. |
-> Outcome tx |
Observe a close transaction. If the closed snapshot number is smaller than our last confirmed, we post a contest transaction. Also, we do schedule a notification for clients to fanout at the deadline.
Transition: OpenState
→ ClosedState
onClosedChainContestTx Source #
:: ClosedState tx | |
-> ChainStateType tx | New chain state. |
-> SnapshotNumber | |
-> UTCTime | Contestation deadline. |
-> Outcome tx |
Observe a contest transaction. If the contested snapshot number is smaller than our last confirmed snapshot, we post a contest transaction.
Transition: ClosedState
→ ClosedState
onClosedClientFanout :: ClosedState tx -> Outcome tx Source #
Client request to fanout leads to a fanout transaction on chain using the
latest confirmed snapshot from ClosedState
.
Transition: ClosedState
→ ClosedState
onClosedChainFanoutTx Source #
:: ClosedState tx | |
-> ChainStateType tx | New chain state |
-> Outcome tx |
Observe a fanout transaction by finalize the head state and notifying clients about it.
Transition: ClosedState
→ IdleState
Inputs that are processed by the head logic (the "core"). Corresponding to each of the "shell" layers, we distinguish between inputs from the client, the network and the chain.
ClientInput | Input received from clients via the Hydra.API. |
| |
NetworkInput | Input received from peers via a Hydra.Network.
|
ChainInput | Input received from the chain via a Hydra.Chain. |
|
Instances
data LogicError tx Source #
UnhandledInput | |
| |
RequireFailed | |
NotOurHead | |
|
Instances
data RequirementFailure tx Source #
Instances
The main state of the Hydra protocol state machine. It holds both, the
overall protocol state, but also the off-chain CoordinatedHeadState
.
Each of the sub-types (InitialState, OpenState, etc.) contain a black-box
IdleState
corresponding to the ChainEvent
that has been observed leading
to the state.
Note that rollbacks are currently not fully handled in the head logic and only this internal chain state gets replaced with the "rolled back to" version.
TODO: chainState would actualy not be needed in the HeadState anymore as we
do not persist the HeadState
and not access it in the HeadLogic either.
Idle (IdleState tx) | |
Initial (InitialState tx) | |
Open (OpenState tx) | |
Closed (ClosedState tx) |
Instances
data ClosedState tx Source #
An Closed
head with an current candidate ConfirmedSnapshot
, which may
be contested before the $sel:contestationDeadline:ClosedState
.
ClosedState | |
|
Instances
data CoordinatedHeadState tx Source #
Off-chain state of the Coordinated Head protocol.
CoordinatedHeadState | |
|
Instances
An Idle
head only having a chain state with things seen on chain so far.
Instances
data InitialState tx Source #
An Initial
head which already has an identity and is collecting commits.
InitialState | |
|
Instances
An Open
head with a CoordinatedHeadState
tracking off-chain
transactions.
OpenState | |
|
Instances
type PendingCommits = Set Party Source #
data SeenSnapshot tx Source #
Data structure to help in tracking whether we have seen or requested a ReqSn already and if seen, the signatures we collected already.
NoSeenSnapshot | Never saw a ReqSn. |
LastSeenSnapshot | No snapshot in flight with last seen snapshot number as given. |
RequestedSnapshot | ReqSn was sent out and it should be considered already in flight. |
SeenSnapshot | ReqSn for given snapshot was received. |
Instances
seenSnapshotNumber :: SeenSnapshot tx -> SnapshotNumber Source #
Get the last seen snapshot number given a SeenSnapshot
.
setChainState :: ChainStateType tx -> HeadState tx -> HeadState tx Source #
Update the chain state in any HeadState
.
Analogous to inputs, the pure head logic "core" can have effects emited to the "shell" layers and we distinguish the same: effects onto the client, the network and the chain.
ClientEffect | Effect to be handled by the Hydra.API, results in sending this |
| |
NetworkEffect | Effect to be handled by a Hydra.Network, results in a |
OnChainEffect | Effect to be handled by a Hydra.Chain, results in a |
|
Instances
data StateChanged tx Source #
Head state changed event. These events represent all the internal state changes, get persisted and processed in an event sourcing manner.
HeadInitialized | |
| |
CommittedUTxO | |
| |
HeadAborted | |
| |
HeadOpened | |
| |
TransactionAppliedToLocalUTxO | |
| |
SnapshotRequestDecided | |
SnapshotRequested | A snapshot was requested by some party. NOTE: We deliberately already include an updated local ledger state to not need a ledger to interpret this event. |
| |
TransactionReceived | |
| |
PartySignedSnapshot | |
SnapshotConfirmed | |
| |
HeadClosed | |
HeadContested | |
HeadIsReadyToFanout | |
HeadFannedOut | |
| |
ChainRolledBack | |
| |
TickObserved | |
Instances
Continue | Continue with the given state updates and side effects. |
| |
Wait | Wait for some condition to be met with optional state updates. |
| |
Error | Processing resulted in an error. |
|
Instances
data WaitReason tx Source #
WaitOnNotApplicableTx | |
WaitOnSnapshotNumber | |
WaitOnSeenSnapshot | |
WaitOnTxs | |
| |
WaitOnContestationDeadline |
Instances
wait :: WaitReason tx -> Outcome tx Source #
newState :: StateChanged tx -> Outcome tx Source #
isLeader :: HeadParameters -> Party -> SnapshotNumber -> Bool Source #