Permalink
Browse files

[BIP44-239] Get Eo wallets from AcidState and use in BListener and Ne…

…w Pending. Enables Eo wallet prefiltering.
  • Loading branch information...
uroboros committed Feb 10, 2019
1 parent 8d2f30a commit 59b531071b9d99c7329f740bfe6adc96e190fcfb
@@ -77,6 +77,12 @@ data ErrAddressPoolInvalid
| ErrNotEnoughAddresses
deriving (Eq, Show)

instance Buildable ErrAddressPoolInvalid where
build ErrIndexesAreNotSequential =
bprint "ErrIndexesAreNotSequential"
build ErrNotEnoughAddresses =
bprint "ErrNotEnoughAddresses"

instance Exception ErrAddressPoolInvalid

instance Arbitrary ErrAddressPoolInvalid where
@@ -1,5 +1,6 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ViewPatterns #-}

-- | React to BListener events
module Cardano.Wallet.Kernel.BListener (
@@ -27,11 +28,13 @@ import qualified Formatting.Buildable
import Pos.Chain.Block (HeaderHash)
import Pos.Chain.Genesis (Config (..))
import Pos.Chain.Txp (TxId, TxIn)
import Pos.Core (Address)
import Pos.Core.Chrono (OldestFirst (..))
import Pos.Crypto (EncryptedSecretKey)
import Pos.DB.Block (getBlund)
import Pos.Util.Log (Severity (..))

import Cardano.Wallet.Kernel.AddressPool (AddressPool)
import Cardano.Wallet.Kernel.DB.AcidState (ApplyBlock (..),
ObservableRollbackUseInTestsOnly (..), SwitchToFork (..),
SwitchToForkInternalError (..))
@@ -50,11 +53,12 @@ import qualified Cardano.Wallet.Kernel.NodeStateAdaptor as Node
import Cardano.Wallet.Kernel.Prefiltering (PrefilteredBlock)
import qualified Cardano.Wallet.Kernel.Prefiltering as P
import Cardano.Wallet.Kernel.Read (foreignPendingByAccount,
getFOWallets, getWalletSnapshot)
getEosPools, getFOWallets, getWalletSnapshot)
import Cardano.Wallet.Kernel.Restore
import qualified Cardano.Wallet.Kernel.Submission as Submission
import Cardano.Wallet.Kernel.Util.NonEmptyMap (NonEmptyMap)
import qualified Cardano.Wallet.Kernel.Util.NonEmptyMap as NEM
import Cardano.Wallet.Kernel.Wallets (mkEosAddress)
import Cardano.Wallet.WalletLayer.Kernel.Wallets
(blundToResolvedBlock)

@@ -68,24 +72,42 @@ type PrefilterResult = ((BlockContext, PrefilteredBlock), [TxMeta])
prefilterContext
:: PassiveWallet
-> IO ( Map HdAccountId (Set TxIn)
, Map HdRootId EncryptedSecretKey)
-- ^ (Foreigns, FO Prefilter Context)
, Map HdRootId EncryptedSecretKey
, Map HdAccountId (AddressPool Address))
-- ^ (Foreigns, FO Prefilter Context, EO Prefilter Context)
prefilterContext pw = do
db <- getWalletSnapshot pw
let foreigns = fmap Pending.txIns . foreignPendingByAccount $ db
hdFOs <- getFOWallets pw db
return (foreigns, hdFOs)
hdEOs <- getEosPools db (mkEosAddress $ pw ^. walletProtocolMagic)
return (foreigns, hdFOs, hdEOs)

