Skip to content

Commit

Permalink
[CSL-2383] Interpreter for pure/cardano
Browse files Browse the repository at this point in the history
  • Loading branch information
edsko committed Apr 25, 2018
1 parent b606580 commit 042cd24
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 8 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 @@ -341,6 +341,7 @@ test-suite wallet-unit-tests
UTxO.Translate
UTxO.Verify
Wallet.Abstract
Wallet.Abstract.Cardano
Wallet.Basic
Wallet.Incremental
Wallet.Prefiltered
Expand Down
25 changes: 19 additions & 6 deletions wallet-new/src/Cardano/Wallet/Kernel.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,24 @@ module Cardano.Wallet.Kernel (
PassiveWallet -- opaque
, bracketPassiveWallet
, init
, applyBlock
, utxo
-- * Active wallet
, ActiveWallet -- opaque
, bracketActiveWallet
, newPending
, hasPending
, walletPassive
) where

import Universum
import System.Wlog (Severity(..))
import System.Wlog (Severity (..))
import Universum

import Cardano.Wallet.Kernel.Diffusion (WalletDiffusion(..))
import Cardano.Wallet.Kernel.Diffusion (WalletDiffusion (..))
import Cardano.Wallet.Kernel.Types

import Pos.Core (TxAux)
import Pos.Core (TxAux)
import Pos.Txp (Utxo)

{-------------------------------------------------------------------------------
Passive wallet
Expand Down Expand Up @@ -59,10 +64,18 @@ bracketPassiveWallet walletLogMessage =
--
-- This is separate from allocating the wallet resources, and will only be
-- called when the node is initialized (when run in the node proper).
init :: PassiveWallet -> IO ()
init PassiveWallet{..} = do
init :: PassiveWallet -> Utxo -> IO ()
init PassiveWallet{..} _utxo = do
walletLogMessage Info "Wallet kernel initialized"

-- | Notify the wallet of a new block
applyBlock :: PassiveWallet -> ResolvedBlock -> IO ()
applyBlock _wallet _block = error "TODO: applyBlock"

-- | Return the wallet's current UTxO
utxo :: PassiveWallet -> IO Utxo
utxo _wallet = error "TODO: utxo"

{-------------------------------------------------------------------------------
Active wallet
-------------------------------------------------------------------------------}
Expand Down
12 changes: 11 additions & 1 deletion wallet-new/test/unit/UTxO/Interpreter.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ module UTxO.Interpreter (
IntException(..)
-- * Interpretation context
, IntCtxt -- opaque
, initIntCtxt
-- * Interpretation monad
, IntT
, runIntT
, runIntT'
, runIntBoot
, runIntBoot'
, liftTranslate
, liftTranslateInt
-- * Interpreter proper
, Interpret(..)
) where
Expand Down Expand Up @@ -243,6 +245,13 @@ class Interpret h a where
Instances that read, but not update, the state
-------------------------------------------------------------------------------}

instance Interpret h DSL.Value where
type Interpreted DSL.Value = Coin

int :: (HasCallStack, Monad m)
=> DSL.Value -> IntT h e m Coin
int = return . mkCoin

instance Interpret h Addr where
type Interpreted Addr = (SomeKeyPair, Address)

