-
Notifications
You must be signed in to change notification settings - Fork 87
/
Copy pathSnapshot.hs
151 lines (129 loc) · 5.12 KB
/
Snapshot.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
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
module Hydra.Snapshot where
import Hydra.Prelude
import Cardano.Crypto.Util (SignableRepresentation (..))
import Codec.Serialise (serialise)
import Data.Aeson (object, withObject, (.:), (.=))
import Hydra.Cardano.Api (SigningKey)
import qualified Hydra.Contract.HeadState as Onchain
import Hydra.Crypto (HydraKey, MultiSignature, aggregate, sign)
import Hydra.Ledger (IsTx (..))
import PlutusLedgerApi.V2 (toBuiltin, toData)
import Test.QuickCheck (frequency, suchThat)
import Test.QuickCheck.Instances.Natural ()
newtype SnapshotNumber
= UnsafeSnapshotNumber Natural
deriving (Eq, Ord, Generic)
deriving newtype (Show, ToJSON, FromJSON, ToCBOR, FromCBOR, Real, Num, Enum, Integral)
instance Arbitrary SnapshotNumber where
arbitrary = UnsafeSnapshotNumber <$> arbitrary
data Snapshot tx = Snapshot
{ number :: SnapshotNumber
, utxo :: UTxOType tx
, confirmed :: [TxIdType tx]
-- ^ The set of transactions that lead to 'utxo'
}
deriving (Generic)
deriving instance IsTx tx => Eq (Snapshot tx)
deriving instance IsTx tx => Show (Snapshot tx)
instance (Arbitrary (TxIdType tx), Arbitrary (UTxOType tx)) => Arbitrary (Snapshot tx) where
arbitrary = genericArbitrary
-- NOTE: See note on 'Arbitrary (ClientInput tx)'
shrink s =
[ Snapshot (number s) utxo' confirmed'
| utxo' <- shrink (utxo s)
, confirmed' <- shrink (confirmed s)
]
-- | Binary representation of snapshot signatures
-- TODO: document CDDL format, either here or on in 'Hydra.Contract.Head.verifyPartySignature'
instance forall tx. IsTx tx => SignableRepresentation (Snapshot tx) where
getSignableRepresentation Snapshot{number, utxo} =
toStrict $
serialise (toData $ toInteger number) -- CBOR(I(integer))
<> serialise (toData . toBuiltin $ hashUTxO @tx utxo) -- CBOR(B(bytestring)
instance IsTx tx => ToJSON (Snapshot tx) where
toJSON s =
object
[ "snapshotNumber" .= number s
, "utxo" .= utxo s
, "confirmedTransactions" .= confirmed s
]
instance IsTx tx => FromJSON (Snapshot tx) where
parseJSON = withObject "Snapshot" $ \obj ->
Snapshot
<$> (obj .: "snapshotNumber")
<*> (obj .: "utxo")
<*> (obj .: "confirmedTransactions")
instance (Typeable tx, ToCBOR (UTxOType tx), ToCBOR (TxIdType tx)) => ToCBOR (Snapshot tx) where
toCBOR Snapshot{number, utxo, confirmed} =
toCBOR number <> toCBOR utxo <> toCBOR confirmed
instance (Typeable tx, FromCBOR (UTxOType tx), FromCBOR (TxIdType tx)) => FromCBOR (Snapshot tx) where
fromCBOR = Snapshot <$> fromCBOR <*> fromCBOR <*> fromCBOR
-- | A snapshot that can be used to close a head with. Either the initial one, or when it was signed by all parties, i.e. it is confirmed.
data ConfirmedSnapshot tx
= InitialSnapshot {initialUTxO :: UTxOType tx}
| ConfirmedSnapshot
{ snapshot :: Snapshot tx
, signatures :: MultiSignature (Snapshot tx)
}
deriving (Generic, Eq, Show, ToJSON, FromJSON)
-- NOTE: While we could use 'snapshot' directly, this is a record-field accessor
-- which may become partial (and lead to unnoticed runtime errors) if we ever
-- add a new branch to the sumtype. So, we explicitely define a getter which
-- will force us into thinking about changing the signature properly if this
-- happens.
-- | Safely get a 'Snapshot' from a confirmed snapshot.
getSnapshot :: ConfirmedSnapshot tx -> Snapshot tx
getSnapshot = \case
InitialSnapshot{initialUTxO} ->
Snapshot
{ number = 0
, utxo = initialUTxO
, confirmed = []
}
ConfirmedSnapshot{snapshot} -> snapshot
-- | Tell whether a snapshot is the initial snapshot coming from the collect-com
-- transaction.
isInitialSnapshot :: ConfirmedSnapshot tx -> Bool
isInitialSnapshot = \case
InitialSnapshot{} -> True
ConfirmedSnapshot{} -> False
instance IsTx tx => Arbitrary (ConfirmedSnapshot tx) where
arbitrary = do
ks <- arbitrary
utxo <- arbitrary
genConfirmedSnapshot 0 utxo ks
genConfirmedSnapshot ::
IsTx tx =>
-- | The lower bound on snapshot number to generate.
-- If this is 0, then we can generate an `InitialSnapshot` or a `ConfirmedSnapshot`.
-- Otherwise we generate only `ConfirmedSnapshot` with a number strictly superior to
-- this lower bound.
SnapshotNumber ->
UTxOType tx ->
[SigningKey HydraKey] ->
Gen (ConfirmedSnapshot tx)
genConfirmedSnapshot minSn utxo sks
| minSn > 0 = confirmedSnapshot
| otherwise =
frequency
[ (1, initialSnapshot)
, (9, confirmedSnapshot)
]
where
initialSnapshot =
pure $ InitialSnapshot utxo
confirmedSnapshot = do
-- FIXME: This is another nail in the coffin to our current modeling of
-- snapshots
number <- arbitrary `suchThat` (> minSn)
let snapshot = Snapshot{number, utxo, confirmed = []}
let signatures = aggregate $ fmap (`sign` snapshot) sks
pure $ ConfirmedSnapshot{snapshot, signatures}
fromChainSnapshot :: Onchain.SnapshotNumber -> SnapshotNumber
fromChainSnapshot onChainSnapshotNumber =
maybe
(error "Failed to convert on-chain SnapShotNumber to off-chain one.")
UnsafeSnapshotNumber
(integerToNatural onChainSnapshotNumber)