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/network-layer/src/Cardano/Wallet/Network/Implementation.hs b/lib/network-layer/src/Cardano/Wallet/Network/Implementation.hs index 0a47e530a9e..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 - $ 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 6eccc28edbf..de50449d135 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 + , UnsealException (..) ) where import Prelude @@ -1018,21 +1021,22 @@ rewardAccountFromAddress (W.Address bytes) = refToAccount . ref =<< parseAddr by refToAccount (SL.StakeRefPtr _) = Nothing refToAccount SL.StakeRefNull = Nothing +newtype UnsealException = 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 UnsealException 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 diff --git a/lib/unit/test/unit/Cardano/Wallet/Api/TypesSpec.hs b/lib/unit/test/unit/Cardano/Wallet/Api/TypesSpec.hs index 3bd825c000b..0444e36a8e1 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 @@ -2471,6 +2472,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 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