miden_node_rpc/server/
mod.rs

1use std::time::Duration;
2
3use accept::AcceptHeaderLayer;
4use anyhow::Context;
5use miden_node_proto::generated::rpc::api_server;
6use miden_node_proto_build::rpc_api_descriptor;
7use miden_node_utils::cors::cors_for_grpc_web_layer;
8use miden_node_utils::panic::{CatchPanicLayer, catch_panic_layer_fn};
9use miden_node_utils::tracing::grpc::grpc_trace_fn;
10use tokio::net::TcpListener;
11use tokio_stream::wrappers::TcpListenerStream;
12use tonic_reflection::server;
13use tonic_web::GrpcWebLayer;
14use tower_http::trace::TraceLayer;
15use tracing::info;
16use url::Url;
17
18use crate::COMPONENT;
19use crate::server::health::HealthCheckLayer;
20
21mod accept;
22mod api;
23mod health;
24mod validator;
25
26/// The RPC server component.
27///
28/// On startup, binds to the provided listener and starts serving the RPC API.
29/// It connects lazily to the store and block producer components as needed.
30/// Requests will fail if the components are not available.
31pub struct Rpc {
32    pub listener: TcpListener,
33    pub store_url: Url,
34    pub block_producer_url: Option<Url>,
35    /// Server-side timeout for an individual gRPC request.
36    ///
37    /// If the handler takes longer than this duration, the server cancels the call.
38    pub grpc_timeout: Duration,
39}
40
41impl Rpc {
42    /// Serves the RPC API.
43    ///
44    /// Note: Executes in place (i.e. not spawned) and will run indefinitely until
45    ///       a fatal error is encountered.
46    pub async fn serve(self) -> anyhow::Result<()> {
47        let mut api = api::RpcService::new(self.store_url.clone(), self.block_producer_url.clone());
48
49        let genesis = api
50            .get_genesis_header_with_retry()
51            .await
52            .context("Fetching genesis header from store")?;
53
54        api.set_genesis_commitment(genesis.commitment())?;
55
56        let api_service = api_server::ApiServer::new(api);
57        let reflection_service = server::Builder::configure()
58            .register_file_descriptor_set(rpc_api_descriptor())
59            .build_v1()
60            .context("failed to build reflection service")?;
61
62        // This is currently required for postman to work properly because
63        // it doesn't support the new version yet.
64        //
65        // See: <https://github.com/postmanlabs/postman-app-support/issues/13120>.
66        let reflection_service_alpha = server::Builder::configure()
67            .register_file_descriptor_set(rpc_api_descriptor())
68            .build_v1alpha()
69            .context("failed to build reflection service")?;
70
71        info!(target: COMPONENT, endpoint=?self.listener, store=%self.store_url, block_producer=?self.block_producer_url, "Server initialized");
72
73        let rpc_version = env!("CARGO_PKG_VERSION");
74        let rpc_version =
75            semver::Version::parse(rpc_version).context("failed to parse crate version")?;
76
77        tonic::transport::Server::builder()
78            .accept_http1(true)
79            .timeout(self.grpc_timeout)
80            .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
81            .layer(TraceLayer::new_for_grpc().make_span_with(grpc_trace_fn))
82            .layer(HealthCheckLayer)
83            .layer(AcceptHeaderLayer::new(&rpc_version, genesis.commitment()))
84            .layer(cors_for_grpc_web_layer())
85            // Enables gRPC-web support.
86            .layer(GrpcWebLayer::new())
87            .add_service(api_service)
88            // Enables gRPC reflection service.
89            .add_service(reflection_service)
90            .add_service(reflection_service_alpha)
91            .serve_with_incoming(TcpListenerStream::new(self.listener))
92            .await
93            .context("failed to serve RPC API")
94    }
95}