From 0b30efc90489a478e37cb53e6bafc48a257c6db1 Mon Sep 17 00:00:00 2001
From: paolino <paolo.veronelli@gmail.com>
Date: Thu, 16 May 2024 10:18:30 +0000
Subject: [PATCH 1/4] Add an exception to report not supported eras during
 post-tx

---
 .../Cardano/Wallet/Api/Http/Server/Error.hs   | 44 ++++++++++++++-----
 lib/api/src/Cardano/Wallet/Api/Types/Error.hs | 15 +++++++
 .../src/Cardano/Wallet/Network.hs             | 14 ++++--
 .../test/unit/Cardano/Wallet/Api/TypesSpec.hs |  5 +++
 4 files changed, 64 insertions(+), 14 deletions(-)

diff --git a/lib/api/src/Cardano/Wallet/Api/Http/Server/Error.hs b/lib/api/src/Cardano/Wallet/Api/Http/Server/Error.hs
index e136aac3694..5b08b1f1202 100644
--- a/lib/api/src/Cardano/Wallet/Api/Http/Server/Error.hs
+++ b/lib/api/src/Cardano/Wallet/Api/Http/Server/Error.hs
@@ -116,6 +116,7 @@ import Cardano.Wallet.Api.Types.Error
     , ApiErrorNotEnoughMoneyShortfall (..)
     , ApiErrorSharedWalletNoSuchCosigner (..)
     , ApiErrorTxOutputLovelaceInsufficient (..)
+    , ApiErrorUnsupportedEra (..)
     )
 import Cardano.Wallet.Primitive.Ledger.Convert
     ( Convert (toWallet)
@@ -198,6 +199,7 @@ import qualified Data.ByteString as BS
 import qualified Data.ByteString.Lazy as BL
 import qualified Data.Foldable as F
 import qualified Data.List as L
+import qualified Data.Set as Set
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
 import qualified Internal.Cardano.Write.Tx as Write
@@ -626,18 +628,38 @@ instance IsServerError ErrRemoveTx where
 instance IsServerError ErrPostTx where
     toServerError = \case
         ErrPostTxValidationError err ->
-            apiError err500 CreatedInvalidTransaction $ mconcat
-                [ "The submitted transaction was rejected by the local "
-                , "node. Here's an error message that may help with "
-                , "debugging:\n", err
-                ]
+            apiError err500 CreatedInvalidTransaction
+                $ mconcat
+                    [ "The submitted transaction was rejected by the local "
+                    , "node. Here's an error message that may help with "
+                    , "debugging:\n"
+                    , err
+                    ]
         ErrPostTxMempoolFull ->
-            apiError err425
-            {errBody = "Mempool is full, please try resubmitting again later."}
-                MempoolIsFull $ mconcat
-                [ "The submitted transaction was rejected by the Cardano node "
-                , "because its mempool was full."
-                ]
+            apiError
+                err425
+                    { errBody =
+                        "Mempool is full, please try resubmitting again later."
+                    }
+                MempoolIsFull
+                $ mconcat
+                    [ "The submitted transaction was rejected by the Cardano "
+                    , "node because its mempool was full."
+                    ]
+        e@(ErrPostTxEraUnsupported unsupported) ->
+            apiError err403 error' $ toText e
+          where
+            error' =
+                UnsupportedEra
+                    $ ApiErrorUnsupportedEra
+                        { unsupportedEra = toApiEra unsupported
+                        , supportedEras =
+                            Set.fromList
+                                ( toApiEra
+                                    . Write.toAnyCardanoEra
+                                    <$> [minBound .. maxBound]
+                                )
+                        }
 
 instance IsServerError ErrSubmitTransaction where
     toServerError = \case
diff --git a/lib/api/src/Cardano/Wallet/Api/Types/Error.hs b/lib/api/src/Cardano/Wallet/Api/Types/Error.hs
index b0cad3f6ca3..7c4a164b211 100644
--- a/lib/api/src/Cardano/Wallet/Api/Types/Error.hs
+++ b/lib/api/src/Cardano/Wallet/Api/Types/Error.hs
@@ -31,6 +31,7 @@ module Cardano.Wallet.Api.Types.Error
     , ApiErrorNotEnoughMoneyShortfall (..)
     , ApiErrorMissingWitnessesInTransaction (..)
     , ApiErrorNoSuchPool (..)
+    , ApiErrorUnsupportedEra (..)
     )
     where
 
