Skip to main content

miden_node_rpc/server/
mod.rs

1use accept::AcceptHeaderLayer;
2use anyhow::Context;
3use miden_node_proto::generated::rpc::api_server;
4use miden_node_proto_build::rpc_api_descriptor;
5use miden_node_utils::clap::GrpcOptionsExternal;
6use miden_node_utils::cors::cors_for_grpc_web_layer;
7use miden_node_utils::grpc;
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;
24
25/// The RPC server component.
26///
27/// On startup, binds to the provided listener and starts serving the RPC API.
28/// It connects lazily to the store, validator and block producer components as needed.
29/// Requests will fail if the components are not available.
30pub struct Rpc {
31    pub listener: TcpListener,
32    pub store_url: Url,
33    pub block_producer_url: Option<Url>,
34    pub validator_url: Url,
35    pub grpc_options: GrpcOptionsExternal,
36}
37
38impl Rpc {
39    /// Serves the RPC API.
40    ///
41    /// Note: Executes in place (i.e. not spawned) and will run indefinitely until
42    ///       a fatal error is encountered.
43    pub async fn serve(self) -> anyhow::Result<()> {
44        let mut api = api::RpcService::new(
45            self.store_url.clone(),
46            self.block_producer_url.clone(),
47            self.validator_url,
48        );
49
50        let genesis = api
51            .get_genesis_header_with_retry()
52            .await
53            .context("Fetching genesis header from store")?;
54
55        api.set_genesis_commitment(genesis.commitment())?;
56
57        let api_service = api_server::ApiServer::new(api);
58        let reflection_service = server::Builder::configure()
59            .register_file_descriptor_set(rpc_api_descriptor())
60            .build_v1()
61            .context("failed to build reflection service")?;
62
63        // This is currently required for postman to work properly because
64        // it doesn't support the new version yet.
65        //
66        // See: <https://github.com/postmanlabs/postman-app-support/issues/13120>.
67        let reflection_service_alpha = server::Builder::configure()
68            .register_file_descriptor_set(rpc_api_descriptor())
69            .build_v1alpha()
70            .context("failed to build reflection service")?;
71
72        info!(target: COMPONENT, endpoint=?self.listener, store=%self.store_url, block_producer=?self.block_producer_url, "Server initialized");
73
74        let rpc_version = env!("CARGO_PKG_VERSION");
75        let rpc_version =
76            semver::Version::parse(rpc_version).context("failed to parse crate version")?;
77
78        tonic::transport::Server::builder()
79            .accept_http1(true)
80            .max_connection_age(self.grpc_options.max_connection_age)
81            .timeout(self.grpc_options.request_timeout)
82            .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
83            .layer(grpc::connect_info_layer())
84            .layer(TraceLayer::new_for_grpc().make_span_with(grpc_trace_fn))
85            .layer(HealthCheckLayer)
86            .layer(grpc::rate_limit_concurrent_connections(self.grpc_options))
87            .layer(grpc::rate_limit_per_ip(self.grpc_options)?)
88            // Note: must come before the accept layer, as otherwise accept rejections
89            // do _not_ get CORS headers applied, masking the accept error in
90            // web-clients (which would experience CORS rejection).
91            .layer(cors_for_grpc_web_layer())
92            .layer(
93                AcceptHeaderLayer::new(&rpc_version, genesis.commitment())
94                    .with_genesis_enforced_method("SubmitProvenTransaction")
95                    .with_genesis_enforced_method("SubmitProvenBatch"),
96            )
97            // Enables gRPC-web support.
98            .layer(GrpcWebLayer::new())
99            .add_service(api_service)
100            // Enables gRPC reflection service.
101            .add_service(reflection_service)
102            .add_service(reflection_service_alpha)
103            .serve_with_incoming(TcpListenerStream::new(self.listener))
104            .await
105            .context("failed to serve RPC API")
106    }
107}