Skip to content
This repository was archived by the owner on Mar 1, 2019. It is now read-only.

Commit a823d57

Browse files
[CBR-313] Write tests for Servant handlers
1 parent 626132e commit a823d57

File tree

7 files changed

+76
-80
lines changed

7 files changed

+76
-80
lines changed

src/Cardano/Wallet/API/V1/Handlers/Transactions.hs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
{-# LANGUAGE OverloadedStrings #-}
22
{-# LANGUAGE TypeOperators #-}
33

4-
module Cardano.Wallet.API.V1.Handlers.Transactions where
4+
module Cardano.Wallet.API.V1.Handlers.Transactions (
5+
handlers
6+
, newTransaction
7+
, getTransactionsHistory
8+
, estimateFees
9+
) where
510

611
import Universum
712

src/Cardano/Wallet/Kernel.hs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ import Cardano.Wallet.Kernel.Util (getCurrentTimestamp)
6767

6868
import Cardano.Wallet.Kernel.DB.Read as Getters
6969

70-
import Pos.Core (TxAux (..))
71-
72-
import Pos.Core (ProtocolMagic)
70+
import Pos.Core (ProtocolMagic, TxAux (..))
7371
import Pos.Core.Chrono (OldestFirst)
7472
import Pos.Crypto (EncryptedSecretKey, hash)
7573
import Pos.Txp (Utxo)

src/Cardano/Wallet/Kernel/Internal.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module Cardano.Wallet.Kernel.Internal (
1515
, walletLogMessage
1616
) where
1717

18-
import Universum hiding (State, init)
18+
import Universum hiding (State)
1919

2020
import Control.Lens.TH
2121

src/Cardano/Wallet/Kernel/Transactions.hs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -142,16 +142,8 @@ newTransaction :: ActiveWallet
142142
newTransaction ActiveWallet{..} genChangeAddr signAddress options accountId payees = do
143143
snapshot <- getWalletSnapshot walletPassive
144144
let toTxOuts = fmap (\(a,c) -> TxOutAux (TxOut a c))
145-
-- We generate a new change address outside the 'MonadRandom' monad in which
146-
-- coin selection runs, so that we keep our \"external\" model consistent
147-
-- by generating it once, before calling coin selection. Specifically, we
148-
-- pass this potential change address from the outside.
149-
-- One disadvantage of this approach is that in case of failure of the
150-
-- coin-selection we might be left with such dangling address, but in the
151-
-- future we could easily add a function to delete an address, keeping the
152-
-- possible number of orphans at bay.
153145
let mkTx = mkStdTx walletProtocolMagic signAddress
154-
-- | FIXME(adn) This number was computed out of the work Matt Noonan did
146+
-- | NOTE(adn) This number was computed out of the work Matt Noonan did
155147
-- on the size estimation. See [CBR-318].
156148
let maxInputs = 350
157149
let availableUtxo = accountAvailableUtxo snapshot accountId
@@ -170,7 +162,6 @@ newTransaction ActiveWallet{..} genChangeAddr signAddress options accountId paye
170162
-- from a fixed seed obtained from hashing the payees. This guarantees that
171163
-- when we estimate the fees and later create a transaction, the coin selection
172164
-- will always yield the same value, making the process externally-predicatable.
173-
174165
newtype PayMonad a = PayMonad {
175166
buildPayment :: ReaderT (NonEmpty (Address, Coin)) IO a
176167
} deriving (Functor, Applicative, Monad, MonadIO, MonadReader (NonEmpty (Address, Coin)))
@@ -212,7 +203,7 @@ estimateFees :: ActiveWallet
212203
-> IO (Either EstimateFeesError Coin)
213204
estimateFees activeWallet@ActiveWallet{..} genChangeAddr signAddress options accountId payees = do
214205
snapshot <- getWalletSnapshot walletPassive
215-
let originalUtxo = accountUtxo snapshot accountId
206+
let originalUtxo = accountAvailableUtxo snapshot accountId
216207
res <- newTransaction activeWallet genChangeAddr signAddress options accountId payees
217208
case res of
218209
Left e -> return . Left . EstFeesTxCreationFailed $ e
@@ -271,7 +262,7 @@ mkSigner spendingPassword (Just esk) allWallets addr =
271262
_ ->
272263
Left (CoinSelHardErrAddressNotOwned (Proxy @ Cardano) addr)
273264

274-
-- | An hopefully-accurate estimate of the Tx fees in Cardano.
265+
-- | An estimate of the Tx fees in Cardano based on a sensible number of defaults.
275266
cardanoFee :: Int -> NonEmpty Coin -> Coin
276267
cardanoFee inputs outputs = Core.mkCoin $
277268
estimateCardanoFee linearFeePolicy inputs (toList $ fmap Core.getCoin outputs)

src/Cardano/Wallet/WalletLayer/Kernel.hs

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,8 @@ import Cardano.Wallet.Kernel.CoinSelection.FromGeneric
4242
InputGrouping, newOptions)
4343
import Cardano.Wallet.Kernel.CoinSelection.Generic
4444
(CoinSelHardErr (..))
45-
import Pos.Core (decodeTextAddress)
4645

47-
import Pos.Core (Address, Coin)
46+
import Pos.Core (Address, Coin, decodeTextAddress)
4847
import qualified Pos.Core as Core
4948
import Pos.Core.Chrono (OldestFirst (..))
5049
import Pos.Crypto (safeDeterministicKeyGen)
@@ -168,13 +167,8 @@ bracketActiveWallet pm walletPassiveLayer passiveWallet walletDiffusion runActiv
168167
-- | Generates a new transaction @and submit it as pending@.
169168
, pay = \spendingPassword grouping regulation payment -> do
170169
liftIO $ limitExecutionTimeTo (60 :: Second) NewPaymentTimeLimitReached $ do
171-
let pw = Kernel.walletPassive activeWallet
172-
snapshot <- liftIO (Kernel.getWalletSnapshot pw)
173-
let keystore = Kernel.walletPassive activeWallet ^. Internal.walletKeystore
174170
(genChangeAddr, mkSigner, opts, accountId, payees) <-
175-
liftIO $ setupPayment pw
176-
(Kernel.hdWallets snapshot)
177-
keystore
171+
liftIO $ setupPayment (Kernel.walletPassive activeWallet)
178172
spendingPassword
179173
grouping
180174
regulation
@@ -192,13 +186,8 @@ bracketActiveWallet pm walletPassiveLayer passiveWallet walletDiffusion runActiv
192186
-- | Estimates the fees for a payment.
193187
, estimateFees = \spendingPassword grouping regulation payment -> do
194188
liftIO $ limitExecutionTimeTo (60 :: Second) EstimateFeesTimeLimitReached $ do
195-
let pw = Kernel.walletPassive activeWallet
196-
snapshot <- liftIO (Kernel.getWalletSnapshot pw)
197-
let keystore = Kernel.walletPassive activeWallet ^. Internal.walletKeystore
198189
(genChangeAddr, mkSigner, opts, accountId, payees) <-
199-
liftIO $ setupPayment pw
200-
(Kernel.hdWallets snapshot)
201-
keystore
190+
liftIO $ setupPayment (Kernel.walletPassive activeWallet)
202191
spendingPassword
203192
grouping
204193
regulation
@@ -215,9 +204,9 @@ bracketActiveWallet pm walletPassiveLayer passiveWallet walletDiffusion runActiv
215204
}
216205

