Skip to content

Commit

Permalink
Merge #3412
Browse files Browse the repository at this point in the history
3412: PendingIxs refactoring and decode tx shared wallets r=paweljakubas a=paweljakubas

<!--
Detail in a few bullet points the work accomplished in this PR.

Before you submit, don't forget to:

* Make sure the GitHub PR fields are correct:
   ✓ Set a good Title for your PR.
   ✓ Assign yourself to the PR.
   ✓ Assign one or more reviewer(s).
   ✓ Link to a Jira issue, and/or other GitHub issues or PRs.
   ✓ In the PR description delete any empty sections
     and all text commented in <!--, so that this text does not appear
     in merge commit messages.

* Don't waste reviewers' time:
   ✓ If it's a draft, select the Create Draft PR option.
   ✓ Self-review your changes to make sure nothing unexpected slipped through.

* Try to make your intent clear:
   ✓ Write a good Description that explains what this PR is meant to do.
   ✓ Jira will detect and link to this PR once created, but you can also
     link this PR in the description of the corresponding Jira ticket.
   ✓ Highlight what Testing you have done.
   ✓ Acknowledge any changes required to the Documentation.
-->
- [x] I have introduced `PendingIxs` in `AddressDerivation` and reused it in both sequential and shared mode 
- [x]  I have implemented endpoint for decode tx for multisig working for all cases supported for construct tx
as in #3378
- [x]  I have added integration tests

### Comments

<!-- Additional comments, links, or screenshots to attach, if any. -->

### Issue Number
adp-2008

<!-- Reference the Jira/GitHub issue that this PR relates to, and which requirements it tackles.
  Note: Jira issues of the form ADP- will be auto-linked. -->


Co-authored-by: Pawel Jakubas <[email protected]>
  • Loading branch information
iohk-bors[bot] and paweljakubas authored Aug 1, 2022
2 parents 62eede3 + ac83bfa commit 8dba4af
Show file tree
Hide file tree
Showing 13 changed files with 333 additions and 181 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,34 @@ import Cardano.Mnemonic
import Cardano.Wallet.Api.Types
( ApiAddress
, ApiConstructTransaction (..)
, ApiDecodedTransaction (..)
, ApiFee (..)
, ApiSerialisedTransaction (..)
, ApiSharedWallet (..)
, ApiT (..)
, ApiTransaction
, ApiTxInputGeneral (..)
, ApiTxMetadata (..)
, ApiTxOutputGeneral (..)
, ApiWallet
, ApiWalletOutput (..)
, DecodeAddress
, DecodeStakeAddress
, EncodeAddress (..)
, WalletStyle (..)
)
import Cardano.Wallet.Primitive.AddressDerivation
( DerivationIndex (..) )
import Cardano.Wallet.Primitive.Passphrase
( Passphrase (..) )
import Cardano.Wallet.Primitive.Types.Tx
( TxMetadata (..), TxMetadataValue (..), TxScriptValidity (..) )
import Control.Monad.IO.Unlift
( MonadUnliftIO (..), liftIO )
import Control.Monad.Trans.Resource
( runResourceT )
import Data.Aeson
( toJSON )
import Data.Either.Combinators
( swapEither )
import Data.Generics.Internal.VL.Lens
Expand All @@ -52,7 +65,7 @@ import Numeric.Natural
import Test.Hspec
( SpecWith, describe )
import Test.Hspec.Expectations.Lifted
( shouldBe, shouldSatisfy )
( shouldBe, shouldNotContain, shouldSatisfy )
import Test.Hspec.Extra
( it )
import Test.Integration.Framework.DSL
Expand Down Expand Up @@ -91,7 +104,10 @@ import Test.Integration.Framework.TestData
)

import qualified Cardano.Wallet.Api.Link as Link
import qualified Cardano.Wallet.Primitive.Types.TokenMap as TokenMap
import qualified Data.ByteArray as BA
import qualified Data.List.NonEmpty as NE
import qualified Data.Map.Strict as Map
import qualified Data.Text.Encoding as T
import qualified Network.HTTP.Types as HTTP

Expand Down Expand Up @@ -134,7 +150,8 @@ spec = describe "SHARED_TRANSACTIONS" $ do
[ expectResponseCode HTTP.status201
]

let (ApiSharedWallet (Left wal)) = getFromResponse id rPost
let (ApiSharedWallet (Left wal)) =
getFromResponse Prelude.id rPost

let metadata = Json [json|{ "metadata": { "1": { "string": "hello" } } }|]

Expand Down Expand Up @@ -173,7 +190,8 @@ spec = describe "SHARED_TRANSACTIONS" $ do
[ expectResponseCode HTTP.status201
]

