diff --git a/lib/cli/src/Cardano/CLI.hs b/lib/cli/src/Cardano/CLI.hs index 1793c438c78..5f29fc0b151 100644 --- a/lib/cli/src/Cardano/CLI.hs +++ b/lib/cli/src/Cardano/CLI.hs @@ -958,6 +958,10 @@ data WalletClient t = WalletClient , postExternalTransaction :: PostExternalTransactionData -> ClientM ApiTxId + , deleteTransaction + :: ApiT WalletId + -> ApiTxId + -> ClientM NoContent , listPools :: ClientM [ApiStakePool] , networkInformation @@ -986,6 +990,7 @@ walletClient = :<|> _listTransactions :<|> _postTransactionFee :<|> _postExternalTransaction + :<|> _deleteTransaction = transactions _listPools = pools @@ -1003,6 +1008,7 @@ walletClient = , listTransactions = _listTransactions , postTransaction = _postTransaction , postExternalTransaction = _postExternalTransaction + , deleteTransaction = _deleteTransaction , postTransactionFee = _postTransactionFee , getWalletUtxoStatistics = _getWalletUtxoStatistics , listPools = _listPools diff --git a/lib/core/src/Cardano/Wallet.hs b/lib/core/src/Cardano/Wallet.hs index 8a4eb85a207..40089bdd47d 100644 --- a/lib/core/src/Cardano/Wallet.hs +++ b/lib/core/src/Cardano/Wallet.hs @@ -72,6 +72,7 @@ module Cardano.Wallet , updateWalletPassphrase , ErrWalletAlreadyExists (..) , ErrNoSuchWallet (..) + , ErrNoSuchTransaction (..) , ErrListUTxOStatistics (..) , ErrUpdatePassphrase (..) @@ -81,6 +82,7 @@ module Cardano.Wallet -- ** Transaction , createUnsignedTx , estimateTxFee + , forgetPendingTx , listTransactions , signTx , submitExternalTx @@ -93,6 +95,7 @@ module Cardano.Wallet , ErrCoinSelection (..) , ErrSubmitTx (..) , ErrSubmitExternalTx (..) + , ErrForgetPendingTx (..) , ErrPostTx (..) , ErrDecodeSignedTx (..) , ErrValidateSelection @@ -110,6 +113,7 @@ import Cardano.BM.Trace ( Trace, logDebug, logInfo ) import Cardano.Wallet.DB ( DBLayer + , ErrNoSuchTransaction (..) , ErrNoSuchWallet (..) , ErrWalletAlreadyExists (..) , PrimaryKey (..) @@ -167,6 +171,7 @@ import Cardano.Wallet.Primitive.Types , Coin (..) , DefineTx (..) , Direction (..) + , Hash (..) , Range (..) , SlotId (..) , SlotParameters (..) @@ -829,6 +834,17 @@ submitExternalTx ctx bytes = do nw = ctx ^. networkLayer @t tl = ctx ^. transactionLayer @t @k +-- | Forget pending transaction. +forgetPendingTx + :: forall ctx s t k. + ( HasDBLayer s t k ctx + , DefineTx t + ) + => ctx + -> WalletId + -> Hash "Tx" + -> ExceptT ErrForgetPendingTx IO () +forgetPendingTx _ctx _wid _tid = undefined -- | List all transactions and metadata from history for a given wallet. listTransactions @@ -1013,6 +1029,13 @@ data ErrSubmitExternalTx | ErrSubmitExternalTxDecode ErrDecodeSignedTx deriving (Show, Eq) +-- | Errors that can occur when trying to change a wallet's passphrase. +data ErrForgetPendingTx + = ErrForgetPendingTxNoSuchWallet ErrNoSuchWallet + | ErrForgetPendingTxNoSuchTransaction ErrNoSuchTransaction + | ErrForgetPendingTxTransactionIsNotPending (Hash "Tx") + deriving (Show, Eq) + -- | Errors that can occur when trying to change a wallet's passphrase. data ErrUpdatePassphrase = ErrUpdatePassphraseNoSuchWallet ErrNoSuchWallet diff --git a/lib/core/src/Cardano/Wallet/Api.hs b/lib/core/src/Cardano/Wallet/Api.hs index e2e92be7f84..f0cd098979d 100644 --- a/lib/core/src/Cardano/Wallet/Api.hs +++ b/lib/core/src/Cardano/Wallet/Api.hs @@ -208,6 +208,7 @@ type Transactions t = :<|> ListTransactions t :<|> PostTransactionFee t :<|> PostExternalTransaction + :<|> DeleteTransaction -- | https://input-output-hk.github.io/cardano-wallet/api/#operation/postTransaction type CreateTransaction t = "wallets" @@ -233,6 +234,13 @@ type ListTransactions t = "wallets" :> QueryParam "order" (ApiT SortOrder) :> Get '[JSON] [ApiTransaction t] +-- | https://input-output-hk.github.io/cardano-wallet/api/#operation/deleteTransaction +type DeleteTransaction = "wallets" + :> Capture "walletId" (ApiT WalletId) + :> "transactions" + :> Capture "transactionId" ApiTxId + :> DeleteNoContent '[Any] NoContent + {------------------------------------------------------------------------------- StakePools diff --git a/lib/core/src/Cardano/Wallet/Api/Server.hs b/lib/core/src/Cardano/Wallet/Api/Server.hs index fbf413756d9..9bd725e6653 100644 --- a/lib/core/src/Cardano/Wallet/Api/Server.hs +++ b/lib/core/src/Cardano/Wallet/Api/Server.hs @@ -41,9 +41,11 @@ import Cardano.Wallet , ErrCreateUnsignedTx (..) , ErrDecodeSignedTx (..) , ErrEstimateTxFee (..) + , ErrForgetPendingTx (..) , ErrListTransactions (..) , ErrListUTxOStatistics (..) , ErrMkStdTx (..) + , ErrNoSuchTransaction (..) , ErrNoSuchWallet (..) , ErrPostTx (..) , ErrSignTx (..) @@ -559,6 +561,7 @@ transactions ctx = :<|> listTransactions ctx :<|> postTransactionFee ctx :<|> postExternalTransaction ctx + :<|> deleteTransaction ctx postTransaction :: forall ctx s t k. @@ -611,6 +614,22 @@ postExternalTransaction ctx (PostExternalTransactionData load) = do tx <- liftHandler $ W.submitExternalTx @ctx @t @k ctx load return $ ApiTxId (ApiT (txId @t tx)) +deleteTransaction + :: forall ctx s t k. + ( ctx ~ ApiLayer s t k + , DefineTx t + ) + => ctx + -> ApiT WalletId + -> ApiTxId + -> Handler NoContent +deleteTransaction ctx (ApiT wid) (ApiTxId (ApiT (tid))) = do + liftHandler $ withWorkerCtx ctx wid liftE $ \wrk -> + W.forgetPendingTx wrk wid tid + return NoContent + where + liftE = throwE . ErrForgetPendingTxNoSuchWallet + listTransactions :: forall ctx s t k. ( DefineTx t @@ -1079,6 +1098,14 @@ instance LiftHandler ErrNoSuchWallet where , toText wid ] +instance LiftHandler ErrNoSuchTransaction where + handler = \case + ErrNoSuchTransaction tid -> + apiError err404 NoSuchTransaction $ mconcat + [ "I couldn't find a transaction with the given id: " + , toText tid + ] + instance LiftHandler ErrWalletAlreadyExists where handler = \case ErrWalletAlreadyExists wid -> @@ -1236,6 +1263,16 @@ instance LiftHandler ErrSubmitExternalTx where , errReasonPhrase = errReasonPhrase err400 } +instance LiftHandler ErrForgetPendingTx where + handler = \case + ErrForgetPendingTxNoSuchWallet e -> handler e + ErrForgetPendingTxNoSuchTransaction e -> handler e + ErrForgetPendingTxTransactionIsNotPending tid -> + apiError err404 TransactionNotPending $ mconcat + [ "The transaction with id : ", toText tid, + " cannot be forgotten as it is not pending anymore." + ] + instance LiftHandler ErrSubmitTx where handler = \case ErrSubmitTxNetwork e -> case e of diff --git a/lib/core/src/Cardano/Wallet/Api/Types.hs b/lib/core/src/Cardano/Wallet/Api/Types.hs index df7f1d651f1..c9928244a35 100644 --- a/lib/core/src/Cardano/Wallet/Api/Types.hs +++ b/lib/core/src/Cardano/Wallet/Api/Types.hs @@ -298,6 +298,8 @@ data ApiNetworkInformation = ApiNetworkInformation -- | Error codes returned by the API, in the form of snake_cased strings data ApiErrorCode = NoSuchWallet + | NoSuchTransaction + | TransactionNotPending | WalletAlreadyExists | NoRootKey | WrongEncryptionPassphrase @@ -734,6 +736,14 @@ instance MimeUnrender OctetStream PostExternalTransactionData where instance MimeRender OctetStream PostExternalTransactionData where mimeRender _ (PostExternalTransactionData val) = BL.fromStrict val +instance FromHttpApiData ApiTxId where + parseUrlPiece txt = case fromText txt of + Left (TextDecodingError err) -> Left $ T.pack err + Right tid -> Right $ ApiTxId $ ApiT tid + +instance ToHttpApiData ApiTxId where + toUrlPiece (ApiTxId (ApiT tid)) = toText tid + {------------------------------------------------------------------------------- Aeson Options -------------------------------------------------------------------------------} diff --git a/lib/core/src/Cardano/Wallet/DB.hs b/lib/core/src/Cardano/Wallet/DB.hs index 4dc20973a8a..22ac90ce703 100644 --- a/lib/core/src/Cardano/Wallet/DB.hs +++ b/lib/core/src/Cardano/Wallet/DB.hs @@ -20,6 +20,7 @@ module Cardano.Wallet.DB , sparseCheckpoints -- * Errors + , ErrNoSuchTransaction (..) , ErrNoSuchWallet(..) , ErrWalletAlreadyExists(..) ) where @@ -185,6 +186,11 @@ newtype ErrNoSuchWallet = ErrNoSuchWallet WalletId -- Wallet is gone or doesn't exist yet deriving (Eq, Show) +-- | Can't perform given operation because there's no transaction +newtype ErrNoSuchTransaction + = ErrNoSuchTransaction (Hash "Tx") + deriving (Eq, Show) + -- | Forbidden operation was executed on an already existing wallet newtype ErrWalletAlreadyExists = ErrWalletAlreadyExists WalletId -- Wallet already exists in db diff --git a/lib/core/src/Cardano/Wallet/DaedalusIPC.hs b/lib/core/src/Cardano/Wallet/DaedalusIPC.hs index 60e696f94bb..922430d0209 100644 --- a/lib/core/src/Cardano/Wallet/DaedalusIPC.hs +++ b/lib/core/src/Cardano/Wallet/DaedalusIPC.hs @@ -63,7 +63,7 @@ import GHC.IO.Handle.FD import System.Environment ( lookupEnv ) import System.Info - ( arch ) + ( os ) import System.IO ( Handle, hFlush, hGetLine, hSetNewlineMode, noNewlineTranslation ) import System.IO.Error @@ -214,7 +214,7 @@ readMessage :: Handle -> IO BL.ByteString readMessage = if isWindows then windowsReadMessage else posixReadMessage isWindows :: Bool -isWindows = arch == "windows" +isWindows = os == "windows" windowsReadMessage :: Handle -> IO BL.ByteString windowsReadMessage handle = do diff --git a/lib/jormungandr/src/Cardano/Wallet/Jormungandr.hs b/lib/jormungandr/src/Cardano/Wallet/Jormungandr.hs index 6038a20252a..801100ad572 100644 --- a/lib/jormungandr/src/Cardano/Wallet/Jormungandr.hs +++ b/lib/jormungandr/src/Cardano/Wallet/Jormungandr.hs @@ -97,6 +97,7 @@ import System.Exit import qualified Cardano.BM.Configuration.Model as CM import qualified Cardano.Wallet.Api.Server as Server import qualified Cardano.Wallet.DB.Sqlite as Sqlite +import qualified Cardano.Wallet.Jormungandr.Binary as J import qualified Data.Text as T import qualified Network.Wai.Handler.Warp as Warp @@ -128,21 +129,21 @@ serveWallet (cfg, sb, tr) databaseDir listen lj beforeMainLoop = do let nPort = Port $ baseUrlPort $ _restApi cp waitForService "Jörmungandr" (sb, tr) nPort $ waitForNetwork nl defaultRetryPolicy - rndApi <- apiLayer tr nl - seqApi <- apiLayer tr nl - startServer tr nPort nl rndApi seqApi + let (_, bp) = staticBlockchainParameters nl + rndApi <- apiLayer tr (toWLBlock <$> nl) + seqApi <- apiLayer tr (toWLBlock <$> nl) + startServer tr nPort bp rndApi seqApi pure ExitSuccess Left e -> handleNetworkStartupError e where startServer :: Trace IO Text -> Port "node" - -> NetworkLayer IO t (Block Tx) + -> BlockchainParameters -> ApiLayer (RndState t) t RndKey -> ApiLayer (SeqState t) t SeqKey -> IO () - startServer tracer nPort nl rndWallet seqWallet = do - let (_, bp) = staticBlockchainParameters nl + startServer tracer nPort bp rndWallet seqWallet = do Server.withListeningSocket listen $ \(wPort, socket) -> do let tracerIPC = appendName "daedalus-ipc" tracer let tracerApi = appendName "api" tracer @@ -153,6 +154,8 @@ serveWallet (cfg, sb, tr) databaseDir listen lj beforeMainLoop = do Server.start settings tracerApi socket rndWallet seqWallet race_ ipcServer apiServer + toWLBlock = J.convertBlock + apiLayer :: forall s k . ( KeyToAddress (Jormungandr 'Testnet) k diff --git a/lib/jormungandr/src/Cardano/Wallet/Jormungandr/Network.hs b/lib/jormungandr/src/Cardano/Wallet/Jormungandr/Network.hs index 364c989a7be..8ed2d43102d 100644 --- a/lib/jormungandr/src/Cardano/Wallet/Jormungandr/Network.hs +++ b/lib/jormungandr/src/Cardano/Wallet/Jormungandr/Network.hs @@ -29,6 +29,7 @@ module Cardano.Wallet.Jormungandr.Network JormungandrBackend (..) , JormungandrConnParams (..) , withNetworkLayer + , newNetworkLayer -- * Launching the node backend , JormungandrConfig (..) @@ -86,7 +87,7 @@ import Cardano.Wallet.Jormungandr.Api.Client , postMessage ) import Cardano.Wallet.Jormungandr.Binary - ( convertBlock, runGetOrFail ) + ( runGetOrFail ) import Cardano.Wallet.Jormungandr.BlockHeaders ( BlockHeaders (..) , appendBlockHeaders @@ -100,8 +101,6 @@ import Cardano.Wallet.Jormungandr.BlockHeaders ) import Cardano.Wallet.Jormungandr.Compatibility ( Jormungandr, genConfigFile, localhostBaseUrl ) -import Cardano.Wallet.Jormungandr.Primitive.Types - ( Tx ) import Cardano.Wallet.Network ( Cursor, NetworkLayer (..), NextBlocksResult (..), defaultRetryPolicy ) import Cardano.Wallet.Network.Ports @@ -109,7 +108,7 @@ import Cardano.Wallet.Network.Ports import Cardano.Wallet.Primitive.Model ( BlockchainParameters (..) ) import Cardano.Wallet.Primitive.Types - ( Block (..), BlockHeader (..), Hash (..), SlotId (..) ) + ( BlockHeader (..), Hash (..), SlotId (..) ) import Control.Concurrent.MVar.Lifted ( MVar, modifyMVar, newMVar, readMVar ) import Control.Exception @@ -179,7 +178,7 @@ withNetworkLayer -- ^ Logging -> JormungandrBackend -- ^ How Jörmungandr is started. - -> (Either ErrStartup (JormungandrConnParams, NetworkLayer IO t (Block Tx)) -> IO a) + -> (Either ErrStartup (JormungandrConnParams, NetworkLayer IO t J.Block) -> IO a) -- ^ The action to run. It will be passed the connection parameters used, -- and a network layer if startup was successful. -> IO a @@ -192,7 +191,7 @@ withNetworkLayerLaunch -- ^ Logging of node startup. -> JormungandrConfig -- ^ Configuration for starting Jörmungandr. - -> (Either ErrStartup (JormungandrConnParams, NetworkLayer IO t (Block Tx)) -> IO a) + -> (Either ErrStartup (JormungandrConnParams, NetworkLayer IO t J.Block) -> IO a) -- ^ The action to run. It will be passed the connection parameters used, -- and a network layer if startup was successful. -> IO a @@ -204,7 +203,7 @@ withNetworkLayerConn :: forall n a t. (t ~ Jormungandr n) => JormungandrConnParams -- ^ Parameters for connecting to Jörmungandr node which is already running. - -> (Either ErrStartup (JormungandrConnParams, NetworkLayer IO t (Block Tx)) -> IO a) + -> (Either ErrStartup (JormungandrConnParams, NetworkLayer IO t J.Block) -> IO a) -- ^ Action to run with the network layer. -> IO a withNetworkLayerConn cp@(JormungandrConnParams block0H baseUrl) action = @@ -219,13 +218,13 @@ newNetworkLayer :: forall n t. (t ~ Jormungandr n) => BaseUrl -> Hash "Genesis" - -> ExceptT ErrGetBlockchainParams IO (NetworkLayer IO t (Block Tx)) + -> ExceptT ErrGetBlockchainParams IO (NetworkLayer IO t J.Block) newNetworkLayer baseUrl block0H = do mgr <- liftIO $ newManager defaultManagerSettings st <- newMVar emptyBlockHeaders let jor = mkJormungandrClient mgr baseUrl g0 <- getInitialBlockchainParameters jor (coerce block0H) - return (convertBlock <$> mkRawNetworkLayer g0 st jor) + return (mkRawNetworkLayer g0 st jor) -- | Wrap a Jormungandr client into a 'NetworkLayer' common interface. -- diff --git a/specifications/api/swagger.yaml b/specifications/api/swagger.yaml index d32ca71b9eb..0ec21effd15 100644 --- a/specifications/api/swagger.yaml +++ b/specifications/api/swagger.yaml @@ -674,6 +674,15 @@ x-parametersWalletId: ¶metersWalletId maxLength: 40 minLength: 40 +x-parametersTransactionId: ¶metersTransactionId + in: path + name: transactionId + required: true + type: string + format: hex + maxLength: 64 + minLength: 64 + x-parametersStakePoolId: ¶metersStakePoolId in: path name: stakePoolId @@ -866,6 +875,16 @@ x-responsesPutWalletPassphrase: &responsesPutWalletPassphrase 204: description: No Content +x-responsesDeleteTransaction: &responsesDeleteTransaction + <<: *responsesErr400 + <<: *responsesErr403 + <<: *responsesErr404 + <<: *responsesErr405 + <<: *responsesErr406 + <<: *responsesErr415 + 204: + description: No Content + x-responsesListTransactions: &responsesListTransactions <<: *responsesErr404 <<: *responsesErr405 @@ -1155,6 +1174,20 @@ paths: schema: *ApiPostTransactionFeeData responses: *responsesPostTransactionFee + /wallets/{walletId}/transactions/{transactionId}: + delete: + operationId: deleteTransaction + tags: ["Transactions"] + summary: Delete + description: | +

status: under development

+ + Forget pending transaction. + parameters: + - *parametersWalletId + - *parametersTransactionId + responses: *responsesDeleteTransaction + /wallets/{walletId}/addresses: get: operationId: listAddresses