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