Skip to content
This repository has been archived by the owner on Aug 18, 2020. It is now read-only.

Commit

Permalink
Merge pull request #3210 from input-output-hk/KtorZ/CO-324/per-field-…
Browse files Browse the repository at this point in the history
…endpoints

[CO-324] Accounts per-field endpoints
  • Loading branch information
KtorZ committed Aug 9, 2018
2 parents bf70d0f + 5820d55 commit 3e2a136
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 73 deletions.
1 change: 1 addition & 0 deletions wallet-new/cardano-sl-wallet-new.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ executable wal-integr-test
Error
Util
WalletSpecs
AccountSpecs
AddressSpecs
TransactionSpecs
QuickCheckSpecs
Expand Down
57 changes: 57 additions & 0 deletions wallet-new/integration/AccountSpecs.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TupleSections #-}

module AccountSpecs (accountSpecs) where

import Universum

import Cardano.Wallet.API.Indices (accessIx)
import Cardano.Wallet.Client.Http
import Control.Lens
import Pos.Core.Common (mkCoin)
import Test.Hspec
import Util

import qualified Pos.Core as Core
import qualified Prelude


accountSpecs :: WalletRef -> WalletClient IO -> Spec
accountSpecs _ wc =
describe "Accounts" $ do
it "can retrieve only an accounts balance" $ do
let zero = V1 (mkCoin 0)
(Wallet{..}, Account{..}) <- randomAccount wc
eresp <- getAccountBalance wc walId accIndex

partialAccount <- wrData <$> eresp `shouldPrism` _Right
partialAccount `shouldBe` AccountBalance zero

it "can retrieve only an account's addresses" $ do
pair@(Wallet{..}, Account{..}) <- randomAccount wc
addresses <- createAddresses wc 10 pair
let addr = Prelude.head addresses
let tests =
[ PaginationTest (Just 1) (Just 5) NoFilters NoSorts
(expectNAddresses 5)
, PaginationTest (Just 1) (Just 5) (filterByAddress addr) NoSorts
(expectExactlyAddresses [addr])
, PaginationTest (Just 2) (Just 5) (filterByAddress addr) NoSorts
(expectExactlyAddresses [])
]

forM_ tests $ \PaginationTest{..} -> do
eresp <- getAccountAddresses wc walId accIndex page perPage filters sorts
expectations . acaAddresses . wrData =<< eresp `shouldPrism` _Right
where
filterByAddress :: WalletAddress -> FilterOperations WalletAddress
filterByAddress addr =
FilterOp (FilterByIndex $ accessIx @_ @(V1 Core.Address) addr) NoFilters

expectNAddresses :: Int -> [WalletAddress] -> IO ()
expectNAddresses n addrs =
length addrs `shouldBe` n

expectExactlyAddresses :: [WalletAddress] -> [WalletAddress] -> IO ()
expectExactlyAddresses as bs =
sort as `shouldBe` sort bs
19 changes: 11 additions & 8 deletions wallet-new/integration/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ module Main where
import Universum

import Cardano.Wallet.Client.Http
import qualified Data.ByteString.Char8 as B8
import Data.Map (fromList)
import Data.Traversable (for)
import Data.X509.File (readSignedObject)
Expand All @@ -15,20 +14,23 @@ import System.Environment (withArgs)
import System.IO (hSetEncoding, stdout, utf8)
import Test.Hspec

import AccountSpecs (accountSpecs)
import AddressSpecs (addressSpecs)
import CLI
import Functions
import qualified QuickCheckSpecs as QuickCheck
import TransactionSpecs (transactionSpecs)
import Types
import Util (WalletRef, newWalletRef)
import WalletSpecs (walletSpecs)

import qualified Data.ByteString.Char8 as B8
import qualified QuickCheckSpecs as QuickCheck


-- | Here we want to run main when the (local) nodes
-- have started.
main :: IO ()
main = do

hSetEncoding stdout utf8
CLOptions {..} <- getOptions

Expand All @@ -53,9 +55,9 @@ main = do

-- some monadic fold or smth similar
_ <- runActionCheck
walletClient
walletState
actionDistribution
walletClient
walletState
actionDistribution

-- Acquire the initial state for the deterministic tests
wRef <- newWalletRef
Expand All @@ -75,7 +77,7 @@ main = do
either (fail . ("Error decoding X509 certificates: " <>)) return

actionDistribution :: ActionProbabilities
actionDistribution = do
actionDistribution =
(PostWallet, Weight 2)
:| (PostTransaction, Weight 5)
: fmap (, Weight 1) [minBound .. maxBound]
Expand All @@ -94,12 +96,13 @@ initialWalletState wc = do
_transactions = mempty
_actionsNum = 0
_successActions = mempty
pure $ WalletState {..}
return WalletState {..}
where
fromResp = (either throwM (pure . wrData) =<<)

