Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changes/20260828_cardano_rpc_grpc_tcp_listener.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
project: cardano-rpc
pr: 1322
kind:
- feature
- breaking
description: |
The cardano-rpc gRPC server can now listen on HTTP/2 (h2c) or HTTP/2 over TLS on a configured IP address and port instead of only a unix domain socket, configured via new cardano-node options such as `--grpc-listen-port` and `--grpc-tls-certificate`. `RpcConfigF`'s `rpcSocketPath` field was replaced by the new `RpcEndpoint` sum type.
2 changes: 2 additions & 0 deletions cardano-rpc/cardano-rpc.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,11 @@ library
generic-data,
grapesy,
grpc-spec,
iproute,
memory,
mempack,
microlens,
network,
proto-lens >=0.7.1.7,
proto-lens-protobuf-types,
random,
Expand Down
41 changes: 34 additions & 7 deletions cardano-rpc/src/Cardano/Rpc/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,40 @@ runRpcServer
runRpcServer tracer rpcConfig networkMagic nodeKernelAccessRef = handleFatalExceptions $ do
let RpcConfig
{ isEnabled = Identity isEnabled
, rpcSocketPath = Identity (File rpcSocketPathFp)
, rpcEndpoint = Identity rpcEndpoint
, nodeSocketPath = Identity nodeSocketPath
} = rpcConfig
config =
ServerConfig
{ serverInsecure = Just $ InsecureUnix rpcSocketPathFp
, serverSecure = Nothing
}
config :: ServerConfig
config = case rpcEndpoint of
RpcEndpointUnixSocket (File socketPath) ->
ServerConfig
{ serverInsecure = Just $ InsecureUnix socketPath
, serverSecure = Nothing
}
RpcEndpointHttp host port ->
ServerConfig
{ serverInsecure =
Just
InsecureConfig
{ insecureHost = Just $ show host
, insecurePort = port
}
, serverSecure = Nothing
}
RpcEndpointHttps host port (RpcTlsFiles certificateFile privateKeyFile chainCertificateFiles) ->
ServerConfig
{ serverInsecure = Nothing
, serverSecure =
Just
SecureConfig
{ secureHost = show host
, securePort = port
, securePubCert = unFile certificateFile
, secureChainCerts = unFile <$> chainCertificateFiles
, securePrivKey = unFile privateKeyFile
, secureSslKeyLog = def
}
}
rpcEnv =
RpcEnv
{ config = rpcConfig
Expand All @@ -133,7 +159,8 @@ runRpcServer tracer rpcConfig networkMagic nodeKernelAccessRef = handleFatalExce
, rpcNodeKernelAccess = nodeKernelAccessRef
}

when isEnabled $
when isEnabled $ do
traceWith tracer $ TraceRpcServerListening rpcEndpoint
runRIO rpcEnv $
withRunInIO $ \runInIO ->
runServerWithHandlers serverParams config . fmap (hoistSomeRpcHandler runInIO) $
Expand Down
67 changes: 57 additions & 10 deletions cardano-rpc/src/Cardano/Rpc/Server/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE NoFieldSelectors #-}

module Cardano.Rpc.Server.Config
( RpcConfig
, PartialRpcConfig
, RpcConfigF (..)
, RpcEndpoint (..)
, RpcTlsFiles (..)
, TlsCertificate
, TlsPrivateKey
, defaultRpcListenAddress
, makeRpcConfig
, nodeSocketPathToRpcSocketPath
)
Expand All @@ -18,7 +24,9 @@ import Cardano.Api

import RIO

import Data.IP (IP)
import Data.Monoid
import Network.Socket (PortNumber)
import System.FilePath (takeDirectory, (</>))

import Generic.Data (gmappend, gmempty)
Expand All @@ -31,8 +39,8 @@ type RpcConfig = RpcConfigF Identity
data RpcConfigF m = RpcConfig
{ isEnabled :: !(m Bool)
-- ^ whether the RPC server is enabled
, rpcSocketPath :: !(m SocketPath)
-- ^ path to the socket file where the RPC server listens
, rpcEndpoint :: !(m RpcEndpoint)
-- ^ endpoint where the RPC server listens
, nodeSocketPath :: !(m SocketPath)
-- ^ cardano-node socket path. Only valid if RPC endpoint is enabled.
}
Expand All @@ -53,11 +61,49 @@ instance Semigroup (RpcConfigF Last) where
instance Monoid (RpcConfigF Last) where
mempty = gmempty

