diff --git a/.github/workflows/haskell.yml b/.github/workflows/haskell.yml index 89ef0d1c449..3dda0e678a1 100644 --- a/.github/workflows/haskell.yml +++ b/.github/workflows/haskell.yml @@ -38,7 +38,7 @@ jobs: # If you edit these versions, make sure the version in the lonely macos-latest job below is updated accordingly # TODO add 9.8 again to the versions list when GHC-9.8 gets released with stm > 2.5.2, # see https://github.com/haskell/stm/issues/76 - ghc: ["9.6", "9.12"] + ghc: ["9.6", "9.10"] cabal: ["3.12"] sys: - { os: windows-latest, shell: 'C:/msys64/usr/bin/bash.exe -e {0}' } @@ -97,6 +97,18 @@ jobs: with: use-sodium-vrf: true # default is true + - name: "[Linux] Install grpc dependencies" + if: runner.os == 'Linux' + run: sudo apt install libsnappy-dev protobuf-compiler + + - name: "[Windows] Install grpc dependencies" + if: runner.os == 'Windows' + run: /usr/bin/pacman --noconfirm -S mingw-w64-x86_64-snappy mingw-w64-x86_64-protobuf + + - name: "[macOS] Install grpc dependencies" + if: runner.os == 'macOS' + run: brew install snappy protobuf + - uses: actions/checkout@v4 - name: Cabal update @@ -230,9 +242,9 @@ jobs: # and will silently fail if msys2 is not in path. See the "Run tests" step. # # - name: Setup tmate session - # if: ${{ failure() }} - # uses: mxschmitt/action-tmate@v3 - # with: + # if: ${{ failure() }} + # uses: mxschmitt/action-tmate@v3 + # with: # limit-access-to-actor: true build-complete: diff --git a/bench/plutus-scripts-bench/plutus-scripts-bench.cabal b/bench/plutus-scripts-bench/plutus-scripts-bench.cabal index 169a3126ed8..bc879350134 100644 --- a/bench/plutus-scripts-bench/plutus-scripts-bench.cabal +++ b/bench/plutus-scripts-bench/plutus-scripts-bench.cabal @@ -82,7 +82,7 @@ library -- IOG dependencies -------------------------- build-depends: - , cardano-api ^>=10.19 + , cardano-api ^>=10.20 , plutus-ledger-api ^>=1.53 , plutus-tx ^>=1.53 , plutus-tx-plugin ^>=1.53 diff --git a/bench/tx-generator/src/Cardano/TxGenerator/Setup/Plutus.hs b/bench/tx-generator/src/Cardano/TxGenerator/Setup/Plutus.hs index 22e40dd9432..a9650cb48b7 100644 --- a/bench/tx-generator/src/Cardano/TxGenerator/Setup/Plutus.hs +++ b/bench/tx-generator/src/Cardano/TxGenerator/Setup/Plutus.hs @@ -36,6 +36,7 @@ import qualified PlutusTx.AssocMap as AssocMap (empty) import Cardano.TxGenerator.ProtocolParameters (ProtocolParameters(..)) import Cardano.TxGenerator.Types (TxGenError (..), TxGenPlutusResolvedTo (..)) import Control.Exception (SomeException (..), try, displayException) +import RIO (runRIO) import System.FilePath ((<.>), ()) #ifdef WITH_LIBRARY import Cardano.Benchmarking.PlutusScripts (findPlutusScript) diff --git a/bench/tx-generator/tx-generator.cabal b/bench/tx-generator/tx-generator.cabal index afaa52fd929..718a2217ae0 100644 --- a/bench/tx-generator/tx-generator.cabal +++ b/bench/tx-generator/tx-generator.cabal @@ -111,9 +111,9 @@ library , attoparsec-aeson , base16-bytestring , bytestring - , cardano-api ^>= 10.19 + , cardano-api ^>= 10.21 , cardano-binary - , cardano-cli ^>= 10.13 + , cardano-cli ^>= 10.14 , cardano-crypto-class , cardano-crypto-wrapper , cardano-data @@ -140,6 +140,7 @@ library , network , network-mux , optparse-applicative + , rio , ouroboros-consensus >= 0.6 , ouroboros-consensus-cardano >= 0.5 , ouroboros-consensus-diffusion >= 0.7.0 diff --git a/cabal.project b/cabal.project index 3ef2447a9db..2e3f2153d89 100644 --- a/cabal.project +++ b/cabal.project @@ -61,6 +61,13 @@ package plutus-scripts-bench allow-newer: , katip:Win32 + +if impl (ghc >= 9.10) + allow-newer: + -- TODO: remove - this is for protolens + , *:base + , *:ghc-prim + if impl (ghc >= 9.12) allow-newer: -- https://github.com/kapralVV/Unique/issues/11 @@ -72,3 +79,22 @@ if impl (ghc >= 9.12) -- IMPORTANT -- Do NOT add more source-repository-package stanzas here unless they are strictly -- temporary! Please read the section in CONTRIBUTING about updating dependencies. + + +source-repository-package + type: git + location: https://github.com/intersectmbo/cardano-api + -- master @ Fri Jul 25 18:47:11 2025 + tag: 1765e37a04a8ed3dd7455e0434c1b2b09fa609a5 + --sha256: sha256-XkoP1eBoyQGjmq1n3Oma0OJgxRos5oW4QCQ8HSfAexo= + subdir: cardano-api + cardano-api-gen + cardano-rpc + +source-repository-package + type: git + location: https://github.com/intersectmbo/cardano-cli + -- master @ Fri Jul 18 14:45:44 2025 + tag: 5dc410f1ec0b234a572e4470c3801fbac783bbeb + --sha256: sha256-f3RviGvNoDu5WX79BgAzPo9bL6PsNBqm+Qt1UlJGe5E= + subdir: cardano-cli diff --git a/cardano-node-chairman/cardano-node-chairman.cabal b/cardano-node-chairman/cardano-node-chairman.cabal index ed6faec2369..d8a96c5343b 100644 --- a/cardano-node-chairman/cardano-node-chairman.cabal +++ b/cardano-node-chairman/cardano-node-chairman.cabal @@ -88,5 +88,5 @@ test-suite chairman-tests ghc-options: -threaded -rtsopts "-with-rtsopts=-N -T" build-tool-depends: cardano-node:cardano-node - , cardano-cli:cardano-cli ^>= 10.13 + , cardano-cli:cardano-cli ^>= 10.14 , cardano-node-chairman:cardano-node-chairman diff --git a/cardano-node/cardano-node.cabal b/cardano-node/cardano-node.cabal index f6836d20bd6..7603ff78d17 100644 --- a/cardano-node/cardano-node.cabal +++ b/cardano-node/cardano-node.cabal @@ -113,6 +113,7 @@ library Cardano.Node.Tracing.Tracers.NodeVersion Cardano.Node.Tracing.Tracers.P2P Cardano.Node.Tracing.Tracers.Resources + Cardano.Node.Tracing.Tracers.Rpc Cardano.Node.Tracing.Tracers.Shutdown Cardano.Node.Tracing.Tracers.Startup Cardano.Node.Types @@ -138,7 +139,7 @@ library , async , base16-bytestring , bytestring - , cardano-api ^>= 10.19 + , cardano-api ^>= 10.21 , cardano-crypto-class ^>=2.2.3.2 , cardano-crypto-wrapper , cardano-git-rev ^>=0.2.2 @@ -155,6 +156,7 @@ library , cardano-prelude , cardano-protocol-tpraos >= 1.4 , cardano-slotting >= 0.2 + , cardano-rpc ^>= 10.0 , cborg ^>= 0.2.4 , containers , contra-tracer @@ -162,6 +164,7 @@ library , deepseq , directory , dns + , ekg , ekg-wai , ekg-core , filepath @@ -217,6 +220,7 @@ library , typed-protocols:{typed-protocols, stateful} >= 1.0 , yaml + executable cardano-node import: project-config hs-source-dirs: app @@ -252,6 +256,7 @@ test-suite cardano-node-test , cardano-crypto-class , cardano-crypto-wrapper , cardano-api + , cardano-rpc , cardano-protocol-tpraos , cardano-node , cardano-slotting diff --git a/cardano-node/src/Cardano/Node/Configuration/POM.hs b/cardano-node/src/Cardano/Node/Configuration/POM.hs index 4255c77d775..4be8814c53d 100644 --- a/cardano-node/src/Cardano/Node/Configuration/POM.hs +++ b/cardano-node/src/Cardano/Node/Configuration/POM.hs @@ -10,6 +10,8 @@ {-# OPTIONS_GHC -Wno-noncanonical-monoid-instances #-} +{- HLINT ignore "Functor law" -} + module Cardano.Node.Configuration.POM ( NodeConfiguration (..) , ResponderCoreAffinityPolicy (..) @@ -34,6 +36,8 @@ import Cardano.Node.Configuration.Socket (SocketConfig (..)) import Cardano.Node.Handlers.Shutdown import Cardano.Node.Protocol.Types (Protocol (..)) import Cardano.Node.Types +import Cardano.Rpc.Server.Config (PartialRpcConfig, RpcConfig, RpcConfigF (..), + makeRpcConfig) import Cardano.Tracing.Config import Cardano.Tracing.OrphanInstances.Network () import Ouroboros.Consensus.Ledger.SupportsMempool @@ -174,6 +178,9 @@ data NodeConfiguration , ncGenesisConfig :: GenesisConfig , ncResponderCoreAffinityPolicy :: ResponderCoreAffinityPolicy + + -- gRPC + , ncRpcConfig :: RpcConfig } deriving (Eq, Show) -- | We expose the `Ouroboros.Network.Mux.ForkPolicy` as a `NodeConfiguration` field. @@ -255,6 +262,7 @@ data PartialNodeConfiguration , pncSyncTargetOfKnownBigLedgerPeers :: !(Last Int) , pncSyncTargetOfEstablishedBigLedgerPeers :: !(Last Int) , pncSyncTargetOfActiveBigLedgerPeers :: !(Last Int) + -- Minimum number of active big ledger peers we must be connected to -- in Genesis mode , pncMinBigLedgerPeersForTrustedState :: !(Last NumberOfBigLedgerPeers) @@ -269,6 +277,9 @@ data PartialNodeConfiguration , pncGenesisConfigFlags :: !(Last GenesisConfigFlags) , pncResponderCoreAffinityPolicy :: !(Last ResponderCoreAffinityPolicy) + + -- gRPC + , pncRpcConfig :: !PartialRpcConfig } deriving (Eq, Generic, Show) instance AdjustFilePaths PartialNodeConfiguration where @@ -381,6 +392,12 @@ instance FromJSON PartialNodeConfiguration where <$> v .:? "ResponderCoreAffinityPolicy" <*> v .:? "ForkPolicy" -- deprecated + pncRpcConfig <- + RpcConfig + <$> (Last <$> v .:? "EnableRpc") + <*> (Last <$> v .:? "RpcSocketPath") + <*> pure mempty + pure PartialNodeConfiguration { pncProtocolConfig , pncSocketConfig = Last . Just $ SocketConfig mempty mempty mempty pncSocketPath @@ -425,6 +442,7 @@ instance FromJSON PartialNodeConfiguration where , pncPeerSharing , pncGenesisConfigFlags , pncResponderCoreAffinityPolicy + , pncRpcConfig } where parseMempoolCapacityBytesOverride v = parseNoOverride <|> parseOverride @@ -687,6 +705,7 @@ defaultPartialNodeConfiguration = , pncGenesisConfigFlags = Last (Just defaultGenesisConfigFlags) -- https://ouroboros-consensus.cardano.intersectmbo.org/haddocks/ouroboros-consensus-diffusion/Ouroboros-Consensus-Node-Genesis.html#v:defaultGenesisConfigFlags , pncResponderCoreAffinityPolicy = Last $ Just NoResponderCoreAffinity + , pncRpcConfig = mempty } lastOption :: Parser a -> Parser (Last a) @@ -827,6 +846,9 @@ makeNodeConfiguration pnc = do experimentalProtocols <- lastToEither "Missing ExperimentalProtocolsEnabled" $ pncExperimentalProtocolsEnabled pnc + + ncRpcConfig <- makeRpcConfig $ (pncRpcConfig pnc){nodeSocketPath=ncSocketPath socketConfig} + return $ NodeConfiguration { ncConfigFile = configFile , ncTopologyFile = topologyFile @@ -872,6 +894,7 @@ makeNodeConfiguration pnc = do , ncConsensusMode , ncGenesisConfig , ncResponderCoreAffinityPolicy + , ncRpcConfig } ncProtocol :: NodeConfiguration -> Protocol diff --git a/cardano-node/src/Cardano/Node/Parsers.hs b/cardano-node/src/Cardano/Node/Parsers.hs index 077e9675b62..3f4d9da4b99 100644 --- a/cardano-node/src/Cardano/Node/Parsers.hs +++ b/cardano-node/src/Cardano/Node/Parsers.hs @@ -15,27 +15,26 @@ module Cardano.Node.Parsers import Cardano.Logging.Types import qualified Cardano.Logging.Types as Net -import Cardano.Node.Configuration.NodeAddress ( - NodeHostIPv4Address (NodeHostIPv4Address), File (..), +import Cardano.Node.Configuration.NodeAddress (File (..), + NodeHostIPv4Address (NodeHostIPv4Address), NodeHostIPv6Address (NodeHostIPv6Address), PortNumber, SocketPath) import Cardano.Node.Configuration.POM (PartialNodeConfiguration (..), lastOption) import Cardano.Node.Configuration.Socket import Cardano.Node.Handlers.Shutdown import Cardano.Node.Types import Cardano.Prelude (ConvertText (..)) +import Cardano.Rpc.Server.Config (PartialRpcConfig, RpcConfigF (..)) import Ouroboros.Consensus.Ledger.SupportsMempool import Ouroboros.Consensus.Node -import Data.Foldable import Data.Char (isDigit) +import Data.Foldable import Data.Maybe (fromMaybe) import Data.Monoid (Last (..)) import Data.Text (Text) import qualified Data.Text as Text import Data.Word (Word16, Word32) import Options.Applicative hiding (str, switch) --- Don't use switch. It will not allow to set an option in a configuration --- file. See `parseStartAsNonProducingNode` and `parseValidateDB`. import qualified Options.Applicative as Opt import qualified Options.Applicative.Help as OptI import qualified Prettyprinter.Internal as PP @@ -57,7 +56,7 @@ nodeRunParser = do topFp <- lastOption parseTopologyFile dbFp <- lastOption parseNodeDatabasePaths validate <- lastOption parseValidateDB - socketFp <- lastOption $ parseSocketPath "Path to a cardano-node socket" + socketFp <- lastOption $ parseSocketPath "socket-path" "Path to a cardano-node socket" traceForwardSocket <- lastOption parseTracerSocketMode nodeConfigFp <- lastOption parseConfigFile @@ -84,6 +83,9 @@ nodeRunParser = do -- Hidden options (to be removed eventually) maybeMempoolCapacityOverride <- lastOption parseMempoolCapacityOverride + -- gRPC + pncRpcConfig <- parseRpcConfig + pure $ PartialNodeConfiguration { pncSocketConfig = Last . Just $ SocketConfig @@ -141,12 +143,15 @@ nodeRunParser = do , pncPeerSharing = mempty , pncGenesisConfigFlags = mempty , pncResponderCoreAffinityPolicy = mempty + , pncRpcConfig } -parseSocketPath :: Text -> Parser SocketPath -parseSocketPath helpMessage = +parseSocketPath :: Text -- ^ option name + -> Text -- ^ help text + -> Parser SocketPath +parseSocketPath optionName helpMessage = fmap File $ strOption $ mconcat - [ long "socket-path" + [ long (toS optionName) , help (toS helpMessage) , completer (bashCompleter "file") , metavar "FILEPATH" @@ -420,6 +425,23 @@ parseStartAsNonProducingNode = ] ] +parseRpcConfig :: Parser PartialRpcConfig +parseRpcConfig = do + isEnabled <- lastOption parseRpcToggle + socketPath <- lastOption parseRpcSocketPath + pure $ RpcConfig isEnabled socketPath mempty + where + parseRpcToggle :: Parser Bool + parseRpcToggle = + Opt.switch ( + long "grpc-enable" + <> help "[EXPERIMENTAL] Enable node gRPC endpoint." + ) + parseRpcSocketPath :: Parser SocketPath + parseRpcSocketPath = + parseSocketPath + "gprc-socket-path" + "[EXPERIMENTAL] gRPC socket path. Defaults to rpc.sock in the same directory as node socket." -- | Produce just the brief help header for a given CLI option parser, -- without the options. diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index 2c87c34956f..073345d6f6d 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -22,7 +22,7 @@ module Cardano.Node.Run , checkVRFFilePermissions ) where -import Cardano.Api (File (..), FileDirection (..)) +import Cardano.Api (File (..), FileDirection (..), NetworkMagic, fromNetworkMagic) import Cardano.Api.Error (displayError) import qualified Cardano.Api as Api import System.Random (randomIO) @@ -53,6 +53,8 @@ import Cardano.Node.Protocol.Shelley (PraosLeaderCredentialsError (..) ShelleyProtocolInstantiationError (PraosLeaderCredentialsError)) import Cardano.Node.Protocol.Types import Cardano.Node.Queries +import Cardano.Rpc.Server +import Cardano.Rpc.Server.Config import Cardano.Node.Startup import Cardano.Node.TraceConstraints (TraceConstraints) import Cardano.Node.Tracing.API @@ -122,6 +124,7 @@ import Ouroboros.Network.Protocol.ChainSync.Codec import Control.Applicative (empty) import Control.Concurrent (killThread, mkWeakThreadId, myThreadId, getNumCapabilities) +import Control.Concurrent.Async import Control.Concurrent.Class.MonadSTM.Strict import Control.Exception (try, Exception, IOException) import qualified Control.Exception as Exception @@ -154,6 +157,7 @@ import Network.HostName (getHostName) import Network.Socket (Socket) import System.Directory (canonicalizePath, createDirectoryIfMissing, makeAbsolute) import System.Environment (lookupEnv) +import System.FilePath (takeDirectory, ()) import System.IO (hPutStrLn) #ifdef UNIX import GHC.Weak (deRefWeak) @@ -166,6 +170,7 @@ import System.Win32.File import Paths_cardano_node (version) import Paths_cardano_node (version) +import GHC.Stack {- HLINT ignore "Fuse concatMap/map" -} {- HLINT ignore "Redundant <$>" -} @@ -175,37 +180,45 @@ runNode :: PartialNodeConfiguration -> IO () runNode cmdPc = do - installSigTermHandler + installSigTermHandler - Crypto.cryptoInit + Crypto.cryptoInit - configYamlPc <- parseNodeConfigurationFP . getLast $ pncConfigFile cmdPc + nc@NodeConfiguration + { ncProtocolConfig + , ncProtocolFiles=ncProtocolFiles@ProtocolFilepaths{shelleyVRFFile=mShelleyVrfFile} + } <- buildNodeConfiguration cmdPc - nc <- case makeNodeConfiguration $ defaultPartialNodeConfiguration <> configYamlPc <> cmdPc of - Left err -> error $ "Error in creating the NodeConfiguration: " <> err - Right nc' -> return nc' + let earlyTracer = stdoutTracer + traceWith earlyTracer $ "Node configuration: " <> show nc - putStrLn $ "Node configuration: " <> show nc + forM_ mShelleyVrfFile $ + runThrowExceptT . checkVRFFilePermissions earlyTracer . File - case ncProtocolFiles nc of - ProtocolFilepaths{shelleyVRFFile=Just vrfFp} -> - runThrowExceptT $ - checkVRFFilePermissions stdoutTracer (File vrfFp) - _ -> pure () + consensusProtocol <- + runThrowExceptT $ + mkConsensusProtocol + ncProtocolConfig + -- TODO: Convert ncProtocolFiles to Maybe as relay nodes + -- don't need these. + (Just ncProtocolFiles) - consensusProtocol <- - runThrowExceptT $ - mkConsensusProtocol - (ncProtocolConfig nc) - -- TODO: Convert ncProtocolFiles to Maybe as relay nodes - -- don't need these. - (Just $ ncProtocolFiles nc) - - handleNodeWithTracers cmdPc nc consensusProtocol + handleNodeWithTracers cmdPc nc consensusProtocol 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) + -- | Workaround to ensure that the main thread throws an async exception on -- receiving a SIGTERM signal. installSigTermHandler :: IO () @@ -505,63 +518,65 @@ handleSimpleNode blockType runP tracers nc onKernel = do #endif nForkPolicy <- getForkPolicy $ ncResponderCoreAffinityPolicy nc cForkPolicy <- getForkPolicy $ ncResponderCoreAffinityPolicy nc - void $ - let diffusionNodeArguments :: Cardano.Diffusion.CardanoNodeArguments IO - diffusionNodeArguments = Cardano.Diffusion.CardanoNodeArguments { - Cardano.Diffusion.consensusMode = ncConsensusMode nc, - Cardano.Diffusion.genesisPeerTargets = - PeerSelectionTargets { - targetNumberOfRootPeers = ncSyncTargetOfRootPeers nc, - targetNumberOfKnownPeers = ncSyncTargetOfKnownPeers nc, - targetNumberOfEstablishedPeers = ncSyncTargetOfEstablishedPeers nc, - targetNumberOfActivePeers = ncSyncTargetOfActivePeers nc, - targetNumberOfKnownBigLedgerPeers = ncSyncTargetOfKnownBigLedgerPeers nc, - targetNumberOfEstablishedBigLedgerPeers = ncSyncTargetOfEstablishedBigLedgerPeers nc, - targetNumberOfActiveBigLedgerPeers = ncSyncTargetOfActiveBigLedgerPeers nc - }, - Cardano.Diffusion.minNumOfBigLedgerPeers = ncMinBigLedgerPeersForTrustedState nc, - Cardano.Diffusion.tracerChurnMode = churnModeTracer tracers - } + let diffusionNodeArguments :: Cardano.Diffusion.CardanoNodeArguments IO + diffusionNodeArguments = Cardano.Diffusion.CardanoNodeArguments { + Cardano.Diffusion.consensusMode = ncConsensusMode nc, + Cardano.Diffusion.genesisPeerTargets = + PeerSelectionTargets { + targetNumberOfRootPeers = ncSyncTargetOfRootPeers nc, + targetNumberOfKnownPeers = ncSyncTargetOfKnownPeers nc, + targetNumberOfEstablishedPeers = ncSyncTargetOfEstablishedPeers nc, + targetNumberOfActivePeers = ncSyncTargetOfActivePeers nc, + targetNumberOfKnownBigLedgerPeers = ncSyncTargetOfKnownBigLedgerPeers nc, + targetNumberOfEstablishedBigLedgerPeers = ncSyncTargetOfEstablishedBigLedgerPeers nc, + targetNumberOfActiveBigLedgerPeers = ncSyncTargetOfActiveBigLedgerPeers nc + }, + Cardano.Diffusion.minNumOfBigLedgerPeers = ncMinBigLedgerPeersForTrustedState nc, + Cardano.Diffusion.tracerChurnMode = churnModeTracer tracers + } - diffusionConfiguration :: Cardano.Diffusion.CardanoConfiguration IO - diffusionConfiguration = - mkDiffusionConfiguration - publicIPv4SocketOrAddr - publicIPv6SocketOrAddr - localSocketOrPath - publicPeerSelectionVar - nForkPolicy cForkPolicy - (readTVar localRootsVar) - (readTVar publicRootsVar) - (readTVar useLedgerVar) - (readTVar ledgerPeerSnapshotVar) - nc - in - Node.run - nodeArgs { - rnNodeKernelHook = \registry nodeKernel -> do - -- reinstall `SIGHUP` handler - installSigHUPHandler (startupTracer tracers) (Consensus.kesAgentTracer $ consensusTracers tracers) blockType nc nodeKernel - localRootsVar publicRootsVar useLedgerVar useBootstrapVar - ledgerPeerSnapshotPathVar ledgerPeerSnapshotVar - rnNodeKernelHook nodeArgs registry nodeKernel - } - StdRunNodeArgs - { srnBfcMaxConcurrencyBulkSync = unMaxConcurrencyBulkSync <$> ncMaxConcurrencyBulkSync nc - , srnBfcMaxConcurrencyDeadline = unMaxConcurrencyDeadline <$> ncMaxConcurrencyDeadline nc - , srnChainDbValidateOverride = ncValidateDB nc - , srnDatabasePath = dbPath - , srnDiffusionConfiguration = diffusionConfiguration - , srnDiffusionArguments = diffusionNodeArguments - , srnDiffusionTracers = diffusionTracers tracers - , srnEnableInDevelopmentVersions = ncExperimentalProtocolsEnabled nc - , srnTraceChainDB = chainDBTracer tracers - , srnMaybeMempoolCapacityOverride = ncMaybeMempoolCapacityOverride nc - , srnChainSyncIdleTimeout = customizeChainSyncTimeout - , srnSnapshotPolicyArgs = snapshotPolicyArgs - , srnQueryBatchSize = queryBatchSize - , srnLdbFlavorArgs = selectorToArgs ldbBackend + diffusionConfiguration :: Cardano.Diffusion.CardanoConfiguration IO + diffusionConfiguration = + mkDiffusionConfiguration + publicIPv4SocketOrAddr + publicIPv6SocketOrAddr + localSocketOrPath + publicPeerSelectionVar + nForkPolicy cForkPolicy + (readTVar localRootsVar) + (readTVar publicRootsVar) + (readTVar useLedgerVar) + (readTVar ledgerPeerSnapshotVar) + nc + + ProtocolInfo{pInfoConfig} = fst $ Api.protocolInfo @IO runP + networkMagic :: Api.NetworkMagic = getNetworkMagic $ Consensus.configBlock pInfoConfig + withAsync (runRpcServer (rpcTracer tracers) (pure (ncRpcConfig nc, networkMagic))) $ \_ -> + Node.run + nodeArgs { + rnNodeKernelHook = \registry nodeKernel -> do + -- reinstall `SIGHUP` handler + installSigHUPHandler (startupTracer tracers) (Consensus.kesAgentTracer $ consensusTracers tracers) blockType nc nodeKernel + localRootsVar publicRootsVar useLedgerVar useBootstrapVar + ledgerPeerSnapshotPathVar ledgerPeerSnapshotVar + rnNodeKernelHook nodeArgs registry nodeKernel } + StdRunNodeArgs + { srnBfcMaxConcurrencyBulkSync = unMaxConcurrencyBulkSync <$> ncMaxConcurrencyBulkSync nc + , srnBfcMaxConcurrencyDeadline = unMaxConcurrencyDeadline <$> ncMaxConcurrencyDeadline nc + , srnChainDbValidateOverride = ncValidateDB nc + , srnDatabasePath = dbPath + , srnDiffusionConfiguration = diffusionConfiguration + , srnDiffusionArguments = diffusionNodeArguments + , srnDiffusionTracers = diffusionTracers tracers + , srnEnableInDevelopmentVersions = ncExperimentalProtocolsEnabled nc + , srnTraceChainDB = chainDBTracer tracers + , srnMaybeMempoolCapacityOverride = ncMaybeMempoolCapacityOverride nc + , srnChainSyncIdleTimeout = customizeChainSyncTimeout + , srnSnapshotPolicyArgs = snapshotPolicyArgs + , srnQueryBatchSize = queryBatchSize + , srnLdbFlavorArgs = selectorToArgs ldbBackend + } where customizeChainSyncTimeout :: ChainSyncIdleTimeout customizeChainSyncTimeout = case ncChainSyncIdleTimeout nc of @@ -631,6 +646,8 @@ handleSimpleNode blockType runP tracers nc onKernel = do -- SIGHUP Handlers -------------------------------------------------------------------------------- +-- TODO add SIGHUP handler for RPC configuration reloading + -- | The P2P SIGHUP handler can update block forging & reconfigure network topology. -- installSigHUPHandler :: Tracer IO (StartupTrace blk) diff --git a/cardano-node/src/Cardano/Node/Tracing.hs b/cardano-node/src/Cardano/Node/Tracing.hs index 2a751a58562..127a1e75ce0 100644 --- a/cardano-node/src/Cardano/Node/Tracing.hs +++ b/cardano-node/src/Cardano/Node/Tracing.hs @@ -10,6 +10,7 @@ module Cardano.Node.Tracing ) where import Cardano.Logging.Resources +import qualified Cardano.Network.Diffusion as Cardano.Diffusion import Cardano.Node.Handlers.Shutdown (ShutdownTrace) import Cardano.Node.Startup (NodeInfo, NodeStartupInfo, StartupTrace (..)) import Cardano.Node.Tracing.StateRep (NodeState) @@ -17,12 +18,12 @@ import Cardano.Node.Tracing.Tracers.ConsensusStartupException (ConsensusStartupException (..)) import Cardano.Node.Tracing.Tracers.LedgerMetrics (LedgerMetrics) import Cardano.Node.Tracing.Tracers.NodeVersion (NodeVersionTrace) +import Cardano.Rpc.Server (TraceRpc) import qualified Ouroboros.Consensus.Network.NodeToClient as NodeToClient import qualified Ouroboros.Consensus.Network.NodeToNode as NodeToNode import qualified Ouroboros.Consensus.Node.Tracers as Consensus import qualified Ouroboros.Consensus.Storage.ChainDB as ChainDB import Ouroboros.Network.ConnectionId -import qualified Cardano.Network.Diffusion as Cardano.Diffusion import Prelude (IO) @@ -50,4 +51,5 @@ data Tracers peer localPeer blk m = Tracers , nodeStateTracer :: !(Tracer IO NodeState) , resourcesTracer :: !(Tracer IO ResourceStats) , ledgerMetricsTracer :: !(Tracer IO LedgerMetrics) + , rpcTracer :: !(Tracer IO TraceRpc) } diff --git a/cardano-node/src/Cardano/Node/Tracing/API.hs b/cardano-node/src/Cardano/Node/Tracing/API.hs index e33d1c88915..5a5b2fddbf8 100644 --- a/cardano-node/src/Cardano/Node/Tracing/API.hs +++ b/cardano-node/src/Cardano/Node/Tracing/API.hs @@ -49,6 +49,7 @@ import Data.Time.Clock (getCurrentTime) import Network.Mux.Trace (TraceLabelPeer (..)) import Network.Socket (HostName) import System.Metrics as EKG +import System.Remote.Monitoring import Trace.Forward.Forwarding (InitForwardingConfig (..), initForwardingDelayed) import Trace.Forward.Utils.TraceObject (writeToSink) diff --git a/cardano-node/src/Cardano/Node/Tracing/Documentation.hs b/cardano-node/src/Cardano/Node/Tracing/Documentation.hs index 1658bed634d..d3e341754fa 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Documentation.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Documentation.hs @@ -24,6 +24,9 @@ import Cardano.Git.Rev (gitRev) import Cardano.Logging as Logging import Cardano.Logging.Resources import Cardano.Logging.Resources.Types () +import qualified Cardano.Network.PeerSelection.ExtraRootPeers as Cardano.PublicRootPeers +import qualified Cardano.Network.PeerSelection.Governor.PeerSelectionState as Cardano +import qualified Cardano.Network.PeerSelection.Governor.Types as Cardano import Cardano.Network.PeerSelection.PeerTrustable (PeerTrustable (..)) import Cardano.Node.Handlers.Shutdown (ShutdownTrace) import Cardano.Node.Startup @@ -45,11 +48,10 @@ import Cardano.Node.Tracing.Tracers.NodeToClient () import Cardano.Node.Tracing.Tracers.NodeToNode () import Cardano.Node.Tracing.Tracers.NodeVersion (NodeVersionTrace) import Cardano.Node.Tracing.Tracers.P2P () +import Cardano.Node.Tracing.Tracers.Rpc () import Cardano.Node.Tracing.Tracers.Shutdown () import Cardano.Node.Tracing.Tracers.Startup () -import qualified Cardano.Network.PeerSelection.Governor.PeerSelectionState as Cardano -import qualified Cardano.Network.PeerSelection.Governor.Types as Cardano -import qualified Cardano.Network.PeerSelection.ExtraRootPeers as Cardano.PublicRootPeers +import Cardano.Rpc.Server (TraceRpc) import Cardano.Tracing.OrphanInstances.Network () import Ouroboros.Consensus.Block.SupportsSanityCheck (SanityCheckIssue) import Ouroboros.Consensus.BlockchainTime.WallClock.Types (RelativeTime) @@ -699,6 +701,9 @@ docTracersFirstPhase condConfigFileName = do internalTrDoc <- documentTracer (internalTr :: Logging.Trace IO TraceDispatcherMessage) + rpcTr <- mkCardanoTracer trBase trForward mbTrEKG ["RPC"] + configureTracers configReflection trConfig [rpcTr] + rpcTrDoc <- documentTracer (rpcTr :: Logging.Trace IO TraceRpc) let bl = nodeInfoDpDoc <> nodeStartupInfoDpDoc @@ -767,6 +772,8 @@ docTracersFirstPhase condConfigFileName = do <> localServerTrDoc <> localInboundGovernorTrDoc <> dtAcceptPolicyTrDoc +-- gRPC + <> rpcTrDoc -- Internal tracer <> internalTrDoc diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers.hs index 485d28e71f0..f7f8e7b21ab 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers.hs @@ -36,6 +36,7 @@ import Cardano.Node.Tracing.Tracers.NodeToClient () import Cardano.Node.Tracing.Tracers.NodeToNode () import Cardano.Node.Tracing.Tracers.NodeVersion (getNodeVersion) import Cardano.Node.Tracing.Tracers.P2P () +import Cardano.Node.Tracing.Tracers.Rpc () import Cardano.Node.Tracing.Tracers.Shutdown () import Cardano.Node.Tracing.Tracers.Startup () import Ouroboros.Consensus.Ledger.Inspect (LedgerEvent) @@ -152,6 +153,9 @@ mkDispatchTracers nodeKernel trBase trForward mbTrEKG trDataPoint trConfig p = d !churnModeTr <- mkCardanoTracer trBase trForward mbTrEKG ["Net", "PeerSelection", "ChurnMode"] configureTracers configReflection trConfig [churnModeTr] + !rpcTr <- mkCardanoTracer trBase trForward mbTrEKG ["RPC"] + configureTracers configReflection trConfig [rpcTr] + traceTracerInfo trBase trForward configReflection let warnings = checkNodeTraceConfiguration' trConfig @@ -183,6 +187,7 @@ mkDispatchTracers nodeKernel trBase trForward mbTrEKG trDataPoint trConfig p = d , nodeVersionTracer = Tracer (traceWith nodeVersionTr) , resourcesTracer = Tracer (traceWith resourcesTr) , ledgerMetricsTracer = Tracer (traceWith ledgerMetricsTr) + , rpcTracer = Tracer (traceWith rpcTr) } mkConsensusTracers :: forall blk. diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs new file mode 100644 index 00000000000..6ebe368b5a5 --- /dev/null +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs @@ -0,0 +1,121 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +module Cardano.Node.Tracing.Tracers.Rpc () where + +import Cardano.Api.Pretty + +import Cardano.Logging +import Cardano.Rpc.Server (TraceRpc (..), TraceRpcQuery (..), TraceRpcSubmit (..), + TraceSpanEvent (..)) + +import Data.Aeson (Object, ToJSON, ToJSONKey, Value (..), object, toJSON, toJSONList, + (.=)) + +instance LogFormatting TraceRpc where + forMachine _dtal tr = + mconcat $ + ("reason" .= prettyShow tr) + : case tr of + TraceRpcFatalError _ -> ["kind" .= String "FatalError"] + TraceRpcError _ -> ["kind" .= String "Error"] + TraceRpcQuery queryTrace -> + ["kind" .= String "QueryService"] + <> case queryTrace of + TraceRpcQueryParamsSpan s -> + [ "queryName" .= String "ReadParams" + , spanToObject s + ] + TraceRpcQueryReadUtxosSpan s -> + [ "queryName" .= String "ReadUtxos" + , spanToObject s + ] + TraceRpcSubmit submitTrace -> + ["kind" .= String "SubmitService"] + <> case submitTrace of + TraceRpcSubmitN2cConnectionError _ -> [] + TraceRpcSubmitTxDecodingError _ -> [] + TraceRpcSubmitTxValidationError _ -> [] + TraceRpcSubmitSpan s -> [spanToObject s] + + forHuman = docToText . pretty + + asMetrics = \case + -- metrics for each rpc request + -- query names here are taken from UTXORPC spec: https://utxorpc.org/query/intro/#operations + TraceRpcQuery (TraceRpcQueryParamsSpan (SpanBegin _)) -> [CounterM "rpc.request.QueryService.ReadParams" Nothing] + TraceRpcQuery (TraceRpcQueryReadUtxosSpan (SpanBegin _)) -> [CounterM "rpc.request.QueryService.ReadUtxos" Nothing] + TraceRpcSubmit (TraceRpcSubmitSpan (SpanBegin _)) -> [CounterM "rpc.request.SubmitService.SubmitTx" Nothing] + _ -> [] + +instance MetaTrace TraceRpc where + namespaceFor = + Namespace [] . \case + TraceRpcFatalError _ -> ["FatalError"] + TraceRpcError _ -> ["Error"] + TraceRpcQuery queryTrace -> + "QueryService" + : case queryTrace of + TraceRpcQueryParamsSpan _ -> ["ReadParams", "Span"] + TraceRpcQueryReadUtxosSpan _ -> ["ReadUtxos", "Span"] + TraceRpcSubmit submitTrace -> + "SubmitService" + : case submitTrace of + TraceRpcSubmitN2cConnectionError _ -> ["N2cConnectionError"] + TraceRpcSubmitTxDecodingError _ -> ["TxDecodingError"] + TraceRpcSubmitTxValidationError _ -> ["TxValidationError"] + TraceRpcSubmitSpan _ -> ["Span"] + + severityFor (Namespace _ nsInner) _ = case nsInner of + ["FatalError"] -> Just Error -- RPC server startup errors + ["Error"] -> Just Debug -- those are normal operation errors, like request errors, hide them by default + ["QueryService", "ReadParams", "Span"] -> Just Debug + ["QueryService", "ReadUtxos", "Span"] -> Just Debug + ["SubmitService", "SubmitTx", "Span"] -> Just Debug + ["SubmitService", "N2cConnectionError"] -> Just Warning -- this is a more serious error, this shouldn't happen + ["SubmitService", "TxDecodingError"] -> Just Debug -- request error + ["SubmitService", "TxValidationError"] -> Just Debug -- request error + _ -> Nothing + + documentFor (Namespace _ nsInner) = case nsInner of + ["FatalError"] -> Just "" + ["Error"] -> Just "" + ["QueryService", "ReadParams", "Span"] -> Just "" + ["QueryService", "ReadUtxos", "Span"] -> Just "" + ["SubmitService", "SubmitTx", "Span"] -> Just "" + ["SubmitService", "N2cConnectionError"] -> Just "" + ["SubmitService", "TxDecodingError"] -> Just "" + ["SubmitService", "TxValidationError"] -> Just "" + _ -> Nothing + + metricsDocFor (Namespace _ nsInner) = case nsInner of + ["QueryService", "ReadParams", "Span"] -> [("rpc.request.QueryService.ReadParams", "")] + ["QueryService", "ReadUtxos", "Span"] -> [("rpc.request.QueryService.ReadUtxos", "")] + ["SubmitService", "SubmitTx", "Span"] -> [("rpc.request.SubmitService.SubmitTx", "")] + _ -> [] + + allNamespaces = + Namespace [] + <$> [ ["FatalError"] + , ["Error"] + , ["QueryService", "ReadParams", "Span"] + , ["QueryService", "ReadParams", "Span"] + , ["SubmitService", "SubmitTx", "Span"] + , ["SubmitService", "N2cConnectionError"] + , ["SubmitService", "TxDecodingError"] + , ["SubmitService", "TxValidationError"] + ] + +-- helper functions + +spanToObject :: TraceSpanEvent -> Object +spanToObject = + mconcat . \case + SpanBegin spanId -> ["span" .= String "begin", "spanId" .= spanId] + SpanEnd spanId -> ["span" .= String "end", "spanId" .= spanId] diff --git a/cardano-node/src/Cardano/Node/Types.hs b/cardano-node/src/Cardano/Node/Types.hs index b3c9109cb4c..05aed33003c 100644 --- a/cardano-node/src/Cardano/Node/Types.hs +++ b/cardano-node/src/Cardano/Node/Types.hs @@ -47,6 +47,7 @@ import qualified Cardano.Crypto.Hash as Crypto import Cardano.Network.ConsensusMode (ConsensusMode (..)) import Cardano.Node.Configuration.Socket (SocketConfig (..)) import Cardano.Node.Orphans () +import Cardano.Rpc.Server.Config (RpcConfigF (..)) import Ouroboros.Network.NodeToNode (DiffusionMode (..)) import Control.Exception @@ -499,6 +500,16 @@ instance AdjustFilePaths a => AdjustFilePaths (Maybe a) where instance AdjustFilePaths a => AdjustFilePaths (Last a) where adjustFilePaths f = fmap (adjustFilePaths f) +instance AdjustFilePaths (File a b) where + adjustFilePaths f (File p) = File $ f p + +instance Functor f => AdjustFilePaths (RpcConfigF f) where + adjustFilePaths f (RpcConfig isEnabled rpcSocketPath nodeSocketPath) = + RpcConfig + isEnabled + (adjustFilePaths f <$> rpcSocketPath) + (adjustFilePaths f <$> nodeSocketPath) + data VRFPrivateKeyFilePermissionError = OtherPermissionsExist FilePath | GroupPermissionsExist FilePath diff --git a/cardano-node/src/Cardano/Tracing/Tracers.hs b/cardano-node/src/Cardano/Tracing/Tracers.hs index 7aaf9b03cb4..a96d0dac0c4 100644 --- a/cardano-node/src/Cardano/Tracing/Tracers.hs +++ b/cardano-node/src/Cardano/Tracing/Tracers.hs @@ -35,6 +35,9 @@ import Cardano.BM.Data.Transformers import Cardano.BM.Internal.ElidingTracer import Cardano.BM.Trace (traceNamedObject) import Cardano.BM.Tracing +import Cardano.Network.Diffusion (CardanoPeerSelectionCounters) +import qualified Cardano.Network.Diffusion.Types as Cardano.Diffusion +import qualified Cardano.Network.PeerSelection.Governor.Types as Cardano import Cardano.Node.Configuration.Logging import Cardano.Node.Protocol.Byron () import Cardano.Node.Protocol.Shelley () @@ -44,7 +47,7 @@ import qualified Cardano.Node.STM as STM import Cardano.Node.TraceConstraints import Cardano.Node.Tracing import Cardano.Node.Tracing.Tracers.NodeVersion -import Cardano.Network.Diffusion (CardanoPeerSelectionCounters) +import Cardano.Node.Tracing.Tracers.Rpc () import Cardano.Protocol.TPraos.OCert (KESPeriod (..)) import Cardano.Slotting.Slot (EpochNo (..), SlotNo (..), WithOrigin (..)) import Cardano.Tracing.Config @@ -64,8 +67,8 @@ import Ouroboros.Consensus.Ledger.Abstract (LedgerErr, LedgerState) import Ouroboros.Consensus.Ledger.Extended (ledgerState) import Ouroboros.Consensus.Ledger.Inspect (InspectLedger, LedgerEvent) import Ouroboros.Consensus.Ledger.Query (BlockQuery, Query) -import Ouroboros.Consensus.Ledger.SupportsMempool (ApplyTxErr, GenTx, GenTxId, HasTxs, - LedgerSupportsMempool, ByteSize32 (..)) +import Ouroboros.Consensus.Ledger.SupportsMempool (ApplyTxErr, ByteSize32 (..), GenTx, + GenTxId, HasTxs, LedgerSupportsMempool) import Ouroboros.Consensus.Ledger.SupportsProtocol (LedgerSupportsProtocol) import Ouroboros.Consensus.Mempool (MempoolSize (..), TraceEventMempool (..)) import Ouroboros.Consensus.MiniProtocol.BlockFetch.Server @@ -79,10 +82,6 @@ import qualified Ouroboros.Consensus.Protocol.Ledger.HotKey as HotKey import qualified Ouroboros.Consensus.Storage.ChainDB as ChainDB import qualified Ouroboros.Consensus.Storage.LedgerDB as LedgerDB import Ouroboros.Consensus.Util.Enclose - -import qualified Cardano.Network.Diffusion.Types as Cardano.Diffusion -import qualified Cardano.Network.PeerSelection.Governor.Types as Cardano - import qualified Ouroboros.Network.AnchoredFragment as AF import Ouroboros.Network.Block (BlockNo (..), ChainUpdate (..), HasHeader (..), Point, StandardHash, blockNo, pointSlot, unBlockNo) @@ -100,8 +99,7 @@ import Ouroboros.Network.InboundGovernor.State as InboundGovernor import Ouroboros.Network.NodeToClient (LocalAddress) import Ouroboros.Network.NodeToNode (RemoteAddress) import Ouroboros.Network.PeerSelection.Churn (ChurnCounters (..)) -import Ouroboros.Network.PeerSelection.Governor ( - PeerSelectionView (..)) +import Ouroboros.Network.PeerSelection.Governor (PeerSelectionView (..)) import qualified Ouroboros.Network.PeerSelection.Governor as Governor import Ouroboros.Network.Point (fromWithOrigin, withOrigin) import Ouroboros.Network.Protocol.LocalStateQuery.Type (LocalStateQuery, ShowQuery) @@ -360,6 +358,7 @@ mkTracers blockConfig tOpts@(TracingOnLegacy trSel) tr nodeKern ekgDirect = do , nodeStateTracer = nullTracer , resourcesTracer = nullTracer , ledgerMetricsTracer = nullTracer + , rpcTracer = nullTracer } where traceForgeEnabledMetric :: Maybe EKGDirect -> StartupTrace blk -> IO () @@ -535,6 +534,7 @@ mkTracers _ _ _ _ _ = , nodeVersionTracer = nullTracer , resourcesTracer = nullTracer , ledgerMetricsTracer = nullTracer + , rpcTracer = nullTracer } -------------------------------------------------------------------------------- diff --git a/cardano-node/test/Test/Cardano/Node/FilePermissions.hs b/cardano-node/test/Test/Cardano/Node/FilePermissions.hs index 524e1ee3593..0aa16e86453 100644 --- a/cardano-node/test/Test/Cardano/Node/FilePermissions.hs +++ b/cardano-node/test/Test/Cardano/Node/FilePermissions.hs @@ -39,6 +39,7 @@ import Hedgehog.Internal.Property (Group (..), failWith) import System.IO (FilePath, IO) import Text.Show (Show (..)) import Cardano.Node.Types (VRFPrivateKeyFilePermissionError (..)) +import Control.Exception (bracket) #ifdef UNIX @@ -47,7 +48,7 @@ import System.Posix.IO (closeFd, createFile) import System.Posix.Types (FileMode) import Hedgehog -import Hedgehog.Extras +import qualified Hedgehog.Extras as H import qualified Hedgehog.Gen as Gen #endif @@ -134,7 +135,7 @@ prop_sanityCheck_checkVRFFilePermissions = (const . liftIO . runExceptT $ checkVRFFilePermissions capturingTracer vrfPrivateKeyGroup) case groupResult of Left (GroupPermissionsExist _) -> do - note_ "Group permissions check should not fail" + H.note_ "Group permissions check should not fail" failure Left err -> failWith Nothing $ "checkVRFFilePermissions should not have failed with error: " diff --git a/cardano-node/test/Test/Cardano/Node/POM.hs b/cardano-node/test/Test/Cardano/Node/POM.hs index cfe7fd8be2a..28429ef79c4 100644 --- a/cardano-node/test/Test/Cardano/Node/POM.hs +++ b/cardano-node/test/Test/Cardano/Node/POM.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE TemplateHaskell #-} @@ -14,6 +15,7 @@ import Cardano.Node.Configuration.POM import Cardano.Node.Configuration.Socket import Cardano.Node.Handlers.Shutdown import Cardano.Node.Types +import Cardano.Rpc.Server.Config (makeRpcConfig) import Cardano.Tracing.Config (PartialTraceOptions (..), defaultPartialTraceConfiguration, partialTraceSelectionToEither) import Ouroboros.Consensus.Node (NodeDatabasePaths (..)) @@ -27,7 +29,9 @@ import Ouroboros.Network.NodeToNode (AcceptedConnectionsLimit (..), DiffusionMode (InitiatorAndResponderDiffusionMode)) import Ouroboros.Network.PeerSelection.PeerSharing (PeerSharing (..)) +import Data.Bifunctor (first) import Data.Monoid (Last (..)) +import Data.String import Data.Text (Text) import Hedgehog (Property, discover, withTests, (===)) @@ -171,6 +175,7 @@ testPartialYamlConfig = , pncResponderCoreAffinityPolicy = mempty , pncLedgerDbConfig = mempty , pncEgressPollInterval = mempty + , pncRpcConfig = mempty } -- | Example partial configuration theoretically created @@ -221,6 +226,7 @@ testPartialCliConfig = , pncResponderCoreAffinityPolicy = mempty , pncLedgerDbConfig = mempty , pncEgressPollInterval = mempty + , pncRpcConfig = mempty } -- | Expected final NodeConfiguration @@ -228,6 +234,7 @@ eExpectedConfig :: Either Text NodeConfiguration eExpectedConfig = do traceOptions <- partialTraceSelectionToEither (return $ PartialTracingOnLegacy defaultPartialTraceConfiguration) + ncRpcConfig <- first fromString $ makeRpcConfig mempty return $ NodeConfiguration { ncSocketConfig = SocketConfig mempty mempty mempty mempty , ncShutdownConfig = ShutdownConfig Nothing (Just . ASlot $ SlotNo 42) @@ -277,6 +284,7 @@ eExpectedConfig = do , ncGenesisConfig = disableGenesisConfig , ncResponderCoreAffinityPolicy = NoResponderCoreAffinity , ncLedgerDbConfig = LedgerDbConfiguration DefaultNumOfDiskSnapshots DefaultSnapshotInterval DefaultQueryBatchSize V2InMemory noDeprecatedOptions + , ncRpcConfig } -- ----------------------------------------------------------------------------- diff --git a/cardano-submit-api/cardano-submit-api.cabal b/cardano-submit-api/cardano-submit-api.cabal index 866ca526430..248564ae3ba 100644 --- a/cardano-submit-api/cardano-submit-api.cabal +++ b/cardano-submit-api/cardano-submit-api.cabal @@ -39,9 +39,9 @@ library , aeson , async , bytestring - , cardano-api ^>= 10.19 + , cardano-api ^>= 10.21 , cardano-binary - , cardano-cli ^>= 10.13 + , cardano-cli ^>= 10.14 , cardano-crypto-class ^>=2.2.3.2 , containers , ekg-core diff --git a/cardano-testnet/cardano-testnet.cabal b/cardano-testnet/cardano-testnet.cabal index d9ad3e1840a..53f54c2c887 100644 --- a/cardano-testnet/cardano-testnet.cabal +++ b/cardano-testnet/cardano-testnet.cabal @@ -41,8 +41,8 @@ library , annotated-exception , ansi-terminal , bytestring - , cardano-api ^>= 10.19 - , cardano-cli:{cardano-cli, cardano-cli-test-lib} ^>= 10.13 + , cardano-api ^>= 10.21 + , cardano-cli:{cardano-cli, cardano-cli-test-lib} ^>= 10.14 , cardano-crypto-class ^>=2.2.3.2 , cardano-crypto-wrapper , cardano-git-rev ^>= 0.2.2 @@ -59,6 +59,7 @@ library , cardano-node , cardano-ping ^>= 0.9 , cardano-prelude + , cardano-rpc , contra-tracer , containers , data-default-class @@ -229,6 +230,8 @@ test-suite cardano-testnet-test Cardano.Testnet.Test.Gov.TreasuryDonation Cardano.Testnet.Test.Gov.TreasuryGrowth Cardano.Testnet.Test.Gov.TreasuryWithdrawal + Cardano.Testnet.Test.Rpc.Query + Cardano.Testnet.Test.Rpc.Transaction Cardano.Testnet.Test.Misc Cardano.Testnet.Test.Node.Shutdown Cardano.Testnet.Test.MainnetParams @@ -247,12 +250,15 @@ test-suite cardano-testnet-test , bytestring , cardano-api , cardano-cli:{cardano-cli, cardano-cli-test-lib} + , cardano-ledger-api , cardano-crypto-class + , cardano-ledger-binary , cardano-ledger-conway , cardano-ledger-core , cardano-ledger-shelley , cardano-node , cardano-prelude + , cardano-rpc , cardano-slotting , cardano-strict-containers ^>= 0.1 , cardano-testnet @@ -272,6 +278,7 @@ test-suite cardano-testnet-test , mtl , process , resourcet + , rio , regex-compat , rio , tasty ^>= 1.5 diff --git a/cardano-testnet/src/Cardano/Testnet.hs b/cardano-testnet/src/Cardano/Testnet.hs index a5c88dee0e6..ec467f46ae9 100644 --- a/cardano-testnet/src/Cardano/Testnet.hs +++ b/cardano-testnet/src/Cardano/Testnet.hs @@ -46,6 +46,7 @@ module Cardano.Testnet ( TestnetNode(..), isTestnetNodeSpo, nodeSocketPath, + nodeRpcSocketPath, ) where import Testnet.Components.Query diff --git a/cardano-testnet/src/Parsers/Cardano.hs b/cardano-testnet/src/Parsers/Cardano.hs index 19291a95e33..24bb845a8a2 100644 --- a/cardano-testnet/src/Parsers/Cardano.hs +++ b/cardano-testnet/src/Parsers/Cardano.hs @@ -7,6 +7,12 @@ module Parsers.Cardano import Cardano.Api (AnyShelleyBasedEra (..)) +import Cardano.Api (AnyShelleyBasedEra(..)) +import Cardano.CLI.EraBased.Common.Option (bounded, command') +import Cardano.Api (AnyShelleyBasedEra (..), EraInEon (..), prettyShow) +import Cardano.Api.Era (AnyShelleyBasedEra (..)) +import Cardano.Api.Pretty + import Cardano.CLI.EraBased.Common.Option hiding (pNetworkId) import Prelude @@ -50,10 +56,10 @@ pCardanoTestnetCliOptions = CardanoTestnetOptions <*> pure (AnyShelleyBasedEra defaultEra) <*> pMaxLovelaceSupply <*> OA.option (OA.eitherReader readNodeLoggingFormat) - ( OA.long "nodeLoggingFormat" + ( OA.long "node-logging-format" <> OA.help "Node logging format (json|text)" <> OA.metavar "LOGGING_FORMAT" - <> OA.showDefault + <> OA.showDefaultWith prettyShow <> OA.value (cardanoNodeLoggingFormat def) ) <*> OA.option OA.auto @@ -63,7 +69,7 @@ pCardanoTestnetCliOptions = CardanoTestnetOptions <> OA.showDefault <> OA.value 3 ) - <*> OA.flag False True + <*> OA.switch ( OA.long "enable-new-epoch-state-logging" <> OA.help "Enable new epoch state logging to logs/ledger-epoch-state.log" <> OA.showDefault @@ -73,6 +79,11 @@ pCardanoTestnetCliOptions = CardanoTestnetOptions <> OA.help "Directory where to store files, sockets, and so on. It is created if it doesn't exist. If unset, a temporary directory is used." <> OA.metavar "DIRECTORY" ))) + <*> OA.switch + ( OA.long "enable-grpc" + <> OA.help "[EXPERIMENTAL] Enable gRPC endpoint on all of testnet nodes. The listening socket file will be the same directory as node's N2C socket." + <> OA.showDefault + ) pTestnetNodeOptions :: Parser [NodeOption] pTestnetNodeOptions = diff --git a/cardano-testnet/src/Testnet/Defaults.hs b/cardano-testnet/src/Testnet/Defaults.hs index b84c3508ead..579614f9a64 100644 --- a/cardano-testnet/src/Testnet/Defaults.hs +++ b/cardano-testnet/src/Testnet/Defaults.hs @@ -85,9 +85,11 @@ import qualified Data.Aeson.Key as Aeson import qualified Data.Aeson.KeyMap as Aeson import Data.Bifunctor (bimap) import qualified Data.Default.Class as DefaultClass +import Data.IORef import Data.Proxy import Data.Ratio import Data.Scientific +import Data.String import Data.Text (Text) import qualified Data.Text as Text import Data.Time (UTCTime) @@ -95,6 +97,8 @@ import Data.Word (Word64) import Lens.Micro import Numeric.Natural import System.FilePath (()) +import System.IO.Unsafe +import GHC.Exts (IsList(..)) import Test.Cardano.Ledger.Core.Rational import Testnet.Start.Types @@ -108,13 +112,13 @@ newtype AlonzoGenesisError = AlonzoGenErrTooMuchPrecision Rational deriving Show -instance Exception AlonzoGenesisError where +instance Exception AlonzoGenesisError where displayException = Api.docToString . Api.prettyError defaultAlonzoGenesis :: Either AlonzoGenesisError AlonzoGenesis defaultAlonzoGenesis = do - let genesis = Api.alonzoGenesisDefaults + let genesis = Api.alonzoGenesisDefaults prices = Ledger.agPrices genesis -- double check that prices have correct values - they're set using unsafeBoundedRational in cardano-api @@ -186,7 +190,8 @@ defaultYamlHardforkViaConfig :: ShelleyBasedEra era -> Aeson.KeyMap Aeson.Value defaultYamlHardforkViaConfig sbe = defaultYamlConfig <> tracers - <> [("TraceOptions", Aeson.Object mempty)] + <> [("TraceOptions", traceOptions)] + -- <> [("TraceOptions", Aeson.Object mempty)] <> protocolVersions sbe <> hardforkViaConfig sbe where @@ -300,6 +305,28 @@ defaultYamlHardforkViaConfig sbe = , (proxyName (Proxy @TraceTxSubmissionProtocol), False) ] + traceOptions = do + Aeson.object + [ "" .= Aeson.object + [ "backends" .= Aeson.Array + [ "EKGBackend" + , "PrometheusSimple suffix 0.0.0.0 12798" + , "Stdout MachineFormat" + -- , "Stdout HumanFormatColoured" + ] + , "detail" .= ("DNormal" :: Aeson.Value) + -- , "severity" .= ("Notice" :: Aeson.Value) + , "severity" .= ("Debug" :: Aeson.Value) + ] + ] + -- traceOptions = toJSON $ + -- emptyTraceConfig + -- { tcOptions = fromList + -- [([], [ ConfSeverity (SeverityF (Just Info)) + -- , ConfBackend [Stdout HumanFormatColoured, EKGBackend]]) + -- ] + -- } + defaultYamlConfig :: Aeson.KeyMap Aeson.Value defaultYamlConfig = Aeson.fromList diff --git a/cardano-testnet/src/Testnet/Ping.hs b/cardano-testnet/src/Testnet/Ping.hs index 92c550d02cf..b2d2824de09 100644 --- a/cardano-testnet/src/Testnet/Ping.hs +++ b/cardano-testnet/src/Testnet/Ping.hs @@ -42,6 +42,7 @@ import qualified Network.Mux.Types as Mux import Network.Socket (AddrInfo (..), PortNumber, StructLinger (..)) import qualified Network.Socket as Socket import Prettyprinter + import Testnet.Process.RunIO (liftIOAnnotated) import qualified Hedgehog.Extras.Stock.IO.Network.Socket as IO diff --git a/cardano-testnet/src/Testnet/Property/Assert.hs b/cardano-testnet/src/Testnet/Property/Assert.hs index 800eb927884..f0c68fba937 100644 --- a/cardano-testnet/src/Testnet/Property/Assert.hs +++ b/cardano-testnet/src/Testnet/Property/Assert.hs @@ -1,7 +1,6 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} -{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} @@ -10,7 +9,6 @@ module Testnet.Property.Assert ( assertByDeadlineIOCustom , readJsonLines - , assertChainExtended , getRelevantSlots , assertExpectedSposInLedgerState , assertErasEqual @@ -39,31 +37,24 @@ import qualified Data.Time.Clock as DTC import Data.Type.Equality import Data.Word (Word8) import GHC.Stack as GHC -import RIO (throwString) import Testnet.Process.RunIO import Testnet.Start.Types -import Testnet.Types import Hedgehog (MonadTest) import qualified Hedgehog as H +import qualified Hedgehog.Extras as H import Hedgehog.Extras.Internal.Test.Integration (IntegrationState) -import qualified Hedgehog.Extras.Stock.IO.File as IO -import qualified Hedgehog.Extras.Test.Base as H import Hedgehog.Extras.Test.Process (ExecConfig) +import RIO (throwString) + newlineBytes :: Word8 newlineBytes = 10 readJsonLines :: (MonadTest m, MonadIO m, HasCallStack) => FilePath -> m [Value] readJsonLines fp = withFrozenCallStack $ mapMaybe (Aeson.decode @Value) . LBS.split newlineBytes <$> H.evalIO (LBS.readFile fp) -fileJsonGrep :: FilePath -> (Value -> Bool) -> IO Bool -fileJsonGrep fp f = do - lines <- LBS.split newlineBytes <$> LBS.readFile fp - let jsons = mapMaybe (Aeson.decode @Value) lines - return $ L.any f jsons - assertByDeadlineIOCustom :: (MonadIO m, HasCallStack) => String -> DTC.UTCTime -> IO Bool -> m () @@ -94,31 +85,17 @@ assertExpectedSposInLedgerState output (NumPools numExpectedPools) execConfig = ePoolSet <- liftIOAnnotated (Aeson.eitherDecodeFileStrict' @(Set PoolId) output) case ePoolSet of - Left err -> + Left err -> throwString $ "Failed to decode stake pools from ledger state: " <> err Right poolSet -> do let numPoolsInLedgerState = Set.size poolSet unless (numPoolsInLedgerState == numExpectedPools) $ - throwString $ unlines + throwString $ unlines [ "Expected number of stake pools not found in ledger state" , "Expected: ", show numExpectedPools , "Actual: ", show numPoolsInLedgerState ] -assertChainExtended - :: HasCallStack - => MonadIO m - => DTC.UTCTime - -> NodeLoggingFormat - -> TestnetNode - -> m () -assertChainExtended deadline nodeLoggingFormat TestnetNode{nodeName, nodeStdout} = withFrozenCallStack $ - assertByDeadlineIOCustom ("Chain not extended in " <> nodeName) deadline $ do - case nodeLoggingFormat of - NodeLoggingFormatAsText -> IO.fileContains "Chain extended, new tip" nodeStdout - NodeLoggingFormatAsJson -> fileJsonGrep nodeStdout $ \v -> - Aeson.parseMaybe (Aeson.parseJSON @(LogEntry Kind)) v == Just (LogEntry (Kind "AddedToCurrentChain")) - newtype LogEntry a = LogEntry { unLogEntry :: a } deriving (Eq, Show) diff --git a/cardano-testnet/src/Testnet/Runtime.hs b/cardano-testnet/src/Testnet/Runtime.hs index 1ef96d658e3..8431209fa2d 100644 --- a/cardano-testnet/src/Testnet/Runtime.hs +++ b/cardano-testnet/src/Testnet/Runtime.hs @@ -146,7 +146,7 @@ startNode tp node ipv4 port _testnetMagic nodeCmd = GHC.withFrozenCallStack $ do left MaxSprocketLengthExceededError let socketAbsPath = H.sprocketSystemName sprocket - completeNodeCmd = nodeCmd ++ + completeNodeCmd = nodeCmd <> [ "--socket-path", H.sprocketArgumentName sprocket , "--port", show port , "--host-addr", showIpv4Address ipv4 diff --git a/cardano-testnet/src/Testnet/Start/Cardano.hs b/cardano-testnet/src/Testnet/Start/Cardano.hs index ebc68247503..e02c57ed515 100644 --- a/cardano-testnet/src/Testnet/Start/Cardano.hs +++ b/cardano-testnet/src/Testnet/Start/Cardano.hs @@ -65,7 +65,7 @@ import Testnet.Filepath import Testnet.Handlers (interruptNodesOnSigINT) import Testnet.Orphans () import Testnet.Process.RunIO (execCli', execCli_, liftIOAnnotated, mkExecConfig) -import Testnet.Property.Assert (assertChainExtended, assertExpectedSposInLedgerState) +import Testnet.Property.Assert (assertExpectedSposInLedgerState) import Testnet.Runtime as TR import Testnet.Start.Types import Testnet.Types as TR hiding (shelleyGenesis) @@ -74,8 +74,9 @@ import qualified Hedgehog.Extras as H import qualified Hedgehog.Extras.Stock.IO.Network.Port as H import Hedgehog.Internal.Property (failException) -import RIO (MonadUnliftIO, RIO (..), runRIO, throwString) +import RIO (MonadUnliftIO, RIO (..), runRIO, throwString, timeout) import RIO.Orphans (ResourceMap) +import RIO.State (put) import UnliftIO.Async import UnliftIO.Exception (stringException) @@ -226,9 +227,9 @@ cardanoTestnet , updateTimestamps } = do let CardanoTestnetOptions - { cardanoNodeLoggingFormat=nodeLoggingFormat - , cardanoEnableNewEpochStateLogging=enableNewEpochStateLogging + { cardanoEnableNewEpochStateLogging=enableNewEpochStateLogging , cardanoNodes + , cardanoEnableRpc } = testnetOptions nPools = cardanoNumPools testnetOptions nodeConfigFile = tmpAbsPath "configuration.yaml" @@ -282,7 +283,7 @@ cardanoTestnet liftIOAnnotated $ writeFile (nodeDataDir "port") (show portNumber) let topologyPath = tmpAbsPath Defaults.defaultNodeDataDir i "topology.json" tBytes <- liftIOAnnotated $ LBS.readFile topologyPath - case eitherDecode tBytes of + case eitherDecode tBytes of Right (abstractTopology :: P2P.NetworkTopology NodeId) -> do topology <- mapM idToRemoteAddressP2P abstractTopology liftIOAnnotated $ LBS.writeFile topologyPath $ encode topology @@ -339,7 +340,7 @@ cardanoTestnet ] <> spoNodeCliArgs <> extraCliArgs nodeOptions - + <> ["--grpc-enable" | cardanoEnableRpc] pure $ eRuntime <&> \rt -> rt{poolKeys=mKeys} let (failedNodes, testnetNodes') = partitionEithers eTestnetNodes @@ -349,11 +350,8 @@ cardanoTestnet -- Interrupt cardano nodes when the main process is interrupted liftIOAnnotated $ interruptNodesOnSigINT testnetNodes' - -- FIXME: use foldEpochState waiting for chain extensions - now <- liftIOAnnotated DTC.getCurrentTime - let deadline = DTC.addUTCTime 45 now - forM_ testnetNodes' $ \nodeStdoutFile -> do - assertChainExtended deadline nodeLoggingFormat nodeStdoutFile + -- Make sure that all nodes are healthy by waiting for a chain extension + mapConcurrently_ (waitForBlockThrow 45 (File nodeConfigFile)) testnetNodes' let runtime = TestnetRuntime { configurationFile = File nodeConfigFile @@ -397,6 +395,37 @@ cardanoTestnet mkTestnetNodeKeyPaths :: Int -> SpoNodeKeys mkTestnetNodeKeyPaths n = makePathsAbsolute $ Defaults.defaultSpoKeys n + -- wait for new blocks or throw an exception if there are none in the timeout period + waitForBlockThrow :: MonadUnliftIO m + => MonadCatch m + => Int -- ^ timeout in seconds + -> NodeConfigFile 'In + -> TestnetNode + -> m () + waitForBlockThrow timeoutSeconds nodeConfigFile node@TestnetNode{nodeName} = do + result <- timeout (timeoutSeconds * 1_000_000) $ + runExceptT . foldEpochState + nodeConfigFile + (nodeSocketPath node) + QuickValidation + (EpochNo maxBound) + minBound + $ \_ slotNo blockNo -> do + put slotNo + pure $ if blockNo >= 1 + then ConditionMet -- we got one block + else ConditionNotMet + + case result of + Just (Right (ConditionMet, _)) -> pure () + Just (Right (ConditionNotMet, slotNo)) -> + throwString $ nodeName <> " was unable to produce any blocks. Reached slot " <> show slotNo + Just (Left err) -> + throwString $ "foldBlocks on " <> nodeName <> " encountered an error while waiting for new blocks: " <> show (prettyError err) + _ -> + throwString $ nodeName <> " was unable to produce any blocks for " <> show timeoutSeconds <> "s" + + -- | A convenience wrapper around `createTestnetEnv` and `cardanoTestnet` createAndRunTestnet :: () => HasCallStack @@ -420,8 +449,8 @@ retryOnAddressInUseError retryOnAddressInUseError act = withFrozenCallStack $ go maximumTimeout retryTimeout where go :: HasCallStack => NominalDiffTime -> NominalDiffTime -> ExceptT NodeStartFailure m a - go timeout interval - | timeout <= 0 = withFrozenCallStack $ do + go timeout' interval + | timeout' <= 0 = withFrozenCallStack $ do act | otherwise = withFrozenCallStack $ do !time <- liftIOAnnotated DTC.getCurrentTime @@ -430,7 +459,7 @@ retryOnAddressInUseError act = withFrozenCallStack $ go maximumTimeout retryTime liftIOAnnotated $ threadDelay (round $ interval * 1_000_000) !time' <- liftIOAnnotated DTC.getCurrentTime let elapsedTime = time' `diffUTCTime` time - newTimeout = timeout - elapsedTime + newTimeout = timeout' - elapsedTime go newTimeout interval e -> throwError e diff --git a/cardano-testnet/src/Testnet/Start/Types.hs b/cardano-testnet/src/Testnet/Start/Types.hs index aa615c453fd..f3cf30085e6 100644 --- a/cardano-testnet/src/Testnet/Start/Types.hs +++ b/cardano-testnet/src/Testnet/Start/Types.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingVia #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} @@ -176,6 +177,8 @@ data CardanoTestnetOptions = CardanoTestnetOptions , cardanoNumDReps :: NumDReps -- ^ The number of DReps to generate at creation , cardanoEnableNewEpochStateLogging :: Bool -- ^ if epoch state logging is enabled , cardanoOutputDir :: UserProvidedEnv -- ^ The output directory where to store files, sockets, and so on. If unset, a temporary directory is used. + , cardanoEnableRpc :: Bool + -- ^ True to enable gRPC endpoints in all testnet nodes } deriving (Eq, Show) -- | Path to the configuration file of the node, specified by the user @@ -211,6 +214,7 @@ instance Default CardanoTestnetOptions where , cardanoNumDReps = 3 , cardanoEnableNewEpochStateLogging = True , cardanoOutputDir = def + , cardanoEnableRpc = False } -- | Options that are implemented by writing fields in the Shelley genesis file. @@ -257,11 +261,20 @@ isRelayNodeOptions RelayNodeOptions{} = True cardanoDefaultTestnetNodeOptions :: [NodeOption] cardanoDefaultTestnetNodeOptions = [ SpoNodeOptions [] - , RelayNodeOptions [] - , RelayNodeOptions [] + -- TODO: uncomment relays, because they were causing conflicts for prometheus ports + -- , RelayNodeOptions [] + -- , RelayNodeOptions [] ] -data NodeLoggingFormat = NodeLoggingFormatAsJson | NodeLoggingFormatAsText deriving (Eq, Show) +data NodeLoggingFormat + = NodeLoggingFormatAsJson + | NodeLoggingFormatAsText + deriving (Eq, Show) + +instance Pretty NodeLoggingFormat where + pretty = \case + NodeLoggingFormatAsJson -> "json" + NodeLoggingFormatAsText -> "text" data NodeConfiguration @@ -276,7 +289,7 @@ data Conf = Conf , updateTimestamps :: UpdateTimestamps } deriving (Eq, Show) --- | Same as mkConfig except that it renders the path +-- | Same as mkConfig except that it renders the path -- when failing in a property test. mkConf :: (HasCallStack, MonadTest m) => FilePath -> m Conf mkConf tempAbsPath' = withFrozenCallStack $ do @@ -286,7 +299,7 @@ mkConf tempAbsPath' = withFrozenCallStack $ do -- | Create a 'Conf' from a temporary absolute path, with Genesis Hashes enabled -- and updating time stamps disabled. mkConfig :: FilePath -> Conf -mkConfig tempAbsPath' = +mkConfig tempAbsPath' = Conf { genesisHashesPolicy = WithHashes , tempAbsPath = TmpAbsolutePath (addTrailingPathSeparator tempAbsPath') @@ -296,10 +309,10 @@ mkConfig tempAbsPath' = -- | Create a 'Conf' from an absolute path, with Genesis Hashes enabled -- and updating time stamps disabled. mkConfigAbs :: FilePath -> IO Conf -mkConfigAbs userOutputDir = do +mkConfigAbs userOutputDir = do absUserOutputDir <- makeAbsolute userOutputDir dirExists <- doesDirectoryExist absUserOutputDir - let conf = mkConfig absUserOutputDir + let conf = mkConfig absUserOutputDir unless dirExists $ createDirectory absUserOutputDir pure conf diff --git a/cardano-testnet/src/Testnet/Types.hs b/cardano-testnet/src/Testnet/Types.hs index 700be30a088..a5d67aa861b 100644 --- a/cardano-testnet/src/Testnet/Types.hs +++ b/cardano-testnet/src/Testnet/Types.hs @@ -21,6 +21,7 @@ module Testnet.Types , testnetSprockets , TestnetNode(..) , nodeSocketPath + , nodeRpcSocketPath , nodeConnectionInfo , isTestnetNodeSpo , SpoNodeKeys(..) @@ -52,6 +53,7 @@ import Cardano.Crypto.ProtocolMagic (RequiresNetworkMagic (..)) import Cardano.Node.Configuration.POM import qualified Cardano.Node.Protocol.Byron as Byron import Cardano.Node.Types +import Cardano.Rpc.Server.Config (nodeSocketPathToRpcSocketPath) import Prelude @@ -149,6 +151,10 @@ isTestnetNodeSpo = isJust . poolKeys nodeSocketPath :: TestnetNode -> SocketPath nodeSocketPath = File . H.sprocketSystemName . nodeSprocket +-- | Provide a default RPC socket path +nodeRpcSocketPath :: TestnetNode -> SocketPath +nodeRpcSocketPath = nodeSocketPathToRpcSocketPath . nodeSocketPath + -- | Connection data for a node in the testnet nodeConnectionInfo :: MonadTest m => TestnetRuntime diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/Query.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/Query.hs new file mode 100644 index 00000000000..b4bb2e89882 --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/Query.hs @@ -0,0 +1,194 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedLabels #-} +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +module Cardano.Testnet.Test.Rpc.Query + ( hprop_rpc_query_pparams + ) +where + +import Cardano.Api +import qualified Cardano.Api.Ledger as L + +import Cardano.CLI.Type.Output (QueryTipLocalStateOutput (..)) +import qualified Cardano.Ledger.Api as L +import qualified Cardano.Ledger.Binary.Version as L +import qualified Cardano.Ledger.Conway.Core as L +import qualified Cardano.Ledger.Conway.PParams as L +import qualified Cardano.Ledger.Plutus as L +import qualified Cardano.Rpc.Client as Rpc +import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Query as UtxoRpc +import Cardano.Rpc.Server.Internal.UtxoRpc.Query () +import Cardano.Rpc.Server.Internal.UtxoRpc.Type (anyUtxoDataUtxoRpcToUtxo, + utxoRpcBigIntToInteger) +import Cardano.Testnet + +import Prelude + +import Control.Exception +import qualified Data.ByteString.Short as SBS +import Data.Default.Class +import qualified Data.Map.Strict as M +import Lens.Micro + +import Testnet.Components.Query +import Testnet.Process.Run +import Testnet.Property.Util (integrationRetryWorkspace) +import Testnet.Start.Types + +import Hedgehog +import qualified Hedgehog as H +import qualified Hedgehog.Extras.Test.Base as H +import qualified Hedgehog.Extras.Test.Concurrent as H +import qualified Hedgehog.Extras.Test.TestWatchdog as H + +hprop_rpc_query_pparams :: Property +hprop_rpc_query_pparams = integrationRetryWorkspace 2 "rpc-query-pparams" $ \tempAbsBasePath' -> H.runWithDefaultWatchdog_ $ do + conf@Conf{tempAbsPath} <- mkConf tempAbsBasePath' + let tempAbsPath' = unTmpAbsPath tempAbsPath + + let ceo = ConwayEraOnwardsConway + sbe = convert ceo + eraName = eraToString sbe + options = def{cardanoNodeEra = AnyShelleyBasedEra sbe, cardanoEnableRpc = True} + + TestnetRuntime + { testnetMagic + , configurationFile + , testnetNodes = node0@TestnetNode{nodeSprocket} : _ + } <- + createAndRunTestnet options def conf + + execConfig <- mkExecConfig tempAbsPath' nodeSprocket testnetMagic + epochStateView <- getEpochStateView configurationFile (nodeSocketPath node0) + pparams <- unLedgerProtocolParameters <$> getProtocolParams epochStateView ceo + -- H.noteShowPretty_ pparams + utxos <- findAllUtxos epochStateView sbe + H.noteShowPretty_ utxos + rpcSocket <- H.note . unFile $ nodeRpcSocketPath node0 + + ---------- + -- Get tip + ---------- + QueryTipLocalStateOutput{localStateChainTip} <- + H.noteShowM $ execCliStdoutToJson execConfig [eraName, "query", "tip"] + (slot, blockHash, blockNo) <- case localStateChainTip of + ChainTipAtGenesis -> H.failure + ChainTip (SlotNo slot) (HeaderHash hash) (BlockNo blockNo) -> pure (slot, SBS.fromShort hash, blockNo) + + -------------- + -- RPC queries + -------------- + let rpcServer = Rpc.ServerUnix rpcSocket + (pparamsResponse, utxosResponse) <- H.noteShowM . H.evalIO . Rpc.withConnection def rpcServer $ \conn -> do + pparams' <- do + let req = Rpc.defMessage + Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf UtxoRpc.QueryService "readParams")) req + + utxos' <- do + let req = Rpc.defMessage + Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf UtxoRpc.QueryService "readUtxos")) req + pure (pparams', utxos') + + --------------------------- + -- Test readParams response + --------------------------- + pparamsResponse ^. #ledgerTip . #slot === slot + pparamsResponse ^. #ledgerTip . #hash === blockHash + pparamsResponse ^. #ledgerTip . #height === blockNo + pparamsResponse ^. #ledgerTip . #timestamp === 0 -- not possible to implement at this moment + + -- https://docs.cardano.org/about-cardano/explore-more/parameter-guide + let chainParams = pparamsResponse ^. #values . #cardano + babbageEraOnwardsConstraints (convert ceo) $ do + pparams ^. L.ppCoinsPerUTxOByteL . to L.unCoinPerByte . to L.unCoin + ===^ chainParams ^. #coinsPerUtxoByte . to utxoRpcBigIntToInteger + pparams ^. L.ppMaxTxSizeL === chainParams ^. #maxTxSize . to fromIntegral + pparams ^. L.ppMinFeeBL ===^ chainParams ^. #minFeeCoefficient . to (fmap L.Coin . utxoRpcBigIntToInteger) + pparams ^. L.ppMinFeeAL ===^ chainParams ^. #minFeeConstant . to (fmap L.Coin . utxoRpcBigIntToInteger) + pparams ^. L.ppMaxBBSizeL === chainParams ^. #maxBlockBodySize . to fromIntegral + pparams ^. L.ppMaxBHSizeL === chainParams ^. #maxBlockHeaderSize . to fromIntegral + pparams ^. L.ppKeyDepositL ===^ chainParams ^. #stakeKeyDeposit . to (fmap L.Coin . utxoRpcBigIntToInteger) + pparams ^. L.ppPoolDepositL ===^ chainParams ^. #poolDeposit . to (fmap L.Coin . utxoRpcBigIntToInteger) + pparams ^. L.ppEMaxL . to L.unEpochInterval === chainParams ^. #poolRetirementEpochBound . to fromIntegral + pparams ^. L.ppNOptL === chainParams ^. #desiredNumberOfPools . to fromIntegral + pparams ^. L.ppA0L . to L.unboundRational === chainParams ^. #poolInfluence . to inject + pparams ^. L.ppNOptL === chainParams ^. #desiredNumberOfPools . to fromIntegral + pparams ^. L.ppRhoL . to L.unboundRational === chainParams ^. #monetaryExpansion . to inject + pparams ^. L.ppMinPoolCostL ===^ chainParams ^. #minPoolCost . to (fmap L.Coin . utxoRpcBigIntToInteger) + ( pparams ^. L.ppProtocolVersionL . to L.pvMajor . to L.getVersion + , pparams ^. L.ppProtocolVersionL . to L.pvMinor + ) + === ( chainParams ^. #protocolVersion . #major + , chainParams ^. #protocolVersion . #minor . to fromIntegral + ) + pparams ^. L.ppMaxValSizeL === chainParams ^. #maxValueSize . to fromIntegral + pparams ^. L.ppCollateralPercentageL === chainParams ^. #collateralPercentage . to fromIntegral + pparams ^. L.ppMaxCollateralInputsL === chainParams ^. #maxCollateralInputs . to fromIntegral + let pparamsCostModels = L.getCostModelParams <$> pparams ^. L.ppCostModelsL . to L.costModelsValid + wrapInMaybe v = if v == mempty then Nothing else Just v + M.lookup L.PlutusV1 pparamsCostModels === chainParams ^. #costModels . #plutusV1 . #values . to wrapInMaybe + M.lookup L.PlutusV2 pparamsCostModels === chainParams ^. #costModels . #plutusV2 . #values . to wrapInMaybe + M.lookup L.PlutusV3 pparamsCostModels === chainParams ^. #costModels . #plutusV3 . #values . to wrapInMaybe + M.lookup L.PlutusV4 pparamsCostModels === chainParams ^. #costModels . #plutusV4 . #values . to wrapInMaybe + pparams ^. L.ppPricesL . to L.prSteps . to L.unboundRational === chainParams ^. #prices . #steps . to inject + pparams ^. L.ppPricesL . to L.prMem . to L.unboundRational === chainParams ^. #prices . #memory . to inject + pparams ^. L.ppMaxTxExUnitsL === chainParams ^. #maxExecutionUnitsPerTransaction . to inject + pparams ^. L.ppMaxBlockExUnitsL === chainParams ^. #maxExecutionUnitsPerBlock . to inject + pparams ^. L.ppMinFeeRefScriptCostPerByteL . to L.unboundRational + === chainParams ^. #minFeeScriptRefCostPerByte . to inject + let poolVotingThresholds :: L.PoolVotingThresholds = + conwayEraOnwardsConstraints ceo $ + pparams ^. L.ppPoolVotingThresholdsL + ( L.unboundRational + <$> [ poolVotingThresholds ^. L.pvtMotionNoConfidenceL + , poolVotingThresholds ^. L.pvtCommitteeNormalL + , poolVotingThresholds ^. L.pvtCommitteeNoConfidenceL + , poolVotingThresholds ^. L.pvtHardForkInitiationL + , poolVotingThresholds ^. L.pvtPPSecurityGroupL + ] + ) + === chainParams ^. #poolVotingThresholds . #thresholds . to (map inject) + let drepVotingThresholds :: L.DRepVotingThresholds = + conwayEraOnwardsConstraints ceo $ + pparams ^. L.ppDRepVotingThresholdsL + ( L.unboundRational + <$> [ drepVotingThresholds ^. L.dvtMotionNoConfidenceL + , drepVotingThresholds ^. L.dvtCommitteeNormalL + , drepVotingThresholds ^. L.dvtCommitteeNoConfidenceL + , drepVotingThresholds ^. L.dvtUpdateToConstitutionL + , drepVotingThresholds ^. L.dvtHardForkInitiationL + , drepVotingThresholds ^. L.dvtPPNetworkGroupL + , drepVotingThresholds ^. L.dvtPPEconomicGroupL + , drepVotingThresholds ^. L.dvtPPTechnicalGroupL + , drepVotingThresholds ^. L.dvtPPGovGroupL + , drepVotingThresholds ^. L.dvtTreasuryWithdrawalL + ] + ) + === chainParams ^. #drepVotingThresholds . #thresholds . to (map inject) + pparams ^. L.ppCommitteeMinSizeL === chainParams ^. #minCommitteeSize . to fromIntegral + pparams ^. L.ppCommitteeMaxTermLengthL . to L.unEpochInterval + === chainParams ^. #committeeTermLimit . to fromIntegral + pparams ^. L.ppGovActionLifetimeL . to L.unEpochInterval + === chainParams ^. #governanceActionValidityPeriod . to fromIntegral + pparams ^. L.ppGovActionDepositL ===^ chainParams ^. #governanceActionDeposit . to (fmap L.Coin . utxoRpcBigIntToInteger) + pparams ^. L.ppDRepDepositL ===^ chainParams ^. #drepDeposit . to (fmap L.Coin . utxoRpcBigIntToInteger) + pparams ^. L.ppDRepActivityL . to L.unEpochInterval === chainParams ^. #drepInactivityPeriod . to fromIntegral + + -------------------------- + -- Test readUtxos response + -------------------------- + + utxoFromUtxoRpc <- H.leftFail $ utxosResponse ^. #items . to (anyUtxoDataUtxoRpcToUtxo $ convert ceo) + utxos === utxoFromUtxoRpc + +(===^) :: (Eq a, Show a, H.MonadTest m) => a -> Either SomeException a -> m () +expected ===^ actual = do + v <- H.leftFail actual + expected === v + +infix 4 ===^ diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/Transaction.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/Transaction.hs new file mode 100644 index 00000000000..0b0ef7db5fc --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/Transaction.hs @@ -0,0 +1,157 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedLabels #-} +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeOperators #-} + +module Cardano.Testnet.Test.Rpc.Transaction + ( hprop_rpc_transaction + ) +where + +import Cardano.Api +import qualified Cardano.Api.Ledger as L + +import Cardano.Rpc.Client (Proto) +import qualified Cardano.Rpc.Client as Rpc +import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Query as UtxoRpc +import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Submit as UtxoRpc +import Cardano.Rpc.Server.Internal.UtxoRpc.Query () +import Cardano.Rpc.Server.Internal.UtxoRpc.Type +import Cardano.Testnet + +import Prelude + +import Control.Monad +import Control.Monad.Fix +import Data.Default.Class +import qualified Data.Text.Encoding as T +import GHC.Stack +import Lens.Micro + +import Testnet.Property.Util (integrationRetryWorkspace) +import Testnet.Types + +import Hedgehog +import qualified Hedgehog as H +import qualified Hedgehog.Extras.Test.Base as H +import qualified Hedgehog.Extras.Test.TestWatchdog as H + +import RIO (ByteString, threadDelay) + +hprop_rpc_transaction :: Property +hprop_rpc_transaction = integrationRetryWorkspace 2 "rpc-tx" $ \tempAbsBasePath' -> H.runWithDefaultWatchdog_ $ do + conf <- mkConf tempAbsBasePath' + let (ceo, eraProxy) = + (conwayBasedEra, asType) :: era ~ ConwayEra => (ConwayEraOnwards era, AsType era) + sbe = convert ceo + options = def{cardanoNodeEra = AnyShelleyBasedEra sbe, cardanoEnableRpc = True} + addrInEra = AsAddressInEra eraProxy + + TestnetRuntime + { testnetNodes = node0 : _ + , wallets = wallet0@(PaymentKeyInfo _ addrTxt0) : (PaymentKeyInfo _ addrTxt1) : _ + } <- + createAndRunTestnet options def conf + + rpcSocket <- H.note . unFile $ nodeRpcSocketPath node0 + + -- prepare tx inputs and output address + H.noteShow_ addrTxt0 + addr0 <- H.nothingFail $ deserialiseAddress addrInEra addrTxt0 + + H.noteShow_ addrTxt1 + addr1 <- H.nothingFail $ deserialiseAddress addrInEra addrTxt1 + + -- read key witnesses + wit0 :: ShelleyWitnessSigningKey <- + H.leftFailM . H.evalIO $ + readFileTextEnvelopeAnyOf + [FromSomeType asType WitnessGenesisUTxOKey] + (signingKey $ paymentKeyInfoPair wallet0) + + -------------- + -- RPC queries + -------------- + let rpcServer = Rpc.ServerUnix rpcSocket + (pparamsResponse, utxosResponse) <- H.noteShowM . H.evalIO . Rpc.withConnection def rpcServer $ \conn -> do + pparams' <- do + let req = def + Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf UtxoRpc.QueryService "readParams")) req + + utxos' <- do + let req = def -- & #keys .~ [T.encodeUtf8 addrTxt0] + Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf UtxoRpc.QueryService "readUtxos")) req + pure (pparams', utxos') + + pparams <- H.leftFail $ utxoRpcPParamsToProtocolParams (convert ceo) $ pparamsResponse ^. #values . #cardano + + txOut0 : _ <- H.noteShowM . flip filterM (utxosResponse ^. #items) $ \utxo -> do + utxoAddress <- deserialiseAddressBs addrInEra $ utxo ^. #cardano . #address + pure $ addr0 == utxoAddress + txIn0 <- txoRefToTxIn $ txOut0 ^. #txoRef + + outputCoin <- H.leftFail $ txOut0 ^. #cardano . #coin . to utxoRpcBigIntToInteger + let amount = 200_000_000 + fee = 500 + change = outputCoin - amount - fee + txOut = TxOut addr1 (lovelaceToTxOutValue sbe $ L.Coin amount) TxOutDatumNone ReferenceScriptNone + changeTxOut = TxOut addr0 (lovelaceToTxOutValue sbe $ L.Coin change) TxOutDatumNone ReferenceScriptNone + content = + defaultTxBodyContent sbe + & setTxIns [(txIn0, pure $ KeyWitness KeyWitnessForSpending)] + & setTxFee (TxFeeExplicit sbe 500) + & setTxOuts [txOut, changeTxOut] + & setTxProtocolParams (pure . pure $ LedgerProtocolParameters pparams) + + txBody <- H.leftFail $ createTransactionBody sbe content + + let signedTx = signShelleyTransaction sbe txBody [wit0] + txId' <- H.noteShow . getTxId $ getTxBody signedTx + + H.noteShowPretty_ utxosResponse + + (utxos, submitResponse) <- H.noteShowM . H.evalIO . Rpc.withConnection def rpcServer $ \conn -> do + submitResponse <- + Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf UtxoRpc.SubmitService "submitTx")) $ + def & #tx .~ (def & #raw .~ serialiseToCBOR signedTx) + + fix $ \loop -> do + resp <- Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf UtxoRpc.QueryService "readParams")) def + + let previousBlockNo = pparamsResponse ^. #ledgerTip . #height + currentBlockNo = resp ^. #ledgerTip . #height + -- wait for 2 blocks + when (previousBlockNo + 1 >= currentBlockNo) $ do + threadDelay 500_000 + loop + + utxos <- + Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf UtxoRpc.QueryService "readUtxos")) def -- & #keys .~ [T.encodeUtf8 addrTxt1] + pure (utxos, submitResponse) + + submittedTxId <- H.leftFail . deserialiseFromRawBytes AsTxId $ submitResponse ^. #ref + + H.note_ "Ensure that submitted transaction ID is in the submitted transactions list" + txId' === submittedTxId + + H.note_ $ "Enxure that there are 2 UTXOs in the address " <> show addrTxt1 + utxosForAddress <- H.noteShowM . flip filterM (utxos ^. #items) $ \utxo -> do + utxoAddress <- deserialiseAddressBs addrInEra $ utxo ^. #cardano . #address + pure $ addr1 == utxoAddress + 2 === length utxosForAddress + + let outputsAmounts = map (^. #cardano . #coin) $ utxos ^. #items + H.note_ $ "Ensure that the output sent is one of the utxos for the address " <> show addrTxt1 + H.assertWith outputsAmounts $ elem (inject amount) + +txoRefToTxIn :: (HasCallStack, MonadTest m) => Proto UtxoRpc.TxoRef -> m TxIn +txoRefToTxIn r = withFrozenCallStack $ do + txId' <- H.leftFail $ deserialiseFromRawBytes AsTxId $ r ^. #hash + pure $ TxIn txId' (TxIx . fromIntegral $ r ^. #index) + +deserialiseAddressBs :: (MonadTest m, SerialiseAddress c) => AsType c -> ByteString -> m c +deserialiseAddressBs addrInEra = H.nothingFail . deserialiseAddress addrInEra <=< H.leftFail . T.decodeUtf8' diff --git a/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs b/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs index a142563b4ae..e6b2f7bb838 100644 --- a/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs +++ b/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs @@ -29,6 +29,8 @@ import qualified Cardano.Testnet.Test.Gov.TreasuryDonation as Gov import qualified Cardano.Testnet.Test.Gov.TreasuryWithdrawal as Gov import qualified Cardano.Testnet.Test.MainnetParams import qualified Cardano.Testnet.Test.Node.Shutdown +import qualified Cardano.Testnet.Test.Rpc.Query +import qualified Cardano.Testnet.Test.Rpc.Transaction import qualified Cardano.Testnet.Test.RunTestnet import qualified Cardano.Testnet.Test.SanityCheck as LedgerEvents import qualified Cardano.Testnet.Test.SubmitApi.Transaction @@ -124,6 +126,10 @@ tests = do , T.testGroup "SubmitApi" [ ignoreOnMacAndWindows "transaction" Cardano.Testnet.Test.SubmitApi.Transaction.hprop_transaction ] + , T.testGroup "RPC" + [ ignoreOnWindows "RPC Query Protocol Params" Cardano.Testnet.Test.Rpc.Query.hprop_rpc_query_pparams + , ignoreOnWindows "RPC Transaction Submit" Cardano.Testnet.Test.Rpc.Transaction.hprop_rpc_transaction + ] ] main :: IO () diff --git a/cardano-testnet/test/cardano-testnet-test/files/calculatePlutusScriptCost.json b/cardano-testnet/test/cardano-testnet-test/files/calculatePlutusScriptCost.json index 98a7e4de9bd..b2d1ef63f2c 100644 --- a/cardano-testnet/test/cardano-testnet-test/files/calculatePlutusScriptCost.json +++ b/cardano-testnet/test/cardano-testnet-test/files/calculatePlutusScriptCost.json @@ -7,4 +7,4 @@ "lovelaceCost": 34, "scriptHash": "186e32faa80a26810392fda6d559c7ed4721a65ce1c9d4ef3e1c87b4" } -] \ No newline at end of file +] diff --git a/nix/haskell.nix b/nix/haskell.nix index 15650924eff..f3ad2f91428 100644 --- a/nix/haskell.nix +++ b/nix/haskell.nix @@ -308,7 +308,11 @@ let # also needs them to be quoted) export WORKDIR=$TMP/testTracerExt ''; - }) + }) + ({pkgs, ...}: { + packages.proto-lens-protobuf-types.components.library.build-tools = [ pkgs.protobuf ]; + packages.cardano-rpc.components.library.build-tools = [ pkgs.protobuf ]; + }) ({ lib, pkgs, ... }: lib.mkIf (!pkgs.stdenv.hostPlatform.isDarwin) { # Needed for profiled builds to fix an issue loading recursion-schemes part of makeBaseFunctor # that is missing from the `_p` output. See https://gitlab.haskell.org/ghc/ghc/-/issues/18320 diff --git a/scripts-demo/fund.sh b/scripts-demo/fund.sh new file mode 100755 index 00000000000..cf06e62bcab --- /dev/null +++ b/scripts-demo/fund.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Example usage: +# ./scripts-demo/fund.sh addr_test1vq922scgdwrrfa3n2pzu3empkju9ekregg0tza0xnveya3gfl0ycn 1000000000 + +set -euo pipefail + +trap 'rm -f funding.txbody' EXIT +rm -f funding.txbody funding.tx + +run_cardano_cli() { + cabal run -v0 cardano-cli -- "$@" +} + +export CARDANO_NODE_SOCKET_PATH=./testnet-data/socket/node1/sock +export CARDANO_NODE_NETWORK_ID=42 +SRC_ADDR=$(cat testnet-data/utxo-keys/utxo1/utxo.addr) +SRC_UTXO=$(run_cardano_cli latest query utxo --address "$SRC_ADDR" | jq -r 'keys[0]') +run_cardano_cli latest transaction build --tx-in "$SRC_UTXO" --tx-out "$1+$2" --change-address "$SRC_ADDR" --out-file funding.txbody +run_cardano_cli latest transaction sign --tx-body-file funding.txbody --signing-key-file testnet-data/utxo-keys/utxo1/utxo.skey --out-file funding.tx +run_cardano_cli latest transaction submit --tx-file funding.tx +rm -f funding.txbody funding.tx diff --git a/scripts-demo/start-testnet.sh b/scripts-demo/start-testnet.sh new file mode 100755 index 00000000000..b889902cfad --- /dev/null +++ b/scripts-demo/start-testnet.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +rm -fr ./testnet-data +rm -fr ../cardano-api/rpc.socket +cabal build cardano-node cardano-cli cardano-testnet +( + tries=0 + while [ $tries -lt 60 ]; do + if [ -S ./testnet-data/socket/node1/rpc.sock ]; then + break + fi + sleep 1 + tries=$((tries + 1)) + done + + if [ $tries -eq 60 ]; then + echo "Timeout: Socket not found in 60 seconds." >&2 + exit 1 + fi + + ln -sf ./testnet-data/socket/node1/rpc.sock ../cardano-api/rpc.socket +) & cabal run cardano-testnet -- cardano --testnet-magic 42 --enable-grpc --output-dir ./testnet-data