Skip to content

Commit

Permalink
Add check-node-configuration command
Browse files Browse the repository at this point in the history
  • Loading branch information
smelc committed Oct 15, 2024
1 parent c34336c commit fc8aee2
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 3 deletions.
2 changes: 2 additions & 0 deletions cardano-cli/cardano-cli.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ library
Cardano.CLI.Commands
Cardano.CLI.Commands.Address
Cardano.CLI.Commands.Debug
Cardano.CLI.Commands.Debug.CheckNodeConfiguration
Cardano.CLI.Commands.Debug.LogEpochState
Cardano.CLI.Commands.Debug.TransactionView
Cardano.CLI.Commands.Hash
Expand Down Expand Up @@ -137,6 +138,7 @@ library
Cardano.CLI.Run.Address
Cardano.CLI.Run.Address.Info
Cardano.CLI.Run.Debug
Cardano.CLI.Run.Debug.CheckNodeConfiguration
Cardano.CLI.Run.Debug.LogEpochState
Cardano.CLI.Run.Debug.TransactionView
Cardano.CLI.Run.Hash
Expand Down
4 changes: 3 additions & 1 deletion cardano-cli/src/Cardano/CLI/Commands/Debug.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ module Cardano.CLI.Commands.Debug
)
where

import Cardano.CLI.Commands.Debug.CheckNodeConfiguration
import Cardano.CLI.Commands.Debug.LogEpochState
import Cardano.CLI.Commands.Debug.TransactionView

