Copyright | © 2018-2020 IOHK |
---|---|
License | Apache-2.0 |
Safe Haskell | None |
Language | Haskell2010 |
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
- data Wallet s
- initWallet :: ( IsOurs s Address , IsOurs s RewardAccount ) => Block -> s -> ([( Tx , TxMeta )], Wallet s)
- updateState :: s -> Wallet s -> Wallet s
-
data
FilteredBlock
=
FilteredBlock
{
- slot :: ! Slot
- transactions :: ![( Tx , TxMeta )]
- delegations :: ![ DelegationCertificate ]
- applyBlock :: ( IsOurs s Address , IsOurs s RewardAccount ) => Block -> Wallet s -> ( FilteredBlock , ( DeltaWallet s, Wallet s))
- 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)))
- applyBlockData :: ( IsOurs s Address , IsOurs s RewardAccount , Monad m) => BlockData m ( Either Address RewardAccount ) ChainEvents s -> Wallet s -> m ([ FilteredBlock ], ( DeltaWallet s, Wallet s))
-
data
BlockData
m addr tx s
- = List ( NonEmpty Block )
- | Summary ( DiscoverTxs addr tx s) ( BlockSummary m addr tx)
- firstHeader :: BlockData m addr txs s -> BlockHeader
- lastHeader :: BlockData m addr txs s -> BlockHeader
- $sel:currentTip:Wallet :: Wallet s -> BlockHeader
- $sel:getState:Wallet :: Wallet s -> s
- availableBalance :: Set Tx -> Wallet s -> TokenBundle
- totalBalance :: ( IsOurs s Address , IsOurs s RewardAccount ) => Set Tx -> Coin -> Wallet s -> TokenBundle
- totalUTxO :: IsOurs s Address => Set Tx -> Wallet s -> UTxO
- availableUTxO :: Set Tx -> Wallet s -> UTxO
- $sel:utxo:Wallet :: Wallet s -> UTxO
- data DeltaWallet s
- unsafeInitWallet :: UTxO -> BlockHeader -> s -> Wallet s
- spendTx :: Tx -> UTxO -> UTxO
- utxoFromTx :: Tx -> UTxO
- utxoFromTxOutputs :: Tx -> UTxO
- utxoFromTxCollateralOutputs :: Tx -> UTxO
- applyTxToUTxO :: Tx -> UTxO -> UTxO
- applyOurTxToUTxO :: ( IsOurs s Address , IsOurs s RewardAccount ) => Slot -> Quantity "block" Word32 -> s -> Tx -> UTxO -> Maybe (( Tx , TxMeta ), DeltaUTxO , UTxO )
- changeUTxO :: IsOurs s Address => Set Tx -> s -> UTxO
- discoverAddressesBlock :: ( IsOurs s Address , IsOurs s RewardAccount ) => Block -> s -> (DeltaAddressBook s, s)
- discoverFromBlockData :: ( IsOurs s Address , IsOurs s RewardAccount , Monad m) => BlockData m ( Either Address RewardAccount ) ChainEvents s -> s -> m ( ChainEvents , s)
- updateOurs :: IsOurs s addr => s -> addr -> s
Type
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 theIsOurs
class, e.g.IsOurs
sAddress
A few examples to make it concrete:
Wallet (RndState k n) Wallet (SeqState n ShelleyKey)
Instances
Eq s => Eq ( Wallet s) Source # | |
Show s => Show ( Wallet s) Source # | |
Generic ( Wallet s) Source # | |
NFData s => NFData ( Wallet s) Source # | |
Defined in Cardano.Wallet.Primitive.Model |
|
Buildable s => Buildable ( Wallet s) Source # | |
type Rep ( Wallet s) Source # | |
Defined in Cardano.Wallet.Primitive.Model
type
Rep
(
Wallet
s) =
D1
('
MetaData
"Wallet" "Cardano.Wallet.Primitive.Model" "cardano-wallet-core-2022.7.1-AGKhlyz9liLKN3QqZD1gj" '
False
) (
C1
('
MetaCons
"Wallet" '
PrefixI
'
True
) (
S1
('
MetaSel
('
Just
"utxo") '
NoSourceUnpackedness
'
NoSourceStrictness
'
DecidedLazy
) (
Rec0
UTxO
)
:*:
(
S1
('
MetaSel
('
Just
"currentTip") '
NoSourceUnpackedness
'
NoSourceStrictness
'
DecidedLazy
) (
Rec0
BlockHeader
)
:*:
S1
('
MetaSel
('
Just
"getState") '
NoSourceUnpackedness
'
NoSourceStrictness
'
DecidedLazy
) (
Rec0
s))))
|
Construction & Modification
:: ( 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.
FilteredBlock | |
|
Instances
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 blockbj
that were actually applied to the wallet state. -
wi + bj
refers to the wallet state obtained after applying the blockbi
to the walletwj
. -
delta wi -> wj
refers to the delta that was applied in order to obtain
wjfrom
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.
List ( NonEmpty Block ) | |
Summary ( DiscoverTxs addr tx s) ( BlockSummary m addr tx) |
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
availableBalance :: Set Tx -> Wallet s -> TokenBundle Source #
Available balance =
balance
.
availableUTxO
totalBalance :: ( IsOurs s Address , IsOurs s RewardAccount ) => Set Tx -> Coin -> Wallet s -> TokenBundle Source #
$sel:utxo:Wallet :: Wallet s -> UTxO Source #
Unspent tx outputs belonging to this wallet
Delta Type
data DeltaWallet s Source #
Delta encoding for
Wallet
.
Instances
Show s => Show ( DeltaWallet s) Source # | |
Defined in Cardano.Wallet.Primitive.Model |
|
Delta ( DeltaWallet s) Source # | |
Defined in Cardano.Wallet.Primitive.Model type Base ( DeltaWallet s) Source # apply :: DeltaWallet s -> Base ( DeltaWallet s) -> Base ( DeltaWallet s) Source # |
|
type Base ( DeltaWallet s) Source # | |
Defined in Cardano.Wallet.Primitive.Model |
Internal
:: 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 uisSubsetOf
u balance (spendTx tx u) <= balance u balance (spendTx tx u) = balance u - balance (urestrictedBy
inputs tx) spendTx tx u = uexcluding
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 (urestrictedBy
inputs tx) unUTxO (applyTxToUTxO tx u) = unUTxO uunion
unUTxO (utxoFromTx tx)difference
unUTxO (urestrictedBy
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 #
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.
discoverFromBlockData :: ( IsOurs s Address , IsOurs s RewardAccount , Monad m) => BlockData m ( Either Address RewardAccount ) ChainEvents s -> s -> m ( ChainEvents , s) Source #
Perform address and transaction discovery on
BlockData
,
updateOurs :: IsOurs s addr => s -> addr -> s Source #
Add an address to the address discovery state, iff it belongs to us.