From 025ec3e81387864807503449ef529a6ef2b1d5cd Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Fri, 24 Jul 2026 15:44:42 +0200 Subject: [PATCH 1/4] Adopt db-synthesizer into cardano-node repository --- .gitignore | 2 + cabal.project | 10 +- cardano-node/app/DBSynthesizer/Parsers.hs | 161 +++++++++++++++ cardano-node/app/db-synthesizer.hs | 25 +++ cardano-node/cardano-node.cabal | 31 ++- .../src/Cardano/Node/Tools/DBSynthesizer.hs | 140 +++++++++++++ cardano-node/test/db-synthesizer/Main.hs | 142 +++++++++++++ .../disk/config/alonzo-genesis.json | 194 ++++++++++++++++++ .../disk/config/bulk-creds-k2.json | 34 +++ .../disk/config/byron-genesis.json | 42 ++++ .../db-synthesizer/disk/config/config.json | 121 +++++++++++ .../disk/config/conway-genesis.json | 77 +++++++ .../disk/config/dijkstra-genesis.json | 6 + .../disk/config/shelley-genesis.json | 83 ++++++++ 14 files changed, 1065 insertions(+), 3 deletions(-) create mode 100644 cardano-node/app/DBSynthesizer/Parsers.hs create mode 100644 cardano-node/app/db-synthesizer.hs create mode 100644 cardano-node/src/Cardano/Node/Tools/DBSynthesizer.hs create mode 100644 cardano-node/test/db-synthesizer/Main.hs create mode 100644 cardano-node/test/db-synthesizer/disk/config/alonzo-genesis.json create mode 100644 cardano-node/test/db-synthesizer/disk/config/bulk-creds-k2.json create mode 100644 cardano-node/test/db-synthesizer/disk/config/byron-genesis.json create mode 100644 cardano-node/test/db-synthesizer/disk/config/config.json create mode 100644 cardano-node/test/db-synthesizer/disk/config/conway-genesis.json create mode 100644 cardano-node/test/db-synthesizer/disk/config/dijkstra-genesis.json create mode 100644 cardano-node/test/db-synthesizer/disk/config/shelley-genesis.json diff --git a/.gitignore b/.gitignore index 617e5c62f91..e4fe935faca 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,5 @@ cardano-tracer/cardano-tracer-test .codex .serena/ + +cardano-node/test/db-synthesizer/disk/chaindb \ No newline at end of file diff --git a/cabal.project b/cabal.project index 18e43a9c25b..3205f3fcb4b 100644 --- a/cabal.project +++ b/cabal.project @@ -141,11 +141,17 @@ source-repository-package kes-agent kes-agent-crypto +source-repository-package + type: git + location: https://github.com/IntersectMBO/cardano-config + tag: 50994eb21ae1d99528ce468cc5fba08ba67ca1ee + --sha256: sha256-h+tYgNmkT+kBQcuE4ujOmbv6WoT8uLVPE2phjtPz4LQ= + source-repository-package type: git location: https://github.com/IntersectMBO/ouroboros-consensus.git - tag: e468a936006a890d4469d1cbfaa3cfbe6867e29c - --sha256: sha256-X1Yd6TMYhhxbm8qiD3y8Ad3nY2D5wieGWf9kwoRCWxc= + tag: ba6574636b3f85641cea3c93aea83822d9be5342 + --sha256: sha256-d5xlkoy5JVuwz+kCF/1o2o5nyK58shmpgdirSBvL7xE= subdir: . diff --git a/cardano-node/app/DBSynthesizer/Parsers.hs b/cardano-node/app/DBSynthesizer/Parsers.hs new file mode 100644 index 00000000000..aac3ecdecfd --- /dev/null +++ b/cardano-node/app/DBSynthesizer/Parsers.hs @@ -0,0 +1,161 @@ +module DBSynthesizer.Parsers (parseCommandLine) where + +import Cardano.Node.Types (KESSource (..), ProtocolFilepaths (..)) +import Cardano.Tools.DBSynthesizer.Types +import Data.Word (Word64) +import Options.Applicative as Opt +import Ouroboros.Consensus.Block.Abstract (SlotNo (..)) + +parseCommandLine :: IO (FilePath, FilePath, ProtocolFilepaths, DBSynthesizerOptions) +parseCommandLine = + Opt.customExecParser p opts + where + p = Opt.prefs Opt.showHelpOnEmpty + opts = Opt.info parserCommandLine mempty + +parserCommandLine :: Parser (FilePath, FilePath, ProtocolFilepaths, DBSynthesizerOptions) +parserCommandLine = + (,,,) + <$> parseNodeConfigFilePath + <*> parseChainDBFilePath + <*> parseProtocolFilepaths + <*> parseDBSynthesizerOptions + +-- | The forging credentials, as file paths. Byron delegation credentials are +-- not wired up (the synthesizer forges Shelley-based blocks); the KES key path, +-- when given, is interpreted as a key file (not a KES agent socket). +parseProtocolFilepaths :: Parser ProtocolFilepaths +parseProtocolFilepaths = + mkFilepaths + <$> optional parseKesKeyFilePath + <*> optional parseVrfKeyFilePath + <*> optional parseOperationalCertFilePath + <*> optional parseBulkFilePath + where + mkFilepaths mKes mVrf mCert mBulk = + ProtocolFilepaths + { byronCertFile = Nothing + , byronKeyFile = Nothing + , shelleyKESSource = KESKeyFilePath <$> mKes + , shelleyVRFFile = mVrf + , shelleyCertFile = mCert + , shelleyBulkCredsFile = mBulk + } + +parseDBSynthesizerOptions :: Parser DBSynthesizerOptions +parseDBSynthesizerOptions = + DBSynthesizerOptions + <$> parseForgeOptions + <*> parseOpenMode + +parseForgeOptions :: Parser ForgeLimit +parseForgeOptions = + ForgeLimitSlot <$> parseSlotLimit + <|> ForgeLimitBlock <$> parseBlockLimit + <|> ForgeLimitEpoch <$> parseEpochLimit + +parseChainDBFilePath :: Parser FilePath +parseChainDBFilePath = + strOption + ( long "db" + <> metavar "PATH" + <> help "Path to the Chain DB" + <> completer (bashCompleter "directory") + ) + +parseNodeConfigFilePath :: Parser FilePath +parseNodeConfigFilePath = + strOption + ( long "config" + <> metavar "FILE" + <> help "Path to the node's config.json" + <> completer (bashCompleter "file") + ) + +parseOperationalCertFilePath :: Parser FilePath +parseOperationalCertFilePath = + strOption + ( long "shelley-operational-certificate" + <> metavar "FILE" + <> help "Path to the delegation certificate (in JSON TextEnvelope format)" + <> completer (bashCompleter "file") + ) + +parseKesKeyFilePath :: Parser FilePath +parseKesKeyFilePath = + strOption + ( long "shelley-kes-key" + <> metavar "FILE" + <> help "Path to the KES signing key (in JSON TextEnvelope format)" + <> completer (bashCompleter "file") + ) + +parseVrfKeyFilePath :: Parser FilePath +parseVrfKeyFilePath = + strOption + ( long "shelley-vrf-key" + <> metavar "FILE" + <> help "Path to the VRF signing key (in JSON TextEnvelope format)" + <> completer (bashCompleter "file") + ) + +parseBulkFilePath :: Parser FilePath +parseBulkFilePath = + strOption + ( long "bulk-credentials-file" + <> metavar "FILE" + <> help + "Path to the bulk credentials file (a JSON file containing an array of arrays containing 3 TextEnvelope objects for the opcert, VRF Signing key, KES signing key)" + <> completer (bashCompleter "file") + ) + +parseSlotLimit :: Parser SlotNo +parseSlotLimit = + SlotNo + <$> option + auto + ( short 's' + <> long "slots" + <> metavar "NUMBER" + <> help "Amount of slots to process" + ) + +parseBlockLimit :: Parser Word64 +parseBlockLimit = + option + auto + ( short 'b' + <> long "blocks" + <> metavar "NUMBER" + <> help "Amount of blocks to forge" + ) + +parseEpochLimit :: Parser Word64 +parseEpochLimit = + option + auto + ( short 'e' + <> long "epochs" + <> metavar "NUMBER" + <> help "Amount of epochs to process" + ) + +parseForce :: Parser Bool +parseForce = + switch + ( short 'f' + <> help "Force overwrite an existing Chain DB" + ) + +parseAppend :: Parser Bool +parseAppend = + switch + ( short 'a' + <> help "Append to an existing Chain DB" + ) + +parseOpenMode :: Parser DBSynthesizerOpenMode +parseOpenMode = + (parseForce *> pure OpenCreateForce) + <|> (parseAppend *> pure OpenAppend) + <|> pure OpenCreate diff --git a/cardano-node/app/db-synthesizer.hs b/cardano-node/app/db-synthesizer.hs new file mode 100644 index 00000000000..d31782cd262 --- /dev/null +++ b/cardano-node/app/db-synthesizer.hs @@ -0,0 +1,25 @@ +-- | This tool synthesizes a valid ChainDB, replicating cardano-node's UX. +-- +-- Usage: db-synthesizer --config FILE --db PATH +-- [--shelley-operational-certificate FILE] +-- [--shelley-vrf-key FILE] [--shelley-kes-key FILE] +-- [--bulk-credentials-file FILE] +-- ((-s|--slots NUMBER) | (-b|--blocks NUMBER) | +-- (-e|--epochs NUMBER)) [-f | -a] +-- +-- The node configuration and forging credentials are turned into a Cardano +-- 'ProtocolInfo' and block forgers using cardano-node's own protocol-instantiation +-- machinery (see "Cardano.Node.Tools.DBSynthesizer"); the actual forging is done +-- by @ouroboros-consensus@'s @synthesize@. +module Main (main) where + +import Cardano.Crypto.Init (cryptoInit) +import Cardano.Node.Tools.DBSynthesizer (synthesizeFromConfig) +import DBSynthesizer.Parsers (parseCommandLine) + +main :: IO () +main = do + cryptoInit + (configFp, dbDir, protocolFiles, opts) <- parseCommandLine + result <- synthesizeFromConfig configFp protocolFiles opts dbDir + putStrLn $ "--> done; result: " ++ show result diff --git a/cardano-node/cardano-node.cabal b/cardano-node/cardano-node.cabal index 315fcdef6d1..3096c01af32 100644 --- a/cardano-node/cardano-node.cabal +++ b/cardano-node/cardano-node.cabal @@ -113,6 +113,7 @@ library Cardano.Node.Tracing.Tracers.Shutdown Cardano.Node.Tracing.Tracers.HasIssuer Cardano.Node.Tracing.Tracers.Startup + Cardano.Node.Tools.DBSynthesizer Cardano.Node.Types other-modules: Paths_cardano_node @@ -166,7 +167,7 @@ library , network-mux >= 0.8 , nothunks , optparse-applicative - , ouroboros-consensus:{ouroboros-consensus, lsm, cardano, diffusion, protocol} ^>= 3.0.1 + , ouroboros-consensus:{ouroboros-consensus, lsm, cardano, diffusion, protocol, unstable-cardano-tools} ^>= 3.0.1 , ouroboros-network:{api, ouroboros-network, orphan-instances, framework, protocols, tracing} ^>= 1.1 , cardano-diffusion:{api, cardano-diffusion, tracing, orphan-instances} ^>=1.0 , prettyprinter @@ -213,6 +214,34 @@ executable cardano-node , optparse-applicative , text +executable db-synthesizer + import: project-config + hs-source-dirs: app + main-is: db-synthesizer.hs + ghc-options: -threaded + -rtsopts + + other-modules: DBSynthesizer.Parsers + + build-depends: base + , cardano-crypto-class + , cardano-node + , optparse-applicative + , ouroboros-consensus:{ouroboros-consensus, unstable-cardano-tools} ^>= 3.0.1 + +test-suite db-synthesizer-test + import: project-config + hs-source-dirs: test/db-synthesizer + main-is: Main.hs + type: exitcode-stdio-1.0 + + build-depends: base + , cardano-crypto-class + , cardano-node + , ouroboros-consensus:{ouroboros-consensus, cardano, unstable-cardano-tools} ^>= 3.0.1 + , tasty + , tasty-hunit + test-suite cardano-node-test import: project-config , maybe-unix diff --git a/cardano-node/src/Cardano/Node/Tools/DBSynthesizer.hs b/cardano-node/src/Cardano/Node/Tools/DBSynthesizer.hs new file mode 100644 index 00000000000..86a5aae5828 --- /dev/null +++ b/cardano-node/src/Cardano/Node/Tools/DBSynthesizer.hs @@ -0,0 +1,140 @@ +{-# LANGUAGE GADTs #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +-- | Downstream home for the db-synthesizer's configuration and credential +-- machinery. +-- +-- The forging engine ('Consensus.synthesize') lives in consensus and +-- consumes a @('ProtocolInfo', block forgers)@ pair plus an 'EpochSize'. +-- Constructing those from a node configuration file and on-disk forging +-- credentials is a node concern, so it is done here using cardano-node's own +-- protocol-instantiation machinery ('Node.mkConsensusProtocol') and cardano-api's +-- 'Api.protocolInfo' bridge — the same path the running node takes at startup. +module Cardano.Node.Tools.DBSynthesizer + ( DBSynthesizerException (..) + , initializeProtocol + , synthesizeFromConfig + ) where + +import Cardano.Api (BlockType (..), ProtocolInfoArgs (..)) +import qualified Cardano.Api as Api (protocolInfo) + +import qualified Cardano.Ledger.Api.Transition as Ledger (tcShelleyGenesisL) +import Cardano.Ledger.Shelley.Genesis (ShelleyGenesis, sgEpochLength) +import Cardano.Node.Configuration.POM + ( NodeConfiguration (..) + , PartialNodeConfiguration (..) + , defaultPartialNodeConfiguration + , makeNodeConfiguration + , parseNodeConfigurationFP + ) +import Cardano.Node.Handlers.Shutdown (ShutdownConfig (..)) +import Cardano.Node.Protocol (ProtocolInstantiationError) +import qualified Cardano.Node.Protocol as Node (mkConsensusProtocol) +import Cardano.Node.Protocol.Types (SomeConsensusProtocol (..)) +import Cardano.Node.Types + ( ConfigYamlFilePath (..) + , ProtocolFilepaths (..) + ) +import Cardano.Slotting.Slot (EpochSize) +import qualified Cardano.Tools.DBSynthesizer.Run as Consensus (synthesize) +import Cardano.Tools.DBSynthesizer.Types (DBSynthesizerOptions, ForgeResult) +import qualified Ouroboros.Consensus.Cardano.Node as Consensus + ( CardanoProtocolParams (..) + ) + +import Control.Applicative (Const (..)) +import Control.Exception (Exception (..), throwIO) +import Control.Monad.Trans.Except (runExceptT) +import Data.Monoid (Last (..)) + +import Ouroboros.Consensus.Block.Forging (MkBlockForging) +import Ouroboros.Consensus.Cardano.Block (CardanoBlock, StandardCrypto) +import Ouroboros.Consensus.Node.ProtocolInfo (ProtocolInfo) +import Ouroboros.Consensus.Protocol.Praos.AgentClient (KESAgentClientTrace) +import Control.Tracer (Tracer) + +-- | Something went wrong turning a node configuration (plus credentials) into a +-- forging-capable Cardano protocol. +data DBSynthesizerException + = -- | The configuration file could not be parsed or assembled. + DBSynthesizerConfigError String + | -- | The protocol could not be instantiated from the configuration. + DBSynthesizerProtocolError ProtocolInstantiationError + | -- | The configuration resolved to a non-Cardano protocol, which the + -- synthesizer does not support. + DBSynthesizerNotCardano + deriving Show + +instance Exception DBSynthesizerException + +-- | Build the ready-made 'ProtocolInfo', block forgers and 'EpochSize' that +-- 'Consensus.synthesize' needs, from a node configuration file and forging +-- credentials. This is the ejected @initialize@, now built on the node. +initializeProtocol :: + -- | Path to the node's @config.json@. + FilePath -> + -- | Forging credentials (KES\/VRF\/opcert or bulk creds). + ProtocolFilepaths -> + IO + ( ProtocolInfo (CardanoBlock StandardCrypto) + , Tracer IO KESAgentClientTrace -> + IO [MkBlockForging IO (CardanoBlock StandardCrypto)] + , EpochSize + ) +initializeProtocol configFp protocolFiles = do + nc <- + either (throwIO . DBSynthesizerConfigError) pure + =<< mkNodeConfig configFp protocolFiles + someProto <- + either (throwIO . DBSynthesizerProtocolError) pure + =<< runExceptT (Node.mkConsensusProtocol (ncProtocolConfig nc) (Just (ncProtocolFiles nc))) + case someProto of + SomeConsensusProtocol CardanoBlockType runP -> do + (protoInfo, mkForgers) <- Api.protocolInfo @IO runP + pure (protoInfo, mkForgers, sgEpochLength (shelleyGenesisOf runP)) + SomeConsensusProtocol{} -> throwIO DBSynthesizerNotCardano + +-- | Forge a ChainDB from a node configuration file, credentials and forge +-- options — the whole @config -> protocol -> forge@ pipeline the standalone +-- @db-synthesizer@ executable runs. No transactions are injected. +synthesizeFromConfig :: + -- | Path to the node's @config.json@. + FilePath -> + -- | Forging credentials. + ProtocolFilepaths -> + DBSynthesizerOptions -> + -- | Directory of the ChainDB to forge into. + FilePath -> + IO ForgeResult +synthesizeFromConfig configFp protocolFiles opts dbDir = do + (protoInfo, mkForgers, epochSize) <- initializeProtocol configFp protocolFiles + Consensus.synthesize genTxs opts epochSize dbDir (protoInfo, mkForgers) + where + genTxs _ _ _ _ = pure [] + +-- | Assemble a 'NodeConfiguration' from a config file, injecting the given +-- forging credentials (which can only otherwise be supplied on the node command +-- line) and the defaults the synthesizer needs. +mkNodeConfig :: FilePath -> ProtocolFilepaths -> IO (Either String NodeConfiguration) +mkNodeConfig configFp protocolFiles = do + configYamlPc <- parseNodeConfigurationFP (Just configYaml) + pure $ makeNodeConfiguration (configYamlPc <> filesPc) + where + configYaml = ConfigYamlFilePath configFp + filesPc = + defaultPartialNodeConfiguration + { pncProtocolFiles = Last (Just protocolFiles) + , pncValidateDB = Last (Just False) + , pncShutdownConfig = Last (Just (ShutdownConfig Nothing Nothing)) + , pncConfigFile = Last (Just configYaml) + } + +-- | Extract the Shelley genesis from a Cardano protocol's transition config. +-- Total for the Cardano protocol; the caller has already matched +-- 'CardanoBlockType'. +shelleyGenesisOf :: ProtocolInfoArgs IO (CardanoBlock StandardCrypto) -> ShelleyGenesis +shelleyGenesisOf (ProtocolInfoArgsCardano _ Consensus.CardanoProtocolParams{Consensus.cardanoLedgerTransitionConfig = transCfg}) = + getConst $ Ledger.tcShelleyGenesisL Const transCfg diff --git a/cardano-node/test/db-synthesizer/Main.hs b/cardano-node/test/db-synthesizer/Main.hs new file mode 100644 index 00000000000..068bfe5a3e0 --- /dev/null +++ b/cardano-node/test/db-synthesizer/Main.hs @@ -0,0 +1,142 @@ +{-# LANGUAGE TypeApplications #-} + +-- | End-to-end regression test for the downstream @db-synthesizer@: build a +-- Cardano 'ProtocolInfo' and block forgers from a node config file plus a +-- (bulk) forging-credentials fixture, synthesize a ChainDB, immutalise it, and +-- analyse it — checking the block count is preserved end to end. +-- +-- This is the config/credential-driven pipeline that used to live in +-- @ouroboros-consensus@'s @tools-test@ and moved downstream with the eject. It +-- deliberately uses real bulk credentials (a proper KES validity window) and the +-- original forge limits, exercising the path the standalone tool takes — not the +-- short-lived testlib credentials the in-repo synthesis-only test now uses. +module Main (main) where + +import Cardano.Crypto.Init (cryptoInit) +import Cardano.Node.Tools.DBSynthesizer (initializeProtocol) +import Cardano.Node.Types (KESSource, ProtocolFilepaths (..)) +import qualified Cardano.Tools.DBAnalyser.Block.Cardano as Cardano +import qualified Cardano.Tools.DBAnalyser.Run as DBAnalyser +import Cardano.Tools.DBAnalyser.Types +import qualified Cardano.Tools.DBImmutaliser.Run as DBImmutaliser +import qualified Cardano.Tools.DBSynthesizer.Run as DBSynthesizer +import Cardano.Tools.DBSynthesizer.Types +import Ouroboros.Consensus.Block (WithOrigin (Origin)) +import Ouroboros.Consensus.Cardano.Block (CardanoBlock, StandardCrypto) +import Test.Tasty +import Test.Tasty.HUnit + +-- | Fixtures live next to this test; paths are relative to the @cardano-node@ +-- package directory (cabal's working directory when running the test suite). +fixtureDir, nodeConfig, bulkCreds, chainDB :: FilePath +fixtureDir = "cardano-node/test/db-synthesizer/disk/config" +nodeConfig = fixtureDir <> "/config.json" +bulkCreds = fixtureDir <> "/bulk-creds-k2.json" +chainDB = "cardano-node/test/db-synthesizer/disk/chaindb" + +-- | Forging credentials for the test: only the bulk-credentials file, mirroring +-- how the tool is typically driven. Bulk creds carry a real KES validity window, +-- so the original (larger) forge limits below stay well within it. +testProtocolFiles :: ProtocolFilepaths +testProtocolFiles = + ProtocolFilepaths + { byronCertFile = Nothing + , byronKeyFile = Nothing + , shelleyKESSource = Nothing :: Maybe KESSource + , shelleyVRFFile = Nothing + , shelleyCertFile = Nothing + , shelleyBulkCredsFile = Just bulkCreds + } + +testSynthOptionsCreate :: DBSynthesizerOptions +testSynthOptionsCreate = + DBSynthesizerOptions + { synthLimit = ForgeLimitEpoch 1 + , synthOpenMode = OpenCreateForce + } + +testSynthOptionsAppend :: DBSynthesizerOptions +testSynthOptionsAppend = + DBSynthesizerOptions + { synthLimit = ForgeLimitSlot 8192 + , synthOpenMode = OpenAppend + } + +testImmutaliserConfig :: DBImmutaliser.Opts +testImmutaliserConfig = + DBImmutaliser.Opts + { DBImmutaliser.dbDirs = + DBImmutaliser.DBDirs + { DBImmutaliser.immDBDir = chainDB <> "/immutable" + , DBImmutaliser.volDBDir = chainDB <> "/volatile" + } + , DBImmutaliser.configFile = nodeConfig + , DBImmutaliser.verbose = False + , DBImmutaliser.dotOut = Nothing + , DBImmutaliser.dryRun = False + } + +testAnalyserConfig :: DBAnalyserConfig +testAnalyserConfig = + DBAnalyserConfig + { dbDir = chainDB + , ldbBackend = V2InMem + , verbose = False + , selectDB = SelectImmutableDB Origin + , validation = Just ValidateAllBlocks + , analysis = CountBlocks + , confLimit = Unlimited + } + +testBlockArgs :: Cardano.Args (CardanoBlock StandardCrypto) +testBlockArgs = Cardano.CardanoBlockArgs nodeConfig Nothing + +-- | 1. synthesize a ChainDB from scratch (create) and count blocks forged. +-- 2. append to it and count blocks forged. +-- 3. copy the VolatileDB into the ImmutableDB. +-- 4. analyse the ImmutableDB and confirm the total block count matches. +blockCountTest :: (String -> IO ()) -> Assertion +blockCountTest logStep = do + logStep "building protocol from config + bulk credentials" + (protocolInfo, mkForgers, epochSize) <- initializeProtocol nodeConfig testProtocolFiles + + logStep "running synthesis - create" + resultCreate <- + DBSynthesizer.synthesize genTxs testSynthOptionsCreate epochSize chainDB (protocolInfo, mkForgers) + let blockCountCreate = resultForged resultCreate + blockCountCreate > 0 @? "no blocks have been forged during create step" + + logStep "running synthesis - append" + resultAppend <- + DBSynthesizer.synthesize genTxs testSynthOptionsAppend epochSize chainDB (protocolInfo, mkForgers) + let blockCountAppend = resultForged resultAppend + blockCountAppend > 0 @? "no blocks have been forged during append step" + + logStep "copy volatile to immutable DB" + DBImmutaliser.run testImmutaliserConfig + + logStep "running analysis" + resultAnalysis <- DBAnalyser.analyse testAnalyserConfig testBlockArgs + + let blockCount = blockCountCreate + blockCountAppend + resultAnalysis == Just (ResultCountBlock blockCount) + @? "wrong number of blocks encountered during analysis \ + \ (counted: " + ++ show resultAnalysis + ++ "; expected: " + ++ show blockCount + ++ ")" + where + genTxs _ _ _ _ = pure [] + +tests :: TestTree +tests = + testGroup + "db-synthesizer" + [ testCaseSteps "synthesize (bulk creds) -> immutalise -> analyse: blockCount\n" blockCountTest + ] + +main :: IO () +main = do + cryptoInit + defaultMain tests diff --git a/cardano-node/test/db-synthesizer/disk/config/alonzo-genesis.json b/cardano-node/test/db-synthesizer/disk/config/alonzo-genesis.json new file mode 100644 index 00000000000..093071bb398 --- /dev/null +++ b/cardano-node/test/db-synthesizer/disk/config/alonzo-genesis.json @@ -0,0 +1,194 @@ +{ + "lovelacePerUTxOWord": 34482, + "executionPrices": { + "prSteps": { + "numerator": 721, + "denominator": 10000000 + }, + "prMem": { + "numerator": 577, + "denominator": 10000 + } + }, + "maxTxExUnits": { + "exUnitsMem": 14000000, + "exUnitsSteps": 10000000000 + }, + "maxBlockExUnits": { + "exUnitsMem": 56000000, + "exUnitsSteps": 40000000000 + }, + "maxValueSize": 5000, + "collateralPercentage": 150, + "maxCollateralInputs": 3, + "costModels": { + "PlutusV1": { + "sha2_256-memory-arguments": 4, + "equalsString-cpu-arguments-constant": 1000, + "cekDelayCost-exBudgetMemory": 100, + "lessThanEqualsByteString-cpu-arguments-intercept": 103599, + "divideInteger-memory-arguments-minimum": 1, + "appendByteString-cpu-arguments-slope": 621, + "blake2b-cpu-arguments-slope": 29175, + "iData-cpu-arguments": 150000, + "encodeUtf8-cpu-arguments-slope": 1000, + "unBData-cpu-arguments": 150000, + "multiplyInteger-cpu-arguments-intercept": 61516, + "cekConstCost-exBudgetMemory": 100, + "nullList-cpu-arguments": 150000, + "equalsString-cpu-arguments-intercept": 150000, + "trace-cpu-arguments": 150000, + "mkNilData-memory-arguments": 32, + "lengthOfByteString-cpu-arguments": 150000, + "cekBuiltinCost-exBudgetCPU": 29773, + "bData-cpu-arguments": 150000, + "subtractInteger-cpu-arguments-slope": 0, + "unIData-cpu-arguments": 150000, + "consByteString-memory-arguments-intercept": 0, + "divideInteger-memory-arguments-slope": 1, + "divideInteger-cpu-arguments-model-arguments-slope": 118, + "listData-cpu-arguments": 150000, + "headList-cpu-arguments": 150000, + "chooseData-memory-arguments": 32, + "equalsInteger-cpu-arguments-intercept": 136542, + "sha3_256-cpu-arguments-slope": 82363, + "sliceByteString-cpu-arguments-slope": 5000, + "unMapData-cpu-arguments": 150000, + "lessThanInteger-cpu-arguments-intercept": 179690, + "mkCons-cpu-arguments": 150000, + "appendString-memory-arguments-intercept": 0, + "modInteger-cpu-arguments-model-arguments-slope": 118, + "ifThenElse-cpu-arguments": 1, + "mkNilPairData-cpu-arguments": 150000, + "lessThanEqualsInteger-cpu-arguments-intercept": 145276, + "addInteger-memory-arguments-slope": 1, + "chooseList-memory-arguments": 32, + "constrData-memory-arguments": 32, + "decodeUtf8-cpu-arguments-intercept": 150000, + "equalsData-memory-arguments": 1, + "subtractInteger-memory-arguments-slope": 1, + "appendByteString-memory-arguments-intercept": 0, + "lengthOfByteString-memory-arguments": 4, + "headList-memory-arguments": 32, + "listData-memory-arguments": 32, + "consByteString-cpu-arguments-intercept": 150000, + "unIData-memory-arguments": 32, + "remainderInteger-memory-arguments-minimum": 1, + "bData-memory-arguments": 32, + "lessThanByteString-cpu-arguments-slope": 248, + "encodeUtf8-memory-arguments-intercept": 0, + "cekStartupCost-exBudgetCPU": 100, + "multiplyInteger-memory-arguments-intercept": 0, + "unListData-memory-arguments": 32, + "remainderInteger-cpu-arguments-model-arguments-slope": 118, + "cekVarCost-exBudgetCPU": 29773, + "remainderInteger-memory-arguments-slope": 1, + "cekForceCost-exBudgetCPU": 29773, + "sha2_256-cpu-arguments-slope": 29175, + "equalsInteger-memory-arguments": 1, + "indexByteString-memory-arguments": 1, + "addInteger-memory-arguments-intercept": 1, + "chooseUnit-cpu-arguments": 150000, + "sndPair-cpu-arguments": 150000, + "cekLamCost-exBudgetCPU": 29773, + "fstPair-cpu-arguments": 150000, + "quotientInteger-memory-arguments-minimum": 1, + "decodeUtf8-cpu-arguments-slope": 1000, + "lessThanInteger-memory-arguments": 1, + "lessThanEqualsInteger-cpu-arguments-slope": 1366, + "fstPair-memory-arguments": 32, + "modInteger-memory-arguments-intercept": 0, + "unConstrData-cpu-arguments": 150000, + "lessThanEqualsInteger-memory-arguments": 1, + "chooseUnit-memory-arguments": 32, + "sndPair-memory-arguments": 32, + "addInteger-cpu-arguments-intercept": 197209, + "decodeUtf8-memory-arguments-slope": 8, + "equalsData-cpu-arguments-intercept": 150000, + "mapData-cpu-arguments": 150000, + "mkPairData-cpu-arguments": 150000, + "quotientInteger-cpu-arguments-constant": 148000, + "consByteString-memory-arguments-slope": 1, + "cekVarCost-exBudgetMemory": 100, + "indexByteString-cpu-arguments": 150000, + "unListData-cpu-arguments": 150000, + "equalsInteger-cpu-arguments-slope": 1326, + "cekStartupCost-exBudgetMemory": 100, + "subtractInteger-cpu-arguments-intercept": 197209, + "divideInteger-cpu-arguments-model-arguments-intercept": 425507, + "divideInteger-memory-arguments-intercept": 0, + "cekForceCost-exBudgetMemory": 100, + "blake2b-cpu-arguments-intercept": 2477736, + "remainderInteger-cpu-arguments-constant": 148000, + "tailList-cpu-arguments": 150000, + "encodeUtf8-cpu-arguments-intercept": 150000, + "equalsString-cpu-arguments-slope": 1000, + "lessThanByteString-memory-arguments": 1, + "multiplyInteger-cpu-arguments-slope": 11218, + "appendByteString-cpu-arguments-intercept": 396231, + "lessThanEqualsByteString-cpu-arguments-slope": 248, + "modInteger-memory-arguments-slope": 1, + "addInteger-cpu-arguments-slope": 0, + "equalsData-cpu-arguments-slope": 10000, + "decodeUtf8-memory-arguments-intercept": 0, + "chooseList-cpu-arguments": 150000, + "constrData-cpu-arguments": 150000, + "equalsByteString-memory-arguments": 1, + "cekApplyCost-exBudgetCPU": 29773, + "quotientInteger-memory-arguments-slope": 1, + "verifySignature-cpu-arguments-intercept": 3345831, + "unMapData-memory-arguments": 32, + "mkCons-memory-arguments": 32, + "sliceByteString-memory-arguments-slope": 1, + "sha3_256-memory-arguments": 4, + "ifThenElse-memory-arguments": 1, + "mkNilPairData-memory-arguments": 32, + "equalsByteString-cpu-arguments-slope": 247, + "appendString-cpu-arguments-intercept": 150000, + "quotientInteger-cpu-arguments-model-arguments-slope": 118, + "cekApplyCost-exBudgetMemory": 100, + "equalsString-memory-arguments": 1, + "multiplyInteger-memory-arguments-slope": 1, + "cekBuiltinCost-exBudgetMemory": 100, + "remainderInteger-memory-arguments-intercept": 0, + "sha2_256-cpu-arguments-intercept": 2477736, + "remainderInteger-cpu-arguments-model-arguments-intercept": 425507, + "lessThanEqualsByteString-memory-arguments": 1, + "tailList-memory-arguments": 32, + "mkNilData-cpu-arguments": 150000, + "chooseData-cpu-arguments": 150000, + "unBData-memory-arguments": 32, + "blake2b-memory-arguments": 4, + "iData-memory-arguments": 32, + "nullList-memory-arguments": 32, + "cekDelayCost-exBudgetCPU": 29773, + "subtractInteger-memory-arguments-intercept": 1, + "lessThanByteString-cpu-arguments-intercept": 103599, + "consByteString-cpu-arguments-slope": 1000, + "appendByteString-memory-arguments-slope": 1, + "trace-memory-arguments": 32, + "divideInteger-cpu-arguments-constant": 148000, + "cekConstCost-exBudgetCPU": 29773, + "encodeUtf8-memory-arguments-slope": 8, + "quotientInteger-cpu-arguments-model-arguments-intercept": 425507, + "mapData-memory-arguments": 32, + "appendString-cpu-arguments-slope": 1000, + "modInteger-cpu-arguments-constant": 148000, + "verifySignature-cpu-arguments-slope": 1, + "unConstrData-memory-arguments": 32, + "quotientInteger-memory-arguments-intercept": 0, + "equalsByteString-cpu-arguments-constant": 150000, + "sliceByteString-memory-arguments-intercept": 0, + "mkPairData-memory-arguments": 32, + "equalsByteString-cpu-arguments-intercept": 112536, + "appendString-memory-arguments-slope": 1, + "lessThanInteger-cpu-arguments-slope": 497, + "modInteger-cpu-arguments-model-arguments-intercept": 425507, + "modInteger-memory-arguments-minimum": 1, + "sha3_256-cpu-arguments-intercept": 0, + "verifySignature-memory-arguments": 1, + "cekLamCost-exBudgetMemory": 100, + "sliceByteString-cpu-arguments-intercept": 150000 + } + } +} diff --git a/cardano-node/test/db-synthesizer/disk/config/bulk-creds-k2.json b/cardano-node/test/db-synthesizer/disk/config/bulk-creds-k2.json new file mode 100644 index 00000000000..fc64d6855b7 --- /dev/null +++ b/cardano-node/test/db-synthesizer/disk/config/bulk-creds-k2.json @@ -0,0 +1,34 @@ +[ + [ + { + "type": "NodeOperationalCertificate", + "description": "", + "cborHex": "82845820465dad8c08ecfe932f70bf287903d2d1973ac224f61cd0f9914ed052853f736b000058402cf9b1523a570f5a3333e1a602d3212e187b1e4b6b147b7cbc94657039de7e79e8ca6dc964cb7368b135c9607151e715d2ea9ccad9f3f550077b79fa3f64d1095820974aab238e812402dc9dbce33dd28203ae6df68616290a1b4aac347e881057bb" +} + , { + "type": "VrfSigningKey_PraosVRF", + "description": "VRF Signing Key", + "cborHex": "584040c0bd2dd8acfaded1d93c4844c2130058f86067af2e065dd3ae001e964a5f18b08644bf6ed9d404ba94c9ba9299a2ab53f36c57c02c38139f2138b6c71302c7" +} + , { + "type": "KesSigningKey_ed25519_kes_2^6", + "description": "KES Signing Key", + "cborHex": "5902606d23bd6e50df9416e52e9ee2cca23ac00f1ae78a62e50afcfc3cc8159b1e9ac888593015ae9c6124e33f143416b5c12195e3a2b947a00ef34e185f672b1047df6f5180047fffdaefea6337b2384087095873ba2d09ba74d1e826bbeec148e2db19ecb1db2e6d28748cf06cd36711d16fbced7fa2d5e0c1111832c36982196b417bd16ed77a4fd795fa22e2d394f3cb8940ca406431f4b105d6e9a47e5bcb4d5f86fa466b8228fcf17056f5e006ed522538c7ed32ad8724d3c63f5443907081f5f54f72868cb1475d05bb79d11a4c6abbed543c4898fc2f157aeb99adb27c31ca22ac195d04b13a0a1a3d118599d7ff8073d90063afcc87586e77b9795f73776e0f0bbf690440a243e729880cbcded7fc778f31cc873791296b1e43f87c869e197f1fd345fdf368136c936c53124caad8786379a194d3b348752b90dbfdd1199a3f8f8388940d5585825e2cffe7108b821d54351b6de2c9c4c8308d157b4b25070c77efc22a327e074e2ec01eac2bf9169a97d65cc826fbe827d0da045e5b680953b17a47b240b5e52653ad495d6ca90513f110d5a8353e92b416273a1bbc05e99050cd38dcb7a1f0e9d73aa0fbac201359fb26faa9235a851480b25dcf0ebe95cb2998b3f10f1baaabde842266c31ede1289ae1212cc9ae57a00262ada16dcd662f40c90ee1032e00dd4b6d1f17a0956517c8c38c354cb65b16bf6ace5d1d056205bd9f596020677ac06747335512dc9bafff75858a92cd6e947da98865ab364e6933d94a999afe22a0e8cbf3b8151e07073b343aa6632607f16d578a94e4f3b7050c2ee5e43a9279fd907e3deb75b244cb707423b06d71ab93b60b6fc23fa28" +} + ], [ + { + "type": "NodeOperationalCertificate", + "description": "", + "cborHex": "82845820a5ae7caf7a79b7f750d3d6da9a31d6523bdc0b99cc9dbfbdc11122e3ae07e8280000584071c1947b93fac5684a327a102f522d7b31daccfe8ef69ed0c36ed4618910245756bfe607b5a2bf7725045564b77ee18bfd7ed086b957d856a5491b51fbaedf065820e41015edc7b39489226d27c51dbe84c636466b3e29758a95445297614a8050bf" +} + , { + "type": "VrfSigningKey_PraosVRF", + "description": "VRF Signing Key", + "cborHex": "5840e2164474b17216bffb9494b8cdfc6d82f31f24e3f4dede8316221c11f616d75306a90f0597762346dd9eee0017623aca4745105f75b6d0d44355b26395372934" +} + , { + "type": "KesSigningKey_ed25519_kes_2^6", + "description": "KES Signing Key", + "cborHex": "59026076ae5c10752636ec89a8e9d25b74a7862f60d276246d13fa11bda92cfaf1417fe924137ee2a71629dcbbe950bb991dc8935033e3a4414b019510feb5f2c56d29de8e5249591afc25d214c024eac3c1186c26136a8719ca647c3c554aff75301df40a7243f0cea69d0da41b0edd95c35cc6644a433e1a59898f70a88b9578635c7f2a0dae07f48267c63e281eaeb4e9aad2e22f46229c4ee9f32e231f081a32c9b4ee2e7a940b2aa19d596f5b160abc0f83c66cd8c26d8f7226f4556d4a406e0b978df024d42a1a9236d58e8c64733aae1ee6e3258a27bfaf060b6c2913fc9babf1758bad0fe98819873bf34828f7ab5515d888f0c107f4c4010423f6ce7523de1cb543b0b471a48af5e367b75d856a36f5899c8019f91d321c22d012ee466e509d49ca12ed800448ad43ee1575de56abad60d0cd1d2bb9b541573504040c3b495d078e558c9ad0015d89e36515d4451c7adc87fdfe21cf21609684093e4d59c143c077e1127e25a0bb1fb8549c503b519f01f6092a3d3452341da2fb8687e07b340575532fe529cadd9701c300770930c4da09feed3a7f9b4d1253efe0fd1dc01122d7bf2324ff0779df1c65ef3886ca5196c4c5107c36dfc3dd292b9f90f2b55e380763e756b7f6b04d45d45e61aba849736babb9224adbf27a8880f1ecc23dd0bbe61a5b73fa269cc100bf3f6cbd17163f31d38aa22db320d37cbb767821de066a0f2b40f11bfc00404963d8418f54e9191f08d3c46319263b69cf51222c2ae4500627980856833e796e4435768172cb98b8b33ed5970a92ab3f046050c9f5aeeafa151f9d11b93c425b68cace42f87c51dee5f0a38071b3a8da23743d699c" +} + ]] diff --git a/cardano-node/test/db-synthesizer/disk/config/byron-genesis.json b/cardano-node/test/db-synthesizer/disk/config/byron-genesis.json new file mode 100644 index 00000000000..aec652492ff --- /dev/null +++ b/cardano-node/test/db-synthesizer/disk/config/byron-genesis.json @@ -0,0 +1,42 @@ +{ "bootStakeholders": + { "ce2950ee9b35c74336371a7393b2f1fa64d4af1831180076b106208d": 1 } +, "heavyDelegation": + { "ce2950ee9b35c74336371a7393b2f1fa64d4af1831180076b106208d": + { "omega": 0 + , "issuerPk": + "0Te/2OpdrE4IFuj6ZCSky8a/oeM9xE0phU0rQJE7v3Zg0wZop+bcZaKVe8qRk1zl0DM5vGIk5+XHZGhv7xh3rQ==" + , "delegatePk": + "ojky67+tV35+CmAFX7hkCCpPgz7EwrU8HCMDk7qEgoynuLaByN8S4ek4HjQXPq/b3vZFd08+Ip2BN/nC+e6Alg==" + , "cert": + "a8e1514662b6edd544f9e22d3bc8a961e6cfe5b1db35378188bb4fcd848e27c977952c8e73c29757b8b5b6b6c0b52254357383272c0b83e24cb91558907e8d04" + } } +, "startTime": 1655366659 +, "nonAvvmBalances": + { "2657WMsDfac5TVJguqJE11Z1tdx9HP72E9Roz32GVecUrX7oScFb1sXPzC43EnLUx": + "30000" + , "2657WMsDfac5V9qqEUfJm252BN5L81ni6CZyDS31cN7XZrAtsyqbz4yGr42bKG5B7": + "270000" + } +, "blockVersionData": + { "scriptVersion": 0 + , "slotDuration": "20000" + , "maxBlockSize": "641000" + , "maxHeaderSize": "200000" + , "maxTxSize": "4096" + , "maxProposalSize": "700" + , "mpcThd": "200000" + , "heavyDelThd": "300000" + , "updateVoteThd": "100000" + , "updateProposalThd": "100000" + , "updateImplicit": "10000" + , "softforkRule": + { "initThd": "900000" + , "minThd": "600000" + , "thdDecrement": "100000" + } + , "txFeePolicy": { "summand": "0" , "multiplier": "439460" } + , "unlockStakeEpoch": "184467" + } +, "protocolConsts": { "k": 2160 , "protocolMagic": 42 } +, "avvmDistr": {} +} \ No newline at end of file diff --git a/cardano-node/test/db-synthesizer/disk/config/config.json b/cardano-node/test/db-synthesizer/disk/config/config.json new file mode 100644 index 00000000000..70836962f78 --- /dev/null +++ b/cardano-node/test/db-synthesizer/disk/config/config.json @@ -0,0 +1,121 @@ +{ + "AcceptedConnectionsLimit": { + "delay": 5, + "hardLimit": 512, + "softLimit": 384 + }, + "AlonzoGenesisFile": "alonzo-genesis.json", + "AlonzoGenesisHash": "edb92321b614cfd7dcee3f49eedcde4f559ea9526194ff3cf42cd28a4bf0ad7b", + "ApplicationName": "cardano-sl", + "ApplicationVersion": 0, + "ByronGenesisFile": "byron-genesis.json", + "ByronGenesisHash": "6836b5d0ae3bb7250c318e8906ab2bf8e42e6acfd4483526be948752707ad435", + "ConwayGenesisFile": "conway-genesis.json", + "ConwayGenesisHash": "e4cbda3b0a0db8ea984330d0951d280ca490540c97cc6407ecd1011b174d983d", + "DijkstraGenesisFile": "dijkstra-genesis.json", + "DijkstraGenesisHash": "56c06ff0f668c584fc54fa3cee92dd5e121b67696924ac3b01b5aec9ecf95b78", + "EnableP2P": false, + "LastKnownBlockVersion-Alt": 0, + "LastKnownBlockVersion-Major": 3, + "LastKnownBlockVersion-Minor": 0, + "MaxKnownMajorProtocolVersion": 2, + "MempoolCapacityBytesOverride": "NoOverride", + "Protocol": "Cardano", + "ProtocolIdleTimeout": 5, + "RequiresNetworkMagic": "RequiresMagic", + "ShelleyGenesisFile": "shelley-genesis.json", + "ShelleyGenesisHash": "f6bb6e9d9b217681180754232470aa936716d62f8e11e944520b97490b100b7c", + "TargetNumberOfActivePeers": 20, + "TargetNumberOfEstablishedPeers": 50, + "TargetNumberOfKnownPeers": 100, + "TargetNumberOfRootPeers": 100, + "TestAllegraHardForkAtEpoch": 0, + "TestAlonzoHardForkAtEpoch": 0, + "TestBabbageHardForkAtEpoch": 0, + "ExperimentalHardForksEnabled": true, + "ExperimentalProtocolsEnabled": true, + "TestMaryHardForkAtEpoch": 0, + "TestShelleyHardForkAtEpoch": 0, + "TimeWaitTimeout": 60, + "TraceOptions": { + "": { + "backends": [ + "Stdout MachineFormat", + "EKGBackend", + "Forwarder" + ], + "severity": "Notice" + }, + "AcceptPolicy": { + "severity": "Info" + }, + "BlockFetchClient": { + "detail": "DMinimal", + "severity": "Info" + }, + "BlockFetchClient.CompletedBlockFetch": { + "maxFrequency": 2 + }, + "BlockFetchServer": { + "severity": "Info" + }, + "ChainDB": { + "severity": "Info" + }, + "ChainDB.AddBlockEvent.AddBlockValidation.ValidCandidate": { + "maxFrequency": 2 + }, + "ChainDB.AddBlockEvent.AddedBlockToQueue": { + "maxFrequency": 2 + }, + "ChainDB.AddBlockEvent.AddedBlockToVolatileDB": { + "maxFrequency": 2 + }, + "ChainDB.CopyToImmutableDBEvent.CopiedBlockToImmutableDB": { + "maxFrequency": 2 + }, + "ChainSyncClient": { + "detail": "DMinimal", + "severity": "Info" + }, + "ChainSyncServerBlock": { + "severity": "Info" + }, + "ChainSyncServerHeader": { + "severity": "Info" + }, + "DNSResolver": { + "severity": "Info" + }, + "DNSSubscription": { + "severity": "Info" + }, + "DiffusionInit": { + "severity": "Info" + }, + "ErrorPolicy": { + "severity": "Info" + }, + "Forge": { + "severity": "Info" + }, + "IpSubscription": { + "severity": "Info" + }, + "LocalErrorPolicy": { + "severity": "Info" + }, + "Mempool": { + "severity": "Info" + }, + "Resources": { + "severity": "Info" + }, + "TxSubmission2": { + "detail": "DMinimal" + } + }, + "TurnOnLogMetrics": true, + "TurnOnLogging": true, + "UseTraceDispatcher": true +} diff --git a/cardano-node/test/db-synthesizer/disk/config/conway-genesis.json b/cardano-node/test/db-synthesizer/disk/config/conway-genesis.json new file mode 100644 index 00000000000..08e1aed42a3 --- /dev/null +++ b/cardano-node/test/db-synthesizer/disk/config/conway-genesis.json @@ -0,0 +1,77 @@ +{ + "poolVotingThresholds": { + "committeeNormal": 0, + "committeeNoConfidence": 0, + "hardForkInitiation": 0, + "motionNoConfidence": 0, + "ppSecurityGroup": 0 + }, + "dRepVotingThresholds": { + "motionNoConfidence": 0, + "committeeNormal": 0, + "committeeNoConfidence": 0, + "updateToConstitution": 0, + "hardForkInitiation": 0, + "ppNetworkGroup": 0, + "ppEconomicGroup": 0, + "ppTechnicalGroup": 0, + "ppGovGroup": 0, + "treasuryWithdrawal": 0 + }, + "committeeMinSize": 0, + "committeeMaxTermLength": 0, + "govActionLifetime": 0, + "govActionDeposit": 0, + "dRepDeposit": 0, + "dRepActivity": 0, + "minFeeRefScriptCostPerByte": 0, + "plutusV3CostModel": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + "constitution": { + "anchor": { + "url": "", + "dataHash": "0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "committee": { + "members": { + "keyHash-4e88cc2d27c364aaf90648a87dfb95f8ee103ba67fa1f12f5e86c42a": 1, + "scriptHash-4e88cc2d27c364aaf90648a87dfb95f8ee103ba67fa1f12f5e86c42a": 2 + }, + "threshold": 0.5 + }, + "delegs": { + "keyHash-4e88cc2d27c364aaf90648a87dfb95f8ee103ba67fa1f12f5e86c42a": { + "dRep": "drep-alwaysAbstain" + }, + "keyHash-35bc5e86c42afbc593ab4cdd78301005df84ba67fa1f12f95f8ee103": { + "dRep": "drep-alwaysNoConfidence" + }, + "scriptHash-afbc5005df84ba5f8ee93ab435bc5e83067fa1f12f9c42cdd7110386": { + "dRep": "drep-keyHash-78301005df84ba67fa1f12f95f8ee10335bc5e86c42afbc593ab4cdd" + }, + "keyHash-df93ab435bc5eafbc500583067fa1f12f9110386c42cdd784ba5f8ee": { + "dRep": "drep-scriptHash-01305df84b078ac5e86c42afbc593ab4cdd67fa1f12f95f8ee10335b" + }, + "keyHash-5df84bcdd7a5f8ee93aafbc500b435bc5e83067fa1f12f9110386c42": { + "poolId": "0335bc5e86c42afbc578301005df84ba67fa1f12f95f8ee193ab4cdd" + }, + "keyHash-8ee93a5df84bc42cdd7a5fafbc500b435bc5e83067fa1f12f9110386": { + "poolId": "086c42afbc578301005df84ba67fa1f12f95f8ee193ab4cdd335bc5e", + "dRep": "drep-alwaysAbstain" + } + }, + "initialDReps": { + "keyHash-78301005df84ba67fa1f12f95f8ee10335bc5e86c42afbc593ab4cdd": { + "expiry": 1000, + "deposit": 5000 + }, + "scriptHash-01305df84b078ac5e86c42afbc593ab4cdd67fa1f12f95f8ee10335b": { + "expiry": 300, + "deposit": 6000, + "anchor": { + "url": "example.com", + "dataHash": "0000000000000000000000000000000000000000000000000000000000000000" + } + } + } +} diff --git a/cardano-node/test/db-synthesizer/disk/config/dijkstra-genesis.json b/cardano-node/test/db-synthesizer/disk/config/dijkstra-genesis.json new file mode 100644 index 00000000000..c33c6755721 --- /dev/null +++ b/cardano-node/test/db-synthesizer/disk/config/dijkstra-genesis.json @@ -0,0 +1,6 @@ +{ + "maxRefScriptSizePerBlock": 1048576, + "maxRefScriptSizePerTx": 204800, + "refScriptCostStride": 25600, + "refScriptCostMultiplier": 1.2 +} diff --git a/cardano-node/test/db-synthesizer/disk/config/shelley-genesis.json b/cardano-node/test/db-synthesizer/disk/config/shelley-genesis.json new file mode 100644 index 00000000000..7755bcd2242 --- /dev/null +++ b/cardano-node/test/db-synthesizer/disk/config/shelley-genesis.json @@ -0,0 +1,83 @@ +{ + "activeSlotsCoeff": 0.05, + "epochLength": 432000, + "genDelegs": {}, + "initialFunds": { + "0032635dc627da054f2a9e99559c56e16b02cf5f9237ba586ee1e648336b47a4e6e19ac5257fc97bd220bd9ae368aa2d775d86315ab7ef058f": 999500000000000, + "0064f4987ff07483636803f71f5f8442dad7f7fc46d83e2242d1548ad5d1f6f71a04ca856cc569fd068b069a555999c4776ad50b4cdd049d13": 999500000000000, + "602b43cb2b891e2dc9f5b07e051fb8d221a4a88ca161d95e859aa9ad8a": 9000000000000 + }, + "maxKESEvolutions": 60, + "maxLovelaceSupply": 2010000000000000, + "networkId": "Testnet", + "networkMagic": 42, + "protocolParams": { + "a0": 0.3, + "decentralisationParam": 0, + "eMax": 18, + "extraEntropy": { + "tag": "NeutralNonce" + }, + "keyDeposit": 400000, + "maxBlockBodySize": 81920, + "maxBlockHeaderSize": 1100, + "maxTxSize": 16384, + "minFeeA": 0, + "minFeeB": 0, + "minPoolCost": 0, + "minUTxOValue": 0, + "nOpt": 50, + "poolDeposit": 500000000, + "protocolVersion": { + "major": 5, + "minor": 0 + }, + "rho": 0.0022, + "tau": 0.05 + }, + "securityParam": 2160, + "slotLength": 1, + "slotsPerKESPeriod": 129600, + "staking": { + "pools": { + "1dc0a846ec816bdcc5288c0b57871a0400e86728ad0851132e43883f": { + "cost": 0, + "margin": 0, + "metadata": null, + "owners": [], + "pledge": 0, + "publicKey": "1dc0a846ec816bdcc5288c0b57871a0400e86728ad0851132e43883f", + "relays": [], + "rewardAccount": { + "credential": { + "key hash": "22c700325ec932f59048a7258e89b5f166604f184e0809d16a495550" + }, + "network": "Testnet" + }, + "vrf": "ee8fdadab21abed48fadee52492596841c561640920d1c022fa8ae51d206c714" + }, + "d3e16257b8c608ec4b2cb89621d7ddb60fc08839c1b491a598615fb5": { + "cost": 0, + "margin": 0, + "metadata": null, + "owners": [], + "pledge": 0, + "publicKey": "d3e16257b8c608ec4b2cb89621d7ddb60fc08839c1b491a598615fb5", + "relays": [], + "rewardAccount": { + "credential": { + "key hash": "3832f1051268ab7a7765142f21d695b7aaf40c576bf2d1a71894f0a4" + }, + "network": "Testnet" + }, + "vrf": "bbc57641002e501cf8bf98b776ba082c604e6af7c482f716934ed08d19c22733" + } + }, + "stake": { + "6b47a4e6e19ac5257fc97bd220bd9ae368aa2d775d86315ab7ef058f": "d3e16257b8c608ec4b2cb89621d7ddb60fc08839c1b491a598615fb5", + "d1f6f71a04ca856cc569fd068b069a555999c4776ad50b4cdd049d13": "1dc0a846ec816bdcc5288c0b57871a0400e86728ad0851132e43883f" + } + }, + "systemStart": "2022-06-16T08:04:19Z", + "updateQuorum": 5 +} From 2f4d57641f76be31d00bc0027905695764874703 Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Fri, 24 Jul 2026 16:02:11 +0200 Subject: [PATCH 2/4] Adapt cardano-config resolution to the node configuration --- cabal.project | 10 +- cardano-node/cardano-node.cabal | 4 +- .../Configuration/CardanoConfigAdapter.hs | 403 ++++++++++++++++++ .../src/Cardano/Node/Tools/DBSynthesizer.hs | 73 ++-- 4 files changed, 440 insertions(+), 50 deletions(-) create mode 100644 cardano-node/src/Cardano/Node/Configuration/CardanoConfigAdapter.hs diff --git a/cabal.project b/cabal.project index 3205f3fcb4b..ff64bc8d67f 100644 --- a/cabal.project +++ b/cabal.project @@ -144,16 +144,14 @@ source-repository-package source-repository-package type: git location: https://github.com/IntersectMBO/cardano-config - tag: 50994eb21ae1d99528ce468cc5fba08ba67ca1ee - --sha256: sha256-h+tYgNmkT+kBQcuE4ujOmbv6WoT8uLVPE2phjtPz4LQ= + tag: 200f0333a352718152315a712dfc1f39b7ed5e4a + --sha256: sha256-YgVaOMt6Vjqg9pg10ovsXOJvV5dRmOCjIoQJOs5zjbU= source-repository-package type: git location: https://github.com/IntersectMBO/ouroboros-consensus.git - tag: ba6574636b3f85641cea3c93aea83822d9be5342 - --sha256: sha256-d5xlkoy5JVuwz+kCF/1o2o5nyK58shmpgdirSBvL7xE= - subdir: - . + tag: 97dac2a1f85f9dd8c5be14200b6ea4e756a3f07e + --sha256: sha256-47Po3W1Ut2hFA/LyZweg3orL2ZM3WVZEH65lOQziYRs= source-repository-package type: git diff --git a/cardano-node/cardano-node.cabal b/cardano-node/cardano-node.cabal index 3096c01af32..5c3c2f92c0e 100644 --- a/cardano-node/cardano-node.cabal +++ b/cardano-node/cardano-node.cabal @@ -59,7 +59,8 @@ library hs-source-dirs: src - exposed-modules: Cardano.Node.Configuration.NodeAddress + exposed-modules: Cardano.Node.Configuration.CardanoConfigAdapter + Cardano.Node.Configuration.NodeAddress Cardano.Node.Configuration.POM Cardano.Node.Configuration.LedgerDB Cardano.Node.Configuration.Socket @@ -125,6 +126,7 @@ library , base16-bytestring , bytestring , cardano-api ^>= 11.3 + , cardano-config , cardano-data , cardano-crypto-class ^>=2.5 , cardano-crypto-wrapper diff --git a/cardano-node/src/Cardano/Node/Configuration/CardanoConfigAdapter.hs b/cardano-node/src/Cardano/Node/Configuration/CardanoConfigAdapter.hs new file mode 100644 index 00000000000..6c16d4835d9 --- /dev/null +++ b/cardano-node/src/Cardano/Node/Configuration/CardanoConfigAdapter.hs @@ -0,0 +1,403 @@ +{-# LANGUAGE ScopedTypeVariables #-} + +-- | Adapter from @cardano-config@'s resolved configuration to the node's own +-- 'NodeConfiguration' (the POM one). +-- +-- The node currently runs both parsers and will eventually drop the legacy POM +-- parser, at which point @cardano-config@ must produce the 'NodeConfiguration' +-- that starts consensus and networking. This adapter is that eventual +-- replacement: it maps @cardano-config@'s resolved values onto a +-- 'PartialNodeConfiguration' and runs the node's own 'makeNodeConfiguration', so +-- fields cardano-config supplies come from cardano-config and the rest fall back +-- to the node defaults. Fields not yet mapped are listed in 'adapterGaps' — that +-- gap list is exactly what must be closed before POM can be dropped, and any +-- gap also shows up concretely as a divergence in +-- 'Cardano.Node.Configuration.CardanoConfigCompare.compareConfigurations'. +module Cardano.Node.Configuration.CardanoConfigAdapter + ( cardanoConfigToNodeConfiguration + , cardanoConfigToPartialNodeConfiguration + , nodeProtocolConfigurationFromCardanoConfig + , adapterGaps + ) where + +import Cardano.Api (File (..)) +import qualified Cardano.Configuration as Cfg +import Cardano.Crypto (RequiresNetworkMagic (..)) +import Cardano.Ledger.BaseTypes (strictMaybeToMaybe) +import Cardano.Ledger.BaseTypes.NonZero (nonZero) +import Cardano.Network.ConsensusMode (ConsensusMode (..)) +import Cardano.Network.NodeToNode (DiffusionMode (..)) +import Cardano.Network.PeerSelection (NumberOfBigLedgerPeers (..)) +import Cardano.Node.Configuration.LedgerDB (LedgerDbConfiguration (..), + LedgerDbSelectorFlag (..), noDeprecatedOptions) +import Cardano.Node.Configuration.POM (NodeConfiguration, + PartialNodeConfiguration (..), ResponderCoreAffinityPolicy (..), + defaultPartialNodeConfiguration, makeNodeConfiguration) +import Cardano.Node.Configuration.Socket (SocketConfig (..)) +import Cardano.Node.Handlers.Shutdown (ShutdownConfig (..), + ShutdownOn (..)) +import Cardano.Node.Types (CheckpointsFile (..), CheckpointsHash (..), + ConfigYamlFilePath (..), GenesisFile (..), + GenesisHash (..), KESSource (..), MaxConcurrencyBulkSync (..), + MaxConcurrencyDeadline (..), + NodeAlonzoProtocolConfiguration (..), + NodeByronProtocolConfiguration (..), + NodeCheckpointsConfiguration (..), + NodeConwayProtocolConfiguration (..), + NodeDijkstraProtocolConfiguration (..), + NodeHardForkProtocolConfiguration (..), + NodeProtocolConfiguration (..), + NodeShelleyProtocolConfiguration (..), ProtocolFilepaths (..), + TopologyFile (..)) +import Cardano.Slotting.Block (BlockNo (..)) +import Cardano.Slotting.Slot (EpochNo (..), SlotNo (..)) +import Cardano.Rpc.Server.Config (RpcConfigF (..)) +import Data.Functor.Identity (runIdentity) +import Data.Monoid (Last (..)) +import Data.Time.Clock (secondsToDiffTime) +import Ouroboros.Consensus.Node (NodeDatabasePaths (..)) +import Ouroboros.Consensus.Node.Genesis (GenesisConfigFlags (..), + defaultGenesisConfigFlags) +import Ouroboros.Consensus.Ledger.SupportsMempool (ByteSize32 (..)) +import Ouroboros.Consensus.Mempool (MempoolCapacityBytesOverride (..)) +import Ouroboros.Consensus.Storage.LedgerDB.Args (QueryBatchSize (..)) +import Ouroboros.Consensus.Storage.LedgerDB.Snapshots + (NumOfDiskSnapshots (..), SnapshotDelayRange (..), + SnapshotFrequency (..), SnapshotFrequencyArgs (..), + SnapshotPolicyArgs (..), defaultSnapshotPolicyArgs, + mithrilSnapshotPolicyArgs) +import Ouroboros.Consensus.Util.Args (OverrideOrDefault (..)) +import Ouroboros.Network.PeerSelection.PeerSharing (PeerSharing (..)) +import Ouroboros.Network.Server.RateLimiting (AcceptedConnectionsLimit (..)) +import Ouroboros.Network.TxSubmission.Inbound.V2.Types + (TxSubmissionInitDelay (..), TxSubmissionLogicVersion (..)) +import System.FilePath (takeDirectory, ()) + +-- | Build the node's 'NodeConfiguration' from a @cardano-config@-resolved +-- configuration, reusing the node's own 'makeNodeConfiguration'. Fields +-- cardano-config does not yet supply keep the node defaults (see 'adapterGaps'). +cardanoConfigToNodeConfiguration :: Cfg.NodeConfiguration -> Either String NodeConfiguration +cardanoConfigToNodeConfiguration = + makeNodeConfiguration . cardanoConfigToPartialNodeConfiguration + +-- | Map the @cardano-config@-resolved values onto a 'PartialNodeConfiguration', +-- overriding the node defaults for every field cardano-config supplies. +cardanoConfigToPartialNodeConfiguration :: Cfg.NodeConfiguration -> PartialNodeConfiguration +cardanoConfigToPartialNodeConfiguration cfg = + defaultPartialNodeConfiguration + { pncConfigFile = Last (Just (ConfigYamlFilePath (Cfg.configFilePath cfg))) + , pncTopologyFile = Last (Just (TopologyFile (Cfg.topologyFile cfg))) + , pncValidateDB = Last (Just (Cfg.validateDatabase cfg)) + , pncStartAsNonProducingNode = Last (Just (runIdentity (Cfg.startAsNonProducingNode protoCfg))) + , pncProtocolConfig = Last (Just (nodeProtocolConfigurationFromCardanoConfig cfg)) + , pncProtocolFiles = Last (Just (credentialsToProtocolFilepaths (Cfg.credentials cfg))) + , pncExperimentalProtocolsEnabled = Last (Just (runIdentity (Cfg.experimentalProtocolsEnabled netCfg))) + , pncMempoolTimeoutSoft = Last (Just (runIdentity (Cfg.mempoolTimeoutSoft mempCfg))) + , pncMempoolTimeoutHard = Last (Just (runIdentity (Cfg.mempoolTimeoutHard mempCfg))) + , pncMempoolTimeoutCapacity = Last (Just (runIdentity (Cfg.mempoolTimeoutCapacity mempCfg))) + , pncMinBigLedgerPeersForTrustedState = + Last (Just (NumberOfBigLedgerPeers (runIdentity (Cfg.minBigLedgerPeersForTrustedState netCfg)))) + , -- Peer-selection targets: deadline targets are optional (StrictMaybe), + -- sync targets are always resolved (Identity). Map them all. + pncDeadlineTargetOfRootPeers = + Last (strictMaybeToMaybe (Cfg.deadlineTargetOfRootPeers netCfg)) + , pncDeadlineTargetOfKnownPeers = + Last (strictMaybeToMaybe (Cfg.deadlineTargetOfKnownPeers netCfg)) + , pncDeadlineTargetOfEstablishedPeers = + Last (strictMaybeToMaybe (Cfg.deadlineTargetOfEstablishedPeers netCfg)) + , pncDeadlineTargetOfActivePeers = + Last (strictMaybeToMaybe (Cfg.deadlineTargetOfActivePeers netCfg)) + , pncDeadlineTargetOfKnownBigLedgerPeers = + Last (strictMaybeToMaybe (Cfg.deadlineTargetOfKnownBigLedgerPeers netCfg)) + , pncDeadlineTargetOfEstablishedBigLedgerPeers = + Last (strictMaybeToMaybe (Cfg.deadlineTargetOfEstablishedBigLedgerPeers netCfg)) + , pncDeadlineTargetOfActiveBigLedgerPeers = + Last (strictMaybeToMaybe (Cfg.deadlineTargetOfActiveBigLedgerPeers netCfg)) + , pncSyncTargetOfRootPeers = + Last (Just (runIdentity (Cfg.syncTargetOfRootPeers netCfg))) + , pncSyncTargetOfKnownPeers = + Last (Just (runIdentity (Cfg.syncTargetOfKnownPeers netCfg))) + , pncSyncTargetOfEstablishedPeers = + Last (Just (runIdentity (Cfg.syncTargetOfEstablishedPeers netCfg))) + , pncSyncTargetOfActivePeers = + Last (Just (runIdentity (Cfg.syncTargetOfActivePeers netCfg))) + , pncSyncTargetOfKnownBigLedgerPeers = + Last (Just (runIdentity (Cfg.syncTargetOfKnownBigLedgerPeers netCfg))) + , pncSyncTargetOfEstablishedBigLedgerPeers = + Last (Just (runIdentity (Cfg.syncTargetOfEstablishedBigLedgerPeers netCfg))) + , pncSyncTargetOfActiveBigLedgerPeers = + Last (Just (runIdentity (Cfg.syncTargetOfActiveBigLedgerPeers netCfg))) + , pncDatabaseFile = Last (Just (fromCfgDbPaths (runIdentity (Cfg.databasePath storeCfg)))) + , pncDiffusionMode = Last (Just (fromCfgDiffusionMode (runIdentity (Cfg.diffusionMode netCfg)))) + , pncMaxConcurrencyBulkSync = + Last (Just (MaxConcurrencyBulkSync (runIdentity (Cfg.maxConcurrencyBulkSync netCfg)))) + , pncMaxConcurrencyDeadline = + Last (Just (MaxConcurrencyDeadline (runIdentity (Cfg.maxConcurrencyDeadline netCfg)))) + , pncTxSubmissionInitDelay = + Last (Just (TxSubmissionInitDelay (runIdentity (Cfg.txSubmissionInitDelay netCfg)))) + , pncAcceptedConnectionsLimit = + Last (Just (fromCfgAcceptedConnLimit (runIdentity (Cfg.acceptedConnectionsLimit netCfg)))) + , pncConsensusMode = Last (Just (fromCfgConsensusMode consensusModeVal)) + , pncPeerSharing = + Last (fmap toPeerSharing (strictMaybeToMaybe (Cfg.peerSharing netCfg))) + , pncMaybeMempoolCapacityOverride = + Last (fmap (MempoolCapacityBytesOverride . ByteSize32 . fromIntegral) + (strictMaybeToMaybe (Cfg.mempoolCapacityOverride mempCfg))) + , pncShutdownConfig = + Last (Just (ShutdownConfig + (strictMaybeToMaybe (Cfg.shutdownIPC cfg)) + (fmap toNodeShutdownOn (strictMaybeToMaybe (Cfg.shutdownOnTarget cfg))))) + , pncResponderCoreAffinityPolicy = + Last (Just (fromCfgAffinity (runIdentity (Cfg.responderCoreAffinityPolicy netCfg)))) + , pncTxSubmissionLogicVersion = + Last (Just (fromCfgTxSubmissionLogic (runIdentity (Cfg.txSubmissionLogicVersion netCfg)))) + , -- The Genesis tuning flags only feed 'ncGenesisConfig' when the node runs + -- in Genesis mode (see 'makeNodeConfiguration'); in Praos mode the node + -- ignores them, so mirror POM and keep the defaults there. + pncGenesisConfigFlags = + Last (Just (case consensusModeVal of + Cfg.GenesisMode flags -> fromCfgGenesisFlags flags + Cfg.PraosMode -> defaultGenesisConfigFlags)) + , -- Only the local (IPC) socket path lives in the configuration file; the + -- node-to-node IPv4/IPv6/port bindings are CLI-only in the node, so they + -- stay empty here exactly as POM leaves them when parsing config alone. + pncSocketConfig = + Last (Just (SocketConfig mempty mempty mempty + (Last (fmap File (strictMaybeToMaybe (Cfg.socketPath lcc)))))) + , -- 'nodeSocketPath' (third field) is filled in by 'makeNodeConfiguration' + -- from the resolved socket config, so leave it empty here. + pncRpcConfig = + RpcConfig + (Last (Just (runIdentity (Cfg.enableGrpc lcc)))) + (Last (fmap File (strictMaybeToMaybe (Cfg.grpcSocketPath lcc)))) + mempty + , -- Backend selector, query batch size and snapshot policy are all mapped + -- from cardano-config. 'DeprecatedOptions' has no cardano-config + -- counterpart (they are the legacy top-level SnapshotInterval / + -- NumOfDiskSnapshots keys), so it keeps the node's empty default. + pncLedgerDbConfig = + Last (Just (LedgerDbConfiguration + (fromCfgSnapshotPolicy (strictMaybeToMaybe (Cfg.snapshots ledgerDbCfg))) + (maybe DefaultQueryBatchSize RequestedQueryBatchSize + (strictMaybeToMaybe (Cfg.queryBatchSize ledgerDbCfg))) + (maybe V2InMemory fromCfgBackend + (strictMaybeToMaybe (Cfg.backendSelector ledgerDbCfg))) + noDeprecatedOptions)) + } + where + protoCfg = Cfg.protocolConfiguration cfg + netCfg = Cfg.networkConfiguration cfg + mempCfg = Cfg.mempoolConfiguration cfg + storeCfg = Cfg.storageConfiguration cfg + lcc = Cfg.localConnectionsConfig cfg + ledgerDbCfg = runIdentity (Cfg.ledgerDbConfiguration storeCfg) + consensusModeVal = runIdentity (Cfg.getConsensusConfiguration (Cfg.consensusConfiguration cfg)) + + fromCfgDbPaths :: Cfg.NodeDatabasePaths -> NodeDatabasePaths + fromCfgDbPaths (Cfg.SingleDB p) = OnePathForAllDbs p + fromCfgDbPaths (Cfg.SplitDB imm vol) = MultipleDbPaths imm vol + + fromCfgDiffusionMode :: Cfg.DiffusionMode -> DiffusionMode + fromCfgDiffusionMode Cfg.InitiatorOnly = InitiatorOnlyDiffusionMode + fromCfgDiffusionMode Cfg.InitiatorAndResponder = InitiatorAndResponderDiffusionMode + + fromCfgAcceptedConnLimit :: Cfg.AcceptedConnectionsLimit -> AcceptedConnectionsLimit + fromCfgAcceptedConnLimit c = + AcceptedConnectionsLimit + { acceptedConnectionsHardLimit = Cfg.hardLimit c + , acceptedConnectionsSoftLimit = Cfg.softLimit c + , acceptedConnectionsDelay = Cfg.delayOnSoftLimit c + } + + fromCfgConsensusMode :: Cfg.ConsensusMode -> ConsensusMode + fromCfgConsensusMode Cfg.PraosMode = PraosMode + fromCfgConsensusMode (Cfg.GenesisMode _) = GenesisMode + + toPeerSharing :: Bool -> PeerSharing + toPeerSharing True = PeerSharingEnabled + toPeerSharing False = PeerSharingDisabled + + toNodeShutdownOn :: Cfg.ShutdownOn -> ShutdownOn + toNodeShutdownOn (Cfg.ShutdownAtSlot w) = ASlot (SlotNo w) + toNodeShutdownOn (Cfg.ShutdownAtBlock w) = ABlock (BlockNo w) + + fromCfgAffinity :: Cfg.ResponderCoreAffinityPolicy -> ResponderCoreAffinityPolicy + fromCfgAffinity Cfg.NoResponderCoreAffinity = NoResponderCoreAffinity + fromCfgAffinity Cfg.ResponderCoreAffinity = ResponderCoreAffinity + + fromCfgTxSubmissionLogic :: Cfg.TxSubmissionLogicVersion -> TxSubmissionLogicVersion + fromCfgTxSubmissionLogic Cfg.TxSubmissionLogicV1 = TxSubmissionLogicV1 + fromCfgTxSubmissionLogic Cfg.TxSubmissionLogicV2 = TxSubmissionLogicV2 + + -- Map cardano-config's snapshot policy onto the node's 'SnapshotPolicyArgs', + -- mirroring how POM's LedgerDB parser builds it: a named Mithril policy + -- selects the predefined 'mithrilSnapshotPolicyArgs', a custom policy is + -- mapped field-by-field, and absence keeps the node default. + fromCfgSnapshotPolicy :: Maybe Cfg.SnapshotPolicy -> SnapshotPolicyArgs + fromCfgSnapshotPolicy Nothing = defaultSnapshotPolicyArgs + fromCfgSnapshotPolicy (Just Cfg.MithrilSnapshotPolicy) = mithrilSnapshotPolicyArgs + fromCfgSnapshotPolicy (Just (Cfg.CustomSnapshotPolicy opts)) = + SnapshotPolicyArgs + (SnapshotFrequency SnapshotFrequencyArgs + { sfaInterval = + maybe UseDefault Override (strictMaybeToMaybe (Cfg.snapshotInterval opts) >>= nonZero) + , sfaOffset = + maybe UseDefault (Override . SlotNo) (strictMaybeToMaybe (Cfg.slotOffset opts)) + , sfaRateLimit = + maybe UseDefault (Override . secondsToDiffTime . fromIntegral) + (strictMaybeToMaybe (Cfg.snapshotRateLimit opts)) + , sfaDelaySnapshotRange = + case (strictMaybeToMaybe (Cfg.minDelay opts), strictMaybeToMaybe (Cfg.maxDelay opts)) of + (Just mn, Just mx) -> + Override (SnapshotDelayRange (secondsToDiffTime (fromIntegral mn)) + (secondsToDiffTime (fromIntegral mx))) + _ -> UseDefault + }) + (maybe UseDefault (Override . NumOfDiskSnapshots . fromIntegral) + (strictMaybeToMaybe (Cfg.numOfDiskSnapshots opts))) + + fromCfgBackend :: Cfg.LedgerDbBackendSelector -> LedgerDbSelectorFlag + fromCfgBackend Cfg.V2InMemory = V2InMemory + -- The node's 'V2LSM' only carries the database path; cardano-config's extra + -- export path has no node counterpart and is dropped here. + fromCfgBackend (Cfg.V2LSM dbPath _exportPath) = V2LSM (strictMaybeToMaybe dbPath) + + -- cardano-config's 'GenesisConfigFlags' mirrors the node's field-for-field, + -- except 'gcfCSJJumpSize' is a raw 'Word64' there vs a 'SlotNo' here, and the + -- optional fields are 'StrictMaybe' vs 'Maybe'. + fromCfgGenesisFlags :: Cfg.GenesisConfigFlags -> GenesisConfigFlags + fromCfgGenesisFlags f = + GenesisConfigFlags + (Cfg.gcfEnableCSJ f) + (Cfg.gcfEnableLoEAndGDD f) + (Cfg.gcfEnableLoP f) + (strictMaybeToMaybe (Cfg.gcfBlockFetchGracePeriod f)) + (strictMaybeToMaybe (Cfg.gcfBucketCapacity f)) + (strictMaybeToMaybe (Cfg.gcfBucketRate f)) + (fmap SlotNo (strictMaybeToMaybe (Cfg.gcfCSJJumpSize f))) + (strictMaybeToMaybe (Cfg.gcfGDDRateLimit f)) + +-- | Map @cardano-config@ 'Cfg.Credentials' (file paths) onto the node's +-- 'ProtocolFilepaths'. +credentialsToProtocolFilepaths :: Cfg.Credentials -> ProtocolFilepaths +credentialsToProtocolFilepaths c = + ProtocolFilepaths + { byronCertFile = strictMaybeToMaybe (Cfg.byronDelegationCertificate c) + , byronKeyFile = strictMaybeToMaybe (Cfg.byronSigningKey c) + , shelleyKESSource = fmap fromCfgKES (strictMaybeToMaybe (Cfg.shelleyKES c)) + , shelleyVRFFile = strictMaybeToMaybe (Cfg.shelleyVRFKey c) + , shelleyCertFile = strictMaybeToMaybe (Cfg.shelleyOperationalCertificate c) + , shelleyBulkCredsFile = strictMaybeToMaybe (Cfg.bulkCredentialsFile c) + } + where + fromCfgKES (Cfg.KESKeyFilePath fp) = KESKeyFilePath fp + fromCfgKES (Cfg.KESAgentSocketPath fp) = KESAgentSocketPath fp + +-- | Build the node's 'NodeProtocolConfiguration' from a @cardano-config@-resolved +-- configuration. Genesis file paths are resolved relative to the configuration +-- file's directory (the way cardano-config resolves them at read time). +nodeProtocolConfigurationFromCardanoConfig :: + Cfg.NodeConfiguration -> NodeProtocolConfiguration +nodeProtocolConfigurationFromCardanoConfig cfg = + NodeProtocolConfigurationCardano + byronConfig + shelleyConfig + alonzoConfig + conwayConfig + dijkstraConfig + hardforkConfig + checkpointsConfig + where + protoCfg = Cfg.protocolConfiguration cfg + testCfg = Cfg.testingConfiguration cfg + configDir = takeDirectory (Cfg.configFilePath cfg) + + genFile :: Cfg.Hashed FilePath -> GenesisFile + genFile h = GenesisFile (configDir Cfg.hashed h) + + genHash :: Cfg.Hashed FilePath -> Maybe GenesisHash + genHash h = Just (GenesisHash (Cfg.hash h)) + + byronGen = Cfg.byronGenesis protoCfg + byronConfig = + NodeByronProtocolConfiguration + { npcByronGenesisFile = genFile (Cfg.byronGenesisFile byronGen) + , npcByronGenesisFileHash = genHash (Cfg.byronGenesisFile byronGen) + , npcByronReqNetworkMagic = + maybe RequiresNoMagic fromCfgReqNetworkMagic + (strictMaybeToMaybe (Cfg.byronReqNetworkMagic byronGen)) + , npcByronPbftSignatureThresh = Nothing + , -- cardano-config does not model the Byron software (block) version. The + -- Byron era is genesis-only for synthesis (the test configuration + -- hard-forks to a Shelley-based era at epoch 0), so a fixed default is + -- used. This surfaces as a divergence against POM (see 'adapterGaps'). + npcByronSupportedProtocolVersionMajor = 1 + , npcByronSupportedProtocolVersionMinor = 0 + , npcByronSupportedProtocolVersionAlt = 0 + } + + shelleyConfig = + NodeShelleyProtocolConfiguration + (genFile (Cfg.shelleyGenesis protoCfg)) + (genHash (Cfg.shelleyGenesis protoCfg)) + alonzoConfig = + NodeAlonzoProtocolConfiguration + (genFile (Cfg.alonzoGenesis protoCfg)) + (genHash (Cfg.alonzoGenesis protoCfg)) + conwayConfig = + NodeConwayProtocolConfiguration + (genFile (Cfg.conwayGenesis protoCfg)) + (genHash (Cfg.conwayGenesis protoCfg)) + dijkstraConfig = + fmap + (\h -> NodeDijkstraProtocolConfiguration (genFile h) (genHash h)) + (strictMaybeToMaybe (Cfg.experimentalGenesis testCfg)) + + hardforkConfig = + NodeHardForkProtocolConfiguration + { npcExperimentalHardForksEnabled = runIdentity (Cfg.experimentalHardForksEnabled testCfg) + , npcTestShelleyHardForkAtEpoch = epochOf (Cfg.testShelleyHardForkAtEpoch testCfg) + , npcTestShelleyHardForkAtVersion = strictMaybeToMaybe (Cfg.testShelleyHardForkAtVersion testCfg) + , npcTestAllegraHardForkAtEpoch = epochOf (Cfg.testAllegraHardForkAtEpoch testCfg) + , npcTestAllegraHardForkAtVersion = strictMaybeToMaybe (Cfg.testAllegraHardForkAtVersion testCfg) + , npcTestMaryHardForkAtEpoch = epochOf (Cfg.testMaryHardForkAtEpoch testCfg) + , npcTestMaryHardForkAtVersion = strictMaybeToMaybe (Cfg.testMaryHardForkAtVersion testCfg) + , npcTestAlonzoHardForkAtEpoch = epochOf (Cfg.testAlonzoHardForkAtEpoch testCfg) + , npcTestAlonzoHardForkAtVersion = strictMaybeToMaybe (Cfg.testAlonzoHardForkAtVersion testCfg) + , npcTestBabbageHardForkAtEpoch = epochOf (Cfg.testBabbageHardForkAtEpoch testCfg) + , npcTestBabbageHardForkAtVersion = strictMaybeToMaybe (Cfg.testBabbageHardForkAtVersion testCfg) + , npcTestConwayHardForkAtEpoch = epochOf (Cfg.testConwayHardForkAtEpoch testCfg) + , npcTestConwayHardForkAtVersion = strictMaybeToMaybe (Cfg.testConwayHardForkAtVersion testCfg) + , npcTestDijkstraHardForkAtEpoch = epochOf (Cfg.testDijkstraHardForkAtEpoch testCfg) + , npcTestDijkstraHardForkAtVersion = strictMaybeToMaybe (Cfg.testDijkstraHardForkAtVersion testCfg) + } + + -- Optional checkpoints file (and hash), path resolved relative to the config + -- directory like the genesis files above. + checkpointsConfig = + case strictMaybeToMaybe (Cfg.checkpointsFile protoCfg) of + Nothing -> NodeCheckpointsConfiguration Nothing Nothing + Just mh -> + NodeCheckpointsConfiguration + (Just (CheckpointsFile (configDir Cfg.maybeHashed mh))) + (fmap CheckpointsHash (strictMaybeToMaybe (Cfg.maybeHash mh))) + + epochOf = fmap EpochNo . strictMaybeToMaybe + + fromCfgReqNetworkMagic :: Cfg.RequiresNetworkMagic -> RequiresNetworkMagic + fromCfgReqNetworkMagic Cfg.RequiresNoMagic = RequiresNoMagic + fromCfgReqNetworkMagic Cfg.RequiresMagic = RequiresMagic + +-- | Node 'NodeConfiguration' fields the adapter does not yet populate from +-- @cardano-config@ (they keep the node defaults, so they show up as divergences +-- against POM). Closing these is the remaining work before POM can be dropped. +adapterGaps :: [String] +adapterGaps = + [ "ncProtocolConfig: Byron supported-protocol-version — genuinely not modelled by" + <> " cardano-config; hard-coded default (Byron era is genesis-only for synthesis)" + , "ncTraceForwardSocket — CLI-only in both the node and cardano-config (POM leaves it" + <> " empty when parsing the config file, filling it only from the command line;" + <> " cardano-config's tracerSocket is likewise a CLI argument). It is absent from" + <> " the resolved configuration file, so there is nothing to map and it stays at" + <> " the node default (empty), exactly as POM does when parsing config alone." + ] diff --git a/cardano-node/src/Cardano/Node/Tools/DBSynthesizer.hs b/cardano-node/src/Cardano/Node/Tools/DBSynthesizer.hs index 86a5aae5828..a23e12db559 100644 --- a/cardano-node/src/Cardano/Node/Tools/DBSynthesizer.hs +++ b/cardano-node/src/Cardano/Node/Tools/DBSynthesizer.hs @@ -1,4 +1,5 @@ {-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} @@ -6,12 +7,18 @@ -- | Downstream home for the db-synthesizer's configuration and credential -- machinery. -- --- The forging engine ('Consensus.synthesize') lives in consensus and --- consumes a @('ProtocolInfo', block forgers)@ pair plus an 'EpochSize'. --- Constructing those from a node configuration file and on-disk forging --- credentials is a node concern, so it is done here using cardano-node's own --- protocol-instantiation machinery ('Node.mkConsensusProtocol') and cardano-api's --- 'Api.protocolInfo' bridge — the same path the running node takes at startup. +-- The forging engine ('Consensus.synthesize') lives in consensus and consumes a +-- @('ProtocolInfo', block forgers)@ pair plus an 'EpochSize'. Constructing those +-- from a node configuration file and on-disk forging credentials is a node +-- concern, so it is done here. +-- +-- The node configuration file is parsed and resolved by the shared +-- @cardano-config@ package and then mapped to the node's own 'NodeConfiguration' +-- by the shared adapter ('cardanoConfigToNodeConfiguration'); its +-- 'ncProtocolConfig' is handed to cardano-node's protocol-instantiation +-- machinery ('Node.mkConsensusProtocol'), with the forging credentials supplied +-- separately (from the tool's CLI), and cardano-api's 'Api.protocolInfo' bridge +-- produces the forging @(ProtocolInfo, forgers)@ pair. module Cardano.Node.Tools.DBSynthesizer ( DBSynthesizerException (..) , initializeProtocol @@ -21,23 +28,15 @@ module Cardano.Node.Tools.DBSynthesizer import Cardano.Api (BlockType (..), ProtocolInfoArgs (..)) import qualified Cardano.Api as Api (protocolInfo) +import qualified Cardano.Configuration as Cfg (resolveConfigurationFromFile) import qualified Cardano.Ledger.Api.Transition as Ledger (tcShelleyGenesisL) import Cardano.Ledger.Shelley.Genesis (ShelleyGenesis, sgEpochLength) -import Cardano.Node.Configuration.POM - ( NodeConfiguration (..) - , PartialNodeConfiguration (..) - , defaultPartialNodeConfiguration - , makeNodeConfiguration - , parseNodeConfigurationFP - ) -import Cardano.Node.Handlers.Shutdown (ShutdownConfig (..)) +import Cardano.Node.Configuration.CardanoConfigAdapter (cardanoConfigToNodeConfiguration) +import Cardano.Node.Configuration.POM (NodeConfiguration (..)) import Cardano.Node.Protocol (ProtocolInstantiationError) import qualified Cardano.Node.Protocol as Node (mkConsensusProtocol) import Cardano.Node.Protocol.Types (SomeConsensusProtocol (..)) -import Cardano.Node.Types - ( ConfigYamlFilePath (..) - , ProtocolFilepaths (..) - ) +import Cardano.Node.Types (ProtocolFilepaths) import Cardano.Slotting.Slot (EpochSize) import qualified Cardano.Tools.DBSynthesizer.Run as Consensus (synthesize) import Cardano.Tools.DBSynthesizer.Types (DBSynthesizerOptions, ForgeResult) @@ -48,18 +47,18 @@ import qualified Ouroboros.Consensus.Cardano.Node as Consensus import Control.Applicative (Const (..)) import Control.Exception (Exception (..), throwIO) import Control.Monad.Trans.Except (runExceptT) -import Data.Monoid (Last (..)) +import Control.Tracer (Tracer) import Ouroboros.Consensus.Block.Forging (MkBlockForging) import Ouroboros.Consensus.Cardano.Block (CardanoBlock, StandardCrypto) import Ouroboros.Consensus.Node.ProtocolInfo (ProtocolInfo) import Ouroboros.Consensus.Protocol.Praos.AgentClient (KESAgentClientTrace) -import Control.Tracer (Tracer) -- | Something went wrong turning a node configuration (plus credentials) into a -- forging-capable Cardano protocol. data DBSynthesizerException - = -- | The configuration file could not be parsed or assembled. + = -- | The configuration file could not be parsed/resolved by cardano-config, + -- or adapted to the node's 'NodeConfiguration'. DBSynthesizerConfigError String | -- | The protocol could not be instantiated from the configuration. DBSynthesizerProtocolError ProtocolInstantiationError @@ -71,8 +70,9 @@ data DBSynthesizerException instance Exception DBSynthesizerException -- | Build the ready-made 'ProtocolInfo', block forgers and 'EpochSize' that --- 'Consensus.synthesize' needs, from a node configuration file and forging --- credentials. This is the ejected @initialize@, now built on the node. +-- 'Consensus.synthesize' needs, from a node configuration file (parsed with +-- @cardano-config@, adapted to the node's 'NodeConfiguration') and forging +-- credentials (supplied separately, e.g. from the tool's CLI). initializeProtocol :: -- | Path to the node's @config.json@. FilePath -> @@ -85,12 +85,16 @@ initializeProtocol :: , EpochSize ) initializeProtocol configFp protocolFiles = do - nc <- + cfgNc <- + Cfg.resolveConfigurationFromFile configFp >>= \case + Left err -> throwIO (DBSynthesizerConfigError (show err)) + Right (nc, _warnings) -> pure nc + nodeCfg <- either (throwIO . DBSynthesizerConfigError) pure - =<< mkNodeConfig configFp protocolFiles + (cardanoConfigToNodeConfiguration cfgNc) someProto <- either (throwIO . DBSynthesizerProtocolError) pure - =<< runExceptT (Node.mkConsensusProtocol (ncProtocolConfig nc) (Just (ncProtocolFiles nc))) + =<< runExceptT (Node.mkConsensusProtocol (ncProtocolConfig nodeCfg) (Just protocolFiles)) case someProto of SomeConsensusProtocol CardanoBlockType runP -> do (protoInfo, mkForgers) <- Api.protocolInfo @IO runP @@ -115,23 +119,6 @@ synthesizeFromConfig configFp protocolFiles opts dbDir = do where genTxs _ _ _ _ = pure [] --- | Assemble a 'NodeConfiguration' from a config file, injecting the given --- forging credentials (which can only otherwise be supplied on the node command --- line) and the defaults the synthesizer needs. -mkNodeConfig :: FilePath -> ProtocolFilepaths -> IO (Either String NodeConfiguration) -mkNodeConfig configFp protocolFiles = do - configYamlPc <- parseNodeConfigurationFP (Just configYaml) - pure $ makeNodeConfiguration (configYamlPc <> filesPc) - where - configYaml = ConfigYamlFilePath configFp - filesPc = - defaultPartialNodeConfiguration - { pncProtocolFiles = Last (Just protocolFiles) - , pncValidateDB = Last (Just False) - , pncShutdownConfig = Last (Just (ShutdownConfig Nothing Nothing)) - , pncConfigFile = Last (Just configYaml) - } - -- | Extract the Shelley genesis from a Cardano protocol's transition config. -- Total for the Cardano protocol; the caller has already matched -- 'CardanoBlockType'. From cce84c1680220da30c13cbfd175867d910e03143 Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Fri, 24 Jul 2026 16:02:41 +0200 Subject: [PATCH 3/4] Compare the resolved configuration and flag discrepancies --- cardano-node/app/cardano-node.hs | 99 +++++- cardano-node/cardano-node.cabal | 16 + .../Configuration/CardanoConfigCompare.hs | 311 ++++++++++++++++++ cardano-node/src/Cardano/Node/Run.hs | 77 +++++ .../test/cardano-config-compare/Main.hs | 103 ++++++ 5 files changed, 604 insertions(+), 2 deletions(-) create mode 100644 cardano-node/src/Cardano/Node/Configuration/CardanoConfigCompare.hs create mode 100644 cardano-node/test/cardano-config-compare/Main.hs diff --git a/cardano-node/app/cardano-node.hs b/cardano-node/app/cardano-node.hs index 563193bd652..b52c80289f5 100644 --- a/cardano-node/app/cardano-node.hs +++ b/cardano-node/app/cardano-node.hs @@ -4,21 +4,32 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TemplateHaskell #-} +import qualified Cardano.Configuration as Cfg +import qualified Cardano.Configuration.CliArgs as CliArgs +import qualified Cardano.Configuration.Commands as Cmds import qualified Cardano.Crypto.Init as Crypto import Cardano.Git.Rev (gitRev) -import Cardano.Node.Configuration.POM (PartialNodeConfiguration (..)) +import Cardano.Node.Configuration.CardanoConfigAdapter + (cardanoConfigToNodeConfiguration) +import Cardano.Node.Configuration.CardanoConfigCompare + (compareConfigurations) +import Cardano.Node.Configuration.POM (NodeConfiguration (..), + PartialNodeConfiguration (..), defaultPartialNodeConfiguration, + makeNodeConfiguration, parseNodeConfigurationFP) import Cardano.Node.Handlers.TopLevel import Cardano.Node.Parsers (nodeCLIParser) import Cardano.Node.Run (runNode) import Cardano.Node.Tracing.Documentation (TraceDocumentationCmd (..), parseTraceDocumentationCmd, runTraceDocumentationCmd) +import Cardano.Node.Types (ConfigYamlFilePath (..)) -import Data.Monoid (Last (getLast)) +import Data.Monoid (Last (..)) import qualified Data.Text as Text import qualified Data.Text.IO as Text import Data.Version (showVersion) import Options.Applicative import qualified Options.Applicative as Opt +import System.Exit (exitFailure) import System.Info (arch, compilerName, compilerVersion, os) import System.IO (hPutStrLn, stderr) @@ -37,6 +48,7 @@ main = do runNode args TraceDocumentation tdc -> runTraceDocumentationCmd tdc VersionCmd -> runVersionCommand + ConfigCmd act -> act where p = Opt.prefs Opt.showHelpOnEmpty @@ -56,6 +68,7 @@ main = do Opt.info (fmap RunCmd nodeCLIParser <|> fmap TraceDocumentation parseTraceDocumentationCmd <|> parseVersionCmd + <|> fmap ConfigCmd configSubcommands <**> helper) ( Opt.fullDesc <> @@ -66,6 +79,7 @@ main = do data Command = RunCmd PartialNodeConfiguration | TraceDocumentation TraceDocumentationCmd | VersionCmd + | ConfigCmd (IO ()) -- Yes! A --version flag or version command. Either guess is right! parseVersionCmd :: Parser Command @@ -105,3 +119,84 @@ command' c descr p = [ command c (info (p <**> helper) $ mconcat [ progDesc descr ]) , metavar c ] + +-- cardano-config subcommands -------------------------------------------------- + +-- | The @migrate@, @schema@ and @resolve@ subcommands, spliced from the shared +-- @cardano-config:commands@ sublibrary. @migrate@ and @schema@ are +-- cardano-config's own commands, unchanged; @resolve@ is a node-specific variant +-- (see 'resolveDualCommand') that additionally cross-checks the node's own parser +-- against cardano-config's. +configSubcommands :: Parser (IO ()) +configSubcommands = + Opt.hsubparser + ( Opt.commandGroup "Configuration commands:" + <> Cmds.migrateCommand + <> Cmds.schemaCommand + <> resolveDualCommand + ) + +-- | A node-specific @resolve@: resolve the configuration with cardano-config +-- (printing the result as YAML, exactly like cardano-config's own @resolve@), +-- then re-resolve the same configuration with the node's own POM parser and +-- report any discrepancies between the two. Exits non-zero when they disagree, +-- so it doubles as a CI parity check while the node still has two parsers. +resolveDualCommand :: Mod CommandFields (IO ()) +resolveDualCommand = + command "resolve" + ( info + (runDualResolve <$> Cmds.resolveOptionsParser) + ( progDesc + ( "Resolve a cardano-node configuration (defaults + file + CLI) with both the " + <> "node and cardano-config parsers, print the result as YAML, and report any " + <> "discrepancies between the two parsers (exit non-zero if they disagree)." + ) + ) + ) + +runDualResolve :: Cmds.ResolveOptions -> IO () +runDualResolve resolveOpts@(Cmds.ResolveOptions cli _geneses) = do + -- Print the resolved configuration using cardano-config's own renderer (which + -- honours --with-geneses); this also terminates via 'die' if resolution fails. + Cmds.runResolveCommand resolveOpts + -- Cross-check: resolve the same inputs with the node's POM parser and diff. + discrepancies <- resolveDiscrepancies cli + case discrepancies of + [] -> + putStrLn "resolve: the node and cardano-config parsers agree on the resolved configuration." + ds -> do + hPutStrLn stderr $ + "resolve: " <> show (length ds) + <> " discrepancy(ies) between the node and cardano-config parsers:" + mapM_ (hPutStrLn stderr . (" - " <>)) ds + exitFailure + +-- | Resolve the configuration file (+ CLI) both ways and return the divergences. +-- The node (POM) side takes its CLI-supplied, file-absent fields (topology / +-- database / protocol files / socket) from the shared cardano-config resolution, +-- so the diff reflects how the two parsers read the configuration FILE (plus the +-- documented adapter gaps) rather than an independent — and necessarily +-- asymmetric — CLI reverse-mapping. +resolveDiscrepancies :: Cfg.CliArgs -> IO [String] +resolveDiscrepancies cli = do + (fileCfg, _warns) <- Cfg.parseConfigurationFiles configFp + case Cfg.resolveConfiguration cli fileCfg of + Left err -> pure ["cardano-config failed to resolve the configuration: " <> show err] + Right (cfgNc, _) -> + case cardanoConfigToNodeConfiguration cfgNc of + Left adaptErr -> pure ["cardano-config configuration could not be adapted: " <> adaptErr] + Right adaptedNc -> do + filePartial <- parseNodeConfigurationFP (Just (ConfigYamlFilePath configFp)) + let withCli = + (defaultPartialNodeConfiguration <> filePartial) + { pncConfigFile = Last (Just (ConfigYamlFilePath configFp)) + , pncTopologyFile = Last (Just (ncTopologyFile adaptedNc)) + , pncDatabaseFile = Last (Just (ncDatabaseFile adaptedNc)) + , pncProtocolFiles = Last (Just (ncProtocolFiles adaptedNc)) + , pncSocketConfig = Last (Just (ncSocketConfig adaptedNc)) + } + case makeNodeConfiguration withCli of + Left err -> pure ["node parser (makeNodeConfiguration) failed: " <> err] + Right pomNc -> pure (compareConfigurations pomNc adaptedNc) + where + configFp = CliArgs.configFilePath cli diff --git a/cardano-node/cardano-node.cabal b/cardano-node/cardano-node.cabal index 5c3c2f92c0e..bb642271cd7 100644 --- a/cardano-node/cardano-node.cabal +++ b/cardano-node/cardano-node.cabal @@ -60,6 +60,7 @@ library hs-source-dirs: src exposed-modules: Cardano.Node.Configuration.CardanoConfigAdapter + Cardano.Node.Configuration.CardanoConfigCompare Cardano.Node.Configuration.NodeAddress Cardano.Node.Configuration.POM Cardano.Node.Configuration.LedgerDB @@ -191,6 +192,7 @@ library , trace-resources ^>= 0.2.4 , transformers , transformers-except + , tree-diff , typed-protocols:{typed-protocols, stateful} >= 1.2 , yaml @@ -210,6 +212,8 @@ executable cardano-node autogen-modules: Paths_cardano_node build-depends: base + , cardano-config + , cardano-config:commands , cardano-crypto-class , cardano-git-rev , cardano-node @@ -244,6 +248,18 @@ test-suite db-synthesizer-test , tasty , tasty-hunit +test-suite cardano-config-compare-test + import: project-config + hs-source-dirs: test/cardano-config-compare + main-is: Main.hs + type: exitcode-stdio-1.0 + + build-depends: base + , cardano-config + , cardano-node + , tasty + , tasty-hunit + test-suite cardano-node-test import: project-config , maybe-unix diff --git a/cardano-node/src/Cardano/Node/Configuration/CardanoConfigCompare.hs b/cardano-node/src/Cardano/Node/Configuration/CardanoConfigCompare.hs new file mode 100644 index 00000000000..699b518c1d3 --- /dev/null +++ b/cardano-node/src/Cardano/Node/Configuration/CardanoConfigCompare.hs @@ -0,0 +1,311 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE StandaloneDeriving #-} + +-- The 'ToExpr'/'Generic' instances below are orphans, used only for diffing here. +{-# OPTIONS_GHC -Wno-orphans #-} + +-- | Diff the node's POM-resolved 'NodeConfiguration' against the one produced by +-- the @cardano-config@ adapter. The composite fields (the per-era protocol +-- configuration records and the LedgerDB configuration) are diffed structurally +-- with @tree-diff@; scalar fields use a plain @node=… vs cardano-config=…@ line. +module Cardano.Node.Configuration.CardanoConfigCompare + ( compareConfigurations + , deprecatedFlagWarnings + ) where + +import Cardano.Node.Configuration.POM (NodeConfiguration (..)) +import Cardano.Node.Types (NodeProtocolConfiguration (..)) + +import Data.List (intercalate) + +import Cardano.Crypto (RequiresNetworkMagic) +import Cardano.Ledger.BaseTypes.NonZero (NonZero, unNonZero) +import Cardano.Node.Configuration.LedgerDB (DeprecatedOptions (..), + LedgerDbConfiguration (..), LedgerDbSelectorFlag (..)) +import Cardano.Node.Types (CheckpointsFile (..), CheckpointsHash, + GenesisFile (..), GenesisHash, + MaxConcurrencyBulkSync (..), MaxConcurrencyDeadline (..), + NodeAlonzoProtocolConfiguration (..), + NodeByronProtocolConfiguration (..), + NodeCheckpointsConfiguration (..), + NodeConwayProtocolConfiguration (..), + NodeDijkstraProtocolConfiguration (..), + NodeHardForkProtocolConfiguration (..), + NodeShelleyProtocolConfiguration (..)) +import Cardano.Slotting.Slot (EpochNo, SlotNo (..)) +import Data.Time.Clock (DiffTime, secondsToDiffTime) +import Data.TreeDiff (Expr (App, Rec), ToExpr (..), ediff, prettyEditExpr) +import qualified Data.TreeDiff.OMap as OMap +import GHC.Generics (Generic) +import Ouroboros.Consensus.Mempool (MempoolCapacityBytesOverride (..)) +import Ouroboros.Consensus.Storage.LedgerDB.Args (QueryBatchSize (..), + defaultQueryBatchSize) +import Ouroboros.Consensus.Storage.LedgerDB.Snapshots + (NumOfDiskSnapshots (..), SnapshotDelayRange (..), + SnapshotFrequency (..), SnapshotFrequencyArgs (..), + SnapshotPolicyArgs (..)) +import Ouroboros.Consensus.Util.Args (OverrideOrDefault (..)) + +-- | Guidance for deprecated node CLI flags that cardano-config's parser rejects: +-- the legacy aliases have a new spelling, and the mempool flags were removed +-- (mempool capacity is a config-file setting now). Pure, so it is unit-testable. +deprecatedFlagWarnings :: [String] -> [String] +deprecatedFlagWarnings = concatMap diagnose + where + -- Deprecated alias -> new (cardano-config-accepted) spelling. + renamed = + [ ("--delegation-certificate", "--byron-delegation-certificate") + , ("--signing-key", "--byron-signing-key") + , ("--non-producing-node", "--start-as-non-producing-node") + ] + removed = ["--mempool-capacity-override", "--no-mempool-capacity-override"] + + diagnose tok = + -- Accept both @--flag value@ and @--flag=value@ spellings. + let opt = takeWhile (/= '=') tok + in case lookup opt renamed of + Just new -> + [ "warning: deprecated CLI flag '" <> opt <> "'; use '" <> new + <> "' (required for cardano-config parsing / the upcoming config parser)" ] + Nothing + | opt `elem` removed -> + [ "warning: '" <> opt <> "' is deprecated and no longer supported; remove it" + <> " and set 'MempoolCapacityBytesOverride' in the configuration file instead" ] + | otherwise -> [] + +-- | Compare a POM-resolved configuration against the adapter-produced one, field +-- by field. Returns one entry per diverging field; empty means they agree. +compareConfigurations :: NodeConfiguration -> NodeConfiguration -> [String] +compareConfigurations pom adapted = + concat + [ compareProtocol (ncProtocolConfig pom) (ncProtocolConfig adapted) + , cmp "ValidateDB" ncValidateDB + , cmp "TopologyFile" ncTopologyFile + , cmp "DatabaseFile" ncDatabaseFile + , cmp "StartAsNonProducingNode" ncStartAsNonProducingNode + , cmp "ProtocolFiles" ncProtocolFiles + , cmp "ShutdownConfig" ncShutdownConfig + , cmp "SocketConfig" ncSocketConfig + , cmp "DiffusionMode" ncDiffusionMode + , cmp "ExperimentalProtocolsEnabled" ncExperimentalProtocolsEnabled + , cmp "MaxConcurrencyBulkSync" (normalizeMaxConcurrencyBulkSync . ncMaxConcurrencyBulkSync) + , cmp "MaxConcurrencyDeadline" (normalizeMaxConcurrencyDeadline . ncMaxConcurrencyDeadline) + , cmp "TraceForwardSocket" ncTraceForwardSocket + , cmp "MaybeMempoolCapacityOverride" (normalizeMempoolOverride . ncMaybeMempoolCapacityOverride) + , cmpTree "LedgerDbConfig" (normalizeLedgerDb . ncLedgerDbConfig) + , cmp "ProtocolIdleTimeout" ncProtocolIdleTimeout + , cmp "TimeWaitTimeout" ncTimeWaitTimeout + , cmp "EgressPollInterval" ncEgressPollInterval + , cmp "ChainSyncIdleTimeout" ncChainSyncIdleTimeout + , cmp "MempoolTimeoutSoft" ncMempoolTimeoutSoft + , cmp "MempoolTimeoutHard" ncMempoolTimeoutHard + , cmp "MempoolTimeoutCapacity" ncMempoolTimeoutCapacity + , cmp "AcceptedConnectionsLimit" ncAcceptedConnectionsLimit + , cmp "DeadlineTargetOfRootPeers" ncDeadlineTargetOfRootPeers + , cmp "DeadlineTargetOfKnownPeers" ncDeadlineTargetOfKnownPeers + , cmp "DeadlineTargetOfEstablishedPeers" ncDeadlineTargetOfEstablishedPeers + , cmp "DeadlineTargetOfActivePeers" ncDeadlineTargetOfActivePeers + , cmp "DeadlineTargetOfKnownBigLedgerPeers" ncDeadlineTargetOfKnownBigLedgerPeers + , cmp "DeadlineTargetOfEstablishedBigLedgerPeers" ncDeadlineTargetOfEstablishedBigLedgerPeers + , cmp "DeadlineTargetOfActiveBigLedgerPeers" ncDeadlineTargetOfActiveBigLedgerPeers + , cmp "SyncTargetOfRootPeers" ncSyncTargetOfRootPeers + , cmp "SyncTargetOfKnownPeers" ncSyncTargetOfKnownPeers + , cmp "SyncTargetOfEstablishedPeers" ncSyncTargetOfEstablishedPeers + , cmp "SyncTargetOfActivePeers" ncSyncTargetOfActivePeers + , cmp "SyncTargetOfKnownBigLedgerPeers" ncSyncTargetOfKnownBigLedgerPeers + , cmp "SyncTargetOfEstablishedBigLedgerPeers" ncSyncTargetOfEstablishedBigLedgerPeers + , cmp "SyncTargetOfActiveBigLedgerPeers" ncSyncTargetOfActiveBigLedgerPeers + , cmp "ConsensusMode" ncConsensusMode + , cmp "MinBigLedgerPeersForTrustedState" ncMinBigLedgerPeersForTrustedState + , cmp "PeerSharing" ncPeerSharing + , cmp "GenesisConfig" ncGenesisConfig + , cmp "ResponderCoreAffinityPolicy" ncResponderCoreAffinityPolicy + , cmp "RpcConfig" ncRpcConfig + , cmp "TxSubmissionLogicVersion" ncTxSubmissionLogicVersion + , cmp "TxSubmissionInitDelay" ncTxSubmissionInitDelay + ] + where + cmp :: (Eq a, Show a) => String -> (NodeConfiguration -> a) -> [String] + cmp label accessor = cmpValues label (accessor pom) (accessor adapted) + + cmpTree :: (Eq a, ToExpr a) => String -> (NodeConfiguration -> a) -> [String] + cmpTree label accessor = cmpValuesTree label (accessor pom) (accessor adapted) + +-- | Report a divergence between two scalar values. +cmpValues :: (Eq a, Show a) => String -> a -> a -> [String] +cmpValues label a b + | a == b = [] + | otherwise = [label <> ": node=" <> show a <> " vs cardano-config=" <> show b] + +-- | Report a divergence between two composite values as a @tree-diff@ structural +-- diff (@-@ is the node value, @+@ the cardano-config one), as one indented entry. +cmpValuesTree :: (Eq a, ToExpr a) => String -> a -> a -> [String] +cmpValuesTree label a b + | a == b = [] + | otherwise = + [ label <> ":\n" + <> intercalate "\n" + (map (" " <>) (lines (show (prettyEditExpr (ediff a b))))) ] + +-- | Compare the Cardano protocol configuration per era/component. +compareProtocol :: NodeProtocolConfiguration -> NodeProtocolConfiguration -> [String] +compareProtocol + (NodeProtocolConfigurationCardano b1 s1 a1 c1 d1 h1 k1) + (NodeProtocolConfigurationCardano b2 s2 a2 c2 d2 h2 k2) = + concat + [ cmpValuesTree "Byron protocol config" (normalizeByron b1) (normalizeByron b2) + , cmpValuesTree "Shelley protocol config" s1 s2 + , cmpValuesTree "Alonzo protocol config" a1 a2 + , cmpValuesTree "Conway protocol config" c1 c2 + , cmpValuesTree "Dijkstra protocol config" d1 d2 + , cmpValuesTree "HardFork protocol config" h1 h2 + , cmpValuesTree "Checkpoints protocol config" k1 k2 + ] + +-- --------------------------------------------------------------------------- +-- Normalization: hide representational-only divergences, where POM keeps a +-- "use the default" sentinel and cardano-config spells the default out. +-- --------------------------------------------------------------------------- + +-- | Normalize a 'LedgerDbConfiguration' before diffing (snapshot policy and +-- query batch size). +normalizeLedgerDb :: LedgerDbConfiguration -> LedgerDbConfiguration +normalizeLedgerDb (LedgerDbConfiguration spa qbs sel dep) = + LedgerDbConfiguration (normalizeSnapshotPolicy spa) (normalizeQueryBatchSize qbs) sel dep + +-- | 'DefaultQueryBatchSize' and an explicit request for the same size are +-- equivalent; collapse the latter. +normalizeQueryBatchSize :: QueryBatchSize -> QueryBatchSize +normalizeQueryBatchSize q + | defaultQueryBatchSize q == defaultQueryBatchSize DefaultQueryBatchSize = DefaultQueryBatchSize + | otherwise = q + +-- | Unset ('Nothing') resolves to the 'defaultBlockFetchConfiguration' value of +-- 1, which cardano-config spells out; treat @Just 1@ as 'Nothing'. +normalizeMaxConcurrencyBulkSync :: Maybe MaxConcurrencyBulkSync -> Maybe MaxConcurrencyBulkSync +normalizeMaxConcurrencyBulkSync (Just (MaxConcurrencyBulkSync 1)) = Nothing +normalizeMaxConcurrencyBulkSync x = x + +normalizeMaxConcurrencyDeadline :: Maybe MaxConcurrencyDeadline -> Maybe MaxConcurrencyDeadline +normalizeMaxConcurrencyDeadline (Just (MaxConcurrencyDeadline 1)) = Nothing +normalizeMaxConcurrencyDeadline x = x + +-- | @Just NoMempoolCapacityBytesOverride@ and 'Nothing' both mean "no override". +normalizeMempoolOverride :: Maybe MempoolCapacityBytesOverride -> Maybe MempoolCapacityBytesOverride +normalizeMempoolOverride (Just NoMempoolCapacityBytesOverride) = Nothing +normalizeMempoolOverride x = x + +-- | The Byron supported-protocol version is not modelled by cardano-config and +-- has no effect for a genesis-only Byron era; blank it on both sides. +normalizeByron :: NodeByronProtocolConfiguration -> NodeByronProtocolConfiguration +normalizeByron c = + c { npcByronSupportedProtocolVersionMajor = 0 + , npcByronSupportedProtocolVersionMinor = 0 + , npcByronSupportedProtocolVersionAlt = 0 + } + +-- | Collapse the snapshot overrides that merely restate the consensus default +-- (rate limit, delay range, count). The offset and interval genuinely differ and +-- stay flagged; the interval default is security-parameter dependent (@2*k@) and +-- cannot be reconstructed here. Constants mirror 'defaultSnapshotPolicy'. +normalizeSnapshotPolicy :: SnapshotPolicyArgs -> SnapshotPolicyArgs +normalizeSnapshotPolicy (SnapshotPolicyArgs freq num) = + SnapshotPolicyArgs (normalizeFrequency freq) (collapse defaultNumSnapshots num) + where + defaultNumSnapshots = NumOfDiskSnapshots 2 + defaultOffset = SlotNo 0 + defaultRateLimit = secondsToDiffTime (10 * 60) + defaultDelayRange = SnapshotDelayRange (secondsToDiffTime 300) (secondsToDiffTime 600) + + normalizeFrequency DisableSnapshots = DisableSnapshots + normalizeFrequency (SnapshotFrequency (SnapshotFrequencyArgs interval offset rateLimit delayRange)) = + SnapshotFrequency $ SnapshotFrequencyArgs + interval + (collapse defaultOffset offset) + (collapse defaultRateLimit rateLimit) + (collapse defaultDelayRange delayRange) + + collapse :: Eq a => a -> OverrideOrDefault a -> OverrideOrDefault a + collapse def (Override v) | v == def = UseDefault + collapse _ x = x + +-- --------------------------------------------------------------------------- +-- tree-diff instances for the composite records. Node-local records derive +-- 'Generic' here; opaque leaves (hashes, 'DiffTime') render via 'Show'; the +-- consensus snapshot-policy types lack 'Generic' and get explicit instances. +-- --------------------------------------------------------------------------- + +deriving instance Generic GenesisFile +deriving instance Generic CheckpointsFile +deriving instance Generic NodeByronProtocolConfiguration +deriving instance Generic NodeShelleyProtocolConfiguration +deriving instance Generic NodeAlonzoProtocolConfiguration +deriving instance Generic NodeConwayProtocolConfiguration +deriving instance Generic NodeDijkstraProtocolConfiguration +deriving instance Generic NodeHardForkProtocolConfiguration +deriving instance Generic NodeCheckpointsConfiguration +deriving instance Generic LedgerDbConfiguration +deriving instance Generic LedgerDbSelectorFlag +deriving instance Generic DeprecatedOptions + +instance ToExpr GenesisFile +instance ToExpr CheckpointsFile +instance ToExpr NodeByronProtocolConfiguration +instance ToExpr NodeShelleyProtocolConfiguration +instance ToExpr NodeAlonzoProtocolConfiguration +instance ToExpr NodeConwayProtocolConfiguration +instance ToExpr NodeDijkstraProtocolConfiguration +instance ToExpr NodeHardForkProtocolConfiguration +instance ToExpr NodeCheckpointsConfiguration +instance ToExpr LedgerDbConfiguration +instance ToExpr LedgerDbSelectorFlag +instance ToExpr DeprecatedOptions + +-- External types that already derive 'Generic'. +instance ToExpr RequiresNetworkMagic +instance ToExpr QueryBatchSize + +-- Opaque leaves rendered through 'Show'. +instance ToExpr GenesisHash where toExpr = exprViaShow +instance ToExpr CheckpointsHash where toExpr = exprViaShow +instance ToExpr EpochNo where toExpr = exprViaShow +instance ToExpr DiffTime where toExpr = exprViaShow + +-- The consensus snapshot-policy types (no usable 'Generic'). +instance ToExpr SnapshotPolicyArgs where + toExpr (SnapshotPolicyArgs freq num) = + Rec "SnapshotPolicyArgs" $ OMap.fromList + [ ("spaFrequency", toExpr freq) + , ("spaNum", toExpr num) + ] + +instance ToExpr SnapshotFrequency where + toExpr (SnapshotFrequency args) = App "SnapshotFrequency" [toExpr args] + toExpr DisableSnapshots = App "DisableSnapshots" [] + +instance ToExpr SnapshotFrequencyArgs where + toExpr (SnapshotFrequencyArgs interval offset rateLimit delayRange) = + Rec "SnapshotFrequencyArgs" $ OMap.fromList + [ ("sfaInterval", toExpr interval) + , ("sfaOffset", toExpr offset) + , ("sfaRateLimit", toExpr rateLimit) + , ("sfaDelaySnapshotRange", toExpr delayRange) + ] + +instance ToExpr a => ToExpr (OverrideOrDefault a) where + toExpr (Override a) = App "Override" [toExpr a] + toExpr UseDefault = App "UseDefault" [] + +-- 'NonZero' hides its constructor, so unwrap via 'unNonZero'. +instance ToExpr a => ToExpr (NonZero a) where + toExpr n = App "NonZero" [toExpr (unNonZero n)] + +instance ToExpr SlotNo where + toExpr (SlotNo w) = App "SlotNo" [toExpr w] + +instance ToExpr SnapshotDelayRange +instance ToExpr NumOfDiskSnapshots + +-- | Render a value as an opaque @tree-diff@ leaf via its 'Show' instance. +exprViaShow :: Show a => a -> Expr +exprViaShow x = App (show x) [] diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index 78f05cac05d..6335368ff9c 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -26,9 +26,15 @@ module Cardano.Node.Run import Cardano.Api (File (..), FileDirection (..)) import Cardano.Api.Error (displayError) import qualified Cardano.Api as Api +import qualified Cardano.Configuration as Cfg +import qualified Options.Applicative as Opt +import System.Environment (getArgs) import System.Random (randomIO) import qualified Cardano.Crypto.Init as Crypto +import Cardano.Node.Configuration.CardanoConfigAdapter (cardanoConfigToNodeConfiguration) +import Cardano.Node.Configuration.CardanoConfigCompare (compareConfigurations, + deprecatedFlagWarnings) import Cardano.Node.Configuration.LedgerDB import Cardano.Node.Configuration.NodeAddress import Cardano.Node.Configuration.POM (NodeConfiguration (..), @@ -141,6 +147,7 @@ import Data.Either (partitionEithers) import Data.Functor.Identity (Identity (..)) import Data.IP (toSockAddr) import Data.Map.Strict (Map) +import Data.List (isPrefixOf) import qualified Data.Map.Strict as Map import Data.Maybe (catMaybes, fromMaybe, mapMaybe) import Data.Monoid (Last (..)) @@ -188,6 +195,11 @@ runNode cmdPc = do let earlyTracer = stdoutTracer traceWith earlyTracer $ "Node configuration: " <> show nc + -- Also resolve the same configuration with the shared cardano-config parser + -- and warn (non-fatally) if it diverges from the node's own parser, so the two + -- can be reconciled before cardano-config becomes the sole parser. + compareWithCardanoConfig earlyTracer cmdPc nc + forM_ mShelleyVrfFile $ runThrowExceptT . checkVRFFilePermissions earlyTracer . File @@ -204,6 +216,71 @@ runNode cmdPc = do runThrowExceptT :: Exception e => ExceptT e IO a -> IO a runThrowExceptT act = runExceptT act >>= either Exception.throwIO pure +-- | Resolve the node configuration with the shared @cardano-config@ parser and +-- compare it against the node's own POM-resolved configuration, tracing a +-- non-fatal warning for each field that diverges. +-- +-- To keep the comparison fair, cardano-config resolves from the SAME two inputs +-- the node used: it parses the node's own command line with its own CLI parser +-- and combines that with the configuration file, so both sides are @file + CLI@. +-- If cardano-config cannot parse the argv (e.g. a node flag its CLI parser does +-- not model), that is itself a meaningful divergence signal: we warn and fall +-- back to a file-only cardano-config resolution so the rest is still checked. +-- Every parse\/resolve failure here is only ever warned about, never fatal. +compareWithCardanoConfig + :: Tracer IO String + -> PartialNodeConfiguration + -> NodeConfiguration + -> IO () +compareWithCardanoConfig tracer cmdPc nc = + case getLast (pncConfigFile cmdPc) of + Nothing -> pure () + Just (ConfigYamlFilePath cfgFp) -> do + -- Drop the leading @run@ subcommand token(s) to get the flag list, matching + -- cardano-config's flat 'parseCliArgs' (which has no @run@ subcommand). + argv <- getArgs + let flags = dropWhile (not . ("-" `isPrefixOf`)) argv + cliInfo = Opt.info Cfg.parseCliArgs mempty + cliArgs <- case Opt.execParserPure Opt.defaultPrefs cliInfo flags of + Opt.Success cli -> pure cli + Opt.Failure f -> do + let (msg, _exit) = Opt.renderFailure f "cardano-config" + -- A parse failure usually means a deprecated node flag alias; surface + -- actionable guidance for each before the generic parser error. + mapM_ (traceWith tracer . ("cardano-config: " <>)) (deprecatedFlagWarnings flags) + traceWith tracer $ + "cardano-config: could not parse the node CLI arguments (a node flag its" + <> " parser does not model?); comparing file-only. Parser error: " <> msg + pure (Cfg.defaultCliArgs cfgFp) + Opt.CompletionInvoked _ -> pure (Cfg.defaultCliArgs cfgFp) + -- Parse the same configuration file the node used and resolve it together + -- with the CLI arguments, exactly as the node's POM path does. + result <- try $ do + (fileCfg, _fileWarns) <- Cfg.parseConfigurationFiles cfgFp + Exception.evaluate (Cfg.resolveConfiguration cliArgs fileCfg) + case result of + Left (e :: Exception.SomeException) -> + traceWith tracer $ + "cardano-config: failed to parse node configuration (ignored): " <> show e + Right (Left err) -> + traceWith tracer $ + "cardano-config: failed to resolve node configuration (ignored): " <> show err + Right (Right (cfgNc, _warns)) -> + case cardanoConfigToNodeConfiguration cfgNc of + Left adaptErr -> + traceWith tracer $ + "cardano-config: could not adapt to node configuration (ignored): " <> adaptErr + Right adaptedNc -> + case compareConfigurations nc adaptedNc of + [] -> + traceWith tracer + "cardano-config: resolved configuration (file + CLI) agrees with the node parser." + divergences -> + traceWith tracer $ + unlines $ + "cardano-config: WARNING - resolved configuration (file + CLI) diverges from the node parser:" + : map (" - " <>) divergences + -- | Read node configuration from a file specified in 'PartialNodeConfiguration' buildNodeConfiguration :: HasCallStack => PartialNodeConfiguration -- ^ defaults diff --git a/cardano-node/test/cardano-config-compare/Main.hs b/cardano-node/test/cardano-config-compare/Main.hs new file mode 100644 index 00000000000..b833ac6b3ac --- /dev/null +++ b/cardano-node/test/cardano-config-compare/Main.hs @@ -0,0 +1,103 @@ +{-# LANGUAGE ScopedTypeVariables #-} + +-- | Tests for the dual-parse (node vs @cardano-config@) comparison: +-- 'deprecatedFlagWarnings' and 'compareConfigurations' run end-to-end on a +-- fixture resolved by both the POM parser and cardano-config + the adapter. +module Main (main) where + +import Data.List (isInfixOf, isPrefixOf) +import Data.Monoid (Last (..)) + +import qualified Cardano.Configuration as Cfg +import Cardano.Node.Configuration.CardanoConfigAdapter + (cardanoConfigToNodeConfiguration) +import Cardano.Node.Configuration.CardanoConfigCompare + (compareConfigurations, deprecatedFlagWarnings) +import Cardano.Node.Configuration.POM (NodeConfiguration (..), + PartialNodeConfiguration (..), defaultPartialNodeConfiguration, + makeNodeConfiguration, parseNodeConfigurationFP) +import Cardano.Node.Types (ConfigYamlFilePath (..)) + +import Test.Tasty +import Test.Tasty.HUnit + +-- | The db-synthesizer fixture configuration (test cwd is the repo root). +configPath :: FilePath +configPath = "cardano-node/test/db-synthesizer/disk/config/config.json" + +-- | Documented, expected divergences on this fixture — none: the node and +-- cardano-config are expected to agree on everything. +allowedResidualLabels :: [String] +allowedResidualLabels = [] + +main :: IO () +main = defaultMain tests + +tests :: TestTree +tests = testGroup "cardano-config dual-parse comparison" + [ testCase "deprecated CLI aliases yield migration guidance" testDeprecatedAliases + , testCase "removed mempool flags yield removal guidance" testRemovedMempoolFlags + , testCase "no guidance for accepted / unrelated flags" testNoFalsePositives + , testCase "compareConfigurations runs on the fixture (divergences ⊆ residuals)" + testFixtureComparison + ] + +testDeprecatedAliases :: Assertion +testDeprecatedAliases = do + let warnings = + deprecatedFlagWarnings + ["--delegation-certificate", "x", "--signing-key", "y", "--non-producing-node"] + suggests new = any (new `isInfixOf`) warnings + assertBool "suggests --byron-delegation-certificate" (suggests "--byron-delegation-certificate") + assertBool "suggests --byron-signing-key" (suggests "--byron-signing-key") + assertBool "suggests --start-as-non-producing-node" (suggests "--start-as-non-producing-node") + length warnings @?= 3 + +testRemovedMempoolFlags :: Assertion +testRemovedMempoolFlags = do + let warnings = deprecatedFlagWarnings ["--mempool-capacity-override", "100"] + length warnings @?= 1 + assertBool "says no longer supported" + (any ("no longer supported" `isInfixOf`) warnings) + assertBool "points to MempoolCapacityBytesOverride in the config file" + (any ("MempoolCapacityBytesOverride" `isInfixOf`) warnings) + +testNoFalsePositives :: Assertion +testNoFalsePositives = + deprecatedFlagWarnings ["--config", "c.json", "--topology", "t.json", "--database-path", "db"] + @?= [] + +testFixtureComparison :: Assertion +testFixtureComparison = do + -- cardano-config side: resolve from the file and adapt to a NodeConfiguration. + resolved <- Cfg.resolveConfigurationFromFile configPath + (cfgNc, _warns) <- either (assertFailure . (("cardano-config resolve failed: " <>) . show)) pure resolved + adapted <- either (assertFailure . ("adapter failed: " <>)) pure + (cardanoConfigToNodeConfiguration cfgNc) + + -- Node side: parse the same file with POM, mirroring the CLI-only fields + -- (topology / database / protocol files / socket) from the adapter output. + fileYaml <- parseNodeConfigurationFP (Just (ConfigYamlFilePath configPath)) + let withCli = + (defaultPartialNodeConfiguration <> fileYaml) + { pncConfigFile = Last (Just (ConfigYamlFilePath configPath)) + , pncTopologyFile = Last (Just (ncTopologyFile adapted)) + , pncDatabaseFile = Last (Just (ncDatabaseFile adapted)) + , pncProtocolFiles = Last (Just (ncProtocolFiles adapted)) + , pncSocketConfig = Last (Just (ncSocketConfig adapted)) + } + pomNc <- either (assertFailure . ("POM makeNodeConfiguration failed: " <>)) pure + (makeNodeConfiguration withCli) + + let divergences = compareConfigurations pomNc adapted + isAllowed d = any (`isPrefixOf` d) allowedResidualLabels + unexpected = filter (not . isAllowed) divergences + + -- Print what the comparison reports, so the run is legible even when it passes. + putStrLn $ " compareConfigurations reported " <> show (length divergences) + <> " divergence(s) on the fixture:" + mapM_ (putStrLn . (" - " <>)) divergences + + assertBool + ("divergences outside the documented residual set: " <> show unexpected) + (null unexpected) From 7baf7121c353c5bd3b5da56bd3ad89375a3f8b51 Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Fri, 24 Jul 2026 16:53:37 +0200 Subject: [PATCH 4/4] Update to newer LSM options format --- cabal.project | 4 ++-- .../src/Cardano/Node/Configuration/CardanoConfigAdapter.hs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cabal.project b/cabal.project index ff64bc8d67f..b9868d361ab 100644 --- a/cabal.project +++ b/cabal.project @@ -144,8 +144,8 @@ source-repository-package source-repository-package type: git location: https://github.com/IntersectMBO/cardano-config - tag: 200f0333a352718152315a712dfc1f39b7ed5e4a - --sha256: sha256-YgVaOMt6Vjqg9pg10ovsXOJvV5dRmOCjIoQJOs5zjbU= + tag: 146fae3b13ad13a72a72ca9792a8a44ef5076b52 + --sha256: sha256-M+LYeNr30ng261MnjwOURcWe1Gajn8T4YArAUf3XyRY= source-repository-package type: git diff --git a/cardano-node/src/Cardano/Node/Configuration/CardanoConfigAdapter.hs b/cardano-node/src/Cardano/Node/Configuration/CardanoConfigAdapter.hs index 6c16d4835d9..ce8d698dc79 100644 --- a/cardano-node/src/Cardano/Node/Configuration/CardanoConfigAdapter.hs +++ b/cardano-node/src/Cardano/Node/Configuration/CardanoConfigAdapter.hs @@ -258,9 +258,8 @@ cardanoConfigToPartialNodeConfiguration cfg = fromCfgBackend :: Cfg.LedgerDbBackendSelector -> LedgerDbSelectorFlag fromCfgBackend Cfg.V2InMemory = V2InMemory - -- The node's 'V2LSM' only carries the database path; cardano-config's extra - -- export path has no node counterpart and is dropped here. - fromCfgBackend (Cfg.V2LSM dbPath _exportPath) = V2LSM (strictMaybeToMaybe dbPath) + fromCfgBackend (Cfg.V2LSM dbPath exportPath) = + V2LSM (strictMaybeToMaybe dbPath) (strictMaybeToMaybe exportPath) -- cardano-config's 'GenesisConfigFlags' mirrors the node's field-for-field, -- except 'gcfCSJJumpSize' is a raw 'Word64' there vs a 'SlotNo' here, and the