Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new address discovery schemes for testing and benchmarking #160

Merged
merged 4 commits into from
Apr 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 7 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ It can be used as a component of a frontend such as
interface for wallets. Most users who would like to use Cardano should
start with Daedalus.

## Development
## Overview

This source code repository contains the next major version of Cardano
Wallet, which has been completely rewritten for the
Expand All @@ -46,57 +46,15 @@ $ stack build --test --no-run-tests

## How to test

#### unit

```
$ stack test cardano-wallet:unit
```

#### integration

##### pre-requisites

1. Install our fork of [cardano-http-bridge](https://github.com/KtorZ/cardano-http-bridge)

```
$ cargo install --branch cardano-wallet-integration --git https://github.com/KtorZ/cardano-http-bridge.git
```

2. Install [cardano-sl@cardano-node-simple](https://github.com/input-output-hk/cardano-sl)

```
$ git clone [email protected]:input-output-hk/cardano-sl.git
$ cd cardano-sl
$ stack install cardano-sl-node:exe:cardano-node-simple
```

Alternatively, if you're running on linux, you may use a pre-compiled version:

```
$ curl -L -o cardano-node-simple-3.0.1.tar.gz https://raw.githubusercontent.com/input-output-hk/cardano-wallet/master/test/data/cardano-node-simple/cardano-node-simple-3.0.1.tar.gz
$ tar xzf cardano-node-simple-3.0.1.tar.gz -C /usr/local/bin && rm cardano-node-simple-3.0.1.tar.gz
```

3. Import the initial testnet chain bootstrap for the `cardano-http-bridge`

```
$ curl -L -o hermes-testnet.tar.gz https://raw.githubusercontent.com/input-output-hk/cardano-wallet/master/test/data/cardano-http-bridge/hermes-testnet.tar.gz
$ tar xzf hermes-testnet.tar.gz -C $HOME && rm hermes-testnet.tar.gz
```

##### test

```
$ stack test cardano-wallet:integration
```

See [Wiki - Testing](https://github.com/input-output-hk/cardano-wallet/wiki/Testing)

## Documentation

* Users of the Cardano Wallet API can refer to the [API Documentation](https://input-output-hk.github.io/cardano-wallet/api/).
* Development-related information can be found in the [Wiki](https://github.com/input-output-hk/cardano-wallet/wiki).
* To help understand the source code, refer to the [Haddock Documentation](https://input-output-hk.github.io/cardano-wallet/haddock/).

| Link | Audience |
| --- | --- |
| [API Documentation](https://input-output-hk.github.io/cardano-wallet/api/) | Users of the Cardano Wallet API |
| [Haddock Documentation](https://input-output-hk.github.io/cardano-wallet/haddock/) | Haskell Developers using the `cardano-wallet` as a library |
| [Wiki](https://github.com/input-output-hk/cardano-wallet/wiki) | Anyone interested in the project and our development process |

<hr/>

Expand Down
5 changes: 5 additions & 0 deletions cardano-wallet.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,13 @@ benchmark restore
base
, ansi-terminal
, async
, bytestring
, cardano-wallet
, containers
, criterion-measurement
, cryptonite
, deepseq
, digest
, docopt
, fmt
, generic-lens
Expand All @@ -337,6 +341,7 @@ benchmark restore
other-modules:
Cardano.CLI
Cardano.Launcher
Cardano.Wallet.Primitive.AddressDiscovery.Any
if os(windows)
build-depends: Win32
other-modules: Cardano.Launcher.Windows
Expand Down
89 changes: 35 additions & 54 deletions src/Cardano/Wallet.hs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- |
-- Copyright: © 2018-2019 IOHK
Expand All @@ -20,7 +20,6 @@ module Cardano.Wallet
(
-- * Interface
WalletLayer (..)
, NewWallet(..)

-- * Errors
, ErrNoSuchWallet(..)
Expand Down Expand Up @@ -61,19 +60,10 @@ import Cardano.Wallet.Primitive.AddressDerivation
, Passphrase
, XPrv
, checkPassphrase
, deriveAccountPrivateKey
, digest
, encryptPassphrase
, generateKeyFromSeed
, publicKey
)
import Cardano.Wallet.Primitive.AddressDiscovery
( AddressPoolGap
, SeqState (..)
, emptyPendingIxs
, mkAddressPool
, nextChangeAddress
)
( AddressScheme (..) )
import Cardano.Wallet.Primitive.Model
( Wallet
, applyBlocks
Expand All @@ -90,6 +80,7 @@ import Cardano.Wallet.Primitive.Types
( Block (..)
, Coin (..)
, Direction (..)
, IsOurs (..)
, SignedTx (..)
, SlotId (..)
, Tx
Expand All @@ -109,6 +100,8 @@ import Control.Arrow
( first )
import Control.Concurrent
( forkIO, threadDelay )
import Control.DeepSeq
( NFData )
import Control.Monad
( forM, void, (>=>) )
import Control.Monad.Fail
Expand All @@ -135,8 +128,6 @@ import Data.Time.Clock
( getCurrentTime )
import Fmt
( (+|), (+||), (|+), (||+) )
import GHC.Generics
( Generic )

import qualified Cardano.Wallet.CoinSelection.Policy.Random as CoinSelection
import qualified Cardano.Wallet.DB as DB
Expand All @@ -149,9 +140,11 @@ import qualified Data.Text.IO as TIO

data WalletLayer s = WalletLayer
{ createWallet
:: NewWallet
:: WalletId
-> WalletName
-> s
-> ExceptT ErrWalletAlreadyExists IO WalletId
-- ^ Initialise and store a new wallet, returning its ID.
-- ^ Initialise and store a new wallet, returning its Id.

, readWallet
:: WalletId
Expand Down Expand Up @@ -205,20 +198,15 @@ data WalletLayer s = WalletLayer
-> (Tx, TxMeta, [TxWitness])
-> ExceptT ErrSubmitTx IO ()
-- ^ Broadcast a (signed) transaction to the network.
}

data NewWallet = NewWallet
{ seed
:: !(Passphrase "seed")
, secondFactor
:: !(Passphrase "generation")
, name
:: !WalletName
, passphrase
:: !(Passphrase "encryption")
, gap
:: !AddressPoolGap
} deriving (Show, Generic)
, attachPrivateKey
:: WalletId
-> (Key 'RootK XPrv, Passphrase "encryption")
-> ExceptT ErrNoSuchWallet IO ()
-- ^ Attach a given private key to a wallet. The private key is
-- necessary for some operations like signing transactions or,
-- generating new accounts.
}
Copy link
Member

@KtorZ KtorZ Apr 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A suggestion here: let's remove the NewWallet type wrapper here and simply modify the wallet layer signature as follows:

        :: WalletName
        -> Key scheme 'Root0 XPrv
        -> Passphrase "encryption"
        -> s
        -> ExceptT (ErrWalletAlreadyExists "createWallet") IO WalletId

Leave the creation of the state to the caller 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK good idea. Turns out NewWallet is kind of helpful, so I have added a createWalletSeq :: WalletLayer SeqState -> NewWallet ExceptT ErrWalletAlreadyExists IO WalletId.


-- | Errors occuring when creating an unsigned transaction
data ErrCreateUnsignedTx
Expand All @@ -242,41 +230,26 @@ data ErrSubmitTx

-- | Create a new instance of the wallet layer.
mkWalletLayer
:: DBLayer IO SeqState
:: forall s. (IsOurs s, AddressScheme s, NFData s, Show s)
=> DBLayer IO s
-> NetworkLayer IO
-> WalletLayer SeqState
-> WalletLayer s
mkWalletLayer db network = WalletLayer

{---------------------------------------------------------------------------
Wallets
---------------------------------------------------------------------------}

{ createWallet = \w -> do
let rootXPrv =
generateKeyFromSeed (seed w, secondFactor w) (passphrase w)
let accXPrv =
deriveAccountPrivateKey mempty rootXPrv minBound
let extPool =
mkAddressPool (publicKey accXPrv) (gap w) []
let intPool =
mkAddressPool (publicKey accXPrv) minBound []
let wid =
WalletId (digest $ publicKey rootXPrv)
let checkpoint = initWallet $ SeqState
{ externalPool = extPool
, internalPool = intPool
, pendingChangeIxs = emptyPendingIxs
}
{ createWallet = \wid wname s -> do
let checkpoint = initWallet s
now <- liftIO getCurrentTime
let metadata = WalletMetadata
{ name = Cardano.Wallet.name w
{ name = wname
, passphraseInfo = WalletPassphraseInfo now
, status = Restoring minBound
, delegation = NotDelegating
}
hpwd <- liftIO $ encryptPassphrase (passphrase w)
let creds = ( rootXPrv, hpwd )
DB.createWallet db (PrimaryKey wid) checkpoint metadata creds $> wid
DB.createWallet db (PrimaryKey wid) checkpoint metadata $> wid

, readWallet = _readWallet

Expand Down Expand Up @@ -314,8 +287,8 @@ mkWalletLayer db network = WalletLayer
withRootKey wid pwd ErrSignTxWrongPassphrase $ \xprv -> do
case mkStdTx (getState w) (xprv, pwd) ins allShuffledOuts of
Right (tx, wit) -> do
-- Safe because we have a lock and we already fetched the wallet
-- within this context.
-- Safe because we have a lock and we already fetched the
-- wallet within this context.
liftIO . unsafeRunExceptT $
DB.putCheckpoint db (PrimaryKey wid) (updateState s' w)
let amtChng = fromIntegral $
Expand All @@ -340,11 +313,19 @@ mkWalletLayer db network = WalletLayer
let history = Map.fromList [(txId tx, (tx, meta))]
DB.putCheckpoint db (PrimaryKey wid) (newPending tx w)
DB.putTxHistory db (PrimaryKey wid) history

{---------------------------------------------------------------------------
Keystore
---------------------------------------------------------------------------}

, attachPrivateKey = \wid (xprv, pwd) -> do
hpwd <- liftIO $ encryptPassphrase pwd
DB.putPrivateKey db (PrimaryKey wid) (xprv, hpwd)
}
where
_readWallet
:: WalletId
-> ExceptT ErrNoSuchWallet IO (Wallet SeqState, WalletMetadata)
-> ExceptT ErrNoSuchWallet IO (Wallet s, WalletMetadata)
_readWallet wid = maybeToExceptT (ErrNoSuchWallet wid) $ do
cp <- MaybeT $ DB.readCheckpoint db (PrimaryKey wid)
meta <- MaybeT $ DB.readWalletMeta db (PrimaryKey wid)
Expand Down
30 changes: 14 additions & 16 deletions src/Cardano/Wallet/Api/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import Cardano.Wallet
, ErrSignTx (..)
, ErrSubmitTx (..)
, ErrWalletAlreadyExists (..)
, NewWallet (..)
, WalletLayer
)
import Cardano.Wallet.Api
Expand All @@ -43,12 +42,14 @@ import Cardano.Wallet.Binary
( txId )
import Cardano.Wallet.CoinSelection
( CoinSelectionOptions (..) )
import Cardano.Wallet.Primitive.AddressDerivation
( digest, generateKeyFromSeed, publicKey )
import Cardano.Wallet.Primitive.AddressDiscovery
( SeqState (..), defaultAddressPoolGap )
( SeqState (..), defaultAddressPoolGap, mkSeqState )
import Cardano.Wallet.Primitive.Model
( availableBalance, getState, totalBalance )
import Cardano.Wallet.Primitive.Types
( AddressState, Coin (..), TxOut (..), WalletId )
( AddressState, Coin (..), TxOut (..), WalletId (..) )
import Control.Monad.Catch
( throwM )
import Control.Monad.IO.Class
Expand Down Expand Up @@ -143,19 +144,16 @@ postWallet
:: WalletLayer SeqState
-> WalletPostData
-> Handler ApiWallet
postWallet w req = do
wid <- liftHandler $ W.createWallet w $ NewWallet
{ seed =
getApiMnemonicT (req ^. #mnemonicSentence)
, secondFactor =
maybe mempty getApiMnemonicT (req ^. #mnemonicSecondFactor)
, name =
getApiT (req ^. #name)
, passphrase =
getApiT (req ^. #passphrase)
, gap =
maybe defaultAddressPoolGap getApiT (req ^. #addressPoolGap)
}
postWallet w body = do
let seed = getApiMnemonicT (body ^. #mnemonicSentence)
let secondFactor = maybe mempty getApiMnemonicT (body ^. #mnemonicSecondFactor)
let pwd = getApiT (body ^. #passphrase)
let rootXPrv = generateKeyFromSeed (seed, secondFactor) pwd
let g = maybe defaultAddressPoolGap getApiT (body ^. #addressPoolGap)
let s = mkSeqState (rootXPrv, pwd) g
let wid = WalletId $ digest $ publicKey rootXPrv
_ <- liftHandler $ W.createWallet w wid (getApiT (body ^. #name)) s
liftHandler $ W.attachPrivateKey w wid (rootXPrv, pwd)
liftHandler $ W.restoreWallet w wid
getWallet w (ApiT wid)

Expand Down
10 changes: 9 additions & 1 deletion src/Cardano/Wallet/DB.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ data DBLayer m s = DBLayer
:: PrimaryKey WalletId
-> Wallet s
-> WalletMetadata
-> (Key 'RootK XPrv, Hash "encryption")
-> ExceptT ErrWalletAlreadyExists m ()
-- ^ Initialize a database entry for a given wallet. 'putCheckpoint',
-- 'putWalletMeta' or 'putTxHistory' will actually all fail if they are
Expand Down Expand Up @@ -106,6 +105,15 @@ data DBLayer m s = DBLayer
--
-- Returns an empty map if the wallet isn't found.

, putPrivateKey
:: PrimaryKey WalletId
-> (Key 'RootK XPrv, Hash "encryption")
-> ExceptT ErrNoSuchWallet m ()
-- ^ Store or replace a private key for a given wallet. Note that wallet
-- _could_ be stored and manipulated without any private key associated
-- to it. A private key is only seldomly required for very specific
-- operations (like transaction signing).

, readPrivateKey
:: PrimaryKey WalletId
-> m (Maybe (Key 'RootK XPrv, Hash "encryption"))
Expand Down
Loading