diff --git a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBAnalyser/Run.hs b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBAnalyser/Run.hs index 2832ffd437..f5dcb402e3 100644 --- a/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBAnalyser/Run.hs +++ b/ouroboros-consensus-cardano/src/unstable-cardano-tools/Cardano/Tools/DBAnalyser/Run.hs @@ -75,6 +75,7 @@ openLedgerDB lgrDbArgs@LedgerDB.LedgerDbArgs{LedgerDB.lgrFlavorArgs = LedgerDB.L bss (\_ -> error "no replay") snapManager + (LedgerDB.praosGetVolatileSuffix $ LedgerDB.ledgerDbCfgSecParam $ LedgerDB.lgrConfig lgrDbArgs) ) snapManager emptyStream @@ -92,6 +93,7 @@ openLedgerDB lgrDbArgs@LedgerDB.LedgerDbArgs{LedgerDB.lgrFlavorArgs = LedgerDB.L bss' (\_ -> error "no replay") snapManager + (LedgerDB.praosGetVolatileSuffix $ LedgerDB.ledgerDbCfgSecParam $ LedgerDB.lgrConfig lgrDbArgs) ) snapManager emptyStream diff --git a/ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/PeerSimulator/Trace.hs b/ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/PeerSimulator/Trace.hs index feda424c51..d7acdf1fdc 100644 --- a/ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/PeerSimulator/Trace.hs +++ b/ouroboros-consensus-diffusion/test/consensus-test/Test/Consensus/PeerSimulator/Trace.hs @@ -418,8 +418,8 @@ traceChainDBEventTestBlockWith tracer = \case trace $ "Switched to a fork; now: " ++ terseHFragment newFragment StoreButDontChange point -> trace $ "Did not select block due to LoE: " ++ terseRealPoint point - IgnoreBlockOlderThanK point -> - trace $ "Ignored block older than k: " ++ terseRealPoint point + IgnoreBlockOlderThanImmTip point -> + trace $ "Ignored block older than imm tip: " ++ terseRealPoint point ChainSelectionLoEDebug curChain (LoEEnabled loeFrag0) -> do trace $ "Current chain: " ++ terseHFragment curChain trace $ "LoE fragment: " ++ terseHFragment loeFrag0 diff --git a/ouroboros-consensus/changelog.d/20250811_130239_alexander.esgen_decouple_immutability.md b/ouroboros-consensus/changelog.d/20250811_130239_alexander.esgen_decouple_immutability.md new file mode 100644 index 0000000000..43ecf32dcd --- /dev/null +++ b/ouroboros-consensus/changelog.d/20250811_130239_alexander.esgen_decouple_immutability.md @@ -0,0 +1,4 @@ +### Breaking + +- Renamed `IgnoreBlockOlderThanK` to `IgnoreBlockOlderThanImmTip` for future-proofing. +- Renamed and simplified `olderThanK` to `olderThanImmTip`. diff --git a/ouroboros-consensus/changelog.d/20250811_150947_alexander.esgen_decouple_immutability.md b/ouroboros-consensus/changelog.d/20250811_150947_alexander.esgen_decouple_immutability.md new file mode 100644 index 0000000000..c48e80ce56 --- /dev/null +++ b/ouroboros-consensus/changelog.d/20250811_150947_alexander.esgen_decouple_immutability.md @@ -0,0 +1,7 @@ +### Breaking + +- LedgerDB: generalized over the criterion used to determine which states are + volatile/immutable, in preparation for Ouroboros Peras. + + Concretely, `LedgerDB.openDB` takes a new argument, `GetVolatileSuffix m blk`. + For Praos behavior, use `praosGetVolatileSuffix`. diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/ChainSync/Client.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/ChainSync/Client.hs index c5b3602353..ec16c91eca 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/ChainSync/Client.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/MiniProtocol/ChainSync/Client.hs @@ -690,12 +690,10 @@ checkKnownIntersectionInvariants :: ( HasHeader blk , HasHeader (Header blk) , HasAnnTip blk - , ConsensusProtocol (BlockProtocol blk) ) => - ConsensusConfig (BlockProtocol blk) -> KnownIntersectionState blk -> Either String () -checkKnownIntersectionInvariants cfg kis +checkKnownIntersectionInvariants kis -- 'theirHeaderStateHistory' invariant | let HeaderStateHistory snapshots = theirHeaderStateHistory historyTips :: [WithOrigin (AnnTip blk)] @@ -722,19 +720,6 @@ checkKnownIntersectionInvariants cfg kis , show fragmentAnchorPoint ] -- 'ourFrag' invariants - | let nbHeaders = AF.length ourFrag - ourAnchorPoint = AF.anchorPoint ourFrag - , nbHeaders < fromIntegral (unNonZero k) - , ourAnchorPoint /= GenesisPoint = - throwError $ - unwords - [ "ourFrag contains fewer than k headers and not close to genesis:" - , show nbHeaders - , "vs" - , show k - , "with anchor" - , show ourAnchorPoint - ] | let ourFragAnchor = AF.anchorPoint ourFrag theirFragAnchor = AF.anchorPoint theirFrag , ourFragAnchor /= castPoint theirFragAnchor = @@ -760,8 +745,6 @@ checkKnownIntersectionInvariants cfg kis | otherwise = return () where - SecurityParam k = protocolSecurityParam cfg - KnownIntersectionState { mostRecentIntersection , ourFrag @@ -773,14 +756,12 @@ assertKnownIntersectionInvariants :: ( HasHeader blk , HasHeader (Header blk) , HasAnnTip blk - , ConsensusProtocol (BlockProtocol blk) , HasCallStack ) => - ConsensusConfig (BlockProtocol blk) -> KnownIntersectionState blk -> KnownIntersectionState blk -assertKnownIntersectionInvariants cfg kis = - assertWithMsg (checkKnownIntersectionInvariants cfg kis) kis +assertKnownIntersectionInvariants kis = + assertWithMsg (checkKnownIntersectionInvariants kis) kis {------------------------------------------------------------------------------- The ChainSync client definition @@ -891,8 +872,7 @@ chainSyncClient cfgEnv dynEnv = (ForkTooDeep GenesisPoint) where ConfigEnv - { cfg - , chainDbView + { chainDbView , tracer } = cfgEnv @@ -994,7 +974,7 @@ chainSyncClient cfgEnv dynEnv = -- we will /never/ adopt them, which is handled in the "no -- more intersection case". StillIntersects () $ - assertKnownIntersectionInvariants (configConsensus cfg) $ + assertKnownIntersectionInvariants $ KnownIntersectionState { mostRecentIntersection = castPoint intersection , ourFrag = ourFrag' @@ -1157,7 +1137,7 @@ findIntersectionTop cfgEnv dynEnv intEnv = (ourTipFromChain ourFrag) theirTip let kis = - assertKnownIntersectionInvariants (configConsensus cfg) $ + assertKnownIntersectionInvariants $ KnownIntersectionState { mostRecentIntersection = intersection , ourFrag @@ -1233,7 +1213,6 @@ knownIntersectionStateTop cfgEnv dynEnv intEnv = ConfigEnv { mkPipelineDecision0 , tracer - , cfg , historicityCheck } = cfgEnv @@ -1621,9 +1600,8 @@ knownIntersectionStateTop cfgEnv dynEnv intEnv = else mostRecentIntersection kis' = - assertKnownIntersectionInvariants - (configConsensus cfg) - $ KnownIntersectionState + assertKnownIntersectionInvariants $ + KnownIntersectionState { mostRecentIntersection = mostRecentIntersection' , ourFrag = ourFrag , theirFrag = theirFrag' @@ -1960,7 +1938,7 @@ checkValid cfgEnv intEnv hdr hdrSlotTime theirTip kis ledgerView = do traceWith (tracer cfgEnv) $ TraceValidatedHeader hdr pure $ - assertKnownIntersectionInvariants (configConsensus cfg) $ + assertKnownIntersectionInvariants $ KnownIntersectionState { mostRecentIntersection = mostRecentIntersection' , ourFrag = ourFrag diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl.hs index 3ee8da303f..e5f7b21014 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl.hs @@ -53,6 +53,7 @@ import Data.Functor.Contravariant ((>$<)) import qualified Data.Map.Strict as Map import Data.Maybe.Strict (StrictMaybe (..)) import GHC.Stack (HasCallStack) +import NoThunks.Class import Ouroboros.Consensus.Block import Ouroboros.Consensus.Config import qualified Ouroboros.Consensus.Fragment.Validated as VF @@ -86,6 +87,7 @@ import Ouroboros.Consensus.Util.STM ( Fingerprint (..) , WithFingerprint (..) ) +import Ouroboros.Network.AnchoredFragment (AnchoredFragment) import qualified Ouroboros.Network.AnchoredFragment as AF import Ouroboros.Network.BlockFetch.ConsensusInterface ( ChainSelStarvation (..) @@ -160,12 +162,15 @@ openDBInternal args launchBgTasks = runWithTempRegistry $ do (chainDB, testing, env) <- lift $ do traceWith tracer $ TraceOpenEvent (OpenedVolatileDB maxSlot) traceWith tracer $ TraceOpenEvent StartedOpeningLgrDB + (ledgerDbGetVolatileSuffix, setGetCurrentChainForLedgerDB) <- + mkLedgerDbGetVolatileSuffix (lgrDB, replayed) <- LedgerDB.openDB argsLgrDb (ImmutableDB.streamAPI immutableDB) immutableDbTipPoint (Query.getAnyKnownBlock immutableDB volatileDB) + ledgerDbGetVolatileSuffix traceWith tracer $ TraceOpenEvent OpenedLgrDB varInvalid <- newTVarIO (WithFingerprint Map.empty (Fingerprint 0)) @@ -246,6 +251,9 @@ openDBInternal args launchBgTasks = runWithTempRegistry $ do , cdbLoE = Args.cdbsLoE cdbSpecificArgs , cdbChainSelStarvation = varChainSelStarvation } + + setGetCurrentChainForLedgerDB $ Query.getCurrentChain env + h <- fmap CDBHandle $ newTVarIO $ ChainDbOpen env let chainDB = API.ChainDB @@ -304,6 +312,38 @@ openDBInternal args launchBgTasks = runWithTempRegistry $ do tracer = Args.cdbsTracer cdbSpecificArgs Args.ChainDbArgs argsImmutableDb argsVolatileDb argsLgrDb cdbSpecificArgs = args + -- The LedgerDB requires a criterion ('LedgerDB.GetVolatileSuffix') + -- determining which of its states are volatile/immutable. Once we have + -- initialized the ChainDB we can defer this decision to + -- 'Query.getCurrentChain'. + -- + -- However, we initialize the LedgerDB before the ChainDB (for initial chain + -- selection), so during that period, we temporarily consider no state (apart + -- from the anchor state) as immutable. This is fine as we don't perform eg + -- any rollbacks during this period. + mkLedgerDbGetVolatileSuffix :: + m + ( LedgerDB.GetVolatileSuffix m blk + , STM m (AnchoredFragment (Header blk)) -> m () + ) + mkLedgerDbGetVolatileSuffix = do + varGetCurrentChain :: + StrictTMVar m (OnlyCheckWhnf (STM m (AnchoredFragment (Header blk)))) <- + newEmptyTMVarIO + let getVolatileSuffix = + LedgerDB.GetVolatileSuffix $ + tryReadTMVar varGetCurrentChain >>= \case + -- If @setVarChain@ has not yet been invoked, return the entire + -- suffix as volatile. + Nothing -> pure id + -- Otherwise, return the suffix with the same length as the + -- current chain. + Just (OnlyCheckWhnf getCurrentChain) -> do + curChainLen <- AF.length <$> getCurrentChain + pure $ AF.anchorNewest (fromIntegral curChainLen) + setVarChain = atomically . writeTMVar varGetCurrentChain . OnlyCheckWhnf + pure (getVolatileSuffix, setVarChain) + -- | We use 'runInnerWithTempRegistry' for the component databases. innerOpenCont :: IOLike m => diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/Background.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/Background.hs index ed8ce9bc97..37cfd65e27 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/Background.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/Background.hs @@ -41,7 +41,6 @@ module Ouroboros.Consensus.Storage.ChainDB.Impl.Background , addBlockRunner ) where -import Cardano.Ledger.BaseTypes (unNonZero) import Control.Exception (assert) import Control.Monad (forM_, forever, void) import Control.Monad.Trans.Class (lift) @@ -57,7 +56,6 @@ import Data.Word import GHC.Generics (Generic) import GHC.Stack (HasCallStack) import Ouroboros.Consensus.Block -import Ouroboros.Consensus.Config import Ouroboros.Consensus.HardFork.Abstract import Ouroboros.Consensus.Ledger.Inspect import Ouroboros.Consensus.Ledger.SupportsProtocol @@ -69,6 +67,7 @@ import Ouroboros.Consensus.Storage.ChainDB.API import Ouroboros.Consensus.Storage.ChainDB.Impl.ChainSel ( chainSelSync ) +import qualified Ouroboros.Consensus.Storage.ChainDB.Impl.Query as Query import Ouroboros.Consensus.Storage.ChainDB.Impl.Types import qualified Ouroboros.Consensus.Storage.ImmutableDB as ImmutableDB import qualified Ouroboros.Consensus.Storage.LedgerDB as LedgerDB @@ -132,10 +131,11 @@ launchBgTasks cdb@CDB{..} replayed = do Copying blocks from the VolatileDB to the ImmutableDB -------------------------------------------------------------------------------} --- | Copy the blocks older than @k@ from the VolatileDB to the ImmutableDB. +-- | Copy the blocks older than the immutable tip from the VolatileDB to the +-- ImmutableDB. -- --- These headers of these blocks can be retrieved by dropping the @k@ most --- recent blocks from the fragment stored in 'cdbChain'. +-- The headers of these blocks can be retrieved by considering headers in +-- 'cdbChain' that are not also in 'getCurrentChain' (a suffix of 'cdbChain'). -- -- The copied blocks are removed from the fragment stored in 'cdbChain'. -- @@ -153,10 +153,11 @@ copyToImmutableDB :: ) => ChainDbEnv m blk -> Electric m (WithOrigin SlotNo) -copyToImmutableDB CDB{..} = electric $ do +copyToImmutableDB cdb@CDB{..} = electric $ do toCopy <- atomically $ do curChain <- icWithoutTime <$> readTVar cdbChain - let nbToCopy = max 0 (AF.length curChain - fromIntegral (unNonZero k)) + curChainVolSuffix <- Query.getCurrentChain cdb + let nbToCopy = max 0 $ AF.length curChain - AF.length curChainVolSuffix toCopy :: [Point blk] toCopy = map headerPoint $ @@ -165,10 +166,10 @@ copyToImmutableDB CDB{..} = electric $ do return toCopy if null toCopy - -- This can't happen in practice, as we're only called when the fragment - -- is longer than @k@. However, in the tests, we will be calling this - -- function manually, which means it might be called when there are no - -- blocks to copy. + -- This can't happen in practice, as we're only called when there are new + -- immutable blocks. However, in the tests, we will be calling this function + -- manually, which means it might be called when there are no blocks to + -- copy. then trace NoBlocksToCopyToImmutableDB else forM_ toCopy $ \pt -> do let hash = case pointHash pt of @@ -193,7 +194,6 @@ copyToImmutableDB CDB{..} = electric $ do -- Get the /possibly/ updated tip of the ImmutableDB atomically $ ImmutableDB.getTipSlot cdbImmutableDB where - SecurityParam k = configSecurityParam cdbTopLevelConfig trace = traceWith (contramap TraceCopyToImmutableDBEvent cdbTracer) -- \| Remove the header corresponding to the given point from the beginning @@ -218,9 +218,11 @@ copyToImmutableDB CDB{..} = electric $ do -- | Copy blocks from the VolatileDB to ImmutableDB and trigger further tasks in -- other threads. -- --- We watch the chain for changes. Whenever the chain is longer than @k@, then --- the headers older than @k@ are copied from the VolatileDB to the ImmutableDB --- (using 'copyToImmutableDB'). Once that is complete, +-- Wait until the current chain ('cdbChain') is longer than its volatile suffix +-- ('getCurrentChain'). When this occurs, it indicates that new blocks have +-- become immutable. These newly immutable blocks are then copied from the +-- VolatileDB to the ImmutableDB (using 'copyToImmutableDB'). Once that is +-- complete, -- -- * Trigger LedgerDB maintenance tasks, namely flushing, taking snapshots and -- garbage collection. @@ -254,15 +256,15 @@ copyToImmutableDBRunner cdb@CDB{..} ledgerDbTasksTrigger gcSchedule fuse = do LedgerDB.tryFlush cdbLedgerDB forever copyAndTrigger where - SecurityParam k = configSecurityParam cdbTopLevelConfig - copyAndTrigger :: m () copyAndTrigger = do - -- Wait for the chain to grow larger than @k@ + -- Wait for 'cdbChain' to become longer than 'getCurrentChain'. numToWrite <- atomically $ do curChain <- icWithoutTime <$> readTVar cdbChain - check $ fromIntegral (AF.length curChain) > unNonZero k - return $ fromIntegral (AF.length curChain) - unNonZero k + curChainVolSuffix <- Query.getCurrentChain cdb + let numToWrite = AF.length curChain - AF.length curChainVolSuffix + check $ numToWrite > 0 + return $ fromIntegral numToWrite -- Copy blocks to ImmutableDB -- diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/ChainSel.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/ChainSel.hs index 64abc21d14..a16e674b3d 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/ChainSel.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/ChainSel.hs @@ -19,7 +19,7 @@ module Ouroboros.Consensus.Storage.ChainDB.Impl.ChainSel , triggerChainSelectionAsync -- * Exported for testing purposes - , olderThanK + , olderThanImmTip ) where import Cardano.Ledger.BaseTypes (unNonZero) @@ -411,8 +411,8 @@ chainSelSync cdb@CDB{..} (ChainSelAddBlock BlockToAdd{blockToAdd = b, ..}) = do -- We follow the steps from section "## Adding a block" in ChainDB.md if - | olderThanK hdr isEBB immBlockNo -> do - lift $ traceWith addBlockTracer $ IgnoreBlockOlderThanK (blockRealPoint b) + | olderThanImmTip hdr immBlockNo -> do + lift $ traceWith addBlockTracer $ IgnoreBlockOlderThanImmTip (blockRealPoint b) lift $ deliverWrittenToDisk False | isMember (blockHash b) -> do lift $ traceWith addBlockTracer $ IgnoreBlockAlreadyInVolatileDB (blockRealPoint b) @@ -466,31 +466,28 @@ chainSelSync cdb@CDB{..} (ChainSelAddBlock BlockToAdd{blockToAdd = b, ..}) = do -- | Return 'True' when the given header should be ignored when adding it -- because it is too old, i.e., we wouldn't be able to switch to a chain --- containing the corresponding block because its block number is more than --- @k@ blocks or exactly @k@ blocks back. +-- containing the corresponding block because its block number is (weakly) older +-- than that of the immutable tip. -- -- Special case: the header corresponds to an EBB which has the same block --- number as the block @k@ blocks back (the most recent \"immutable\" block). --- As EBBs share their block number with the block before them, the EBB is not --- too old in that case and can be adopted as part of our chain. +-- number as the most recent \"immutable\" block. As EBBs share their block +-- number with the block before them, the EBB is not too old in that case and +-- can be adopted as part of our chain. -- -- This special case can occur, for example, when the VolatileDB is empty -- (because of corruption). The \"immutable\" block is then also the tip of -- the chain. If we then try to add the EBB after it, it will have the same -- block number, so we must allow it. -olderThanK :: - HasHeader (Header blk) => +olderThanImmTip :: + GetHeader blk => -- | Header of the block to add Header blk -> - -- | Whether the block is an EBB or not - IsEBB -> - -- | The block number of the most recent \"immutable\" block, i.e., the - -- block @k@ blocks back. + -- | The block number of the most recent immutable block. WithOrigin BlockNo -> Bool -olderThanK hdr isEBB immBlockNo +olderThanImmTip hdr immBlockNo | NotOrigin bNo == immBlockNo - , isEBB == IsEBB = + , headerToIsEBB hdr == IsEBB = False | otherwise = NotOrigin bNo <= immBlockNo @@ -559,9 +556,9 @@ chainSelectionForBlock cdb@CDB{..} blockCache hdr punish = electric $ withRegist if -- The chain might have grown since we added the block such that the - -- block is older than @k@. - | olderThanK hdr isEBB immBlockNo -> do - traceWith addBlockTracer $ IgnoreBlockOlderThanK p + -- block is older than the immutable tip. + | olderThanImmTip hdr immBlockNo -> do + traceWith addBlockTracer $ IgnoreBlockOlderThanImmTip p -- The block is invalid | Just (InvalidBlockInfo reason _) <- Map.lookup (headerHash hdr) invalid -> do @@ -609,9 +606,6 @@ chainSelectionForBlock cdb@CDB{..} blockCache hdr punish = electric $ withRegist p :: RealPoint blk p = headerRealPoint hdr - isEBB :: IsEBB - isEBB = headerToIsEBB hdr - addBlockTracer :: Tracer m (TraceAddBlockEvent blk) addBlockTracer = TraceAddBlockEvent >$< cdbTracer diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/Types.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/Types.hs index 61a3c7380d..30193ba314 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/Types.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/ChainDB/Impl/Types.hs @@ -815,9 +815,8 @@ deriving stock instance -- | Trace type for the various events that occur when adding a block. data TraceAddBlockEvent blk - = -- | A block with a 'BlockNo' more than @k@ back than the current tip was - -- ignored. - IgnoreBlockOlderThanK (RealPoint blk) + = -- | A block with a 'BlockNo' not newer than the immutable tip was ignored. + IgnoreBlockOlderThanImmTip (RealPoint blk) | -- | A block that is already in the Volatile DB was ignored. IgnoreBlockAlreadyInVolatileDB (RealPoint blk) | -- | A block that is know to be invalid was ignored. diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB.hs index 4b03b5e22d..0d1380d505 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB.hs @@ -62,12 +62,14 @@ openDB :: Point blk -> -- | How to get blocks from the ChainDB ResolveBlock m blk -> + GetVolatileSuffix m blk -> m (LedgerDB' m blk, Word64) openDB args stream replayGoal - getBlock = case lgrFlavorArgs args of + getBlock + getVolatileSuffix = case lgrFlavorArgs args of LedgerDbFlavorArgsV1 bss -> let snapManager = V1.snapshotManager args initDb = @@ -76,6 +78,7 @@ openDB bss getBlock snapManager + getVolatileSuffix in doOpenDB args initDb snapManager stream replayGoal LedgerDbFlavorArgsV2 bss -> do (snapManager, bss') <- case bss of @@ -87,6 +90,7 @@ openDB bss' getBlock snapManager + getVolatileSuffix doOpenDB args initDb snapManager stream replayGoal {------------------------------------------------------------------------------- diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/Args.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/Args.hs index 63935c89fa..fa3835306a 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/Args.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/Args.hs @@ -2,7 +2,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} @@ -22,14 +22,21 @@ module Ouroboros.Consensus.Storage.LedgerDB.Args , QueryBatchSize (..) , defaultArgs , defaultQueryBatchSize + + -- * 'GetVolatileSuffix' + , GetVolatileSuffix (..) + , praosGetVolatileSuffix ) where +import Cardano.Ledger.BaseTypes (unNonZero) import Control.ResourceRegistry import Control.Tracer import Data.Kind import Data.Word import GHC.Generics (Generic) import NoThunks.Class +import Ouroboros.Consensus.Block +import Ouroboros.Consensus.Config.SecurityParam import Ouroboros.Consensus.Ledger.Abstract import Ouroboros.Consensus.Ledger.Extended import Ouroboros.Consensus.Storage.LedgerDB.API @@ -38,6 +45,9 @@ import Ouroboros.Consensus.Storage.LedgerDB.TraceEvent import qualified Ouroboros.Consensus.Storage.LedgerDB.V1.Args as V1 import qualified Ouroboros.Consensus.Storage.LedgerDB.V2.Args as V2 import Ouroboros.Consensus.Util.Args +import Ouroboros.Consensus.Util.IOLike +import Ouroboros.Network.AnchoredSeq (AnchoredSeq) +import qualified Ouroboros.Network.AnchoredSeq as AS import System.FS.API {------------------------------------------------------------------------------- @@ -120,3 +130,28 @@ defaultQueryBatchSize requestedQueryBatchSize = case requestedQueryBatchSize of -- acceptable performance. We might want to tweak this further, but for now -- this default seems good enough. DefaultQueryBatchSize -> 100_000 + +{------------------------------------------------------------------------------- + GetVolatileSuffix +-------------------------------------------------------------------------------} + +-- | Get the volatile suffix of the given 'AnchoredSeq' of states that the +-- LedgerDB maintains. +newtype GetVolatileSuffix m blk = GetVolatileSuffix + { getVolatileSuffix :: + forall s. + AS.Anchorable (WithOrigin SlotNo) s s => + STM + m + ( AnchoredSeq (WithOrigin SlotNo) s s -> + AnchoredSeq (WithOrigin SlotNo) s s + ) + } + deriving NoThunks via OnlyCheckWhnfNamed "GetVolatileSuffix" (GetVolatileSuffix m blk) + +-- | Return the the most recent @k@ blocks, which is the rule mandated by Praos. +praosGetVolatileSuffix :: IOLike m => SecurityParam -> GetVolatileSuffix m blk +praosGetVolatileSuffix secParam = + GetVolatileSuffix $ pure $ AS.anchorNewest k + where + k = unNonZero $ maxRollbacks secParam diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/V1.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/V1.hs index c66f4152e3..13a9847273 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/V1.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/V1.hs @@ -19,7 +19,6 @@ -- module will be gone. module Ouroboros.Consensus.Storage.LedgerDB.V1 (mkInitDb) where -import Cardano.Ledger.BaseTypes.NonZero (NonZero (..)) import Control.Arrow ((>>>)) import Control.Monad import Control.Monad.Except @@ -87,8 +86,9 @@ mkInitDb :: Complete V1.LedgerDbFlavorArgs m -> ResolveBlock m blk -> SnapshotManagerV1 m blk -> + GetVolatileSuffix m blk -> InitDB (DbChangelog' blk, ResourceKey m, BackingStore' m blk) m blk -mkInitDb args bss getBlock snapManager = +mkInitDb args bss getBlock snapManager getVolatileSuffix = InitDB { initFromGenesis = do st <- lgrGenesis @@ -146,6 +146,7 @@ mkInitDb args bss getBlock snapManager = , ldbShouldFlush = shouldFlush flushFreq , ldbQueryBatchSize = lgrQueryBatchSize , ldbResolveBlock = getBlock + , ldbGetVolatileSuffix = getVolatileSuffix } h <- LDBHandle <$> newTVarIO (LedgerDBOpen env) pure $ implMkLedgerDb h snapManager @@ -208,10 +209,11 @@ implGetImmutableTip :: (MonadSTM m, GetTip l) => LedgerDBEnv m l blk -> STM m (l EmptyMK) -implGetImmutableTip env = +implGetImmutableTip env = do + volSuffix <- getVolatileSuffix $ ldbGetVolatileSuffix env -- The DbChangelog might contain more than k states if they have not yet -- been garbage-collected. - fmap (AS.anchor . AS.anchorNewest (envMaxRollbacks env) . changelogStates) + fmap (AS.anchor . volSuffix . changelogStates) . readTVar $ ldbChangelog env @@ -224,7 +226,8 @@ implGetPastLedgerState :: , HeaderHash l ~ HeaderHash blk ) => LedgerDBEnv m l blk -> Point blk -> STM m (Maybe (l EmptyMK)) -implGetPastLedgerState env point = +implGetPastLedgerState env point = do + volSuffix <- getVolatileSuffix $ ldbGetVolatileSuffix env readTVar (ldbChangelog env) <&> \chlog -> do -- The DbChangelog might contain more than k states if they have not yet -- been garbage-collected, so make sure that the point is volatile (or the @@ -233,7 +236,7 @@ implGetPastLedgerState env point = AS.withinBounds (pointSlot point) ((point ==) . castPoint . either getTip getTip) - (AS.anchorNewest (envMaxRollbacks env) (changelogStates chlog)) + (volSuffix (changelogStates chlog)) getPastLedgerAt point chlog implGetHeaderStateHistory :: @@ -246,6 +249,7 @@ implGetHeaderStateHistory :: LedgerDBEnv m l blk -> STM m (HeaderStateHistory blk) implGetHeaderStateHistory env = do ldb <- readTVar (ldbChangelog env) + volSuffix <- getVolatileSuffix $ ldbGetVolatileSuffix env let currentLedgerState = ledgerState $ current ldb -- This summary can convert all tip slots of the ledger states in the -- @ledgerDb@ as these are not newer than the tip slot of the current @@ -259,7 +263,7 @@ implGetHeaderStateHistory env = do . AS.bimap mkHeaderStateWithTime' mkHeaderStateWithTime' -- The DbChangelog might contain more than k states if they have not yet -- been garbage-collected, so only take the corresponding suffix. - . AS.anchorNewest (envMaxRollbacks env) + . volSuffix $ changelogStates ldb implValidate :: @@ -575,6 +579,7 @@ data LedgerDBEnv m l blk = LedgerDBEnv -- frequency that was provided when opening the LedgerDB. , ldbQueryBatchSize :: !QueryBatchSize , ldbResolveBlock :: !(ResolveBlock m blk) + , ldbGetVolatileSuffix :: !(GetVolatileSuffix m blk) } deriving Generic @@ -588,10 +593,6 @@ deriving instance ) => NoThunks (LedgerDBEnv m l blk) --- | Return the security parameter @k@. Convenience function. -envMaxRollbacks :: LedgerDBEnv m l blk -> Word64 -envMaxRollbacks = unNonZero . maxRollbacks . ledgerDbCfgSecParam . ldbCfg - -- | Check if the LedgerDB is open, if so, executing the given function on the -- 'LedgerDBEnv', otherwise, throw a 'CloseDBError'. getEnv :: @@ -763,10 +764,16 @@ acquireAtTarget :: ReadLocked m (Either GetForkerError (DbChangelog l)) acquireAtTarget ldbEnv target = readLocked $ runExceptT $ do dblog <- lift $ readTVarIO (ldbChangelog ldbEnv) + volSuffix <- lift $ atomically $ getVolatileSuffix $ ldbGetVolatileSuffix ldbEnv -- The DbChangelog might contain more than k states if they have not yet -- been garbage-collected. - let immTip :: Point blk - immTip = castPoint $ getTip $ AS.anchor $ AS.anchorNewest k $ changelogStates dblog + let volStates = volSuffix $ changelogStates dblog + + immTip :: Point blk + immTip = castPoint $ getTip $ AS.anchor volStates + + rollbackMax :: Word64 + rollbackMax = fromIntegral $ AS.length volStates rollbackTo pt | pointSlot pt < pointSlot immTip = throwError $ PointTooOld Nothing @@ -779,7 +786,6 @@ acquireAtTarget ldbEnv target = readLocked $ runExceptT $ do Right ImmutableTip -> rollbackTo immTip Right (SpecificPoint pt) -> rollbackTo pt Left n -> do - let rollbackMax = maxRollback dblog `min` k when (n > rollbackMax) $ throwError $ PointTooOld $ @@ -791,8 +797,6 @@ acquireAtTarget ldbEnv target = readLocked $ runExceptT $ do case rollbackN n dblog of Nothing -> error "unreachable" Just dblog' -> pure dblog' - where - k = envMaxRollbacks ldbEnv {------------------------------------------------------------------------------- Make forkers from consistent views diff --git a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/V2.hs b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/V2.hs index ef6ba64882..b6c6f6b087 100644 --- a/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/V2.hs +++ b/ouroboros-consensus/src/ouroboros-consensus/Ouroboros/Consensus/Storage/LedgerDB/V2.hs @@ -16,7 +16,6 @@ module Ouroboros.Consensus.Storage.LedgerDB.V2 (mkInitDb) where -import Cardano.Ledger.BaseTypes (unNonZero) import Control.Arrow ((>>>)) import qualified Control.Monad as Monad (join, void) import Control.Monad.Except @@ -80,8 +79,9 @@ mkInitDb :: HandleEnv m -> ResolveBlock m blk -> SnapshotManagerV2 m blk -> + GetVolatileSuffix m blk -> InitDB (LedgerSeq' m blk) m blk -mkInitDb args bss getBlock snapManager = +mkInitDb args bss getBlock snapManager getVolatileSuffix = InitDB { initFromGenesis = emptyF =<< lgrGenesis , initFromSnapshot = @@ -116,6 +116,7 @@ mkInitDb args bss getBlock snapManager = , ldbOpenHandlesLock = lock , ldbResourceKeys = case bss of InMemoryHandleEnv -> Nothing + , ldbGetVolatileSuffix = getVolatileSuffix } h <- LDBHandle <$> newTVarIO (LedgerDBOpen env) pure $ implMkLedgerDb h snapManager @@ -455,6 +456,7 @@ data LedgerDBEnv m l blk = LedgerDBEnv -- ^ Resource keys used in the LSM backend so that the closing function used -- in tests can release such resources. These are the resource keys for the -- LSM session and the resource key for the BlockIO interface. + , ldbGetVolatileSuffix :: !(GetVolatileSuffix m blk) } deriving Generic @@ -559,15 +561,16 @@ getEnvSTM (LDBHandle varState) f = Acquiring consistent views -------------------------------------------------------------------------------} --- | Take the suffix of the 'ldbSeq' containing the @k@ most recent states. The --- 'LedgerSeq' can contain more than @k@ states if we adopted new blocks, but --- garbage collection has not yet been run. +-- | Take the suffix of the 'ldbSeq' containing the only the volatile states +-- (and the first immutable state at the anchor). The 'LedgerSeq' can contain +-- more than one immutable state if we adopted new blocks, but garbage +-- collection has not yet been run. getVolatileLedgerSeq :: - (MonadSTM m, GetTip l) => LedgerDBEnv m l blk -> STM m (LedgerSeq m l) -getVolatileLedgerSeq env = - LedgerSeq . AS.anchorNewest k . getLedgerSeq <$> readTVar (ldbSeq env) - where - k = unNonZero $ maxRollbacks $ ledgerDbCfgSecParam $ ldbCfg env + (MonadSTM m, GetTip l) => + LedgerDBEnv m l blk -> STM m (LedgerSeq m l) +getVolatileLedgerSeq env = do + volSuffix <- getVolatileSuffix (ldbGetVolatileSuffix env) + LedgerSeq . volSuffix . getLedgerSeq <$> readTVar (ldbSeq env) -- | Get a 'StateRef' from the 'LedgerSeq' in the 'LedgerDBEnv', with the -- 'LedgerTablesHandle' having been duplicated (such that the original can be diff --git a/ouroboros-consensus/test/consensus-test/Test/Consensus/MiniProtocol/LocalStateQuery/Server.hs b/ouroboros-consensus/test/consensus-test/Test/Consensus/MiniProtocol/LocalStateQuery/Server.hs index 0ab66ab540..efafdc18aa 100644 --- a/ouroboros-consensus/test/consensus-test/Test/Consensus/MiniProtocol/LocalStateQuery/Server.hs +++ b/ouroboros-consensus/test/consensus-test/Test/Consensus/MiniProtocol/LocalStateQuery/Server.hs @@ -244,6 +244,7 @@ initLedgerDB s c = do streamAPI (Chain.headPoint c) (\rpt -> pure $ fromMaybe (error "impossible") $ Chain.findBlock ((rpt ==) . blockRealPoint) c) + (LedgerDB.praosGetVolatileSuffix s) result <- LedgerDB.validateFork diff --git a/ouroboros-consensus/test/storage-test/Test/Ouroboros/Storage/ChainDB/Model.hs b/ouroboros-consensus/test/storage-test/Test/Ouroboros/Storage/ChainDB/Model.hs index 3369265f5e..d8cbf1acb0 100644 --- a/ouroboros-consensus/test/storage-test/Test/Ouroboros/Storage/ChainDB/Model.hs +++ b/ouroboros-consensus/test/storage-test/Test/Ouroboros/Storage/ChainDB/Model.hs @@ -122,7 +122,7 @@ import Ouroboros.Consensus.Storage.ChainDB.API , UnknownRange (..) , validBounds ) -import Ouroboros.Consensus.Storage.ChainDB.Impl.ChainSel (olderThanK) +import Ouroboros.Consensus.Storage.ChainDB.Impl.ChainSel (olderThanImmTip) import Ouroboros.Consensus.Storage.Common () import Ouroboros.Consensus.Util (repeatedly) import qualified Ouroboros.Consensus.Util.AnchoredFragment as Fragment @@ -415,7 +415,7 @@ addBlock cfg blk m ignoreBlock = -- If the block is as old as the tip of the ImmutableDB, i.e. older -- than @k@, we ignore it, as we can never switch to it. - olderThanK hdr (headerToIsEBB hdr) immBlockNo + olderThanImmTip hdr immBlockNo || -- If it's an invalid block we've seen before, ignore it. Map.member (blockHash blk) (invalid m) diff --git a/ouroboros-consensus/test/storage-test/Test/Ouroboros/Storage/LedgerDB/StateMachine.hs b/ouroboros-consensus/test/storage-test/Test/Ouroboros/Storage/LedgerDB/StateMachine.hs index 786951c7ea..5ab41c0551 100644 --- a/ouroboros-consensus/test/storage-test/Test/Ouroboros/Storage/LedgerDB/StateMachine.hs +++ b/ouroboros-consensus/test/storage-test/Test/Ouroboros/Storage/LedgerDB/StateMachine.hs @@ -516,6 +516,7 @@ openLedgerDB flavArgs env cfg fs = do bss getBlock snapManager + (praosGetVolatileSuffix $ ledgerDbCfgSecParam cfg) in openDBInternal args initDb snapManager stream replayGoal LedgerDbFlavorArgsV2 bss -> do (snapManager, bss') <- case bss of @@ -527,6 +528,7 @@ openLedgerDB flavArgs env cfg fs = do bss' getBlock snapManager + (praosGetVolatileSuffix $ ledgerDbCfgSecParam cfg) openDBInternal args initDb snapManager stream replayGoal withRegistry $ \reg -> do vr <- validateFork ldb reg (const $ pure ()) BlockCache.empty 0 (map getHeader volBlocks)