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::{miden_node_v1_node_service, miden_sequencer_v1_sequencer_service};
15use miden_node_proto_build::rpc_api_descriptor;
16use miden_node_store::state::{BlockWriter, ProofWriter, State};
17use miden_node_tracing::grpc::grpc_trace_fn;
18use miden_node_tracing::info;
19use miden_node_tracing::panic::{CatchPanicLayer, catch_panic_layer_fn};
20use miden_node_utils::clap::GrpcOptions;
21use miden_node_utils::cors::cors_for_grpc_web_layer;
22use miden_node_utils::grpc;
23use miden_node_utils::shutdown::CancellationToken;
24use miden_node_utils::tasks::Tasks;
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;
40mod admission;
41pub(crate) mod api;
42mod health;
43
44pub use admission::AccountAdmission;
45
46pub struct Rpc {
51 pub listener: TcpListener,
52 pub state: Arc<State>,
53 pub mode: RpcMode,
54 pub ntx_builder: Option<NtxBuilderClient>,
55 pub grpc_options: GrpcOptions,
56 pub network_tx_auth: Option<AsciiMetadataValue>,
57}
58
59#[derive(Clone, Debug)]
60pub(crate) struct NetworkTxAuth(pub(crate) AsciiMetadataValue);
62
63pub enum RpcMode {
70 Sequencer {
77 block_producer: Box<BlockProducerApi>,
78 validators: ValidatorClients,
79 account_admission: AccountAdmission,
80 },
81 FullNode {
90 source_rpc: Box<SourceRpcClient>,
91 readiness_threshold: u32,
92 pre_auth: Option<PreAuthSubmission>,
93 block_writer: BlockWriter,
95 proof_writer: ProofWriter,
97 },
98}
99
100#[derive(Clone)]
107pub(crate) enum RpcBackend {
108 Sequencer {
109 block_producer: Box<BlockProducerApi>,
110 validators: ValidatorClients,
111 account_admission: AccountAdmission,
112 },
113 FullNode {
114 source_rpc: Box<SourceRpcClient>,
115 pre_auth: Option<PreAuthSubmission>,
116 },
117}
118
119#[cfg(test)]
120impl RpcBackend {
121 pub(crate) fn sequencer(
125 block_producer: BlockProducerApi,
126 validators: ValidatorClients,
127 account_admission: AccountAdmission,
128 ) -> Self {
129 Self::Sequencer {
130 block_producer: Box::new(block_producer),
131 validators,
132 account_admission,
133 }
134 }
135
136 pub(crate) fn full_node(
137 source_rpc: SourceRpcClient,
138 pre_auth: Option<PreAuthSubmission>,
139 ) -> Self {
140 Self::FullNode {
141 source_rpc: Box::new(source_rpc),
142 pre_auth,
143 }
144 }
145}
146
147#[derive(Clone, Debug)]
153pub struct ValidatorClients(Vec<ValidatorClient>);
154
155impl ValidatorClients {
156 pub fn new(validators: Vec<ValidatorClient>) -> anyhow::Result<Self> {
160 anyhow::ensure!(!validators.is_empty(), "at least one validator is required");
161 Ok(Self(validators))
162 }
163
164 pub(crate) fn random(&self) -> &ValidatorClient {
167 let index = rand::rng().random_range(0..self.0.len());
168 &self.0[index]
169 }
170
171 pub(crate) fn as_slice(&self) -> &[ValidatorClient] {
172 &self.0
173 }
174}
175
176#[derive(Clone, Debug)]
182pub struct PreAuthSubmission {
183 validators: ValidatorClients,
184 sequencer: Box<SequencerClient>,
185}
186
187impl PreAuthSubmission {
188 pub fn new(
192 validators: Vec<ValidatorClient>,
193 sequencer: SequencerClient,
194 ) -> anyhow::Result<Self> {
195 let validators = ValidatorClients::new(validators)
196 .context("pre-authenticated submission requires at least one validator")?;
197 Ok(Self {
198 validators,
199 sequencer: Box::new(sequencer),
200 })
201 }
202
203 pub(crate) fn validators(&self) -> &ValidatorClients {
204 &self.validators
205 }
206
207 pub(crate) fn sequencer(&self) -> &SequencerClient {
208 &self.sequencer
209 }
210}
211
212impl RpcMode {
213 pub fn sequencer(
214 block_producer: BlockProducerApi,
215 validators: ValidatorClients,
216 account_admission: AccountAdmission,
217 ) -> Self {
218 Self::Sequencer {
219 block_producer: Box::new(block_producer),
220 validators,
221 account_admission,
222 }
223 }
224
225 pub fn full_node(
226 source_rpc: SourceRpcClient,
227 readiness_threshold: u32,
228 pre_auth: Option<PreAuthSubmission>,
229 block_writer: BlockWriter,
230 proof_writer: ProofWriter,
231 ) -> Self {
232 Self::FullNode {
233 source_rpc: Box::new(source_rpc),
234 readiness_threshold,
235 pre_auth,
236 block_writer,
237 proof_writer,
238 }
239 }
240
241 const fn as_str(&self) -> &'static str {
242 match self {
243 Self::Sequencer { .. } => "sequencer",
244 Self::FullNode { .. } => "full",
245 }
246 }
247
248 fn backend(&self) -> RpcBackend {
251 match self {
252 Self::Sequencer {
253 block_producer,
254 validators,
255 account_admission,
256 } => RpcBackend::Sequencer {
257 block_producer: block_producer.clone(),
258 validators: validators.clone(),
259 account_admission: account_admission.clone(),
260 },
261 Self::FullNode { source_rpc, pre_auth, .. } => RpcBackend::FullNode {
262 source_rpc: source_rpc.clone(),
263 pre_auth: pre_auth.clone(),
264 },
265 }
266 }
267}
268
269impl Rpc {
270 pub async fn serve(self, shutdown: CancellationToken) -> anyhow::Result<()> {
278 let endpoint = self.listener.local_addr().context("failed to read RPC listen address")?;
279 let mode = self.mode.as_str();
280 let mut api = api::RpcService::new(
281 self.state.clone(),
282 self.mode.backend(),
283 self.ntx_builder.clone(),
284 NonZeroUsize::new(1_000_000).unwrap(),
285 self.network_tx_auth.map(NetworkTxAuth),
286 );
287
288 let genesis =
289 api.get_genesis_header().await.context("Fetching genesis header from store")?;
290
291 api.set_genesis_commitment(genesis.commitment())?;
292
293 let api_service = miden_node_v1_node_service::service(api);
294
295 let mut tasks = Tasks::new();
296
297 let (health_reporter, health_service) = tonic_health::server::health_reporter();
299 match self.mode {
300 RpcMode::Sequencer { .. } => {
301 health_reporter
302 .set_service_status(
303 miden_node_v1_node_service::service_name(),
304 tonic_health::ServingStatus::Serving,
305 )
306 .await;
307 let chain_tip = self.state.committed_tip();
308 log_node_ready(mode, endpoint, chain_tip);
309 },
310 RpcMode::FullNode {
311 source_rpc,
312 readiness_threshold,
313 block_writer,
314 proof_writer,
315 ..
316 } => {
317 Self::spawn_full_node_sync(
318 &self.state,
319 &mut tasks,
320 health_reporter,
321 mode,
322 endpoint,
323 shutdown.clone(),
324 *source_rpc,
325 readiness_threshold,
326 block_writer,
327 proof_writer,
328 )
329 .await;
330 },
331 }
332
333 let reflection_service = server::Builder::configure()
334 .register_file_descriptor_set(rpc_api_descriptor())
335 .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET)
336 .build_v1()
337 .context("failed to build reflection service")?;
338
339 let rpc_version = env!("CARGO_PKG_VERSION");
340 let rpc_version =
341 semver::Version::parse(rpc_version).context("failed to parse crate version")?;
342
343 let rpc = tonic::transport::Server::builder()
344 .accept_http1(true)
345 .timeout(self.grpc_options.request_timeout)
346 .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
347 .layer(
348 TraceLayer::new(SharedClassifier::new(
349 GrpcErrorsAsFailures::new()
350 .with_success(GrpcCode::InvalidArgument)
351 .with_success(GrpcCode::NotFound)
352 .with_success(GrpcCode::ResourceExhausted)
353 .with_success(GrpcCode::Unimplemented)
354 .with_success(GrpcCode::Unknown),
355 ))
356 .make_span_with(grpc_trace_fn),
357 )
358 .layer(HealthCheckLayer)
359 .layer(cors_for_grpc_web_layer())
360 .layer(GrpcWebLayer::new())
363 .layer(grpc::ResolveClientIpLayer)
366 .layer(
370 AcceptHeaderLayer::new(&rpc_version, genesis.commitment())
371 .with_genesis_enforced_method("RegisterAccount")
372 .with_genesis_enforced_method("SubmitProvenTx")
373 .with_genesis_enforced_method("SubmitProvenTxBatch"),
374 )
375 .add_service(api_service)
376 .add_service(health_service)
377 .add_service(reflection_service)
379 .serve_with_incoming_shutdown(
380 TcpListenerStream::new(self.listener),
381 shutdown.clone().cancelled_owned(),
382 );
383 tasks.spawn("RPC server", async move { rpc.await.map_err(|e| anyhow::anyhow!(e)) });
384
385 tasks.join_next_or_cancelled(shutdown).await
386 }
387
388 #[expect(
390 clippy::too_many_arguments,
391 reason = "assembles the full-node sync task from Rpc::serve's local state"
392 )]
393 async fn spawn_full_node_sync(
394 state: &Arc<State>,
395 tasks: &mut Tasks,
396 health_reporter: tonic_health::server::HealthReporter,
397 mode: &str,
398 endpoint: impl Display,
399 shutdown: CancellationToken,
400 source_rpc: SourceRpcClient,
401 readiness_threshold: u32,
402 block_writer: BlockWriter,
403 proof_writer: ProofWriter,
404 ) {
405 health_reporter
406 .set_service_status(
407 miden_node_v1_node_service::service_name(),
408 tonic_health::ServingStatus::NotServing,
409 )
410 .await;
411 let readiness = RpcReadiness::new(health_reporter, readiness_threshold);
412 tasks.spawn(
413 "RPC sync",
414 RpcSync {
415 state: Arc::clone(state),
416 block_writer,
417 proof_writer,
418 source_rpc,
419 readiness,
420 }
421 .run(shutdown),
422 );
423 log_node_synchronizing(mode, endpoint, readiness_threshold);
424 }
425}
426
427fn log_node_ready(mode: &str, endpoint: impl Display, chain_tip: BlockNumber) {
428 info!(
429 target: LOG_TARGET,
430 "Node ready",
431 service.name = "miden-node",
432 service.version = env!("CARGO_PKG_VERSION"),
433 node.role = mode,
434 rpc.listen = endpoint.to_string(),
435 block.number = chain_tip
436 );
437}
438
439fn log_node_synchronizing(mode: &str, endpoint: impl Display, readiness_threshold: u32) {
440 info!(
441 target: LOG_TARGET,
442 "Node started; synchronizing",
443 service.name = "miden-node",
444 service.version = env!("CARGO_PKG_VERSION"),
445 node.role = mode,
446 rpc.listen = endpoint.to_string(),
447 sync.ready_threshold = readiness_threshold
448 );
449}
450
451pub struct SequencerInternal {
463 pub listener: TcpListener,
465 pub state: Arc<State>,
467 pub block_producer: BlockProducerApi,
469 pub account_admission: AccountAdmission,
471 pub grpc_options: GrpcOptions,
473}
474
475impl SequencerInternal {
476 pub async fn serve(self, shutdown: CancellationToken) -> anyhow::Result<()> {
481 let endpoint = self
482 .listener
483 .local_addr()
484 .context("failed to read internal sequencer listen address")?;
485 info!(
486 target: LOG_TARGET,
487 "Internal sequencer server ready",
488 internal.listen = endpoint.to_string()
489 );
490
491 let service = SequencerInternalService {
492 state: self.state,
493 block_producer: self.block_producer,
494 account_admission: self.account_admission,
495 };
496
497 tonic::transport::Server::builder()
500 .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
501 .layer(TraceLayer::new_for_grpc().make_span_with(grpc_trace_fn))
502 .timeout(self.grpc_options.request_timeout)
503 .add_service(miden_sequencer_v1_sequencer_service::service(service))
504 .serve_with_incoming_shutdown(
505 TcpListenerStream::new(self.listener),
506 shutdown.cancelled_owned(),
507 )
508 .await
509 .context("failed to serve internal sequencer API")
510 }
511}