deterministicTests :: WalletRef -> WalletClient IO -> Manager -> Spec
deterministicTests wref wc manager = do
accountSpecs wref wc
addressSpecs wref wc
walletSpecs wref wc
transactionSpecs wref wc
Expand Down
23 changes: 23 additions & 0 deletions wallet-new/integration/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ import Test.QuickCheck (arbitrary, generate)

type WalletRef = MVar Wallet

data PaginationTest a = PaginationTest
{ page :: Maybe Page
, perPage :: Maybe PerPage
, filters :: FilterOperations a
, sorts :: SortOperations a
, expectations :: [a] -> IO ()
}

randomWallet :: WalletOperation -> IO NewWallet
randomWallet walletOp =
generate $
Expand All @@ -29,6 +37,12 @@ randomCreateWallet = randomWallet CreateWallet
randomRestoreWallet :: IO NewWallet
randomRestoreWallet = randomWallet RestoreWallet

randomAccount :: WalletClient IO -> IO (Wallet, Account)
randomAccount wc = do
newWallet <- randomWallet CreateWallet
wallet@Wallet{..} <- createWalletCheck wc newWallet
(\(account, _) -> (wallet, account)) <$> firstAccountAndId wc wallet

createWalletCheck :: WalletClient IO -> NewWallet -> IO Wallet
createWalletCheck wc newWallet = do
result <- fmap wrData <$> postWallet wc newWallet
Expand All @@ -47,6 +61,15 @@ firstAccountAndId wc wallet = do

pure (toAcct, toAddr)

createAddress :: WalletClient IO -> (Wallet, Account) -> IO WalletAddress
createAddress wc (Wallet{..}, Account{..}) = do
eresp <- postAddress wc (NewAddress Nothing accIndex walId)
wrData <$> eresp `shouldPrism` _Right

createAddresses :: WalletClient IO -> Int -> (Wallet, Account) -> IO [WalletAddress]
createAddresses wc n src =
replicateM n (createAddress wc src)

newWalletRef :: IO WalletRef
newWalletRef = newEmptyMVar

Expand Down
6 changes: 5 additions & 1 deletion wallet-new/src/Cardano/Wallet/API/Indices.hs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ instance ToIndex Transaction (V1 Core.Timestamp) where
toIndex _ = fmap V1 . Core.parseTimestamp
accessIx Transaction{..} = txCreationTime

instance ToIndex WalletAddress (V1 Core.Address) where
toIndex _ = fmap V1 . either (const Nothing) Just . Core.decodeTextAddress
accessIx WalletAddress{..} = addrId

-- | A type family mapping a resource 'a' to all its indices.
type family IndicesOf a :: [*] where
IndicesOf Wallet = WalletIxs
Expand Down Expand Up @@ -118,7 +122,7 @@ type family IndexToQueryParam resource ix where
IndexToQueryParam Wallet WalletId = "id"
IndexToQueryParam Wallet (V1 Core.Timestamp) = "created_at"

IndexToQueryParam WalletAddress (V1 Core.Address) = "id"
IndexToQueryParam WalletAddress (V1 Core.Address) = "address"

IndexToQueryParam Transaction (V1 Txp.TxId) = "id"
IndexToQueryParam Transaction (V1 Core.Timestamp) = "created_at"
Expand Down
14 changes: 14 additions & 0 deletions wallet-new/src/Cardano/Wallet/API/V1/Accounts.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ module Cardano.Wallet.API.V1.Accounts where

import Servant

import Cardano.Wallet.API.Request
import Cardano.Wallet.API.Response
import Cardano.Wallet.API.Types
import Cardano.Wallet.API.V1.Parameters
import Cardano.Wallet.API.V1.Types

import qualified Pos.Core as Core


type API
= Tags '["Accounts"] :>
Expand Down Expand Up @@ -37,4 +40,15 @@ type API
:> "certificates"
:> ReqBody '[ValidJSON] Redemption
:> Post '[ValidJSON] (WalletResponse Transaction)
:<|> "wallets" :> CaptureWalletId :> "accounts"
:> CaptureAccountId :> "addresses"
:> Summary "Retrieve only account's addressees."
:> WalletRequestParams
:> FilterBy '[V1 Core.Address] WalletAddress
:> SortBy '[V1 Core.Address] WalletAddress
:> Get '[ValidJSON] (WalletResponse AccountAddresses)
:<|> "wallets" :> CaptureWalletId :> "accounts"
:> CaptureAccountId :> "amount"
:> Summary "Retrieve only account's balance."
:> Get '[ValidJSON] (WalletResponse AccountBalance)
)
24 changes: 24 additions & 0 deletions wallet-new/src/Cardano/Wallet/API/V1/LegacyHandlers/Accounts.hs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ handlers pm txpConfig submitTx =
:<|> newAccount
:<|> updateAccount
:<|> redeemAda pm txpConfig submitTx
:<|> getAccountAddresses
:<|> getAccountBalance

