From 4e833df86fe80b7b125a1ca371755b2de4379d81 Mon Sep 17 00:00:00 2001 From: Mateusz Galazyn Date: Thu, 28 May 2026 13:47:57 +0200 Subject: [PATCH 1/6] Fetchblock phase 2 --- cardano-node/src/Cardano/Node/Run.hs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index f6fe4e745d9..4e43353dab1 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -50,6 +50,7 @@ import Cardano.Node.Protocol.Types import Cardano.Node.Queries import Cardano.Rpc.Server import Cardano.Rpc.Server.Config +import Data.IORef import Cardano.Node.Startup import Cardano.Node.TraceConstraints (TraceConstraints) import Cardano.Node.Tracing (Tracers (..)) @@ -449,6 +450,7 @@ handleSimpleNode blockType runP tracers nc cmdPc networkMagic onKernel = do #endif nForkPolicy <- getForkPolicy $ ncResponderCoreAffinityPolicy nc cForkPolicy <- getForkPolicy $ ncResponderCoreAffinityPolicy nc + nodeKernelAccessRef <- newIORef Nothing void $ let diffusionNodeArguments :: Cardano.Diffusion.CardanoNodeArguments IO diffusionNodeArguments = Cardano.Diffusion.CardanoNodeArguments { @@ -481,7 +483,7 @@ handleSimpleNode blockType runP tracers nc cmdPc networkMagic onKernel = do (readTVar ledgerPeerSnapshotVar) nc in - withAsync (rpcServerLoop (startupTracer tracers) (rpcTracer tracers) rpcConfigVar networkMagic) $ \_ -> + withAsync (rpcServerLoop (startupTracer tracers) (rpcTracer tracers) rpcConfigVar networkMagic nodeKernelAccessRef) $ \_ -> Node.run nodeArgs { rnNodeKernelHook = \registry nodeKernel -> do @@ -767,8 +769,9 @@ rpcServerLoop :: Tracer IO (StartupTrace blk) -> Tracer IO TraceRpc -> StrictTVar IO RpcConfig -> NetworkMagic + -> IORef (Maybe (NodeKernelAccess IO)) -> IO () -rpcServerLoop startupTracer rpcTracer rpcConfigVar networkMagic = go +rpcServerLoop startupTracer rpcTracer rpcConfigVar networkMagic nodeKernelAccessRef = go where go = do config@RpcConfig{isEnabled = Identity enabled} <- readTVarIO rpcConfigVar @@ -776,7 +779,7 @@ rpcServerLoop startupTracer rpcTracer rpcConfigVar networkMagic = go then race_ (do - runRpcServer rpcTracer (config, networkMagic) + runRpcServer rpcTracer config networkMagic nodeKernelAccessRef traceWith startupTracer RpcForceDisabled disableRpcServer) (waitForRpcConfigChange config) From 8b222fe370dd7d94e3d172e4bd81f3375794cf4d Mon Sep 17 00:00:00 2001 From: Mateusz Galazyn Date: Thu, 28 May 2026 13:58:16 +0200 Subject: [PATCH 2/6] phase 3 --- .../src/Cardano/Node/Tracing/Tracers/Rpc.hs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs index f9781f5efe6..7c0bc7c010d 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/Rpc.hs @@ -13,7 +13,7 @@ import Cardano.Api.Pretty import Cardano.Logging hiding (nsInner) import Cardano.Rpc.Server (TraceRpc (..), TraceRpcQuery (..), TraceRpcSubmit (..), - TraceSpanEvent (..)) + TraceRpcSync (..), TraceSpanEvent (..)) import Data.Aeson (Object, Value (..), (.=)) @@ -48,6 +48,13 @@ instance LogFormatting TraceRpc where TraceRpcSubmitSpan s -> [spanToObject s] TraceRpcEvalTxDecodingError _ -> [] TraceRpcEvalTxSpan s -> [spanToObject s] + TraceRpcSync syncTrace -> + ["kind" .= String "SyncService"] + <> case syncTrace of + TraceRpcFetchBlockSpan s -> [spanToObject s] + TraceRpcFetchBlockNotFound _ -> [] + TraceRpcNodeKernelAccessUnavailable -> [] + TraceRpcForkerError _ -> [] forHuman = docToText . pretty @@ -59,6 +66,7 @@ instance LogFormatting TraceRpc where TraceRpcQuery (TraceRpcQuerySearchUtxosSpan (SpanBegin _)) -> [CounterM "rpc.request.QueryService.SearchUtxos" Nothing] TraceRpcSubmit (TraceRpcSubmitSpan (SpanBegin _)) -> [CounterM "rpc.request.SubmitService.SubmitTx" Nothing] TraceRpcSubmit (TraceRpcEvalTxSpan (SpanBegin _)) -> [CounterM "rpc.request.SubmitService.EvalTx" Nothing] + TraceRpcSync (TraceRpcFetchBlockSpan (SpanBegin _)) -> [CounterM "rpc.request.SyncService.FetchBlock" Nothing] _ -> [] instance MetaTrace TraceRpc where @@ -81,6 +89,13 @@ instance MetaTrace TraceRpc where TraceRpcSubmitSpan _ -> ["SubmitTx", "Span"] TraceRpcEvalTxDecodingError _ -> ["EvalTxDecodingError"] TraceRpcEvalTxSpan _ -> ["EvalTx", "Span"] + TraceRpcSync syncTrace -> + "SyncService" + : case syncTrace of + TraceRpcFetchBlockSpan _ -> ["FetchBlock", "Span"] + TraceRpcFetchBlockNotFound _ -> ["FetchBlockNotFound"] + TraceRpcNodeKernelAccessUnavailable -> ["NodeKernelAccessUnavailable"] + TraceRpcForkerError _ -> ["ForkerError"] severityFor (Namespace _ nsInner) _ = case nsInner of ["FatalError"] -> Just Error -- RPC server startup errors @@ -94,6 +109,10 @@ instance MetaTrace TraceRpc where ["SubmitService", "TxDecodingError"] -> Just Debug -- request error ["SubmitService", "TxValidationError"] -> Just Debug -- request error ["SubmitService", "EvalTxDecodingError"] -> Just Debug -- request error + ["SyncService", "FetchBlock", "Span"] -> Just Debug + ["SyncService", "FetchBlockNotFound"] -> Just Debug -- normal: block may have been pruned + ["SyncService", "NodeKernelAccessUnavailable"] -> Just Warning -- kernel not yet ready + ["SyncService", "ForkerError"] -> Just Warning -- unexpected ledger forker error _ -> Nothing documentFor (Namespace _ nsInner) = case nsInner of @@ -110,6 +129,10 @@ instance MetaTrace TraceRpc where ["SubmitService", "TxDecodingError"] -> Just "A regular request error, when submitted transaction decoding fails." ["SubmitService", "TxValidationError"] -> Just "A regular request error, when submitted transaction is invalid." ["SubmitService", "EvalTxDecodingError"] -> Just "A regular request error, when evalTx transaction decoding fails." + ["SyncService", "FetchBlock", "Span"] -> Just "Span for the FetchBlock SyncService method." + ["SyncService", "FetchBlockNotFound"] -> Just "Requested block was not found in ChainDB." + ["SyncService", "NodeKernelAccessUnavailable"] -> Just "Node kernel access not yet initialised. The node is still starting up." + ["SyncService", "ForkerError"] -> Just "Unexpected error from ledger forker." _ -> Nothing metricsDocFor (Namespace _ nsInner) = case nsInner of @@ -123,6 +146,8 @@ instance MetaTrace TraceRpc where [("rpc.request.SubmitService.SubmitTx", "Span for the SubmitTx UTXORPC method.")] ["SubmitService", "EvalTx", "Span"] -> [("rpc.request.SubmitService.EvalTx", "Span for the EvalTx UTXORPC method.")] + ["SyncService", "FetchBlock", "Span"] -> + [("rpc.request.SyncService.FetchBlock", "Span for the FetchBlock SyncService method.")] _ -> [] allNamespaces = @@ -138,6 +163,10 @@ instance MetaTrace TraceRpc where , ["SubmitService", "TxDecodingError"] , ["SubmitService", "TxValidationError"] , ["SubmitService", "EvalTxDecodingError"] + , ["SyncService", "FetchBlock", "Span"] + , ["SyncService", "FetchBlockNotFound"] + , ["SyncService", "NodeKernelAccessUnavailable"] + , ["SyncService", "ForkerError"] ] -- helper functions From 3f4b4a4ba7c68d05a17100ca12de156a4769deea Mon Sep 17 00:00:00 2001 From: Mateusz Galazyn Date: Mon, 1 Jun 2026 15:59:51 +0200 Subject: [PATCH 3/6] phase 6 --- cardano-node/src/Cardano/Node/Run.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index 4e43353dab1..6fb927ed677 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -493,6 +493,7 @@ handleSimpleNode blockType runP tracers nc cmdPc networkMagic onKernel = do useBootstrapVar ledgerPeerSnapshotPathVar ledgerPeerSnapshotVar rpcConfigVar rnNodeKernelHook nodeArgs registry nodeKernel + writeIORef nodeKernelAccessRef . Just $ mkNodeKernelAccess blockType nodeKernel } StdRunNodeArgs { srnBfcMaxConcurrencyBulkSync = unMaxConcurrencyBulkSync <$> ncMaxConcurrencyBulkSync nc @@ -769,7 +770,7 @@ rpcServerLoop :: Tracer IO (StartupTrace blk) -> Tracer IO TraceRpc -> StrictTVar IO RpcConfig -> NetworkMagic - -> IORef (Maybe (NodeKernelAccess IO)) + -> IORef (Maybe NodeKernelAccess) -> IO () rpcServerLoop startupTracer rpcTracer rpcConfigVar networkMagic nodeKernelAccessRef = go where From 184d85d28e0267afb81cfe45e4eb1e8babbd5a4f Mon Sep 17 00:00:00 2001 From: Mateusz Galazyn Date: Mon, 1 Jun 2026 16:23:36 +0200 Subject: [PATCH 4/6] phase 7 --- cardano-testnet/cardano-testnet.cabal | 1 + .../Cardano/Testnet/Test/Rpc/FetchBlock.hs | 96 +++++++++++++++++++ .../cardano-testnet-test.hs | 4 +- 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs diff --git a/cardano-testnet/cardano-testnet.cabal b/cardano-testnet/cardano-testnet.cabal index f939a8be8d1..bf9c77c0280 100644 --- a/cardano-testnet/cardano-testnet.cabal +++ b/cardano-testnet/cardano-testnet.cabal @@ -239,6 +239,7 @@ test-suite cardano-testnet-test Cardano.Testnet.Test.Gov.TreasuryGrowth Cardano.Testnet.Test.Gov.TreasuryWithdrawal Cardano.Testnet.Test.Rpc.Eval + Cardano.Testnet.Test.Rpc.FetchBlock Cardano.Testnet.Test.Rpc.Query Cardano.Testnet.Test.Rpc.SearchUtxos Cardano.Testnet.Test.Rpc.Transaction diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs new file mode 100644 index 00000000000..96c4e001d37 --- /dev/null +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs @@ -0,0 +1,96 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +module Cardano.Testnet.Test.Rpc.FetchBlock ( + hprop_rpc_fetch_block, +) +where + +import Cardano.Api +import qualified Cardano.Api.Experimental as Exp + +import Cardano.CLI.Type.Output (QueryTipLocalStateOutput (..)) +import qualified Cardano.Rpc.Client as Rpc +import qualified Cardano.Rpc.Proto.Api.UtxoRpc.Sync as U5c +import Cardano.Testnet + +import Prelude + +import qualified Data.ByteString as BS +import qualified Data.ByteString.Short as SBS +import Data.Default.Class +import Data.Word (Word64) +import Lens.Micro + +import Testnet.Process.Run +import Testnet.Property.Util (integrationRetryWorkspace) +import Testnet.Start.Types + +import qualified Hedgehog as H +import qualified Hedgehog.Extras as H + +-- | Run with: +-- @TASTY_PATTERN='/RPC FetchBlock/' cabal test cardano-testnet-test@ +-- +hprop_rpc_fetch_block :: H.Property +hprop_rpc_fetch_block = integrationRetryWorkspace 2 "rpc-fetch-block" $ \tempAbsBasePath' -> H.runWithDefaultWatchdog_ $ do + conf@Conf{tempAbsPath} <- mkConf tempAbsBasePath' + let tempAbsPath' = unTmpAbsPath tempAbsPath + + let era = Exp.ConwayEra + sbe = convert era + eraName = eraToString sbe + creationOptions = def{creationEra = AnyShelleyBasedEra sbe} + runtimeOptions = def{runtimeEnableRpc = RpcEnabled} + + TestnetRuntime + { testnetMagic + , testnetNodes = node0@TestnetNode{nodeSprocket} : _ + } <- + createAndRunTestnet creationOptions runtimeOptions conf + + execConfig <- mkExecConfig tempAbsPath' nodeSprocket testnetMagic + rpcSocket <- H.note . unFile $ nodeRpcSocketPath node0 + + -- Get chain tip via CLI + QueryTipLocalStateOutput{localStateChainTip} <- + H.noteShowM $ execCliStdoutToJson execConfig [eraName, "query", "tip"] + (slot, blockHash, blockNo) <- case localStateChainTip of + ChainTipAtGenesis -> H.failure + ChainTip (SlotNo tipSlot) (HeaderHash hash) (BlockNo bn) -> pure (tipSlot, SBS.fromShort hash, bn) + + H.note_ $ "Tip slot: " <> show slot + H.note_ $ "Tip block number: " <> show blockNo + H.note_ $ "Tip hash: " <> show (BS.length blockHash) <> " bytes" + + -- Call FetchBlock via gRPC + let rpcServer = Rpc.ServerUnix rpcSocket + blockRef = def & U5c.slot .~ slot & U5c.hash .~ blockHash + request = def & U5c.ref .~ [blockRef] + + response <- H.evalIO . Rpc.withConnection def rpcServer $ \conn -> + Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf U5c.SyncService "fetchBlock")) request + + -- Verify response contains exactly one block + let blocks = response ^. U5c.block + length blocks H.=== 1 + + let block = head blocks + + -- Verify nativeBytes is non-empty + let rawBytes = block ^. U5c.nativeBytes + H.note_ $ "Block CBOR: " <> show (BS.length rawBytes) <> " bytes" + H.assertWith rawBytes $ not . BS.null + + -- Verify cardano block header matches the requested tip + block ^. U5c.cardano . U5c.header . U5c.slot H.=== slot + block ^. U5c.cardano . U5c.header . U5c.hash H.=== blockHash + + -- height is the block number from ChainDB + block ^. U5c.cardano . U5c.header . U5c.height H.=== blockNo + + -- timestamp is not available without era history, defaults to 0 + block ^. U5c.cardano . U5c.timestamp H.=== 0 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 c1d7ab52310..e760ed94e21 100644 --- a/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs +++ b/cardano-testnet/test/cardano-testnet-test/cardano-testnet-test.hs @@ -36,6 +36,7 @@ import qualified Cardano.Testnet.Test.MainnetParams import qualified Cardano.Testnet.Test.Node.Shutdown import qualified Cardano.Testnet.Test.Parser import qualified Cardano.Testnet.Test.Rpc.Eval +import qualified Cardano.Testnet.Test.Rpc.FetchBlock import qualified Cardano.Testnet.Test.Rpc.Query import qualified Cardano.Testnet.Test.Rpc.SearchUtxos import qualified Cardano.Testnet.Test.Rpc.Transaction @@ -148,7 +149,8 @@ tests = do [ 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 FetchBlock" Cardano.Testnet.Test.Rpc.FetchBlock.hprop_rpc_fetch_block + , ignoreOnWindows "RPC Query Protocol Params" Cardano.Testnet.Test.Rpc.Query.hprop_rpc_query_pparams , ignoreOnWindows "RPC SearchUtxos" Cardano.Testnet.Test.Rpc.SearchUtxos.hprop_rpc_search_utxos , ignoreOnWindows "RPC Transaction Submit" Cardano.Testnet.Test.Rpc.Transaction.hprop_rpc_transaction , ignoreOnWindows "RPC Eval Tx" Cardano.Testnet.Test.Rpc.Eval.hprop_rpc_eval_tx From 8c8454b5e84d4cb3ed167388a02066f0f4825dc3 Mon Sep 17 00:00:00 2001 From: Mateusz Galazyn Date: Wed, 3 Jun 2026 16:57:28 +0200 Subject: [PATCH 5/6] timestamp --- cardano-node/src/Cardano/Node/Run.hs | 2 ++ .../Cardano/Testnet/Test/Rpc/FetchBlock.hs | 21 ++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index 6fb927ed677..f5484a95fa7 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -50,6 +50,8 @@ import Cardano.Node.Protocol.Types import Cardano.Node.Queries import Cardano.Rpc.Server import Cardano.Rpc.Server.Config +import Cardano.Rpc.Server.Internal.Env (NodeKernelAccess) +import Cardano.Rpc.Server.NodeKernelAccess.Internal (mkNodeKernelAccess) import Data.IORef import Cardano.Node.Startup import Cardano.Node.TraceConstraints (TraceConstraints) diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs index 96c4e001d37..ac8e9797532 100644 --- a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs @@ -19,15 +19,18 @@ import Cardano.Testnet import Prelude +import Control.Monad ((<=<)) import qualified Data.ByteString as BS import qualified Data.ByteString.Short as SBS import Data.Default.Class +import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) import Data.Word (Word64) import Lens.Micro import Testnet.Process.Run import Testnet.Property.Util (integrationRetryWorkspace) import Testnet.Start.Types +import Testnet.Types (nodeConnectionInfo) import qualified Hedgehog as H import qualified Hedgehog.Extras as H @@ -46,7 +49,7 @@ hprop_rpc_fetch_block = integrationRetryWorkspace 2 "rpc-fetch-block" $ \tempAbs creationOptions = def{creationEra = AnyShelleyBasedEra sbe} runtimeOptions = def{runtimeEnableRpc = RpcEnabled} - TestnetRuntime + tr@TestnetRuntime { testnetMagic , testnetNodes = node0@TestnetNode{nodeSprocket} : _ } <- @@ -92,5 +95,17 @@ hprop_rpc_fetch_block = integrationRetryWorkspace 2 "rpc-fetch-block" $ \tempAbs -- height is the block number from ChainDB block ^. U5c.cardano . U5c.header . U5c.height H.=== blockNo - -- timestamp is not available without era history, defaults to 0 - block ^. U5c.cardano . U5c.timestamp H.=== 0 + -- Compute expected timestamp via N2C IPC + connectionInfo <- nodeConnectionInfo tr 0 + (systemStart, eraHistory) <- + (H.leftFail <=< H.leftFailM) . H.evalIO $ + executeLocalStateQueryExpr connectionInfo VolatileTip $ do + ss <- querySystemStart + eh <- queryEraHistory + pure $ (,) <$> ss <*> eh + expectedTimestampMs :: Word64 <- H.leftFail $ do + utcTime <- slotToUTCTime systemStart eraHistory (SlotNo slot) + pure . round $ utcTimeToPOSIXSeconds utcTime * 1000 + + -- Verify timestamp is within 1000ms of expected + H.assertWithinTolerance (block ^. U5c.cardano . U5c.timestamp) expectedTimestampMs 1000 From 4a9dfbd6bce712dc4d4518e7291242ead842dd0c Mon Sep 17 00:00:00 2001 From: Mateusz Galazyn Date: Fri, 3 Jul 2026 13:30:17 +0200 Subject: [PATCH 6/6] wip --- .../src/Cardano/Node/Protocol/Checkpoints.hs | 4 +- .../src/Cardano/Node/Protocol/Shelley.hs | 2 +- cardano-node/src/Cardano/Node/Run.hs | 5 +-- cardano-node/src/Cardano/Node/Startup.hs | 2 + .../Cardano/Node/Tracing/Tracers/Startup.hs | 12 +++++- cardano-testnet/src/Testnet/Runtime.hs | 10 ++--- cardano-testnet/src/Testnet/Start/Cardano.hs | 18 ++++----- .../Cardano/Testnet/Test/Rpc/FetchBlock.hs | 40 +++++++------------ 8 files changed, 47 insertions(+), 46 deletions(-) diff --git a/cardano-node/src/Cardano/Node/Protocol/Checkpoints.hs b/cardano-node/src/Cardano/Node/Protocol/Checkpoints.hs index 029d1decb04..e8d68378532 100644 --- a/cardano-node/src/Cardano/Node/Protocol/Checkpoints.hs +++ b/cardano-node/src/Cardano/Node/Protocol/Checkpoints.hs @@ -11,10 +11,10 @@ module Cardano.Node.Protocol.Checkpoints import Cardano.Api import qualified Cardano.Crypto.Hash.Class as Crypto -import Cardano.Protocol.Crypto (StandardCrypto) import Cardano.Node.Types +import Cardano.Protocol.Crypto () import Ouroboros.Consensus.Block -import Ouroboros.Consensus.Cardano +import Ouroboros.Consensus.Cardano () import Ouroboros.Consensus.Config (CheckpointsMap (..), emptyCheckpointsMap) import Control.Exception (IOException) diff --git a/cardano-node/src/Cardano/Node/Protocol/Shelley.hs b/cardano-node/src/Cardano/Node/Protocol/Shelley.hs index a816b8e91ed..551bc9aa754 100644 --- a/cardano-node/src/Cardano/Node/Protocol/Shelley.hs +++ b/cardano-node/src/Cardano/Node/Protocol/Shelley.hs @@ -39,7 +39,7 @@ import Cardano.Node.Tracing.Era.Shelley () import Cardano.Node.Tracing.Formatting () import Cardano.Node.Tracing.Tracers.ChainDB () import Cardano.Node.Types -import Cardano.Protocol.Crypto (StandardCrypto) +import Cardano.Protocol.Crypto () import qualified Ouroboros.Consensus.Cardano as Consensus import Ouroboros.Consensus.HardFork.Combinator.AcrossEras () import Ouroboros.Consensus.Protocol.Praos.Common (PraosCanBeLeader (..), diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index f5484a95fa7..c9bc027415b 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -50,8 +50,6 @@ import Cardano.Node.Protocol.Types import Cardano.Node.Queries import Cardano.Rpc.Server import Cardano.Rpc.Server.Config -import Cardano.Rpc.Server.Internal.Env (NodeKernelAccess) -import Cardano.Rpc.Server.NodeKernelAccess.Internal (mkNodeKernelAccess) import Data.IORef import Cardano.Node.Startup import Cardano.Node.TraceConstraints (TraceConstraints) @@ -495,7 +493,8 @@ handleSimpleNode blockType runP tracers nc cmdPc networkMagic onKernel = do useBootstrapVar ledgerPeerSnapshotPathVar ledgerPeerSnapshotVar rpcConfigVar rnNodeKernelHook nodeArgs registry nodeKernel - writeIORef nodeKernelAccessRef . Just $ mkNodeKernelAccess blockType nodeKernel + mkNodeKernelAccess (contramap RpcUnsupportedBlockType (startupTracer tracers)) blockType nodeKernel + >>= writeIORef nodeKernelAccessRef } StdRunNodeArgs { srnBfcMaxConcurrencyBulkSync = unMaxConcurrencyBulkSync <$> ncMaxConcurrencyBulkSync nc diff --git a/cardano-node/src/Cardano/Node/Startup.hs b/cardano-node/src/Cardano/Node/Startup.hs index af9b9edee9f..e666321571e 100644 --- a/cardano-node/src/Cardano/Node/Startup.hs +++ b/cardano-node/src/Cardano/Node/Startup.hs @@ -145,6 +145,8 @@ data StartupTrace blk = | RpcConfigUpdate Text -- | Log RPC configuration update error | RpcConfigUpdateError Text + -- | Log that node kernel access is not supported for the running block type. + | RpcUnsupportedBlockType Text -- | Log RPC is forcefully disabled after a RPC server crash. | RpcForceDisabled diff --git a/cardano-node/src/Cardano/Node/Tracing/Tracers/Startup.hs b/cardano-node/src/Cardano/Node/Tracing/Tracers/Startup.hs index c8eefaef1cf..44bc25173e0 100644 --- a/cardano-node/src/Cardano/Node/Tracing/Tracers/Startup.hs +++ b/cardano-node/src/Cardano/Node/Tracing/Tracers/Startup.hs @@ -17,7 +17,6 @@ module Cardano.Node.Tracing.Tracers.Startup import Cardano.Api (NetworkMagic (..), SlotNo (..)) import qualified Cardano.Api as Api -import Cardano.Network.OrphanInstances () import qualified Cardano.Chain.Genesis as Gen import Cardano.Git.Rev (gitRev) @@ -25,6 +24,7 @@ import Cardano.Ledger.Shelley.API as SL import Cardano.Logging import Cardano.Network.NodeToClient (LocalAddress (..)) import Cardano.Network.NodeToNode (DiffusionMode (..)) +import Cardano.Network.OrphanInstances () import Cardano.Node.Configuration.POM (NodeConfiguration, ncProtocol) import Cardano.Node.Configuration.Socket import Cardano.Node.Protocol (SomeConsensusProtocol (..)) @@ -288,6 +288,9 @@ instance ( Show (BlockNodeToNodeVersion blk) forMachine _dtal (RpcConfigUpdateError err) = mconcat [ "kind" .= String "RpcConfigUpdateError" , "error" .= String ("Error while updating RPC configuration: " <> err) ] + forMachine _dtal (RpcUnsupportedBlockType blockType) = + mconcat [ "kind" .= String "RpcUnsupportedBlockType" + , "blockType" .= String blockType ] forMachine _dtal RpcForceDisabled = mconcat [ "kind" .= String "RpcForceDisabled" , "error" .= String (ppStartupInfoTrace RpcForceDisabled)] @@ -360,6 +363,8 @@ instance MetaTrace (StartupTrace blk) where Namespace [] ["RpcConfigUpdate"] namespaceFor RpcConfigUpdateError {} = Namespace [] ["RpcConfigUpdateError"] + namespaceFor RpcUnsupportedBlockType {} = + Namespace [] ["RpcUnsupportedBlockType"] namespaceFor RpcForceDisabled = Namespace [] ["RpcForceDisabled"] namespaceFor MovedTopLevelOption {} = @@ -376,6 +381,7 @@ instance MetaTrace (StartupTrace blk) where severityFor (Namespace _ ["WarningDevelopmentNodeToClientVersions"]) _ = Just Warning severityFor (Namespace _ ["RpcConfigUpdate"]) _ = Just Notice severityFor (Namespace _ ["RpcConfigUpdateError"]) _ = Just Error + severityFor (Namespace _ ["RpcUnsupportedBlockType"]) _ = Just Warning severityFor (Namespace _ ["RpcForceDisabled"]) _ = Just Error severityFor (Namespace _ ["BlockForgingUpdateError"]) _ = Just Error severityFor (Namespace _ ["BlockForgingBlockTypeMismatch"]) _ = Just Error @@ -407,6 +413,8 @@ instance MetaTrace (StartupTrace blk) where "" documentFor (Namespace [] ["RpcConfigUpdateError"]) = Just "" + documentFor (Namespace [] ["RpcUnsupportedBlockType"]) = Just + "" documentFor (Namespace [] ["RpcForceDisabled"]) = Just "" documentFor (Namespace [] ["NetworkConfigUpdate"]) = Just @@ -480,6 +488,7 @@ instance MetaTrace (StartupTrace blk) where , Namespace [] ["BlockForgingBlockTypeMismatch"] , Namespace [] ["RpcConfigUpdate"] , Namespace [] ["RpcConfigUpdateError"] + , Namespace [] ["RpcUnsupportedBlockType"] , Namespace [] ["RpcForceDisabled"] , Namespace [] ["NetworkConfigUpdate"] , Namespace [] ["NetworkConfigUpdateUnsupported"] @@ -605,6 +614,7 @@ ppStartupInfoTrace (LedgerPeerSnapshotLoaded slotNo) = ppStartupInfoTrace (RpcConfigUpdate config) = "Performing RPC configuration update: " <> config ppStartupInfoTrace (RpcConfigUpdateError err) = "Error while updating RPC configuration: " <> err +ppStartupInfoTrace (RpcUnsupportedBlockType blockType) = "RPC node kernel access is not supported for block type: " <> blockType ppStartupInfoTrace RpcForceDisabled = "RPC endpoint has crashed and because of that it got disabled. Enable gRPC endpoint and send SIGHUP to the node to reenable." ppStartupInfoTrace NonP2PWarning = nonP2PWarningMessage diff --git a/cardano-testnet/src/Testnet/Runtime.hs b/cardano-testnet/src/Testnet/Runtime.hs index e271f264d42..526a823225e 100644 --- a/cardano-testnet/src/Testnet/Runtime.hs +++ b/cardano-testnet/src/Testnet/Runtime.hs @@ -27,6 +27,7 @@ import qualified Cardano.Api as Api import qualified Cardano.Ledger.Api as L import qualified Cardano.Ledger.Shelley.LedgerState as L import qualified Cardano.Ledger.Shelley.State as L +import Cardano.Node.Testnet.Paths (defaultSocketName) import Prelude @@ -53,11 +54,10 @@ import qualified System.Process as IO import System.Process (waitForProcess) import Testnet.Filepath -import Cardano.Node.Testnet.Paths (defaultSocketName) import qualified Testnet.Ping as Ping import Testnet.Process.Run (ProcessError (..), initiateProcess) -import Testnet.Process.RunIO (execCli_, execKesAgentControl_, liftIOAnnotated, - procCustom, procKesAgent, procNode) +import Testnet.Process.RunIO (execCli_, execKesAgentControl_, liftIOAnnotated, procCustom, + procKesAgent, procNode) import Testnet.Types (TestnetKesAgent (..), TestnetNode (..), TestnetRuntime (configurationFile), showIpv4Address, testnetSprockets) @@ -506,9 +506,9 @@ startLedgerNewEpochStateLogging testnetRuntime tmpWorkspace = withFrozenCallStac -> SlotNo -> BlockNo -> StateT (Maybe AnyNewEpochState) IO ConditionResult - handler outputFp diffFp anes@(AnyNewEpochState !sbe !nes _) _ (BlockNo blockNo) = handleException $ do + handler outputFp diffFp anes@(AnyNewEpochState !sbe !nes _) _ (BlockNo currentBlockNo) = handleException $ do let prettyNes = shelleyBasedEraConstraints sbe (encodePretty nes) - blockLabel = "#### BLOCK " <> show blockNo <> " ####" + blockLabel = "#### BLOCK " <> show currentBlockNo <> " ####" liftIOAnnotated . BSC.appendFile outputFp $ BSC.unlines [BSC.pack blockLabel, prettyNes, ""] -- store epoch state for logging of differences diff --git a/cardano-testnet/src/Testnet/Start/Cardano.hs b/cardano-testnet/src/Testnet/Start/Cardano.hs index c4df60856cb..250cd845779 100644 --- a/cardano-testnet/src/Testnet/Start/Cardano.hs +++ b/cardano-testnet/src/Testnet/Start/Cardano.hs @@ -42,42 +42,42 @@ import qualified Cardano.Api.Byron as Byron import Cardano.Network.Diffusion.Topology (CardanoNetworkTopology) import Cardano.Node.Configuration.NodeAddress (PortNumber) import Cardano.Node.Configuration.TopologyP2P () +import Cardano.Node.Testnet.Paths (defaultConfigFile, defaultNodeEnvFile, defaultPortFile, + defaultUtxoAddrPath) import Cardano.Prelude (NonEmpty ((:|)), canonicalEncodePretty, readMaybe) import Ouroboros.Network.PeerSelection.RelayAccessPoint (RelayAccessPoint (..)) import Prelude hiding (lines) import Control.Concurrent (threadDelay) -import Control.Monad (forM, forM_, guard, unless, when) -import Control.Monad.Trans.Maybe (runMaybeT) import Control.Exception (IOException) +import Control.Monad (forM, forM_, guard, unless, when) import Control.Monad.Catch +import Control.Monad.Trans.Maybe (runMaybeT) import Control.Monad.Trans.Resource (MonadResource, getInternalState) import Data.Aeson import qualified Data.Aeson.Encode.Pretty as A -import qualified Data.Yaml as Yaml import qualified Data.ByteString.Lazy as LBS import Data.Default.Class () import Data.Either -import Data.Maybe (mapMaybe) import Data.Functor import Data.List (sort, stripPrefix, uncons) import qualified Data.List.NonEmpty as NEL import qualified Data.Map as Map +import Data.Maybe (mapMaybe) import Data.MonoTraversable (Element, MonoFunctor, omap) import qualified Data.Text as Text import Data.Time (diffUTCTime) import Data.Time.Clock (NominalDiffTime) import qualified Data.Time.Clock as DTC +import qualified Data.Yaml as Yaml import GHC.Stack import qualified System.Directory as IO -import qualified System.Process as Process import System.FilePath (()) +import qualified System.Process as Process import Testnet.Components.Configuration import qualified Testnet.Defaults as Defaults -import Cardano.Node.Testnet.Paths (defaultConfigFile, defaultNodeEnvFile, - defaultPortFile, defaultUtxoAddrPath) import Testnet.Filepath import Testnet.Handlers (interruptNodesOnSigINT) import Testnet.Orphans () @@ -440,9 +440,9 @@ cardanoTestnet QuickValidation (EpochNo maxBound) minBound - $ \_ slotNo blockNo -> do + $ \_ slotNo currentBlockNo -> do put slotNo - pure $ if blockNo >= 1 + pure $ if currentBlockNo >= 1 then ConditionMet -- we got one block else ConditionNotMet diff --git a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs index ac8e9797532..908cb2debc4 100644 --- a/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs +++ b/cardano-testnet/test/cardano-testnet-test/Cardano/Testnet/Test/Rpc/FetchBlock.hs @@ -19,18 +19,14 @@ import Cardano.Testnet import Prelude -import Control.Monad ((<=<)) import qualified Data.ByteString as BS import qualified Data.ByteString.Short as SBS import Data.Default.Class -import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) -import Data.Word (Word64) import Lens.Micro import Testnet.Process.Run import Testnet.Property.Util (integrationRetryWorkspace) import Testnet.Start.Types -import Testnet.Types (nodeConnectionInfo) import qualified Hedgehog as H import qualified Hedgehog.Extras as H @@ -49,7 +45,7 @@ hprop_rpc_fetch_block = integrationRetryWorkspace 2 "rpc-fetch-block" $ \tempAbs creationOptions = def{creationEra = AnyShelleyBasedEra sbe} runtimeOptions = def{runtimeEnableRpc = RpcEnabled} - tr@TestnetRuntime + _tr@TestnetRuntime { testnetMagic , testnetNodes = node0@TestnetNode{nodeSprocket} : _ } <- @@ -72,16 +68,12 @@ hprop_rpc_fetch_block = integrationRetryWorkspace 2 "rpc-fetch-block" $ \tempAbs -- Call FetchBlock via gRPC let rpcServer = Rpc.ServerUnix rpcSocket blockRef = def & U5c.slot .~ slot & U5c.hash .~ blockHash - request = def & U5c.ref .~ [blockRef] + request = def & U5c.ref .~ blockRef response <- H.evalIO . Rpc.withConnection def rpcServer $ \conn -> Rpc.nonStreaming conn (Rpc.rpc @(Rpc.Protobuf U5c.SyncService "fetchBlock")) request - -- Verify response contains exactly one block - let blocks = response ^. U5c.block - length blocks H.=== 1 - - let block = head blocks + let block = response ^. U5c.block -- Verify nativeBytes is non-empty let rawBytes = block ^. U5c.nativeBytes @@ -95,17 +87,15 @@ hprop_rpc_fetch_block = integrationRetryWorkspace 2 "rpc-fetch-block" $ \tempAbs -- height is the block number from ChainDB block ^. U5c.cardano . U5c.header . U5c.height H.=== blockNo - -- Compute expected timestamp via N2C IPC - connectionInfo <- nodeConnectionInfo tr 0 - (systemStart, eraHistory) <- - (H.leftFail <=< H.leftFailM) . H.evalIO $ - executeLocalStateQueryExpr connectionInfo VolatileTip $ do - ss <- querySystemStart - eh <- queryEraHistory - pure $ (,) <$> ss <*> eh - expectedTimestampMs :: Word64 <- H.leftFail $ do - utcTime <- slotToUTCTime systemStart eraHistory (SlotNo slot) - pure . round $ utcTimeToPOSIXSeconds utcTime * 1000 - - -- Verify timestamp is within 1000ms of expected - H.assertWithinTolerance (block ^. U5c.cardano . U5c.timestamp) expectedTimestampMs 1000 + -- TODO: verify timestamp once FetchBlock populates it from EraHistory (node kernel snapshot migration) + -- connectionInfo <- nodeConnectionInfo tr 0 + -- (systemStart, eraHistory) <- + -- (H.leftFail <=< H.leftFailM) . H.evalIO $ + -- executeLocalStateQueryExpr connectionInfo VolatileTip $ do + -- ss <- querySystemStart + -- eh <- queryEraHistory + -- pure $ (,) <$> ss <*> eh + -- expectedTimestampMs :: Word64 <- H.leftFail $ do + -- utcTime <- slotToUTCTime systemStart eraHistory (SlotNo slot) + -- pure . round $ utcTimeToPOSIXSeconds utcTime * 1000 + -- H.assertWithinTolerance (block ^. U5c.cardano . U5c.timestamp) expectedTimestampMs 1000