miden_node_rpc/server/
mod.rs1use std::num::NonZeroUsize;
2use std::sync::Arc;
3
4use accept::AcceptHeaderLayer;
5use anyhow::Context;
6use miden_node_block_producer::{BlockProducerApi, RpcReadiness, RpcSync};
7use miden_node_proto::clients::{
8 NtxBuilderClient,
9 RpcClient as SourceRpcClient,
10 SequencerClient,
11 ValidatorClient,
12};
13use miden_node_proto::server::{rpc_api, sequencer_api};
14use miden_node_proto_build::rpc_api_descriptor;
15use miden_node_store::state::State;
16use miden_node_utils::clap::{GrpcOptionsExternal, GrpcOptionsInternal};
17use miden_node_utils::cors::cors_for_grpc_web_layer;
18use miden_node_utils::grpc;
19use miden_node_utils::panic::{CatchPanicLayer, catch_panic_layer_fn};
20use miden_node_utils::shutdown::CancellationToken;
21use miden_node_utils::tasks::Tasks;
22use miden_node_utils::tracing::grpc::grpc_trace_fn;
23use tokio::net::TcpListener;
24use tokio_stream::wrappers::TcpListenerStream;
25use tonic::metadata::AsciiMetadataValue;
26use tonic_reflection::server;
27use tonic_web::GrpcWebLayer;
28use tower_http::classify::{GrpcCode, GrpcErrorsAsFailures, SharedClassifier};
29use tower_http::trace::TraceLayer;
30use tracing::info;
31
32use crate::server::api::SequencerInternalService;
33use crate::server::health::HealthCheckLayer;
34use crate::{COMPONENT, LOG_TARGET};
35
36mod accept;
37pub(crate) mod api;
38mod health;
39
40pub struct Rpc {
45 pub listener: TcpListener,
46 pub store: Arc<State>,
47 pub mode: RpcMode,
48 pub ntx_builder: Option<NtxBuilderClient>,
49 pub grpc_options: GrpcOptionsExternal,
50 pub network_tx_auth: Option<AsciiMetadataValue>,
51}
52
53#[derive(Clone, Debug)]
54pub(crate) struct NetworkTxAuth(pub(crate) AsciiMetadataValue);
56
57#[derive(Clone, Debug)]
58pub enum RpcMode {
59 Sequencer {
62 block_producer: Box<BlockProducerApi>,
63 validator: Box<ValidatorClient>,
64 },
65 FullNode {
74 source_rpc: Box<SourceRpcClient>,
75 readiness_threshold: u32,
76 validator: Option<Box<ValidatorClient>>,
77 sequencer: Option<Box<SequencerClient>>,
78 },
79}
80
81impl RpcMode {
82 pub fn sequencer(block_producer: BlockProducerApi, validator: ValidatorClient) -> Self {
83 Self::Sequencer {
84 block_producer: Box::new(block_producer),
85 validator: Box::new(validator),
86 }
87 }
88
89 pub fn full_node(
90 source_rpc: SourceRpcClient,
91 readiness_threshold: u32,
92 validator: Option<ValidatorClient>,
93 sequencer: Option<SequencerClient>,
94 ) -> Self {
95 Self::FullNode {
96 source_rpc: Box::new(source_rpc),
97 readiness_threshold,
98 sequencer: sequencer.map(Box::new),
99 validator: validator.map(Box::new),
100 }
101 }
102}
103
104impl Rpc {
105 pub async fn serve(self, shutdown: CancellationToken) -> anyhow::Result<()> {
113 let mut api = api::RpcService::new(
114 self.store.clone(),
115 self.mode.clone(),
116 self.ntx_builder.clone(),
117 NonZeroUsize::new(1_000_000).unwrap(),
118 self.network_tx_auth.map(NetworkTxAuth),
119 );
120
121 let genesis = api
122 .get_genesis_header_with_retry()
123 .await
124 .context("Fetching genesis header from store")?;
125
126 api.set_genesis_commitment(genesis.commitment())?;
127
128 let api_service = rpc_api::service(api);
129
130 info!(target: LOG_TARGET, endpoint=?self.listener, mode=?self.mode, "Server initialized");
131
132 let mut tasks = Tasks::new();
133
134 let (health_reporter, health_service) = tonic_health::server::health_reporter();
136 match self.mode {
137 RpcMode::Sequencer { .. } => {
138 health_reporter
139 .set_service_status(
140 rpc_api::service_name(),
141 tonic_health::ServingStatus::Serving,
142 )
143 .await;
144 },
145 RpcMode::FullNode { source_rpc, readiness_threshold, .. } => {
146 health_reporter
147 .set_service_status(
148 rpc_api::service_name(),
149 tonic_health::ServingStatus::NotServing,
150 )
151 .await;
152 let readiness = RpcReadiness::new(health_reporter, readiness_threshold);
153 tasks.spawn(
154 "RPC sync",
155 RpcSync {
156 state: Arc::clone(&self.store),
157 source_rpc: *source_rpc,
158 readiness,
159 }
160 .run(shutdown.clone()),
161 );
162 },
163 }
164
165 let reflection_service = server::Builder::configure()
166 .register_file_descriptor_set(rpc_api_descriptor())
167 .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET)
168 .build_v1()
169 .context("failed to build reflection service")?;
170
171 let rpc_version = env!("CARGO_PKG_VERSION");
172 let rpc_version =
173 semver::Version::parse(rpc_version).context("failed to parse crate version")?;
174
175 let rpc = tonic::transport::Server::builder()
176 .accept_http1(true)
177 .max_connection_age(self.grpc_options.max_connection_age)
178 .timeout(self.grpc_options.request_timeout)
179 .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
180 .layer(
181 TraceLayer::new(SharedClassifier::new(
182 GrpcErrorsAsFailures::new()
183 .with_success(GrpcCode::InvalidArgument)
184 .with_success(GrpcCode::NotFound)
185 .with_success(GrpcCode::ResourceExhausted)
186 .with_success(GrpcCode::Unimplemented)
187 .with_success(GrpcCode::Unknown),
188 ))
189 .make_span_with(grpc_trace_fn),
190 )
191 .layer(HealthCheckLayer)
192 .layer(cors_for_grpc_web_layer())
193 .layer(GrpcWebLayer::new())
196 .layer(grpc::rate_limit_concurrent_connections(self.grpc_options))
197 .layer(grpc::rate_limit_per_ip(self.grpc_options)?)
198 .layer(grpc::ResolveClientIpLayer)
201 .layer(
205 AcceptHeaderLayer::new(&rpc_version, genesis.commitment())
206 .with_genesis_enforced_method("SubmitProvenTx")
207 .with_genesis_enforced_method("SubmitProvenTxBatch"),
208 )
209 .add_service(api_service)
210 .add_service(health_service)
211 .add_service(reflection_service)
213 .serve_with_incoming_shutdown(
214 TcpListenerStream::new(self.listener),
215 shutdown.clone().cancelled_owned(),
216 );
217 tasks.spawn("RPC server", async move { rpc.await.map_err(|e| anyhow::anyhow!(e)) });
218
219 tasks.join_next_or_cancelled(shutdown).await
220 }
221}
222
223pub struct SequencerInternal {
235 pub listener: TcpListener,
237 pub block_producer: BlockProducerApi,
239 pub grpc_options: GrpcOptionsInternal,
241}
242
243impl SequencerInternal {
244 pub async fn serve(self, shutdown: CancellationToken) -> anyhow::Result<()> {
249 info!(target: COMPONENT, endpoint = ?self.listener, "Internal sequencer server initialized");
250
251 let service = SequencerInternalService { block_producer: self.block_producer };
252
253 tonic::transport::Server::builder()
256 .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
257 .layer(TraceLayer::new_for_grpc().make_span_with(grpc_trace_fn))
258 .timeout(self.grpc_options.request_timeout)
259 .add_service(sequencer_api::service(service))
260 .serve_with_incoming_shutdown(
261 TcpListenerStream::new(self.listener),
262 shutdown.cancelled_owned(),
263 )
264 .await
265 .context("failed to serve internal sequencer API")
266 }
267}