Skip to content

Commit 3dffe72

Browse files
authored
Fix tui recovery (#2201)
<!-- Describe your change here --> Follow-up on #2159 Fixes #2156 #### Motivation To fix WS clients trying to recover their state when reconnecting to a rotated node #### 🔄 Changes - Added head state to rotated log event - Added network info type + projection from network state changed events - Extended greetings message with network info #### ⚠️ Unresolved - To recover pending Increments > note FIXME! comments --- <!-- Consider each and tick it off one way or the other --> * [x] CHANGELOG updated or not needed * [x] Documentation updated or not needed * [x] Haddocks updated or not needed * [x] No new TODOs introduced or explained herafter
1 parent e6e2ac2 commit 3dffe72

File tree

12 files changed

+684
-77
lines changed

12 files changed

+684
-77
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ changes.
1010

1111
## [0.23.0] - UNRELEASED
1212

13-
- Fix bug where TUI would have out-of-date head status information in the
14-
presence of event rotation.
13+
- Fix bug where TUI would have out-of-date head status information
14+
after restarting on a rotated node (due to event rotation).
1515

1616
- Accept additional field `tokens` when depositing to specify different (non-ADA) assets that should be depositted to a Head returning any leftover to the user.
1717

hydra-node/golden/Greetings/Greetings.json

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,42 @@
22
"samples": [
33
{
44
"env": {
5-
"configuredPeers": "",
5+
"configuredPeers": "s",
66
"contestationPeriod": 43200,
7-
"depositPeriod": 61129,
7+
"depositPeriod": 60080,
88
"otherParties": [],
9-
"participants": [
10-
"01000000000001000101000101010101000101000001000000010100"
11-
],
9+
"participants": [],
1210
"party": {
13-
"vkey": "35719d612496ce0f320cd39e4693a2997194cec954ec602064f67a5c81150136"
11+
"vkey": "348cdf360a866dedf92cb18cfb736c2da212ea44d2f1270fe60dad88801be97c"
1412
},
15-
"signingKey": "c659b97ce8782bc64476a5e82a14cbd38d0413cb15b18fa87d281b44272fa476"
13+
"signingKey": "23db2c1b05cc1c905486253e1a39b0df087217f33180f7d5cf4aa1ec8c0b6634"
1614
},
1715
"headStatus": "Closed",
1816
"hydraHeadId": "01000001000001000000000001000101",
19-
"hydraNodeVersion": "",
17+
"hydraNodeVersion": "G",
2018
"me": {
2119
"vkey": "3d8fb298d77d2fda814e9530b5539758751d27ad1c234080e04ec2e15dea1490"
2220
},
21+
"networkInfo": {
22+
"networkConnected": true,
23+
"peersInfo": {
24+
"127.0.0.1:5000": true,
25+
"peer.local:5001": false
26+
}
27+
},
28+
"snapshotUtxo": {
29+
"0101010101010001010001010001010101010001010000000100010101010100#30": {
30+
"address": "addr_test1xqqxwuptvt44m42v49ux7l37cy6zg9ylxpr0ltmsnaedfx9frwx7crjjl27pwadu3a4vere9d2cmrm8x2xey5rmzw73sfcqwk4",
31+
"datum": null,
32+
"datumhash": "da0beccb4644ffb9db547cb2c058ee0b27abe96774d43dd85884483b5452b696",
33+
"inlineDatum": null,
34+
"inlineDatumRaw": null,
35+
"referenceScript": null,
36+
"value": {
37+
"lovelace": 45000000000000000
38+
}
39+
}
40+
},
2341
"tag": "Greetings"
2442
}
2543
],

hydra-node/golden/ServerOutput/EventLogRotated.json

Lines changed: 446 additions & 0 deletions
Large diffs are not rendered by default.

hydra-node/json-schemas/api.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,7 @@ components:
872872
- headStatus
873873
- hydraNodeVersion
874874
- env
875+
- networkInfo
875876
properties:
876877
tag:
877878
type: string
@@ -1773,13 +1774,16 @@ components:
17731774
required:
17741775
- tag
17751776
- seq
1777+
- checkpoint
17761778
- timestamp
17771779
properties:
17781780
tag:
17791781
type: string
17801782
enum: ["EventLogRotated"]
17811783
seq:
17821784
$ref: "api.yaml#/components/schemas/SequenceNumber"
1785+
checkpoint:
1786+
$ref: "api.yaml#/components/schemas/HeadState"
17831787
timestamp:
17841788
$ref: "api.yaml#/components/schemas/UTCTime"
17851789

@@ -2985,6 +2989,26 @@ components:
29852989
configuredPeers:
29862990
type: string
29872991

2992+
NetworkInfo:
2993+
type: object
2994+
description: |
2995+
L2 Hydra network status information.
2996+
required:
2997+
- networkConnected
2998+
- peersInfo
2999+
additionalProperties: false
3000+
properties:
3001+
networkConnected:
3002+
type: boolean
3003+
peersInfo:
3004+
type: object
3005+
description: Mapping from peer Host to connection status.
3006+
additionalProperties:
3007+
type: boolean
3008+
example:
3009+
"127.0.0.1:5000": true
3010+
"peer.local:5001": false
3011+
29883012
SequenceNumber:
29893013
type: integer
29903014
minimum: 0

hydra-node/src/Hydra/API/Server.hs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import Hydra.API.Projection (Projection (..), mkProjection)
2020
import Hydra.API.ServerOutput (
2121
ClientMessage,
2222
CommitInfo (..),
23+
NetworkInfo (..),
2324
ServerOutput (..),
2425
TimedServerOutput (..),
2526
)
@@ -105,6 +106,7 @@ withAPIServer config env party eventSource tracer chain pparams serverOutputFilt
105106
-- CommitInfo etc. would suffice and are less fragile
106107
commitInfoP <- mkProjection "commitInfoP" CannotCommit projectCommitInfo
107108
pendingDepositsP <- mkProjection "pendingDepositsP" [] projectPendingDeposits
109+
networkInfoP <- mkProjection "networkInfoP" (NetworkInfo False mempty) projectNetworkInfo
108110
let historyTimedOutputs = sourceEvents .| map mkTimedServerOutputFromStateEvent .| catMaybes
109111
_ <-
110112
runConduitRes $
@@ -133,7 +135,7 @@ withAPIServer config env party eventSource tracer chain pparams serverOutputFilt
133135
. simpleCors
134136
$ websocketsOr
135137
defaultConnectionOptions
136-
(wsApp env party tracer historyTimedOutputs callback headStateP responseChannel serverOutputFilter)
138+
(wsApp env party tracer historyTimedOutputs callback headStateP networkInfoP responseChannel serverOutputFilter)
137139
( httpApp
138140
tracer
139141
chain
@@ -158,6 +160,7 @@ withAPIServer config env party eventSource tracer chain pparams serverOutputFilt
158160
update headStateP stateChanged
159161
update commitInfoP stateChanged
160162
update pendingDepositsP stateChanged
163+
update networkInfoP stateChanged
161164
-- Send to the client if it maps to a server output
162165
case mkTimedServerOutputFromStateEvent event of
163166
Nothing -> pure ()
@@ -216,7 +219,7 @@ setupServerNotification = do
216219
pure (putMVar mv (), takeMVar mv)
217220

218221
-- | Defines the subset of 'StateEvent' that should be sent as 'TimedServerOutput' to clients.
219-
mkTimedServerOutputFromStateEvent :: IsTx tx => StateEvent tx -> Maybe (TimedServerOutput tx)
222+
mkTimedServerOutputFromStateEvent :: IsChainState tx => StateEvent tx -> Maybe (TimedServerOutput tx)
220223
mkTimedServerOutputFromStateEvent event =
221224
case mapStateChangedToServerOutput stateChanged of
222225
Nothing -> Nothing
@@ -261,7 +264,7 @@ mkTimedServerOutputFromStateEvent event =
261264
StateChanged.ChainRolledBack{} -> Nothing
262265
StateChanged.TickObserved{} -> Nothing
263266
StateChanged.LocalStateCleared{..} -> Just SnapshotSideLoaded{..}
264-
StateChanged.Checkpoint{} -> Just EventLogRotated
267+
StateChanged.Checkpoint{state} -> Just $ EventLogRotated state
265268

266269
-- | Projection to obtain the list of pending deposits.
267270
projectPendingDeposits :: IsTx tx => [TxIdType tx] -> StateChanged.StateChanged tx -> [TxIdType tx]
@@ -288,3 +291,15 @@ projectCommitInfo commitInfo = \case
288291
StateChanged.HeadAborted{} -> CannotCommit
289292
StateChanged.HeadClosed{} -> CannotCommit
290293
_other -> commitInfo
294+
295+
projectNetworkInfo :: NetworkInfo -> StateChanged.StateChanged tx -> NetworkInfo
296+
projectNetworkInfo networkInfo = \case
297+
StateChanged.NetworkConnected ->
298+
networkInfo{networkConnected = True}
299+
StateChanged.NetworkDisconnected ->
300+
networkInfo{networkConnected = False, peersInfo = mempty}
301+
StateChanged.PeerConnected{peer} ->
302+
networkInfo{peersInfo = Map.insert peer True (peersInfo networkInfo)}
303+
StateChanged.PeerDisconnected{peer} ->
304+
networkInfo{peersInfo = Map.insert peer False (peersInfo networkInfo)}
305+
_other -> networkInfo

hydra-node/src/Hydra/API/ServerOutput.hs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import Data.Aeson.Lens (atKey, key)
1111
import Data.ByteString.Lazy qualified as LBS
1212
import Hydra.API.ClientInput (ClientInput)
1313
import Hydra.Chain (PostChainTx, PostTxError)
14-
import Hydra.Chain.ChainState (IsChainState)
14+
import Hydra.Chain.ChainState (ChainStateType, IsChainState)
1515
import Hydra.HeadLogic.State (ClosedState (..), HeadState (..), InitialState (..), OpenState (..), SeenSnapshot (..))
1616
import Hydra.HeadLogic.State qualified as HeadState
1717
import Hydra.Ledger (ValidationError)
@@ -103,6 +103,7 @@ data Greetings tx = Greetings
103103
, snapshotUtxo :: Maybe (UTxOType tx)
104104
, hydraNodeVersion :: String
105105
, env :: Environment
106+
, networkInfo :: NetworkInfo
106107
}
107108
deriving (Generic)
108109

@@ -212,15 +213,15 @@ data ServerOutput tx
212213
-- The local state has been reset, meaning pending transactions were pruned.
213214
-- Any signing round has been discarded, and the snapshot leader has changed accordingly.
214215
SnapshotSideLoaded {headId :: HeadId, snapshotNumber :: SnapshotNumber}
215-
| EventLogRotated
216+
| EventLogRotated {checkpoint :: HeadState tx}
216217
deriving stock (Generic)
217218

218219
deriving stock instance IsChainState tx => Eq (ServerOutput tx)
219220
deriving stock instance IsChainState tx => Show (ServerOutput tx)
220221
deriving anyclass instance IsChainState tx => FromJSON (ServerOutput tx)
221222
deriving anyclass instance IsChainState tx => ToJSON (ServerOutput tx)
222223

223-
instance ArbitraryIsTx tx => Arbitrary (ServerOutput tx) where
224+
instance (ArbitraryIsTx tx, Arbitrary (ChainStateType tx)) => Arbitrary (ServerOutput tx) where
224225
arbitrary = genericArbitrary
225226
shrink = recursivelyShrink
226227

@@ -317,6 +318,17 @@ data CommitInfo
317318
| NormalCommit HeadId
318319
| IncrementalCommit HeadId
319320

321+
-- | L2 Hydra network status information.
322+
data NetworkInfo = NetworkInfo
323+
{ networkConnected :: Bool
324+
, peersInfo :: Map Host Bool
325+
}
326+
deriving stock (Eq, Show, Generic)
327+
deriving anyclass (ToJSON, FromJSON)
328+
329+
instance Arbitrary NetworkInfo where
330+
arbitrary = genericArbitrary
331+
320332
-- | Get latest confirmed snapshot UTxO from 'HeadState'.
321333
getSnapshotUtxo :: Monoid (UTxOType tx) => HeadState tx -> Maybe (UTxOType tx)
322334
getSnapshotUtxo = \case

hydra-node/src/Hydra/API/WSServer.hs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Hydra.API.ServerOutput (
2121
Greetings (..),
2222
HeadStatus (..),
2323
InvalidInput (..),
24+
NetworkInfo,
2425
ServerOutputConfig (..),
2526
TimedServerOutput (..),
2627
WithAddressedTx (..),
@@ -67,11 +68,13 @@ wsApp ::
6768
(ClientInput tx -> IO ()) ->
6869
-- | Read model to enhance 'Greetings' messages with 'HeadStatus'.
6970
Projection STM.STM (StateChanged tx) (HeadState tx) ->
71+
-- | Read model to enhance 'Greetings' messages with 'NetworkInfo'.
72+
Projection STM.STM (StateChanged tx) NetworkInfo ->
7073
TChan (Either (TimedServerOutput tx) (ClientMessage tx)) ->
7174
ServerOutputFilter tx ->
7275
PendingConnection ->
7376
IO ()
74-
wsApp env party tracer history callback headStateP responseChannel ServerOutputFilter{txContainsAddr} pending = do
77+
wsApp env party tracer history callback headStateP networkInfoP responseChannel ServerOutputFilter{txContainsAddr} pending = do
7578
traceWith tracer NewAPIConnection
7679
let path = requestPath $ pendingRequest pending
7780
queryParams <- uriQuery <$> mkURIBs path
@@ -95,7 +98,8 @@ wsApp env party tracer history callback headStateP responseChannel ServerOutputF
9598
-- important to make sure the latest configured 'party' is reaching the
9699
-- client.
97100
forwardGreetingOnly config con = do
98-
headState <- atomically getLatest
101+
headState <- atomically getLatestHeadState
102+
networkInfo <- atomically getLatestNetworkInfo
99103
sendTextData con $
100104
handleUtxoInclusion config (atKey "snapshotUtxo" .~ Nothing) $
101105
Aeson.encode
@@ -106,9 +110,11 @@ wsApp env party tracer history callback headStateP responseChannel ServerOutputF
106110
, snapshotUtxo = getSnapshotUtxo headState
107111
, hydraNodeVersion = showVersion NetworkVersions.hydraNodeVersion
108112
, env
113+
, networkInfo
109114
}
110115

111-
Projection{getLatest} = headStateP
116+
Projection{getLatest = getLatestHeadState} = headStateP
117+
Projection{getLatest = getLatestNetworkInfo} = networkInfoP
112118

113119
mkServerOutputConfig :: [QueryParam] -> ServerOutputConfig
114120
mkServerOutputConfig qp =

hydra-node/src/Hydra/Network.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ module Hydra.Network (
1717
import Hydra.Prelude hiding (show)
1818

1919
import Cardano.Ledger.Orphans ()
20+
import Data.Aeson (FromJSONKeyFunction (FromJSONKeyTextParser), ToJSONKey (..))
21+
import Data.Aeson.Types (FromJSONKey (..), toJSONKeyText)
2022
import Data.IP (IP, toIPv4w)
2123
import Data.Text (pack, unpack)
24+
import Data.Text qualified as T
2225
import Hydra.Cardano.Api (Key (SigningKey))
2326
import Hydra.Tx (Party)
2427
import Hydra.Tx.Crypto (HydraKey)
@@ -160,6 +163,12 @@ instance Arbitrary Host where
160163
ip <- toIPv4w <$> arbitrary
161164
Host (toText $ show ip) <$> arbitrary
162165

166+
instance ToJSONKey Host where
167+
toJSONKey = toJSONKeyText (T.pack . show)
168+
169+
instance FromJSONKey Host where
170+
fromJSONKey = FromJSONKeyTextParser (readHost . T.unpack)
171+
163172
showHost :: Host -> String
164173
showHost Host{hostname, port} =
165174
unpack hostname <> ":" <> show port

hydra-node/test/Hydra/BehaviorSpec.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1299,7 +1299,7 @@ createTestHydraClient outputs messages outputHistory HydraNode{inputQueue, nodeS
12991299
}
13001300

13011301
createHydraNode ::
1302-
(IsTx tx, MonadDelay m, MonadAsync m, MonadLabelledSTM m, MonadThrow m) =>
1302+
(IsChainState tx, MonadDelay m, MonadAsync m, MonadLabelledSTM m, MonadThrow m) =>
13031303
Tracer m (HydraNodeLog tx) ->
13041304
Ledger tx ->
13051305
ChainStateType tx ->

0 commit comments

Comments
 (0)