cardano-wallet-core-2022.7.1: The Wallet Backend for a Cardano node.
Copyright © 2018-2020 IOHK
License Apache-2.0
Safe Haskell None
Language Haskell2010

Cardano.Wallet.Primitive.Model

Description

This module implements the "business logic" to manage a Cardano wallet. It is a direct implementation of the model, with extensions, from the Formal Specification for a Cardano Wallet .

In other words, this module is about how the wallet keeps track of its internal state, specifically the UTxO set and the address discovery state. This module is intentionally agnostic to specific address formats, and instead relies on the IsOurs abstraction. It is also agnostic to issues such as how blocks are retrieved from the network, or how the state is serialized and cached in the local database.

All those functions are pure and there's no reason to shove in any sort of side-effects in here. 🙂

Synopsis

Type

data Wallet s Source #

Abstract data type representing a wallet state.

A Wallet keeps track of transaction outputs and associated addresses that belong to us -- we are able to spend these outputs because we know the corresponding signing key belonging to the output. Hence, we are able to produce witness engaging those outputs as they become inputs in forthcoming transactions according to UTxO model. This information is associated to a particular point on the blockchain.

Internally, a Wallet keeps track of

  • UTxOs
  • Known & used addresses, via address discovery state
  • The associated BlockHeader indicating the point on the chain.

The Wallet is parameterized over a single type:

  • s is a state used to keep track of known addresses. Typically, this state will be an instance of the IsOurs class, e.g. IsOurs s Address .

A few examples to make it concrete:

Wallet (RndState k n)
Wallet (SeqState n ShelleyKey)

Instances

Instances details
Eq s => Eq ( Wallet s) Source #
Instance details

Defined in Cardano.Wallet.Primitive.Model

Show s => Show ( Wallet s) Source #
Instance details

Defined in Cardano.Wallet.Primitive.Model

Generic ( Wallet s) Source #
Instance details

Defined in Cardano.Wallet.Primitive.Model

Associated Types

type Rep ( Wallet s) :: Type -> Type Source #

NFData s => NFData ( Wallet s) Source #
Instance details

Defined in Cardano.Wallet.Primitive.Model

Methods

rnf :: Wallet s -> () Source #

Buildable s => Buildable ( Wallet s) Source #
Instance details

Defined in Cardano.Wallet.Primitive.Model

type Rep ( Wallet s) Source #
Instance details

Defined in Cardano.Wallet.Primitive.Model

Construction & Modification

initWallet Source #

Arguments

:: ( IsOurs s Address , IsOurs s RewardAccount )
=> Block

The genesis block

-> s

Initial address discovery state

-> ([( Tx , TxMeta )], Wallet s)

Create an empty wallet and apply the given genesis block.

The wallet tip will be the genesis block header.

updateState :: s -> Wallet s -> Wallet s Source #

Update the address discovery state of a wallet.

data FilteredBlock Source #

Represents the subset of data from a single block that are relevant to a particular wallet, discovered when applying a block to that wallet.

Constructors

FilteredBlock

Fields

  • slot :: ! Slot

    The slot of this block.

  • transactions :: ![( Tx , TxMeta )]

    The set of transactions that affect the wallet, list in the same order which they appeared in the block.

  • delegations :: ![ DelegationCertificate ]

    Stake delegations made on behalf of the wallet, listed in the order in which they appear on the chain. If the list contains more than element, those that appear later in the list supersede those that appear earlier on.

applyBlock :: ( IsOurs s Address , IsOurs s RewardAccount ) => Block -> Wallet s -> ( FilteredBlock , ( DeltaWallet s, Wallet s)) Source #

Apply a single block to a wallet.

This is the most fundamental way of making a wallet evolve.

Returns an updated wallet, as well as the address data relevant to the wallet that were discovered while applying the block.

applyBlocks :: ( IsOurs s Address , IsOurs s RewardAccount , Monad m) => BlockData m ( Either Address RewardAccount ) ChainEvents s -> Wallet s -> m ( NonEmpty ([ FilteredBlock ], ( DeltaWallet s, Wallet s))) Source #

Apply multiple blocks in sequence to an existing wallet and return a list of intermediate wallet states.

If the input blocks are a List , then one intermediate wallet state is returned for each block in the list. If the input blocks are a Summary , then only one final wallet state is returned.

More specifically, for an initial wallet state w0 and a List of of blocks

bs = [b1, b2, …, bn]@

, the function returns

@ [ (filtered b1, (delta w0 -> w1 , w1 = w0 + b1)) , (filtered b2, (delta w1 -> w2 , w2 = w1 + b2)) , … , (filtered bn, (delta w(n-1) -> wn, wn = w(n-1)+bn)) ]

Here:

  • filtered bj refers to the set of transactions contained in the block bj that were actually applied to the wallet state.
  • wi + bj refers to the wallet state obtained after applying the block bi to the wallet wj .
  • delta wi -> wj refers to the delta that was applied in order to obtain wj from wi@.

applyBlockData :: ( IsOurs s Address , IsOurs s RewardAccount , Monad m) => BlockData m ( Either Address RewardAccount ) ChainEvents s -> Wallet s -> m ([ FilteredBlock ], ( DeltaWallet s, Wallet s)) Source #

Apply multiple blocks in sequence to an existing wallet and return the final wallet state as well as the transactions that were applied.

data BlockData m addr tx s Source #

BlockData which has been paired with discovery facilities.

firstHeader :: BlockData m addr txs s -> BlockHeader Source #

First BlockHeader of the blocks represented by BlockData .