-- TODO @uroboros this function is trivial as it stands,
-- as we add support for EO wallets, we will here do a separate call to
-- prefilterBlock_ for FO and EO wallets and return the combined result
-- | Prefilter a resolved block for all wallets. If no wallets are present
-- we return Nothing. If either wallet type is present, we return only the
-- relevant results.
--
-- In the case of both wallet types being present, we must merge the prefilter
-- results and ensure that the block contexts returned by the different
-- prefilterings are the same.
prefilterBlock
:: Map HdAccountId (Set TxIn)
-> Map HdRootId EncryptedSecretKey
-> Map HdAccountId (AddressPool Address)
-> ResolvedBlock
-> IO PrefilterResult
prefilterBlock fs foContext rb = do
prefilterBlock_ rb fs foContext
-> IO (Maybe PrefilterResult)
prefilterBlock _ (Map.null -> True) (Map.null -> True) _
= return Nothing
prefilterBlock fs (Map.null -> True) hdEOs rb
= Just <$> prefilterBlock_ rb fs hdEOs
prefilterBlock fs hdFOs (Map.null -> True) rb
= Just <$> prefilterBlock_ rb fs hdFOs
prefilterBlock fs hdFOs hdEOs rb = do
((ctxt, pb), txMetas) <- prefilterBlock_ rb fs hdEOs
((ctxt', pb'), txMetas') <- prefilterBlock_ rb fs hdFOs
if ctxt /= ctxt'
then (error $ "When merging the prefiltering results for FO and EO "
<> "wallets, there were different block contexts")
else (return . Just $ ((ctxt, pb <> pb'), txMetas <> txMetas'))

-- Prefilter blocks for any class of wallets for which prefiltering is
-- defined (any type constrained by IsOurs)
@@ -230,16 +252,18 @@ applyBlock pw@PassiveWallet{..} b = do
-> ResolvedBlock
-> ExceptT (NonEmptyMap HdAccountId ApplyBlockFailed) IO ()
applyOneBlock k accts b' = ExceptT $ do
(foreigns, hdFOs) <- prefilterContext pw
((ctxt, blocksByAccount), metas) <- prefilterBlock foreigns hdFOs b'
-- apply block to all Accounts in all Wallets
mConfirmed <- update' _wallets $ ApplyBlock k ctxt accts blocksByAccount
case mConfirmed of
Left errs -> return (Left errs)
Right confirmed -> do
modifyMVar_ _walletSubmission (return . Submission.remPending confirmed)
mapM_ (putTxMeta _walletMeta) metas
return $ Right ()
(foreigns, hdFOs, hdEOs) <- prefilterContext pw
prefilterBlock foreigns hdFOs hdEOs b' >>= \case
Nothing -> return $ Right ()
Just ((ctxt, blocksByAccount), metas) -> do
-- apply block to all Accounts in all Wallets
mConfirmed <- update' _wallets $ ApplyBlock k ctxt accts blocksByAccount
case mConfirmed of
Left errs -> return (Left errs)
Right confirmed -> do
modifyMVar_ _walletSubmission (return . Submission.remPending confirmed)
mapM_ (putTxMeta _walletMeta) metas
return $ Right ()

-- Determine if a failure in 'ApplyBlock' was due to the account being ahead, behind,
-- or incomparable with the provided block.
@@ -325,23 +349,21 @@ switchToFork :: PassiveWallet
-> IO ()
switchToFork pw@PassiveWallet{..} oldest bs = do
k <- Node.getSecurityParameter _walletNode
(foreigns, hdFOs) <- prefilterContext pw
blocksAndMeta <- mapM (prefilterBlock foreigns hdFOs) bs
case blocksAndMeta of


prefilterBlocks >>= \case
-- We skip the switchToFork completely, as no wallet is configured
-- in this node.
[] -> return ()
xs -> do
let (blockssByAccount, metas) = unzip xs

changes <- trySwitchingToFork k blockssByAccount

mapM_ (putTxMeta _walletMeta) $ concat metas
modifyMVar_ _walletSubmission $
return . Submission.addPendings (fst <$> changes)
. Submission.remPending (snd <$> changes)
where

trySwitchingToFork :: Node.SecurityParameter
-> [(BlockContext, PrefilteredBlock)]
-> IO (Map HdAccountId (Pending, Set TxId))
@@ -370,6 +392,19 @@ switchToFork pw@PassiveWallet{..} oldest bs = do
mapM_ restartRestoration restorations
return changes

-- Prefilter the resolved blocks for all wallets
-- NOTE: if there are no wallets to prefilter we expect no results. If there
-- are any wallets present, we expect the same number of results as blocks
prefilterBlocks :: IO [PrefilterResult]
prefilterBlocks = do
(fs, hdFOs, hdEOs) <- prefilterContext pw
blocksAndMeta <- catMaybes <$> mapM (prefilterBlock fs hdFOs hdEOs) bs
if (not (null blocksAndMeta)) && ((length blocksAndMeta) /= length bs)
then (error $ "the impossible has happened, mapM prefilterBlocks returned "
<> "a different number of prefilter results than blocks (we"
<> "expect no results or the same number as there are blocks)")
else (return blocksAndMeta)

-- | Observable rollback
--
-- Only used for tests. See 'switchToFork'.
@@ -101,6 +101,7 @@ module Cardano.Wallet.Kernel.DB.HdWallet (
, IsOurs(..)
-- Address pool
, mkAddressPool
, mkAddressPoolExisting
) where

import Universum hiding ((:|))
@@ -124,7 +125,8 @@ import qualified Pos.Crypto as Core

import Cardano.Wallet.API.V1.Types (WalAddress (..))
import Cardano.Wallet.Kernel.AddressPool (AddressPool,
emptyAddressPool, lookupAddressPool)
ErrAddressPoolInvalid(..),
emptyAddressPool, initAddressPool, lookupAddressPool)
import Cardano.Wallet.Kernel.AddressPoolGap (AddressPoolGap)
import Cardano.Wallet.Kernel.DB.BlockContext
import Cardano.Wallet.Kernel.DB.HdRootId (HdRootId)
@@ -571,10 +573,25 @@ mkAddressPool
-> Core.PublicKey
-> AddressPoolGap
-> AddressPool Core.Address
mkAddressPool mkAddress accPK gap = emptyAddressPool gap newAddress
where
newAddress :: Word32 -> Core.Address
newAddress addrIx = case deriveAddressPublicKey accPK ExternalChain addrIx of
mkAddressPool mkAddress accPK gap
= emptyAddressPool gap (mkAddressBuilder mkAddress accPK)

mkAddressPoolExisting
:: (Core.PublicKey -> Core.Address)
-> Core.PublicKey
-> AddressPoolGap
-> [(Core.Address, Word32)]
-> Either ErrAddressPoolInvalid (AddressPool Core.Address)
mkAddressPoolExisting mkAddress accPK gap addrs
= initAddressPool gap (mkAddressBuilder mkAddress accPK) addrs

mkAddressBuilder
:: (Core.PublicKey -> Core.Address)
-> Core.PublicKey
-> Word32
-> Core.Address
mkAddressBuilder mkAddress accPK addrIx
= case deriveAddressPublicKey accPK ExternalChain addrIx of
Nothing -> error "mkAddressPool: maximum number of addresses reached."
Just addrPK -> mkAddress addrPK

Oops, something went wrong.

0 comments on commit 59b5310

Please sign in to comment.