Skip to content

Commit e949c6e

Browse files
committed
Handle ClientInput operation failures in HTTP API responses
1 parent 8f94eb2 commit e949c6e

File tree

2 files changed

+277
-34
lines changed

2 files changed

+277
-34
lines changed

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

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import Data.ByteString.Short ()
1414
import Data.Text (pack)
1515
import Hydra.API.APIServerLog (APIServerLog (..), Method (..), PathInfo (..))
1616
import Hydra.API.ClientInput (ClientInput (..))
17-
import Hydra.API.ServerOutput (ClientMessage, CommitInfo (..), ServerOutput (..), TimedServerOutput (..), getConfirmedSnapshot, getSeenSnapshot, getSnapshotUtxo)
17+
import Hydra.API.ServerOutput (ClientMessage (..), CommitInfo (..), ServerOutput (..), TimedServerOutput (..), getConfirmedSnapshot, getSeenSnapshot, getSnapshotUtxo)
1818
import Hydra.Cardano.Api (Coin, LedgerEra, Tx)
1919
import Hydra.Chain (Chain (..), PostTxError (..), draftCommitTx)
2020
import Hydra.Chain.ChainState (IsChainState)
@@ -227,21 +227,21 @@ httpApp tracer directChain env pparams getHeadState getCommitInfo getPendingDepo
227227
respond . okJSON $ getSeenSnapshot hs
228228
("POST", ["snapshot"]) ->
229229
consumeRequestBodyStrict request
230-
>>= handleSideLoadSnapshot putClientInput
230+
>>= handleSideLoadSnapshot putClientInput apiTransactionTimeout responseChannel
231231
>>= respond
232232
("POST", ["commit"]) ->
233233
consumeRequestBodyStrict request
234234
>>= handleDraftCommitUtxo env pparams directChain getCommitInfo
235235
>>= respond
236236
("DELETE", ["commits", _]) ->
237237
consumeRequestBodyStrict request
238-
>>= handleRecoverCommitUtxo putClientInput (last . fromList $ pathInfo request)
238+
>>= handleRecoverCommitUtxo putClientInput apiTransactionTimeout responseChannel (last . fromList $ pathInfo request)
239239
>>= respond
240240
("GET", ["commits"]) ->
241241
getPendingDeposits >>= respond . responseLBS status200 jsonContent . Aeson.encode
242242
("POST", ["decommit"]) ->
243243
consumeRequestBodyStrict request
244-
>>= handleDecommit putClientInput
244+
>>= handleDecommit putClientInput apiTransactionTimeout responseChannel
245245
>>= respond
246246
("GET", ["protocol-parameters"]) ->
247247
respond . responseLBS status200 jsonContent . Aeson.encode $ pparams
@@ -326,15 +326,38 @@ handleRecoverCommitUtxo ::
326326
forall tx.
327327
IsChainState tx =>
328328
(ClientInput tx -> IO ()) ->
329+
ApiTransactionTimeout ->
330+
TChan (Either (TimedServerOutput tx) (ClientMessage tx)) ->
329331
Text ->
330332
LBS.ByteString ->
331333
IO Response
332-
handleRecoverCommitUtxo putClientInput recoverPath _body = do
334+
handleRecoverCommitUtxo putClientInput apiTransactionTimeout responseChannel recoverPath _body = do
333335
case parseTxIdFromPath recoverPath of
334336
Left err -> pure err
335337
Right recoverTxId -> do
338+
dupChannel <- atomically $ dupTChan responseChannel
336339
putClientInput Recover{recoverTxId}
337-
pure $ responseLBS status200 jsonContent (Aeson.encode $ Aeson.String "OK")
340+
let wait = do
341+
event <- atomically $ readTChan dupChannel
342+
case event of
343+
Left TimedServerOutput{output = CommitRecovered{}} ->
344+
pure $ responseLBS status200 jsonContent (Aeson.encode $ Aeson.String "OK")
345+
Right (CommandFailed{clientInput = Recover{}}) ->
346+
pure $ responseLBS status400 jsonContent (Aeson.encode $ Aeson.String "Recover failed")
347+
_ -> wait
348+
timeout (realToFrac (apiTransactionTimeoutNominalDiffTime apiTransactionTimeout)) wait >>= \case
349+
Just r -> pure r
350+
Nothing ->
351+
pure $
352+
responseLBS
353+
status202
354+
jsonContent
355+
( Aeson.encode $
356+
object
357+
[ "tag" .= Aeson.String "RecoverSubmitted"
358+
, "timeout" .= Aeson.String ("Operation timed out after " <> pack (show apiTransactionTimeout) <> " seconds")
359+
]
360+
)
338361
where
339362
parseTxIdFromPath txIdStr =
340363
case Aeson.eitherDecode (encodeUtf8 txIdStr) :: Either String (TxIdType tx) of
@@ -361,29 +384,82 @@ handleSubmitUserTx directChain body = do
361384
where
362385
Chain{submitTx} = directChain
363386

