Rollbacks are an integral part of the behaviour of the Cardano chain: Any application built on top of Cardano and synchronizing its behaviour with the chain must be prepared to occasionally observe such rollbacks and Hydra is no exception.
This short document explains what rollbacks are and where they come from, and how Hydra Heads handle them.
What are rollbacks really?
Rollbacks happen on the Cardano chain, or any other truly decentralised blockchain for that matter, because it is essentially asynchronous in nature, eg. each node has its own view of the state of chain which it updates by communicating with other nodes, exchanging messages about known blocks, and this process takes time. New blocks are produced, which may be valid or invalid, and the state of the chain is eventually consistent, all nodes agreeing on the state of the chain only after some number of blocks have been processed.
Actually, Rollbacks is a misnomer and we should rather talk about forks. Let's see what this means from the perspective of three nodes running a Hydra Head. The following picture represents each node's view of the Layer 1 chain.
The immutable part is guaranteed to be identical on all nodes, being
k blocks in the past from current tip (on the mainnet
k is 2160). Here, node 2 receives a new block that's identical node 1 but node 3 receives a different block. Eventually, as node 3's chain is shorter than the other's it will be superseded by a longer one hence rolled back.
What happens for the node's Direct Chain observer is detailed in the following picture:
When new blocks are available, the
ChainSync client receives a
RollForward message with each new block. When a fork happens, it will first receive a
RollBackward message with a point, which identifies the slot and block hash at which point the chain has been rolled back (abstracted as a single number in the figure), then resume receiving new blocks through
How do they impact Hydra Node?
Rollbacks are problematic because, when a transaction is observed on-chain, it potentially changes the state of the Head, first by Initialising it, then collecting the Commits, opening the head through the CollectCom transaction and ultimately Closing it and Fanoutting the Head's final UTxO.
The following picture illustrates the issue of a rollback leading to potentially conflicting
If the Head does not properly handle the rollback, then it risks being in an inconsistent state w.r.t other nodes taking part in the Head. It is thus important that a rollback observed at the level of the
Direct chain component be propagated to the
HeadLogic in order for the latter to reset its state to be consistent with whatever happened on layer 1.
The consequences of a rollback on the Head's state are different depending at which point the Head is rolled back:
- If the rollback happens before or after the Head is open, eg. before the
CollectComtransaction or after the
Close, then things are relatively straightforward: We can just reset the Head's state to the point it was before the rolled back transaction was observed,
- If it happens while the Head is open, eg. the
CollectComtransaction is rolled back, it's much more problematic because the node has already started exchanging messages with its peers and its state no longer depends only on the chain.
How do we handle them?
🛠 Hydra currently handles rollback gracefully in simple cases, eg. case 1 above, and does not try to do anything clever when a
CollectCom happens which can lead easily to a Head becoming stale: Because one node is desynchronised from other nodes (it has observed a rollback of a
Collectcom transaction, reset its state before that, thus lost track of everything that happened while the Head was open), it will be necessary to close the head.
Rollbacks are handled in a very simple way in the Head, as the following sequence diagram shows:
- When a relevant transaction is observed, eg.
OnCommitin the case depicted above, it induces a new
HeadStatewhich is linked to the previous state,
- When a rollback is observed on-chain, the
HeadLogicis notified with
Rollbackevent telling the number of steps that need to be rolled-back: The
HeadLogiclayer does not need to care about the details of what a
Point Blockis on-chain, it only stores a sequence of state that can be easily "rolled back" to a previous state.