data DebugCmds
= DebugLogEpochStateCmd LogEpochStateCmdArgs
= DebugCheckNodeConfigurationCmd CheckNodeConfigCmdArgs
| DebugLogEpochStateCmd LogEpochStateCmdArgs
| DebugTransactionViewCmd TransactionViewCmdArgs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{-# LANGUAGE DataKinds #-}

module Cardano.CLI.Commands.Debug.CheckNodeConfiguration where

import Cardano.Api

-- | Argument for the 'debug check-node-configuration' command.
data CheckNodeConfigCmdArgs = CheckNodeConfigCmdArgs
{ configFileToCheck :: !(NodeConfigFile 'In)
}
deriving Show
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import Cardano.CLI.Run.Address (generateAndWriteKeyFiles)
import qualified Cardano.CLI.Run.Key as Key
import Cardano.CLI.Run.Node (runNodeIssueOpCertCmd, runNodeKeyGenColdCmd,
runNodeKeyGenKesCmd, runNodeKeyGenVrfCmd)
import qualified Cardano.CLI.Read as Read
import Cardano.CLI.Types.Common
import Cardano.CLI.Types.Errors.GenesisCmdError
import Cardano.CLI.Types.Errors.NodeCmdError
Expand All @@ -61,7 +60,6 @@ import qualified Data.Aeson.Encode.Pretty as Aeson
import qualified Data.Aeson.Key as Aeson
import qualified Data.Aeson.KeyMap as Aeson
import Data.Bifunctor (Bifunctor (..))
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy.Char8 as LBS
import Data.ListMap (ListMap (..))
import Data.Map.Strict (Map)
Expand Down
10 changes: 10 additions & 0 deletions cardano-cli/src/Cardano/CLI/Options/Debug.hs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ where
import Cardano.Api.Shelley hiding (QueryInShelleyBasedEra (..), parseFilePath)

import Cardano.CLI.Commands.Debug
import Cardano.CLI.Commands.Debug.CheckNodeConfiguration
import Cardano.CLI.Commands.Debug.LogEpochState
import Cardano.CLI.Commands.Debug.TransactionView
import Cardano.CLI.Environment
Expand Down Expand Up @@ -46,6 +47,10 @@ pDebugCmds envCli =
, " The log file format is line delimited JSON."
, " The command will not terminate."
]
, subParser "check-node-configuration" $
Opt.info pCheckNodeConfigurationCmdArgs $
Opt.progDesc
"Check hashes and paths of genesis files in the given node configuration file."
, subParser "transaction" $
Opt.info
( asum
Expand All @@ -64,6 +69,11 @@ pDebugCmds envCli =
<*> pFileOutDirection
"out-file"
"Output filepath of the log file. The log file format is line delimited JSON."
pCheckNodeConfigurationCmdArgs :: Parser DebugCmds
pCheckNodeConfigurationCmdArgs =
fmap DebugCheckNodeConfigurationCmd $
CheckNodeConfigCmdArgs
<$> pNodeConfigurationFileIn
pTransactionView :: Parser DebugCmds
pTransactionView =
fmap DebugTransactionViewCmd $
Expand Down
1 change: 1 addition & 0 deletions cardano-cli/src/Cardano/CLI/Run/Debug.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ import Cardano.CLI.Types.Errors.DebugCmdError

runDebugCmds :: DebugCmds -> ExceptT DebugCmdError IO ()
runDebugCmds = \case
DebugCheckNodeConfigurationCmd cmd -> runCheckNodeConfig cmd
DebugLogEpochStateCmd cmd -> liftIO $ runLogEpochStateCmd cmd
DebugTransactionViewCmd cmd -> firstExceptT DebugTxCmdError $ runTransactionViewCmd cmd
135 changes: 135 additions & 0 deletions cardano-cli/src/Cardano/CLI/Run/Debug/CheckNodeConfiguration.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}

module Cardano.CLI.Run.Debug.CheckNodeConfiguration (eraToStringKey, runCheckNodeConfig) where

import Cardano.Api

import Cardano.Chain.Genesis (GenesisHash (..), readGenesisData)
import Cardano.CLI.Commands.Debug.CheckNodeConfiguration
import qualified Cardano.CLI.Read as Read
import Cardano.CLI.Types.Errors.DebugCmdError
import qualified Cardano.Crypto.Hash as Crypto

import Data.Maybe (catMaybes)
import qualified Data.Text as Text
import qualified Data.Yaml as Yaml
import System.FilePath (takeDirectory, (</>))

runCheckNodeConfig :: CheckNodeConfigCmdArgs -> ExceptT DebugCmdError IO ()
runCheckNodeConfig (CheckNodeConfigCmdArgs configFile) = do
nodeConfig :: NodeConfig <- liftIO $ Yaml.decodeFileThrow configFilePath
analyses <- catMaybes <$> mapM (\era -> mkAnalysis era configFile nodeConfig) eras
mapM_ (runAnalysis configFile) analyses
liftIO $ putStrLn $ "Successfully checked node configuration file: " <> configFilePath
where
configFilePath = unFile configFile
eras :: [AnyCardanoEra] = [minBound .. maxBound]

data Analysis = Analysis
{ concernedEra :: AnyCardanoEra
, genesisFilepath :: FilePath
-- ^ The path to the genesis file, as found in the node configuration file, if any
, actualHash :: Text.Text
-- ^ The actual hash of the genesis file, computed from the file's content
, expectedHash :: Maybe Text.Text
-- ^ The hash of the genesis file, as found in the node configuration file, if any.
}

runAnalysis
:: NodeConfigFile 'In
-- ^ The node configuration file. It's not read by this function, but used for producing error messages.
-> Analysis
-> ExceptT DebugCmdError IO ()
-- ^ The fixed node configuration file
runAnalysis configFile (Analysis{concernedEra, genesisFilepath = _, actualHash, expectedHash}) = do
case expectedHash of
Nothing ->
-- Genesis hash is not declared in the node configuration file: we have nothing to check against
pure ()
Just expectedHash'
| expectedHash' == actualHash ->
-- Genesis hash is correct: no error
pure ()
Just expectedHash' ->
-- Genesis hash is incorrect: report an error
throwError $
DebugNodeConfigWrongGenesisHashCmdError (unFile configFile) concernedEra actualHash expectedHash'

-- | Build an 'Analysis' value from the given node configuration file, for the given era.
-- Reads the genesis file path and hash from the node configuration file, if they exist.
mkAnalysis
:: AnyCardanoEra
-- ^ The era whose data must be analyzed. This parameter isn't about the eras we support.
-> NodeConfigFile 'In
-- ^ The node configuration file path. It's not read by this function, but used for producing error messages.
-> NodeConfig
-- ^ The parsed node configuration file
-> ExceptT DebugCmdError IO (Maybe Analysis)
mkAnalysis cEra@(AnyCardanoEra era) configFile nodeConfig = do
case (getGenesisFilePath era nodeConfig, getGenesisHash era nodeConfig) of
(Nothing, Nothing) ->
-- Neither the genesis file nor the genesis hash are declared in the node configuration file
-- Note that this is expected for eras whose hash doesn't appear in the node configuration file,
-- for example Mary.
pure Nothing
(Nothing, Just _) -> do
-- The path to the genesis file is not given in the node configuration file,
-- but a genesis hash is provided. Since we can't verify the latter,
-- we write something to stdout, because this is weird.
liftIO $
putStrLn $
"Genesis file for " <> Text.unpack (eraToStringKey era) <> " is not declared in " <> configFilePath
liftIO $ putStrLn "So the hash's correctness cannot be checked."
pure Nothing
(Just filepath, expectedHash) -> do
-- We make the genesis filepath relative to the node configuration file, like the node does:
-- https://github.com/IntersectMBO/cardano-node/blob/9671e7b6a1b91f5a530722937949b86deafaad43/cardano-node/src/Cardano/Node/Configuration/POM.hs#L668
-- Note that, if the genesis filepath is absolute, the node configuration file's directory is ignored (by property of </>)
let genesisFilePath = takeDirectory configFilePath </> filepath
actualHash <-
case era of
ByronEra -> do
(_, GenesisHash byronHash) <-
firstExceptT (DebugNodeConfigGenesisDataCmdError genesisFilePath) $ readGenesisData genesisFilePath
return $ Text.pack $ show byronHash
_ -> Crypto.hashToTextAsHex <$> Read.readShelleyOnwardsGenesisAndHash genesisFilePath
case expectedHash of
Nothing ->
pure $ Just $ Analysis cEra genesisFilePath actualHash Nothing
Just expectedHash' ->
pure $ Just $ Analysis cEra genesisFilePath actualHash (Just expectedHash')
where
configFilePath = unFile configFile

-- | Get the hash of the genesis file, of the given era.
getGenesisHash :: CardanoEra a -> NodeConfig -> Maybe Text.Text
getGenesisHash era config =
case era of
MaryEra -> Nothing
AllegraEra -> Nothing
BabbageEra -> Nothing
ByronEra -> Just $ unGenesisHashByron $ ncByronGenesisHash config
ShelleyEra -> Just $ Crypto.hashToTextAsHex $ unGenesisHashShelley $ ncShelleyGenesisHash config
AlonzoEra -> Just $ Crypto.hashToTextAsHex $ unGenesisHashAlonzo $ ncAlonzoGenesisHash config
ConwayEra -> Crypto.hashToTextAsHex . unGenesisHashConway <$> ncConwayGenesisHash config

-- | Get the path to the genesis file, of the given era.
getGenesisFilePath :: CardanoEra a -> NodeConfig -> Maybe FilePath
getGenesisFilePath era config =
case era of
MaryEra -> Nothing
AllegraEra -> Nothing
BabbageEra -> Nothing
ByronEra -> Just $ unFile $ ncByronGenesisFile config
ShelleyEra -> Just $ unFile $ ncShelleyGenesisFile config
AlonzoEra -> Just $ unFile $ ncAlonzoGenesisFile config
ConwayEra -> unFile <$> ncConwayGenesisFile config

-- | Part of the JSON keys associated with the given era. For example,
-- @eraToStringKey ByronEra@ returns @"Byron"@.
eraToStringKey :: CardanoEra a -> Text.Text
eraToStringKey = docToText . pretty
33 changes: 33 additions & 0 deletions cardano-cli/src/Cardano/CLI/Types/Errors/DebugCmdError.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,46 @@ where

import Cardano.Api

import Cardano.Chain.Genesis
import Cardano.CLI.Types.Errors.TxCmdError

import Data.Text (Text)
import qualified Data.Text.Lazy.Builder as Text
import Formatting.Buildable (build)

data DebugCmdError
= DebugCmdFailed
| -- | @DebugNodeConfigGenesisDataCmdError filepath error@ represents an error when
-- reading the node configuration at @filepath@
DebugNodeConfigGenesisDataCmdError !FilePath !GenesisDataError
| -- | @DebugNodeConfigWrongGenesisHashCmdError filepath era actualHash expectedHash@ represents a user error
-- that the genesis hash for @era@ in @filepath@ is @actualHash@, whereas it should be @expectedHash@
DebugNodeConfigWrongGenesisHashCmdError
!FilePath
-- ^ The file path of the node configuration file
AnyCardanoEra
-- ^ The era whose data is incorrect
!Text
-- ^ The actual hash (the hash found by hashing the genesis file)
!Text
-- ^ The expected hash (the hash mentioned in the configuration file)
| DebugTxCmdError !TxCmdError

instance Error DebugCmdError where
prettyError = \case
DebugCmdFailed -> "Debug command failed"
DebugNodeConfigGenesisDataCmdError fp err ->
"Error reading node configuration at: "
<> pretty fp
<> ": "
<> pretty (Text.toLazyText $ build err)
DebugNodeConfigWrongGenesisHashCmdError fp era actualHash expectedHash ->
"Wrong genesis hash for "
<> pretty era
<> " in "
<> pretty fp
<> ": "
<> pretty actualHash
<> ", expected: "
<> pretty expectedHash
DebugTxCmdError err -> renderTxCmdError err

0 comments on commit fc8aee2

Please sign in to comment.