364-
handleDecommit :: forall tx. FromJSON tx => (ClientInput tx -> IO ()) -> LBS.ByteString -> IO Response
365-
handleDecommit putClientInput body =
387+
handleDecommit ::
388+
forall tx.
389+
FromJSON tx =>
390+
(ClientInput tx -> IO ()) ->
391+
ApiTransactionTimeout ->
392+
TChan (Either (TimedServerOutput tx) (ClientMessage tx)) ->
393+
LBS.ByteString ->
394+
IO Response
395+
handleDecommit putClientInput apiTransactionTimeout responseChannel body =
366396
case Aeson.eitherDecode' body :: Either String tx of
367397
Left err ->
368398
pure $ responseLBS status400 jsonContent (Aeson.encode $ Aeson.String $ pack err)
369399
Right decommitTx -> do
400+
dupChannel <- atomically $ dupTChan responseChannel
370401
putClientInput Decommit{decommitTx}
371-
pure $ responseLBS status200 jsonContent (Aeson.encode $ Aeson.String "OK")
402+
let wait = do
403+
event <- atomically $ readTChan dupChannel
404+
case event of
405+
Left TimedServerOutput{output = DecommitFinalized{}} ->
406+
pure $ responseLBS status200 jsonContent (Aeson.encode $ Aeson.String "OK")
407+
Left TimedServerOutput{output = DecommitInvalid{}} ->
408+
pure $ responseLBS status400 jsonContent (Aeson.encode $ Aeson.String "Decommit invalid")
409+
Right (CommandFailed{clientInput = Decommit{}}) ->
410+
pure $ responseLBS status400 jsonContent (Aeson.encode $ Aeson.String "Decommit failed")
411+
_ -> wait
412+
timeout (realToFrac (apiTransactionTimeoutNominalDiffTime apiTransactionTimeout)) wait >>= \case
413+
Just r -> pure r
414+
Nothing ->
415+
pure $
416+
responseLBS
417+
status202
418+
jsonContent
419+
( Aeson.encode $
420+
object
421+
[ "tag" .= Aeson.String "DecommitSubmitted"
422+
, "timeout" .= Aeson.String ("Operation timed out after " <> pack (show apiTransactionTimeout) <> " seconds")
423+
]
424+
)
372425

373426
-- | Handle request to side load confirmed snapshot.
374427
handleSideLoadSnapshot ::
375428
forall tx.
376429
IsChainState tx =>
377430
(ClientInput tx -> IO ()) ->
431+
ApiTransactionTimeout ->
432+
TChan (Either (TimedServerOutput tx) (ClientMessage tx)) ->
378433
LBS.ByteString ->
379434
IO Response
380-
handleSideLoadSnapshot putClientInput body = do
435+
handleSideLoadSnapshot putClientInput apiTransactionTimeout responseChannel body = do
381436
case Aeson.eitherDecode' body :: Either String (SideLoadSnapshotRequest tx) of
382437
Left err ->
383438
pure $ responseLBS status400 jsonContent (Aeson.encode $ Aeson.String $ pack err)
384439
Right SideLoadSnapshotRequest{snapshot} -> do
440+
dupChannel <- atomically $ dupTChan responseChannel
385441
putClientInput $ SideLoadSnapshot snapshot
386-
pure $ responseLBS status200 jsonContent (Aeson.encode $ Aeson.String "OK")
442+
let wait = do
443+
event <- atomically $ readTChan dupChannel
444+
case event of
445+
Left TimedServerOutput{output = SnapshotSideLoaded{}} ->
446+
pure $ responseLBS status200 jsonContent (Aeson.encode $ Aeson.String "OK")
447+
Right (CommandFailed{clientInput = SideLoadSnapshot{}}) ->
448+
pure $ responseLBS status400 jsonContent (Aeson.encode $ Aeson.String "Side-load snapshot failed")
449+
_ -> wait
450+
timeout (realToFrac (apiTransactionTimeoutNominalDiffTime apiTransactionTimeout)) wait >>= \case
451+
Just r -> pure r
452+
Nothing ->
453+
pure $
454+
responseLBS
455+
status202
456+
jsonContent
457+
( Aeson.encode $
458+
object
459+
[ "tag" .= Aeson.String "SideLoadSnapshotSubmitted"
460+
, "timeout" .= Aeson.String ("Operation timed out after " <> pack (show apiTransactionTimeout) <> " seconds")
461+
]
462+
)
387463

388464
-- | Handle request to submit a transaction to the head.
389465
handleSubmitL2Tx ::

0 commit comments

Comments
 (0)