Skip to content

Commit

Permalink
[ADP-3306] Make additional API errors machine-readable. (#4590)
Browse files Browse the repository at this point in the history
This PR makes the following API errors machine-readable:
- `ApiErrorNoSuchPool`
- `ApiErrorMissingWitnessesInTransaction`

In addition, we adjust the integration test suite to express
expectations in terms of rich error objects instead of interpolated
error message strings.

### Issue

ADP-3306
  • Loading branch information
paweljakubas authored May 15, 2024
2 parents 59176a5 + e9cd7df commit bfbb5b8
Show file tree
Hide file tree
Showing 13 changed files with 770 additions and 729 deletions.
46 changes: 32 additions & 14 deletions lib/api/src/Cardano/Wallet/Api/Http/Server/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ import Cardano.Wallet.Api.Types
import Cardano.Wallet.Api.Types.Error
( ApiErrorBalanceTxUnderestimatedFee (..)
, ApiErrorInfo (..)
, ApiErrorMissingWitnessesInTransaction (..)
, ApiErrorNoSuchPool (..)
, ApiErrorNodeNotYetInRecentEra (..)
, ApiErrorNotEnoughMoney (..)
, ApiErrorNotEnoughMoneyShortfall (..)
Expand Down Expand Up @@ -640,21 +642,33 @@ instance IsServerError ErrSubmitTransaction where
toServerError = \case
ErrSubmitTransactionForeignWallet ->
apiError err403 ForeignTransaction $ mconcat
[ "The transaction to be submitted is foreign to the current wallet "
, "and cannot be sent. Submit a transaction that has either input "
, "or withdrawal belonging to the wallet."
[ "The transaction to be submitted is foreign to the current "
, "wallet and cannot be sent. Submit a transaction that has "
, "either an input or a withdrawal belonging to the wallet."
]
ErrSubmitTransactionPartiallySignedOrNoSignedTx expectedWitsNo foundWitsNo ->
apiError err403 MissingWitnessesInTransaction $ mconcat
[ "The transaction expects ", toText expectedWitsNo
, " witness(es) to be fully-signed but ", toText foundWitsNo, " was provided."
, " Submit fully-signed transaction."
ErrSubmitTransactionPartiallySignedOrNoSignedTx
expectedWitsNo foundWitsNo ->
flip (apiError err403) message $
MissingWitnessesInTransaction
ApiErrorMissingWitnessesInTransaction
{ expectedNumberOfKeyWits = fromIntegral expectedWitsNo
, detectedNumberOfKeyWits = fromIntegral foundWitsNo
}
where
message = mconcat
[ "The transaction expects "
, toText expectedWitsNo
, " witness(es) to be fully-signed but "
, toText foundWitsNo
, " was provided."
, " Please submit a fully-signed transaction."
]
ErrSubmitTransactionMultidelegationNotSupported ->
apiError err403 CreatedMultidelegationTransaction $ mconcat
[ "It looks like the transaction to be sent contains"
, "multiple delegations, which is not supported at this moment."
, "Please use at most one delegation action in a submitted transaction: join, quit or none."
[ "It looks like the transaction to be sent contains "
, "multiple delegations, which is not supported at this moment. "
, "Please use at most one delegation action in a submitted "
, "transaction: join, quit or none."
]

instance IsServerError ErrSubmitTx where
Expand Down Expand Up @@ -719,16 +733,20 @@ instance IsServerError ErrCannotJoin where
, " joining again would incur an unnecessary fee!"
]
ErrNoSuchPool pid ->
apiError err404 NoSuchPool $ mconcat
flip (apiError err404) message $
NoSuchPool ApiErrorNoSuchPool { poolId = pid }
where
message = mconcat
[ "I couldn't find any stake pool with the given id: "
, toText pid
]
ErrAlreadyDelegatingVoting pid ->
apiError err403 PoolAlreadyJoinedSameVote $ mconcat
[ "I couldn't join a stake pool with the given id: "
, toText pid
, " and vote. I have already joined this pool, also voted the same last time;"
, " joining/voting again would incur an unnecessary fee!"
, " and vote. I have already joined this pool, also voted the "
, "same last time; "
, "joining/voting again would incur an unnecessary fee!"
]

instance IsServerError ErrCannotVote where
Expand Down
24 changes: 24 additions & 0 deletions lib/api/src/Cardano/Wallet/Api/Types/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ module Cardano.Wallet.Api.Types.Error
, ApiErrorNodeNotYetInRecentEra (..)
, ApiErrorNotEnoughMoney (..)
, ApiErrorNotEnoughMoneyShortfall (..)
, ApiErrorMissingWitnessesInTransaction (..)
, ApiErrorNoSuchPool (..)
)
where

Expand All @@ -49,6 +51,9 @@ import Cardano.Wallet.Api.Types.Amount
import Cardano.Wallet.Api.Types.WalletAssets
( ApiWalletAssets
)
import Cardano.Wallet.Primitive.Types.Pool
( PoolId
)
import Control.DeepSeq
( NFData (..)
)
Expand Down Expand Up @@ -145,11 +150,13 @@ data ApiErrorInfo
| MissingPolicyPublicKey
| MissingRewardAccount
| MissingWitnessesInTransaction
!ApiErrorMissingWitnessesInTransaction
| NetworkMisconfigured
| NetworkQueryFailed
| NetworkUnreachable
| NoRootKey
| NoSuchPool
!ApiErrorNoSuchPool
| NoSuchTransaction
| NoSuchWallet
| NoUtxosAvailable
Expand Down Expand Up @@ -273,6 +280,16 @@ data ApiErrorNodeNotYetInRecentEra = ApiErrorNodeNotYetInRecentEra
via DefaultRecord ApiErrorNodeNotYetInRecentEra
deriving anyclass NFData

data ApiErrorMissingWitnessesInTransaction =
ApiErrorMissingWitnessesInTransaction
{ expectedNumberOfKeyWits :: !Natural
, detectedNumberOfKeyWits :: !Natural
}
deriving (Data, Eq, Generic, Show, Typeable)
deriving (FromJSON, ToJSON)
via DefaultRecord ApiErrorMissingWitnessesInTransaction
deriving anyclass NFData

data ApiErrorNotEnoughMoney = ApiErrorNotEnoughMoney
{ shortfall :: !ApiErrorNotEnoughMoneyShortfall
}
Expand All @@ -288,3 +305,10 @@ data ApiErrorNotEnoughMoneyShortfall = ApiErrorNotEnoughMoneyShortfall
deriving (FromJSON, ToJSON) via
DefaultRecord ApiErrorNotEnoughMoneyShortfall
deriving anyclass NFData

data ApiErrorNoSuchPool = ApiErrorNoSuchPool
{ poolId :: !PoolId
}
deriving (Data, Eq, Generic, Show, Typeable)
deriving (FromJSON, ToJSON) via DefaultRecord ApiErrorNoSuchPool
deriving anyclass NFData
116 changes: 0 additions & 116 deletions lib/integration/framework/Test/Integration/Framework/TestData.hs
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,13 @@ module Test.Integration.Framework.TestData
-- * Error messages
, errMsg400WalletIdEncoding
, errMsg400StartTimeLaterThanEndTime
, errMsg403Fee
, errMsg403Collateral
, errMsg403NotAByronWallet
, errMsg403NotAnIcarusWallet
, errMsg403WrongPass
, errMsg403WrongMnemonic
, errMsg403AlreadyInLedger
, errMsg404NoSuchPool
, errMsg403PoolAlreadyJoined
, errMsg403NotDelegating
, errMsg403NonNullReward
, errMsg403NothingToMigrate
, errMsg404NoAsset
, errMsg404NoEndpoint
Expand All @@ -77,7 +73,6 @@ module Test.Integration.Framework.TestData
, errMsg500
, errMsg400NumberOfWords
, errMsgNotInDictionary
, errMsg400MinWithdrawalWrong
, errMsg403WithdrawalNotBeneficial
, errMsg403CouldntIdentifyAddrAsMine
, errMsg503PastHorizon
Expand All @@ -95,17 +90,6 @@ module Test.Integration.Framework.TestData
, errMsg403TemplateInvalidUnknownCosigner
, errMsg403TemplateInvalidDuplicateXPub
, errMsg403TemplateInvalidScript
, errMsg403InvalidConstructTx
, errMsg403ForeignTransaction
, errMsg403MissingWitsInTransaction
, errMsg403MultidelegationTransaction
, errMsg403MultiaccountTransaction
, errMsg403CreatedWrongPolicyScriptTemplateTx
, errMsg403CreatedWrongPolicyScriptTemplatePolicyId
, errMsg403AssetNameTooLong
, errMsg403MintOrBurnAssetQuantityOutOfBounds
, errMsg403InvalidValidityBounds
, errMsg403ValidityIntervalNotInsideScriptTimelock
) where

import Prelude
Expand Down Expand Up @@ -328,12 +312,6 @@ versionLine = "Running as " <> pack (showFullVersion version gitRevision)
--- Error messages
---

errMsg403InvalidConstructTx :: String
errMsg403InvalidConstructTx =
"It looks like I've created an empty transaction that does not have \
\any payments, withdrawals, delegations, metadata nor minting. \
\Include at least one of them."

errMsg409WalletExists :: String -> String
errMsg409WalletExists walId = "This operation would yield a wallet with the following\
\ id: " ++ walId ++ " However, I already know of a wallet with this id."
Expand All @@ -351,18 +329,6 @@ errMsg400StartTimeLaterThanEndTime startTime endTime = mconcat
, "'."
]

errMsg403Fee :: String
errMsg403Fee =
"I am unable to finalize the transaction, as there is not enough ada \
\available to pay for the fee and also pay for the minimum ada quantities \
\of all change outputs."

errMsg403Collateral :: String
errMsg403Collateral =
"I'm unable to create this transaction because the balance of pure ada \
\UTxOs in your wallet is insufficient to cover the minimum amount of \
\collateral required."

errMsg403NotAByronWallet :: String
errMsg403NotAByronWallet =
"I cannot derive new address for this wallet type.\
Expand Down Expand Up @@ -416,10 +382,6 @@ errMsg403WrongPass = "The given encryption passphrase doesn't match the one\
errMsg403WrongMnemonic :: String
errMsg403WrongMnemonic = "The given mnemonic doesn't match the one this wallet was created with"

errMsg400MinWithdrawalWrong :: String
errMsg400MinWithdrawalWrong = "The minimum withdrawal value must be at least \
\1 Lovelace."

errMsg403NothingToMigrate :: Text -> String
errMsg403NothingToMigrate _wid = mconcat
[ "I wasn't able to construct a migration plan. This could be "
Expand All @@ -441,10 +403,6 @@ errMsg403AlreadyInLedger :: Text -> String
errMsg403AlreadyInLedger tid = "The transaction with id: " ++ unpack tid ++
" cannot be forgotten as it is already in the ledger."

errMsg404NoSuchPool :: Text -> String
errMsg404NoSuchPool pid = "I couldn't find any stake pool with the given id: "
++ unpack pid

errMsg403PoolAlreadyJoined :: Text -> String
errMsg403PoolAlreadyJoined pid = "I couldn't join a stake pool with the given id: "
++ unpack pid ++ ". I have already joined this pool; joining again would "
Expand All @@ -455,10 +413,6 @@ errMsg403NotDelegating = "It seems that you're trying to retire from \
\delegation although you're not even delegating, nor won't be in an \
\immediate future."

errMsg403NonNullReward :: String
errMsg403NonNullReward = "It seems that you're trying to retire from delegation \
\although you've unspoiled rewards in your rewards account!"

errMsg404CannotFindTx :: Text -> String
errMsg404CannotFindTx tid = "I couldn't find a transaction with the given id: "
++ unpack tid
Expand Down Expand Up @@ -634,76 +588,6 @@ errMsg400ScriptNotUniformRoles :: String
errMsg400ScriptNotUniformRoles =
"All keys of a script must have the same role: either payment or delegation."

errMsg403ForeignTransaction :: String
errMsg403ForeignTransaction = mconcat
[ "The transaction to be submitted is foreign to the current wallet "
, "and cannot be sent. Submit a transaction that has either input "
, "or withdrawal belonging to the wallet."
]

errMsg403MissingWitsInTransaction :: Int -> Int -> String
errMsg403MissingWitsInTransaction expected got = mconcat
[ "The transaction expects ", show expected
, " witness(es) to be fully-signed but ", show got, " was provided."
, " Submit fully-signed transaction."
]

errMsg403MultidelegationTransaction :: String
errMsg403MultidelegationTransaction = mconcat
[ "It looks like I've created a transaction "
, "with multiple delegations, which is not supported at this moment. "
, "Please use at most one delegation action: join, quit or none."
]

errMsg403MultiaccountTransaction :: String
errMsg403MultiaccountTransaction = mconcat
[ "It looks like I've created a transaction "
, "with a delegation, which uses a stake key for the unsupported account. "
, "Please use delegation action engaging '0H' account."
]

errMsg403CreatedWrongPolicyScriptTemplateTx :: String
errMsg403CreatedWrongPolicyScriptTemplateTx = mconcat
[ "It looks like I've created a transaction with a minting/burning "
, "policy script that either does not pass validation, contains more "
, "than one cosigner, or has a cosigner that is different from cosigner#0."
]

errMsg403CreatedWrongPolicyScriptTemplatePolicyId :: String
errMsg403CreatedWrongPolicyScriptTemplatePolicyId = mconcat
[ "It looks like policy id is requested for a "
, "policy script that either does not pass validation, contains more "
, "than one cosigner, or has a cosigner that is different from cosigner#0."
]

errMsg403AssetNameTooLong :: String
errMsg403AssetNameTooLong = mconcat
[ "Attempted to create a transaction with an asset name that is "
, "too long. The maximum length is 32 bytes."
]

errMsg403MintOrBurnAssetQuantityOutOfBounds :: String
errMsg403MintOrBurnAssetQuantityOutOfBounds = mconcat
[ "Attempted to mint or burn an asset quantity that is out of "
, "bounds. The asset quantity must be greater than zero and must "
, "not exceed 9223372036854775807 (2^63 - 1)."
]

errMsg403InvalidValidityBounds :: String
errMsg403InvalidValidityBounds = unwords
[ "Attempted to create a transaction with invalid validity bounds."
, "Please make sure that the 'invalid_before' bound precedes the"
, "'invalid_hereafter' bound, and that you have not used negative"
, "time values."
]

errMsg403ValidityIntervalNotInsideScriptTimelock :: String
errMsg403ValidityIntervalNotInsideScriptTimelock = unwords
[ "Attempted to create a transaction with a validity interval"
, "that is not a subinterval of an associated script's timelock"
, "interval."
]

--------------------------------------------------------------------------------
-- Transaction metadata
--------------------------------------------------------------------------------
Expand Down
Loading

0 comments on commit bfbb5b8

Please sign in to comment.