diff --git a/cardano-node/src/Cardano/Node/Run.hs b/cardano-node/src/Cardano/Node/Run.hs index 5298d926c9d..8a142dc83d5 100644 --- a/cardano-node/src/Cardano/Node/Run.hs +++ b/cardano-node/src/Cardano/Node/Run.hs @@ -57,6 +57,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.API @@ -541,6 +542,7 @@ handleSimpleNode blockType runP tracers nc 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 { @@ -573,7 +575,7 @@ handleSimpleNode blockType runP tracers nc 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 @@ -583,6 +585,7 @@ handleSimpleNode blockType runP tracers nc networkMagic onKernel = do useBootstrapVar ledgerPeerSnapshotPathVar ledgerPeerSnapshotVar rpcConfigVar rnNodeKernelHook nodeArgs registry nodeKernel + writeIORef nodeKernelAccessRef . Just $ mkNodeKernelAccess blockType nodeKernel } StdRunNodeArgs { srnBfcMaxConcurrencyBulkSync = unMaxConcurrencyBulkSync <$> ncMaxConcurrencyBulkSync nc @@ -858,8 +861,9 @@ rpcServerLoop :: Tracer IO (StartupTrace blk) -> Tracer IO TraceRpc -> StrictTVar IO RpcConfig -> NetworkMagic + -> IORef (Maybe NodeKernelAccess) -> IO () -rpcServerLoop startupTracer rpcTracer rpcConfigVar networkMagic = go +rpcServerLoop startupTracer rpcTracer rpcConfigVar networkMagic nodeKernelAccessRef = go where go = do config@RpcConfig{isEnabled = Identity enabled} <- readTVarIO rpcConfigVar @@ -867,7 +871,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) 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 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