Skip to content

Commit dcf27db

Browse files
committed
Checksum the data when writing and reading ledger snapshots
- Allow skipping snapshot checksum check - Generalise `Test/Ouroboros/Storage/LedgerDB/OnDisk.hs` - Restrict `Ord` instance for `DiskSnapshot` to `dsNumber` - Use the `Ord` instance in `listSnapshots` - Connect snapshot checksum with the node interface: - Add `Flag "DoDiskSnapshotChecksum"` to `DiskPolicyArgs` - Expose `(No)DoDiskSnapshotChecksum` in Ouroboros.Consensus.Node - Re-export `Flag` from `DiskPolicy` Update changelog
1 parent b8a13dd commit dcf27db

File tree

19 files changed

+289
-79
lines changed

19 files changed

+289
-79
lines changed

ouroboros-consensus-cardano/app/DBAnalyser/Parsers.hs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
{-# LANGUAGE ApplicativeDo #-}
2-
{-# LANGUAGE CPP #-}
3-
{-# LANGUAGE LambdaCase #-}
1+
{-# LANGUAGE ApplicativeDo #-}
2+
{-# LANGUAGE CPP #-}
3+
{-# LANGUAGE LambdaCase #-}
4+
{-# LANGUAGE PatternSynonyms #-}
45

56
module DBAnalyser.Parsers (
67
BlockType (..)
@@ -21,6 +22,7 @@ import Options.Applicative
2122
import Ouroboros.Consensus.Block
2223
import Ouroboros.Consensus.Byron.Node (PBftSignatureThreshold (..))
2324
import Ouroboros.Consensus.Shelley.Node (Nonce (..))
25+
import Ouroboros.Consensus.Storage.LedgerDB.DiskPolicy (pattern DoDiskSnapshotChecksum, pattern NoDoDiskSnapshotChecksum)
2426

2527
{-------------------------------------------------------------------------------
2628
Parsing
@@ -44,6 +46,10 @@ parseDBAnalyserConfig = DBAnalyserConfig
4446
<*> parseValidationPolicy
4547
<*> parseAnalysis
4648
<*> parseLimit
49+
<*> flag NoDoDiskSnapshotChecksum DoDiskSnapshotChecksum (mconcat [
50+
long "do-disk-snapshot-checksum"
51+
, help "Check the '.checksum' file if reading a ledger snapshot"
52+
])
4753

4854
parseSelectDB :: Parser SelectDB
4955
parseSelectDB =

ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBAnalyser/Analysis.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
{-# LANGUAGE LambdaCase #-}
77
{-# LANGUAGE NamedFieldPuns #-}
88
{-# LANGUAGE OverloadedStrings #-}
9+
{-# LANGUAGE PatternSynonyms #-}
910
{-# LANGUAGE ScopedTypeVariables #-}
1011
{-# LANGUAGE TupleSections #-}
1112
{-# LANGUAGE TypeApplications #-}
@@ -72,7 +73,7 @@ import Ouroboros.Consensus.Storage.Common (BlockComponent (..))
7273
import Ouroboros.Consensus.Storage.ImmutableDB (ImmutableDB)
7374
import qualified Ouroboros.Consensus.Storage.ImmutableDB as ImmutableDB
7475
import Ouroboros.Consensus.Storage.LedgerDB (DiskSnapshot (..),
75-
writeSnapshot)
76+
pattern NoDoDiskSnapshotChecksum, writeSnapshot)
7677
import Ouroboros.Consensus.Storage.Serialisation (encodeDisk)
7778
import Ouroboros.Consensus.Util ((..:))
7879
import qualified Ouroboros.Consensus.Util.IOLike as IOLike
@@ -422,7 +423,7 @@ storeLedgerStateAt slotNo ledgerAppMode env = do
422423
storeLedgerState ledgerState = case pointSlot pt of
423424
NotOrigin slot -> do
424425
let snapshot = DiskSnapshot (unSlotNo slot) (Just "db-analyser")
425-
writeSnapshot ledgerDbFS encLedger snapshot ledgerState
426+
writeSnapshot ledgerDbFS NoDoDiskSnapshotChecksum encLedger snapshot ledgerState
426427
traceWith tracer $ SnapshotStoredEvent slot
427428
Origin -> pure ()
428429
where

ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBAnalyser/Run.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ analyse ::
5151
=> DBAnalyserConfig
5252
-> Args blk
5353
-> IO (Maybe AnalysisResult)
54-
analyse DBAnalyserConfig{analysis, confLimit, dbDir, selectDB, validation, verbose} args =
54+
analyse DBAnalyserConfig{analysis, confLimit, dbDir, selectDB, validation, verbose, diskSnapshotChecksum} args =
5555
withRegistry $ \registry -> do
5656
lock <- newMVar ()
5757
chainDBTracer <- mkTracer lock verbose
@@ -92,6 +92,7 @@ analyse DBAnalyserConfig{analysis, confLimit, dbDir, selectDB, validation, verbo
9292
ledgerDbFS
9393
(decodeDiskExtLedgerState $ configCodec cfg)
9494
decode
95+
diskSnapshotChecksum
9596
(DiskSnapshot slot (Just "db-analyser"))
9697
-- TODO @readSnapshot@ has type @ExceptT ReadIncrementalErr m
9798
-- (ExtLedgerState blk)@ but it also throws exceptions! This makes

ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBAnalyser/Types.hs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1+
{-# LANGUAGE DataKinds #-}
12
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
23

34
module Cardano.Tools.DBAnalyser.Types (module Cardano.Tools.DBAnalyser.Types) where
45

56
import Data.Word
67
import Ouroboros.Consensus.Block
8+
import Ouroboros.Consensus.Util (Flag)
79

810
data SelectDB =
911
SelectImmutableDB (WithOrigin SlotNo)
1012

1113
data DBAnalyserConfig = DBAnalyserConfig {
12-
dbDir :: FilePath
13-
, verbose :: Bool
14-
, selectDB :: SelectDB
15-
, validation :: Maybe ValidateBlocks
16-
, analysis :: AnalysisName
17-
, confLimit :: Limit
14+
dbDir :: FilePath
15+
, verbose :: Bool
16+
, selectDB :: SelectDB
17+
, validation :: Maybe ValidateBlocks
18+
, analysis :: AnalysisName
19+
, confLimit :: Limit
20+
, diskSnapshotChecksum :: Flag "DoDiskSnapshotChecksum"
1821
}
1922

2023
data AnalysisName =

ouroboros-consensus-cardano/test/tools-test/Main.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
{-# LANGUAGE PatternSynonyms #-}
2+
13
module Main (main) where
24

35
import qualified Cardano.Tools.DBAnalyser.Block.Cardano as Cardano
@@ -8,6 +10,8 @@ import qualified Cardano.Tools.DBSynthesizer.Run as DBSynthesizer
810
import Cardano.Tools.DBSynthesizer.Types
911
import Ouroboros.Consensus.Block
1012
import Ouroboros.Consensus.Cardano.Block
13+
import Ouroboros.Consensus.Storage.LedgerDB.DiskPolicy
14+
(pattern NoDoDiskSnapshotChecksum)
1115
import qualified Test.Cardano.Tools.Headers
1216
import Test.Tasty
1317
import Test.Tasty.HUnit
@@ -68,6 +72,7 @@ testAnalyserConfig =
6872
, validation = Just ValidateAllBlocks
6973
, analysis = CountBlocks
7074
, confLimit = Unlimited
75+
, diskSnapshotChecksum = NoDoDiskSnapshotChecksum
7176
}
7277

7378
testBlockArgs :: Cardano.Args (CardanoBlock StandardCrypto)

ouroboros-consensus-diffusion/src/ouroboros-consensus-diffusion/Ouroboros/Consensus/Node.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
{-# LANGUAGE LambdaCase #-}
77
{-# LANGUAGE MonadComprehensions #-}
88
{-# LANGUAGE NamedFieldPuns #-}
9+
{-# LANGUAGE PatternSynonyms #-}
910
{-# LANGUAGE Rank2Types #-}
1011
{-# LANGUAGE RecordWildCards #-}
1112
{-# LANGUAGE ScopedTypeVariables #-}
@@ -51,6 +52,8 @@ module Ouroboros.Consensus.Node (
5152
, RunNodeArgs (..)
5253
, Tracers
5354
, Tracers' (..)
55+
, pattern DoDiskSnapshotChecksum
56+
, pattern NoDoDiskSnapshotChecksum
5457
-- * Internal helpers
5558
, mkNodeKernelArgs
5659
, nodeKernelArgsEnforceInvariants
@@ -107,7 +110,8 @@ import Ouroboros.Consensus.Storage.ChainDB (ChainDB, ChainDbArgs,
107110
import qualified Ouroboros.Consensus.Storage.ChainDB as ChainDB
108111
import qualified Ouroboros.Consensus.Storage.ChainDB.Impl.Args as ChainDB
109112
import Ouroboros.Consensus.Storage.LedgerDB.DiskPolicy
110-
(DiskPolicyArgs (..))
113+
(DiskPolicyArgs (..), pattern DoDiskSnapshotChecksum,
114+
pattern NoDoDiskSnapshotChecksum)
111115
import Ouroboros.Consensus.Util.Args
112116
import Ouroboros.Consensus.Util.IOLike
113117
import Ouroboros.Consensus.Util.Orphans ()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
### Breaking
2+
3+
- When writing a ledger state snapshot to disk, calculate the state's CRC32 checksum and write it to a separate file, which is named the same as the snapshot file, plus the `.checksum` extension.
4+
- When reading a snapshot file in `readSnapshot`, calculate its checksum and compare it to the value in the corresponding `.checksum` file. Return an error if the checksum is different or invalid. Issue a warning if the checksum file does not exist, but still initialise the ledger DB.
5+
- To support the previous item, change the error type of the `readSnapshot` from `ReadIncrementalErr` to the extended `ReadSnaphotErr`.
6+
- Checksumming the snapshots is controlled via the `doChecksum :: Flag "DoDiskSnapshotChecksum"` parameter of `initFromSnapshot`. Ultimately, this parameter comes from the Node's configuration file via the `DiskPolicy` data type.
7+
- Extend the `DiskPolicyArgs` data type to enable the node to pass `Flag "DoDiskSnapshotChecksum"` to Consensus.
8+
9+
### Non-breaking
10+
11+
- Make `Ouroboros.Consensus.Util.CBOR.readIncremental` optionally compute the checksum of the data as it is read.
12+
- Introduce an explicit `Ord` instance for `DiskSnapshot` that compares the values on `dsNumber`.
13+
- Introduce a new utility newtype `Flag` to represent type-safe boolean flags. See ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Util.hs.
14+
- Use `Flag "DoDiskSnapshotChecksum"` to control the check of the snapshot checksum file in `takeSnapshot`, `readSnapshot` and `writeSnapshot`.

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/LgrDB.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ initFromDisk ::
222222
initFromDisk LgrDbArgs { lgrHasFS = hasFS, .. }
223223
replayTracer
224224
immutableDB = wrapFailure (Proxy @blk) $ do
225+
let LedgerDB.DiskPolicyArgs _ _ doDiskSnapshotChecksum = lgrDiskPolicyArgs
225226
(_initLog, db, replayed) <-
226227
LedgerDB.initLedgerDB
227228
replayTracer
@@ -232,6 +233,7 @@ initFromDisk LgrDbArgs { lgrHasFS = hasFS, .. }
232233
lgrConfig
233234
lgrGenesis
234235
(streamAPI immutableDB)
236+
doDiskSnapshotChecksum
235237
return (db, replayed)
236238
where
237239
ccfg = configCodec $ getExtLedgerCfg $ LedgerDB.ledgerDbCfg lgrConfig
@@ -280,11 +282,12 @@ takeSnapshot ::
280282
, IsLedger (LedgerState blk)
281283
)
282284
=> LgrDB m blk -> m (Maybe (LedgerDB.DiskSnapshot, RealPoint blk))
283-
takeSnapshot lgrDB@LgrDB{ cfg, tracer, hasFS } = wrapFailure (Proxy @blk) $ do
285+
takeSnapshot lgrDB@LgrDB{ cfg, tracer, hasFS, diskPolicy } = wrapFailure (Proxy @blk) $ do
284286
ledgerDB <- LedgerDB.ledgerDbAnchor <$> atomically (getCurrent lgrDB)
285287
LedgerDB.takeSnapshot
286288
tracer
287289
hasFS
290+
(LedgerDB.onDiskShouldChecksumSnapshots diskPolicy)
288291
(encodeDiskExtLedgerState ccfg)
289292
ledgerDB
290293
where

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB.hs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
{-# LANGUAGE PatternSynonyms #-}
2+
13
-- | The Ledger DB is responsible for the following tasks:
24
--
35
-- - __Maintaining the in-memory ledger state at the tip__: When we try to
@@ -134,6 +136,8 @@ module Ouroboros.Consensus.Storage.LedgerDB (
134136
, SnapshotFailure (..)
135137
, diskSnapshotIsTemporary
136138
, listSnapshots
139+
, pattern DoDiskSnapshotChecksum
140+
, pattern NoDoDiskSnapshotChecksum
137141
, readSnapshot
138142
-- ** Write to disk
139143
, takeSnapshot
@@ -160,7 +164,9 @@ module Ouroboros.Consensus.Storage.LedgerDB (
160164
import Ouroboros.Consensus.Storage.LedgerDB.DiskPolicy
161165
(DiskPolicy (..), DiskPolicyArgs (..),
162166
NumOfDiskSnapshots (..), SnapshotInterval (..),
163-
TimeSinceLast (..), defaultDiskPolicyArgs, mkDiskPolicy)
167+
TimeSinceLast (..), defaultDiskPolicyArgs, mkDiskPolicy,
168+
pattern DoDiskSnapshotChecksum,
169+
pattern NoDoDiskSnapshotChecksum)
164170
import Ouroboros.Consensus.Storage.LedgerDB.Init (InitLog (..),
165171
ReplayGoal (..), ReplayStart (..), TraceReplayEvent (..),
166172
decorateReplayTracerWithGoal,

ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/DiskPolicy.hs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
{-# LANGUAGE DeriveGeneric #-}
44
{-# LANGUAGE DerivingVia #-}
55
{-# LANGUAGE NumericUnderscores #-}
6+
{-# LANGUAGE PatternSynonyms #-}
67
{-# LANGUAGE RecordWildCards #-}
78

89
module Ouroboros.Consensus.Storage.LedgerDB.DiskPolicy (
@@ -13,6 +14,10 @@ module Ouroboros.Consensus.Storage.LedgerDB.DiskPolicy (
1314
, TimeSinceLast (..)
1415
, defaultDiskPolicyArgs
1516
, mkDiskPolicy
17+
, pattern DoDiskSnapshotChecksum
18+
, pattern NoDoDiskSnapshotChecksum
19+
-- * Re-exports
20+
, Flag (..)
1621
) where
1722

1823
import Control.Monad.Class.MonadTime.SI
@@ -21,6 +26,7 @@ import Data.Word
2126
import GHC.Generics
2227
import NoThunks.Class (NoThunks, OnlyCheckWhnf (..))
2328
import Ouroboros.Consensus.Config.SecurityParam
29+
import Ouroboros.Consensus.Util (Flag (..))
2430

2531
-- | Length of time, requested by the user, that has to pass after which
2632
-- a snapshot is taken. It can be:
@@ -41,7 +47,11 @@ data NumOfDiskSnapshots =
4147
| RequestedNumOfDiskSnapshots Word
4248
deriving stock (Eq, Generic, Show)
4349

44-
data DiskPolicyArgs = DiskPolicyArgs SnapshotInterval NumOfDiskSnapshots
50+
pattern DoDiskSnapshotChecksum, NoDoDiskSnapshotChecksum :: Flag "DoDiskSnapshotChecksum"
51+
pattern DoDiskSnapshotChecksum = Flag True
52+
pattern NoDoDiskSnapshotChecksum = Flag False
53+
54+
data DiskPolicyArgs = DiskPolicyArgs SnapshotInterval NumOfDiskSnapshots (Flag "DoDiskSnapshotChecksum")
4555

4656
-- | On-disk policy
4757
--
@@ -67,7 +77,7 @@ data DiskPolicy = DiskPolicy {
6777
-- the next snapshot, we delete the oldest one, leaving the middle
6878
-- one available in case of truncation of the write. This is
6979
-- probably a sane value in most circumstances.
70-
onDiskNumSnapshots :: Word
80+
onDiskNumSnapshots :: Word
7181

7282
-- | Should we write a snapshot of the ledger state to disk?
7383
--
@@ -87,7 +97,11 @@ data DiskPolicy = DiskPolicy {
8797
-- blocks had to be replayed.
8898
--
8999
-- See also 'mkDiskPolicy'
90-
, onDiskShouldTakeSnapshot :: TimeSinceLast DiffTime -> Word64 -> Bool
100+
, onDiskShouldTakeSnapshot :: TimeSinceLast DiffTime -> Word64 -> Bool
101+
102+
-- | Whether or not to checksum the ledger snapshots to detect data corruption on disk.
103+
-- "yes" if @'DoDiskSnapshotChecksum'@; "no" if @'NoDoDiskSnapshotChecksum'@.
104+
, onDiskShouldChecksumSnapshots :: Flag "DoDiskSnapshotChecksum"
91105
}
92106
deriving NoThunks via OnlyCheckWhnf DiskPolicy
93107

@@ -97,10 +111,10 @@ data TimeSinceLast time = NoSnapshotTakenYet | TimeSinceLast time
97111
-- | Default on-disk policy arguments suitable to use with cardano-node
98112
--
99113
defaultDiskPolicyArgs :: DiskPolicyArgs
100-
defaultDiskPolicyArgs = DiskPolicyArgs DefaultSnapshotInterval DefaultNumOfDiskSnapshots
114+
defaultDiskPolicyArgs = DiskPolicyArgs DefaultSnapshotInterval DefaultNumOfDiskSnapshots DoDiskSnapshotChecksum
101115

102116
mkDiskPolicy :: SecurityParam -> DiskPolicyArgs -> DiskPolicy
103-
mkDiskPolicy (SecurityParam k) (DiskPolicyArgs reqInterval reqNumOfSnapshots) =
117+
mkDiskPolicy (SecurityParam k) (DiskPolicyArgs reqInterval reqNumOfSnapshots onDiskShouldChecksumSnapshots) =
104118
DiskPolicy {..}
105119
where
106120
onDiskNumSnapshots :: Word

0 commit comments

Comments
 (0)