Skip to content

Commit b14da83

Browse files
authored
A more precise type for Nary.inject (#1399)
See the commit message of `consensus: a more precise type for Nary.inject`; the other commits are superficial.
2 parents 1cc9658 + 9db14f8 commit b14da83

File tree

3 files changed

+135
-47
lines changed

3 files changed

+135
-47
lines changed

ouroboros-consensus-cardano/src/unstable-cardano-testlib/Test/Consensus/Cardano/Examples.hs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ combineEras = mconcat . hcollapse . hap eraInjections
9696
-> Examples (CardanoBlock Crypto)
9797
injExamples eraName idx =
9898
prefixExamples eraName
99-
. inject exampleStartBounds idx
99+
. inject (oracularInjectionIndex exampleStartBounds idx)
100100

101101
{-------------------------------------------------------------------------------
102102
Inject instances
@@ -105,14 +105,16 @@ combineEras = mconcat . hcollapse . hap eraInjections
105105
-- | In reality, an era tag would be prepended, but we're testing that the
106106
-- encoder doesn't care what the bytes are.
107107
instance Inject Serialised where
108-
inject _ _ (Serialised _) = Serialised "<CARDANO_BLOCK>"
108+
inject _ (Serialised _) = Serialised "<CARDANO_BLOCK>"
109109

110110
instance Inject SomeResult where
111-
inject _ idx (SomeResult q r) =
112-
SomeResult (QueryIfCurrent (injectQuery idx q)) (Right r)
111+
inject iidx (SomeResult q r) =
112+
SomeResult
113+
(QueryIfCurrent (injectQuery (forgetInjectionIndex iidx) q))
114+
(Right r)
113115

114116
instance Inject Examples where
115-
inject startBounds (idx :: Index xs x) Examples {..} = Examples {
117+
inject (iidx :: InjectionIndex xs x) Examples {..} = Examples {
116118
exampleBlock = inj (Proxy @I) exampleBlock
117119
, exampleSerialisedBlock = inj (Proxy @Serialised) exampleSerialisedBlock
118120
, exampleHeader = inj (Proxy @Header) exampleHeader
@@ -137,7 +139,7 @@ instance Inject Examples where
137139
, Coercible b (f (HardForkBlock xs))
138140
)
139141
=> Proxy f -> Labelled a -> Labelled b
140-
inj p = fmap (fmap (inject' p startBounds idx))
142+
inj p = map (fmap (inject' p iidx))
141143

142144
{-------------------------------------------------------------------------------
143145
Setup
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!--
2+
A new scriv changelog fragment.
3+
4+
Uncomment the section that is right (remove the HTML comment wrapper).
5+
-->
6+
7+
<!--
8+
### Patch
9+
10+
- A bullet item for the Patch category.
11+
12+
-->
13+
<!--
14+
### Non-Breaking
15+
16+
- A bullet item for the Non-Breaking category.
17+
18+
-->
19+
20+
### Breaking
21+
22+
- Make the type of `Nary.inject` more precise.
23+
The old type involves oracular data, so any required changes downstream are almost certainly limited to testing code.
24+
(I'm therefore tempted to list this as Patch instead --- violating the [PVP](https://pvp.haskell.org/) --- but the risk-reward seems prohibitive.)

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/HardFork/Combinator/Embed/Nary.hs

Lines changed: 103 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
{-# LANGUAGE DataKinds #-}
2-
{-# LANGUAGE EmptyCase #-}
32
{-# LANGUAGE FlexibleContexts #-}
43
{-# LANGUAGE FlexibleInstances #-}
54
{-# LANGUAGE GADTs #-}
@@ -10,13 +9,17 @@
109

1110
module Ouroboros.Consensus.HardFork.Combinator.Embed.Nary (
1211
Inject (..)
12+
, InjectionIndex (InjectionIndex)
1313
, inject'
1414
-- * Defaults
1515
, injectHardForkState
1616
, injectNestedCtxt_
1717
, injectQuery
1818
-- * Initial 'ExtLedgerState'
1919
, injectInitialExtLedgerState
20+
-- * Convenience
21+
, forgetInjectionIndex
22+
, oracularInjectionIndex
2023
) where
2124

2225
import Data.Bifunctor (first)
@@ -46,9 +49,7 @@ import Ouroboros.Consensus.Util ((.:))
4649
class Inject f where
4750
inject ::
4851
forall x xs. CanHardFork xs
49-
=> Exactly xs History.Bound
50-
-- ^ Start bound of each era
51-
-> Index xs x
52+
=> InjectionIndex xs x
5253
-> f x
5354
-> f (HardForkBlock xs)
5455

@@ -59,8 +60,57 @@ inject' ::
5960
, Coercible a (f x)
6061
, Coercible b (f (HardForkBlock xs))
6162
)
62-
=> Proxy f -> Exactly xs History.Bound -> Index xs x -> a -> b
63-
inject' _ startBounds idx = coerce . inject @f startBounds idx . coerce
63+
=> Proxy f -> InjectionIndex xs x -> a -> b
64+
inject' _ iidx = coerce . inject @f iidx . coerce
65+
66+
{-------------------------------------------------------------------------------
67+
InjectionIndex
68+
-------------------------------------------------------------------------------}
69+
70+
-- | This data type is isomorphic to an 'Index' that additionally provides a
71+
-- 'History.Bound' for every era up to and including that index, but none of
72+
-- the subsequent eras.
73+
newtype InjectionIndex xs x =
74+
InjectionIndex (Telescope (K History.Bound) (State.Current ((:~:) x)) xs)
75+
76+
-- | Many instances of 'Inject' do not need the 'History.Bound's, eg those that
77+
-- do not construct 'HardForkState's
78+
forgetInjectionIndex :: InjectionIndex xs x -> Index xs x
79+
forgetInjectionIndex (InjectionIndex tele) = case tele of
80+
TZ (State.Current _k Refl) -> IZ
81+
TS _k tele' ->
82+
IS (forgetInjectionIndex (InjectionIndex tele'))
83+
84+
-- | Build an 'InjectionIndex' from oracular 'History.Bound's and an 'Index'
85+
--
86+
-- This bounds data is oracular, since the later eras in @xs@ might have not
87+
-- yet started. However, it can be known in test code.
88+
--
89+
-- INVARIANT: the result is completely independent of the 'history.Bound's for
90+
-- eras /after/ the given 'Index'.
91+
oracularInjectionIndex ::
92+
Exactly xs History.Bound
93+
-> Index xs x
94+
-> InjectionIndex xs x
95+
oracularInjectionIndex (Exactly np) idx = case (idx, np) of
96+
(IZ , K start :* _ ) ->
97+
InjectionIndex
98+
$ TZ State.Current { currentStart = start, currentState = Refl }
99+
(IS idx', kstart :* kstarts) ->
100+
let InjectionIndex iidx =
101+
oracularInjectionIndex (Exactly kstarts) idx'
102+
in
103+
InjectionIndex (TS kstart iidx)
104+
105+
-- | NOT EXPORTED
106+
--
107+
-- The first bound in a telescope. This is used in an inductive alternative
108+
-- within 'injectHardForkState', since the end of one era is the start of the
109+
-- next.
110+
firstBound :: InjectionIndex xs x -> History.Bound
111+
firstBound (InjectionIndex tele) = case tele of
112+
TZ State.Current {currentStart = start} -> start
113+
TS (K start) _tele' -> start
64114

65115
{-------------------------------------------------------------------------------
66116
Defaults (to ease implementation)
@@ -86,88 +136,97 @@ injectQuery idx q = case idx of
86136

87137
injectHardForkState ::
88138
forall f x xs.
89-
Exactly xs History.Bound
90-
-- ^ Start bound of each era
91-
-> Index xs x
139+
InjectionIndex xs x
92140
-> f x
93141
-> HardForkState f xs
94-
injectHardForkState startBounds idx x =
95-
HardForkState $ go startBounds idx
142+
injectHardForkState iidx x =
143+
HardForkState $ go iidx
96144
where
97145
go ::
98-
Exactly xs' History.Bound
99-
-> Index xs' x
146+
InjectionIndex xs' x
100147
-> Telescope (K State.Past) (State.Current f) xs'
101-
go (ExactlyCons start _) IZ =
102-
TZ (State.Current { currentStart = start, currentState = x })
103-
go (ExactlyCons start startBounds'@(ExactlyCons nextStart _)) (IS idx') =
104-
TS (K State.Past { pastStart = start, pastEnd = nextStart })
105-
(go startBounds' idx')
106-
go (ExactlyCons _ ExactlyNil) (IS idx') = case idx' of {}
107-
go ExactlyNil idx' = case idx' of {}
148+
go (InjectionIndex (TZ current@State.Current { currentState = Refl })) =
149+
TZ
150+
current { State.currentState = x }
151+
go (InjectionIndex (TS (K start) tele)) =
152+
TS
153+
(K State.Past {
154+
pastStart = start
155+
, pastEnd = firstBound (InjectionIndex tele)
156+
}
157+
)
158+
(go (InjectionIndex tele))
108159

109160
{-------------------------------------------------------------------------------
110161
Instances
111162
-------------------------------------------------------------------------------}
112163

113164
instance Inject I where
114-
inject _ = injectNS' (Proxy @I)
165+
inject = injectNS' (Proxy @I) . forgetInjectionIndex
115166

116167
instance Inject Header where
117-
inject _ = injectNS' (Proxy @Header)
168+
inject = injectNS' (Proxy @Header) . forgetInjectionIndex
118169

119170
instance Inject SerialisedHeader where
120-
inject _ idx =
171+
inject iidx =
121172
serialisedHeaderFromPair
122173
. first (mapSomeNestedCtxt (injectNestedCtxt_ idx))
123174
. serialisedHeaderToPair
175+
where
176+
idx = forgetInjectionIndex iidx
124177

125178
instance Inject WrapHeaderHash where
126-
inject _ (idx :: Index xs x) =
179+
inject (iidx :: InjectionIndex xs x) =
127180
case dictIndexAll (Proxy @SingleEraBlock) idx of
128181
Dict ->
129182
WrapHeaderHash
130183
. OneEraHash
131184
. toShortRawHash (Proxy @x)
132185
. unwrapHeaderHash
186+
where
187+
idx = forgetInjectionIndex iidx
133188

134189
instance Inject GenTx where
135-
inject _ = injectNS' (Proxy @GenTx)
190+
inject = injectNS' (Proxy @GenTx) . forgetInjectionIndex
136191

137192
instance Inject WrapGenTxId where
138-
inject _ = injectNS' (Proxy @WrapGenTxId)
193+
inject = injectNS' (Proxy @WrapGenTxId) . forgetInjectionIndex
139194

140195
instance Inject WrapApplyTxErr where
141-
inject _ =
142-
(WrapApplyTxErr . HardForkApplyTxErrFromEra)
143-
.: injectNS' (Proxy @WrapApplyTxErr)
196+
inject =
197+
( (WrapApplyTxErr . HardForkApplyTxErrFromEra)
198+
.: injectNS' (Proxy @WrapApplyTxErr)
199+
)
200+
. forgetInjectionIndex
144201

145202
instance Inject (SomeSecond BlockQuery) where
146-
inject _ idx (SomeSecond q) = SomeSecond (QueryIfCurrent (injectQuery idx q))
203+
inject iidx (SomeSecond q) =
204+
SomeSecond (QueryIfCurrent (injectQuery idx q))
205+
where
206+
idx = forgetInjectionIndex iidx
147207

148208
instance Inject AnnTip where
149-
inject _ = undistribAnnTip .: injectNS' (Proxy @AnnTip)
209+
inject =
210+
(undistribAnnTip .: injectNS' (Proxy @AnnTip)) . forgetInjectionIndex
150211

151212
instance Inject LedgerState where
152-
inject startBounds idx =
153-
HardForkLedgerState . injectHardForkState startBounds idx
213+
inject = HardForkLedgerState .: injectHardForkState
154214

155215
instance Inject WrapChainDepState where
156-
inject startBounds idx =
157-
coerce . injectHardForkState startBounds idx
216+
inject = coerce .: injectHardForkState
158217

159218
instance Inject HeaderState where
160-
inject startBounds idx HeaderState {..} = HeaderState {
161-
headerStateTip = inject startBounds idx <$> headerStateTip
219+
inject iidx HeaderState {..} = HeaderState {
220+
headerStateTip = inject iidx <$> headerStateTip
162221
, headerStateChainDep = unwrapChainDepState
163-
$ inject startBounds idx
222+
$ inject iidx
164223
$ WrapChainDepState headerStateChainDep
165224
}
166225

167226
instance Inject ExtLedgerState where
168-
inject startBounds idx ExtLedgerState {..} = ExtLedgerState {
169-
ledgerState = inject startBounds idx ledgerState
170-
, headerState = inject startBounds idx headerState
227+
inject iidx ExtLedgerState {..} = ExtLedgerState {
228+
ledgerState = inject iidx ledgerState
229+
, headerState = inject iidx headerState
171230
}
172231

173232
{-------------------------------------------------------------------------------
@@ -184,6 +243,9 @@ instance Inject ExtLedgerState where
184243
-- extending 'applyChainTick' to translate across more than one era is not
185244
-- problematic, but extending 'ledgerViewForecastAt' is a lot more subtle; see
186245
-- @forecastNotFinal@.
246+
--
247+
-- Note: this function is an /alternative/ to the 'Inject' class above. It does
248+
-- not rely on that class.
187249
injectInitialExtLedgerState ::
188250
forall x xs. CanHardFork (x ': xs)
189251
=> TopLevelConfig (HardForkBlock (x ': xs))

0 commit comments

Comments
 (0)