Skip to content

Commit d1aff7b

Browse files
authored
Merge pull request #1126 from IntersectMBO/jordan/1102-submittxtonodelocal-leaky-exception
Fix submitTxToNodeLocal leaky exception
2 parents 2aa5ce7 + 9ad849c commit d1aff7b

File tree

3 files changed

+63
-24
lines changed

3 files changed

+63
-24
lines changed

cardano-api/src/Cardano/Api/Network/IPC.hs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,13 @@ module Cardano.Api.Network.IPC
172172
-- result <- Api.submitTxToNodeLocal connectionInfo (Api.TxInMode sbe signedTx)
173173
-- @
174174
--
175-
-- The result is a 'SubmitResult' value, which can be inspected as follows:
175+
-- The result is a 'TxSubmitResult' value, which can be inspected as follows:
176176
--
177177
-- @
178178
-- case result of
179-
-- Api.SubmitSuccess -> putStrLn "Transaction submitted successfully!"
180-
-- Api.SubmitFail reason -> error $ "Error submitting transaction: " ++ show reason
179+
-- Api.TxSubmitSuccess -> putStrLn "Transaction submitted successfully!"
180+
-- Api.TxSubmitFail reason -> error $ "Validation error: " ++ show reason
181+
-- Api.TxSubmitError err -> error $ "Error: " ++ show err
181182
-- @
182183
--
183184
-- If the command succeeds, the transaction gets into the node's mempool, ready
@@ -206,6 +207,7 @@ module Cardano.Api.Network.IPC
206207
, TxValidationErrorInCardanoMode
207208
, TxValidationError
208209
, submitTxToNodeLocal
210+
, TxSubmitResult (..)
209211
, SubmitResult (..)
210212

211213
-- *** Local state query

cardano-api/src/Cardano/Api/Network/IPC/Internal.hs

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ module Cardano.Api.Network.IPC.Internal
3535
, TxValidationErrorInCardanoMode
3636
, TxValidationError
3737
, submitTxToNodeLocal
38+
, TxSubmitResult (..)
3839
, SubmitResult (..)
3940

4041
-- *** Local state query
@@ -122,15 +123,18 @@ import Control.Concurrent.STM
122123
, putTMVar
123124
, takeTMVar
124125
, tryPutTMVar
126+
, tryTakeTMVar
125127
)
126-
import Control.Exception (throwIO)
128+
import Control.Exception (SomeException, throwIO)
129+
import Control.Exception.Safe (tryAny)
127130
import Control.Monad (void)
128131
import Control.Monad.IO.Class
129132
import Control.Tracer (nullTracer)
130133
import Data.Aeson (ToJSON, object, toJSON, (.=))
131134
import Data.ByteString.Lazy qualified as LBS
132135
import Data.Void (Void)
133136
import GHC.Exts (IsList (..))
137+
import GHC.Stack (HasCallStack)
134138
import Network.Mux qualified as Net
135139
import Network.Mux.Trace (nullTracers)
136140

@@ -621,22 +625,56 @@ queryNodeLocalState connctInfo mpoint query = do
621625
pure $ Net.Query.SendMsgDone ()
622626
}
623627