let walShared@(ApiSharedWallet (Right wal)) = getFromResponse id rPost
let walShared@(ApiSharedWallet (Right wal)) =
getFromResponse Prelude.id rPost

let metadata = Json [json|{ "metadata": { "1": { "string": "hello" } } }|]

Expand All @@ -195,6 +213,22 @@ spec = describe "SHARED_TRANSACTIONS" $ do
, expectField (#fee . #getQuantity) (`shouldSatisfy` (>0))
]

let txCbor = getFromResponse #transaction rTx2
let decodePayload = Json (toJSON $ ApiSerialisedTransaction txCbor)
rDecodedTx <- request @(ApiDecodedTransaction n) ctx
(Link.decodeTransaction @'Shared wal) Default decodePayload
let expectedFee = getFromResponse (#fee . #getQuantity) rTx2
let metadata' = ApiT (TxMetadata (Map.fromList [(1,TxMetaText "hello")]))
verify rDecodedTx
[ expectResponseCode HTTP.status202
, expectField (#fee . #getQuantity) (`shouldBe` expectedFee)
, expectField #withdrawals (`shouldBe` [])
, expectField #collateral (`shouldBe` [])
, expectField #metadata
(`shouldBe` (ApiTxMetadata (Just metadata')))
, expectField #scriptValidity (`shouldBe` (Just $ ApiT TxScriptValid))
]

it "SHARED_TRANSACTIONS_CREATE_01a - Empty payload is not allowed" $ \ctx -> runResourceT $ do
wa <- fixtureSharedWallet ctx
let emptyPayload = Json [json|{}|]
Expand Down Expand Up @@ -249,6 +283,55 @@ spec = describe "SHARED_TRANSACTIONS" $ do
, expectField (#coinSelection . #change) (`shouldSatisfy` (not . null))
, expectField (#fee . #getQuantity) (`shouldSatisfy` (> 0))
]
let txCbor = getFromResponse #transaction rTx
let decodePayload = Json (toJSON $ ApiSerialisedTransaction txCbor)
rDecodedTxSource <- request @(ApiDecodedTransaction n) ctx
(Link.decodeTransaction @'Shared wa) Default decodePayload
rDecodedTxTarget <- request @(ApiDecodedTransaction n) ctx
(Link.decodeTransaction @'Shelley wb) Default decodePayload

let expectedFee = getFromResponse (#fee . #getQuantity) rTx
let sharedExpectationsBetweenWallets =
[ expectResponseCode HTTP.status202
, expectField (#fee . #getQuantity) (`shouldBe` expectedFee)
, expectField #withdrawals (`shouldBe` [])
, expectField #collateral (`shouldBe` [])
, expectField #metadata (`shouldBe` (ApiTxMetadata Nothing))
, expectField #scriptValidity (`shouldBe` (Just $ ApiT TxScriptValid))
]

verify rDecodedTxTarget sharedExpectationsBetweenWallets

let isInpOurs inp = case inp of
ExternalInput _ -> False
WalletInput _ -> True
let areOurs = all isInpOurs
addrs <- listAddresses @n ctx wb
let addrIx = 1
let addrDest = (addrs !! addrIx) ^. #id
let expectedTxOutTarget = WalletOutput $ ApiWalletOutput
{ address = addrDest
, amount = Quantity amt
, assets = ApiT TokenMap.empty
, derivationPath = NE.fromList
[ ApiT (DerivationIndex 2147485500)
, ApiT (DerivationIndex 2147485463)
, ApiT (DerivationIndex 2147483648)
, ApiT (DerivationIndex 0)
, ApiT (DerivationIndex $ fromIntegral addrIx)
]
}
let isOutOurs out = case out of
WalletOutput _ -> False
ExternalOutput _ -> True

verify rDecodedTxSource $
sharedExpectationsBetweenWallets ++
[ expectField #inputs (`shouldSatisfy` areOurs)
, expectField #outputs (`shouldNotContain` [expectedTxOutTarget])
-- Check that the change output is there:
, expectField (#outputs) ((`shouldBe` 1) . length . filter isOutOurs)
]

it "SHARED_TRANSACTIONS_CREATE_04b - Cannot spend less than minUTxOValue" $ \ctx -> runResourceT $ do
wa <- fixtureSharedWallet ctx
Expand Down Expand Up @@ -320,7 +403,7 @@ spec = describe "SHARED_TRANSACTIONS" $ do
rAddr <- request @[ApiAddress n] ctx
(Link.listAddresses @'Shared wal) Default Empty
expectResponseCode HTTP.status200 rAddr
let sharedAddrs = getFromResponse id rAddr
let sharedAddrs = getFromResponse Prelude.id rAddr
let destination = (sharedAddrs !! 1) ^. #id

wShelley <- fixtureWallet ctx
Expand Down Expand Up @@ -379,7 +462,8 @@ spec = describe "SHARED_TRANSACTIONS" $ do
verify (fmap (swapEither . view #wallet) <$> rPost)
[ expectResponseCode HTTP.status201
]
let walShared@(ApiSharedWallet (Right wal)) = getFromResponse id rPost
let walShared@(ApiSharedWallet (Right wal)) =
getFromResponse Prelude.id rPost

fundSharedWallet ctx faucetUtxoAmt walShared

Expand Down
8 changes: 8 additions & 0 deletions lib/core/src/Cardano/Wallet/Api.hs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ module Cardano.Wallet.Api

, SharedTransactions
, ConstructSharedTransaction
, DecodeSharedTransaction

, Proxy_
, PostExternalTransaction
Expand Down Expand Up @@ -1112,6 +1113,7 @@ type ListSharedAddresses n = "shared-wallets"

type SharedTransactions n =
ConstructSharedTransaction n
:<|> DecodeSharedTransaction n

-- | https://input-output-hk.github.io/cardano-wallet/api/#operation/constructSharedTransaction
type ConstructSharedTransaction n = "shared-wallets"
Expand All @@ -1120,6 +1122,12 @@ type ConstructSharedTransaction n = "shared-wallets"
:> ReqBody '[JSON] (ApiConstructTransactionDataT n)
:> PostAccepted '[JSON] (ApiConstructTransactionT n)

-- | https://input-output-hk.github.io/cardano-wallet/api/#operation/decodeSharedTransaction
type DecodeSharedTransaction n = "shared-wallets"
:> Capture "walletId" (ApiT WalletId)
:> "transactions-decode"
:> ReqBody '[JSON] ApiSerialisedTransaction
:> PostAccepted '[JSON] (ApiDecodedTransactionT n)

{-------------------------------------------------------------------------------
Proxy_
Expand Down
2 changes: 1 addition & 1 deletion lib/core/src/Cardano/Wallet/Api/Link.hs
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@ decodeTransaction
decodeTransaction w = discriminate @style
(endpoint @(Api.DecodeTransaction Net) (wid &))
(notSupported "Byron")
(notSupported "Shared")
(endpoint @(Api.DecodeSharedTransaction Net) (wid &))
where
wid = w ^. typed @(ApiT WalletId)

Expand Down
120 changes: 97 additions & 23 deletions lib/core/src/Cardano/Wallet/Api/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ module Cardano.Wallet.Api.Server
, postPolicyKey
, postPolicyId
, constructSharedTransaction
, decodeSharedTransaction

-- * Server error responses
, IsServerError(..)
Expand Down Expand Up @@ -2708,6 +2709,72 @@ constructSharedTransaction ctx genChange _knownPools _getPoolStatus (ApiT wid) b
ti :: TimeInterpreter (ExceptT PastHorizonException IO)
ti = timeInterpreter (ctx ^. networkLayer)

decodeSharedTransaction
:: forall ctx s k n.
( ctx ~ ApiLayer s k
, IsOurs s Address
, HasNetworkLayer IO ctx
)
=> ctx
-> ApiT WalletId
-> ApiSerialisedTransaction
-> Handler (ApiDecodedTransaction n)
decodeSharedTransaction ctx (ApiT wid) (ApiSerialisedTransaction (ApiT sealed)) = do
era <- liftIO $ NW.currentNodeEra nl
let (decodedTx, _toMint, _toBurn, _allCerts, interval) =
decodeTx tl era sealed
let (Tx { txId
, fee
, resolvedInputs
, resolvedCollateralInputs
, outputs
, metadata
, scriptValidity
}) = decodedTx
(txinsOutsPaths, collateralInsOutsPaths, outsPath)
<- withWorkerCtx ctx wid liftE liftE $ \wrk -> do
inputPaths <-
liftHandler $ W.lookupTxIns @_ @s @k wrk wid $
fst <$> resolvedInputs
collateralInputPaths <-
liftHandler $ W.lookupTxIns @_ @s @k wrk wid $
fst <$> resolvedCollateralInputs
outputPaths <-
liftHandler $ W.lookupTxOuts @_ @s @k wrk wid outputs
pure
( inputPaths
, collateralInputPaths
, outputPaths
)
pure $ ApiDecodedTransaction
{ id = ApiT txId
, fee = maybe (Quantity 0) (Quantity . fromIntegral . unCoin) fee
, inputs = map toInp txinsOutsPaths
, outputs = map toOut outsPath
, collateral = map toInp collateralInsOutsPaths
-- TODO: [ADP-1670]
, collateralOutputs = ApiAsArray Nothing
, withdrawals = []
-- TODO minting/burning multisig
, mint = emptyApiAssetMntBurn
, burn = emptyApiAssetMntBurn
-- TODO delegation/withdrawals multisig
, certificates = []
, depositsTaken = []
, depositsReturned = []
, metadata = ApiTxMetadata $ ApiT <$> metadata
, scriptValidity = ApiT <$> scriptValidity
, validityInterval = interval
}
where
tl = ctx ^. W.transactionLayer @k
nl = ctx ^. W.networkLayer @IO

emptyApiAssetMntBurn = ApiAssetMintBurn
{ tokens = []
, walletPolicyKeyHash = Nothing
, walletPolicyKeyIndex = Nothing
}

-- TODO: Most of the body of this function should really belong to
-- Cardano.Wallet to keep the Api.Server module free of business logic!
Expand Down Expand Up @@ -2889,28 +2956,6 @@ decodeTransaction ctx (ApiT wid) (ApiSerialisedTransaction (ApiT sealed)) = do
, walletPolicyKeyIndex =
policyIx <$ includePolicyKeyInfo tokenWithScripts xpubM
}
toOut (txoutIncoming, Nothing) =
ExternalOutput $ toAddressAmount @n txoutIncoming
toOut ((TxOut addr (TokenBundle (Coin c) tmap)), (Just path)) =
WalletOutput $ ApiWalletOutput
{ address = (ApiT addr, Proxy @n)
, amount = Quantity $ fromIntegral c
, assets = ApiT tmap
, derivationPath = NE.map ApiT path
}
toInp (txin@(TxIn txid ix), txoutPathM) =
case txoutPathM of
Nothing ->
ExternalInput (ApiT txin)
Just (TxOut addr (TokenBundle (Coin c) tmap), path) ->
WalletInput $ ApiWalletInput
{ id = ApiT txid
, index = ix
, address = (ApiT addr, Proxy @n)
, derivationPath = NE.map ApiT path
, amount = Quantity $ fromIntegral c
, assets = ApiT tmap
}
toWrdl acct (rewardKey, (Coin c)) =
if rewardKey == acct then
ApiWithdrawalGeneral (ApiT rewardKey, Proxy @n) (Quantity $ fromIntegral c) Our
Expand All @@ -2921,7 +2966,6 @@ decodeTransaction ctx (ApiT wid) (ApiSerialisedTransaction (ApiT sealed)) = do
W.CertificateOfDelegation delCert -> toApiDelCert acct acctPath delCert
W.CertificateOfPool poolCert -> toApiPoolCert poolCert
W.CertificateOther otherCert -> toApiOtherCert otherCert

toApiOtherCert = OtherCertificate . ApiT

toApiPoolCert (W.Registration (W.PoolRegistrationCertificate poolId' poolOwners' poolMargin' poolCost' poolPledge' poolMetadata')) =
Expand Down Expand Up @@ -2965,6 +3009,36 @@ decodeTransaction ctx (ApiT wid) (ApiSerialisedTransaction (ApiT sealed)) = do
WalletDelegationCertificate (QuitPool _) -> True
_ -> False

toInp
:: forall n. (TxIn, Maybe (TxOut, NonEmpty DerivationIndex))
-> ApiTxInputGeneral n
toInp (txin@(TxIn txid ix), txoutPathM) =
case txoutPathM of
Nothing ->
ExternalInput (ApiT txin)
Just (TxOut addr (TokenBundle (Coin c) tmap), path) ->
WalletInput $ ApiWalletInput
{ id = ApiT txid
, index = ix
, address = (ApiT addr, Proxy @n)
, derivationPath = NE.map ApiT path
, amount = Quantity $ fromIntegral c
, assets = ApiT tmap
}

toOut
:: forall n. (TxOut, Maybe (NonEmpty DerivationIndex))
-> ApiTxOutputGeneral n
toOut (txoutIncoming, Nothing) =
ExternalOutput $ toAddressAmount @n txoutIncoming
toOut ((TxOut addr (TokenBundle (Coin c) tmap)), (Just path)) =
WalletOutput $ ApiWalletOutput
{ address = (ApiT addr, Proxy @n)
, amount = Quantity $ fromIntegral c
, assets = ApiT tmap
, derivationPath = NE.map ApiT path
}

submitTransaction
:: forall ctx s k (n :: NetworkDiscriminant).
( ctx ~ ApiLayer s k
Expand Down
Loading

0 comments on commit 8dba4af

Please sign in to comment.