1use std::fmt::Display;
2use std::num::NonZeroUsize;
3use std::sync::Arc;
4
5use accept::AcceptHeaderLayer;
6use anyhow::Context;
7use miden_node_block_producer::{BlockProducerApi, RpcReadiness, RpcSync};
8use miden_node_proto::clients::{
9 NtxBuilderClient,
10 RpcClient as SourceRpcClient,
11 SequencerClient,
12 ValidatorClient,
13};
14use miden_node_proto::server::{rpc_api, sequencer_api};
15use miden_node_proto_build::rpc_api_descriptor;
16use miden_node_store::state::{BlockWriter, ProofWriter, State};
17use miden_node_utils::clap::GrpcOptions;
18use miden_node_utils::cors::cors_for_grpc_web_layer;
19use miden_node_utils::grpc;
20use miden_node_utils::panic::{CatchPanicLayer, catch_panic_layer_fn};
21use miden_node_utils::shutdown::CancellationToken;
22use miden_node_utils::tasks::Tasks;
23use miden_node_utils::tracing::grpc::grpc_trace_fn;
24use miden_node_utils::tracing::info;
25use miden_protocol::block::BlockNumber;
26use rand::RngExt;
27use tokio::net::TcpListener;
28use tokio_stream::wrappers::TcpListenerStream;
29use tonic::metadata::AsciiMetadataValue;
30use tonic_reflection::server;
31use tonic_web::GrpcWebLayer;
32use tower_http::classify::{GrpcCode, GrpcErrorsAsFailures, SharedClassifier};
33use tower_http::trace::TraceLayer;
34
35use crate::LOG_TARGET;
36use crate::server::api::SequencerInternalService;
37use crate::server::health::HealthCheckLayer;
38
39mod accept;
40pub(crate) mod api;
41mod health;
42
43pub struct Rpc {
48 pub listener: TcpListener,
49 pub state: Arc<State>,
50 pub mode: RpcMode,
51 pub ntx_builder: Option<NtxBuilderClient>,
52 pub grpc_options: GrpcOptions,
53 pub network_tx_auth: Option<AsciiMetadataValue>,
54}
55
56#[derive(Clone, Debug)]
57pub(crate) struct NetworkTxAuth(pub(crate) AsciiMetadataValue);
59
60pub enum RpcMode {
67 Sequencer {
74 block_producer: Box<BlockProducerApi>,
75 validators: ValidatorClients,
76 },
77 FullNode {
86 source_rpc: Box<SourceRpcClient>,
87 readiness_threshold: u32,
88 pre_auth: Option<PreAuthSubmission>,
89 block_writer: BlockWriter,
91 proof_writer: ProofWriter,
93 },
94}
95
96#[derive(Clone, Debug)]
103pub(crate) enum RpcBackend {
104 Sequencer {
105 block_producer: Box<BlockProducerApi>,
106 validators: ValidatorClients,
107 },
108 FullNode {
109 source_rpc: Box<SourceRpcClient>,
110 pre_auth: Option<PreAuthSubmission>,
111 },
112}
113
114#[cfg(test)]
115impl RpcBackend {
116 pub(crate) fn sequencer(
120 block_producer: BlockProducerApi,
121 validators: ValidatorClients,
122 ) -> Self {
123 Self::Sequencer {
124 block_producer: Box::new(block_producer),
125 validators,
126 }
127 }
128
129 pub(crate) fn full_node(
130 source_rpc: SourceRpcClient,
131 pre_auth: Option<PreAuthSubmission>,
132 ) -> Self {
133 Self::FullNode {
134 source_rpc: Box::new(source_rpc),
135 pre_auth,
136 }
137 }
138}
139
140#[derive(Clone, Debug)]
146pub struct ValidatorClients(Vec<ValidatorClient>);
147
148impl ValidatorClients {
149 pub fn new(validators: Vec<ValidatorClient>) -> anyhow::Result<Self> {
153 anyhow::ensure!(!validators.is_empty(), "at least one validator is required");
154 Ok(Self(validators))
155 }
156
157 pub(crate) fn random(&self) -> &ValidatorClient {
160 let index = rand::rng().random_range(0..self.0.len());
161 &self.0[index]
162 }
163
164 pub(crate) fn as_slice(&self) -> &[ValidatorClient] {
165 &self.0
166 }
167}
168
169#[derive(Clone, Debug)]
175pub struct PreAuthSubmission {
176 validators: ValidatorClients,
177 sequencer: Box<SequencerClient>,
178}
179
180impl PreAuthSubmission {
181 pub fn new(
185 validators: Vec<ValidatorClient>,
186 sequencer: SequencerClient,
187 ) -> anyhow::Result<Self> {
188 let validators = ValidatorClients::new(validators)
189 .context("pre-authenticated submission requires at least one validator")?;
190 Ok(Self {
191 validators,
192 sequencer: Box::new(sequencer),
193 })
194 }
195
196 pub(crate) fn validators(&self) -> &ValidatorClients {
197 &self.validators
198 }
199
200 pub(crate) fn sequencer(&self) -> &SequencerClient {
201 &self.sequencer
202 }
203}
204
205impl RpcMode {
206 pub fn sequencer(block_producer: BlockProducerApi, validators: ValidatorClients) -> Self {
207 Self::Sequencer {
208 block_producer: Box::new(block_producer),
209 validators,
210 }
211 }
212
213 pub fn full_node(
214 source_rpc: SourceRpcClient,
215 readiness_threshold: u32,
216 pre_auth: Option<PreAuthSubmission>,
217 block_writer: BlockWriter,
218 proof_writer: ProofWriter,
219 ) -> Self {
220 Self::FullNode {
221 source_rpc: Box::new(source_rpc),
222 readiness_threshold,
223 pre_auth,
224 block_writer,
225 proof_writer,
226 }
227 }
228
229 const fn as_str(&self) -> &'static str {
230 match self {
231 Self::Sequencer { .. } => "sequencer",
232 Self::FullNode { .. } => "full",
233 }
234 }
235
236 fn backend(&self) -> RpcBackend {
239 match self {
240 Self::Sequencer { block_producer, validators } => RpcBackend::Sequencer {
241 block_producer: block_producer.clone(),
242 validators: validators.clone(),
243 },
244 Self::FullNode { source_rpc, pre_auth, .. } => RpcBackend::FullNode {
245 source_rpc: source_rpc.clone(),
246 pre_auth: pre_auth.clone(),
247 },
248 }
249 }
250}
251
252impl Rpc {
253 pub async fn serve(self, shutdown: CancellationToken) -> anyhow::Result<()> {
261 let endpoint = self.listener.local_addr().context("failed to read RPC listen address")?;
262 let mode = self.mode.as_str();
263 let mut api = api::RpcService::new(
264 self.state.clone(),
265 self.mode.backend(),
266 self.ntx_builder.clone(),
267 NonZeroUsize::new(1_000_000).unwrap(),
268 self.network_tx_auth.map(NetworkTxAuth),
269 );
270
271 let genesis = api
272 .get_genesis_header_with_retry()
273 .await
274 .context("Fetching genesis header from store")?;
275
276 api.set_genesis_commitment(genesis.commitment())?;
277
278 let api_service = rpc_api::service(api);
279
280 let mut tasks = Tasks::new();
281
282 let (health_reporter, health_service) = tonic_health::server::health_reporter();
284 match self.mode {
285 RpcMode::Sequencer { .. } => {
286 health_reporter
287 .set_service_status(
288 rpc_api::service_name(),
289 tonic_health::ServingStatus::Serving,
290 )
291 .await;
292 let chain_tip = self.state.committed_tip();
293 log_node_ready(mode, endpoint, chain_tip);
294 },
295 RpcMode::FullNode {
296 source_rpc,
297 readiness_threshold,
298 block_writer,
299 proof_writer,
300 ..
301 } => {
302 Self::spawn_full_node_sync(
303 &self.state,
304 &mut tasks,
305 health_reporter,
306 mode,
307 endpoint,
308 shutdown.clone(),
309 *source_rpc,
310 readiness_threshold,
311 block_writer,
312 proof_writer,
313 )
314 .await;
315 },
316 }
317
318 let reflection_service = server::Builder::configure()
319 .register_file_descriptor_set(rpc_api_descriptor())
320 .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET)
321 .build_v1()
322 .context("failed to build reflection service")?;
323
324 let rpc_version = env!("CARGO_PKG_VERSION");
325 let rpc_version =
326 semver::Version::parse(rpc_version).context("failed to parse crate version")?;
327
328 let rpc = tonic::transport::Server::builder()
329 .accept_http1(true)
330 .timeout(self.grpc_options.request_timeout)
331 .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
332 .layer(
333 TraceLayer::new(SharedClassifier::new(
334 GrpcErrorsAsFailures::new()
335 .with_success(GrpcCode::InvalidArgument)
336 .with_success(GrpcCode::NotFound)
337 .with_success(GrpcCode::ResourceExhausted)
338 .with_success(GrpcCode::Unimplemented)
339 .with_success(GrpcCode::Unknown),
340 ))
341 .make_span_with(grpc_trace_fn),
342 )
343 .layer(HealthCheckLayer)
344 .layer(cors_for_grpc_web_layer())
345 .layer(GrpcWebLayer::new())
348 .layer(grpc::ResolveClientIpLayer)
351 .layer(
355 AcceptHeaderLayer::new(&rpc_version, genesis.commitment())
356 .with_genesis_enforced_method("SubmitProvenTx")
357 .with_genesis_enforced_method("SubmitProvenTxBatch"),
358 )
359 .add_service(api_service)
360 .add_service(health_service)
361 .add_service(reflection_service)
363 .serve_with_incoming_shutdown(
364 TcpListenerStream::new(self.listener),
365 shutdown.clone().cancelled_owned(),
366 );
367 tasks.spawn("RPC server", async move { rpc.await.map_err(|e| anyhow::anyhow!(e)) });
368
369 tasks.join_next_or_cancelled(shutdown).await
370 }
371
372 #[expect(
374 clippy::too_many_arguments,
375 reason = "assembles the full-node sync task from Rpc::serve's local state"
376 )]
377 async fn spawn_full_node_sync(
378 state: &Arc<State>,
379 tasks: &mut Tasks,
380 health_reporter: tonic_health::server::HealthReporter,
381 mode: &str,
382 endpoint: impl Display,
383 shutdown: CancellationToken,
384 source_rpc: SourceRpcClient,
385 readiness_threshold: u32,
386 block_writer: BlockWriter,
387 proof_writer: ProofWriter,
388 ) {
389 health_reporter
390 .set_service_status(rpc_api::service_name(), tonic_health::ServingStatus::NotServing)
391 .await;
392 let readiness = RpcReadiness::new(health_reporter, readiness_threshold);
393 tasks.spawn(
394 "RPC sync",
395 RpcSync {
396 state: Arc::clone(state),
397 block_writer,
398 proof_writer,
399 source_rpc,
400 readiness,
401 }
402 .run(shutdown),
403 );
404 log_node_synchronizing(mode, endpoint, readiness_threshold);
405 }
406}
407
408fn log_node_ready(mode: &str, endpoint: impl Display, chain_tip: BlockNumber) {
409 info!(
410 target: LOG_TARGET,
411 "Node ready",
412 service.name = "miden-node",
413 service.version = env!("CARGO_PKG_VERSION"),
414 node.role = mode,
415 rpc.listen = endpoint.to_string(),
416 block.number = chain_tip
417 );
418}
419
420fn log_node_synchronizing(mode: &str, endpoint: impl Display, readiness_threshold: u32) {
421 info!(
422 target: LOG_TARGET,
423 "Node started; synchronizing",
424 service.name = "miden-node",
425 service.version = env!("CARGO_PKG_VERSION"),
426 node.role = mode,
427 rpc.listen = endpoint.to_string(),
428 sync.ready_threshold = readiness_threshold
429 );
430}
431
432pub struct SequencerInternal {
444 pub listener: TcpListener,
446 pub block_producer: BlockProducerApi,
448 pub grpc_options: GrpcOptions,
450}
451
452impl SequencerInternal {
453 pub async fn serve(self, shutdown: CancellationToken) -> anyhow::Result<()> {
458 let endpoint = self
459 .listener
460 .local_addr()
461 .context("failed to read internal sequencer listen address")?;
462 info!(
463 target: LOG_TARGET,
464 "Internal sequencer server ready",
465 internal.listen = endpoint.to_string()
466 );
467
468 let service = SequencerInternalService { block_producer: self.block_producer };
469
470 tonic::transport::Server::builder()
473 .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
474 .layer(TraceLayer::new_for_grpc().make_span_with(grpc_trace_fn))
475 .timeout(self.grpc_options.request_timeout)
476 .add_service(sequencer_api::service(service))
477 .serve_with_incoming_shutdown(
478 TcpListenerStream::new(self.listener),
479 shutdown.cancelled_owned(),
480 )
481 .await
482 .context("failed to serve internal sequencer API")
483 }
484}