628+
-- | The result of submitting a transaction via 'submitTxToNodeLocal'.
629+
data TxSubmitResult
630+
= -- | The transaction was accepted into the node's mempool.
631+
TxSubmitSuccess
632+
| -- | The node rejected the transaction due to a validation error.
633+
TxSubmitFail TxValidationErrorInCardanoMode
634+
| -- | An exception escaped the underlying connection machinery. This covers
635+
-- network-level errors (e.g. socket missing, bearer closed mid-protocol,
636+
-- handshake failure) but may also include unexpected exceptions from the
637+
-- protocol handlers.
638+
--
639+
-- Note: if the protocol handler wrote a result to the internal TMVar before
640+
-- the exception was thrown (e.g. the node responded but the bearer then
641+
-- closed), that result takes precedence and 'TxSubmitSuccess' or
642+
-- 'TxSubmitFail' is returned instead.
643+
TxSubmitError SomeException
644+
deriving Show
645+
624646
submitTxToNodeLocal
625-
:: MonadIO m
647+
:: (HasCallStack, MonadIO m)
626648
=> LocalNodeConnectInfo
627649
-> TxInMode
628-
-> m (Net.Tx.SubmitResult TxValidationErrorInCardanoMode)
629-
submitTxToNodeLocal connctInfo tx = do
630-
resultVar <- liftIO newEmptyTMVarIO
631-
connectToLocalNode
632-
connctInfo
633-
LocalNodeClientProtocols
634-
{ localChainSyncClient = NoLocalChainSyncClient
635-
, localTxSubmissionClient = Just (localTxSubmissionClientSingle resultVar)
636-
, localStateQueryClient = Nothing
637-
, localTxMonitoringClient = Nothing
638-
}
639-
liftIO $ atomically (takeTMVar resultVar)
650+
-> m TxSubmitResult
651+
submitTxToNodeLocal connctInfo tx = liftIO $ do
652+
resultVar <- newEmptyTMVarIO
653+
result <-
654+
tryAny $
655+
connectToLocalNode
656+
connctInfo
657+
LocalNodeClientProtocols
658+
{ localChainSyncClient = NoLocalChainSyncClient
659+
, localTxSubmissionClient = Just (localTxSubmissionClientSingle resultVar)
660+
, localStateQueryClient = Nothing
661+
, localTxMonitoringClient = Nothing
662+
}
663+
case result of
664+
Left e -> do
665+
-- The connection threw an exception, but the protocol handler may have
666+
-- already written a result (e.g. node responded then bearer closed).
667+
-- Prefer the protocol result if available; fall back to the exception.
668+
mResult <- atomically (tryTakeTMVar resultVar)
669+
pure $ case mResult of
670+
Just Net.Tx.SubmitSuccess -> TxSubmitSuccess
671+
Just (Net.Tx.SubmitFail reason) -> TxSubmitFail reason
672+
Nothing -> TxSubmitError e
673+
Right () -> do
674+
submitResult <- atomically (takeTMVar resultVar)
675+
pure $ case submitResult of
676+
Net.Tx.SubmitSuccess -> TxSubmitSuccess
677+
Net.Tx.SubmitFail reason -> TxSubmitFail reason
640678
where
641679
localTxSubmissionClientSingle
642680
:: ()

cardano-rpc/src/Cardano/Rpc/Server/Internal/UtxoRpc/Submit.hs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ module Cardano.Rpc.Server.Internal.UtxoRpc.Submit
1414
where
1515

1616
import Cardano.Api
17-
import Cardano.Api.Network.IPC qualified as Net.Tx
1817
import Cardano.Rpc.Proto.Api.UtxoRpc.Submit qualified as U5c
1918
import Cardano.Rpc.Proto.Api.UtxoRpc.Submit qualified as UtxoRpc
2019
import Cardano.Rpc.Server.Internal.Error
@@ -58,12 +57,12 @@ submitTxMethod req = do
5857
-> m TxId
5958
submitTx sbe tx = do
6059
nodeConnInfo <- grab
61-
putTraceThrowEither . join . first TraceRpcSubmitN2cConnectionError
62-
=<< tryAny
63-
( submitTxToNodeLocal nodeConnInfo (TxInMode sbe tx) >>= \case
64-
Net.Tx.SubmitFail reason -> pure . Left $ TraceRpcSubmitTxValidationError reason
65-
Net.Tx.SubmitSuccess -> pure . Right $ getTxId $ getTxBody tx
66-
)
60+
result <-
61+
submitTxToNodeLocal nodeConnInfo (TxInMode sbe tx) <&> \case
62+
TxSubmitError e -> Left $ TraceRpcSubmitN2cConnectionError e
63+
TxSubmitFail reason -> Left $ TraceRpcSubmitTxValidationError reason
64+
TxSubmitSuccess -> Right $ getTxId $ getTxBody tx
65+
putTraceThrowEither result
6766

6867
putTraceThrowEither v = withFrozenCallStack $ do
6968
-- See Cardano.Node.Tracing.Tracers.Rpc in cardano-node for details how this is logged

0 commit comments

Comments
 (0)