217206

207+
-- | Internal function setup to facilitate the creation of the necessary
208+
-- context to perform either a new payment or the estimation of the fees.
218209
setupPayment :: Kernel.PassiveWallet
219-
-> HD.HdWallets
220-
-> Keystore
221210
-> PassPhrase
222211
-> InputGrouping
223212
-> ExpenseRegulation
@@ -228,8 +217,13 @@ setupPayment :: Kernel.PassiveWallet
228217
, HD.HdAccountId
229218
, NonEmpty (Address, Coin)
230219
)
231-
setupPayment pw wallets keystore spendingPassword grouping regulation payment = do
232-
let (WalletId wId) = psWalletId . pmtSource $ payment
220+
setupPayment pw spendingPassword grouping regulation payment = do
221+
snapshot <- liftIO (Kernel.getWalletSnapshot pw)
222+
223+
let keystore = pw ^. Internal.walletKeystore
224+
wallets = Kernel.hdWallets snapshot
225+
(WalletId wId) = psWalletId . pmtSource $ payment
226+
233227
hdRootId <- case Core.decodeTextAddress wId of
234228
Left e -> throwM (InvalidAddressConversionFailed e)
235229
Right a -> return (HD.HdRootId . InDb $ a)

src/Cardano/Wallet/WalletLayer/Types.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import qualified Formatting.Buildable
3535

3636
import Cardano.Wallet.API.V1.Types (Account, AccountIndex,
3737
AccountUpdate, Address, NewAccount, NewAddress, NewWallet,
38-
Wallet, WalletId, WalletUpdate)
38+
Payment, Wallet, WalletId, WalletUpdate)
3939

4040
import qualified Cardano.Wallet.Kernel.Addresses as Kernel
4141
import qualified Cardano.Wallet.Kernel.Transactions as Kernel
@@ -44,7 +44,6 @@ import Cardano.Wallet.WalletLayer.ExecutionTimeLimit
4444

