From 0843d0c44458ca2b6ea9df3d4850e95037f487a3 Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Tue, 15 Sep 2026 14:59:51 +0200 Subject: [PATCH 1/9] Add a trace for when mempool capacity changes --- .../Cardano/Node/Tracing/Tracers/Consensus.hs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/Consensus.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/Consensus.hs index 803a65d23ec..23cfc3f95dd 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers/Consensus.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/Consensus.hs @@ -50,7 +50,7 @@ import Ouroboros.Consensus.Genesis.Governor (DensityBounds (..), GDDDe import Ouroboros.Consensus.Ledger.Extended (ExtValidationError) import Ouroboros.Consensus.Ledger.Inspect (LedgerEvent (..), LedgerUpdate, LedgerWarning) import Ouroboros.Consensus.Ledger.SupportsMempool (ApplyTxErr, ByteSize32 (..), GenTxId, - HasTxId, LedgerSupportsMempool, txForgetValidated, txId) + HasTxId, LedgerSupportsMempool, txForgetValidated, txId, txMeasureByteSize) import Ouroboros.Consensus.Ledger.SupportsProtocol import Ouroboros.Consensus.Mempool (MempoolRejectionDetails (..), MempoolSize (..), TraceEventMempool (..), jsonMempoolRejectionDetails) @@ -1142,6 +1142,12 @@ instance mconcat [ "kind" .= String "TraceMempoolTipMovedBetweenSTMBlocks" ] + forMachine _dtal (TraceMempoolCapacityChanged capBefore capAfter) = + mconcat + [ "kind" .= String "TraceMempoolCapacityChanged" + , "capacityBytesBefore" .= unByteSize32 (txMeasureByteSize capBefore) + , "capacityBytesAfter" .= unByteSize32 (txMeasureByteSize capAfter) + ] asMetrics (TraceMempoolAddedTx _tx _mpSzBefore mpSz) = [ IntM "txsInMempool" (fromIntegral $ msNumTxs mpSz) @@ -1176,6 +1182,10 @@ instance asMetrics TraceMempoolTipMovedBetweenSTMBlocks {} = [] + asMetrics (TraceMempoolCapacityChanged _capBefore capAfter) = + [ IntM "mempoolCapacityBytes" (fromIntegral . unByteSize32 . txMeasureByteSize $ capAfter) + ] + instance LogFormatting MempoolSize where forMachine _dtal MempoolSize{msNumTxs, msNumBytes} = mconcat @@ -1193,6 +1203,7 @@ instance MetaTrace (TraceEventMempool blk) where namespaceFor TraceMempoolSyncNotNeeded {} = Namespace [] ["SyncNotNeeded"] namespaceFor TraceMempoolAttemptingAdd {} = Namespace [] ["AttemptAdd"] namespaceFor TraceMempoolTipMovedBetweenSTMBlocks {} = Namespace [] ["TipMovedBetweenSTMBlocks"] + namespaceFor TraceMempoolCapacityChanged {} = Namespace [] ["CapacityChanged"] severityFor (Namespace _ ["AddedTx"]) _ = Just Info @@ -1203,6 +1214,7 @@ instance MetaTrace (TraceEventMempool blk) where severityFor (Namespace _ ["SyncNotNeeded"]) _ = Just Debug severityFor (Namespace _ ["AttemptAdd"]) _ = Just Debug severityFor (Namespace [] ["TipMovedBetweenSTMBlocks"]) _ = Just Debug + severityFor (Namespace _ ["CapacityChanged"]) _ = Just Info severityFor _ _ = Nothing metricsDocFor (Namespace _ ["AddedTx"]) = @@ -1227,6 +1239,9 @@ instance MetaTrace (TraceEventMempool blk) where [ ("txsSyncDuration", "Latest time to sync the mempool in ms after block adoption") , (txsSyncDurationTotalCounterName, "Cumulative time spent syncing the mempool in ms after block adoption") ] + metricsDocFor (Namespace _ ["CapacityChanged"]) = + [ ("mempoolCapacityBytes", "Byte capacity of the mempool") + ] metricsDocFor _ = [] documentFor (Namespace _ ["AddedTx"]) = Just @@ -1250,6 +1265,9 @@ instance MetaTrace (TraceEventMempool blk) where "Mempool is about to try to validate and add a transaction." documentFor (Namespace _ ["TipMovedBetweenSTMBlocks"]) = Just "LedgerDB moved to an alternative fork between two reads during re-sync." + documentFor (Namespace _ ["CapacityChanged"]) = Just + "The mempool capacity changed while syncing with the ledger, e.g. because\ + \ a protocol parameter update was adopted." documentFor _ = Nothing allNamespaces = @@ -1261,6 +1279,7 @@ instance MetaTrace (TraceEventMempool blk) where , Namespace [] ["SyncNotNeeded"] , Namespace [] ["AttemptAdd"] , Namespace [] ["TipMovedBetweenSTMBlocks"] + , Namespace [] ["CapacityChanged"] ] -------------------------------------------------------------------------------- From 006a2bfe44c62d96c2f3bf4c6e895ad46d73c8a4 Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Sat, 19 Sep 2026 13:33:16 +0200 Subject: [PATCH 2/9] Tear the LeiosDB down on node shutdown The SQLite LeiosDB starts a writer worker holding the only write connections plus maintenance threads, and until now nothing ever stopped them. Open it through consensus' openLeiosDBSQLite and run its teardown when the node stops: everything else has wound down by then, so this flushes the pending writes, stops the threads and closes the connections. Depends on the writer-lifecycle work on ouroboros-consensus #2298. Co-Authored-By: Claude Fable 5 --- cardano-node/src/Cardano/Node/Run.hs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index 99e71924f66..d81c0e0bcf3 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -169,7 +169,7 @@ import System.Win32.File import Ouroboros.Consensus.Mempool (MempoolTimeoutConfig(..)) import GHC.Stack -import LeiosDemoDb (newLeiosDBInMemory, newLeiosDBSQLite) +import LeiosDemoDb (newLeiosDBInMemory, openLeiosDBSQLite) import LeiosDemoTypes (TraceLeiosKernel (TraceLeiosDb)) {- HLINT ignore "Fuse concatMap/map" -} @@ -365,8 +365,8 @@ handleSimpleNode blockType shelleyGenesisHash runP tracers nc networkMagic onKer $ Proxy @blk )) - leiosDB <- case ncLeiosDbConfig nc of - LeiosDbInMemory -> newLeiosDBInMemory + (leiosDB, closeLeiosDB) <- case ncLeiosDbConfig nc of + LeiosDbInMemory -> (\db -> (db, pure ())) <$> newLeiosDBInMemory LeiosDbSQLite leiosVolDbPath leiosImmDbPath -> do let resolvedVolPath | isAbsolute leiosVolDbPath = leiosVolDbPath @@ -376,12 +376,14 @@ handleSimpleNode blockType shelleyGenesisHash runP tracers nc networkMagic onKer | otherwise = nonImmutableDbPath dbPath leiosImmDbPath createDirectoryIfMissing True (takeDirectory resolvedVolPath) createDirectoryIfMissing True (takeDirectory resolvedImmPath) - newLeiosDBSQLite + openLeiosDBSQLite (contramap TraceLeiosDb (Consensus.leiosKernelTracer (consensusTracers tracers))) resolvedVolPath resolvedImmPath - withShutdownHandling (ncShutdownConfig nc) (shutdownTracer tracers) $ do + -- Orderly shutdown of the LeiosDB: everything below has stopped by then, so + -- this flushes the pending writes and closes the connections. + (`Exception.finally` closeLeiosDB) $ withShutdownHandling (ncShutdownConfig nc) (shutdownTracer tracers) $ do traceWith (startupTracer tracers) (StartupP2PInfo (ncDiffusionMode nc)) nt@NetworkTopology From 79eb29f67e96ae7d8597693543012a5186ae3789 Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Sun, 20 Sep 2026 07:26:02 +0200 Subject: [PATCH 3/9] Put the immutable LeiosDB partition on the immutable path Both partitions resolved against the non-immutable path, so a node configured with MultipleDbPaths put the LeiosDB's immutable partition on the performant volume -- the one that option exists to keep small. The partitions split the same way the node's own do: the volatile one churns and is swept, the immutable one only grows. Resolve each against the matching path. No change under OnePathForAllDbs, where both are the same directory. Co-Authored-By: Claude Fable 5 --- cardano-node/src/Cardano/Node/Run.hs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index d81c0e0bcf3..3b7b163a7bc 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -368,12 +368,16 @@ handleSimpleNode blockType shelleyGenesisHash runP tracers nc networkMagic onKer (leiosDB, closeLeiosDB) <- case ncLeiosDbConfig nc of LeiosDbInMemory -> (\db -> (db, pure ())) <$> newLeiosDBInMemory LeiosDbSQLite leiosVolDbPath leiosImmDbPath -> do + -- Each partition follows the node's own split: the volatile one + -- churns and is swept, so it belongs on the performant volume with + -- the VolatileDB; the immutable one only grows, so it belongs with + -- the ImmutableDB. Identical under 'OnePathForAllDbs'. let resolvedVolPath | isAbsolute leiosVolDbPath = leiosVolDbPath | otherwise = nonImmutableDbPath dbPath leiosVolDbPath resolvedImmPath | isAbsolute leiosImmDbPath = leiosImmDbPath - | otherwise = nonImmutableDbPath dbPath leiosImmDbPath + | otherwise = immutableDbPath dbPath leiosImmDbPath createDirectoryIfMissing True (takeDirectory resolvedVolPath) createDirectoryIfMissing True (takeDirectory resolvedImmPath) openLeiosDBSQLite From 451c8ef7fd886690fab394cd79044c744c2a4fd4 Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Sun, 20 Sep 2026 07:06:45 +0200 Subject: [PATCH 4/9] Establish the ChainDB marker before opening the LeiosDB A node could not start on a fresh database directory. The ChainDB refuses a directory that holds files but no marker of its own, and opening the LeiosDB creates its SQLite files in that same directory whenever the node runs on a single database path -- which happens before 'Node.run' gets as far as writing the marker. Run that check here instead, before the LeiosDB is opened. It is the same 'checkDbMarker' the node makes later and it is idempotent: with the marker present it only verifies the network magic. Nothing else changes order, and no lazy construction is needed to get it. Also drops the leiosDbCopyQueueFull counter, whose trace constructor is gone with the copy queue. Co-Authored-By: Claude Fable 5 --- cardano-node/src/Cardano/Node/Run.hs | 16 +++++++++++++++- .../Cardano/Node/Tracing/Tracers/Consensus.hs | 2 -- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index 3b7b163a7bc..ac3872148c7 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -67,7 +67,11 @@ import Cardano.Logging.Utils (showT) import qualified Ouroboros.Consensus.Config as Consensus import Ouroboros.Consensus.Config.SupportsNode (ConfigSupportsNode (..)) import Ouroboros.Consensus.Node (SnapshotPolicyArgs (..), - NodeDatabasePaths (..), nonImmutableDbPath, RunNodeArgs (..), StdRunNodeArgs (..)) + NodeDatabasePaths (..), immutableDbPath, nonImmutableDbPath, + RunNodeArgs (..), StdRunNodeArgs (..)) +import Ouroboros.Consensus.Node.DbMarker (checkDbMarker) +import System.FS.API.Types (MountPoint (..)) +import System.FS.IO (ioHasFS) import Ouroboros.Consensus.Protocol.Praos.AgentClient (KESAgentClientTrace) import Ouroboros.Consensus.Ledger.SupportsMempool (GenTxId) import Ouroboros.Consensus.Node (RunNodeArgs (..), @@ -365,6 +369,16 @@ handleSimpleNode blockType shelleyGenesisHash runP tracers nc networkMagic onKer $ Proxy @blk )) + -- Establish the ChainDB's marker before anything else writes into its + -- directory. The check refuses a directory that holds files but no marker + -- of its own, and the Leios DB's files land in that same directory + -- whenever the node runs on a single database path. 'Node.run' makes this + -- same check later; it is idempotent, so doing it here only moves it + -- earlier. + let dbMarkerMountPoint = MountPoint (immutableDbPath dbPath) + either Exception.throwIO pure + =<< checkDbMarker (ioHasFS dbMarkerMountPoint) dbMarkerMountPoint networkMagic + (leiosDB, closeLeiosDB) <- case ncLeiosDbConfig nc of LeiosDbInMemory -> (\db -> (db, pure ())) <$> newLeiosDBInMemory LeiosDbSQLite leiosVolDbPath leiosImmDbPath -> do diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/Consensus.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/Consensus.hs index 23cfc3f95dd..cb58eab2321 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers/Consensus.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/Consensus.hs @@ -2491,8 +2491,6 @@ instance LogFormatting TraceLeiosKernel where [ CounterM "leiosDbEvictedEbs" (Just evictedEbs) ] TraceLeiosDb TraceLeiosDbGCError{} -> [ CounterM "leiosDbSweepErrors" (Just 1) ] - TraceLeiosDb TraceLeiosDbCopyQueueFull{} -> - [ CounterM "leiosDbCopyQueueFull" (Just 1) ] TraceLeiosDb TraceLeiosDbCopyError{} -> [ CounterM "leiosDbCopyErrors" (Just 1) ] _ -> [] From 4607e8f8b09cb71d7377ee5926ea4cebd9e7f003 Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Sun, 20 Sep 2026 07:33:17 +0200 Subject: [PATCH 5/9] Name the LeiosDB partitions by infix, not suffix Matches the tools: leios.vol.db and leios.imm.db. Co-Authored-By: Claude Fable 5 --- cardano-node/src/Cardano/Node/Configuration/POM.hs | 2 +- cardano-node/test/Test/Cardano/Node/POM.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cardano-node/src/Cardano/Node/Configuration/POM.hs b/cardano-node/src/Cardano/Node/Configuration/POM.hs index 525e779f155..a03d4250ad9 100644 --- a/cardano-node/src/Cardano/Node/Configuration/POM.hs +++ b/cardano-node/src/Cardano/Node/Configuration/POM.hs @@ -755,7 +755,7 @@ defaultPartialNodeConfiguration = , pncTxSubmissionLogicVersion = Last $ Just TxSubmissionLogicV1 , pncTxSubmissionInitDelay = Last $ Just defaultTxSubmissionInitDelay - , pncLeiosDbConfig = Last (Just (LeiosDbSQLite "leios.db.vol" "leios.db.imm")) + , pncLeiosDbConfig = Last (Just (LeiosDbSQLite "leios.vol.db" "leios.imm.db")) } lastOption :: Parser a -> Parser (Last a) diff --git a/cardano-node/test/Test/Cardano/Node/POM.hs b/cardano-node/test/Test/Cardano/Node/POM.hs index b9e0c90fb46..9a81c11ee5a 100644 --- a/cardano-node/test/Test/Cardano/Node/POM.hs +++ b/cardano-node/test/Test/Cardano/Node/POM.hs @@ -294,7 +294,7 @@ eExpectedConfig = do , ncRpcConfig , ncTxSubmissionLogicVersion = TxSubmissionLogicV1 , ncTxSubmissionInitDelay = defaultTxSubmissionInitDelay - , ncLeiosDbConfig = LeiosDbSQLite "leios.db.vol" "leios.db.imm" + , ncLeiosDbConfig = LeiosDbSQLite "leios.vol.db" "leios.imm.db" } -- | Test that the legacy flat LedgerDB snapshot config format (options directly From 9b12a5477847cebe6d2092b9a2d4723df6420a1f Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Sun, 20 Sep 2026 07:35:52 +0200 Subject: [PATCH 6/9] LeiosDbConfig: follow the node's database paths The SQLite backend took a path per partition. Now that each partition follows the node's own DatabasePath -- volatile beside volatile/, immutable beside immutable/ -- a second way to name them can only contradict it: an absolute pair could put the growing partition on the performant volume, or put both back inside a directory the ChainDB expects to own. So the backend is all the option carries now, and the paths are derived. Co-Authored-By: Claude Fable 5 --- cardano-node/ChangeLog.md | 4 +--- .../src/Cardano/Node/Configuration/Leios.hs | 18 +++++++++--------- .../src/Cardano/Node/Configuration/POM.hs | 2 +- cardano-node/src/Cardano/Node/Run.hs | 10 +++------- cardano-node/test/Test/Cardano/Node/POM.hs | 2 +- 5 files changed, 15 insertions(+), 21 deletions(-) diff --git a/cardano-node/ChangeLog.md b/cardano-node/ChangeLog.md index cdf9c5b826f..7d6f1bc9c15 100644 --- a/cardano-node/ChangeLog.md +++ b/cardano-node/ChangeLog.md @@ -4,9 +4,7 @@ * Added a `--shelley-bls-key FILEPATH` option to `cardano-node run` for supplying a block producer's BLS (Leios) signing key alongside the existing VRF/KES/operational-certificate keys. The key is threaded into the consensus block-producer credentials and used as the Leios voting key. It is optional: producers that do not supply it no longer vote (previously a placeholder key was derived from cold-key material). Generate one with `cardano-cli dijkstra node key-gen-BLS`. -* `LeiosDbConfig` with `Backend: SQLite` now takes two paths, `VolatileFilepath` and `ImmutableFilepath`, one per LeiosDb partition, replacing the single `Filepath` key. The defaults are `leios.db.vol` and `leios.db.imm`. - -* Resolve relative `LeiosDbConfig` SQLite paths against `--database-path`, so the default `leios.db.vol` and `leios.db.imm` are placed alongside `immutable/` and `volatile/`. +* `LeiosDbConfig` with `Backend: SQLite` no longer takes any paths. Its two partitions follow the node's own `DatabasePath` the way the VolatileDB and the ImmutableDB do: `leios.vol.db` next to `volatile/`, `leios.imm.db` next to `immutable/`, which are separate volumes when `--immutable-database-path` and `--volatile-database-path` are given. Any `Filepath`, `VolatileFilepath` or `ImmutableFilepath` key is ignored, and an existing LeiosDb under the old `leios.db.vol`/`leios.db.imm` names is not picked up: the node starts a fresh one and re-fetches the endorser-block closures. * Added txsSyncDurationTotal counter for tracking the total time spent syncing the mempool. diff --git a/cardano-node/src/Cardano/Node/Configuration/Leios.hs b/cardano-node/src/Cardano/Node/Configuration/Leios.hs index 9941a0e07fa..ec70dfde589 100644 --- a/cardano-node/src/Cardano/Node/Configuration/Leios.hs +++ b/cardano-node/src/Cardano/Node/Configuration/Leios.hs @@ -8,8 +8,13 @@ module Cardano.Node.Configuration.Leios( import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON), Value (String), object, withObject, (.:), (.=)) +-- | Which LeiosDB backend to run. Where its files live is not configurable +-- here: the SQLite backend has a volatile and an immutable partition, and +-- each follows the node's own 'DatabasePath' the way the VolatileDB and the +-- ImmutableDB do. Naming them separately could only put them somewhere that +-- contradicts that. data LeiosDbConfig = LeiosDbInMemory - | LeiosDbSQLite !FilePath !FilePath + | LeiosDbSQLite deriving (Eq, Show) instance FromJSON LeiosDbConfig where @@ -17,10 +22,7 @@ instance FromJSON LeiosDbConfig where backend :: String <- o .: "Backend" case backend of "InMemory" -> return LeiosDbInMemory - "SQLite" -> do - volPath <- o .: "VolatileFilepath" - immPath <- o .: "ImmutableFilepath" - return $ LeiosDbSQLite volPath immPath + "SQLite" -> return LeiosDbSQLite _ -> fail $ "Invalid LeiosDb backend " <> backend <> ", did you mean InMemory or SQLite?" instance ToJSON LeiosDbConfig where @@ -28,9 +30,7 @@ instance ToJSON LeiosDbConfig where object [ "Backend" .= String "InMemory" ] - toJSON (LeiosDbSQLite volPath immPath) = + toJSON LeiosDbSQLite = object - [ "Backend" .= String "SQLite", - "VolatileFilepath" .= volPath, - "ImmutableFilepath" .= immPath + [ "Backend" .= String "SQLite" ] diff --git a/cardano-node/src/Cardano/Node/Configuration/POM.hs b/cardano-node/src/Cardano/Node/Configuration/POM.hs index a03d4250ad9..491b4cba35e 100644 --- a/cardano-node/src/Cardano/Node/Configuration/POM.hs +++ b/cardano-node/src/Cardano/Node/Configuration/POM.hs @@ -755,7 +755,7 @@ defaultPartialNodeConfiguration = , pncTxSubmissionLogicVersion = Last $ Just TxSubmissionLogicV1 , pncTxSubmissionInitDelay = Last $ Just defaultTxSubmissionInitDelay - , pncLeiosDbConfig = Last (Just (LeiosDbSQLite "leios.vol.db" "leios.imm.db")) + , pncLeiosDbConfig = Last (Just (LeiosDbSQLite)) } lastOption :: Parser a -> Parser (Last a) diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index ac3872148c7..4184f841a0d 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -381,17 +381,13 @@ handleSimpleNode blockType shelleyGenesisHash runP tracers nc networkMagic onKer (leiosDB, closeLeiosDB) <- case ncLeiosDbConfig nc of LeiosDbInMemory -> (\db -> (db, pure ())) <$> newLeiosDBInMemory - LeiosDbSQLite leiosVolDbPath leiosImmDbPath -> do + LeiosDbSQLite -> do -- Each partition follows the node's own split: the volatile one -- churns and is swept, so it belongs on the performant volume with -- the VolatileDB; the immutable one only grows, so it belongs with -- the ImmutableDB. Identical under 'OnePathForAllDbs'. - let resolvedVolPath - | isAbsolute leiosVolDbPath = leiosVolDbPath - | otherwise = nonImmutableDbPath dbPath leiosVolDbPath - resolvedImmPath - | isAbsolute leiosImmDbPath = leiosImmDbPath - | otherwise = immutableDbPath dbPath leiosImmDbPath + let resolvedVolPath = nonImmutableDbPath dbPath "leios.vol.db" + resolvedImmPath = immutableDbPath dbPath "leios.imm.db" createDirectoryIfMissing True (takeDirectory resolvedVolPath) createDirectoryIfMissing True (takeDirectory resolvedImmPath) openLeiosDBSQLite diff --git a/cardano-node/test/Test/Cardano/Node/POM.hs b/cardano-node/test/Test/Cardano/Node/POM.hs index 9a81c11ee5a..f7b710f0754 100644 --- a/cardano-node/test/Test/Cardano/Node/POM.hs +++ b/cardano-node/test/Test/Cardano/Node/POM.hs @@ -294,7 +294,7 @@ eExpectedConfig = do , ncRpcConfig , ncTxSubmissionLogicVersion = TxSubmissionLogicV1 , ncTxSubmissionInitDelay = defaultTxSubmissionInitDelay - , ncLeiosDbConfig = LeiosDbSQLite "leios.vol.db" "leios.imm.db" + , ncLeiosDbConfig = LeiosDbSQLite } -- | Test that the legacy flat LedgerDB snapshot config format (options directly From 84c175f1d57a0a9910b62960ee1813fb93f89e50 Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Sun, 20 Sep 2026 21:13:15 +0200 Subject: [PATCH 7/9] Close the LeiosDB through the handle The DB creates its own partition directories now, so the node does not. --- cardano-node/src/Cardano/Node/Run.hs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index 4184f841a0d..f8047860b39 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -160,7 +160,7 @@ import Data.Time.Clock (getCurrentTime) import Network.DNS (Resolver) import Network.Socket (Socket) import System.Directory (canonicalizePath, createDirectoryIfMissing, makeAbsolute) -import System.FilePath (isAbsolute, takeDirectory, ()) +import System.FilePath (isAbsolute, ()) import System.IO (hPutStrLn) #ifdef UNIX import GHC.Weak (deRefWeak) @@ -173,7 +173,7 @@ import System.Win32.File import Ouroboros.Consensus.Mempool (MempoolTimeoutConfig(..)) import GHC.Stack -import LeiosDemoDb (newLeiosDBInMemory, openLeiosDBSQLite) +import LeiosDemoDb (LeiosDbHandle (close), newLeiosDBInMemory, newLeiosDBSQLite) import LeiosDemoTypes (TraceLeiosKernel (TraceLeiosDb)) {- HLINT ignore "Fuse concatMap/map" -} @@ -379,8 +379,8 @@ handleSimpleNode blockType shelleyGenesisHash runP tracers nc networkMagic onKer either Exception.throwIO pure =<< checkDbMarker (ioHasFS dbMarkerMountPoint) dbMarkerMountPoint networkMagic - (leiosDB, closeLeiosDB) <- case ncLeiosDbConfig nc of - LeiosDbInMemory -> (\db -> (db, pure ())) <$> newLeiosDBInMemory + leiosDB <- case ncLeiosDbConfig nc of + LeiosDbInMemory -> newLeiosDBInMemory LeiosDbSQLite -> do -- Each partition follows the node's own split: the volatile one -- churns and is swept, so it belongs on the performant volume with @@ -388,16 +388,14 @@ handleSimpleNode blockType shelleyGenesisHash runP tracers nc networkMagic onKer -- the ImmutableDB. Identical under 'OnePathForAllDbs'. let resolvedVolPath = nonImmutableDbPath dbPath "leios.vol.db" resolvedImmPath = immutableDbPath dbPath "leios.imm.db" - createDirectoryIfMissing True (takeDirectory resolvedVolPath) - createDirectoryIfMissing True (takeDirectory resolvedImmPath) - openLeiosDBSQLite + newLeiosDBSQLite (contramap TraceLeiosDb (Consensus.leiosKernelTracer (consensusTracers tracers))) resolvedVolPath resolvedImmPath -- Orderly shutdown of the LeiosDB: everything below has stopped by then, so -- this flushes the pending writes and closes the connections. - (`Exception.finally` closeLeiosDB) $ withShutdownHandling (ncShutdownConfig nc) (shutdownTracer tracers) $ do + (`Exception.finally` close leiosDB) $ withShutdownHandling (ncShutdownConfig nc) (shutdownTracer tracers) $ do traceWith (startupTracer tracers) (StartupP2PInfo (ncDiffusionMode nc)) nt@NetworkTopology From 68e8e2629e5382b2bbdcdcfa75741e7905692fd9 Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Fri, 18 Sep 2026 09:12:43 +0200 Subject: [PATCH 8/9] Accept a JSON array of BLS key envelopes in --shelley-bls-key All keys reach the Leios voting logic, which votes once per committee seat any of them holds: a rotation pair keeps voting across the epoch boundary, a bundle of pool keys votes for every seat it covers. --- cardano-node/src/Cardano/Node/Parsers.hs | 6 ++- .../src/Cardano/Node/Protocol/Shelley.hs | 38 +++++++++++++------ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/cardano-node/src/Cardano/Node/Parsers.hs b/cardano-node/src/Cardano/Node/Parsers.hs index 1d6b4a44f2f..986193e7465 100644 --- a/cardano-node/src/Cardano/Node/Parsers.hs +++ b/cardano-node/src/Cardano/Node/Parsers.hs @@ -419,7 +419,11 @@ parseBlsKeyFilePath = strOption ( long "shelley-bls-key" <> metavar "FILEPATH" - <> help "Path to the BLS (Leios) signing key." + <> help + ( "Path to the BLS (Leios) signing key: a single text envelope, " + <> "or a JSON array of them to vote with every key that holds a " + <> "committee seat (e.g. a rotation pair, or a bundle of pool keys)." + ) <> completer (bashCompleter "file") ) diff --git a/cardano-node/src/Cardano/Node/Protocol/Shelley.hs b/cardano-node/src/Cardano/Node/Protocol/Shelley.hs index cc05b119e80..254c241d56d 100644 --- a/cardano-node/src/Cardano/Node/Protocol/Shelley.hs +++ b/cardano-node/src/Cardano/Node/Protocol/Shelley.hs @@ -53,10 +53,10 @@ import qualified Data.Aeson as Aeson import qualified Data.ByteString as BS import qualified Data.Text as T import System.Directory (getFileSize) +import System.FilePath (takeDirectory) import System.FS.API (SomeHasFS (..)) import System.FS.API.Types (MountPoint (MountPoint)) import System.FS.IO (ioHasFS) -import System.FilePath (takeDirectory) import qualified System.IO.MMap as MMap @@ -190,11 +190,9 @@ readLeaderCredentialsSingleton vrfSKey <- firstExceptT FileError (newExceptT $ readFileTextEnvelope (File vrfFile)) - -- The BLS (Leios) key is optional: only block producers participating in - -- Leios supply one alongside their VRF/KES/opcert credentials. - blsSKey <- - firstExceptT FileError $ - traverse (\blsFile -> newExceptT $ readFileTextEnvelope (File blsFile)) mBlsFile + -- The BLS (Leios) keys are optional: only block producers participating in + -- Leios supply any alongside their VRF/KES/opcert credentials. + blsSKeys <- maybe (pure []) readBlsSigningKeys mBlsFile (credentialsSource, vkey) <- case kesSource of KESKeyFilePath kesFile -> do @@ -209,7 +207,7 @@ readLeaderCredentialsSingleton OperationalCertificate _ vkey <- firstExceptT FileError $ newExceptT $ readFileTextEnvelope $ File opCertFile pure (PraosCredentialsAgent socketFile, vkey) - return [mkPraosLeaderCredentials credentialsSource vkey vrfSKey blsSKey] + return [mkPraosLeaderCredentials credentialsSource vkey vrfSKey blsSKeys] -- But not OK to supply some of the files without the others. readLeaderCredentialsSingleton ProtocolFilepaths {shelleyCertFile = Nothing} = @@ -259,8 +257,8 @@ readLeaderCredentialsBulk ProtocolFilepaths { shelleyBulkCredsFile = mfp } = KesSigningKey kesKey <- parseEnvelope scKes let credentialsSource = PraosCredentialsUnsound opCert kesKey vrfSKey <- parseEnvelope scVrf - -- Bulk credentials files do not carry a BLS (Leios) key. - pure $ mkPraosLeaderCredentials credentialsSource vkey vrfSKey Nothing + -- Bulk credentials files do not carry BLS (Leios) keys. + pure $ mkPraosLeaderCredentials credentialsSource vkey vrfSKey [] readBulkFile :: Maybe FilePath @@ -285,26 +283,42 @@ mkPraosLeaderCredentials :: PraosCredentialsSource StandardCrypto -> VerificationKey StakePoolKey -> SigningKey VrfKey - -> Maybe (SigningKey BlsKey) + -> [SigningKey BlsKey] -> ShelleyLeaderCredentials StandardCrypto mkPraosLeaderCredentials credentialsSource (StakePoolVerificationKey vkey) (VrfSigningKey vrfKey) - mBlsKey = + blsKeys = ShelleyLeaderCredentials { shelleyLeaderCredentialsCanBeLeader = PraosCanBeLeader { praosCanBeLeaderCredentialsSource = credentialsSource, praosCanBeLeaderColdVerKey = coerceKeyRole vkey, praosCanBeLeaderSignKeyVRF = vrfKey, - praosCanBeLeaderSignKeyBLS = unBlsSigningKey <$> mBlsKey + praosCanBeLeaderSignKeyBLS = unBlsSigningKey <$> blsKeys }, shelleyLeaderCredentialsLabel = "Shelley" } where unBlsSigningKey (BlsSigningKey k) = k +-- | Read the BLS (Leios) signing keys from a file holding either a single text +-- envelope or a JSON array of them. A pair of keys keeps a rotated key voting +-- across the epoch boundary; a larger bundle casts one vote per committee seat +-- held. +readBlsSigningKeys :: + FilePath + -> ExceptT PraosLeaderCredentialsError IO [SigningKey BlsKey] +readBlsSigningKeys fp = do + content <- handleIOExceptT (CredentialsReadError fp) $ BS.readFile fp + envelopes <- + firstExceptT (EnvelopeParseError fp) . hoistEither $ + case Aeson.eitherDecodeStrict' content of + Right tes -> Right tes + Left _ -> (:[]) <$> Aeson.eitherDecodeStrict' content + traverse (\te -> parseEnvelope (te, fp)) envelopes + parseEnvelope :: HasTextEnvelope a => (TextEnvelope, String) From 97e669a27cf94a44fd85895dae12dfc0c378244e Mon Sep 17 00:00:00 2001 From: Sebastian Nagel Date: Sun, 20 Sep 2026 23:22:05 +0200 Subject: [PATCH 9/9] Update SRPs for consensus, api, cli and ledger --- cabal.project | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/cabal.project b/cabal.project index 3e7cff8d412..cc88888c376 100644 --- a/cabal.project +++ b/cabal.project @@ -108,12 +108,19 @@ source-repository-package subdir: typed-protocols --- Points to ouroboros-ledger/leios-prototype +-- Points to ouroboros-consensus/leios-prototype +source-repository-package + type: git + location: https://github.com/IntersectMBO/ouroboros-consensus + tag: e5b82026e4f3395fbb94f0af62f711655d96fec7 + --sha256: sha256-WfinPoIUwdq3fm5aeDpyJCgXHjuIMtqrq57QZMq5E7Y= + +-- Points to cardano-ledger/leios-prototype source-repository-package type: git location: https://github.com/IntersectMBO/cardano-ledger - tag: 1587f21a7d1306dc590c2749a5c66232ef66aad0 - --sha256: sha256-zGKFgOU+p0uMTNbIm0Em6GMYMUVZMJsJtynzXG46E58= + tag: daa38fec95ddba68ad4e780b933e7e094d68241d + --sha256: sha256-UtLWITAOilFFXXRwCyAoG+6NHd/34wIYG296z3+L7FA= subdir: libs/cardano-data libs/cardano-ledger-api @@ -150,8 +157,8 @@ source-repository-package source-repository-package type: git location: https://github.com/IntersectMBO/cardano-api - tag: e32d8c07035283233fbdca4beeafbd78abc66512 - --sha256: sha256-yYKuOUoIOvJy6872Sj3CruRT+UXfoQsqqaHXcYu8N1A= + tag: 8749531d1b09f8ab5c50c3ac7b41cb4bb8ed9445 + --sha256: sha256-9892q0kBsCy5CLrYPjxHv5C7GOK/41XZZ5Z3JBZpB9E= subdir: cardano-api cardano-api-gen @@ -161,8 +168,8 @@ source-repository-package source-repository-package type: git location: https://github.com/IntersectMBO/cardano-cli - tag: 517ffe9ed02bba2b9ed644454a74c280ad62c2fa - --sha256: sha256-vNw0fqUF/dboMVVOgUs1eKGEdT36xN5lJEbJ2tRV1EI= + tag: cca726ea4026f6e0a40d38d549ff7ea5d458fc12 + --sha256: sha256-Oz9WGXDF7sqRLlqDXBJjtQr4NxxcprT+lCZAaDZRDPk= subdir: cardano-cli