-
Notifications
You must be signed in to change notification settings - Fork 217
/
Primitive.hs
282 lines (221 loc) · 6.3 KB
/
Primitive.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
-- |
-- Copyright: © 2018-2019 IOHK
-- License: MIT
--
-- This module contains the core primitive of a Wallet. This is roughly a
-- Haskell translation of the [Formal Specification for a Cardano Wallet](https://github.com/input-output-hk/cardano-wallet/blob/master/specifications/wallet/formal-specification-for-a-cardano-wallet.pdf)
--
-- It doesn't contain any particular business-logic code, but define a few
-- primitive operations on Wallet core types as well.
module Cardano.Wallet.Primitive
(
-- * Block
Block(..)
, BlockHeader(..)
-- * Epoch
, EpochId (..)
-- * Slot
, SlotId (..)
-- * Tx
, Tx(..)
, TxIn(..)
, TxOut(..)
, txIns
, txOutsOurs
, updatePending
-- * Address
, Address (..)
, IsOurs(..)
-- * Coin
, Coin (..)
, isValidCoin
-- * UTxO
, UTxO (..)
, balance
, excluding
, isSubsetOf
, restrictedBy
, restrictedTo
, Dom(..)
-- * Generic
, Hash (..)
) where
import Prelude
import Control.DeepSeq
( NFData (..) )
import Control.Monad.Trans.State.Strict
( State, runState, state )
import Data.ByteString
( ByteString )
import Data.Map.Strict
( Map )
import Data.Maybe
( catMaybes )
import Data.Set
( Set )
import Data.Traversable
( for )
import Data.Word
( Word16, Word32, Word64 )
import GHC.Generics
( Generic )
import GHC.TypeLits
( Symbol )
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
-- * Epoch
newtype EpochId = EpochId
{ getEpochId :: Word64
} deriving (Eq, Generic, NFData, Num, Ord, Show)
-- * Slot
newtype SlotId = SlotId
{ getSlotId :: Word16
} deriving (Eq, Generic, NFData, Num, Ord, Show)
-- * Block
data Block = Block
{ header
:: !BlockHeader
, transactions
:: !(Set Tx)
} deriving (Show, Eq, Ord, Generic)
instance NFData Block
data BlockHeader = BlockHeader
{ epochIndex
:: !EpochId
, slotNumber
:: !SlotId
, prevBlockHash
:: !(Hash "BlockHeader")
} deriving (Show, Eq, Ord, Generic)
instance NFData BlockHeader
-- * Tx
data Tx = Tx
{ inputs
:: ![TxIn]
-- ^ Order of inputs matters in the transaction representation. The
-- transaction id is computed from the binary representation of a tx,
-- for which inputs are serialized in a specific order.
, outputs
:: ![TxOut]
-- ^ Order of outputs matter in the transaction representations. Outputs
-- are used as inputs for next transactions which refer to them using
-- their indexes. It matters also for serialization.
} deriving (Show, Generic, Ord, Eq)
instance NFData Tx
txIns :: Set Tx -> Set TxIn
txIns =
foldMap (Set.fromList . inputs)
txOutsOurs
:: forall s. (IsOurs s)
=> Set Tx
-> s
-> (Set TxOut, s)
txOutsOurs txs =
runState $ Set.fromList <$> forMaybe (foldMap outputs txs) pick
where
pick :: TxOut -> State s (Maybe TxOut)
pick out = do
predicate <- state $ isOurs (address out)
return $ if predicate then Just out else Nothing
forMaybe :: Monad m => [a] -> (a -> m (Maybe b)) -> m [b]
forMaybe xs = fmap catMaybes . for xs
updatePending :: Block -> Set Tx -> Set Tx
updatePending b =
let
isStillPending ins =
Set.null . Set.intersection ins . Set.fromList . inputs
in
Set.filter (isStillPending (txIns $ transactions b))
data TxIn = TxIn
{ inputId
:: !(Hash "Tx")
, inputIx
:: !Word32
} deriving (Show, Generic, Eq, Ord)
instance NFData TxIn
data TxOut = TxOut
{ address
:: !Address
, coin
:: !Coin
} deriving (Show, Generic, Eq, Ord)
instance NFData TxOut
-- * Address
newtype Address = Address
{ getAddress :: ByteString
} deriving (Show, Generic, Eq, Ord)
instance NFData Address
-- | This abstraction exists to give us the ability to keep the wallet business
-- logic agnostic to the address derivation and discovery mechanisms.
--
-- This is needed because two different address schemes lives on Cardano:
-- - A hierarchical random scheme:
-- rather 'custom' made, with several flaws; this is the original and now
-- legacy address scheme.
--
-- - A hierarchical sequential scheme:
-- a new scheme based on the BIP-0044 specification, which is better suited
-- for our present needs.
--
-- In practice, we will need a wallet that can support both, even if not at the
-- same time, and this little abstraction can buy us this without introducing
-- too much overhead.
class IsOurs s where
isOurs :: Address -> s -> (Bool, s)
-- * Coin
-- | Coins are stored as Lovelace (reminder: 1 Lovelace = 1e6 ADA)
newtype Coin = Coin
{ getCoin :: Word64
} deriving stock (Show, Ord, Eq, Generic)
instance NFData Coin
instance Bounded Coin where
minBound = Coin 0
maxBound = Coin 45000000000000000
isValidCoin :: Coin -> Bool
isValidCoin c = c >= minBound && c <= maxBound
-- * UTxO
newtype UTxO = UTxO { getUTxO :: Map TxIn TxOut }
deriving stock (Show, Generic, Eq, Ord)
deriving newtype (Semigroup, Monoid)
instance NFData UTxO
instance Dom UTxO where
type DomElem UTxO = TxIn
dom (UTxO utxo) = Map.keysSet utxo
balance :: UTxO -> Integer
balance =
Map.foldl' fn 0 . getUTxO
where
fn :: Integer -> TxOut -> Integer
fn total out = total + fromIntegral (getCoin (coin out))
-- ins⋪ u
excluding :: UTxO -> Set TxIn -> UTxO
excluding (UTxO utxo) =
UTxO . Map.withoutKeys utxo
-- a ⊆ b
isSubsetOf :: UTxO -> UTxO -> Bool
isSubsetOf (UTxO a) (UTxO b) =
a `Map.isSubmapOf` b
-- ins⊲ u
restrictedBy :: UTxO -> Set TxIn -> UTxO
restrictedBy (UTxO utxo) =
UTxO . Map.restrictKeys utxo
-- u ⊳ outs
restrictedTo :: UTxO -> Set TxOut -> UTxO
restrictedTo (UTxO utxo) outs =
UTxO $ Map.filter (`Set.member` outs) utxo
-- * Generic
class Dom a where
type DomElem a :: *
dom :: a -> Set (DomElem a)
newtype Hash (tag :: Symbol) = Hash
{ getHash :: ByteString
} deriving (Show, Generic, Eq, Ord)
instance NFData (Hash tag)