-- | Endpoint the RPC server listens on. Exactly one listener is active at a
-- time.
data RpcEndpoint
= RpcEndpointUnixSocket !SocketPath
| -- | IP address and port of the HTTP/2 without TLS (h2c) listener.
RpcEndpointHttp !IP !PortNumber
| -- | IP address, port and TLS credential files of the HTTP/2 over TLS
-- listener.
RpcEndpointHttps !IP !PortNumber !RpcTlsFiles
deriving (Eq, Show)

instance Pretty RpcEndpoint where
pretty = \case
RpcEndpointUnixSocket (File socketPath) -> pretty socketPath
RpcEndpointHttp host port -> pshow host <> ":" <> pshow port
RpcEndpointHttps host port _ -> pshow host <> ":" <> pshow port <> " (TLS)"

-- | TLS credential files for the RPC server, PEM format.
data RpcTlsFiles = RpcTlsFiles
{ certificateFile :: !(File TlsCertificate In)
-- ^ server X.509 certificate
, privateKeyFile :: !(File TlsPrivateKey In)
-- ^ private key matching the certificate
, chainCertificateFiles :: ![File TlsCertificate In]
-- ^ intermediate chain certificates, if any
}
deriving (Eq, Show)

-- | Empty content tag for 'File' identifying a TLS certificate file.
data TlsCertificate

-- | Empty content tag for 'File' identifying a TLS private key file.
data TlsPrivateKey

-- | Default IP address the HTTP/2 listener binds to when only a port is configured.
defaultRpcListenAddress :: IP
defaultRpcListenAddress = "127.0.0.1"

-- | Build RPC Config
--
-- Uses the following defaults if the values are not provided
-- * RPC is disabled
-- * @rpc.sock@ is placed in the same path as the node socket
-- * the endpoint is a unix socket, @rpc.sock@, placed in the same path as the node socket
--
-- Validates if the node socket is enabled if RPC is enabled.
makeRpcConfig
Expand All @@ -67,21 +113,22 @@ makeRpcConfig
makeRpcConfig
RpcConfig
{ isEnabled = Last mIsEnabled
, rpcSocketPath = Last mRpcSocketPath
, rpcEndpoint = Last mRpcEndpoint
, nodeSocketPath = Last mNodeSocketPath
} = do
let isEnabled = fromMaybe False mIsEnabled
-- default to a some non-existing path. Does not matter if the gRPC endpoint is disabled
-- Default to a non-existing path. Irrelevant when the RPC server is disabled; when enabled, the validation below requires an explicit node socket path.
nodeSocketPath = fromMaybe "./node.socket" mNodeSocketPath
rpcSocketPath = fromMaybe (nodeSocketPathToRpcSocketPath nodeSocketPath) mRpcSocketPath
rpcEndpoint = fromMaybe (RpcEndpointUnixSocket $ nodeSocketPathToRpcSocketPath nodeSocketPath) mRpcEndpoint
when (isEnabled && isNothing mNodeSocketPath) $
throwError
"Configuration error: gRPC endpoint was enabled but node socket file was not specified. Cannot run gRPC server without node socket."
pure $
pure
RpcConfig
(pure isEnabled)
(pure rpcSocketPath)
(pure nodeSocketPath)
{ isEnabled = pure isEnabled
, rpcEndpoint = pure rpcEndpoint
, nodeSocketPath = pure nodeSocketPath
}

-- | Convert node socket path to a default rpc socket path.
-- By default it's @rpc.sock@ in the same directory as node socket path.
Expand Down
3 changes: 3 additions & 0 deletions cardano-rpc/src/Cardano/Rpc/Server/Internal/Tracing.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Cardano.Api.Pretty
import Cardano.Api.Serialise.Cbor (DecoderError)
import Cardano.Api.Serialise.Raw (SerialiseAsRawBytesError)
import Cardano.Api.Serialise.SerialiseUsing
import Cardano.Rpc.Server.Config (RpcEndpoint)

import Control.Exception
import Data.Word (Word64)
Expand All @@ -24,6 +25,7 @@ data TraceRpc
| TraceRpcNodeKernelAccess TraceRpcNodeKernelAccess
| TraceRpcError SomeException
| TraceRpcFatalError SomeException
| TraceRpcServerListening !RpcEndpoint

-- | Traces used in Query service
data TraceRpcQuery
Expand All @@ -45,6 +47,7 @@ instance Pretty TraceRpc where
TraceRpcNodeKernelAccess t -> pretty t
TraceRpcError e -> "Exception when processing RPC request:\n" <> prettyException e
TraceRpcFatalError e -> "RPC server fatal error: " <> prettyException e
TraceRpcServerListening endpoint -> "RPC server starting on " <> pretty endpoint

-- | Span type
data TraceSpanEvent
Expand Down
Loading