From 7152932ac311438e24d3c3464185da5c44fe6238 Mon Sep 17 00:00:00 2001 From: Georgy Lukyanov Date: Fri, 10 Apr 2026 09:07:24 +0200 Subject: [PATCH 1/6] [wip] Add SRP for `ouroboros-consensus` --- cabal.project | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cabal.project b/cabal.project index c818a53ab0c..505a34dd01c 100644 --- a/cabal.project +++ b/cabal.project @@ -86,3 +86,13 @@ allow-newer: -- IMPORTANT -- Do NOT add more source-repository-package stanzas here unless they are strictly -- temporary! Please read the section in CONTRIBUTING about updating dependencies. + +-- points to a temporary branch geo2a/ledger-snaphot-param-revision-for-node +-- The changes from that branch are already on ouroboros-consensus/main, but +-- the preceding changes on main are not yet integrated into the cardano-node/main +source-repository-package + type: git + location: https://github.com/IntersectMBO/ouroboros-consensus + tag: d1a31119402e6237c58a87e5940d246af59427e6 + --sha256: sha256-jX4Gav8StCSBDuqso2OJ1maTYU0oRQpUfCelIaHtbxo= + subdir: . From 0203977a0a7cab988f0c70c2f2267be61e1d635a Mon Sep 17 00:00:00 2001 From: Georgy Lukyanov Date: Wed, 22 Apr 2026 09:40:16 +0200 Subject: [PATCH 2/6] Integrate new PerasVoteDB and updated PerasCertDB trace events --- .../Cardano/Node/Tracing/Tracers/ChainDB.hs | 149 +++++++++++------- 1 file changed, 94 insertions(+), 55 deletions(-) diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs index 1427af94e67..dba57f0a828 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs @@ -49,6 +49,7 @@ import qualified Ouroboros.Consensus.Storage.LedgerDB.V2.Backend as V2 import qualified Ouroboros.Consensus.Storage.LedgerDB.V2.InMemory as InMemory import qualified Ouroboros.Consensus.Storage.LedgerDB.V2.LSM as LSM import qualified Ouroboros.Consensus.Storage.PerasCertDB.Impl as PerasCertDB +import qualified Ouroboros.Consensus.Storage.PerasVoteDB.Impl as PerasVoteDB import qualified Ouroboros.Consensus.Storage.VolatileDB as VolDB import Ouroboros.Consensus.TypeFamilyWrappers import Ouroboros.Consensus.Util.Condense (condense) @@ -120,6 +121,7 @@ instance ( LogFormatting (Header blk) ChainDB.ChainSelStarvation (FallingEdgeWith pt) -> "Chain Selection was unstarved by " <> renderRealPoint pt forHuman (ChainDB.TracePerasCertDbEvent ev) = forHuman ev + forHuman (ChainDB.TracePerasVoteDbEvent ev) = forHuman ev forHuman (ChainDB.TraceAddPerasCertEvent ev) = forHuman ev forMachine _ ChainDB.TraceLastShutdownUnclean = @@ -152,6 +154,8 @@ instance ( LogFormatting (Header blk) forMachine details v forMachine details (ChainDB.TracePerasCertDbEvent v) = forMachine details v + forMachine details (ChainDB.TracePerasVoteDbEvent v) = + forMachine details v forMachine details (ChainDB.TraceAddPerasCertEvent v) = forMachine details v @@ -169,6 +173,7 @@ instance ( LogFormatting (Header blk) asMetrics (ChainDB.TraceImmutableDBEvent v) = asMetrics v asMetrics (ChainDB.TraceVolatileDBEvent v) = asMetrics v asMetrics (ChainDB.TracePerasCertDbEvent v) = asMetrics v + asMetrics (ChainDB.TracePerasVoteDbEvent v) = asMetrics v asMetrics (ChainDB.TraceAddPerasCertEvent v) = asMetrics v @@ -199,6 +204,8 @@ instance MetaTrace (ChainDB.TraceEvent blk) where nsPrependInner "VolatileDbEvent" (namespaceFor ev) namespaceFor (ChainDB.TracePerasCertDbEvent ev) = nsPrependInner "PerasCertDbEvent" (namespaceFor ev) + namespaceFor (ChainDB.TracePerasVoteDbEvent ev) = + nsPrependInner "PerasVoteDbEvent" (namespaceFor ev) namespaceFor (ChainDB.TraceAddPerasCertEvent ev) = nsPrependInner "AddPerasCertEvent" (namespaceFor ev) @@ -248,6 +255,10 @@ instance MetaTrace (ChainDB.TraceEvent blk) where severityFor (Namespace out tl) (Just ev') severityFor (Namespace out ("PerasCertDbEvent" : tl)) Nothing = severityFor (Namespace out tl :: Namespace (PerasCertDB.TraceEvent blk)) Nothing + severityFor (Namespace out ("PerasVoteDbEvent" : tl)) (Just (ChainDB.TracePerasVoteDbEvent ev')) = + severityFor (Namespace out tl) (Just ev') + severityFor (Namespace out ("PerasVoteDbEvent" : tl)) Nothing = + severityFor (Namespace out tl :: Namespace (PerasVoteDB.TraceEvent blk)) Nothing severityFor (Namespace out ("AddPerasCertEvent" : tl)) (Just (ChainDB.TraceAddPerasCertEvent ev')) = severityFor (Namespace out tl) (Just ev') severityFor (Namespace out ("AddPerasCertEvent" : tl)) Nothing = @@ -300,6 +311,10 @@ instance MetaTrace (ChainDB.TraceEvent blk) where privacyFor (Namespace out tl) (Just ev') privacyFor (Namespace out ("PerasCertDbEvent" : tl)) Nothing = privacyFor (Namespace out tl :: Namespace (PerasCertDB.TraceEvent blk)) Nothing + privacyFor (Namespace out ("PerasVoteDbEvent" : tl)) (Just (ChainDB.TracePerasVoteDbEvent ev')) = + privacyFor (Namespace out tl) (Just ev') + privacyFor (Namespace out ("PerasVoteDbEvent" : tl)) Nothing = + privacyFor (Namespace out tl :: Namespace (PerasVoteDB.TraceEvent blk)) Nothing privacyFor (Namespace out ("AddPerasCertEvent" : tl)) (Just (ChainDB.TraceAddPerasCertEvent ev')) = privacyFor (Namespace out tl) (Just ev') privacyFor (Namespace out ("AddPerasCertEvent" : tl)) Nothing = @@ -352,6 +367,10 @@ instance MetaTrace (ChainDB.TraceEvent blk) where detailsFor (Namespace out tl) (Just ev') detailsFor (Namespace out ("PerasCertDbEvent" : tl)) Nothing = detailsFor (Namespace out tl :: Namespace (PerasCertDB.TraceEvent blk)) Nothing + detailsFor (Namespace out ("PerasVoteDbEvent" : tl)) (Just (ChainDB.TracePerasVoteDbEvent ev')) = + detailsFor (Namespace out tl) (Just ev') + detailsFor (Namespace out ("PerasVoteDbEvent" : tl)) Nothing = + detailsFor (Namespace out tl :: Namespace (PerasVoteDB.TraceEvent blk)) Nothing detailsFor (Namespace out ("AddPerasCertEvent" : tl)) (Just (ChainDB.TraceAddPerasCertEvent ev')) = detailsFor (Namespace out tl) (Just ev') detailsFor (Namespace out ("AddPerasCertEvent" : tl)) Nothing = @@ -411,6 +430,8 @@ instance MetaTrace (ChainDB.TraceEvent blk) where documentFor (Namespace out tl :: Namespace (VolDB.TraceEvent blk)) documentFor (Namespace out ("PerasCertDbEvent" : tl)) = documentFor (Namespace out tl :: Namespace (PerasCertDB.TraceEvent blk)) + documentFor (Namespace out ("PerasVoteDbEvent" : tl)) = + documentFor (Namespace out tl :: Namespace (PerasVoteDB.TraceEvent blk)) documentFor (Namespace out ("AddPerasCertEvent" : tl)) = documentFor (Namespace out tl :: Namespace (ChainDB.TraceAddPerasCertEvent blk)) documentFor _ = Nothing @@ -440,6 +461,8 @@ instance MetaTrace (ChainDB.TraceEvent blk) where (allNamespaces :: [Namespace (VolDB.TraceEvent blk)]) ++ map (nsPrependInner "PerasCertDbEvent") (allNamespaces :: [Namespace (PerasCertDB.TraceEvent blk)]) + ++ map (nsPrependInner "PerasVoteDbEvent") + (allNamespaces :: [Namespace (PerasVoteDB.TraceEvent blk)]) ++ map (nsPrependInner "AddPerasCertEvent") (allNamespaces :: [Namespace (ChainDB.TraceAddPerasCertEvent blk)]) ) @@ -3090,25 +3113,35 @@ instance (Show (PBFT.PBftVerKeyHash c)) -- PerasCertDB.TraceEvent instances instance LogFormatting (PerasCertDB.TraceEvent blk) where - forHuman (PerasCertDB.AddedPerasCert _cert _peer) = "Added Peras certificate to database" - forHuman (PerasCertDB.IgnoredCertAlreadyInDB _cert _peer) = "Ignored Peras certificate already in database" - forHuman PerasCertDB.OpenedPerasCertDB = "Opened Peras certificate database" - forHuman PerasCertDB.ClosedPerasCertDB = "Closed Peras certificate database" - forHuman (PerasCertDB.AddingPerasCert _cert _peer) = "Adding Peras certificate to database" - - forMachine _dtal (PerasCertDB.AddedPerasCert cert _peer) = - mconcat ["kind" .= String "AddedPerasCert", - "cert" .= String (Text.pack $ show cert)] - forMachine _dtal (PerasCertDB.IgnoredCertAlreadyInDB cert _peer) = - mconcat ["kind" .= String "IgnoredCertAlreadyInDB", - "cert" .= String (Text.pack $ show cert)] - forMachine _dtal PerasCertDB.OpenedPerasCertDB = - mconcat ["kind" .= String "OpenedPerasCertDB"] - forMachine _dtal PerasCertDB.ClosedPerasCertDB = - mconcat ["kind" .= String "ClosedPerasCertDB"] - forMachine _dtal (PerasCertDB.AddingPerasCert cert _peer) = - mconcat ["kind" .= String "AddingPerasCert", - "cert" .= String (Text.pack $ show cert)] + forHuman (PerasCertDB.AddCert roundNo _cert result) = + "Peras certificate for round " <> Text.pack (show roundNo) <> ": " <> Text.pack (show result) + forHuman (PerasCertDB.GarbageCollected slotNo) = + "Peras certificate DB garbage collected at slot " <> Text.pack (show slotNo) + + forMachine _dtal (PerasCertDB.AddCert roundNo _cert result) = + mconcat ["kind" .= String "AddCert", + "round" .= String (Text.pack $ show roundNo), + "result" .= String (Text.pack $ show result)] + forMachine _dtal (PerasCertDB.GarbageCollected slotNo) = + mconcat ["kind" .= String "GarbageCollected", + "slot" .= String (Text.pack $ show slotNo)] + + asMetrics _ = [] + +-- PerasVoteDB.TraceEvent instances +instance StandardHash blk => LogFormatting (PerasVoteDB.TraceEvent blk) where + forHuman (PerasVoteDB.AddVote voteId _vote result) = + "Peras vote " <> Text.pack (show voteId) <> ": " <> Text.pack (show result) + forHuman (PerasVoteDB.GarbageCollected slotNo) = + "Peras vote DB garbage collected at slot " <> Text.pack (show slotNo) + + forMachine _dtal (PerasVoteDB.AddVote voteId _vote result) = + mconcat ["kind" .= String "AddVote", + "voteId" .= String (Text.pack $ show voteId), + "result" .= String (Text.pack $ show result)] + forMachine _dtal (PerasVoteDB.GarbageCollected slotNo) = + mconcat ["kind" .= String "GarbageCollected", + "slot" .= String (Text.pack $ show slotNo)] asMetrics _ = [] @@ -3170,51 +3203,57 @@ instance ConvertRawHash blk => LogFormatting (ChainDB.TraceAddPerasCertEvent blk -- PerasCertDB.TraceEvent MetaTrace instance instance MetaTrace (PerasCertDB.TraceEvent blk) where - namespaceFor (PerasCertDB.AddedPerasCert _ _) = - Namespace [] ["AddedPerasCert"] - namespaceFor (PerasCertDB.IgnoredCertAlreadyInDB _ _) = - Namespace [] ["IgnoredCertAlreadyInDB"] - namespaceFor PerasCertDB.OpenedPerasCertDB = - Namespace [] ["OpenedPerasCertDB"] - namespaceFor PerasCertDB.ClosedPerasCertDB = - Namespace [] ["ClosedPerasCertDB"] - namespaceFor (PerasCertDB.AddingPerasCert _ _) = - Namespace [] ["AddingPerasCert"] - - severityFor (Namespace _ ["AddedPerasCert"]) _ = Just Info - severityFor (Namespace _ ["IgnoredCertAlreadyInDB"]) _ = Just Info - severityFor (Namespace _ ["OpenedPerasCertDB"]) _ = Just Info - severityFor (Namespace _ ["ClosedPerasCertDB"]) _ = Just Info - severityFor (Namespace _ ["AddingPerasCert"]) _ = Just Debug + namespaceFor PerasCertDB.AddCert{} = + Namespace [] ["AddCert"] + namespaceFor PerasCertDB.GarbageCollected{} = + Namespace [] ["GarbageCollected"] + + severityFor (Namespace _ ["AddCert"]) _ = Just Info + severityFor (Namespace _ ["GarbageCollected"]) _ = Just Debug + severityFor _ _ = Nothing + + privacyFor (Namespace _ ["AddCert"]) _ = Just Public + privacyFor (Namespace _ ["GarbageCollected"]) _ = Just Public + privacyFor _ _ = Nothing + + detailsFor (Namespace _ ["AddCert"]) _ = Just DNormal + detailsFor (Namespace _ ["GarbageCollected"]) _ = Just DNormal + detailsFor _ _ = Nothing + + documentFor (Namespace _ ["AddCert"]) = Just "Certificate added to Peras certificate database" + documentFor (Namespace _ ["GarbageCollected"]) = Just "Garbage collection performed on Peras certificate database" + documentFor _ = Nothing + + allNamespaces = + [Namespace [] ["AddCert"], + Namespace [] ["GarbageCollected"]] + +-- PerasVoteDB.TraceEvent MetaTrace instance +instance MetaTrace (PerasVoteDB.TraceEvent blk) where + namespaceFor PerasVoteDB.AddVote{} = + Namespace [] ["AddVote"] + namespaceFor PerasVoteDB.GarbageCollected{} = + Namespace [] ["GarbageCollected"] + + severityFor (Namespace _ ["AddVote"]) _ = Just Info + severityFor (Namespace _ ["GarbageCollected"]) _ = Just Debug severityFor _ _ = Nothing - privacyFor (Namespace _ ["AddedPerasCert"]) _ = Just Public - privacyFor (Namespace _ ["IgnoredCertAlreadyInDB"]) _ = Just Public - privacyFor (Namespace _ ["OpenedPerasCertDB"]) _ = Just Public - privacyFor (Namespace _ ["ClosedPerasCertDB"]) _ = Just Public - privacyFor (Namespace _ ["AddingPerasCert"]) _ = Just Public + privacyFor (Namespace _ ["AddVote"]) _ = Just Public + privacyFor (Namespace _ ["GarbageCollected"]) _ = Just Public privacyFor _ _ = Nothing - detailsFor (Namespace _ ["AddedPerasCert"]) _ = Just DNormal - detailsFor (Namespace _ ["IgnoredCertAlreadyInDB"]) _ = Just DNormal - detailsFor (Namespace _ ["OpenedPerasCertDB"]) _ = Just DNormal - detailsFor (Namespace _ ["ClosedPerasCertDB"]) _ = Just DNormal - detailsFor (Namespace _ ["AddingPerasCert"]) _ = Just DDetailed + detailsFor (Namespace _ ["AddVote"]) _ = Just DNormal + detailsFor (Namespace _ ["GarbageCollected"]) _ = Just DNormal detailsFor _ _ = Nothing - documentFor (Namespace _ ["AddedPerasCert"]) = Just "Certificate added to Peras certificate database" - documentFor (Namespace _ ["IgnoredCertAlreadyInDB"]) = Just "Certificate ignored as it was already in the database" - documentFor (Namespace _ ["OpenedPerasCertDB"]) = Just "Peras certificate database opened" - documentFor (Namespace _ ["ClosedPerasCertDB"]) = Just "Peras certificate database closed" - documentFor (Namespace _ ["AddingPerasCert"]) = Just "Adding certificate to Peras certificate database" + documentFor (Namespace _ ["AddVote"]) = Just "Vote added to Peras vote database" + documentFor (Namespace _ ["GarbageCollected"]) = Just "Garbage collection performed on Peras vote database" documentFor _ = Nothing allNamespaces = - [Namespace [] ["AddedPerasCert"], - Namespace [] ["IgnoredCertAlreadyInDB"], - Namespace [] ["OpenedPerasCertDB"], - Namespace [] ["ClosedPerasCertDB"], - Namespace [] ["AddingPerasCert"]] + [Namespace [] ["AddVote"], + Namespace [] ["GarbageCollected"]] -- ChainDB.TraceAddPerasCertEvent MetaTrace instance instance MetaTrace (ChainDB.TraceAddPerasCertEvent blk) where From 13d851dbbc6b662bdd2f03add4d7a5ec5e5e431d Mon Sep 17 00:00:00 2001 From: Alexander Esgen Date: Sun, 6 Jul 2025 16:28:26 +0200 Subject: [PATCH 3/6] cardano-node: Integrate Predictable Ledger State Snapshots --- .../Cardano/Node/Configuration/LedgerDB.hs | 3 +- .../src/Cardano/Node/Configuration/POM.hs | 66 +++++++++++++++---- cardano-node/src/Cardano/Node/Run.hs | 6 +- .../Cardano/Node/Tracing/Tracers/ChainDB.hs | 25 +++++++ cardano-node/test/Test/Cardano/Node/POM.hs | 44 ++++++++++++- .../cardano/mainnet-config-legacy.json | 6 +- configuration/cardano/mainnet-config.json | 6 +- configuration/cardano/mainnet-config.yaml | 17 ++--- .../testnet-template-config-legacy.json | 6 +- .../cardano/testnet-template-config.json | 6 +- 10 files changed, 148 insertions(+), 37 deletions(-) diff --git a/cardano-node/src/Cardano/Node/Configuration/LedgerDB.hs b/cardano-node/src/Cardano/Node/Configuration/LedgerDB.hs index 2c60b7e9d87..d4e2179b873 100644 --- a/cardano-node/src/Cardano/Node/Configuration/LedgerDB.hs +++ b/cardano-node/src/Cardano/Node/Configuration/LedgerDB.hs @@ -73,8 +73,7 @@ noDeprecatedOptions = DeprecatedOptions [] data LedgerDbConfiguration = LedgerDbConfiguration - NumOfDiskSnapshots - SnapshotInterval + SnapshotPolicyArgs QueryBatchSize LedgerDbSelectorFlag DeprecatedOptions diff --git a/cardano-node/src/Cardano/Node/Configuration/POM.hs b/cardano-node/src/Cardano/Node/Configuration/POM.hs index 72a7fc94ff3..50bc13b48e9 100644 --- a/cardano-node/src/Cardano/Node/Configuration/POM.hs +++ b/cardano-node/src/Cardano/Node/Configuration/POM.hs @@ -28,6 +28,7 @@ module Cardano.Node.Configuration.POM where import Cardano.Crypto (RequiresNetworkMagic (..)) +import Cardano.Ledger.BaseTypes import Cardano.Logging.Types import Cardano.Network.ConsensusMode (ConsensusMode (..), defaultConsensusMode) import qualified Cardano.Network.Diffusion.Configuration as Cardano @@ -46,7 +47,9 @@ import Ouroboros.Consensus.Node.Genesis (GenesisConfig, GenesisConfigF defaultGenesisConfigFlags, mkGenesisConfig) import Ouroboros.Consensus.Storage.LedgerDB.Args (QueryBatchSize (..)) import Ouroboros.Consensus.Storage.LedgerDB.Snapshots (NumOfDiskSnapshots (..), - SnapshotInterval (..)) + SnapshotDelayRange (..), SnapshotFrequency (..), SnapshotFrequencyArgs (..), + SnapshotPolicyArgs (..), defaultSnapshotPolicyArgs) +import Ouroboros.Consensus.Util.Args (OverrideOrDefault (..)) import Ouroboros.Consensus.Storage.LedgerDB.V1.Args (FlushFrequency (..)) import Ouroboros.Network.Diffusion.Configuration as Configuration import qualified Ouroboros.Network.Diffusion.Configuration as Ouroboros @@ -484,8 +487,14 @@ instance FromJSON PartialNodeConfiguration where Nothing -> return Nothing parseLedgerDbConfig v = do - let snapInterval x = fmap (RequestedSnapshotInterval . secondsToDiffTime) <$> x .:? "SnapshotInterval" - snapNum x = fmap RequestedNumOfDiskSnapshots <$> x .:? "NumOfDiskSnapshots" + -- TODO maybe don't silently convert old format (which was in seconds) + -- to new format (which is in slots), despite these being the same on + -- mainnet? + let snapInterval x = do + si <- x .:? "SnapshotInterval" + when (any (<= 0) si) $ fail $ "Non-positive SnapshotInterval: " <> show si + pure $ Override . SlotNo <$> si + snapNum x = fmap (Override . NumOfDiskSnapshots) <$> x .:? "NumOfDiskSnapshots" mTopLevelSnapInterval <- snapInterval v mTopLevelSnapNum <- snapNum v @@ -499,12 +508,48 @@ instance FromJSON PartialNodeConfiguration where mLedgerDB <- v .:? "LedgerDB" case mLedgerDB of Nothing -> do - let si = fromMaybe DefaultSnapshotInterval mTopLevelSnapInterval - sn = fromMaybe DefaultNumOfDiskSnapshots mTopLevelSnapNum - return $ Just $ LedgerDbConfiguration sn si DefaultQueryBatchSize V2InMemory deprecatedOpts + let si = fromMaybe UseDefault mTopLevelSnapInterval + sn = fromMaybe UseDefault mTopLevelSnapNum + sf = SnapshotFrequencyArgs { + sfaInterval = unsafeNonZero . unSlotNo <$> si + , sfaOffset = UseDefault + , sfaRateLimit = UseDefault + , sfaDelaySnapshotRange = UseDefault + } + spArgs = SnapshotPolicyArgs (SnapshotFrequency sf) sn + return $ Just $ LedgerDbConfiguration spArgs DefaultQueryBatchSize V2InMemory deprecatedOpts Just ledgerDB -> flip (withObject "LedgerDB") ledgerDB $ \o -> do - ldbSnapInterval <- (getLast . (Last mTopLevelSnapInterval <>) . Last <$> snapInterval o) .!= DefaultSnapshotInterval - ldbSnapNum <- (getLast . (Last mTopLevelSnapNum <>) . Last <$> snapNum o) .!= DefaultNumOfDiskSnapshots + -- Parse snapshot options from the "Snapshots" sub-object if present, + -- otherwise fall back to the LedgerDB object for backward compatibility. + let parseSnapshotOpts s = do + sInterval <- (getLast . (Last mTopLevelSnapInterval <>) . Last <$> snapInterval s) .!= UseDefault + sNum <- (getLast . (Last mTopLevelSnapNum <>) . Last <$> snapNum s) .!= UseDefault + sOffset <- (fmap Override <$> s .:? "SlotOffset") .!= UseDefault + sRateLimit <- (fmap (Override . secondsToDiffTime) <$> s .:? "RateLimit") .!= UseDefault + sMinDelay <- s .:? "MinDelay" + sMaxDelay <- s .:? "MaxDelay" + sDelayRange <- + case (sMinDelay, sMaxDelay) of + (Just minDelay, Just maxDelay) -> + if minDelay <= maxDelay then + pure (Override (SnapshotDelayRange (secondsToDiffTime minDelay) (secondsToDiffTime maxDelay))) + else fail $ "Invalid ledger snapshot delay range, MinDelay > MaxDelay: " + <> show minDelay <> " > " <> show maxDelay + -- use the default delay range if either min or max is unspecified + _ -> pure UseDefault + let sf = SnapshotFrequencyArgs { + sfaInterval = unsafeNonZero . unSlotNo <$> sInterval + , sfaOffset = sOffset + , sfaRateLimit = sRateLimit + , sfaDelaySnapshotRange = sDelayRange + } + pure $ SnapshotPolicyArgs (SnapshotFrequency sf) sNum + + mSnapshotsVal <- o .:? "Snapshots" + spArgs <- case mSnapshotsVal of + Nothing -> parseSnapshotOpts o + Just sv -> flip (withObject "Snapshots") sv parseSnapshotOpts + qsize <- (fmap RequestedQueryBatchSize <$> o .:? "QueryBatchSize") .!= DefaultQueryBatchSize backend <- o .:? "Backend" .!= "V2InMemory" selector <- case backend of @@ -519,7 +564,7 @@ instance FromJSON PartialNodeConfiguration where lsmPath :: Maybe FilePath <- o .:? "LSMDatabasePath" pure $ V2LSM lsmPath _ -> fail $ "Malformed LedgerDB Backend: " <> backend - pure $ Just $ LedgerDbConfiguration ldbSnapNum ldbSnapInterval qsize selector deprecatedOpts + pure $ Just $ LedgerDbConfiguration spArgs qsize selector deprecatedOpts parseByronProtocol v = do primary <- v .:? "ByronGenesisFile" @@ -683,8 +728,7 @@ defaultPartialNodeConfiguration = , pncLedgerDbConfig = Last $ Just $ LedgerDbConfiguration - DefaultNumOfDiskSnapshots - DefaultSnapshotInterval + defaultSnapshotPolicyArgs DefaultQueryBatchSize V2InMemory noDeprecatedOptions diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index f4f6d43e365..10c77266977 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -561,15 +561,11 @@ handleSimpleNode blockType runP tracers nc networkMagic onKernel = do Just version_ -> Map.takeWhileAntitone (<= version_) LedgerDbConfiguration - snapInterval - numSnaps + snapshotPolicyArgs queryBatchSize ldbBackend deprecatedOpts = ncLedgerDbConfig nc - snapshotPolicyArgs :: SnapshotPolicyArgs - snapshotPolicyArgs = SnapshotPolicyArgs numSnaps snapInterval - -------------------------------------------------------------------------------- -- SIGHUP Handlers -------------------------------------------------------------------------------- diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs index dba57f0a828..8641b3e1940 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs @@ -60,6 +60,7 @@ import Ouroboros.Network.Block (MaxSlotNo (..)) import Data.Aeson (Object, ToJSON, Value (Object, String), object, toJSON, (.=)) import qualified Data.ByteString.Base16 as B16 import Data.Int (Int64) +import qualified Data.List.NonEmpty as NonEmpty import Data.SOP (All, K (..), hcmap, hcollapse) import Data.Text (Text) import qualified Data.Text as Text @@ -1772,6 +1773,14 @@ instance ( StandardHash blk LedgerDB.MetadataBackendMismatch -> " Snapshot was created for a different backend. Convert it with `snapshot-converter`." _ -> "" + forHuman (LedgerDB.SnapshotRequestDelayed _snapshotRequestTime delayBeforeSnapshotting slots) = + Text.unwords ["Scheduling to take ledger state snapshots at slots " + , showT (NonEmpty.toList slots) + , ", with a randomised delay of" + , showT delayBeforeSnapshotting + ] + forHuman LedgerDB.SnapshotRequestCompleted = "Completed taking a ledger state snapshot" + forMachine dtals (LedgerDB.TookSnapshot snap pt enclosedTiming) = mconcat [ "kind" .= String "TookSnapshot" @@ -1786,11 +1795,23 @@ instance ( StandardHash blk mconcat [ "kind" .= String "InvalidSnapshot" , "snapshot" .= forMachine dtals snap , "failure" .= show failure ] + forMachine _dtals (LedgerDB.SnapshotRequestDelayed snapshotRequestTime delayBeforeSnapshotting slots) = + mconcat [ "kind" .= String "TraceLedgerDBEvent.LedgerDBSnapshotEvent.SnapshotRequestDelayed" + , "requestTime" .= show snapshotRequestTime + , "delayBeforeSnapshotting " .= show delayBeforeSnapshotting + , "slots" .= show slots + ] + forMachine _dtals (LedgerDB.SnapshotRequestCompleted) = + mconcat [ "kind" .= String "TraceLedgerDBEvent.LedgerDBSnapshotEvent.SnapshotRequestCompleted" + ] + instance MetaTrace (LedgerDB.TraceSnapshotEvent blk) where namespaceFor LedgerDB.TookSnapshot {} = Namespace [] ["TookSnapshot"] namespaceFor LedgerDB.DeletedSnapshot {} = Namespace [] ["DeletedSnapshot"] namespaceFor LedgerDB.InvalidSnapshot {} = Namespace [] ["InvalidSnapshot"] + namespaceFor LedgerDB.SnapshotRequestDelayed {} = Namespace [] ["SnapshotRequestDelayed"] + namespaceFor LedgerDB.SnapshotRequestCompleted {} = Namespace [] ["SnapshotRequestCompleted"] severityFor (Namespace _ ["TookSnapshot"]) _ = Just Info severityFor (Namespace _ ["DeletedSnapshot"]) _ = Just Debug @@ -1809,6 +1830,10 @@ instance MetaTrace (LedgerDB.TraceSnapshotEvent blk) where , " seems to be from an old node or different backend, it will" , " be deleted" ] + documentFor (Namespace _ ["SnapshotRequestDelayed"]) = Just + "A delayed snapshot requested was issued. The snapshot will be initiated at the specified timestamp, with the specified delay and for the specified slots" + documentFor (Namespace _ ["SnapshotRequestCompleted"]) = Just + "The delayed snapshot request was completed" documentFor _ = Nothing allNamespaces = diff --git a/cardano-node/test/Test/Cardano/Node/POM.hs b/cardano-node/test/Test/Cardano/Node/POM.hs index 2d131f83235..df8ec5b1981 100644 --- a/cardano-node/test/Test/Cardano/Node/POM.hs +++ b/cardano-node/test/Test/Cardano/Node/POM.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} @@ -22,17 +23,18 @@ import Cardano.Rpc.Server.Config (makeRpcConfig) import Ouroboros.Consensus.Node (NodeDatabasePaths (..)) import Ouroboros.Consensus.Node.Genesis (disableGenesisConfig) import Ouroboros.Consensus.Storage.LedgerDB.Args -import Ouroboros.Consensus.Storage.LedgerDB.Snapshots (NumOfDiskSnapshots (..), - SnapshotInterval (..)) +import Ouroboros.Consensus.Storage.LedgerDB.Snapshots (defaultSnapshotPolicyArgs) import Ouroboros.Network.Block (SlotNo (..)) import Ouroboros.Network.PeerSelection.PeerSharing (PeerSharing (..)) import Ouroboros.Network.TxSubmission.Inbound.V2.Types import Data.Bifunctor (first) +import qualified Data.ByteString.Lazy as LBS import Data.Monoid (Last (..)) import Data.String import Data.Text (Text) +import Data.Aeson (eitherDecode) import Hedgehog (Property, discover, withTests, (===)) import qualified Hedgehog import Hedgehog.Internal.Property (evalEither, failWith) @@ -284,12 +286,48 @@ eExpectedConfig = do , ncConsensusMode = PraosMode , ncGenesisConfig = disableGenesisConfig , ncResponderCoreAffinityPolicy = NoResponderCoreAffinity - , ncLedgerDbConfig = LedgerDbConfiguration DefaultNumOfDiskSnapshots DefaultSnapshotInterval DefaultQueryBatchSize V2InMemory noDeprecatedOptions + , ncLedgerDbConfig = LedgerDbConfiguration defaultSnapshotPolicyArgs DefaultQueryBatchSize V2InMemory noDeprecatedOptions , ncRpcConfig , ncTxSubmissionLogicVersion = TxSubmissionLogicV1 , ncTxSubmissionInitDelay = defaultTxSubmissionInitDelay } +-- | Test that the legacy flat LedgerDB snapshot config format (options directly +-- under LedgerDB) parses identically to the new nested Snapshots format. +-- +-- TODO: this test could be removed once the old format is deprecated. +prop_legacySnapshotFormat_POM :: Property +prop_legacySnapshotFormat_POM = + withTests 1 . Hedgehog.property $ do + let legacyJson = "{ " <> dummyRequiredValues <> ", " + <> "\"LedgerDB\": {" + <> " \"Backend\": \"V2InMemory\"," + <> " \"SnapshotInterval\": 4320," + <> " \"NumOfDiskSnapshots\": 2" + <> "} }" + newJson = "{ " <> dummyRequiredValues <> ", " + <> "\"LedgerDB\": {" + <> " \"Backend\": \"V2InMemory\"," + <> " \"Snapshots\": {" + <> " \"SnapshotInterval\": 4320," + <> " \"NumOfDiskSnapshots\": 2" + <> " }" + <> "} }" + legacyConfig :: PartialNodeConfiguration <- evalEither $ eitherDecode legacyJson + newConfig :: PartialNodeConfiguration <- evalEither $ eitherDecode newJson + pncLedgerDbConfig legacyConfig === pncLedgerDbConfig newConfig + where + dummyRequiredValues :: LBS.ByteString + dummyRequiredValues = mconcat + [ "\"ByronGenesisFile\": \"x\"" + , ", \"ShelleyGenesisFile\": \"x\"" + , ", \"AlonzoGenesisFile\": \"x\"" + , ", \"ConwayGenesisFile\": \"x\"" + , ", \"LastKnownBlockVersion-Major\": 0" + , ", \"LastKnownBlockVersion-Minor\": 0" + , ", \"LastKnownBlockVersion-Alt\": 0" + ] + -- ----------------------------------------------------------------------------- tests :: IO Bool diff --git a/configuration/cardano/mainnet-config-legacy.json b/configuration/cardano/mainnet-config-legacy.json index f4bc557037e..a83cf83f481 100644 --- a/configuration/cardano/mainnet-config-legacy.json +++ b/configuration/cardano/mainnet-config-legacy.json @@ -13,9 +13,11 @@ "LastKnownBlockVersion-Minor": 0, "LedgerDB": { "Backend": "V2InMemory", - "NumOfDiskSnapshots": 2, "QueryBatchSize": 100000, - "SnapshotInterval": 4320 + "Snapshots": { + "NumOfDiskSnapshots": 2, + "SnapshotInterval": 4320 + } }, "MaxKnownMajorProtocolVersion": 2, "MinNodeVersion": "10.7.0", diff --git a/configuration/cardano/mainnet-config.json b/configuration/cardano/mainnet-config.json index b587e72e99d..a63aa6f45c9 100644 --- a/configuration/cardano/mainnet-config.json +++ b/configuration/cardano/mainnet-config.json @@ -13,9 +13,11 @@ "LastKnownBlockVersion-Minor": 0, "LedgerDB": { "Backend": "V2InMemory", - "NumOfDiskSnapshots": 2, "QueryBatchSize": 100000, - "SnapshotInterval": 4320 + "Snapshots": { + "NumOfDiskSnapshots": 2, + "SnapshotInterval": 4320 + } }, "MaxKnownMajorProtocolVersion": 2, "MinNodeVersion": "10.7.0", diff --git a/configuration/cardano/mainnet-config.yaml b/configuration/cardano/mainnet-config.yaml index 86f805d4e8e..991e919fd70 100644 --- a/configuration/cardano/mainnet-config.yaml +++ b/configuration/cardano/mainnet-config.yaml @@ -80,19 +80,20 @@ ConsensusMode: PraosMode # Additional configuration options can be found at: # https://ouroboros-consensus.cardano.intersectmbo.org/docs/for-developers/utxo-hd/migrating LedgerDB: - # The time interval between snapshots, in seconds. - SnapshotInterval: 4320 - - # The number of disk snapshots to keep. - NumOfDiskSnapshots: 2 + # The backend can either be in memory with `V2InMemory` or on disk with + # `V1LMDB`. + Backend: V2InMemory # When querying the store for a big range of UTxOs (such as with # QueryUTxOByAddress), the store will be read in batches of this size. QueryBatchSize: 100000 - # The backend can either be in memory with `V2InMemory` or on disk with - # `V1LMDB`. - Backend: V2InMemory + Snapshots: + # The time interval between snapshots, in seconds. + SnapshotInterval: 4320 + + # The number of disk snapshots to keep. + NumOfDiskSnapshots: 2 ##### Version Information ##### diff --git a/configuration/cardano/testnet-template-config-legacy.json b/configuration/cardano/testnet-template-config-legacy.json index 1d58efae3f3..42a402569f7 100644 --- a/configuration/cardano/testnet-template-config-legacy.json +++ b/configuration/cardano/testnet-template-config-legacy.json @@ -12,9 +12,11 @@ "LastKnownBlockVersion-Minor": 1, "LedgerDB": { "Backend": "V2InMemory", - "NumOfDiskSnapshots": 2, "QueryBatchSize": 100000, - "SnapshotInterval": 216 + "Snapshots": { + "NumOfDiskSnapshots": 2, + "SnapshotInterval": 216 + } }, "MaxConcurrencyDeadline": 4, "MaxKnownMajorProtocolVersion": 2, diff --git a/configuration/cardano/testnet-template-config.json b/configuration/cardano/testnet-template-config.json index 0ab850d4bad..ad1471cb456 100644 --- a/configuration/cardano/testnet-template-config.json +++ b/configuration/cardano/testnet-template-config.json @@ -13,9 +13,11 @@ "LastKnownBlockVersion-Minor": 1, "LedgerDB": { "Backend": "V2InMemory", - "NumOfDiskSnapshots": 2, "QueryBatchSize": 100000, - "SnapshotInterval": 216 + "Snapshots": { + "NumOfDiskSnapshots": 2, + "SnapshotInterval": 216 + } }, "MaxConcurrencyDeadline": 4, "MaxKnownMajorProtocolVersion": 2, From 593eb6dc24a6fa6c07d05de00da1815a64d0569c Mon Sep 17 00:00:00 2001 From: Georgy Lukyanov Date: Wed, 10 Jun 2026 12:08:35 +0200 Subject: [PATCH 4/6] Add "Mithril" named LedgerDB configuration --- .../src/Cardano/Node/Configuration/POM.hs | 13 ++++++- cardano-node/test/Test/Cardano/Node/POM.hs | 39 +++++++++++++------ configuration/cardano/mainnet-config.yaml | 2 + 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/cardano-node/src/Cardano/Node/Configuration/POM.hs b/cardano-node/src/Cardano/Node/Configuration/POM.hs index 50bc13b48e9..4d53cb87fdb 100644 --- a/cardano-node/src/Cardano/Node/Configuration/POM.hs +++ b/cardano-node/src/Cardano/Node/Configuration/POM.hs @@ -48,7 +48,7 @@ import Ouroboros.Consensus.Node.Genesis (GenesisConfig, GenesisConfigF import Ouroboros.Consensus.Storage.LedgerDB.Args (QueryBatchSize (..)) import Ouroboros.Consensus.Storage.LedgerDB.Snapshots (NumOfDiskSnapshots (..), SnapshotDelayRange (..), SnapshotFrequency (..), SnapshotFrequencyArgs (..), - SnapshotPolicyArgs (..), defaultSnapshotPolicyArgs) + SnapshotPolicyArgs (..), defaultSnapshotPolicyArgs, mithrilSnapshotPolicyArgs) import Ouroboros.Consensus.Util.Args (OverrideOrDefault (..)) import Ouroboros.Consensus.Storage.LedgerDB.V1.Args (FlushFrequency (..)) import Ouroboros.Network.Diffusion.Configuration as Configuration @@ -67,6 +67,7 @@ import Data.Hashable (Hashable) import Data.Maybe import Data.Monoid (Last (..)) import Data.Text (Text) +import qualified Data.Text as Text import Data.Time.Clock (DiffTime, secondsToDiffTime) import Data.Yaml (decodeFileThrow) import GHC.Generics (Generic) @@ -547,8 +548,16 @@ instance FromJSON PartialNodeConfiguration where mSnapshotsVal <- o .:? "Snapshots" spArgs <- case mSnapshotsVal of + -- A named snapshot policy selects a predefined set of snapshot + -- policy arguments as a whole. + Just (String name) -> case name of + "Mithril" -> pure mithrilSnapshotPolicyArgs + _ -> fail $ "Unknown named ledger snapshot policy: " <> Text.unpack name + <> ". Expected \"Mithril\" or an object with snapshot options." + -- the modern case of the snapshot policy specified under the "Snapshots" key + Just sv -> withObject "Snapshots" parseSnapshotOpts sv + -- the legacy case of the snapshot policy specified at the top-level Nothing -> parseSnapshotOpts o - Just sv -> flip (withObject "Snapshots") sv parseSnapshotOpts qsize <- (fmap RequestedQueryBatchSize <$> o .:? "QueryBatchSize") .!= DefaultQueryBatchSize backend <- o .:? "Backend" .!= "V2InMemory" diff --git a/cardano-node/test/Test/Cardano/Node/POM.hs b/cardano-node/test/Test/Cardano/Node/POM.hs index df8ec5b1981..844bff42a70 100644 --- a/cardano-node/test/Test/Cardano/Node/POM.hs +++ b/cardano-node/test/Test/Cardano/Node/POM.hs @@ -23,7 +23,8 @@ import Cardano.Rpc.Server.Config (makeRpcConfig) import Ouroboros.Consensus.Node (NodeDatabasePaths (..)) import Ouroboros.Consensus.Node.Genesis (disableGenesisConfig) import Ouroboros.Consensus.Storage.LedgerDB.Args -import Ouroboros.Consensus.Storage.LedgerDB.Snapshots (defaultSnapshotPolicyArgs) +import Ouroboros.Consensus.Storage.LedgerDB.Snapshots (defaultSnapshotPolicyArgs, + mithrilSnapshotPolicyArgs) import Ouroboros.Network.Block (SlotNo (..)) import Ouroboros.Network.PeerSelection.PeerSharing (PeerSharing (..)) import Ouroboros.Network.TxSubmission.Inbound.V2.Types @@ -316,17 +317,31 @@ prop_legacySnapshotFormat_POM = legacyConfig :: PartialNodeConfiguration <- evalEither $ eitherDecode legacyJson newConfig :: PartialNodeConfiguration <- evalEither $ eitherDecode newJson pncLedgerDbConfig legacyConfig === pncLedgerDbConfig newConfig - where - dummyRequiredValues :: LBS.ByteString - dummyRequiredValues = mconcat - [ "\"ByronGenesisFile\": \"x\"" - , ", \"ShelleyGenesisFile\": \"x\"" - , ", \"AlonzoGenesisFile\": \"x\"" - , ", \"ConwayGenesisFile\": \"x\"" - , ", \"LastKnownBlockVersion-Major\": 0" - , ", \"LastKnownBlockVersion-Minor\": 0" - , ", \"LastKnownBlockVersion-Alt\": 0" - ] + +-- | Test that the named \"Mithril\" snapshot policy selects +-- 'mithrilSnapshotPolicyArgs' as a whole. +prop_mithrilSnapshotPolicy_POM :: Property +prop_mithrilSnapshotPolicy_POM = + withTests 1 . Hedgehog.property $ do + let json = "{ " <> dummyRequiredValues <> ", " + <> "\"LedgerDB\": {" + <> " \"Backend\": \"V2InMemory\"," + <> " \"Snapshots\": \"Mithril\"" + <> "} }" + config :: PartialNodeConfiguration <- evalEither $ eitherDecode json + getLast (pncLedgerDbConfig config) === + Just (LedgerDbConfiguration mithrilSnapshotPolicyArgs DefaultQueryBatchSize V2InMemory noDeprecatedOptions) + +dummyRequiredValues :: LBS.ByteString +dummyRequiredValues = mconcat + [ "\"ByronGenesisFile\": \"x\"" + , ", \"ShelleyGenesisFile\": \"x\"" + , ", \"AlonzoGenesisFile\": \"x\"" + , ", \"ConwayGenesisFile\": \"x\"" + , ", \"LastKnownBlockVersion-Major\": 0" + , ", \"LastKnownBlockVersion-Minor\": 0" + , ", \"LastKnownBlockVersion-Alt\": 0" + ] -- ----------------------------------------------------------------------------- diff --git a/configuration/cardano/mainnet-config.yaml b/configuration/cardano/mainnet-config.yaml index 991e919fd70..2331e426d6a 100644 --- a/configuration/cardano/mainnet-config.yaml +++ b/configuration/cardano/mainnet-config.yaml @@ -88,6 +88,8 @@ LedgerDB: # QueryUTxOByAddress), the store will be read in batches of this size. QueryBatchSize: 100000 + # Instead of an object with individual options, a predefined snapshot + # policy can be selected by name, e.g. `Snapshots: Mithril`. Snapshots: # The time interval between snapshots, in seconds. SnapshotInterval: 4320 From 901919e0c5fd73c35373520eb9c4e645db1770cf Mon Sep 17 00:00:00 2001 From: Michael Karg Date: Wed, 10 Jun 2026 18:04:17 +0200 Subject: [PATCH 5/6] wb: adjust to new LedgerDB config object --- nix/workbench/service/nodes.nix | 431 +++++++++++++++++--------------- 1 file changed, 235 insertions(+), 196 deletions(-) diff --git a/nix/workbench/service/nodes.nix b/nix/workbench/service/nodes.nix index 22e5a5f85cd..8766d5949ed 100644 --- a/nix/workbench/service/nodes.nix +++ b/nix/workbench/service/nodes.nix @@ -1,30 +1,28 @@ -{ pkgs -, workbenchNix - -## The cardano-node config used as baseline: -, baseNodeConfig - -, backend -, profile -, profiling -, nodeSpecs - -# Derivations used to generate the topology projections -, profileJsonPath, topologyJsonPath +{ + pkgs, + workbenchNix, + ## The cardano-node config used as baseline: + baseNodeConfig, + backend, + profile, + profiling, + nodeSpecs, + # Derivations used to generate the topology projections + profileJsonPath, + topologyJsonPath, }: - -with pkgs.lib; - -let - readJSONMay = fp: - let fv = __tryEval (__readFile fp); - in if fv.success - then __fromJSON fv.value - else {}; +with pkgs.lib; let + readJSONMay = fp: let + fv = __tryEval (__readFile fp); + in + if fv.success + then __fromJSON fv.value + else {}; profileName = profile.name; - eras = [ ## This defines the order of eras -- which is important. + eras = [ + ## This defines the order of eras -- which is important. "byron" "shelley" "allegra" @@ -34,24 +32,26 @@ let "conway" ]; - configHardforksIntoEra = era: - let go = acc: curEra: rest: - let ret = acc // eraSetupHardforks.${curEra}; - in if curEra == era - then ret - else go ret (__head rest) (__tail rest); - eraSetupHardforks = { - byron = {}; - shelley = { TestShelleyHardForkAtEpoch = 0; }; - allegra = { TestAllegraHardForkAtEpoch = 0; }; - mary = { TestMaryHardForkAtEpoch = 0; }; - alonzo = { TestAlonzoHardForkAtEpoch = 0; }; - babbage = { TestBabbageHardForkAtEpoch = 0; }; - conway = { TestConwayHardForkAtEpoch = 0; }; - }; - in if __hasAttr era eraSetupHardforks - then go {} (__head eras) (__tail eras) - else throw "configHardforksIntoEra: unknown era '${era}'"; + configHardforksIntoEra = era: let + go = acc: curEra: rest: let + ret = acc // eraSetupHardforks.${curEra}; + in + if curEra == era + then ret + else go ret (__head rest) (__tail rest); + eraSetupHardforks = { + byron = {}; + shelley = {TestShelleyHardForkAtEpoch = 0;}; + allegra = {TestAllegraHardForkAtEpoch = 0;}; + mary = {TestMaryHardForkAtEpoch = 0;}; + alonzo = {TestAlonzoHardForkAtEpoch = 0;}; + babbage = {TestBabbageHardForkAtEpoch = 0;}; + conway = {TestConwayHardForkAtEpoch = 0;}; + }; + in + if __hasAttr era eraSetupHardforks + then go {} (__head eras) (__tail eras) + else throw "configHardforksIntoEra: unknown era '${era}'"; liveTablesPath = i: if (profile.node ? "ssd_directory" && profile.node.ssd_directory != null) @@ -61,24 +61,30 @@ let ## ## nodeServiceConfig :: NodeSpec -> ServiceConfig ## - nodeServiceConfig = - { name, i, kind, port, isProducer, ... }@nodeSpec: valency: + nodeServiceConfig = { + name, + i, + kind, + port, + isProducer, + ... + } @ nodeSpec: valency: { inherit isProducer port; inherit (profile.node) rts_flags_override; - nodeId = i; - databasePath = "db"; - socketPath = "node.socket"; - topology = "topology.json"; + nodeId = i; + databasePath = "db"; + socketPath = "node.socket"; + topology = "topology.json"; nodeConfigFile = "config.json"; # Allow for local clusters to have multiple LMDB directories in the same physical ssd_directory; # non-block producers (like the explorer node) keep using the in-memory backend - withUtxoHdLmdb = profile.node.utxo_lmdb && isProducer; - withUtxoHdLsmt = profile.node.utxo_lsmt && isProducer; + withUtxoHdLmdb = profile.node.utxo_lmdb && isProducer; + withUtxoHdLsmt = profile.node.utxo_lsmt && isProducer; lmdbDatabasePath = liveTablesPath i; - lsmDatabasePath = liveTablesPath i; + lsmDatabasePath = liveTablesPath i; ## Combine: ## 0. baseNodeConfig (coming cardanoLib's testnet environ) @@ -88,123 +94,146 @@ let ## 4. tracing backend's config nodeConfig = import ./tracing.nix - { - inherit (pkgs) lib; - inherit nodeSpec; - inherit (profile.node) tracing_backend tracer; - } + { + inherit (pkgs) lib; + inherit nodeSpec; + inherit (profile.node) tracing_backend tracer; + } + (recursiveUpdate (recursiveUpdate - (recursiveUpdate + ( + recursiveUpdate (removeAttrs baseNodeConfig - [ ## Let the genesis hashes be auto-computed by the node: + [ + ## Let the genesis hashes be auto-computed by the node: "ByronGenesisHash" "ShelleyGenesisHash" "AlonzoGenesisHash" "ConwayGenesisHash" "DijkstraGenesisHash" - ] // - { + ] + // { ExperimentalHardForksEnabled = true; ExperimentalProtocolsEnabled = true; - TurnOnLogMetrics = true; - SnapshotInterval = 4230; - ChainSyncIdleTimeout = 0; - PeerSharing = false; + TurnOnLogMetrics = true; + ChainSyncIdleTimeout = 0; + PeerSharing = false; ## defaults taken from: ouroboros-network/src/Ouroboros/Network/Diffusion/Configuration.hs ## NB. the following inequality must hold: known >= established >= active >= 0 - SyncTargetNumberOfActivePeers = max 15 valency; # set to same value as TargetNumberOfActivePeers + SyncTargetNumberOfActivePeers = max 15 valency; # set to same value as TargetNumberOfActivePeers SyncTargetNumberOfEstablishedPeers = max 40 valency; - TargetNumberOfActivePeers = max 15 valency; - TargetNumberOfEstablishedPeers = max 40 valency; + TargetNumberOfActivePeers = max 15 valency; + TargetNumberOfEstablishedPeers = max 40 valency; - ByronGenesisFile = "../genesis/byron/genesis.json"; - ShelleyGenesisFile = "../genesis/genesis-shelley.json"; - AlonzoGenesisFile = "../genesis/genesis.alonzo.json"; - ConwayGenesisFile = "../genesis/genesis.conway.json"; - DijkstraGenesisFile = "../genesis/genesis.dijkstra.json"; - } // optionalAttrs (profile.node.utxo_lsmt && isProducer) + ByronGenesisFile = "../genesis/byron/genesis.json"; + ShelleyGenesisFile = "../genesis/genesis-shelley.json"; + AlonzoGenesisFile = "../genesis/genesis.alonzo.json"; + ConwayGenesisFile = "../genesis/genesis.conway.json"; + DijkstraGenesisFile = "../genesis/genesis.dijkstra.json"; + } + // optionalAttrs (profile.node.utxo_lsmt && isProducer) { LedgerDB = { Backend = "V2LSM"; LSMDatabasePath = liveTablesPath i; }; - } // optionalAttrs (profile.node.utxo_lmdb && isProducer) + } + // optionalAttrs (profile.node.utxo_lmdb && isProducer) { LedgerDB = { Backend = "V1LMDB"; LiveTablesPath = liveTablesPath i; }; }) - (if __hasAttr "preset" profile && profile.preset != null - ## It's either an undisturbed preset, - ## or a hardforked setup. - then readJSONMay (../profile/presets + "/${profile.preset}/config.json") - else configHardforksIntoEra profile.era)) - profile.node.verbatim); + { + ## This LedgerDB attrset is defined regardless of backend choice. + ## It assumes the Backend default to be "V2InMemory"; it must not overwrite any of the above, more specfic choices. + LedgerDB = { + Snapshots = { + SnapshotInterval = 4230; + # Disable the randomised delay for kicking off snapshots: + # For benchmarks, exact timing needs to be reproducible, and identical for all nodes. + MinDelay = 0; + MaxDelay = 0; + }; + }; + } + ) + ( + if __hasAttr "preset" profile && profile.preset != null + ## It's either an undisturbed preset, + ## or a hardforked setup. + then readJSONMay (../profile/presets + "/${profile.preset}/config.json") + else configHardforksIntoEra profile.era + )) + profile.node.verbatim); extraArgs = - [ "+RTS" "-scardano-node.gcstats" "-RTS" ] - ++ - optionals (nodeSpec.shutdown_on_block_synced != null) [ + ["+RTS" "-scardano-node.gcstats" "-RTS"] + ++ optionals (nodeSpec.shutdown_on_block_synced != null) [ "--shutdown-on-block-synced" (toString nodeSpec.shutdown_on_block_synced) - ] ++ - optionals (nodeSpec.shutdown_on_slot_synced != null) [ + ] + ++ optionals (nodeSpec.shutdown_on_slot_synced != null) [ "--shutdown-on-slot-synced" (toString nodeSpec.shutdown_on_slot_synced) ]; - } // optionalAttrs ((profiling.profilingTypeParam or "none") != "none") { + } + // optionalAttrs ((profiling.profilingTypeParam or "none") != "none") { # Add the profiling `-h*` RTS option. profiling = profiling.profilingTypeParam; - } // optionalAttrs (profiling.eventlog or false) { + } + // optionalAttrs (profiling.eventlog or false) { # Add the `-l` RTS param with profiling. eventlog = true; - # Decide where the executable comes from: - ######################################### - } // optionalAttrs (!backend.useCabalRun) { - package = workbenchNix.haskellProject.exes.cardano-node; - } // optionalAttrs backend.useCabalRun { + # Decide where the executable comes from: + ######################################### + } + // optionalAttrs (!backend.useCabalRun) { + package = workbenchNix.haskellProject.exes.cardano-node; + } + // optionalAttrs backend.useCabalRun { # Allow the shell function to take precedence. executable = "cardano-node"; - ######################################### - } // optionalAttrs isProducer { + ######################################### + } + // optionalAttrs isProducer { operationalCertificate = "../genesis/node-keys/node${toString i}.opcert"; - kesKey = "../genesis/node-keys/node-kes${toString i}.skey"; - vrfKey = "../genesis/node-keys/node-vrf${toString i}.skey"; - } // optionalAttrs profile.node.tracer { + kesKey = "../genesis/node-keys/node-kes${toString i}.skey"; + vrfKey = "../genesis/node-keys/node-vrf${toString i}.skey"; + } + // optionalAttrs profile.node.tracer { tracerSocketPathConnect = mkDefault "../tracer/tracer.socket"; }; - time_fmtstr = - "{ " + escape [''"''] (concatStringsSep ''\n, '' time_entries) + " }"; - time_entries = [ - ''"wall_clock_s": %e'' - ''"user_cpu_s": %U'' - ''"sys_cpu_s": %S'' - ''"avg_cpu_pct": "%P"'' - ''"rss_peak_kb": %M'' - ''"signals_received": %k'' - ''"ctxsw_involuntary": %c'' - ''"ctxsw_volunt_waits": %w'' - ''"pageflt_major": %F'' - ''"pageflt_minor": %R'' - ''"swaps": %W'' - ''"io_fs_reads": %I'' - ''"io_fs_writes": %O'' - ''"cmdline": "%C"'' - ''"exit_code": %x'' - ]; + time_fmtstr = + "{ " + escape [''"''] (concatStringsSep ''\n, '' time_entries) + " }"; + time_entries = [ + ''"wall_clock_s": %e'' + ''"user_cpu_s": %U'' + ''"sys_cpu_s": %S'' + ''"avg_cpu_pct": "%P"'' + ''"rss_peak_kb": %M'' + ''"signals_received": %k'' + ''"ctxsw_involuntary": %c'' + ''"ctxsw_volunt_waits": %w'' + ''"pageflt_major": %F'' + ''"pageflt_minor": %R'' + ''"swaps": %W'' + ''"io_fs_reads": %I'' + ''"io_fs_writes": %O'' + ''"cmdline": "%C"'' + ''"exit_code": %x'' + ]; ## Given an env config, evaluate it and produce the node service. ## Call the given function on this service. ## ## evalServiceConfigToService :: NodeServiceConfig -> NodeService ## - evalServiceConfigToService = - serviceConfig: - let + evalServiceConfigToService = serviceConfig: let systemdCompat.options = { systemd.services = mkOption {}; systemd.sockets = mkOption {}; @@ -215,98 +244,108 @@ let }; eval = let extra = { - services.cardano-node = { - enable = true; - } // serviceConfig; + services.cardano-node = + { + enable = true; + } + // serviceConfig; }; - in evalModules { - prefix = []; - modules = import ../../nixos/module-list.nix - ++ [ systemdCompat extra - { config._module.args = { inherit pkgs; }; } - ] - ++ [ backend.service-modules.node or {} ]; - }; in - eval.config.services.cardano-node; - - topologiesJsonPaths = - let projections = - pkgs.runCommand "workbench-profile-files-${profileName}-topologies" - { nativeBuildInputs = with pkgs; - # A workbench with only the dependencies needed for this command. - [ workbenchNix.workbench - jq - workbenchNix.haskellProject.exes.cardano-topology - ]; - } - '' - mkdir "$out" - ${builtins.concatStringsSep - "\n" - (pkgs.lib.mapAttrsToList - (name: nodeSpec: '' - wb topology projection-for \ - "local-${nodeSpec.kind}" \ - "${toString nodeSpec.i}" \ - "${profileJsonPath}" \ - "${topologyJsonPath}" \ - "${toString backend.basePort}" \ - > "$out"/"topology-${name}.json" - '') - nodeSpecs - ) - } - '' - ; - in pkgs.lib.mapAttrs - (name: _: "${projections}/topology-${name}.json") - nodeSpecs - ; + evalModules { + prefix = []; + modules = + import ../../nixos/module-list.nix + ++ [ + systemdCompat + extra + {config._module.args = {inherit pkgs;};} + ] + ++ [backend.service-modules.node or {}]; + }; + in + eval.config.services.cardano-node; - nodeService = - { name, i, mode ? null, ... }@nodeSpec: - let - modeIdSuffix = if mode == null then "" else "." + mode; - serviceConfig = nodeServiceConfig nodeSpec valency; - service = evalServiceConfigToService serviceConfig; + topologiesJsonPaths = let + projections = + pkgs.runCommand "workbench-profile-files-${profileName}-topologies" + { + nativeBuildInputs = with pkgs; + # A workbench with only the dependencies needed for this command. + [ + workbenchNix.workbench + jq + workbenchNix.haskellProject.exes.cardano-topology + ]; + } + '' + mkdir "$out" + ${ + builtins.concatStringsSep + "\n" + ( + pkgs.lib.mapAttrsToList + (name: nodeSpec: '' + wb topology projection-for \ + "local-${nodeSpec.kind}" \ + "${toString nodeSpec.i}" \ + "${profileJsonPath}" \ + "${topologyJsonPath}" \ + "${toString backend.basePort}" \ + > "$out"/"topology-${name}.json" + '') + nodeSpecs + ) + } + ''; + in + pkgs.lib.mapAttrs + (name: _: "${projections}/topology-${name}.json") + nodeSpecs; - topology = - rec { - JSON = topologiesJsonPaths.${name}; - value = __fromJSON (__readFile JSON); - } - ; + nodeService = { + name, + i, + mode ? null, + ... + } @ nodeSpec: let + modeIdSuffix = + if mode == null + then "" + else "." + mode; + serviceConfig = nodeServiceConfig nodeSpec valency; + service = evalServiceConfigToService serviceConfig; - valency = - let - topo = topology.value; - val = if hasAttr "localRoots" topo && __length topo.localRoots > 0 - then let lr = head topo.localRoots; in lr.valency - else length (topo.Producers or []); - in val; + topology = rec { + JSON = topologiesJsonPaths.${name}; + value = __fromJSON (__readFile JSON); + }; - in { - start = - '' - #!${pkgs.stdenv.shell} + valency = let + topo = topology.value; + val = + if hasAttr "localRoots" topo && __length topo.localRoots > 0 + then let lr = head topo.localRoots; in lr.valency + else length (topo.Producers or []); + in + val; + in { + start = '' + #!${pkgs.stdenv.shell} - export TRACE_DISPATCHER_LOGGING_HOSTNAME=${name} + export TRACE_DISPATCHER_LOGGING_HOSTNAME=${name} - ${service.script} - '' - ; + ${service.script} + ''; - config = service.nodeConfig; + config = service.nodeConfig; - inherit topology; - }; + inherit topology; + }; ## ## node-services :: Map NodeName (NodeSpec, ServiceConfig, Service, NodeConfig, Script) ## node-services = mapAttrs (_: nodeService) nodeSpecs; -in -{ +in { inherit node-services; } From 5d86d7c0892456bd332af46f76401590236ddc6e Mon Sep 17 00:00:00 2001 From: Georgy Lukyanov Date: Thu, 11 Jun 2026 17:34:16 +0200 Subject: [PATCH 6/6] Address review comments --- cardano-node/src/Cardano/Node/Configuration/POM.hs | 9 +++------ .../src/Cardano/Node/Tracing/Tracers/ChainDB.hs | 14 +++++++++----- configuration/cardano/mainnet-config.yaml | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/cardano-node/src/Cardano/Node/Configuration/POM.hs b/cardano-node/src/Cardano/Node/Configuration/POM.hs index 4d53cb87fdb..2df5b5ba38a 100644 --- a/cardano-node/src/Cardano/Node/Configuration/POM.hs +++ b/cardano-node/src/Cardano/Node/Configuration/POM.hs @@ -488,16 +488,13 @@ instance FromJSON PartialNodeConfiguration where Nothing -> return Nothing parseLedgerDbConfig v = do - -- TODO maybe don't silently convert old format (which was in seconds) - -- to new format (which is in slots), despite these being the same on - -- mainnet? - let snapInterval x = do + let snapIntervalSlots x = do si <- x .:? "SnapshotInterval" when (any (<= 0) si) $ fail $ "Non-positive SnapshotInterval: " <> show si pure $ Override . SlotNo <$> si snapNum x = fmap (Override . NumOfDiskSnapshots) <$> x .:? "NumOfDiskSnapshots" - mTopLevelSnapInterval <- snapInterval v + mTopLevelSnapInterval <- snapIntervalSlots v mTopLevelSnapNum <- snapNum v let topLevelOptionsSet = @@ -523,7 +520,7 @@ instance FromJSON PartialNodeConfiguration where -- Parse snapshot options from the "Snapshots" sub-object if present, -- otherwise fall back to the LedgerDB object for backward compatibility. let parseSnapshotOpts s = do - sInterval <- (getLast . (Last mTopLevelSnapInterval <>) . Last <$> snapInterval s) .!= UseDefault + sInterval <- (getLast . (Last mTopLevelSnapInterval <>) . Last <$> snapIntervalSlots s) .!= UseDefault sNum <- (getLast . (Last mTopLevelSnapNum <>) . Last <$> snapNum s) .!= UseDefault sOffset <- (fmap Override <$> s .:? "SlotOffset") .!= UseDefault sRateLimit <- (fmap (Override . secondsToDiffTime) <$> s .:? "RateLimit") .!= UseDefault diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs index 8641b3e1940..93047f47a0b 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/ChainDB.hs @@ -1796,13 +1796,13 @@ instance ( StandardHash blk , "snapshot" .= forMachine dtals snap , "failure" .= show failure ] forMachine _dtals (LedgerDB.SnapshotRequestDelayed snapshotRequestTime delayBeforeSnapshotting slots) = - mconcat [ "kind" .= String "TraceLedgerDBEvent.LedgerDBSnapshotEvent.SnapshotRequestDelayed" + mconcat [ "kind" .= String "SnapshotRequestDelayed" , "requestTime" .= show snapshotRequestTime - , "delayBeforeSnapshotting " .= show delayBeforeSnapshotting - , "slots" .= show slots + , "delayBeforeSnapshotting" .= show delayBeforeSnapshotting + , "slots" .= toJSON (NonEmpty.toList slots) ] - forMachine _dtals (LedgerDB.SnapshotRequestCompleted) = - mconcat [ "kind" .= String "TraceLedgerDBEvent.LedgerDBSnapshotEvent.SnapshotRequestCompleted" + forMachine _dtals LedgerDB.SnapshotRequestCompleted = + mconcat [ "kind" .= String "SnapshotRequestCompleted" ] @@ -1816,6 +1816,8 @@ instance MetaTrace (LedgerDB.TraceSnapshotEvent blk) where severityFor (Namespace _ ["TookSnapshot"]) _ = Just Info severityFor (Namespace _ ["DeletedSnapshot"]) _ = Just Debug severityFor (Namespace _ ["InvalidSnapshot"]) _ = Just Error + severityFor (Namespace _ ["SnapshotRequestDelayed"]) _ = Just Debug + severityFor (Namespace _ ["SnapshotRequestCompleted"]) _ = Just Debug severityFor _ _ = Nothing documentFor (Namespace _ ["TookSnapshot"]) = Just $ mconcat @@ -1840,6 +1842,8 @@ instance MetaTrace (LedgerDB.TraceSnapshotEvent blk) where [ Namespace [] ["TookSnapshot"] , Namespace [] ["DeletedSnapshot"] , Namespace [] ["InvalidSnapshot"] + , Namespace [] ["SnapshotRequestDelayed"] + , Namespace [] ["SnapshotRequestCompleted"] ] -------------------------------------------------------------------------------- diff --git a/configuration/cardano/mainnet-config.yaml b/configuration/cardano/mainnet-config.yaml index 2331e426d6a..55a893966bd 100644 --- a/configuration/cardano/mainnet-config.yaml +++ b/configuration/cardano/mainnet-config.yaml @@ -91,7 +91,7 @@ LedgerDB: # Instead of an object with individual options, a predefined snapshot # policy can be selected by name, e.g. `Snapshots: Mithril`. Snapshots: - # The time interval between snapshots, in seconds. + # The time interval between snapshots, in slots. SnapshotInterval: 4320 # The number of disk snapshots to keep.