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