@@ -76,6 +77,9 @@ import Data.Data
 import Data.Maybe
     ( fromMaybe
     )
+import Data.Set
+    ( Set
+    )
 import Data.Text
     ( Text
     )
@@ -217,6 +221,7 @@ data ApiErrorInfo
     | BlockHeaderNotFound
     | TranslationByronTxOutInContext
     | BalanceTxInlinePlutusV3ScriptNotSupportedInBabbage
+    | UnsupportedEra !ApiErrorUnsupportedEra
 
     deriving (Eq, Generic, Show, Data, Typeable)
     deriving anyclass NFData
@@ -235,6 +240,16 @@ apiErrorInfoOptions = defaultSumTypeOptions
         }
     }
 
+data ApiErrorUnsupportedEra = ApiErrorUnsupportedEra
+    { unsupportedEra :: !ApiEra
+    -- ^ The unsupported era (as specified by the caller).
+    , supportedEras :: !(Set ApiEra)
+    -- ^ The set of eras that we currently support.
+    }
+    deriving (Data, Eq, Generic, Show, Typeable)
+    deriving (FromJSON, ToJSON) via DefaultRecord ApiErrorUnsupportedEra
+    deriving anyclass NFData
+
 data ApiErrorSharedWalletNoSuchCosigner = ApiErrorSharedWalletNoSuchCosigner
     { cosignerIndex
         :: !ApiCosignerIndex
diff --git a/lib/network-layer/src/Cardano/Wallet/Network.hs b/lib/network-layer/src/Cardano/Wallet/Network.hs
index 84557d8d22f..62e53880c0d 100644
--- a/lib/network-layer/src/Cardano/Wallet/Network.hs
+++ b/lib/network-layer/src/Cardano/Wallet/Network.hs
@@ -106,6 +106,7 @@ import qualified Internal.Cardano.Write.Tx as Write
 {-----------------------------------------------------------------------------
     NetworkLayer
 ------------------------------------------------------------------------------}
+
 -- | Interface for network capabilities.
 data NetworkLayer m block = NetworkLayer
     { chainSync
@@ -134,8 +135,7 @@ data NetworkLayer m block = NetworkLayer
     -- only change once per epoch.
     , currentProtocolParametersInRecentEras
         :: m (MaybeInRecentEra Write.PParams)
-        -- ^ Get the last known protocol parameters for recent eras.
-
+    -- ^ Get the last known protocol parameters for recent eras.
     , currentSlottingParameters
         :: m SlottingParameters
     -- ^ Get the last known slotting parameters. In principle, these can
@@ -193,6 +193,7 @@ instance Functor m => Functor (NetworkLayer m) where
 {-----------------------------------------------------------------------------
     ChainFollower
 ------------------------------------------------------------------------------}
+
 -- | A collection of callbacks to use with the 'chainSync' function.
 data ChainFollower m point tip blocks = ChainFollower
     { checkpointPolicy :: Integer -> CheckpointPolicy
@@ -275,7 +276,10 @@ mapChainFollower fpoint12 fpoint21 ftip fblocks cf =
 -------------------------------------------------------------------------------}
 
 -- | Error while trying to send a transaction
-data ErrPostTx = ErrPostTxValidationError Text | ErrPostTxMempoolFull
+data ErrPostTx
+    = ErrPostTxValidationError Text
+    | ErrPostTxMempoolFull
+    | ErrPostTxEraUnsupported AnyCardanoEra
     deriving (Generic, Show, Eq)
 
 instance ToText ErrPostTx where
@@ -283,6 +287,10 @@ instance ToText ErrPostTx where
         ErrPostTxValidationError msg -> msg
         ErrPostTxMempoolFull ->
             "mempool was full and refused posted transaction"
+        ErrPostTxEraUnsupported unsupported ->
+            "Submitted transaction was in "
+                <> T.pack (show unsupported)
+                <> " era, which is not supported"
 
 -- | Error while trying to retrieve a block
 newtype ErrFetchBlock = ErrNoBlockAt Read.ChainPoint
diff --git a/lib/unit/test/unit/Cardano/Wallet/Api/TypesSpec.hs b/lib/unit/test/unit/Cardano/Wallet/Api/TypesSpec.hs
index 1a88d62d2ef..284165d1edc 100644
--- a/lib/unit/test/unit/Cardano/Wallet/Api/TypesSpec.hs
+++ b/lib/unit/test/unit/Cardano/Wallet/Api/TypesSpec.hs
@@ -283,6 +283,7 @@ import Cardano.Wallet.Api.Types.Error
     , ApiErrorNotEnoughMoneyShortfall (..)
     , ApiErrorSharedWalletNoSuchCosigner (..)
     , ApiErrorTxOutputLovelaceInsufficient (..)
+    , ApiErrorUnsupportedEra (..)
     )
 import Cardano.Wallet.Api.Types.RestorationMode
     ( ApiRestorationMode
@@ -2470,6 +2471,10 @@ instance Arbitrary ApiErrorMissingWitnessesInTransaction where
     arbitrary = genericArbitrary
     shrink = genericShrink
 
+instance Arbitrary ApiErrorUnsupportedEra where
+    arbitrary = genericArbitrary
+    shrink = genericShrink
+
 instance Arbitrary ApiErrorSharedWalletNoSuchCosigner where
     arbitrary = genericArbitrary
     shrink = genericShrink

From 2288f3c10eba4af0901e74a389900da4bd29d95d Mon Sep 17 00:00:00 2001
From: paolino <paolo.veronelli@gmail.com>
Date: Thu, 16 May 2024 10:25:50 +0000
Subject: [PATCH 2/4] Add an error branch to `unsealShelleyTx` to report
 unacceptable eras

---
 .../src/Cardano/Wallet/Network/Implementation.hs |  2 +-
 .../Cardano/Wallet/Primitive/Ledger/Shelley.hs   | 16 ++++++++++------
 2 files changed, 11 insertions(+), 7 deletions(-)

diff --git a/lib/network-layer/src/Cardano/Wallet/Network/Implementation.hs b/lib/network-layer/src/Cardano/Wallet/Network/Implementation.hs
index 0a47e530a9e..4a500a8bcf8 100644
--- a/lib/network-layer/src/Cardano/Wallet/Network/Implementation.hs
+++ b/lib/network-layer/src/Cardano/Wallet/Network/Implementation.hs
@@ -631,7 +631,7 @@ withNodeNetworkLayerBase
             liftIO $ traceWith tr $ MsgPostTx tx
             preferredEra <- liftIO readCurrentEra
             let cmd =
-                    CmdSubmitTx . toConsensusGenTx
+                    CmdSubmitTx . toConsensusGenTx . fromRight (error "SealedTx")
                         $ unsealShelleyTx preferredEra tx
             liftIO (send txSubmissionQueue cmd) >>= \case
                 SubmitSuccess -> pure ()
diff --git a/lib/primitive/lib/Cardano/Wallet/Primitive/Ledger/Shelley.hs b/lib/primitive/lib/Cardano/Wallet/Primitive/Ledger/Shelley.hs
index 6eccc28edbf..707039f4665 100644
--- a/lib/primitive/lib/Cardano/Wallet/Primitive/Ledger/Shelley.hs
+++ b/lib/primitive/lib/Cardano/Wallet/Primitive/Ledger/Shelley.hs
@@ -92,6 +92,9 @@ module Cardano.Wallet.Primitive.Ledger.Shelley
     , interval0
     , interval1
     , numberOfTransactionsInBlock
+
+    -- * Errors
+    , UnsealedTxException (..)
     ) where
 
 import Prelude
@@ -1018,21 +1021,22 @@ rewardAccountFromAddress (W.Address bytes) = refToAccount . ref =<< parseAddr by
     refToAccount (SL.StakeRefPtr _) = Nothing
     refToAccount SL.StakeRefNull = Nothing
 
+newtype UnsealedTxException = UnsealedTxInUnsupportedEra AnyCardanoEra
+
 -- | Converts 'SealedTx' to something that can be submitted with the
 -- 'Cardano.Api' local tx submission client.
 unsealShelleyTx
     :: AnyCardanoEra
     -- ^ Preferred latest era (see 'ideallyNoLaterThan')
     -> W.SealedTx
-    -> TxInMode
+    -> Either UnsealedTxException TxInMode
 unsealShelleyTx era wtx = case W.cardanoTxIdeallyNoLaterThan era wtx of
     Cardano.InAnyCardanoEra BabbageEra tx ->
-        TxInMode ShelleyBasedEraBabbage tx
+        Right $ TxInMode ShelleyBasedEraBabbage tx
     Cardano.InAnyCardanoEra ConwayEra tx ->
-        TxInMode ShelleyBasedEraConway tx
-    _ -> error $
-        "unsealShelleyTx: Creating transactions in era " <> show era
-        <> " is not supported anymore."
+        Right $ TxInMode ShelleyBasedEraConway tx
+    Cardano.InAnyCardanoEra unsupportedEra _  ->
+        Left $ UnsealedTxInUnsupportedEra $ AnyCardanoEra unsupportedEra
 
 instance (forall era. IsCardanoEra era => Show (thing era)) =>
     Show (InAnyCardanoEra thing) where

From 3d22f7ddd6835c5977c23ac1140e65c1277c670f Mon Sep 17 00:00:00 2001
From: paolino <paolo.veronelli@gmail.com>
Date: Thu, 16 May 2024 10:27:48 +0000
Subject: [PATCH 3/4] Intercept the UnsealedTxInUnsupportedEra into the
 relative post tx error

---
 .../Cardano/Wallet/Network/Implementation.hs   | 18 +++++++++++-------
 .../Cardano/Wallet/Primitive/Ledger/Shelley.hs |  6 +++---
 2 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/lib/network-layer/src/Cardano/Wallet/Network/Implementation.hs b/lib/network-layer/src/Cardano/Wallet/Network/Implementation.hs
index 4a500a8bcf8..1f00a67cf4c 100644
--- a/lib/network-layer/src/Cardano/Wallet/Network/Implementation.hs
+++ b/lib/network-layer/src/Cardano/Wallet/Network/Implementation.hs
@@ -96,7 +96,8 @@ import Cardano.Wallet.Primitive.Ledger.Byron
     ( byronCodecConfig
     )
 import Cardano.Wallet.Primitive.Ledger.Shelley
-    ( nodeToClientVersions
+    ( UnsealException (..)
+    , nodeToClientVersions
     , toCardanoEra
     , unsealShelleyTx
     )
@@ -630,12 +631,15 @@ withNodeNetworkLayerBase
         _postTx txSubmissionQueue readCurrentEra tx = do
             liftIO $ traceWith tr $ MsgPostTx tx
             preferredEra <- liftIO readCurrentEra
-            let cmd =
-                    CmdSubmitTx . toConsensusGenTx . fromRight (error "SealedTx")
-                        $ unsealShelleyTx preferredEra tx
-            liftIO (send txSubmissionQueue cmd) >>= \case
-                SubmitSuccess -> pure ()
-                SubmitFail e -> throwE $ ErrPostTxValidationError $ T.pack $ show e
+            case unsealShelleyTx preferredEra tx of
+                Left (UnsealedTxInUnsupportedEra era) ->
+                    throwE $ ErrPostTxEraUnsupported era
+                Right tx' -> do
+                    let cmd = CmdSubmitTx . toConsensusGenTx $ tx'
+                    liftIO (send txSubmissionQueue cmd) >>= \case
+                        SubmitSuccess -> pure ()
+                        SubmitFail e ->
+                            throwE $ ErrPostTxValidationError $ T.pack $ show e
 
         _stakeDistribution queue coin = do
             liftIO $ traceWith tr $ MsgWillQueryRewardsForStake coin
diff --git a/lib/primitive/lib/Cardano/Wallet/Primitive/Ledger/Shelley.hs b/lib/primitive/lib/Cardano/Wallet/Primitive/Ledger/Shelley.hs
index 707039f4665..de50449d135 100644
--- a/lib/primitive/lib/Cardano/Wallet/Primitive/Ledger/Shelley.hs
+++ b/lib/primitive/lib/Cardano/Wallet/Primitive/Ledger/Shelley.hs
@@ -94,7 +94,7 @@ module Cardano.Wallet.Primitive.Ledger.Shelley
     , numberOfTransactionsInBlock
 
     -- * Errors
-    , UnsealedTxException (..)
+    , UnsealException (..)
     ) where
 
 import Prelude
@@ -1021,7 +1021,7 @@ rewardAccountFromAddress (W.Address bytes) = refToAccount . ref =<< parseAddr by
     refToAccount (SL.StakeRefPtr _) = Nothing
     refToAccount SL.StakeRefNull = Nothing
 
-newtype UnsealedTxException = UnsealedTxInUnsupportedEra AnyCardanoEra
+newtype UnsealException = UnsealedTxInUnsupportedEra AnyCardanoEra
 
 -- | Converts 'SealedTx' to something that can be submitted with the
 -- 'Cardano.Api' local tx submission client.
@@ -1029,7 +1029,7 @@ unsealShelleyTx
     :: AnyCardanoEra
     -- ^ Preferred latest era (see 'ideallyNoLaterThan')
     -> W.SealedTx
-    -> Either UnsealedTxException TxInMode
+    -> Either UnsealException TxInMode
 unsealShelleyTx era wtx = case W.cardanoTxIdeallyNoLaterThan era wtx of
     Cardano.InAnyCardanoEra BabbageEra tx ->
         Right $ TxInMode ShelleyBasedEraBabbage tx

From bf3471a28c38a2ab435eba63de6754fe11227b9f Mon Sep 17 00:00:00 2001
From: paolino <paolo.veronelli@gmail.com>
Date: Thu, 16 May 2024 12:21:21 +0000
Subject: [PATCH 4/4] Add openapi schema for the unsupported era HTTP error

---
 specifications/api/swagger.yaml | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/specifications/api/swagger.yaml b/specifications/api/swagger.yaml
index 08d9b58c61e..6ea600a5455 100644
--- a/specifications/api/swagger.yaml
+++ b/specifications/api/swagger.yaml
@@ -5557,6 +5557,26 @@ x-errBlockHeaderNotFound: &errBlockHeaderNotFound
       type: string
       enum: ['block_header_not_found']
 
+x-errUnsupportedEra: &errUnsupportedEra
+   <<: *responsesErr
+   title: unsupported_era
+   properties:
+    message:
+      type: string
+    code:
+      type: string
+      enum: ['unsupported_era']
+    info:
+      type: object
+      required:
+        - unsupported_era
+        - supported_eras
+      properties:
+          unsupported_era: *ApiEra
+          supported_eras:
+            type: array
+            items: *ApiEra
+
 x-responsesErr400: &responsesErr400
   400:
     description: Bad Request
@@ -6015,6 +6035,7 @@ x-responsesPostTransaction: &responsesPostTransaction
             - <<: *errTransactionIsTooBig
             - <<: *errNoRootKey
             - <<: *errWrongEncryptionPassphrase
+            - <<: *errUnsupportedEra
   <<: *responsesErr404WalletNotFound
   <<: *responsesErr406
   <<: *responsesErr415UnsupportedMediaType