/
Analysis.hs
586 lines (536 loc) · 22 KB
/
Analysis.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
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
module Analysis (
AnalysisEnv (..)
, AnalysisName (..)
, Limit (..)
, runAnalysis
) where
import Codec.CBOR.Encoding (Encoding)
import Control.Monad.Except
import Control.Tracer (Tracer (..), traceWith)
import Data.List (intercalate)
import qualified Data.Map.Strict as Map
import Data.Word (Word16, Word64)
import qualified Debug.Trace as Debug
import NoThunks.Class (noThunks)
import Ouroboros.Consensus.Block
import Ouroboros.Consensus.Config
import Ouroboros.Consensus.HeaderValidation (HasAnnTip (..),
HeaderState (..), annTipPoint)
import Ouroboros.Consensus.Ledger.Abstract (tickThenApplyLedgerResult)
import Ouroboros.Consensus.Ledger.Basics (LedgerResult (..))
import Ouroboros.Consensus.Ledger.Extended
import Ouroboros.Consensus.Ledger.SupportsProtocol
(LedgerSupportsProtocol (..))
import Ouroboros.Consensus.Storage.Common (BlockComponent (..),
StreamFrom (..))
import Ouroboros.Consensus.Storage.FS.API (SomeHasFS (..))
import Ouroboros.Consensus.Util.ResourceRegistry
import Ouroboros.Consensus.Storage.ChainDB (ChainDB)
import qualified Ouroboros.Consensus.Storage.ChainDB as ChainDB
import Ouroboros.Consensus.Storage.ChainDB.Impl.LgrDB
(LgrDbSerialiseConstraints)
import Ouroboros.Consensus.Storage.ImmutableDB (ImmutableDB)
import qualified Ouroboros.Consensus.Storage.ImmutableDB as ImmutableDB
import Ouroboros.Consensus.Storage.LedgerDB.OnDisk (DiskSnapshot (..),
writeSnapshot)
import Ouroboros.Consensus.Storage.Serialisation (SizeInBytes,
encodeDisk)
import HasAnalysis (HasAnalysis)
import qualified HasAnalysis
{-------------------------------------------------------------------------------
Run the requested analysis
-------------------------------------------------------------------------------}
data AnalysisName =
ShowSlotBlockNo
| CountTxOutputs
| ExtractTxOutputIdDeltas
| ExtractGenesisTxOutputIds
| ShowBlockHeaderSize
| ShowBlockTxsSize
| ShowEBBs
| OnlyValidation
| StoreLedgerStateAt SlotNo
| CountBlocks
| CheckNoThunksEvery Word64
| TraceLedgerProcessing
deriving Show
runAnalysis ::
forall blk .
( LgrDbSerialiseConstraints blk
, HasAnalysis blk
, LedgerSupportsProtocol blk
)
=> AnalysisName -> Analysis blk
runAnalysis analysisName env@(AnalysisEnv { tracer }) = do
traceWith tracer (StartedEvent analysisName)
go analysisName
traceWith tracer DoneEvent
where
go ShowSlotBlockNo = showSlotBlockNo env
go CountTxOutputs = countTxOutputs env
go ExtractTxOutputIdDeltas = extractTxOutputIdDeltas env
go ExtractGenesisTxOutputIds = extractGenesisTxOutputIds env
go ShowBlockHeaderSize = showHeaderSize env
go ShowBlockTxsSize = showBlockTxsSize env
go ShowEBBs = showEBBs env
go OnlyValidation = return ()
go (StoreLedgerStateAt slotNo) = (storeLedgerStateAt slotNo) env
go CountBlocks = countBlocks env
go (CheckNoThunksEvery nBks) = checkNoThunksEvery nBks env
go TraceLedgerProcessing = traceLedgerProcessing env
type Analysis blk = AnalysisEnv IO blk -> IO ()
data AnalysisEnv m blk = AnalysisEnv {
cfg :: TopLevelConfig blk
, initLedger :: ExtLedgerState blk
, db :: Either (ImmutableDB IO blk) (ChainDB IO blk)
, registry :: ResourceRegistry IO
, ledgerDbFS :: SomeHasFS IO
, limit :: Limit
, tracer :: Tracer m (TraceEvent blk)
}
data TraceEvent blk =
StartedEvent AnalysisName
-- ^ triggered when given analysis has started
| DoneEvent
-- ^ triggered when analysis has ended
| BlockSlotEvent BlockNo SlotNo
-- ^ triggered when block has been found, it holds:
-- * block's number
-- * slot number when the block was forged
| CountTxOutputsEvent BlockNo SlotNo Int Int
-- ^ triggered when block has been found, it holds:
-- * block's number
-- * slot number when the block was forged
-- * cumulative tx output
-- * count tx output
| ExtractTxOutputIdDeltasEvent
BlockNo
SlotNo
Int
[HasAnalysis.TxIn]
[HasAnalysis.TxOutputIds]
-- ^ triggered when listing the TxId delta for a block:
-- * block's number
-- * slot number when the block was forged
-- * how many transactions it contains
-- * txids consumed
-- * txids created
| ExtractGenesisTxOutputIdsEvent
Int
[HasAnalysis.TxOutputIds]
| EbbEvent (HeaderHash blk) (ChainHash blk) Bool
-- ^ triggered when EBB block has been found, it holds:
-- * its hash,
-- * hash of previous block
-- * flag whether the EBB is known
| CountedBlocksEvent Int
-- ^ triggered once during CountBLocks analysis,
-- when blocks were counted
| HeaderSizeEvent BlockNo SlotNo Word16
-- ^ triggered when header size has been measured
-- * block's number
-- * slot number when the block was forged
-- * block's header size
| MaxHeaderSizeEvent Word16
-- ^ triggered once during ShowBlockTxsSize analysis,
-- holding maximum encountered header size
| SnapshotStoredEvent SlotNo
-- ^ triggered when snapshot of ledger has been stored for SlotNo
| SnapshotWarningEvent SlotNo SlotNo
-- ^ triggered once during StoreLedgerStateAt analysis,
-- when snapshot was created in slot proceeding the
-- requested one
| BlockTxSizeEvent SlotNo Int SizeInBytes
-- ^ triggered for all blocks during ShowBlockTxsSize analysis,
-- it holds:
-- * slot number when the block was forged
-- * number of transactions in the block
-- * total size of transactions in the block
instance HasAnalysis blk => Show (TraceEvent blk) where
show (StartedEvent analysisName) = "Started " <> (show analysisName)
show DoneEvent = "Done"
show (BlockSlotEvent bn sn) = intercalate "\t" $ [
show bn
, show sn
]
show (CountTxOutputsEvent bn sn cumulative count) = intercalate "\t" $ [
show bn
, show sn
, "cumulative: " <> show cumulative
, "count: " <> show count
]
show (EbbEvent ebb previous known) = intercalate "\t" [
"EBB: " <> show ebb
, "Prev: " <> show previous
, "Known: " <> show known
]
show (CountedBlocksEvent counted) = "Counted " <> show counted <> " blocks."
show (ExtractTxOutputIdDeltasEvent bn sn count consumed created) = intercalate "\t" $
show (unBlockNo bn)
: show (unSlotNo sn)
: show count
: show (length consumed)
: show (sum $ map HasAnalysis.txOutputIdsCount created)
: (map show consumed <> map show created)
show (ExtractGenesisTxOutputIdsEvent count created) = intercalate "\t" $
show count
: show (sum $ map HasAnalysis.txOutputIdsCount created)
: map show created
show (HeaderSizeEvent bn sn headerSize) = intercalate "\t" $ [
show bn
, show sn
, "header size: " <> show headerSize
]
show (MaxHeaderSizeEvent size) =
"Maximum encountered header size = " <> show size
show (SnapshotStoredEvent slot) =
"Snapshot stored at " <> show slot
show (SnapshotWarningEvent requested actual) =
"Snapshot was created at " <> show actual <> " " <>
"because there was no block forged at requested " <> show requested
show (BlockTxSizeEvent slot numBlocks txsSize) = intercalate "\t" [
show slot
, "Num txs in block = " <> show numBlocks
, "Total size of txs in block = " <> show txsSize
]
{-------------------------------------------------------------------------------
Analysis: show block and slot number for all blocks
-------------------------------------------------------------------------------}
showSlotBlockNo :: forall blk. HasAnalysis blk => Analysis blk
showSlotBlockNo AnalysisEnv { db, registry, initLedger, limit, tracer } =
processAll_ db registry GetHeader initLedger limit process
where
process :: Header blk -> IO ()
process hdr = traceWith tracer $ BlockSlotEvent (blockNo hdr) (blockSlot hdr)
{-------------------------------------------------------------------------------
Analysis: show total number of tx outputs per block
-------------------------------------------------------------------------------}
countTxOutputs :: forall blk. HasAnalysis blk => Analysis blk
countTxOutputs AnalysisEnv { db, registry, initLedger, limit, tracer } = do
void $ processAll db registry GetBlock initLedger limit 0 process
where
process :: Int -> blk -> IO Int
process cumulative blk = do
let cumulative' = cumulative + count
event = CountTxOutputsEvent (blockNo blk)
(blockSlot blk)
cumulative'
count
traceWith tracer event
return cumulative'
where
count = HasAnalysis.countTxOutputs blk
{-------------------------------------------------------------------------------
Analysis: show all txin and txout txids
-------------------------------------------------------------------------------}
extractTxOutputIdDeltas :: forall blk. HasAnalysis blk => Analysis blk
extractTxOutputIdDeltas AnalysisEnv { db, registry, initLedger, limit, tracer } = do
void $ processAll db registry GetBlock initLedger limit () process
where
process :: () -> blk -> IO ()
process () blk =
case isEBB of
IsEBB -> pure ()
IsNotEBB -> do
traceWith tracer $ ExtractTxOutputIdDeltasEvent
(blockNo blk)
(blockSlot blk)
count
consumed
created
where
(isEBB, count, consumed, created) = HasAnalysis.extractTxOutputIdDelta blk
extractGenesisTxOutputIds :: forall blk. HasAnalysis blk => Analysis blk
extractGenesisTxOutputIds AnalysisEnv { initLedger, tracer } = do
traceWith tracer $ ExtractGenesisTxOutputIdsEvent count created
where
(count, created) = HasAnalysis.genesisTxOutputIds (ledgerState initLedger)
{-------------------------------------------------------------------------------
Analysis: show the header size in bytes for all blocks
-------------------------------------------------------------------------------}
showHeaderSize :: forall blk. HasAnalysis blk => Analysis blk
showHeaderSize AnalysisEnv { db, registry, initLedger, limit, tracer } = do
maxHeaderSize <-
processAll db registry ((,) <$> GetHeader <*> GetHeaderSize) initLedger limit 0 process
traceWith tracer $ MaxHeaderSizeEvent maxHeaderSize
where
process :: Word16 -> (Header blk, Word16) -> IO Word16
process maxHeaderSize (hdr, headerSize) = do
let event = HeaderSizeEvent (blockNo hdr)
(blockSlot hdr)
headerSize
traceWith tracer event
return $ maxHeaderSize `max` headerSize
{-------------------------------------------------------------------------------
Analysis: show the total transaction sizes in bytes per block
-------------------------------------------------------------------------------}
showBlockTxsSize :: forall blk. HasAnalysis blk => Analysis blk
showBlockTxsSize AnalysisEnv { db, registry, initLedger, limit, tracer } =
processAll_ db registry GetBlock initLedger limit process
where
process :: blk -> IO ()
process blk =
traceWith tracer $ BlockTxSizeEvent (blockSlot blk) numBlockTxs blockTxsSize
where
txSizes :: [SizeInBytes]
txSizes = HasAnalysis.blockTxSizes blk
numBlockTxs :: Int
numBlockTxs = length txSizes
blockTxsSize :: SizeInBytes
blockTxsSize = sum txSizes
{-------------------------------------------------------------------------------
Analysis: show EBBs and their predecessors
-------------------------------------------------------------------------------}
showEBBs :: forall blk. HasAnalysis blk => Analysis blk
showEBBs AnalysisEnv { db, registry, initLedger, limit, tracer } =
processAll_ db registry GetBlock initLedger limit process
where
process :: blk -> IO ()
process blk =
case blockIsEBB blk of
Just _epoch -> do
let known = Map.lookup
(blockHash blk)
(HasAnalysis.knownEBBs (Proxy @blk))
== Just (blockPrevHash blk)
event = EbbEvent (blockHash blk) (blockPrevHash blk) known
traceWith tracer event
_otherwise -> return () -- Skip regular blocks
{-------------------------------------------------------------------------------
Analysis: store a ledger at specific slot
-------------------------------------------------------------------------------}
storeLedgerStateAt ::
forall blk .
( LgrDbSerialiseConstraints blk
, HasAnalysis blk
, LedgerSupportsProtocol blk
)
=> SlotNo -> Analysis blk
storeLedgerStateAt slotNo (AnalysisEnv { db, registry, initLedger, cfg, limit, ledgerDbFS, tracer }) =
void $ processAllUntil db registry GetBlock initLedger limit initLedger process
where
process :: ExtLedgerState blk -> blk -> IO (NextStep, ExtLedgerState blk)
process oldLedger blk = do
let ledgerCfg = ExtLedgerCfg cfg
appliedResult = tickThenApplyLedgerResult ledgerCfg blk oldLedger
newLedger = either (error . show) lrResult $ runExcept $ appliedResult
when (blockSlot blk >= slotNo) $ storeLedgerState blk newLedger
when (blockSlot blk > slotNo) $ issueWarning blk
when ((unBlockNo $ blockNo blk) `mod` 1000 == 0) $ reportProgress blk
return (continue blk, newLedger)
continue :: blk -> NextStep
continue blk
| blockSlot blk >= slotNo = Stop
| otherwise = Continue
issueWarning blk = let event = SnapshotWarningEvent slotNo (blockSlot blk)
in traceWith tracer event
reportProgress blk = let event = BlockSlotEvent (blockNo blk) (blockSlot blk)
in traceWith tracer event
storeLedgerState ::
blk
-> ExtLedgerState blk
-> IO ()
storeLedgerState blk ledgerState = do
let snapshot = DiskSnapshot
(unSlotNo $ blockSlot blk)
(Just $ "db-analyser")
writeSnapshot ledgerDbFS encLedger snapshot ledgerState
traceWith tracer $ SnapshotStoredEvent (blockSlot blk)
encLedger :: ExtLedgerState blk -> Encoding
encLedger =
let ccfg = configCodec cfg
in encodeExtLedgerState
(encodeDisk ccfg)
(encodeDisk ccfg)
(encodeDisk ccfg)
countBlocks ::
forall blk .
( HasAnalysis blk
)
=> Analysis blk
countBlocks (AnalysisEnv { db, registry, initLedger, limit, tracer }) = do
counted <- processAll db registry (GetPure ()) initLedger limit 0 process
traceWith tracer $ CountedBlocksEvent counted
where
process :: Int -> () -> IO Int
process count _ = pure $ count + 1
{-------------------------------------------------------------------------------
Analysis: check for ledger state thunks every n blocks
-------------------------------------------------------------------------------}
checkNoThunksEvery ::
forall blk.
( HasAnalysis blk,
LedgerSupportsProtocol blk
) =>
Word64 ->
Analysis blk
checkNoThunksEvery
nBlocks
(AnalysisEnv {db, registry, initLedger, cfg, limit}) = do
putStrLn $
"Checking for thunks in each block where blockNo === 0 (mod " <> show nBlocks <> ")."
void $ processAll db registry GetBlock initLedger limit initLedger process
where
process :: ExtLedgerState blk -> blk -> IO (ExtLedgerState blk)
process oldLedger blk = do
let ledgerCfg = ExtLedgerCfg cfg
appliedResult = tickThenApplyLedgerResult ledgerCfg blk oldLedger
newLedger = either (error . show) lrResult $ runExcept $ appliedResult
bn = blockNo blk
when (unBlockNo bn `mod` nBlocks == 0 ) $ checkNoThunks bn newLedger
return newLedger
checkNoThunks :: BlockNo -> ExtLedgerState blk -> IO ()
checkNoThunks bn ls =
noThunks [] (ledgerState ls) >>= \case
Nothing -> putStrLn $ "BlockNo " <> show bn <> ": no thunks found."
Just ti -> do
putStrLn $ "BlockNo " <> show bn <> ": thunks found."
print ti
{-------------------------------------------------------------------------------
Analysis: maintain a ledger state and issue trace markers at appropriate
points in the epoch
-------------------------------------------------------------------------------}
traceLedgerProcessing ::
forall blk.
( HasAnalysis blk,
LedgerSupportsProtocol blk
) =>
Analysis blk
traceLedgerProcessing
(AnalysisEnv {db, registry, initLedger, cfg, limit}) = do
void $ processAll db registry GetBlock initLedger limit initLedger process
where
process
:: ExtLedgerState blk
-> blk
-> IO (ExtLedgerState blk)
process oldLedger blk = do
let ledgerCfg = ExtLedgerCfg cfg
appliedResult = tickThenApplyLedgerResult ledgerCfg blk oldLedger
newLedger = either (error . show) lrResult $ runExcept $ appliedResult
traces =
(HasAnalysis.emitTraces $
HasAnalysis.WithLedgerState blk (ledgerState oldLedger) (ledgerState newLedger))
mapM_ Debug.traceMarkerIO traces
return $ newLedger
{-------------------------------------------------------------------------------
Auxiliary: processing all blocks in the DB
-------------------------------------------------------------------------------}
data Limit = Limit Int | Unlimited
decreaseLimit :: Limit -> Maybe Limit
decreaseLimit Unlimited = Just Unlimited
decreaseLimit (Limit 0) = Nothing
decreaseLimit (Limit n) = Just . Limit $ n - 1
data NextStep = Continue | Stop
processAllUntil ::
forall blk b st. (HasHeader blk, HasAnnTip blk)
=> Either (ImmutableDB IO blk) (ChainDB IO blk)
-> ResourceRegistry IO
-> BlockComponent blk b
-> ExtLedgerState blk
-> Limit
-> st
-> (st -> b -> IO (NextStep, st))
-> IO st
processAllUntil = either processAllImmutableDB processAllChainDB
processAll ::
forall blk b st. (HasHeader blk, HasAnnTip blk)
=> Either (ImmutableDB IO blk) (ChainDB IO blk)
-> ResourceRegistry IO
-> BlockComponent blk b
-> ExtLedgerState blk
-> Limit
-> st
-> (st -> b -> IO st)
-> IO st
processAll db rr blockComponent initLedger limit initSt cb =
processAllUntil db rr blockComponent initLedger limit initSt callback
where
callback st b = (Continue, ) <$> cb st b
processAll_ ::
forall blk b. (HasHeader blk, HasAnnTip blk)
=> Either (ImmutableDB IO blk) (ChainDB IO blk)
-> ResourceRegistry IO
-> BlockComponent blk b
-> ExtLedgerState blk
-> Limit
-> (b -> IO ())
-> IO ()
processAll_ db registry blockComponent initLedger limit callback =
processAll db registry blockComponent initLedger limit () (const callback)
processAllChainDB ::
forall st blk b. (HasHeader blk, HasAnnTip blk)
=> ChainDB IO blk
-> ResourceRegistry IO
-> BlockComponent blk b
-> ExtLedgerState blk
-> Limit
-> st
-> (st -> b -> IO (NextStep, st))
-> IO st
processAllChainDB chainDB registry blockComponent (ExtLedgerState {..}) limit initState callback = do
itr <- case headerStateTip headerState of
Origin -> ChainDB.streamAll
chainDB
registry
blockComponent
NotOrigin annTip -> ChainDB.streamFrom
(StreamFromExclusive $ annTipPoint annTip)
chainDB
registry
blockComponent
go itr limit initState
where
go :: ChainDB.Iterator IO blk b -> Limit -> st -> IO st
go itr lt !st = case decreaseLimit lt of
Nothing -> return st
Just decreasedLimit -> do
itrResult <- ChainDB.iteratorNext itr
case itrResult of
ChainDB.IteratorExhausted -> return st
ChainDB.IteratorResult b -> callback st b >>= \case
(Continue, nst) -> go itr decreasedLimit nst
(Stop, nst) -> return nst
ChainDB.IteratorBlockGCed pt -> error $ "block GC'ed " <> show pt
processAllImmutableDB ::
forall st blk b. (HasHeader blk, HasAnnTip blk)
=> ImmutableDB IO blk
-> ResourceRegistry IO
-> BlockComponent blk b
-> ExtLedgerState blk
-> Limit
-> st
-> (st -> b -> IO (NextStep, st))
-> IO st
processAllImmutableDB immutableDB registry blockComponent (ExtLedgerState {..}) limit initState callback = do
itr <- case headerStateTip headerState of
Origin -> ImmutableDB.streamAll
immutableDB
registry
blockComponent
NotOrigin annTip -> ImmutableDB.streamAfterKnownPoint
immutableDB
registry
blockComponent
(annTipPoint annTip)
go itr limit initState
where
go :: ImmutableDB.Iterator IO blk b -> Limit -> st -> IO st
go itr lt !st = case decreaseLimit lt of
Nothing -> return st
Just decreasedLimit -> do
itrResult <- ImmutableDB.iteratorNext itr
case itrResult of
ImmutableDB.IteratorExhausted -> return st
ImmutableDB.IteratorResult b -> callback st b >>= \case
(Continue, nst) -> go itr decreasedLimit nst
(Stop, nst) -> return nst