This repository was archived by the owner on Aug 18, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 631
/
Copy pathIntegrity.hs
354 lines (329 loc) · 14 KB
/
Integrity.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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
{-# LANGUAGE TypeFamilies #-}
-- | Verification of headers and blocks, also chain integrity
-- checks. Almost pure (requires leaders to be explicitly passed).
module Pos.Chain.Block.Logic.Integrity
(
-- * Header
VerifyHeaderParams (..)
, verifyHeader
, verifyHeaders
-- * Block
, VerifyBlockParams (..)
, verifyBlock
, verifyBlocks
) where
import Universum
import Control.Lens (ix)
import Formatting (build, int, sformat, (%))
import Serokell.Data.Memory.Units (Byte, memory)
import Serokell.Util (VerificationRes (..), verifyGeneric)
import qualified Pos.Binary.Class as Bi
import qualified Pos.Chain.Block.BHelpers as BHelpers
import Pos.Chain.Block.Blockchain (gbExtra, gbhExtra)
import Pos.Chain.Block.Genesis (gebAttributes, gehAttributes,
genBlockLeaders)
import Pos.Chain.Block.Union (Block, BlockHeader (..),
HasHeaderHash (..), HeaderHash, blockHeaderProtocolMagic,
getBlockHeader, headerSlotL, mainHeaderLeaderKey,
mebAttributes, mehAttributes, prevBlockL)
import Pos.Core (ChainDifficulty, EpochOrSlot, HasDifficulty (..),
HasEpochIndex (..), HasEpochOrSlot (..),
HasProtocolConstants, SlotId (..), SlotLeaders,
addressHash, getSlotIndex)
import Pos.Core.Attributes (areAttributesKnown)
import Pos.Core.Chrono (NewestFirst (..), OldestFirst)
import Pos.Core.Slotting (EpochIndex)
import Pos.Core.Update (BlockVersionData (..))
import Pos.Crypto (ProtocolMagic (..))
----------------------------------------------------------------------------
-- Header
----------------------------------------------------------------------------
-- Difficulty of the BlockHeader. 0 for genesis block, 1 for main block.
headerDifficultyIncrement :: BlockHeader -> ChainDifficulty
headerDifficultyIncrement (BlockHeaderGenesis _) = 0
headerDifficultyIncrement (BlockHeaderMain _) = 1
-- | Extra data which may be used by verifyHeader function to do more checks.
data VerifyHeaderParams = VerifyHeaderParams
{ vhpPrevHeader :: !(Maybe BlockHeader)
-- ^ Nothing means that block is unknown, not genesis.
, vhpCurrentSlot :: !(Maybe SlotId)
-- ^ Current slot is used to check whether header is not from future.
, vhpLeaders :: !(Maybe SlotLeaders)
-- ^ Set of leaders for the epoch related block is from.
, vhpMaxSize :: !(Maybe Byte)
-- ^ Maximal allowed header size. It's applied to 'BlockHeader'.
, vhpVerifyNoUnknown :: !Bool
-- ^ Check that header has no unknown attributes.
} deriving (Eq, Show, Generic)
instance NFData VerifyHeaderParams
verifyFromEither :: Text -> Either Text b -> VerificationRes
verifyFromEither txt (Left reason) = verifyGeneric [(False, txt <> ": " <> reason)]
verifyFromEither txt (Right _) = verifyGeneric [(True, txt)]
-- CHECK: @verifyHeader
-- | Check some predicates (determined by 'VerifyHeaderParams') about
-- 'BlockHeader'.
--
-- Supported checks:
-- 1. Checks with respect to the preceding block:
-- 1. If the new block is a genesis one, difficulty does not increase.
-- Otherwise, it increases by one.
-- 2. Hashing the preceding block's header yields the same value as the one
-- stored in the new block's header.
-- 3. Corresponding `EpochOrSlot`s strictly increase.
-- 4. If the new block is a main one, its epoch is equal to the epoch of the
-- preceding block.
-- 2. The block's slot does not exceed the current slot.
-- 3. The block's leader is expected (matches either the corresponding leader from
-- the initial leaders or a leader from one of the preceding genesis blocks).
-- 4. Header size does not exceed `bvdMaxHeaderSize`.
-- 5. (Optional) Header has no unknown attributes.
verifyHeader
:: ProtocolMagic -> VerifyHeaderParams -> BlockHeader -> VerificationRes
verifyHeader pm VerifyHeaderParams {..} h =
verifyFromEither "internal header consistency" (BHelpers.verifyBlockHeader pm h)
<> verifyGeneric checks
where
checks =
mconcat
[ checkProtocolMagic
, maybe mempty relatedToPrevHeader vhpPrevHeader
, maybe mempty relatedToCurrentSlot vhpCurrentSlot
, maybe mempty relatedToLeaders vhpLeaders
, checkSize
, bool mempty (verifyNoUnknown h) vhpVerifyNoUnknown
]
checkHash :: HeaderHash -> HeaderHash -> (Bool, Text)
checkHash expectedHash actualHash =
( expectedHash == actualHash
, sformat
("inconsistent hash (expected "%build%", found "%build%")")
expectedHash
actualHash)
checkDifficulty :: ChainDifficulty -> ChainDifficulty -> (Bool, Text)
checkDifficulty expectedDifficulty actualDifficulty =
( expectedDifficulty == actualDifficulty
, sformat
("incorrect difficulty (expected "%int%", found "%int%")")
expectedDifficulty
actualDifficulty)
checkEpochOrSlot :: EpochOrSlot -> EpochOrSlot -> (Bool, Text)
checkEpochOrSlot oldEOS newEOS =
( oldEOS < newEOS
, sformat
("slots are not monotonic ("%build%" >= "%build%")")
oldEOS newEOS
)
sameEpoch :: EpochIndex -> EpochIndex -> (Bool, Text)
sameEpoch oldEpoch newEpoch =
( oldEpoch == newEpoch
, sformat
("two adjacent blocks are from different epochs ("%build%" != "%build%")")
oldEpoch newEpoch
)
checkProtocolMagic =
[ ( pm == blockHeaderProtocolMagic h
, sformat
("protocol magic number mismatch: got "%int%" but expected "%int)
(getProtocolMagic (blockHeaderProtocolMagic h))
(getProtocolMagic pm)
)
]
checkSize =
case vhpMaxSize of
Nothing -> mempty
-- FIXME do not use 'biSize'! It's expensive.
Just maxSize ->
[ ( Bi.biSize h <= maxSize
, sformat
("header's size exceeds limit ("%memory%" > "%memory%")")
(Bi.biSize h)
maxSize)
]
-- CHECK: Performs checks related to the previous header:
--
-- * Difficulty is correct.
-- * Hash is correct.
-- * Epoch/slot are consistent.
relatedToPrevHeader :: BlockHeader -> [(Bool, Text)]
relatedToPrevHeader prevHeader =
[ checkDifficulty
(prevHeader ^. difficultyL + headerDifficultyIncrement h)
(h ^. difficultyL)
, checkHash
(headerHash prevHeader)
(h ^. prevBlockL)
, checkEpochOrSlot (getEpochOrSlot prevHeader) (getEpochOrSlot h)
, case h of
BlockHeaderGenesis _ -> (True, "") -- check that epochId prevHeader < epochId h performed above
BlockHeaderMain _ -> sameEpoch (prevHeader ^. epochIndexL) (h ^. epochIndexL)
]
-- CHECK: Verifies that the slot does not lie in the future.
relatedToCurrentSlot :: SlotId -> [(Bool, Text)]
relatedToCurrentSlot curSlotId =
case h of
BlockHeaderGenesis _ -> [(True, "block is from slot which hasn't happened yet")]
BlockHeaderMain bh ->
[
( (bh ^. headerSlotL) <= curSlotId
, sformat ("block is from slot "%build%" which hasn't happened yet (current slot "%build%")") (bh ^. headerSlotL) curSlotId
)
]
-- CHECK: Checks that the block leader is the expected one.
relatedToLeaders leaders =
case h of
BlockHeaderGenesis _ -> []
BlockHeaderMain mainHeader ->
[ ( (Just (addressHash $ mainHeader ^. mainHeaderLeaderKey) ==
leaders ^?
ix (fromIntegral $ getSlotIndex $
siSlot $ mainHeader ^. headerSlotL))
, "block's leader is different from expected one")
]
verifyNoUnknown (BlockHeaderGenesis genH) =
let attrs = genH ^. gbhExtra . gehAttributes
in [ ( areAttributesKnown attrs
, sformat ("genesis header has unknown attributes: "%build) attrs)
]
verifyNoUnknown (BlockHeaderMain mainH) =
let attrs = mainH ^. gbhExtra . mehAttributes
in [ ( areAttributesKnown attrs
, sformat ("main header has unknown attributes: "%build) attrs)
]
-- | Verifies a set of block headers. Only basic consensus check and
-- linking checks are performed!
verifyHeaders ::
ProtocolMagic
-> Maybe SlotLeaders
-> NewestFirst [] BlockHeader
-> VerificationRes
verifyHeaders _ _ (NewestFirst []) = mempty
verifyHeaders pm leaders (NewestFirst (headers@(_:xh))) =
snd $
foldr foldFoo (leaders,mempty) $ headers `zip` (map Just xh ++ [Nothing])
where
foldFoo (cur,prev) (prevLeaders,res) =
let curLeaders = case cur of
-- we don't know leaders for the next epoch
BlockHeaderGenesis _ -> Nothing
_ -> prevLeaders
in (curLeaders, verifyHeader pm (toVHP curLeaders prev) cur <> res)
toVHP l p =
VerifyHeaderParams
{ vhpPrevHeader = p
, vhpCurrentSlot = Nothing
, vhpLeaders = l
, vhpMaxSize = Nothing
, vhpVerifyNoUnknown = False
}
----------------------------------------------------------------------------
-- Block
----------------------------------------------------------------------------
-- | Parameters of Block static verification. This type contains all data
-- necessary for verification of a single block.
-- Note: to check that block references previous block and/or is referenced
-- by next block, use header verification (via vbpVerifyHeader).
data VerifyBlockParams = VerifyBlockParams
{ vbpVerifyHeader :: !VerifyHeaderParams
-- ^ Verifies header accordingly to params ('verifyHeader')
, vbpMaxSize :: !Byte
-- ^ Maximal block size. This value limit size of 'Block' (which
-- is either main or genesis block).
, vbpVerifyNoUnknown :: !Bool
-- ^ Check that block has no unknown attributes.
} deriving (Generic)
instance NFData VerifyBlockParams
-- CHECK: @verifyBlock
-- | Check predicates defined by VerifyBlockParams.
-- #verifyHeader
--
-- Supported checks:
--
-- 1. All checks related to the header.
-- 2. The size of each block does not exceed `bvdMaxBlockSize`.
-- 3. (Optional) No block has any unknown attributes.
verifyBlock
:: HasProtocolConstants
=> ProtocolMagic
-> VerifyBlockParams
-> Block
-> VerificationRes
verifyBlock pm VerifyBlockParams {..} blk = mconcat
[ verifyFromEither "internal block consistency" (BHelpers.verifyBlock pm blk)
, verifyHeader pm vbpVerifyHeader (getBlockHeader blk)
, checkSize vbpMaxSize
, bool mempty (verifyNoUnknown blk) vbpVerifyNoUnknown
]
where
-- Oh no! Verification involves re-searilizing the thing!
-- What a tragic waste.
-- What shall we do about this?
blkSize = Bi.biSize blk
checkSize maxSize = verifyGeneric [
(blkSize <= maxSize,
sformat ("block's size exceeds limit ("%memory%" > "%memory%")")
blkSize maxSize)
]
verifyNoUnknown (Left genBlk) =
let attrs = genBlk ^. gbExtra . gebAttributes
in verifyGeneric
[ ( areAttributesKnown attrs
, sformat ("genesis block has unknown attributes: "%build) attrs)
]
verifyNoUnknown (Right mainBlk) =
let attrs = mainBlk ^. gbExtra . mebAttributes
in verifyGeneric
[ ( areAttributesKnown attrs
, sformat ("main block has unknown attributes: "%build) attrs)
]
-- Type alias for the fold accumulator used inside 'verifyBlocks'
type VerifyBlocksIter = (SlotLeaders, Maybe BlockHeader, VerificationRes)
-- CHECK: @verifyBlocks
-- Verifies a sequence of blocks.
-- #verifyBlock
-- | Verify a sequence of blocks.
--
-- Block verification consists of header verification and body verification.
-- See 'verifyHeader' and 'verifyBlock' for more information.
--
-- foldl' is used here which eliminates laziness of triple. It doesn't affect
-- laziness of 'VerificationRes' which is good because laziness for this data
-- type is crucial.
verifyBlocks
:: HasProtocolConstants
=> ProtocolMagic
-> Maybe SlotId
-> Bool
-> BlockVersionData
-> SlotLeaders
-> OldestFirst [] Block
-> VerificationRes
verifyBlocks pm curSlotId verifyNoUnknown bvd initLeaders = view _3 . foldl' step start
where
start :: VerifyBlocksIter
-- Note that here we never know previous header before this
-- function is launched. Which means that we will not do any
-- checks related to previous header. And it is fine, because we
-- must do these checks in advance, when we are processing
-- headers. However, it's a little obscure invariant, so keep it
-- in mind.
start = (initLeaders, Nothing, mempty)
step :: VerifyBlocksIter -> Block -> VerifyBlocksIter
step (leaders, prevHeader, res) blk =
let newLeaders = case blk of
Left genesisBlock -> genesisBlock ^. genBlockLeaders
Right _ -> leaders
vhp =
VerifyHeaderParams
{ vhpPrevHeader = prevHeader
, vhpLeaders = Just newLeaders
, vhpCurrentSlot = curSlotId
, vhpMaxSize = Just (bvdMaxHeaderSize bvd)
, vhpVerifyNoUnknown = verifyNoUnknown
}
vbp =
VerifyBlockParams
{ vbpVerifyHeader = vhp
, vbpMaxSize = bvdMaxBlockSize bvd
, vbpVerifyNoUnknown = verifyNoUnknown
}
in (newLeaders, Just $ getBlockHeader blk, res <> verifyBlock pm vbp blk)