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::{Finality, State};
17use miden_node_utils::clap::{GrpcOptionsExternal, GrpcOptionsInternal};
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 store: Arc<State>,
49 pub mode: RpcMode,
50 pub ntx_builder: Option<NtxBuilderClient>,
51 pub grpc_options: GrpcOptionsExternal,
52 pub network_tx_auth: Option<AsciiMetadataValue>,
53}
54
55#[derive(Clone, Debug)]
56pub(crate) struct NetworkTxAuth(pub(crate) AsciiMetadataValue);
58
59#[derive(Clone, Debug)]
60pub enum RpcMode {
61 Sequencer {
68 block_producer: Box<BlockProducerApi>,
69 validators: ValidatorClients,
70 },
71 FullNode {
80 source_rpc: Box<SourceRpcClient>,
81 readiness_threshold: u32,
82 pre_auth: Option<PreAuthSubmission>,
83 },
84}
85
86#[derive(Clone, Debug)]
92pub struct ValidatorClients(Vec<ValidatorClient>);
93
94impl ValidatorClients {
95 pub fn new(validators: Vec<ValidatorClient>) -> anyhow::Result<Self> {
99 anyhow::ensure!(!validators.is_empty(), "at least one validator is required");
100 Ok(Self(validators))
101 }
102
103 pub(crate) fn random(&self) -> &ValidatorClient {
106 let index = rand::rng().random_range(0..self.0.len());
107 &self.0[index]
108 }
109
110 pub(crate) fn as_slice(&self) -> &[ValidatorClient] {
111 &self.0
112 }
113}
114
115#[derive(Clone, Debug)]
121pub struct PreAuthSubmission {
122 validators: ValidatorClients,
123 sequencer: Box<SequencerClient>,
124}
125
126impl PreAuthSubmission {
127 pub fn new(
131 validators: Vec<ValidatorClient>,
132 sequencer: SequencerClient,
133 ) -> anyhow::Result<Self> {
134 let validators = ValidatorClients::new(validators)
135 .context("pre-authenticated submission requires at least one validator")?;
136 Ok(Self {
137 validators,
138 sequencer: Box::new(sequencer),
139 })
140 }
141
142 pub(crate) fn validators(&self) -> &ValidatorClients {
143 &self.validators
144 }
145
146 pub(crate) fn sequencer(&self) -> &SequencerClient {
147 &self.sequencer
148 }
149}
150
151impl RpcMode {
152 pub fn sequencer(block_producer: BlockProducerApi, validators: ValidatorClients) -> Self {
153 Self::Sequencer {
154 block_producer: Box::new(block_producer),
155 validators,
156 }
157 }
158
159 pub fn full_node(
160 source_rpc: SourceRpcClient,
161 readiness_threshold: u32,
162 pre_auth: Option<PreAuthSubmission>,
163 ) -> Self {
164 Self::FullNode {
165 source_rpc: Box::new(source_rpc),
166 readiness_threshold,
167 pre_auth,
168 }
169 }
170
171 const fn as_str(&self) -> &'static str {
172 match self {
173 Self::Sequencer { .. } => "sequencer",
174 Self::FullNode { .. } => "full",
175 }
176 }
177}
178
179impl Rpc {
180 pub async fn serve(self, shutdown: CancellationToken) -> anyhow::Result<()> {
188 let endpoint = self.listener.local_addr().context("failed to read RPC listen address")?;
189 let mode = self.mode.as_str();
190 let mut api = api::RpcService::new(
191 self.store.clone(),
192 self.mode.clone(),
193 self.ntx_builder.clone(),
194 NonZeroUsize::new(1_000_000).unwrap(),
195 self.network_tx_auth.map(NetworkTxAuth),
196 );
197
198 let genesis = api
199 .get_genesis_header_with_retry()
200 .await
201 .context("Fetching genesis header from store")?;
202
203 api.set_genesis_commitment(genesis.commitment())?;
204
205 let api_service = rpc_api::service(api);
206
207 let mut tasks = Tasks::new();
208
209 let (health_reporter, health_service) = tonic_health::server::health_reporter();
211 match self.mode {
212 RpcMode::Sequencer { .. } => {
213 health_reporter
214 .set_service_status(
215 rpc_api::service_name(),
216 tonic_health::ServingStatus::Serving,
217 )
218 .await;
219 let chain_tip = self.store.chain_tip(Finality::Committed).await;
220 log_node_ready(mode, endpoint, chain_tip);
221 },
222 RpcMode::FullNode { source_rpc, readiness_threshold, .. } => {
223 health_reporter
224 .set_service_status(
225 rpc_api::service_name(),
226 tonic_health::ServingStatus::NotServing,
227 )
228 .await;
229 let readiness = RpcReadiness::new(health_reporter, readiness_threshold);
230 tasks.spawn(
231 "RPC sync",
232 RpcSync {
233 state: Arc::clone(&self.store),
234 source_rpc: *source_rpc,
235 readiness,
236 }
237 .run(shutdown.clone()),
238 );
239 log_node_synchronizing(mode, endpoint, readiness_threshold);
240 },
241 }
242
243 let reflection_service = server::Builder::configure()
244 .register_file_descriptor_set(rpc_api_descriptor())
245 .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET)
246 .build_v1()
247 .context("failed to build reflection service")?;
248
249 let rpc_version = env!("CARGO_PKG_VERSION");
250 let rpc_version =
251 semver::Version::parse(rpc_version).context("failed to parse crate version")?;
252
253 let rpc = tonic::transport::Server::builder()
254 .accept_http1(true)
255 .max_connection_age(self.grpc_options.max_connection_age)
256 .max_connection_age_grace(self.grpc_options.max_connection_age_grace)
257 .timeout(self.grpc_options.request_timeout)
258 .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
259 .layer(
260 TraceLayer::new(SharedClassifier::new(
261 GrpcErrorsAsFailures::new()
262 .with_success(GrpcCode::InvalidArgument)
263 .with_success(GrpcCode::NotFound)
264 .with_success(GrpcCode::ResourceExhausted)
265 .with_success(GrpcCode::Unimplemented)
266 .with_success(GrpcCode::Unknown),
267 ))
268 .make_span_with(grpc_trace_fn),
269 )
270 .layer(HealthCheckLayer)
271 .layer(cors_for_grpc_web_layer())
272 .layer(GrpcWebLayer::new())
275 .layer(grpc::rate_limit_concurrent_connections(self.grpc_options))
276 .layer(grpc::rate_limit_per_ip(self.grpc_options)?)
277 .layer(grpc::ResolveClientIpLayer)
280 .layer(
284 AcceptHeaderLayer::new(&rpc_version, genesis.commitment())
285 .with_genesis_enforced_method("SubmitProvenTx")
286 .with_genesis_enforced_method("SubmitProvenTxBatch"),
287 )
288 .add_service(api_service)
289 .add_service(health_service)
290 .add_service(reflection_service)
292 .serve_with_incoming_shutdown(
293 TcpListenerStream::new(self.listener),
294 shutdown.clone().cancelled_owned(),
295 );
296 tasks.spawn("RPC server", async move { rpc.await.map_err(|e| anyhow::anyhow!(e)) });
297
298 tasks.join_next_or_cancelled(shutdown).await
299 }
300}
301
302fn log_node_ready(mode: &str, endpoint: impl Display, chain_tip: impl Display) {
303 info!(
304 target: LOG_TARGET,
305 {
306 service.name = "miden-node",
307 service.version = env!("CARGO_PKG_VERSION"),
308 node.role = mode,
309 rpc.listen = %endpoint,
310 block.number = %chain_tip,
311 },
312 "Node ready",
313 );
314}
315
316fn log_node_synchronizing(mode: &str, endpoint: impl Display, readiness_threshold: u32) {
317 info!(
318 target: LOG_TARGET,
319 {
320 service.name = "miden-node",
321 service.version = env!("CARGO_PKG_VERSION"),
322 node.role = mode,
323 rpc.listen = %endpoint,
324 sync.ready_threshold = readiness_threshold,
325 },
326 "Node started; synchronizing",
327 );
328}
329
330pub struct SequencerInternal {
342 pub listener: TcpListener,
344 pub block_producer: BlockProducerApi,
346 pub grpc_options: GrpcOptionsInternal,
348}
349
350impl SequencerInternal {
351 pub async fn serve(self, shutdown: CancellationToken) -> anyhow::Result<()> {
356 let endpoint = self
357 .listener
358 .local_addr()
359 .context("failed to read internal sequencer listen address")?;
360 info!(
361 target: LOG_TARGET,
362 { internal.listen = %endpoint },
363 "Internal sequencer server ready",
364 );
365
366 let service = SequencerInternalService { block_producer: self.block_producer };
367
368 tonic::transport::Server::builder()
371 .layer(CatchPanicLayer::custom(catch_panic_layer_fn))
372 .layer(TraceLayer::new_for_grpc().make_span_with(grpc_trace_fn))
373 .timeout(self.grpc_options.request_timeout)
374 .add_service(sequencer_api::service(service))
375 .serve_with_incoming_shutdown(
376 TcpListenerStream::new(self.listener),
377 shutdown.cancelled_owned(),
378 )
379 .await
380 .context("failed to serve internal sequencer API")
381 }
382}