lastHeader :: BlockData m addr txs s -> BlockHeader Source #

Last BlockHeader of the blocks represented by BlockData .

Accessors

$sel:currentTip:Wallet :: Wallet s -> BlockHeader Source #

Header of the latest applied block (current tip)

$sel:getState:Wallet :: Wallet s -> s Source #

Address discovery state

totalUTxO :: IsOurs s Address => Set Tx -> Wallet s -> UTxO Source #

Computes the total UTxO set of a wallet.

This total UTxO set is a projection of how the wallet's UTxO set would look if all pending transactions were applied successfully.

>>> totalUTxO pendingTxs wallet
>>> = utxo wallet
>>> − inputs pendingTxs
>>> ∪ change pendingTxs

availableUTxO :: Set Tx -> Wallet s -> UTxO Source #

Available UTxO = pending ⋪ utxo

$sel:utxo:Wallet :: Wallet s -> UTxO Source #

Unspent tx outputs belonging to this wallet

Delta Type

Internal

unsafeInitWallet Source #

Arguments

:: UTxO

Unspent tx outputs belonging to this wallet

-> BlockHeader

Header of the latest applied block (current tip)

-> s

Address discovery state

-> Wallet s

Construct a wallet from the exact given state.

Using this function instead of initWallet and applyBlock allows the wallet invariants to be broken. Therefore it should only be used in the special case of loading wallet checkpoints from the database (where it is assumed a valid wallet was stored into the database).

Exported for testing

spendTx :: Tx -> UTxO -> UTxO Source #

Remove unspent outputs that are consumed by the given transaction.

spendTx tx u isSubsetOf u
balance (spendTx tx u) <= balance u
balance (spendTx tx u) = balance u - balance (u restrictedBy inputs tx)
spendTx tx u = u excluding inputs tx
spendTx tx (filterByAddress f u) = filterByAddress f (spendTx tx u)
spendTx tx (u <> utxoFromTx tx) = spendTx tx u <> utxoFromTx tx

utxoFromTx :: Tx -> UTxO Source #

Generates a UTxO set from a transaction.

The generated UTxO set corresponds to the value provided by the transaction.

It is important for transaction outputs to be ordered correctly, as their indices within this ordering will determine how they are referenced as transaction inputs in subsequent blocks.

Assuming the transaction is not marked as having an invalid script, the following properties should hold:

balance (utxoFromTx tx) == foldMap tokens (outputs tx)
size    (utxoFromTx tx) == length         (outputs tx)
toList  (utxoFromTx tx) == toList         (outputs tx)

However, if the transaction is marked as having an invalid script, then the following properties should hold:

balance (utxoFromTx tx) == foldMap tokens (collateralOutput tx)
size    (utxoFromTx tx) == length         (collateralOutput tx)
toList  (utxoFromTx tx) == toList         (collateralOutput tx)

utxoFromTxOutputs :: Tx -> UTxO Source #

Generates a UTxO set from the ordinary outputs of a transaction.

This function ignores the transaction's script validity.

utxoFromTxCollateralOutputs :: Tx -> UTxO Source #

Generates a UTxO set from the collateral outputs of a transaction.

This function ignores the transaction's script validity.

applyTxToUTxO :: Tx -> UTxO -> UTxO Source #

Applies a transaction to a UTxO, moving it from one state to another.

When applying a transaction to a UTxO: 1. We need to remove any unspents that have been spent in the transaction. 2. Add any unspents that we've received via the transaction. In this function, we assume that all outputs belong to us.

Properties:

balance (applyTxToUTxO tx u) = balance u
                             + balance (utxoFromTx tx)
                             - balance (u restrictedBy inputs tx)
unUTxO (applyTxToUTxO tx u) = unUTxO u
    union unUTxO (utxoFromTx tx)
    difference unUTxO (u restrictedBy inputs tx)
applyTxToUTxO tx u = spend tx u <> utxoFromTx tx
applyTxToUTxO tx u = spend tx (u <> utxoFromTx tx)

applyOurTxToUTxO :: ( IsOurs s Address , IsOurs s RewardAccount ) => Slot -> Quantity "block" Word32 -> s -> Tx -> UTxO -> Maybe (( Tx , TxMeta ), DeltaUTxO , UTxO ) Source #

Apply the given transaction to the UTxO . Return Just if and only if the transaction is relevant to the wallet (changes the UTxO set or makes a withdrawal).

It satisfies the following property:

isJust (applyOurTxToUTxO slot bh state1 tx u) = b
  where (b, state1) = runState (isOurTx tx u) state0

changeUTxO :: IsOurs s Address => Set Tx -> s -> UTxO Source #

Retrieve the change UTxO contained in a set of pending transactions.

We perform some address discovery within the list of pending addresses, but we do not store the result. Instead, we essentially assume that the address discovery state s contains enough information to collect the change addresses in the pending transactions.

Caveats: * Rollbacks can invalidate this assumption. 🙈 * The order of pending transactions is based on transaction hashes, and typically does not agree with the order in which we have submitted them onto the chain. Hence, the address discovery phase is not really very effective. TODO: Add slot to Tx and sort the pending set by slot.

discoverAddressesBlock :: ( IsOurs s Address , IsOurs s RewardAccount ) => Block -> s -> (DeltaAddressBook s, s) Source #

Perform address discovery on a Block by going through all transactions and delegation certificates in the block.

updateOurs :: IsOurs s addr => s -> addr -> s Source #

Add an address to the address discovery state, iff it belongs to us.