Skip to content

Commit

Permalink
[ADP-3306] Make more HTTP API errors machine-readable. (#4622)
Browse files Browse the repository at this point in the history
- [x] Convert to machine-readable error: `AssetNotPresent`
- [x] Convert to machine-readable error:
`OutputTokenBundleSizeExceedsLimit`
- [x] Convert to machine-readable error:
`OutputTokenQuantityExceedsLimit`
- [x] Convert to machine-readable error: `TransactionAlreadyInLedger`
- [x] Convert to machine-readable error: `WrongEncryptionPassphrase`

### Issue Number
ADP-3306
  • Loading branch information
jonathanknowles authored Jun 13, 2024
2 parents 49f09df + 0d3146c commit b5b9f00
Show file tree
Hide file tree
Showing 19 changed files with 493 additions and 209 deletions.
102 changes: 60 additions & 42 deletions lib/api/src/Cardano/Wallet/Api/Http/Server/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -116,23 +116,32 @@ import Cardano.Wallet.Api.Types.Error
, ApiErrorNodeNotYetInRecentEra (..)
, ApiErrorNotEnoughMoney (..)
, ApiErrorNotEnoughMoneyShortfall (..)
, ApiErrorOutputTokenBundleSizeExceedsLimit (..)
, ApiErrorOutputTokenQuantityExceedsLimit (..)
, ApiErrorSharedWalletNoSuchCosigner (..)
, ApiErrorStartTimeLaterThanEndTime (..)
, ApiErrorTransactionAlreadyInLedger (..)
, ApiErrorTxOutputLovelaceInsufficient (..)
, ApiErrorUnsupportedEra (..)
, ApiErrorWrongEncryptionPassphrase (..)
)
import Cardano.Wallet.Primitive.Ledger.Convert
( Convert (toWallet)
, toWalletAddress
, toWalletAssetName
, toWalletCoin
, toWalletTokenBundle
, toWalletTokenPolicyId
)
import Cardano.Wallet.Primitive.Slotting
( PastHorizonException
)
import Cardano.Wallet.Primitive.Types.TokenBundle
( TokenBundle (TokenBundle)
)
import Cardano.Wallet.Primitive.Types.TokenQuantity
( TokenQuantity (..)
)
import Cardano.Wallet.Transaction
( ErrSignTx (..)
)
Expand Down Expand Up @@ -305,7 +314,11 @@ instance IsServerError ErrWithRootKey where
, "fully own it."
]
ErrWithRootKeyWrongPassphrase wid ErrWrongPassphrase ->
apiError err403 WrongEncryptionPassphrase $ mconcat
flip (apiError err403) message $
WrongEncryptionPassphrase
ApiErrorWrongEncryptionPassphrase { walletId = ApiT wid }
where
message = mconcat
[ "The given encryption passphrase doesn't match the one I use "
, "to encrypt the root private key of the given wallet: "
, toText wid
Expand Down Expand Up @@ -356,23 +369,22 @@ instance IsServerError ErrMkTransaction where
ErrMkTransactionTxBodyError hint ->
apiError err500 CreatedInvalidTransaction hint
ErrMkTransactionOutputTokenQuantityExceedsLimit e ->
apiError err403 OutputTokenQuantityExceedsLimit $ mconcat
[ "One of the token quantities you've specified is greater "
, "than the maximum quantity allowed in a single transaction "
, "output. "
, "Try splitting this quantity across two or more outputs. "
, "Destination address: "
, pretty (view #address e)
, ". Token policy identifier: "
, pretty (view (#asset . #policyId) e)
, ". Asset name: "
, pretty (view (#asset . #assetName) e)
, ". Token quantity specified: "
, pretty (view #quantity e)
, ". Maximum allowable token quantity: "
, pretty (view #quantityMaxBound e)
, "."
]
flip (apiError err403) errorMessage $
OutputTokenQuantityExceedsLimit
ApiErrorOutputTokenQuantityExceedsLimit
{ address = toText $ view #address e
, policyId = ApiT $ view (#asset . #policyId) e
, assetName = ApiT $ view (#asset . #assetName) e
, quantity = view #quantity e
, maxQuantity = view #quantityMaxBound e
}
where
errorMessage = T.unwords
[ "One of the token quantities you've specified is greater "
, "than the maximum quantity allowed in a single transaction "
, "output. "
, "Try splitting this quantity across two or more outputs."
]
ErrMkTransactionInvalidEra _era ->
apiError err500 CreatedInvalidTransaction $ mconcat
[ "Whoops, it seems like I just experienced a hard-fork in the "
Expand Down Expand Up @@ -632,7 +644,11 @@ instance IsServerError ErrRemoveTx where
, toText tid
]
ErrRemoveTxAlreadyInLedger tid ->
apiError err403 TransactionAlreadyInLedger $ mconcat
flip (apiError err403) message $
TransactionAlreadyInLedger
ApiErrorTransactionAlreadyInLedger { transactionId = ApiT tid }
where
message = mconcat
[ "The transaction with id: ", toText tid,
" cannot be forgotten as it is already in the ledger."
]
Expand Down Expand Up @@ -1021,37 +1037,39 @@ instance IsServerError ErrBalanceTxOutputError where
toWalletCoin minimumExpectedCoin
}
ErrBalanceTxOutputSizeExceedsLimit {output} ->
apiError err403 OutputTokenBundleSizeExceedsLimit $ mconcat
flip (apiError err403) errorMessage $
OutputTokenBundleSizeExceedsLimit
ApiErrorOutputTokenBundleSizeExceedsLimit
{ address = toText address
, bundleSize = fromIntegral assetCount
}
where
address = toWalletAddress (fst output)
assetCount = TokenMap.size $
toWalletTokenBundle (snd output) ^. #tokens
errorMessage = T.unwords
[ "One of the outputs you've specified contains too many "
, "assets. Try splitting these assets across two or more "
, "outputs. Destination address: "
, pretty address
, ". Asset count: "
, pretty assetCount
, "."
, "outputs."
]
where
address = toWalletAddress (fst output)
assetCount = TokenMap.size $
toWalletTokenBundle (snd output) ^. #tokens
ErrBalanceTxOutputTokenQuantityExceedsLimit
{address, policyId, assetName, quantity, quantityMaxBound} ->
apiError err403 OutputTokenQuantityExceedsLimit $ mconcat
flip (apiError err403) errorMessage $
OutputTokenQuantityExceedsLimit
ApiErrorOutputTokenQuantityExceedsLimit
{ address = toText address'
, policyId = ApiT $ toWalletTokenPolicyId policyId
, assetName = ApiT $ toWalletAssetName assetName
, quantity = TokenQuantity quantity
, maxQuantity = TokenQuantity quantityMaxBound
}
where
address' = toWalletAddress address
errorMessage = T.unwords
[ "One of the token quantities you've specified is greater "
, "than the maximum quantity allowed in a single transaction "
, "output. Try splitting this quantity across two or more "
, "outputs. "
, "Destination address: "
, pretty (toWalletAddress address)
, ". Token policy identifier: "
, pretty (show policyId)
, ". Asset name: "
, pretty (show assetName)
, ". Token quantity specified: "
, pretty (show quantity)
, ". Maximum allowable token quantity: "
, pretty (show quantityMaxBound)
, "."
, "outputs."
]
where
selectionOutputCoinInsufficientMessage = T.unwords
Expand Down
66 changes: 65 additions & 1 deletion lib/api/src/Cardano/Wallet/Api/Types/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ module Cardano.Wallet.Api.Types.Error
, ApiErrorNoSuchPool (..)
, ApiErrorNoSuchTransaction (..)
, ApiErrorNoSuchWallet (..)
, ApiErrorOutputTokenBundleSizeExceedsLimit (..)
, ApiErrorOutputTokenQuantityExceedsLimit (..)
, ApiErrorStartTimeLaterThanEndTime (..)
, ApiErrorTransactionAlreadyInLedger (..)
, ApiErrorUnsupportedEra (..)
, ApiErrorWrongEncryptionPassphrase (..)
)
where

Expand All @@ -61,12 +65,21 @@ import Cardano.Wallet.Api.Types.WalletAssets
import Cardano.Wallet.Primitive.Types
( WalletId
)
import Cardano.Wallet.Primitive.Types.AssetName
( AssetName
)
import Cardano.Wallet.Primitive.Types.Hash
( Hash
)
import Cardano.Wallet.Primitive.Types.Pool
( PoolId
)
import Cardano.Wallet.Primitive.Types.TokenPolicyId
( TokenPolicyId
)
import Cardano.Wallet.Primitive.Types.TokenQuantity
( TokenQuantity
)
import Control.DeepSeq
( NFData (..)
)
Expand Down Expand Up @@ -194,7 +207,9 @@ data ApiErrorInfo
| NotSynced
| NothingToMigrate
| OutputTokenBundleSizeExceedsLimit
!ApiErrorOutputTokenBundleSizeExceedsLimit
| OutputTokenQuantityExceedsLimit
!ApiErrorOutputTokenQuantityExceedsLimit
| PastHorizon
| PoolAlreadyJoined
| PoolAlreadyJoinedSameVote
Expand All @@ -218,6 +233,7 @@ data ApiErrorInfo
| TokensMintedButNotSpentOrBurned
| TransactionAlreadyBalanced
| TransactionAlreadyInLedger
!ApiErrorTransactionAlreadyInLedger
| TransactionIsTooBig
| TranslationError
| TxNotInNodeEra
Expand All @@ -235,6 +251,7 @@ data ApiErrorInfo
| WalletNotResponding
| WithdrawalNotBeneficial
| WrongEncryptionPassphrase
!ApiErrorWrongEncryptionPassphrase
| WithdrawalNotPossibleWithoutVote
| WrongMnemonic
| BlockHeaderNotFound
Expand Down Expand Up @@ -293,6 +310,36 @@ data ApiErrorTxOutputLovelaceInsufficient = ApiErrorTxOutputLovelaceInsufficient
via DefaultRecord ApiErrorTxOutputLovelaceInsufficient
deriving anyclass NFData

data ApiErrorOutputTokenQuantityExceedsLimit =
ApiErrorOutputTokenQuantityExceedsLimit
{ address
:: !Text
, policyId
:: !(ApiT TokenPolicyId)
, assetName
:: !(ApiT AssetName)
, quantity
:: !TokenQuantity
, maxQuantity
:: !TokenQuantity
}
deriving (Data, Eq, Generic, Show, Typeable)
deriving (FromJSON, ToJSON)
via DefaultRecord ApiErrorOutputTokenQuantityExceedsLimit
deriving anyclass NFData

data ApiErrorOutputTokenBundleSizeExceedsLimit =
ApiErrorOutputTokenBundleSizeExceedsLimit
{ address
:: !Text
, bundleSize
:: !Natural
}
deriving (Data, Eq, Generic, Show, Typeable)
deriving (FromJSON, ToJSON)
via DefaultRecord ApiErrorOutputTokenBundleSizeExceedsLimit
deriving anyclass NFData

data ApiErrorBalanceTxUnderestimatedFee = ApiErrorBalanceTxUnderestimatedFee
{ underestimation :: !ApiAmount
, estimatedNumberOfKeyWits :: Natural
Expand Down Expand Up @@ -361,10 +408,27 @@ data ApiErrorNoSuchTransaction = ApiErrorNoSuchTransaction
deriving (FromJSON, ToJSON) via DefaultRecord ApiErrorNoSuchTransaction
deriving anyclass NFData

data ApiErrorTransactionAlreadyInLedger = ApiErrorTransactionAlreadyInLedger
{ transactionId :: !(ApiT (Hash "Tx"))
}
deriving (Data, Eq, Generic, Show, Typeable)
deriving (FromJSON, ToJSON)
via DefaultRecord ApiErrorTransactionAlreadyInLedger
deriving anyclass NFData

data ApiErrorStartTimeLaterThanEndTime = ApiErrorStartTimeLaterThanEndTime
{ startTime :: UTCTime
, endTime :: UTCTime
}
deriving (Data, Eq, Generic, Show, Typeable)
deriving (FromJSON, ToJSON) via DefaultRecord ApiErrorStartTimeLaterThanEndTime
deriving (FromJSON, ToJSON)
via DefaultRecord ApiErrorStartTimeLaterThanEndTime
deriving anyclass NFData

data ApiErrorWrongEncryptionPassphrase = ApiErrorWrongEncryptionPassphrase
{ walletId :: !(ApiT WalletId)
}
deriving (Data, Eq, Generic, Show, Typeable)
deriving (FromJSON, ToJSON)
via DefaultRecord ApiErrorWrongEncryptionPassphrase
deriving anyclass NFData
64 changes: 0 additions & 64 deletions lib/integration/framework/Test/Integration/Framework/TestData.hs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ module Test.Integration.Framework.TestData
, errMsg403PoolAlreadyJoined
, errMsg403NotDelegating
, errMsg403NothingToMigrate
, errMsg404NoAsset
, errMsg404NoEndpoint
, errMsg404CannotFindTx
, errMsg403NoRootKey
Expand All @@ -75,8 +74,6 @@ module Test.Integration.Framework.TestData
, errMsg403WithdrawalNotBeneficial
, errMsg403CouldntIdentifyAddrAsMine
, errMsg503PastHorizon
, errMsg403OutputTokenBundleSizeExceedsLimit
, errMsg403OutputTokenQuantityExceedsLimit
, errMsg403KeyAlreadyPresent
, errMsg403CreateIllegal
, errMsg400ScriptWrongCoeffcient
Expand All @@ -102,18 +99,6 @@ import Cardano.Wallet.Api.Types
( ApiAssetMetadata (ApiAssetMetadata)
, ApiT (..)
)
import Cardano.Wallet.Primitive.Types.Address
( Address
)
import Cardano.Wallet.Primitive.Types.AssetName
( AssetName
)
import Cardano.Wallet.Primitive.Types.TokenPolicyId
( TokenPolicyId
)
import Cardano.Wallet.Primitive.Types.TokenQuantity
( TokenQuantity
)
import Cardano.Wallet.Primitive.Types.Tx
( TxMetadata (..)
, TxMetadataValue (..)
Expand All @@ -134,9 +119,6 @@ import Data.Text
import Data.Word
( Word32
)
import Fmt
( pretty
)
import Test.Integration.Framework.DSL
( Payload (..)
, fixturePassphrase
Expand Down Expand Up @@ -380,9 +362,6 @@ errMsg403NothingToMigrate _wid = mconcat
, "your wallet before trying again."
]

errMsg404NoAsset :: String
errMsg404NoAsset = "The requested asset is not associated with this wallet."

errMsg404NoEndpoint :: String
errMsg404NoEndpoint = "I couldn't find the requested endpoint. If the endpoint\
\ contains path parameters, please ensure they are well-formed, otherwise I\
Expand Down Expand Up @@ -467,49 +446,6 @@ errMsg403CouldntIdentifyAddrAsMine = "I \
errMsg503PastHorizon :: String
errMsg503PastHorizon = "Tried to convert something that is past the horizon"

errMsg403OutputTokenBundleSizeExceedsLimit
:: Address
-> Int
-- ^ Asset count
-> String
errMsg403OutputTokenBundleSizeExceedsLimit
address assetCount = mconcat
[ "One of the outputs you've specified contains too many assets. "
, "Try splitting these assets across two or more outputs. "
, "Destination address: "
, pretty address
, ". Asset count: "
, pretty assetCount
, "."
]

errMsg403OutputTokenQuantityExceedsLimit
:: Address
-> TokenPolicyId
-> AssetName
-> TokenQuantity
-- ^ Specified token quantity
-> TokenQuantity
-- ^ Maximum allowable token quantity
-> String
errMsg403OutputTokenQuantityExceedsLimit
address policy asset quantity quantityMaxBound = mconcat
[ "One of the token quantities you've specified is greater than the "
, "maximum quantity allowed in a single transaction output. Try "
, "splitting this quantity across two or more outputs. "
, "Destination address: "
, pretty address
, ". Token policy identifier: "
, pretty policy
, ". Asset name: "
, pretty asset
, ". Token quantity specified: "
, pretty quantity
, ". Maximum allowable token quantity: "
, pretty quantityMaxBound
, "."
]

errMsg403KeyAlreadyPresent :: Text -> String
errMsg403KeyAlreadyPresent cred = mconcat
[ "It looks like you've tried to add a cosigner key to a shared wallet's "
Expand Down
Loading

0 comments on commit b5b9f00

Please sign in to comment.