Expand Down Expand Up @@ -290,10 +299,11 @@ instance Interpret h (DSL.Output Addr) where
=> DSL.Output Addr -> IntT h e m TxOutAux
int DSL.Output{..} = do
(_, outAddr') <- int outAddr
outVal' <- int outVal
return TxOutAux {
toaOut = TxOut {
txOutAddress = outAddr'
, txOutValue = mkCoin outVal
, txOutValue = outVal'
}
}

Expand Down
12 changes: 12 additions & 0 deletions wallet-new/test/unit/UTxO/Translate.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module UTxO.Translate (
, withConfig
, mapTranslateErrors
, catchTranslateErrors
, catchSomeTranslateErrors
-- * Convenience wrappers
, translateFirstSlot
, translateNextSlot
Expand Down Expand Up @@ -69,6 +70,7 @@ newtype TranslateT e m a = TranslateT {
, Applicative
, Monad
, MonadError e
, MonadIO
)

instance MonadTrans (TranslateT e) where
Expand Down Expand Up @@ -133,6 +135,16 @@ catchTranslateErrors :: Functor m
catchTranslateErrors (TranslateT (ExceptT (ReaderT ma))) =
TranslateT $ ExceptT $ ReaderT $ \env -> fmap Right (ma env)

catchSomeTranslateErrors :: Monad m
=> TranslateT (Either e e') m a
-> TranslateT e m (Either e' a)
catchSomeTranslateErrors act = do
ma <- catchTranslateErrors act
case ma of
Left (Left e) -> throwError e
Left (Right e') -> return $ Left e'
Right a -> return $ Right a

{-------------------------------------------------------------------------------
Convenience wrappers
-------------------------------------------------------------------------------}
Expand Down
259 changes: 259 additions & 0 deletions wallet-new/test/unit/Wallet/Abstract/Cardano.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE UndecidableInstances #-}

module Wallet.Abstract.Cardano (
-- * Cardano interpreter for the inductive wallet
InductiveT(..)
, interpretT
-- * Equivalence check
, EquivalenceViolation(..)
, EquivalenceViolationEvidence(..)
, equivalentT
) where

import Universum
import qualified Data.Text.Buildable
import Formatting (bprint, build, (%))

import Pos.Txp (Utxo, formatUtxo)
import qualified Cardano.Wallet.Kernel as Kernel
import Cardano.Wallet.Kernel.Types

import Util.Validated
import UTxO.Context (Addr)
import UTxO.DSL (Hash)
import qualified UTxO.DSL as DSL
import UTxO.Interpreter
import UTxO.Translate
import Wallet.Abstract

{-------------------------------------------------------------------------------
Interpreter for the wallet using the translated Cardano types
-------------------------------------------------------------------------------}

-- | Callbacks used in 'interpretT'
--
-- We do not run the callback in the 'IntT' monad so that we maintain
-- control over the interpretation context.
data InductiveT h m = InductiveT {
-- | Initialize the wallet
--
-- The callback is given the translated UTxO of the bootstrap
-- transaction (we cannot give it the translated transaction because
-- we cannot translate the bootstrap transaction).
walletBootT :: InductiveCtxt h -> Utxo -> m ()

-- | Apply a block
, walletApplyBlockT :: InductiveCtxt h -> RawResolvedBlock -> m ()

-- | Insert new pending transaction
, walletNewPendingT :: InductiveCtxt h -> RawResolvedTx -> m ()
}

-- | The context in which a function of 'InductiveT' gets called
data InductiveCtxt h = InductiveCtxt {
-- | The 'Inductive' value that led to this point
inductiveCtxtInd :: Inductive h Addr

-- | The 'IntCtxt' suitable for translation derived values
-- (such as UTxOs)
, inductiveCtxtInt :: IntCtxt h

-- | The pure wallet value at this point
, inductiveCtxtWallet :: Wallet h Addr
}

-- | Interpreter for inductive wallets using the translated Cardano types
interpretT :: forall h e m. (Monad m, Hash h Addr)
=> (DSL.Transaction h Addr -> Wallet h Addr)
-> InductiveT h (TranslateT e m)
-> Inductive h Addr
-> TranslateT (Either IntException e) m (Wallet h Addr, IntCtxt h)
interpretT mkWallet InductiveT{..} =
-- This is ugly, but we only discover the bootstrap transaction once we
-- descend down the 'Inductive' wallet. We will 'put' the right context
-- before the first call to 'int'.
runIntT (error "initialized later") . go
where
go :: Inductive h Addr -> IntT h e m (Wallet h Addr)
go ind@(WalletBoot t) = do
let w' = mkWallet t
ic <- liftTranslateInt (initIntCtxt t)
put ic
utxo' <- int (utxo w') -- translating UTxO does not change the state
liftTranslate $ walletBootT (InductiveCtxt ind ic w') utxo'
return w'
go ind@(ApplyBlock w b) = do
w' <- go w
b' <- int b
ic <- get
liftTranslate $ walletApplyBlockT (InductiveCtxt ind ic w') b'
return w'
go ind@(NewPending w t) = do
w' <- go w
t' <- int t
ic <- get
liftTranslate $ walletNewPendingT (InductiveCtxt ind ic w') t'
return w'

{-------------------------------------------------------------------------------
Equivalence check between the real implementation and (a) pure wallet
-------------------------------------------------------------------------------}

equivalentT :: forall h m. (Hash h Addr, MonadIO m)
=> Kernel.ActiveWallet
-> (DSL.Transaction h Addr -> Wallet h Addr)
-> Inductive h Addr
-> TranslateT IntException m
(Validated (EquivalenceViolation h) (Wallet h Addr, IntCtxt h))
equivalentT activeWallet = \mkWallet w ->
fmap validatedFromEither
$ catchSomeTranslateErrors
$ interpretT mkWallet InductiveT{..} w
where
passiveWallet = Kernel.walletPassive activeWallet

walletBootT :: InductiveCtxt h
-> Utxo
-> TranslateT (EquivalenceViolation h) m ()
walletBootT ctxt utxo = do
liftIO $ Kernel.init passiveWallet utxo
checkWalletState ctxt

walletApplyBlockT :: InductiveCtxt h
-> RawResolvedBlock
-> TranslateT (EquivalenceViolation h) m ()
walletApplyBlockT ctxt block = do
liftIO $ Kernel.applyBlock passiveWallet (fromRawResolvedBlock block)
checkWalletState ctxt

walletNewPendingT :: InductiveCtxt h
-> RawResolvedTx
-> TranslateT (EquivalenceViolation h) m ()
walletNewPendingT ctxt tx = do
liftIO $ Kernel.newPending activeWallet (fst tx)
checkWalletState ctxt

checkWalletState :: InductiveCtxt h
-> TranslateT (EquivalenceViolation h) m ()
checkWalletState ctxt@InductiveCtxt{..} = do
cmp "utxo" utxo Kernel.utxo
-- TODO: check other properties
where
cmp :: ( Interpret h a
, Eq (Interpreted a)
, Buildable a
, Buildable (Interpreted a)
)
=> Text
-> (Wallet h Addr -> a)
-> (Kernel.PassiveWallet -> IO (Interpreted a))
-> TranslateT (EquivalenceViolation h) m ()
cmp fld f g = do
let dsl = f inductiveCtxtWallet
translated <- toCardano ctxt fld dsl
kernel <- liftIO $ g passiveWallet
unless (translated == kernel) $
throwError EquivalenceViolation {
equivalenceViolationName = fld
, equivalenceViolationInductive = inductiveCtxtInd
, equivalenceViolationEvidence = NotEquivalent {
notEquivalentDsl = dsl
, notEquivalentTranslated = translated
, notEquivalentKernel = kernel
}
}

toCardano :: Interpret h a
=> InductiveCtxt h
-> Text
-> a -> TranslateT (EquivalenceViolation h) m (Interpreted a)
toCardano InductiveCtxt{..} fld a = do
ma' <- catchTranslateErrors $ runIntT' inductiveCtxtInt $ int a
case ma' of
Left err -> throwError $ EquivalenceNotChecked {
equivalenceNotCheckedName = fld
, equivalenceNotCheckedReason = err
, equivalenceNotCheckedInductive = inductiveCtxtInd
}
Right (a', _ic') ->
return a'

data EquivalenceViolation h =
-- | Cardano wallet and pure wallet are not equivalent
EquivalenceViolation {
-- | The property we were checking
equivalenceViolationName :: Text

-- | Evidence (what was not the same?)
, equivalenceViolationEvidence :: EquivalenceViolationEvidence

-- | The 'Inductive' value at the point of the error
, equivalenceViolationInductive :: Inductive h Addr
}

-- | We got an unexpected interpretation exception
--
-- This indicates a bug in the tesing infrastructure.
| EquivalenceNotChecked {
-- | The property we were checking
equivalenceNotCheckedName :: Text

-- | Why did we not check the equivalence
, equivalenceNotCheckedReason :: IntException

-- | The 'Inductive' value at the point of the error
, equivalenceNotCheckedInductive :: Inductive h Addr
}

data EquivalenceViolationEvidence =
forall a. (Buildable a, Buildable (Interpreted a)) => NotEquivalent {
notEquivalentDsl :: a
, notEquivalentTranslated :: Interpreted a
, notEquivalentKernel :: Interpreted a
}

{-------------------------------------------------------------------------------
Pretty-printing
-------------------------------------------------------------------------------}

instance Hash h Addr => Buildable (EquivalenceViolation h) where
build EquivalenceViolation{..} = bprint
( "EquivalenceViolation "
% "{ name: " % build
% ", evidence: " % build
% ", inductive: " % build
% "}"
)
equivalenceViolationName
equivalenceViolationEvidence
equivalenceViolationInductive
build (EquivalenceNotChecked{..}) = bprint
( "EquivalenceNotChecked "
% "{ name: " % build
% ", reason: " % build
% ", inductive: " % build
% "}"
)
equivalenceNotCheckedName
equivalenceNotCheckedReason
equivalenceNotCheckedInductive

instance Buildable EquivalenceViolationEvidence where
build NotEquivalent{..} = bprint
( "NotEquivalent "
% "{ notEquivalentDsl: " % build
% ", notEquivalentTranslated: " % build
% ", notEquivalentKernel: " % build
% "}"
)
notEquivalentDsl
notEquivalentTranslated
notEquivalentKernel

{-------------------------------------------------------------------------------
Orphans (TODO: avoid)
-------------------------------------------------------------------------------}

instance Buildable Utxo where
build = formatUtxo
Loading

0 comments on commit 042cd24

Please sign in to comment.