From 0774ef16bb8b60c0019db11fd91157484a849b74 Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Tue, 1 Sep 2026 13:53:53 +0200 Subject: [PATCH 1/2] Use cardano-config to read the node configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The node now recognises two configuration dialects and picks the parser each one calls for: * a legacy (pre-cardano-config) configuration is resolved with the node's own POM parser, as before, and additionally with cardano-config (which migrates it on the fly). The node runs on the POM result; every field the two parsers disagree on is traced as a non-fatal warning at startup, so the two can be reconciled before POM is dropped. * a cardano-config envelope configuration ({ $schema, Version, Configuration }) is resolved with cardano-config alone. The POM parser cannot read one at all — every setting lives nested under Configuration, so POM sees a document with none of the keys it requires — so it is not run, and a failure on this path is fatal rather than a warning: there is no second parser to fall back to. Its tracing configuration is resolved along with the rest and handed to initTraceDispatcher, since trace-dispatcher's own file parser would not find the tracing keys inside the envelope either. CardanoConfigAdapter maps a cardano-config-resolved configuration onto the node's own NodeConfiguration; CardanoConfigCompare diffs two of those field by field; CardanoConfigResolve classifies the file and dispatches. Fields the adapter cannot populate are listed in adapterGaps, and each shows up concretely as a divergence in the legacy cross-check. --- cabal.project | 8 + cardano-node/cardano-node.cabal | 6 +- .../Configuration/CardanoConfigAdapter.hs | 402 ++++++++++++++++++ .../Configuration/CardanoConfigCompare.hs | 126 ++++++ .../Configuration/CardanoConfigResolve.hs | 259 +++++++++++ cardano-node/src/Cardano/Node/Run.hs | 51 ++- cardano-node/src/Cardano/Node/Tracing/API.hs | 16 +- 7 files changed, 841 insertions(+), 27 deletions(-) create mode 100644 cardano-node/src/Cardano/Node/Configuration/CardanoConfigAdapter.hs create mode 100644 cardano-node/src/Cardano/Node/Configuration/CardanoConfigCompare.hs create mode 100644 cardano-node/src/Cardano/Node/Configuration/CardanoConfigResolve.hs diff --git a/cabal.project b/cabal.project index b2e5f12c4d0..74487e0ac73 100644 --- a/cabal.project +++ b/cabal.project @@ -166,3 +166,11 @@ if impl(ghc >= 9.14) -- Do NOT add more source-repository-package stanzas here unless they are strictly -- temporary! Please read the section in CONTRIBUTING about updating dependencies. + +-- TEMPORARY: cardano-config is not published to CHaP yet. Remove this stanza +-- (and depend on the CHaP release) as soon as it is. +source-repository-package + type: git + location: https://github.com/IntersectMBO/cardano-config + tag: 5f17f6c07f7fcccc67b5360da4339e2c16615b41 + --sha256: sha256-IWkreOeztCXfx31MyDtC3PmdlO4vzfklQiB4L2XW5KU= diff --git a/cardano-node/cardano-node.cabal b/cardano-node/cardano-node.cabal index f50385340cc..a99107089df 100644 --- a/cardano-node/cardano-node.cabal +++ b/cardano-node/cardano-node.cabal @@ -69,7 +69,10 @@ library hs-source-dirs: src - exposed-modules: Cardano.Node.Configuration.NodeAddress + exposed-modules: Cardano.Node.Configuration.CardanoConfigAdapter + Cardano.Node.Configuration.CardanoConfigCompare + Cardano.Node.Configuration.CardanoConfigResolve + Cardano.Node.Configuration.NodeAddress Cardano.Node.Configuration.POM Cardano.Node.Configuration.LedgerDB Cardano.Node.Configuration.Socket @@ -134,6 +137,7 @@ library , base16-bytestring , bytestring , cardano-api ^>= 11.6 + , 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..c222f902444 --- /dev/null +++ b/cardano-node/src/Cardano/Node/Configuration/CardanoConfigAdapter.hs @@ -0,0 +1,402 @@ +{-# LANGUAGE ScopedTypeVariables #-} + +-- | Adapter from @cardano-config@'s resolved configuration to the node's own +-- 'NodeConfiguration' (the POM one). +-- +-- 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. +-- +-- This is what the node runs on for a @cardano-config@ envelope configuration — +-- the only parser that can read one — and what the legacy configuration's +-- cross-check is compared against; see +-- 'Cardano.Node.Configuration.CardanoConfigResolve'. +-- +-- Fields not mapped are listed in 'adapterGaps' — that gap list is exactly what +-- must be closed before the POM parser can be dropped, and every 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 qualified Cardano.Configuration.CliArgs as CliArgs +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.Logging.Types (ForwarderMode (..), HowToConnect (..)) +import Cardano.Node.Configuration.LedgerDB (LedgerDbConfiguration (..), + LedgerDbSelectorFlag (..), noDeprecatedOptions) +import Cardano.Node.Configuration.NodeAddress (NodeHostIPv4Address (..), + NodeHostIPv6Address (..)) +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 (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 (..), + SnapshotInterval (..), SnapshotPolicyArgs (..), + defaultSnapshotPolicyArgs, mithrilSnapshotPolicyArgs) +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)))) + , pncDeadlineTargetOfEstablishedPeers = + Last (strictMaybeToMaybe (Cfg.deadlineTargetOfEstablishedPeers netCfg)) + , pncDeadlineTargetOfEstablishedBigLedgerPeers = + Last (strictMaybeToMaybe (Cfg.deadlineTargetOfEstablishedBigLedgerPeers netCfg)) + , pncSyncTargetOfEstablishedBigLedgerPeers = + Last (Just (runIdentity (Cfg.syncTargetOfEstablishedBigLedgerPeers 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)) + , -- The local (IPC) socket path comes from the configuration file or the + -- command line; the node-to-node IPv4/IPv6/port bindings are CLI-only in + -- both parsers, and cardano-config carries them on the resolved + -- configuration, so they are mapped here too. + pncSocketConfig = + Last (Just (SocketConfig + (Last (fmap NodeHostIPv4Address (strictMaybeToMaybe (Cfg.hostAddr cfg)))) + (Last (fmap NodeHostIPv6Address (strictMaybeToMaybe (Cfg.hostIPv6Addr cfg)))) + (Last (strictMaybeToMaybe (Cfg.port cfg))) + (Last (fmap File (strictMaybeToMaybe (Cfg.socketPath lcc)))))) + , pncTraceForwardSocket = + Last (fmap fromCfgTracerConnection (strictMaybeToMaybe (Cfg.tracerSocket cfg))) + , -- '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 + + -- cardano-config records the socket mode as the literal @"Accept"@ / + -- @"Connect"@ its CLI parser produces; anything else cannot occur. + fromCfgTracerConnection :: Cfg.TracerConnection -> (HowToConnect, ForwarderMode) + fromCfgTracerConnection (Cfg.TracerConnection mode method) = + ( case method of + CliArgs.TracerConnectViaPipe fp -> LocalPipe fp + CliArgs.TracerConnectViaRemote host portNo -> + RemoteSocket host (fromIntegral portNo) + , case mode of + "Accept" -> Responder + _ -> Initiator + ) + + 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 (sfaInterval defaultFrequencyArgs) RequestedSnapshotInterval + (strictMaybeToMaybe (Cfg.snapshotInterval opts) >>= nonZero) + , sfaOffset = + maybe (sfaOffset defaultFrequencyArgs) SlotNo + (strictMaybeToMaybe (Cfg.slotOffset opts)) + , sfaRateLimit = + maybe (sfaRateLimit defaultFrequencyArgs) (secondsToDiffTime . fromIntegral) + (strictMaybeToMaybe (Cfg.snapshotRateLimit opts)) + , sfaDelaySnapshotRange = + case (strictMaybeToMaybe (Cfg.minDelay opts), strictMaybeToMaybe (Cfg.maxDelay opts)) of + (Just mn, Just mx) -> + SnapshotDelayRange (secondsToDiffTime (fromIntegral mn)) + (secondsToDiffTime (fromIntegral mx)) + _ -> sfaDelaySnapshotRange defaultFrequencyArgs + }) + (maybe (spaNum defaultSnapshotPolicyArgs) (NumOfDiskSnapshots . fromIntegral) + (strictMaybeToMaybe (Cfg.numOfDiskSnapshots opts))) + + -- A snapshot option the configuration leaves unset keeps the node's own + -- default for that field, exactly as POM's LedgerDB parser does. + defaultFrequencyArgs = case spaFrequency defaultSnapshotPolicyArgs of + SnapshotFrequency sfa -> sfa + DisableSnapshots -> + error "defaultSnapshotPolicyArgs unexpectedly disables snapshots" + + fromCfgBackend :: Cfg.LedgerDbBackendSelector -> LedgerDbSelectorFlag + fromCfgBackend Cfg.V2InMemory = V2InMemory + 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 + -- 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: its + -- @LastKnownBlockVersion-*@ keys are among the ones @migrate@ drops, as + -- they now come from consensus defaults rather than configuration. A + -- fixed default is used here, which surfaces as a divergence against POM + -- for any configuration that still sets them (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) + } + + checkpointsConfig = NodeCheckpointsConfiguration Nothing Nothing + + 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 — the LastKnownBlockVersion-*" + <> " keys are deliberately not modelled by cardano-config (they now come from" + <> " consensus defaults), so a fixed 1/0/0 is used here" + , "ncProtocolConfig: checkpoints — the CheckpointsFile/CheckpointsFileHash keys have" + <> " no cardano-config counterpart, so the checkpoints configuration is always empty" + ] 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..9e6010087b0 --- /dev/null +++ b/cardano-node/src/Cardano/Node/Configuration/CardanoConfigCompare.hs @@ -0,0 +1,126 @@ +-- | Diff the node's POM-resolved 'NodeConfiguration' against the one produced by +-- the @cardano-config@ adapter ('Cardano.Node.Configuration.CardanoConfigAdapter'). +-- +-- Both sides are the node's own 'NodeConfiguration', so every field is compared +-- in the node's own representation — a reported divergence reflects a genuine +-- difference in the resolved value, with no representation mismatch and no +-- deferred set. A field the adapter cannot yet populate from cardano-config keeps +-- the node default and therefore surfaces here as a divergence (see +-- 'Cardano.Node.Configuration.CardanoConfigAdapter.adapterGaps'). +module Cardano.Node.Configuration.CardanoConfigCompare + ( compareConfigurations + , deprecatedFlagWarnings + ) where + +import Cardano.Node.Configuration.POM (NodeConfiguration (..)) +import Cardano.Node.Types (NodeProtocolConfiguration (..)) + +-- | Diagnose node CLI flags that cardano-config's own CLI parser rejects, in +-- terms the operator can act on. All of these flags are deprecated: the three +-- legacy aliases have a new spelling cardano-config accepts, and the two mempool +-- flags have been removed by design (mempool capacity is a config-file setting +-- now). Given the node's argv flag list, returns one guidance line per offending +-- flag found. A pure function so the dual-parse warning path 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") + ] + -- Deprecated and removed by design; not something cardano-config should grow. + 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 (first argument) against the +-- adapter-produced one (second argument), field by field. Returns one line per +-- diverging field; an empty list means they agree on everything compared. +compareConfigurations :: NodeConfiguration -> NodeConfiguration -> [String] +compareConfigurations pom adapted = + concat + [ -- Protocol config, compared per era/component for readable diffs. + 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" ncMaxConcurrencyBulkSync + , cmp "MaxConcurrencyDeadline" ncMaxConcurrencyDeadline + , cmp "TraceForwardSocket" ncTraceForwardSocket + , cmp "MaybeMempoolCapacityOverride" ncMaybeMempoolCapacityOverride + , cmp "LedgerDbConfig" 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) + +-- | Report a divergence between two values of the same type. +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] + +-- | Compare the Cardano protocol configuration per component (each era's genesis +-- settings, the hard-fork triggers and the checkpoints) for readable diffs. +compareProtocol :: NodeProtocolConfiguration -> NodeProtocolConfiguration -> [String] +compareProtocol + (NodeProtocolConfigurationCardano b1 s1 a1 c1 d1 h1 k1) + (NodeProtocolConfigurationCardano b2 s2 a2 c2 d2 h2 k2) = + concat + [ cmpValues "Byron protocol config" b1 b2 + , cmpValues "Shelley protocol config" s1 s2 + , cmpValues "Alonzo protocol config" a1 a2 + , cmpValues "Conway protocol config" c1 c2 + , cmpValues "Dijkstra protocol config" d1 d2 + , cmpValues "HardFork protocol config" h1 h2 + , cmpValues "Checkpoints protocol config" k1 k2 + ] diff --git a/cardano-node/src/Cardano/Node/Configuration/CardanoConfigResolve.hs b/cardano-node/src/Cardano/Node/Configuration/CardanoConfigResolve.hs new file mode 100644 index 00000000000..0ffc2b3a536 --- /dev/null +++ b/cardano-node/src/Cardano/Node/Configuration/CardanoConfigResolve.hs @@ -0,0 +1,259 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE ScopedTypeVariables #-} + +-- | Turning a node configuration file into the node's 'NodeConfiguration', +-- with whichever of the two parsers the file is written for. +-- +-- The node understands two configuration dialects while the migration to the +-- shared @cardano-config@ package is in progress: +-- +-- [Legacy] the flat, pre-@cardano-config@ configuration the node has always +-- read with its own POM parser. Both parsers can read it (@cardano-config@ +-- migrates it on the fly, see 'Cardano.Configuration.File.Migrate.migrate'), +-- so it is resolved with /both/ and the results are compared: the POM result +-- is what the node runs on, and every divergence is reported as a non-fatal +-- warning so the two can be reconciled before POM is dropped. +-- +-- [Envelope] the @cardano-config@ @{ $schema, Version, Configuration }@ +-- envelope. The POM parser cannot read it at all (every setting lives nested +-- under @Configuration@, so POM sees an empty document and fails on the first +-- required field), so it is not run: @cardano-config@ alone resolves the +-- configuration and the adapter maps it to the node's 'NodeConfiguration'. +-- +-- The dialect is decided by 'classifyConfigurationFile', on the file itself. +-- +-- Note that the legacy path pays for the second parse at every startup: +-- @cardano-config@ reads and decodes the era genesis files as part of resolving, +-- so the genesis files are parsed twice. That is the price of the cross-check and +-- it goes away with the POM parser. +module Cardano.Node.Configuration.CardanoConfigResolve + ( -- * Resolving + ResolvedNodeConfiguration (..) + , CrossCheck (..) + , buildNodeConfiguration + , NodeConfigurationError (..) + + -- * Dialects + , ConfigurationDialect (..) + , classifyConfigurationFile + ) where + +import Cardano.Logging.Types (TraceConfig) +import qualified Cardano.Configuration as Cfg +import qualified Cardano.Configuration.CliArgs as CliArgs +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 Control.Exception (Exception (..)) +import qualified Control.Exception as Exception +import Data.Aeson (Value (..)) +import qualified Data.Aeson.KeyMap as KeyMap +import Data.List (isPrefixOf) +import Data.Monoid (Last (..)) +import qualified Data.Yaml as Yaml +import qualified Options.Applicative as Opt +import System.Environment (getArgs) + +-- | The configuration dialect a node configuration file is written in. +data ConfigurationDialect + = -- | A flat, pre-@cardano-config@ configuration: readable by both parsers. + LegacyDialect + | -- | A @cardano-config@ envelope: readable by @cardano-config@ only. + CardanoConfigDialect + deriving (Eq, Show) + +-- | Whether to cross-check a legacy configuration against @cardano-config@'s +-- parser. Worth it at startup; not worth re-reading every genesis file for on a +-- configuration reload, which only wants the new values. +data CrossCheck + = CrossCheckWithCardanoConfig + | SkipCrossCheck + deriving (Eq, Show) + +-- | The node's resolved configuration together with everything the caller needs +-- to know about how it was obtained. +data ResolvedNodeConfiguration = ResolvedNodeConfiguration + { rncConfiguration :: !NodeConfiguration + , rncTraceConfig :: !(Maybe TraceConfig) + -- ^ The tracing configuration, when it was resolved alongside the rest of the + -- configuration (envelope dialect: the tracing settings live nested under + -- @Configuration.HermodTracing@, where @trace-dispatcher@'s own file parser + -- would not find them). 'Nothing' for the legacy dialect, where + -- @trace-dispatcher@ reads the configuration file itself, as it always has. + , rncReport :: ![String] + -- ^ Non-fatal lines to trace at startup: the parser divergences found by the + -- dual parse, and any warning raised while resolving. + } + +-- | The configuration file could not be turned into a 'NodeConfiguration'. +newtype NodeConfigurationError = NodeConfigurationError String + +instance Show NodeConfigurationError where + show (NodeConfigurationError err) = "Error in creating the NodeConfiguration: " <> err + +instance Exception NodeConfigurationError + +-- | Read the node configuration from the file named by the given +-- 'PartialNodeConfiguration', using the parser its dialect calls for (see the +-- module header). +buildNodeConfiguration :: + -- | Whether to cross-check a legacy configuration against @cardano-config@. + CrossCheck -> + -- | The command-line configuration layer (also naming the configuration file). + PartialNodeConfiguration -> + IO ResolvedNodeConfiguration +buildNodeConfiguration crossCheck partialConf = do + classifyConfigurationFile configFp >>= \case + LegacyDialect -> buildFromLegacy crossCheck partialConf configFp + CardanoConfigDialect -> buildFromCardanoConfig configFp + where + -- An absent @--config@ falls back to the node's default path, exactly as + -- 'parseNodeConfigurationFP' does. + -- 'Last' is right-biased, so the command-line layer goes on the RIGHT of the + -- default to win. + configFp = + unConfigPath $ + case getLast (pncConfigFile defaultPartialNodeConfiguration <> pncConfigFile partialConf) of + Just fp -> fp + -- Unreachable: 'defaultPartialNodeConfiguration' always names a file. + Nothing -> ConfigYamlFilePath "configuration/cardano/mainnet-config.json" + +-- | Decide which dialect a configuration file is written in. +-- +-- The marker is the envelope's @Configuration@ key: that is exactly what +-- @cardano-config@ splits the document on +-- ('Cardano.Configuration.File.Merge.splitEnvelope'), and exactly what makes the +-- document unreadable to POM (which expects the settings at the top level). A +-- file that cannot even be decoded as YAML is reported as legacy so that the +-- POM path raises the syntax error, as it always did. +classifyConfigurationFile :: FilePath -> IO ConfigurationDialect +classifyConfigurationFile fp = + Yaml.decodeFileEither fp >>= \case + Right (Object o) | KeyMap.member "Configuration" o -> pure CardanoConfigDialect + _ -> pure LegacyDialect + +-- | Resolve a legacy configuration with the node's own POM parser — the result +-- the node runs on — and cross-check it against @cardano-config@'s. +buildFromLegacy :: + CrossCheck -> PartialNodeConfiguration -> FilePath -> IO ResolvedNodeConfiguration +buildFromLegacy crossCheck partialConf configFp = do + configYamlPc <- parseNodeConfigurationFP (Just (ConfigYamlFilePath configFp)) + nc <- + either (Exception.throwIO . NodeConfigurationError) pure $ + makeNodeConfiguration (defaultPartialNodeConfiguration <> configYamlPc <> partialConf) + report <- case crossCheck of + SkipCrossCheck -> pure [] + CrossCheckWithCardanoConfig -> crossCheckWithCardanoConfig configFp nc + pure + ResolvedNodeConfiguration + { rncConfiguration = nc + , rncTraceConfig = Nothing + , rncReport = report + } + +-- | Resolve an envelope configuration with @cardano-config@ alone and map it to +-- the node's 'NodeConfiguration'. Unlike the cross-check on the legacy path, +-- every failure here is fatal: there is no second parser to fall back to. +buildFromCardanoConfig :: FilePath -> IO ResolvedNodeConfiguration +buildFromCardanoConfig configFp = do + cliArgs <- + cardanoConfigCliArgs configFp + >>= either (Exception.throwIO . NodeConfigurationError) pure + (fileCfg, fileWarnings) <- Cfg.parseConfigurationFiles configFp + (cfgNc, checkWarnings) <- + either (Exception.throwIO . NodeConfigurationError . show) pure $ + Cfg.resolveConfiguration cliArgs fileCfg + nc <- + either (Exception.throwIO . NodeConfigurationError) pure $ + cardanoConfigToNodeConfiguration cfgNc + pure + ResolvedNodeConfiguration + { rncConfiguration = nc + , rncTraceConfig = Just (Cfg.tracingConfiguration cfgNc) + , rncReport = + "cardano-config: resolved the configuration (the node's own parser was not run: this" + <> " is a cardano-config envelope configuration, which it cannot read)" + : map (("cardano-config: " <>) . Cfg.renderConfigWarning) + (fileWarnings <> checkWarnings) + } + +-- | Resolve the same legacy configuration with @cardano-config@ and diff it +-- against the POM result, returning the lines to report. +-- +-- 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@. Every failure here is only ever reported, never fatal — the node +-- runs on the POM result either way. +crossCheckWithCardanoConfig :: FilePath -> NodeConfiguration -> IO [String] +crossCheckWithCardanoConfig configFp nc = do + (cliArgs, cliReport) <- + cardanoConfigCliArgs configFp >>= \case + Right cli -> pure (cli, []) + Left err -> + -- A parse failure means the operator used a node flag cardano-config + -- does not model (in practice, a deprecated one — 'deprecatedFlagWarnings' + -- names it). Fall back to a file-only resolution so the rest is still + -- checked, and say so. + pure + ( Cfg.defaultCliArgs configFp + , [ "cardano-config: could not parse the node command line; comparing the" + <> " configuration file only. " <> err + ] + ) + result <- Exception.try $ do + (fileCfg, fileWarnings) <- Cfg.parseConfigurationFiles configFp + resolved <- Exception.evaluate (Cfg.resolveConfiguration cliArgs fileCfg) + pure (resolved, fileWarnings) + pure . (cliReport <>) $ case result of + Left (e :: Exception.SomeException) -> + ["cardano-config: failed to parse the node configuration (ignored): " <> show e] + Right (Left err, _) -> + ["cardano-config: failed to resolve the node configuration (ignored): " <> show err] + Right (Right (cfgNc, checkWarnings), fileWarnings) -> + map (("cardano-config: " <>) . Cfg.renderConfigWarning) (fileWarnings <> checkWarnings) + <> case cardanoConfigToNodeConfiguration cfgNc of + Left adaptErr -> + ["cardano-config: could not adapt to the node configuration (ignored): " <> adaptErr] + Right adaptedNc -> + case compareConfigurations nc adaptedNc of + [] -> + [ "cardano-config: the resolved configuration (file + CLI) agrees with the" + <> " node's own parser." + ] + divergences -> + ( "cardano-config: WARNING - the resolved configuration (file + CLI) diverges" + <> " from the node's own parser:" + ) + : map (" - " <>) divergences + +-- | Re-parse the node's own command line with @cardano-config@'s CLI parser, so +-- both parsers resolve from the same @file + CLI@ inputs. +-- +-- The node is invoked as @cardano-node run \@ (the RTS has already +-- stripped its own arguments), so the leading non-flag subcommand token is +-- dropped to get the flag list; @cardano-config@'s 'Cfg.parseCliArgs' is a flat +-- parser with no @run@ subcommand, matching that stripped list. On failure the +-- returned message leads with actionable guidance for each offending flag. +cardanoConfigCliArgs :: FilePath -> IO (Either String Cfg.CliArgs) +cardanoConfigCliArgs configFp = do + argv <- getArgs + let flags = dropWhile (not . ("-" `isPrefixOf`)) argv + pure $ case Opt.execParserPure Opt.defaultPrefs (Opt.info Cfg.parseCliArgs mempty) flags of + Opt.Success cli -> Right (onConfigFile cli) + Opt.CompletionInvoked _ -> Right (Cfg.defaultCliArgs configFp) + Opt.Failure failure -> + let (msg, _exitCode) = Opt.renderFailure failure "cardano-node" + in Left (unlines (deprecatedFlagWarnings flags <> [msg])) + where + -- The file being resolved is already settled (it is what was classified); pin + -- it rather than take cardano-config's own @--config@ default, so a caller that + -- named the file some other way than on the command line still resolves the + -- genesis paths (which hang off the configuration file's directory) correctly. + onConfigFile cli = cli{CliArgs.configFilePath = configFp} diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index b75cd5f0b43..89477bdbb56 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -29,12 +29,14 @@ import qualified Cardano.Api as Api import System.Random (randomIO) import qualified Cardano.Crypto.Init as Crypto +import Cardano.Node.Configuration.CardanoConfigResolve + (CrossCheck (..), ResolvedNodeConfiguration (..), + buildNodeConfiguration) import Cardano.Node.Configuration.LedgerDB import Cardano.Node.Configuration.NodeAddress import Cardano.Node.Configuration.POM (NodeConfiguration (..), PartialNodeConfiguration (..), TimeoutOverride (..), - defaultPartialNodeConfiguration, makeNodeConfiguration, - parseNodeConfigurationFP, getForkPolicy) + getForkPolicy) import Cardano.Node.Configuration.Socket (LocalSocketOrSocketInfo, SocketOrSocketInfo, SocketOrSocketInfo' (..), gatherConfiguredSockets, getSocketOrSocketInfoAddr) @@ -62,7 +64,7 @@ import Cardano.Node.Tracing.Tracers.Startup (getStartupInfo) import Cardano.Node.Types import Cardano.Prelude (FatalError (..), bool, (:~:) (..)) import Cardano.Slotting.Slot (WithOrigin (..)) -import Cardano.Logging.Types (LogFormatting) +import Cardano.Logging.Types (LogFormatting, TraceConfig) import Cardano.Logging.Utils (showT) import Ouroboros.Consensus.Block.Forging (MkBlockForging) @@ -181,12 +183,20 @@ runNode cmdPc = do Crypto.cryptoInit - nc@NodeConfiguration - { ncProtocolConfig - , ncProtocolFiles=ncProtocolFiles@ProtocolFilepaths{shelleyVRFFile=mShelleyVrfFile} - } <- buildNodeConfiguration cmdPc - let earlyTracer = stdoutTracer + + ResolvedNodeConfiguration + { rncConfiguration = nc@NodeConfiguration + { ncProtocolConfig + , ncProtocolFiles=ncProtocolFiles@ProtocolFilepaths{shelleyVRFFile=mShelleyVrfFile} + } + , rncTraceConfig + , rncReport + } <- buildNodeConfiguration CrossCheckWithCardanoConfig cmdPc + + -- What the configuration parsers had to say: which one read the file, and, for + -- a legacy configuration, where the two of them disagree. All non-fatal. + mapM_ (traceWith earlyTracer) rncReport traceWith earlyTracer $ "Node configuration: " <> show nc forM_ mShelleyVrfFile $ @@ -200,29 +210,21 @@ runNode cmdPc = do -- don't need these. (Just ncProtocolFiles) - handleNodeWithTracers cmdPc nc consensusProtocol shelleyGenesisHash + handleNodeWithTracers cmdPc nc rncTraceConfig consensusProtocol shelleyGenesisHash runThrowExceptT :: Exception e => ExceptT e IO a -> IO a runThrowExceptT act = runExceptT act >>= either Exception.throwIO pure --- | Read node configuration from a file specified in 'PartialNodeConfiguration' -buildNodeConfiguration :: HasCallStack - => PartialNodeConfiguration -- ^ defaults - -> IO NodeConfiguration -buildNodeConfiguration partialConf = do - configYamlPc <- parseNodeConfigurationFP . getLast $ pncConfigFile partialConf - either - (\err -> error $ "Error in creating the NodeConfiguration: " <> err) - pure - $ makeNodeConfiguration (defaultPartialNodeConfiguration <> configYamlPc <> partialConf) - handleNodeWithTracers :: PartialNodeConfiguration -> NodeConfiguration + -> Maybe TraceConfig + -- ^ The tracing configuration, when the configuration parser resolved it + -- too; 'Nothing' leaves it to trace-dispatcher to read the file. -> SomeConsensusProtocol -> Api.GenesisHashShelley -> IO () -handleNodeWithTracers cmdPc nc (SomeConsensusProtocol blockType runP) shelleyGenesisHash = do +handleNodeWithTracers cmdPc nc mTrConfig (SomeConsensusProtocol blockType runP) shelleyGenesisHash = do (pInfo@ProtocolInfo{pInfoConfig}, mkBlockForging) <- Api.protocolInfo @IO runP let networkMagic :: Api.NetworkMagic = getNetworkMagic $ Consensus.configBlock pInfoConfig -- This IORef contains node kernel structure which holds node kernel. @@ -235,6 +237,7 @@ handleNodeWithTracers cmdPc nc (SomeConsensusProtocol blockType runP) shelleyGen tracers <- initTraceDispatcher nc + mTrConfig blockType pInfoConfig networkMagic @@ -796,7 +799,11 @@ updateRpcConfiguration :: Tracer IO (StartupTrace blk) -- ^ tracer for configura -> StrictTVar IO RpcConfig -- ^ TVar storing RPC configuration -> IO () updateRpcConfiguration tracer cmdPc rpcConfigVar = do - result <- try @Exception.SomeException $ buildNodeConfiguration cmdPc + -- A reload only wants the new values; the parser cross-check already ran (and + -- was reported) at startup. + result <- + try @Exception.SomeException $ + rncConfiguration <$> buildNodeConfiguration SkipCrossCheck cmdPc case result of Left err -> -- reload failure, we don't do anything this time diff --git a/cardano-node/src/Cardano/Node/Tracing/API.hs b/cardano-node/src/Cardano/Node/Tracing/API.hs index 7ba32e24ee3..3f6dcc4f21d 100644 --- a/cardano-node/src/Cardano/Node/Tracing/API.hs +++ b/cardano-node/src/Cardano/Node/Tracing/API.hs @@ -64,16 +64,24 @@ initTraceDispatcher :: , LogFormatting (TraceGsmEvent (Tip blk)) ) => NodeConfiguration + -> Maybe TraceConfig + -- ^ The tracing configuration, when it was already resolved alongside the + -- rest of the node configuration (see + -- 'Cardano.Node.Configuration.CardanoConfigResolve.rncTraceConfig'). + -- 'Nothing' means read it from the configuration file, as ever. -> BlockType blk -> TopLevelConfig blk -> NetworkMagic -> NodeKernelData blk -> Bool -> IO (Tracers RemoteAddress LocalAddress blk IO) -initTraceDispatcher nc blockType cfg networkMagic nodeKernel noBlockForging = do - trConfig <- readConfigurationWithDefault - (FromFile (unConfigPath $ ncConfigFile nc)) - defaultCardanoConfig +initTraceDispatcher nc mTrConfig blockType cfg networkMagic nodeKernel noBlockForging = do + trConfig <- case mTrConfig of + Just resolved -> pure resolved + Nothing -> + readConfigurationWithDefault + (FromFile (unConfigPath $ ncConfigFile nc)) + defaultCardanoConfig (kickoffForwarder, kickoffPrometheusSimple, tracers) <- mkTracers trConfig From fb6616c4be8cd3e168fb68b5327e306ff7da63e8 Mon Sep 17 00:00:00 2001 From: Javier Sagredo Date: Tue, 1 Sep 2026 14:48:03 +0200 Subject: [PATCH 2/2] Add the cardano-config subcommands and configuration-dialect tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cardano-node grows migrate/schema/resolve, spliced from the shared cardano-config:commands sublibrary. migrate and schema are cardano-config's own commands unchanged; resolve is a node-specific variant that, for a legacy configuration, additionally re-resolves it with the node's own parser and reports any discrepancy (exiting non-zero when they disagree), so it doubles as a CI parity check while the node still has two parsers. On an envelope configuration there is nothing to cross-check against and it says so. The test suite covers both dialects on a fixture configuration carried in both forms — config.json and the config-envelope.json that `cardano-node migrate` produces from it — asserting that the legacy form's parser divergences stay inside the documented residual set, that the envelope is classified as one and genuinely defeats the POM parser, and that migrating does not change what the configuration resolves to. --- .gitattributes | 4 + cardano-node/app/cardano-node.hs | 114 +++++++- cardano-node/cardano-node.cabal | 17 ++ .../test/cardano-config-compare/Main.hs | 257 ++++++++++++++++++ .../config/alonzo-genesis.json | 194 +++++++++++++ .../config/byron-genesis.json | 42 +++ .../config/config-envelope.json | 127 +++++++++ .../cardano-config-compare/config/config.json | 121 +++++++++ .../config/conway-genesis.json | 77 ++++++ .../config/dijkstra-genesis.json | 6 + .../config/shelley-genesis.json | 83 ++++++ 11 files changed, 1040 insertions(+), 2 deletions(-) create mode 100644 cardano-node/test/cardano-config-compare/Main.hs create mode 100644 cardano-node/test/cardano-config-compare/config/alonzo-genesis.json create mode 100644 cardano-node/test/cardano-config-compare/config/byron-genesis.json create mode 100644 cardano-node/test/cardano-config-compare/config/config-envelope.json create mode 100644 cardano-node/test/cardano-config-compare/config/config.json create mode 100644 cardano-node/test/cardano-config-compare/config/conway-genesis.json create mode 100644 cardano-node/test/cardano-config-compare/config/dijkstra-genesis.json create mode 100644 cardano-node/test/cardano-config-compare/config/shelley-genesis.json diff --git a/.gitattributes b/.gitattributes index 5a706b986af..14fae058354 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,5 +5,9 @@ configuration/cardano/mainnet-alonzo-genesis.json text eol=lf configuration/cardano/mainnet-byron-genesis.json text eol=lf configuration/cardano/mainnet-conway-genesis.json text eol=lf configuration/cardano/mainnet-shelley-genesis.json text eol=lf +# Genesis hashes are taken over the raw file bytes, so a CRLF checkout on +# Windows would not match the hashes pinned in the fixture configurations. +cardano-node/test/cardano-config-compare/config/*.json text eol=lf + cardano-testnet/test/cardano-testnet-test/files/sample-proposal-anchor text eol=lf cardano-testnet/test/cardano-testnet-test/files/sample-constitution-anchor text eol=lf diff --git a/cardano-node/app/cardano-node.hs b/cardano-node/app/cardano-node.hs index 563193bd652..a6bea346174 100644 --- a/cardano-node/app/cardano-node.hs +++ b/cardano-node/app/cardano-node.hs @@ -1,24 +1,38 @@ {-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuantifiedConstraints #-} {-# 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.CardanoConfigResolve + (ConfigurationDialect (..), classifyConfigurationFile) +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 +51,7 @@ main = do runNode args TraceDocumentation tdc -> runTraceDocumentationCmd tdc VersionCmd -> runVersionCommand + ConfigCmd act -> act where p = Opt.prefs Opt.showHelpOnEmpty @@ -56,6 +71,7 @@ main = do Opt.info (fmap RunCmd nodeCLIParser <|> fmap TraceDocumentation parseTraceDocumentationCmd <|> parseVersionCmd + <|> fmap ConfigCmd configSubcommands <**> helper) ( Opt.fullDesc <> @@ -66,6 +82,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 +122,96 @@ 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@) and, +-- for a legacy configuration, re-resolve it 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. +-- +-- A cardano-config envelope configuration has nothing to cross-check against: +-- the POM parser cannot read it (which is the whole point of the envelope), so +-- @resolve@ just prints the cardano-config result and says so. +resolveDualCommand :: Mod CommandFields (IO ()) +resolveDualCommand = + command "resolve" + ( info + (runDualResolve <$> Cmds.resolveOptionsParser) + ( progDesc + ( "Resolve a cardano-node configuration (defaults + file + CLI) and print the " + <> "result as YAML. A legacy (pre-cardano-config) configuration is resolved " + <> "with both the node and cardano-config parsers and any discrepancy between " + <> "them is reported (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 + classifyConfigurationFile configFp >>= \case + CardanoConfigDialect -> + putStrLn $ + "resolve: this is a cardano-config envelope configuration; the node's own parser" + <> " cannot read it, so there is nothing to cross-check." + LegacyDialect -> do + 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 + where + configFp = CliArgs.configFilePath cli + +-- | Resolve a legacy 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 a99107089df..dd5716101c0 100644 --- a/cardano-node/cardano-node.cabal +++ b/cardano-node/cardano-node.cabal @@ -14,6 +14,7 @@ license-files: LICENSE NOTICE build-type: Simple extra-doc-files: ChangeLog.md +extra-source-files: test/cardano-config-compare/config/*.json Flag unexpected_thunks Description: Turn on unexpected thunks checks @@ -221,12 +222,28 @@ executable cardano-node autogen-modules: Paths_cardano_node build-depends: base + , cardano-config + , cardano-config:commands , cardano-crypto-class , cardano-git-rev , cardano-node , optparse-applicative , text +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 + , directory + , filepath + , tasty + , tasty-hunit + test-suite cardano-node-test import: project-config , maybe-unix 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..1b34accd617 --- /dev/null +++ b/cardano-node/test/cardano-config-compare/Main.hs @@ -0,0 +1,257 @@ +{-# LANGUAGE ScopedTypeVariables #-} + +-- | Tests for the two configuration dialects the node understands +-- ('Cardano.Node.Configuration.CardanoConfigResolve'): +-- +-- * a legacy (pre-@cardano-config@) configuration is read by both parsers, and +-- the divergences between them are exactly the documented residual set; and +-- * a @cardano-config@ envelope configuration is read by @cardano-config@ only +-- — the node's own POM parser genuinely cannot resolve it, which is why the +-- dual parse is skipped for it — and resolving it yields the same +-- configuration as the legacy form it was migrated from. +-- +-- Plus 'deprecatedFlagWarnings', the operator-facing guidance emitted when +-- @cardano-config@'s CLI parser rejects the node's argv. +-- +-- The two fixture configurations are the same configuration in the two dialects: +-- @config-envelope.json@ is @config.json@ put through @cardano-node migrate@. +-- They sit in the same directory, so both resolve the same (relative) genesis +-- file paths and can be compared field by field. +module Main (main) where + +import Control.Exception (SomeException, evaluate, try) +import Control.Monad (filterM) +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.CardanoConfigResolve + (ConfigurationDialect (..), classifyConfigurationFile) +import Cardano.Node.Configuration.POM (NodeConfiguration (..), + PartialNodeConfiguration (..), defaultPartialNodeConfiguration, + makeNodeConfiguration, parseNodeConfigurationFP) +import Cardano.Node.Types (ConfigYamlFilePath (..)) + +import System.Directory (doesFileExist) +import System.FilePath (()) + +import Test.Tasty +import Test.Tasty.HUnit + +-- | Locate the fixture configuration directory. Depending on the runner the +-- working directory is either the package directory or the repository root, so +-- try both rather than assume one. +fixtureDir :: IO FilePath +fixtureDir = do + found <- filterM (doesFileExist . ( "config.json")) candidates + case found of + dir : _ -> pure dir + [] -> + assertFailure $ + "could not find the fixture configuration directory; looked in " <> show candidates + where + candidates = + [ "test/cardano-config-compare/config" + , "cardano-node/test/cardano-config-compare/config" + ] + +-- | The fixture's legacy (flat, pre-cardano-config) configuration file. +legacyConfigPath :: IO FilePath +legacyConfigPath = ( "config.json") <$> fixtureDir + +-- | The same configuration in the cardano-config envelope (the output of +-- @cardano-node migrate@ on 'legacyConfigPath'). +envelopeConfigPath :: IO FilePath +envelopeConfigPath = ( "config-envelope.json") <$> fixtureDir + +-- | Labels ('compareConfigurations' prefixes each divergence with one) that are +-- documented, expected divergences on this fixture. They fall into three kinds, +-- and none is an adapter defect — the adapter faithfully reflects what +-- cardano-config resolved; the check exists precisely to surface these: +-- +-- (1) adapter gaps — a field the adapter cannot populate from cardano-config +-- (see 'Cardano.Node.Configuration.CardanoConfigAdapter.adapterGaps'); +-- (2) representation differences — same meaning, different shape; +-- (3) parser default mismatches — a field the fixture does not set (or sets +-- under a key one parser ignores), for which the two parsers fall back to +-- different defaults. +-- +-- The fixture comparison must not diverge on anything OUTSIDE this set, so a +-- regression that changes a currently-agreeing field (or an adapter change that +-- breaks a mapped one) is still caught. +allowedResidualLabels :: [String] +allowedResidualLabels = + [ -- (1) adapter gap: Byron supported-protocol-version is hard-coded 1/0/0 by + -- the adapter, whereas the fixture sets LastKnownBlockVersion-Major = 3. + "Byron protocol config" + -- (1) adapter gap: the CheckpointsFile/CheckpointsFileHash keys have no + -- cardano-config counterpart. The fixture sets neither (so this label does + -- not appear on it), but a configuration that does — mainnet's, for one — + -- diverges here. + , "Checkpoints protocol config" + -- (2) representation: "MempoolCapacityBytesOverride: NoOverride" is an + -- explicit no-override value for POM but simply absent for cardano-config. + , "MaybeMempoolCapacityOverride" + -- (3) default mismatch: the fixture leaves these unset; POM defaults to + -- Nothing (no limit) while cardano-config supplies its own default. + , "MaxConcurrencyBulkSync" + , "MaxConcurrencyDeadline" + -- (3) default mismatch: cardano-config ships its own storage defaults where + -- the node uses its bare LedgerDB defaults. + , "LedgerDbConfig" + -- (3) key/default mismatch: the fixture sets TargetNumberOf{Root,Known}Peers + -- (= 100), which POM reads as the deadline targets, but cardano-config falls + -- back to its own deadline defaults. + , "DeadlineTargetOfRootPeers" + , "DeadlineTargetOfKnownPeers" + -- (3) default mismatch: the fixture leaves this unset; the two parsers + -- default it differently. + , "TxSubmissionInitDelay" + ] + +main :: IO () +main = defaultMain tests + +tests :: TestTree +tests = testGroup "cardano-config configuration dialects" + [ testGroup "deprecated CLI flag guidance" + [ testCase "deprecated CLI aliases yield migration guidance" testDeprecatedAliases + , testCase "removed mempool flags yield removal guidance" testRemovedMempoolFlags + , testCase "no guidance for accepted / unrelated flags" testNoFalsePositives + ] + , testGroup "legacy dialect (both parsers)" + [ testCase "the fixture is classified as legacy" testLegacyClassification + , testCase "the two parsers diverge only on the documented residuals" + testLegacyDualParse + ] + , testGroup "cardano-config envelope dialect (cardano-config only)" + [ testCase "the migrated fixture is classified as an envelope" + testEnvelopeClassification + , testCase "the node's own parser cannot resolve an envelope" testEnvelopeDefeatsPom + , testCase "cardano-config resolves an envelope to the same configuration" + testEnvelopeResolvesToSameConfiguration + ] + ] + +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"] + @?= [] + +testLegacyClassification :: Assertion +testLegacyClassification = + legacyConfigPath >>= classifyConfigurationFile >>= (@?= LegacyDialect) + +-- | Resolve the fixture both ways and check that the divergences stay inside the +-- documented residual set. +testLegacyDualParse :: Assertion +testLegacyDualParse = do + configPath <- legacyConfigPath + adapted <- resolveWithCardanoConfig configPath + pomNc <- resolveWithPom configPath adapted + + let divergences = compareConfigurations pomNc adapted + isAllowed d = any (`isPrefixOf` d) allowedResidualLabels + unexpected = filter (not . isAllowed) divergences + + -- Print what the comparison reports on this real config, 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) + +testEnvelopeClassification :: Assertion +testEnvelopeClassification = + envelopeConfigPath >>= classifyConfigurationFile >>= (@?= CardanoConfigDialect) + +-- | The reason the node does not run the dual parse on an envelope: POM cannot +-- read it. Every setting lives nested under @Configuration@, so POM sees a +-- document with none of the settings it requires. It may fail either while +-- decoding the file (its decoder throws on a missing required key) or in +-- 'makeNodeConfiguration'; only that it fails matters here. +testEnvelopeDefeatsPom :: Assertion +testEnvelopeDefeatsPom = do + envelope <- envelopeConfigPath + outcome <- try $ do + filePartial <- parseNodeConfigurationFP (Just (ConfigYamlFilePath envelope)) + evaluate (makeNodeConfiguration (defaultPartialNodeConfiguration <> filePartial)) + case outcome of + Left (_ :: SomeException) -> pure () + Right (Left _) -> pure () + Right (Right _) -> + assertFailure + "the node's own parser resolved an envelope configuration; the dual-parse\ + \ dispatch in CardanoConfigResolve assumes it cannot" + +-- | The envelope is a reshaping, not a change of meaning: resolving it must give +-- exactly what resolving the legacy form it was migrated from gives. +testEnvelopeResolvesToSameConfiguration :: Assertion +testEnvelopeResolvesToSameConfiguration = do + fromLegacy <- resolveWithCardanoConfig =<< legacyConfigPath + fromEnvelope <- resolveWithCardanoConfig =<< envelopeConfigPath + let divergences = compareConfigurations fromLegacy fromEnvelope + assertBool + ("the envelope resolved differently from the legacy form it was migrated from: " + <> show divergences) + (null divergences) + +-- | Resolve a configuration file with cardano-config (file only, no CLI layer) +-- and adapt it to the node's own 'NodeConfiguration'. +resolveWithCardanoConfig :: FilePath -> IO NodeConfiguration +resolveWithCardanoConfig fp = do + resolved <- Cfg.resolveConfigurationFromFile fp + (cfgNc, _warns) <- + either (assertFailure . ("cardano-config resolve failed: " <>) . show) pure resolved + either (assertFailure . ("adapter failed: " <>)) pure + (cardanoConfigToNodeConfiguration cfgNc) + +-- | Resolve a configuration file with the node's own POM parser. +-- +-- The CLI-only fields (topology / database / protocol files / socket) are not in +-- the file; mirror them from the given cardano-config result so the comparison +-- isolates the file-parse and adapter-gap differences rather than CLI-supplied +-- noise. +resolveWithPom :: FilePath -> NodeConfiguration -> IO NodeConfiguration +resolveWithPom fp adapted = do + fileYaml <- parseNodeConfigurationFP (Just (ConfigYamlFilePath fp)) + -- 'PartialNodeConfiguration' is a Semigroup but not a Monoid, so there is no + -- empty value to start from: merge defaults with the file layer (mirroring + -- 'buildNodeConfiguration'), then override the CLI-only fields on top. + let withCli = + (defaultPartialNodeConfiguration <> fileYaml) + { pncConfigFile = Last (Just (ConfigYamlFilePath fp)) + , pncTopologyFile = Last (Just (ncTopologyFile adapted)) + , pncDatabaseFile = Last (Just (ncDatabaseFile adapted)) + , pncProtocolFiles = Last (Just (ncProtocolFiles adapted)) + , pncSocketConfig = Last (Just (ncSocketConfig adapted)) + } + either (assertFailure . ("POM makeNodeConfiguration failed: " <>)) pure + (makeNodeConfiguration withCli) diff --git a/cardano-node/test/cardano-config-compare/config/alonzo-genesis.json b/cardano-node/test/cardano-config-compare/config/alonzo-genesis.json new file mode 100644 index 00000000000..093071bb398 --- /dev/null +++ b/cardano-node/test/cardano-config-compare/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/cardano-config-compare/config/byron-genesis.json b/cardano-node/test/cardano-config-compare/config/byron-genesis.json new file mode 100644 index 00000000000..aec652492ff --- /dev/null +++ b/cardano-node/test/cardano-config-compare/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/cardano-config-compare/config/config-envelope.json b/cardano-node/test/cardano-config-compare/config/config-envelope.json new file mode 100644 index 00000000000..bd815e7be71 --- /dev/null +++ b/cardano-node/test/cardano-config-compare/config/config-envelope.json @@ -0,0 +1,127 @@ +{ + "$schema": "https://raw.githubusercontent.com/IntersectMBO/cardano-config/main/schemas/config.schema.json", + "Configuration": { + "ApplicationName": "cardano-sl", + "ApplicationVersion": 0, + "EnableP2P": false, + "HermodTracing": { + "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" + } + } + }, + "MempoolConfig": { + "MempoolCapacityBytesOverride": "NoOverride" + }, + "NetworkConfig": { + "AcceptedConnectionsLimit": { + "Delay": 5, + "HardLimit": 512, + "SoftLimit": 384 + }, + "DeadlineTargetNumberOfActivePeers": 20, + "DeadlineTargetNumberOfEstablishedPeers": 50, + "DeadlineTargetNumberOfKnownPeers": 100, + "DeadlineTargetNumberOfRootPeers": 100, + "ExperimentalProtocolsEnabled": true, + "ProtocolIdleTimeout": 5, + "TimeWaitTimeout": 60 + }, + "ProtocolConfig": { + "AlonzoGenesisFile": "alonzo-genesis.json", + "AlonzoGenesisHash": "edb92321b614cfd7dcee3f49eedcde4f559ea9526194ff3cf42cd28a4bf0ad7b", + "ByronGenesisFile": "byron-genesis.json", + "ByronGenesisHash": "6836b5d0ae3bb7250c318e8906ab2bf8e42e6acfd4483526be948752707ad435", + "ConwayGenesisFile": "conway-genesis.json", + "ConwayGenesisHash": "e4cbda3b0a0db8ea984330d0951d280ca490540c97cc6407ecd1011b174d983d", + "RequiresNetworkMagic": "RequiresMagic", + "ShelleyGenesisFile": "shelley-genesis.json", + "ShelleyGenesisHash": "f6bb6e9d9b217681180754232470aa936716d62f8e11e944520b97490b100b7c" + }, + "TestingConfig": { + "DijkstraGenesisFile": "dijkstra-genesis.json", + "DijkstraGenesisHash": "56c06ff0f668c584fc54fa3cee92dd5e121b67696924ac3b01b5aec9ecf95b78", + "ExperimentalHardForksEnabled": true, + "TestAllegraHardForkAtEpoch": 0, + "TestAlonzoHardForkAtEpoch": 0, + "TestBabbageHardForkAtEpoch": 0, + "TestMaryHardForkAtEpoch": 0, + "TestShelleyHardForkAtEpoch": 0 + } + }, + "Version": 1 +} diff --git a/cardano-node/test/cardano-config-compare/config/config.json b/cardano-node/test/cardano-config-compare/config/config.json new file mode 100644 index 00000000000..70836962f78 --- /dev/null +++ b/cardano-node/test/cardano-config-compare/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/cardano-config-compare/config/conway-genesis.json b/cardano-node/test/cardano-config-compare/config/conway-genesis.json new file mode 100644 index 00000000000..08e1aed42a3 --- /dev/null +++ b/cardano-node/test/cardano-config-compare/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/cardano-config-compare/config/dijkstra-genesis.json b/cardano-node/test/cardano-config-compare/config/dijkstra-genesis.json new file mode 100644 index 00000000000..c33c6755721 --- /dev/null +++ b/cardano-node/test/cardano-config-compare/config/dijkstra-genesis.json @@ -0,0 +1,6 @@ +{ + "maxRefScriptSizePerBlock": 1048576, + "maxRefScriptSizePerTx": 204800, + "refScriptCostStride": 25600, + "refScriptCostMultiplier": 1.2 +} diff --git a/cardano-node/test/cardano-config-compare/config/shelley-genesis.json b/cardano-node/test/cardano-config-compare/config/shelley-genesis.json new file mode 100644 index 00000000000..7755bcd2242 --- /dev/null +++ b/cardano-node/test/cardano-config-compare/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 +}