4545
import Test.QuickCheck (Arbitrary (..), oneof)
4646

47-
import Cardano.Wallet.API.V1.Types (Payment)
4847
import Cardano.Wallet.Kernel.CoinSelection.FromGeneric
4948
(ExpenseRegulation, InputGrouping)
5049

test/unit/Test/Spec/NewPayment.hs

Lines changed: 52 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ import qualified Cardano.Wallet.WalletLayer as WalletLayer
5858

5959
import Util.Buildable (ShowThroughBuild (..))
6060

61+
import qualified Cardano.Wallet.API.V1.Handlers.Transactions as Handlers
62+
import Control.Monad.Except (runExceptT)
63+
import Servant.Server
64+
6165
{-# ANN module ("HLint: ignore Reduce duplication" :: Text) #-}
6266

6367
-- | Do not pollute the test runner output with logs.
@@ -138,44 +142,62 @@ withFixture initialBalance toPay cc = do
138142
walletSendTx = \_tx -> return False
139143
}
140144

145+
-- | Generate a fresh change 'Address', throwing an exception if the underlying
146+
-- operation fails.
141147
genChangeAddr :: AccountId -> PassiveWallet -> IO Address
142148
genChangeAddr accountId pw = do
143149
res <- Kernel.createAddress mempty accountId pw
144150
case res of
145151
Right addr -> pure addr
146152
Left err -> throwM err
147153

154+
-- | A fake signer which doesn't check for 'Address' ownership.
148155
fakeSigner :: PassPhrase
149156
-> Maybe EncryptedSecretKey
150157
-> Address
151158
-> Either CoinSelHardErr SafeSigner
152159
fakeSigner _ Nothing addr = Left (CoinSelHardErrAddressNotOwned (Proxy @ Cardano) addr)
153160
fakeSigner _ (Just esk) _ = Right (FakeSigner (encToSecret esk))
154161

162+
-- | A constant fee calculation.
155163
constantFee :: Int -> NonEmpty Coin -> Coin
156164
constantFee _ _ = mkCoin 10
157165

166+
-- | Helper function to facilitate payments via the Layer or Servant.
167+
withPayment :: MonadIO n
168+
=> InitialBalance
169+
-- ^ How big the wallet Utxo must be
170+
-> Pay
171+
-- ^ How big the payment must be
172+
-> (ActiveWalletLayer n -> V1.Payment -> IO ())
173+
-- ^ The action to run.
174+
-> PropertyM IO ()
175+
withPayment initialBalance toPay action = do
176+
withFixture initialBalance toPay $ \keystore activeLayer Fixture{..} -> do
177+
liftIO $ Keystore.insert (WalletIdHdRnd fixtureHdRootId) fixtureESK keystore
178+
let (AccountIdHdRnd hdAccountId) = fixtureAccountId
179+
let (HdRootId (InDb rootAddress)) = fixtureHdRootId
180+
let sourceWallet = V1.WalletId (sformat build rootAddress)
181+
let accountIndex = hdAccountId ^. hdAccountIdIx . to getHdAccountIx
182+
let destinations =
183+
fmap (\(addr, coin) -> V1.PaymentDistribution (V1.V1 addr) (V1.V1 coin)
184+
) fixturePayees
185+
let newPayment = V1.Payment {
186+
pmtSource = V1.PaymentSource sourceWallet accountIndex
187+
, pmtDestinations = destinations
188+
, pmtGroupingPolicy = Nothing
189+
, pmtSpendingPassword = Nothing
190+
}
191+
action activeLayer newPayment
192+
158193
spec :: Spec
159194
spec = describe "NewPayment" $ do
160195

161196
describe "Generating a new payment (wallet layer)" $ do
162197

