Skip to content

Commit

Permalink
Merge pull request #160 from input-output-hk/rvl/100/address-discovery
Browse files Browse the repository at this point in the history
Add new address discovery schemes for testing and benchmarking
  • Loading branch information
KtorZ authored Apr 26, 2019
2 parents c34b903 + e3964f2 commit 9d81287
Show file tree
Hide file tree
Showing 12 changed files with 348 additions and 285 deletions.
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.
}

-- | 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

0 comments on commit 9d81287

Please sign in to comment.