deleteAccount
:: (V0.MonadWalletLogic ctx m)
Expand Down Expand Up @@ -108,3 +110,25 @@ redeemAda pm txpConfig submitTx walletId accountIndex r = do
, V0.crSeed = seed
}
V0.redeemAda pm txpConfig submitTx spendingPassword cwalletRedeem

getAccountAddresses
:: (V0.MonadWalletLogic ctx m)
=> WalletId
-> AccountIndex
-> RequestParams
-> FilterOperations WalletAddress
-> SortOperations WalletAddress
-> m (WalletResponse AccountAddresses)
getAccountAddresses wId accIdx pagination filters sorts = do
resp <- respondWith pagination filters sorts (getAddresses <$> getAccount wId accIdx)
return resp { wrData = AccountAddresses . wrData $ resp }
where
getAddresses =
IxSet.fromList . accAddresses . wrData

getAccountBalance
:: (V0.MonadWalletLogic ctx m)
=> WalletId -> AccountIndex -> m (WalletResponse AccountBalance)
getAccountBalance wId accIdx = do
resp <- getAccount wId accIdx
return resp { wrData = AccountBalance . accAmount . wrData $ resp }
39 changes: 31 additions & 8 deletions wallet-new/src/Cardano/Wallet/API/V1/Swagger.hs
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,27 @@ curl -X GET \
$readAccounts
```

Partial Representations
-----------------------

The previous endpoint gives you a list of full representations. However, in some cases, it might be interesting to retrieve only a partial representation of an account (e.g. only the balance). There are two extra endpoints one could use to either fetch a given account's balance, and another to retrieve the list of addresses associated to a specific account.

[`GET /api/v1/wallets/{{walletId}}/accounts/{{accountId}}/addresses`](#tag/Accounts%2Fpaths%2F~1api~1v1~1wallets~1%7BwalletId%7D~1accounts~1%7BaccountId%7D~1addresses%2Fget)

```json
$readAccountAddresses
```

Note that this endpoint is paginated and allow basic filtering and sorting on
addresses. Similarly, you can retrieve only the account balance with:

[`GET /api/v1/wallets/{{walletId}}/accounts/{{accountId}}/amount`](#tag/Accounts%2Fpaths%2F~1api~1v1~1wallets~1%7BwalletId%7D~1accounts~1%7BaccountId%7D~1amount%2Fget)


```json
$readAccountBalance
```


Managing Addresses
------------------
Expand Down Expand Up @@ -785,14 +806,16 @@ Make sure to carefully read the section about [Pagination](#section/Pagination)
leverage the API capabilities.
|]
where
createAccount = decodeUtf8 $ encodePretty $ genExample @(WalletResponse Account)
createAddress = decodeUtf8 $ encodePretty $ genExample @(WalletResponse WalletAddress)
createWallet = decodeUtf8 $ encodePretty $ genExample @(WalletResponse Wallet)
readAccounts = decodeUtf8 $ encodePretty $ genExample @(WalletResponse [Account])
readAddresses = decodeUtf8 $ encodePretty $ genExample @(WalletResponse [Address])
readFees = decodeUtf8 $ encodePretty $ genExample @(WalletResponse EstimatedFees)
readNodeInfo = decodeUtf8 $ encodePretty $ genExample @(WalletResponse NodeInfo)
readTransactions = decodeUtf8 $ encodePretty $ genExample @(WalletResponse [Transaction])
createAccount = decodeUtf8 $ encodePretty $ genExample @(WalletResponse Account)
createAddress = decodeUtf8 $ encodePretty $ genExample @(WalletResponse WalletAddress)
createWallet = decodeUtf8 $ encodePretty $ genExample @(WalletResponse Wallet)
readAccounts = decodeUtf8 $ encodePretty $ genExample @(WalletResponse [Account])
readAccountBalance = decodeUtf8 $ encodePretty $ genExample @(WalletResponse AccountBalance)
readAccountAddresses = decodeUtf8 $ encodePretty $ genExample @(WalletResponse AccountAddresses)
readAddresses = decodeUtf8 $ encodePretty $ genExample @(WalletResponse [Address])
readFees = decodeUtf8 $ encodePretty $ genExample @(WalletResponse EstimatedFees)
readNodeInfo = decodeUtf8 $ encodePretty $ genExample @(WalletResponse NodeInfo)
readTransactions = decodeUtf8 $ encodePretty $ genExample @(WalletResponse [Transaction])


-- | Provide an alternative UI (ReDoc) for rendering Swagger documentation.
Expand Down
2 changes: 2 additions & 0 deletions wallet-new/src/Cardano/Wallet/API/V1/Swagger/Example.hs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ instance Example BackupPhrase where
instance Example Address
instance Example Metadata
instance Example AccountIndex
instance Example AccountBalance
instance Example AccountAddresses
instance Example WalletId
instance Example AssuranceLevel
instance Example SyncPercentage
Expand Down
Loading

0 comments on commit 3e2a136

Please sign in to comment.