163198
prop "pay works (realSigner, SenderPaysFee)" $ withMaxSuccess 50 $ do
164199
monadicIO $
165-
withFixture @IO (InitialADA 10000) (PayLovelace 10) $ \_ activeLayer Fixture{..} -> do
166-
let (AccountIdHdRnd hdAccountId) = fixtureAccountId
167-
let (HdRootId (InDb rootAddress)) = fixtureHdRootId
168-
let sourceWallet = V1.WalletId (sformat build rootAddress)
169-
let accountIndex = hdAccountId ^. hdAccountIdIx . to getHdAccountIx
170-
let destinations =
171-
fmap (\(addr, coin) -> V1.PaymentDistribution (V1.V1 addr) (V1.V1 coin)
172-
) fixturePayees
173-
let newPayment = V1.Payment {
174-
pmtSource = V1.PaymentSource sourceWallet accountIndex
175-
, pmtDestinations = destinations
176-
, pmtGroupingPolicy = Nothing
177-
, pmtSpendingPassword = Nothing
178-
}
200+
withPayment (InitialADA 10000) (PayLovelace 10) $ \activeLayer newPayment -> do
179201
res <- liftIO ((WalletLayer.pay activeLayer) mempty
180202
IgnoreGrouping
181203
SenderPaysFee
@@ -205,7 +227,6 @@ spec = describe "NewPayment" $ do
205227
prop "newTransaction works (fake signer, ReceiverPaysFee)" $ withMaxSuccess 50 $ do
206228
monadicIO $
207229
withFixture @IO (InitialADA 10000) (PayADA 1) $ \_ _ Fixture{..} -> do
208-
allWallets <- Kernel.hdWallets <$> Kernel.getWalletSnapshot fixturePw
209230
let opts = (newOptions Kernel.cardanoFee) {
210231
csoExpenseRegulation = ReceiverPaysFee
211232
, csoInputGrouping = IgnoreGrouping
@@ -220,26 +241,21 @@ spec = describe "NewPayment" $ do
220241
)
221242
liftIO ((bimap STB STB res) `shouldSatisfy` isRight)
222243

244+
describe "Generating a new payment (Servant)" $ do
245+
246+
prop "works as expected in the happy path scenario" $ withMaxSuccess 50 $
247+
monadicIO $
248+
withPayment (InitialADA 1000) (PayADA 1) $ \activeLayer newPayment -> do
249+
res <- liftIO (runExceptT . runHandler' $ Handlers.newTransaction activeLayer newPayment)
250+
liftIO ((bimap identity STB res) `shouldSatisfy` isRight)
251+
223252
describe "EstimateFees" $ do
224253

225254
describe "Estimating fees (wallet layer)" $ do
226255

227256
prop "estimating fees works (SenderPaysFee)" $ withMaxSuccess 50 $ do
228257
monadicIO $
229-
withFixture @IO (InitialADA 10000) (PayLovelace 10) $ \_ activeLayer Fixture{..} -> do
230-
let (AccountIdHdRnd hdAccountId) = fixtureAccountId
231-
let (HdRootId (InDb rootAddress)) = fixtureHdRootId
232-
let sourceWallet = V1.WalletId (sformat build rootAddress)
233-
let accountIndex = hdAccountId ^. hdAccountIdIx . to getHdAccountIx
234-
let destinations =
235-
fmap (\(addr, coin) -> V1.PaymentDistribution (V1.V1 addr) (V1.V1 coin)
236-
) fixturePayees
237-
let newPayment = V1.Payment {
238-
pmtSource = V1.PaymentSource sourceWallet accountIndex
239-
, pmtDestinations = destinations
240-
, pmtGroupingPolicy = Nothing
241-
, pmtSpendingPassword = Nothing
242-
}
258+
withPayment (InitialADA 10000) (PayLovelace 10) $ \activeLayer newPayment -> do
243259
res <- liftIO ((WalletLayer.estimateFees activeLayer) mempty
244260
IgnoreGrouping
245261
SenderPaysFee
@@ -314,17 +330,10 @@ spec = describe "NewPayment" $ do
314330
Left e -> fail (formatToString build e)
315331
Right x -> x `shouldSatisfy` (> (Coin 0))
316332

317-
{--
318-
describe "Generating a new payment (Servant)" $ do
319-
prop "works as expected in the happy path scenario" $ do
320-
monadicIO $
321-
withFixture $ \keystore layer Fixture{..} -> do
322-
liftIO $ Keystore.insert (WalletIdHdRnd fixtureHdRootId) fixtureESK keystore
323-
let (HdRootId hdRoot) = fixtureHdRootId
324-
(AccountIdHdRnd myAccountId) = fixtureAccountId
325-
wId = sformat build (view fromDb hdRoot)
326-
accIdx = myAccountId ^. hdAccountIdIx . to getHdAccountIx
327-
req = V1.NewAddress Nothing accIdx (V1.WalletId wId)
328-
res <- liftIO (runExceptT . runHandler' $ Handlers.newAddress layer req)
329-
liftIO ((bimap identity STB res) `shouldSatisfy` isRight)
330-
--}
333+
describe "Estimating fees (Servant)" $ do
334+
prop "works as expected in the happy path scenario" $ withMaxSuccess 50 $
335+
monadicIO $
336+
withPayment (InitialADA 1000) (PayADA 1) $ \activeLayer newPayment -> do
337+
res <- liftIO (runExceptT . runHandler' $ Handlers.estimateFees activeLayer newPayment)
338+
liftIO ((bimap identity STB res) `shouldSatisfy` isRight)
339+

0 commit comments

Comments
 (0)