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::classify::{GrpcCode, GrpcErrorsAsFailures, SharedClassifier};
15use tower_http::trace::TraceLayer;
16use tracing::info;
17use url::Url;
18
19use crate::COMPONENT;
20use crate::server::health::HealthCheckLayer;
21
22mod accept;
23mod api;
24mod health;
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, validator 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    pub validator_url: Url,
36    pub ntx_builder_url: Option<Url>,
37    pub grpc_options: GrpcOptionsExternal,
38}
39
40impl Rpc {
41    /// Serves the RPC API.
42    ///
43    /// Note: Executes in place (i.e. not spawned) and will run indefinitely until
44    ///       a fatal error is encountered.
45    pub async fn serve(self) -> anyhow::Result<()> {
46        let mut api = api::RpcService::new(
47            self.store_url.clone(),
48            self.block_producer_url.clone(),
49            self.validator_url,
50            self.ntx_builder_url.clone(),
51        );
52
53        let genesis = api
54            .get_genesis_header_with_retry()
55            .await
56            .context("Fetching genesis header from store")?;
57
58        api.set_genesis_commitment(genesis.commitment())?;
59
60        let api_service = api_server::ApiServer::new(api);
61        let reflection_service = server::Builder::configure()
62            .register_file_descriptor_set(rpc_api_descriptor())
63            .build_v1()
64            .context("failed to build reflection service")?;
65
66        info!(target: COMPONENT, endpoint=?self.listener, store=%self.store_url, block_producer=?self.block_producer_url, "Server initialized");
67
68        let rpc_version = env!("CARGO_PKG_VERSION");
69        let rpc_version =
70            semver::Version::parse(rpc_version).context("failed to parse crate version")?;
71
72        tonic::transport::Server::builder()
73            .accept_http1(true)
74            .max_connection_age(self.grpc_options.max_connection_age)
75            .timeout(self.grpc_options.request_timeout)
76            .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
77            .layer(grpc::connect_info_layer())
78            .layer(
79                TraceLayer::new(SharedClassifier::new(
80                    GrpcErrorsAsFailures::new()
81                        .with_success(GrpcCode::InvalidArgument)
82                        .with_success(GrpcCode::NotFound)
83                        .with_success(GrpcCode::ResourceExhausted)
84                        .with_success(GrpcCode::Unimplemented)
85                        .with_success(GrpcCode::Unknown),
86                ))
87                .make_span_with(grpc_trace_fn),
88            )
89            .layer(HealthCheckLayer)
90            .layer(cors_for_grpc_web_layer())
91            // Note: must wrap the accept/rate-limit layers so grpc-web callers receive
92            // grpc-web-compatible error responses instead of opaque transport failures.
93            .layer(GrpcWebLayer::new())
94            .layer(grpc::rate_limit_concurrent_connections(self.grpc_options))
95            .layer(grpc::rate_limit_per_ip(self.grpc_options)?)
96            // Note: must come after the CORS layer, as otherwise accept rejections
97            // do _not_ get CORS headers applied, masking the accept error in
98            // web-clients (which would experience CORS rejection).
99            .layer(
100                AcceptHeaderLayer::new(&rpc_version, genesis.commitment())
101                    .with_genesis_enforced_method("SubmitProvenTransaction")
102                    .with_genesis_enforced_method("SubmitProvenBatch"),
103            )
104            .add_service(api_service)
105            // Enables gRPC reflection service.
106            .add_service(reflection_service)
107            .serve_with_incoming(TcpListenerStream::new(self.listener))
108            .await
109            .context("failed to serve RPC API")
110    }
111}