1use std::{
5 net::{IpAddr, SocketAddr},
6 str::FromStr,
7 task::{Context, Poll},
8};
9
10use futures::{
11 channel::mpsc, future::BoxFuture, stream::FuturesUnordered, FutureExt as _, StreamExt as _,
12};
13#[cfg(with_metrics)]
14use linera_base::time::Instant;
15use linera_base::{data_types::Blob, identifiers::ChainId, time::Duration};
16use linera_core::{
17 join_set_ext::JoinSet,
18 node::NodeError,
19 worker::{NetworkActions, Notification, Reason, WorkerState},
20 JoinSetExt as _, ProcessConfirmedBlockMode, TaskHandle,
21};
22use linera_storage::Storage;
23use tokio::sync::{broadcast::error::RecvError, oneshot};
24use tokio_util::sync::CancellationToken;
25use tonic::{transport::Channel, Request, Response, Status};
26use tower::{builder::ServiceBuilder, Layer, Service};
27use tracing::{debug, error, info, instrument, trace, warn};
28
29use super::{
30 api::{
31 self,
32 notifier_service_client::NotifierServiceClient,
33 validator_worker_client::ValidatorWorkerClient,
34 validator_worker_server::{ValidatorWorker as ValidatorWorkerRpc, ValidatorWorkerServer},
35 BlockProposal, ChainInfoQuery, ChainInfoResult, CrossChainRequest,
36 HandlePendingBlobRequest, LiteCertificate, PendingBlobRequest, PendingBlobResult,
37 },
38 pool::GrpcConnectionPool,
39 GrpcError, GRPC_MAX_MESSAGE_SIZE,
40};
41#[cfg(feature = "opentelemetry")]
42use crate::propagation::get_traffic_type_from_request;
43use crate::{
44 config::{CrossChainConfig, NotificationConfig, ShardId, ValidatorInternalNetworkConfig},
45 cross_chain_message_queue, HandleConfirmedCertificateRequest, HandleLiteCertRequest,
46 HandleTimeoutCertificateRequest, HandleValidatedCertificateRequest,
47};
48
49type CrossChainSender = mpsc::Sender<(linera_core::data_types::CrossChainRequest, ShardId)>;
50type NotificationSender = tokio::sync::broadcast::Sender<Notification>;
51
52#[cfg(with_metrics)]
53pub(crate) mod metrics {
54 use linera_base::prometheus_util::{
55 exponential_bucket_interval, linear_bucket_interval, register_histogram,
56 register_histogram_vec, register_int_counter, register_int_counter_vec,
57 };
58 use prometheus::{Histogram, HistogramVec, IntCounter, IntCounterVec};
59
60 use super::super::{ERROR_TYPE_LABEL, METHOD_NAME_LABEL, TRAFFIC_TYPE_LABEL};
61
62 linera_base::declare_metrics! {
63 pub static SERVER_REQUEST_LATENCY: HistogramVec =
64 register_histogram_vec(
65 "server_request_latency",
66 "Server request latency",
67 &[METHOD_NAME_LABEL, TRAFFIC_TYPE_LABEL],
68 linear_bucket_interval(1.0, 50.0, 5000.0),
69 );
70
71 pub static SERVER_REQUEST_COUNT: IntCounterVec =
72 register_int_counter_vec(
73 "server_request_count",
74 "Server request count",
75 &[METHOD_NAME_LABEL, TRAFFIC_TYPE_LABEL],
76 );
77
78 pub static SERVER_REQUEST_SUCCESS: IntCounterVec =
79 register_int_counter_vec(
80 "server_request_success",
81 "Server request success",
82 &[METHOD_NAME_LABEL, TRAFFIC_TYPE_LABEL],
83 );
84
85 pub static SERVER_REQUEST_ERROR: IntCounterVec =
86 register_int_counter_vec(
87 "server_request_error",
88 "Server request error",
89 &[METHOD_NAME_LABEL, TRAFFIC_TYPE_LABEL, ERROR_TYPE_LABEL],
90 );
91
92 pub static SERVER_REQUEST_CANCELLED: IntCounterVec =
93 register_int_counter_vec(
94 "server_request_cancelled",
95 "Server requests whose handler future was dropped before completion (e.g. client-side timeout / disconnect)",
96 &[METHOD_NAME_LABEL, TRAFFIC_TYPE_LABEL],
97 );
98
99 pub static CROSS_CHAIN_MESSAGE_CHANNEL_FULL: IntCounter =
100 register_int_counter(
101 "cross_chain_message_channel_full",
102 "Cross-chain message channel full",
103 );
104
105 pub static NOTIFICATIONS_SKIPPED_RECEIVER_LAG: IntCounter =
106 register_int_counter(
107 "notifications_skipped_receiver_lag",
108 "Number of notifications skipped because receiver lagged behind sender",
109 );
110
111 pub static NOTIFICATIONS_DROPPED_NO_RECEIVER: IntCounter =
112 register_int_counter(
113 "notifications_dropped_no_receiver",
114 "Number of notifications dropped because no receiver was available",
115 );
116
117 pub static NOTIFICATION_BATCH_SIZE: Histogram =
118 register_histogram(
119 "notification_batch_size",
120 "Number of notifications per batch sent to proxy",
121 exponential_bucket_interval(1.0, 250.0),
122 );
123
124 pub static NOTIFICATION_BATCHES_SENT: IntCounterVec =
125 register_int_counter_vec(
126 "notification_batches_sent",
127 "Total notification batches sent",
128 &["status"],
129 );
130 }
131}
132
133struct BatchForwarder {
135 nickname: String,
136 client: NotifierServiceClient<Channel>,
137 exporter_clients: Vec<NotifierServiceClient<Channel>>,
138 pending_notifications: Vec<Notification>,
139 futures: FuturesUnordered<BoxFuture<'static, ()>>,
140 batch_limit: usize,
141 max_tasks: usize,
142}
143
144impl BatchForwarder {
145 fn spawn_batches(&mut self) {
147 while !self.pending_notifications.is_empty() && self.futures.len() < self.max_tasks {
148 let chunk_size = std::cmp::min(self.batch_limit, self.pending_notifications.len());
149 let batch: Vec<Notification> = self.pending_notifications.drain(..chunk_size).collect();
150
151 #[cfg(with_metrics)]
152 metrics::NOTIFICATION_BATCH_SIZE.observe(batch.len() as f64);
153
154 let client = self.client.clone();
155 let exporter_clients = self.exporter_clients.clone();
156 let nickname = self.nickname.clone();
157
158 self.futures.push(
159 async move {
160 Self::send_batch(nickname, client, exporter_clients, batch).await;
161 }
162 .boxed(),
163 );
164 }
165 }
166
167 fn is_fully_drained(&self) -> bool {
169 self.pending_notifications.is_empty() && self.futures.is_empty()
170 }
171
172 async fn send_batch(
174 nickname: String,
175 mut client: NotifierServiceClient<Channel>,
176 mut exporter_clients: Vec<NotifierServiceClient<Channel>>,
177 batch: Vec<Notification>,
178 ) {
179 let mut proto_notifications = Vec::with_capacity(batch.len());
181 for notification in &batch {
182 match notification.clone().try_into() {
183 Ok(proto) => proto_notifications.push(proto),
184 Err(error) => {
185 warn!(
186 %error,
187 nickname,
188 ?notification.chain_id,
189 ?notification.reason,
190 "could not deserialize notification"
191 );
192 }
193 }
194 }
195
196 let chain_ids: Vec<_> = batch.iter().map(|n| n.chain_id).collect();
198
199 let request = Request::new(api::NotificationBatch {
201 notifications: proto_notifications.clone(),
202 });
203 let result = client.notify_batch(request).await;
204
205 #[cfg(with_metrics)]
206 {
207 let status = if result.is_ok() { "success" } else { "error" };
208 metrics::NOTIFICATION_BATCHES_SENT
209 .with_label_values(&[status])
210 .inc();
211 }
212
213 if let Err(error) = result {
214 error!(
215 %error,
216 nickname,
217 batch_size = proto_notifications.len(),
218 ?chain_ids,
219 "proxy: could not send notification batch",
220 );
221 }
222
223 let new_block_notifications: Vec<_> = batch
225 .iter()
226 .filter(|n| matches!(n.reason, Reason::NewBlock { .. }))
227 .collect();
228
229 let exporter_notifications: Vec<api::Notification> = new_block_notifications
230 .iter()
231 .filter_map(|n| (*n).clone().try_into().ok())
232 .collect();
233
234 if !exporter_notifications.is_empty() {
235 let exporter_chain_ids: Vec<_> =
236 new_block_notifications.iter().map(|n| n.chain_id).collect();
237
238 for exporter_client in &mut exporter_clients {
239 let request = Request::new(api::NotificationBatch {
240 notifications: exporter_notifications.clone(),
241 });
242 if let Err(error) = exporter_client.notify_batch(request).await {
243 error!(
244 %error,
245 nickname,
246 batch_size = exporter_notifications.len(),
247 ?exporter_chain_ids,
248 "block exporter: could not send notification batch",
249 );
250 }
251 }
252 }
253 }
254}
255
256#[derive(Clone)]
258pub struct GrpcServer<S>
259where
260 S: Storage,
261{
262 state: WorkerState<S>,
263 shard_id: ShardId,
264 network: ValidatorInternalNetworkConfig,
265 cross_chain_sender: CrossChainSender,
266 notification_sender: NotificationSender,
267}
268
269pub struct GrpcServerHandle {
271 handle: TaskHandle<Result<(), GrpcError>>,
272}
273
274impl GrpcServerHandle {
275 pub async fn join(self) -> Result<(), GrpcError> {
277 self.handle.await?
278 }
279}
280
281#[cfg(with_metrics)]
282struct ServerRequestCancellationGuard {
283 method_name: String,
284 traffic_type: &'static str,
285 completed: bool,
286}
287
288#[cfg(with_metrics)]
289impl Drop for ServerRequestCancellationGuard {
290 fn drop(&mut self) {
291 if !self.completed {
292 metrics::SERVER_REQUEST_CANCELLED
293 .with_label_values(&[&self.method_name, self.traffic_type])
294 .inc();
295 }
296 }
297}
298
299#[derive(Clone)]
301pub struct GrpcPrometheusMetricsMiddlewareLayer;
302
303#[derive(Clone)]
305pub struct GrpcPrometheusMetricsMiddlewareService<T> {
306 service: T,
307}
308
309impl<S> Layer<S> for GrpcPrometheusMetricsMiddlewareLayer {
310 type Service = GrpcPrometheusMetricsMiddlewareService<S>;
311
312 fn layer(&self, service: S) -> Self::Service {
313 GrpcPrometheusMetricsMiddlewareService { service }
314 }
315}
316
317impl<S, B> Service<http::Request<B>> for GrpcPrometheusMetricsMiddlewareService<S>
318where
319 S::Future: Send + 'static,
320 S: Service<http::Request<B>> + std::marker::Send,
321 B: Send + 'static,
322{
323 type Response = S::Response;
324 type Error = S::Error;
325 type Future = BoxFuture<'static, Result<S::Response, S::Error>>;
326
327 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
328 self.service.poll_ready(cx)
329 }
330
331 fn call(&mut self, request: http::Request<B>) -> Self::Future {
332 #[cfg(with_metrics)]
333 let start = Instant::now();
334
335 #[cfg(with_metrics)]
336 let method_name = super::extract_grpc_method_name(request.uri().path()).to_owned();
337
338 #[cfg(all(with_metrics, feature = "opentelemetry"))]
342 let traffic_type: &'static str = get_traffic_type_from_request(&request);
343 #[cfg(all(with_metrics, not(feature = "opentelemetry")))]
344 let traffic_type: &'static str = "unknown";
345
346 let future = self.service.call(request);
347 async move {
348 #[cfg(with_metrics)]
349 let mut cancellation_guard = ServerRequestCancellationGuard {
350 method_name,
351 traffic_type,
352 completed: false,
353 };
354 let response = future.await?;
355 #[cfg(with_metrics)]
356 {
357 cancellation_guard.completed = true;
358 metrics::SERVER_REQUEST_LATENCY
359 .with_label_values(&[&cancellation_guard.method_name, traffic_type])
360 .observe(start.elapsed().as_secs_f64() * 1000.0);
361 metrics::SERVER_REQUEST_COUNT
362 .with_label_values(&[&cancellation_guard.method_name, traffic_type])
363 .inc();
364 }
365 Ok(response)
366 }
367 .boxed()
368 }
369}
370
371impl<S> GrpcServer<S>
372where
373 S: Storage + Clone + Send + Sync + 'static,
374{
375 #[expect(clippy::too_many_arguments)]
377 pub fn spawn(
378 host: String,
379 port: u16,
380 state: WorkerState<S>,
381 shard_id: ShardId,
382 internal_network: ValidatorInternalNetworkConfig,
383 cross_chain_config: &CrossChainConfig,
384 notification_config: &NotificationConfig,
385 shutdown_signal: CancellationToken,
386 join_set: &mut JoinSet,
387 ) -> GrpcServerHandle {
388 info!(
389 "spawning gRPC server on {}:{} for shard {}",
390 host, port, shard_id
391 );
392
393 let (cross_chain_sender, cross_chain_receiver) =
394 mpsc::channel(cross_chain_config.queue_size);
395
396 let state = {
400 let routing_network = internal_network.clone();
401 let routing_sender = cross_chain_sender.clone();
402 state.with_outbound_cross_chain_sender(std::sync::Arc::new(move |request| {
403 let shard_id = routing_network.get_shard_id(request.target_chain_id());
404 if let Err(error) = routing_sender.clone().try_send((request, shard_id)) {
405 error!(%error, "dropping cross-chain request");
406 }
407 }))
408 };
409
410 let (notification_sender, _) =
411 tokio::sync::broadcast::channel(notification_config.notification_queue_size);
412
413 join_set.spawn_task({
414 info!(
415 nickname = state.nickname(),
416 "spawning cross-chain queries thread on {} for shard {}", host, shard_id
417 );
418 Self::forward_cross_chain_queries(
419 state.nickname().to_string(),
420 internal_network.clone(),
421 cross_chain_config.max_retries,
422 Duration::from_millis(cross_chain_config.retry_delay_ms),
423 Duration::from_millis(cross_chain_config.max_backoff_ms),
424 Duration::from_millis(cross_chain_config.sender_delay_ms),
425 cross_chain_config.sender_failure_rate,
426 shard_id,
427 cross_chain_receiver,
428 )
429 });
430
431 let mut exporter_forwarded = false;
432 for proxy in &internal_network.proxies {
433 let receiver = notification_sender.subscribe();
434 join_set.spawn_task({
435 info!(
436 nickname = state.nickname(),
437 "spawning notifications thread on {} for shard {}", host, shard_id
438 );
439 let exporter_addresses = if exporter_forwarded {
440 vec![]
441 } else {
442 exporter_forwarded = true;
443 internal_network.exporter_addresses()
444 };
445 Self::forward_notifications(
446 state.nickname().to_string(),
447 proxy.internal_address(&internal_network.protocol),
448 exporter_addresses,
449 receiver,
450 notification_config.clone(),
451 )
452 });
453 }
454
455 let (health_reporter, health_service) = tonic_health::server::health_reporter();
456
457 let grpc_server = GrpcServer {
458 state,
459 shard_id,
460 network: internal_network,
461 cross_chain_sender,
462 notification_sender,
463 };
464
465 let worker_node = ValidatorWorkerServer::new(grpc_server)
466 .max_encoding_message_size(GRPC_MAX_MESSAGE_SIZE)
467 .max_decoding_message_size(GRPC_MAX_MESSAGE_SIZE);
468
469 let handle = join_set.spawn_task(async move {
470 let server_address = SocketAddr::from((IpAddr::from_str(&host)?, port));
471
472 let reflection_service = tonic_reflection::server::Builder::configure()
473 .register_encoded_file_descriptor_set(crate::FILE_DESCRIPTOR_SET)
474 .build_v1()?;
475
476 health_reporter
477 .set_serving::<ValidatorWorkerServer<Self>>()
478 .await;
479
480 #[cfg(feature = "opentelemetry")]
481 let server = tonic::transport::Server::builder().layer(
482 ServiceBuilder::new()
483 .layer(crate::propagation::OtelContextLayer)
484 .layer(GrpcPrometheusMetricsMiddlewareLayer)
485 .into_inner(),
486 );
487 #[cfg(not(feature = "opentelemetry"))]
488 let server = tonic::transport::Server::builder().layer(
489 ServiceBuilder::new()
490 .layer(GrpcPrometheusMetricsMiddlewareLayer)
491 .into_inner(),
492 );
493 server
494 .max_concurrent_streams(Some(10_000))
498 .add_service(health_service)
499 .add_service(reflection_service)
500 .add_service(worker_node)
501 .serve_with_shutdown(server_address, shutdown_signal.cancelled_owned())
502 .await?;
503
504 Ok(())
505 });
506
507 GrpcServerHandle { handle }
508 }
509
510 #[instrument(skip(receiver, config))]
513 async fn forward_notifications(
514 nickname: String,
515 proxy_address: String,
516 exporter_addresses: Vec<String>,
517 mut receiver: tokio::sync::broadcast::Receiver<Notification>,
518 config: NotificationConfig,
519 ) {
520 let channel = tonic::transport::Channel::from_shared(proxy_address.clone())
521 .expect("Proxy URI should be valid")
522 .connect_lazy();
523 let client = NotifierServiceClient::new(channel)
524 .max_encoding_message_size(GRPC_MAX_MESSAGE_SIZE)
525 .max_decoding_message_size(GRPC_MAX_MESSAGE_SIZE);
526
527 let exporter_clients: Vec<NotifierServiceClient<Channel>> = exporter_addresses
528 .iter()
529 .map(|address| {
530 let channel = tonic::transport::Channel::from_shared(address.clone())
531 .expect("Exporter URI should be valid")
532 .connect_lazy();
533 NotifierServiceClient::new(channel)
534 .max_encoding_message_size(GRPC_MAX_MESSAGE_SIZE)
535 .max_decoding_message_size(GRPC_MAX_MESSAGE_SIZE)
536 })
537 .collect::<Vec<_>>();
538
539 let mut forwarder = BatchForwarder {
540 nickname: nickname.clone(),
541 client,
542 exporter_clients,
543 pending_notifications: Vec::new(),
544 futures: FuturesUnordered::new(),
545 batch_limit: config.notification_batch_size,
546 max_tasks: config.notification_max_in_flight,
547 };
548
549 loop {
550 tokio::select! {
551 biased;
552
553 result = receiver.recv() => {
554 match result {
555 Ok(notification) => {
556 forwarder.pending_notifications.push(notification);
557
558 if forwarder.futures.is_empty()
559 || (forwarder.pending_notifications.len() >= forwarder.batch_limit
560 && forwarder.futures.len() < forwarder.max_tasks) {
561 forwarder.spawn_batches();
562 }
563 }
564 Err(RecvError::Lagged(skipped_count)) => {
565 warn!(
566 nickname,
567 skipped_count, "notification receiver lagged, messages were skipped"
568 );
569 #[cfg(with_metrics)]
570 metrics::NOTIFICATIONS_SKIPPED_RECEIVER_LAG
571 .inc_by(skipped_count);
572 }
573 Err(RecvError::Closed) => {
574 warn!(
575 nickname,
576 "notification channel closed, draining pending notifications"
577 );
578 loop {
580 forwarder.spawn_batches();
581 if forwarder.is_fully_drained() {
582 break;
583 }
584 forwarder.futures.next().await;
585 }
586 break;
587 }
588 }
589 }
590
591 Some(()) = forwarder.futures.next() => {
592 forwarder.spawn_batches();
593 }
594 }
595 }
596 }
597
598 fn handle_network_actions(&self, actions: NetworkActions) {
599 let mut cross_chain_sender = self.cross_chain_sender.clone();
600 let notification_sender = self.notification_sender.clone();
601
602 for request in actions.cross_chain_requests {
603 let shard_id = self.network.get_shard_id(request.target_chain_id());
604 trace!(
605 source_shard_id = self.shard_id,
606 target_shard_id = shard_id,
607 "Scheduling cross-chain query",
608 );
609
610 if let Err(error) = cross_chain_sender.try_send((request, shard_id)) {
611 error!(%error, "dropping cross-chain request");
612 #[cfg(with_metrics)]
613 if error.is_full() {
614 metrics::CROSS_CHAIN_MESSAGE_CHANNEL_FULL.inc();
615 }
616 }
617 }
618
619 for notification in actions.notifications {
620 trace!("Scheduling notification query");
621 if let Err(error) = notification_sender.send(notification) {
622 error!(%error, "dropping notification");
623 #[cfg(with_metrics)]
624 metrics::NOTIFICATIONS_DROPPED_NO_RECEIVER.inc();
625 }
626 }
627 }
628
629 #[instrument(skip_all, fields(nickname, %this_shard))]
630 #[expect(clippy::too_many_arguments)]
631 async fn forward_cross_chain_queries(
632 nickname: String,
633 network: ValidatorInternalNetworkConfig,
634 cross_chain_max_retries: u32,
635 cross_chain_retry_delay: Duration,
636 cross_chain_max_backoff: Duration,
637 cross_chain_sender_delay: Duration,
638 cross_chain_sender_failure_rate: f32,
639 this_shard: ShardId,
640 receiver: mpsc::Receiver<(linera_core::data_types::CrossChainRequest, ShardId)>,
641 ) {
642 let pool = GrpcConnectionPool::default();
643 let handle_request =
644 move |shard_id: ShardId, request: linera_core::data_types::CrossChainRequest| {
645 let channel_result = pool.channel(network.shard(shard_id).http_address());
646 async move {
647 let mut client = ValidatorWorkerClient::new(channel_result?)
648 .max_encoding_message_size(GRPC_MAX_MESSAGE_SIZE)
649 .max_decoding_message_size(GRPC_MAX_MESSAGE_SIZE);
650 client
651 .handle_cross_chain_request(Request::new(request.try_into()?))
652 .await?;
653 anyhow::Result::<_, anyhow::Error>::Ok(())
654 }
655 };
656 cross_chain_message_queue::forward_cross_chain_queries(
657 nickname,
658 cross_chain_max_retries,
659 cross_chain_retry_delay,
660 cross_chain_max_backoff,
661 cross_chain_sender_delay,
662 cross_chain_sender_failure_rate,
663 this_shard,
664 receiver,
665 handle_request,
666 )
667 .await;
668 }
669
670 fn log_request_success(method_name: &str, traffic_type: &str) {
671 #![cfg_attr(not(with_metrics), allow(unused_variables))]
672 #[cfg(with_metrics)]
673 metrics::SERVER_REQUEST_SUCCESS
674 .with_label_values(&[method_name, traffic_type])
675 .inc();
676 }
677
678 fn log_request_error(method_name: &str, traffic_type: &str, error_type: &str) {
679 #![cfg_attr(not(with_metrics), allow(unused_variables))]
680 #[cfg(with_metrics)]
681 metrics::SERVER_REQUEST_ERROR
682 .with_label_values(&[method_name, traffic_type, error_type])
683 .inc();
684 }
685
686 #[cfg(feature = "opentelemetry")]
688 fn get_traffic_type<R>(request: &Request<R>) -> &'static str {
689 get_traffic_type_from_request(request)
690 }
691
692 #[cfg(not(feature = "opentelemetry"))]
694 fn get_traffic_type<R>(_request: &Request<R>) -> &'static str {
695 "unknown"
696 }
697
698 fn log_error(&self, error: &linera_core::worker::WorkerError, context: &str) {
699 let nickname = self.state.nickname();
700 if error.is_local() {
701 error!(nickname, %error, "{}", context);
702 } else {
703 debug!(nickname, %error, "{}", context);
704 }
705 }
706}
707
708#[tonic::async_trait]
709impl<S> ValidatorWorkerRpc for GrpcServer<S>
710where
711 S: Storage + Clone + Send + Sync + 'static,
712{
713 #[instrument(
714 target = "grpc_server",
715 skip_all,
716 err,
717 fields(
718 nickname = self.state.nickname(),
719 chain_id = ?request.get_ref().chain_id()
720 )
721 )]
722 async fn handle_block_proposal(
723 &self,
724 request: Request<BlockProposal>,
725 ) -> Result<Response<ChainInfoResult>, Status> {
726 let traffic_type = Self::get_traffic_type(&request);
727 let proposal = request.into_inner().try_into()?;
728 trace!(?proposal, "Handling block proposal");
729 let (result, actions) = self.state.clone().handle_block_proposal(proposal).await;
730 self.handle_network_actions(actions);
735 Ok(Response::new(match result {
736 Ok(info) => {
737 Self::log_request_success("handle_block_proposal", traffic_type);
738 info.try_into()?
739 }
740 Err(error) => {
741 Self::log_request_error("handle_block_proposal", traffic_type, &error.error_type());
742 self.log_error(&error, "Failed to handle block proposal");
743 NodeError::from(error).try_into()?
744 }
745 }))
746 }
747
748 #[instrument(
749 target = "grpc_server",
750 skip_all,
751 err,
752 fields(
753 nickname = self.state.nickname(),
754 chain_id = ?request.get_ref().chain_id()
755 )
756 )]
757 async fn handle_lite_certificate(
758 &self,
759 request: Request<LiteCertificate>,
760 ) -> Result<Response<ChainInfoResult>, Status> {
761 let traffic_type = Self::get_traffic_type(&request);
762 let HandleLiteCertRequest {
763 certificate,
764 wait_for_outgoing_messages,
765 } = request.into_inner().try_into()?;
766 trace!(?certificate, "Handling lite certificate");
767 let (sender, receiver) = wait_for_outgoing_messages.then(oneshot::channel).unzip();
768 match Box::pin(
769 self.state
770 .clone()
771 .handle_lite_certificate(certificate, sender),
772 )
773 .await
774 {
775 Ok((info, actions)) => {
776 Self::log_request_success("handle_lite_certificate", traffic_type);
777 self.handle_network_actions(actions);
778 if let Some(receiver) = receiver {
779 if let Err(e) = receiver.await {
780 error!("Failed to wait for message delivery: {e}");
781 }
782 }
783 Ok(Response::new(info.try_into()?))
784 }
785 Err(error) => {
786 Self::log_request_error(
787 "handle_lite_certificate",
788 traffic_type,
789 &error.error_type(),
790 );
791 self.log_error(&error, "Failed to handle lite certificate");
792 Ok(Response::new(NodeError::from(error).try_into()?))
793 }
794 }
795 }
796
797 #[instrument(
798 target = "grpc_server",
799 skip_all,
800 err,
801 fields(
802 nickname = self.state.nickname(),
803 chain_id = ?request.get_ref().chain_id()
804 )
805 )]
806 async fn handle_confirmed_certificate(
807 &self,
808 request: Request<api::HandleConfirmedCertificateRequest>,
809 ) -> Result<Response<ChainInfoResult>, Status> {
810 let traffic_type = Self::get_traffic_type(&request);
811 let HandleConfirmedCertificateRequest {
812 certificate,
813 wait_for_outgoing_messages,
814 } = request.into_inner().try_into()?;
815 trace!(?certificate, "Handling certificate");
816 let (sender, receiver) = wait_for_outgoing_messages.then(oneshot::channel).unzip();
817 match self
818 .state
819 .clone()
820 .handle_confirmed_certificate(certificate, ProcessConfirmedBlockMode::Auto, sender)
821 .await
822 {
823 Ok((info, actions)) => {
824 Self::log_request_success("handle_confirmed_certificate", traffic_type);
825 self.handle_network_actions(actions);
826 if let Some(receiver) = receiver {
827 if let Err(e) = receiver.await {
828 error!("Failed to wait for message delivery: {e}");
829 }
830 }
831 Ok(Response::new(info.try_into()?))
832 }
833 Err(error) => {
834 Self::log_request_error(
835 "handle_confirmed_certificate",
836 traffic_type,
837 &error.error_type(),
838 );
839 self.log_error(&error, "Failed to handle confirmed certificate");
840 Ok(Response::new(NodeError::from(error).try_into()?))
841 }
842 }
843 }
844
845 #[instrument(
846 target = "grpc_server",
847 skip_all,
848 err,
849 fields(
850 nickname = self.state.nickname(),
851 chain_id = ?request.get_ref().chain_id()
852 )
853 )]
854 async fn handle_validated_certificate(
855 &self,
856 request: Request<api::HandleValidatedCertificateRequest>,
857 ) -> Result<Response<ChainInfoResult>, Status> {
858 let traffic_type = Self::get_traffic_type(&request);
859 let HandleValidatedCertificateRequest { certificate } = request.into_inner().try_into()?;
860 trace!(?certificate, "Handling certificate");
861 match self
862 .state
863 .clone()
864 .handle_validated_certificate(certificate)
865 .await
866 {
867 Ok((info, actions)) => {
868 Self::log_request_success("handle_validated_certificate", traffic_type);
869 self.handle_network_actions(actions);
870 Ok(Response::new(info.try_into()?))
871 }
872 Err(error) => {
873 Self::log_request_error(
874 "handle_validated_certificate",
875 traffic_type,
876 &error.error_type(),
877 );
878 self.log_error(&error, "Failed to handle validated certificate");
879 Ok(Response::new(NodeError::from(error).try_into()?))
880 }
881 }
882 }
883
884 #[instrument(
885 target = "grpc_server",
886 skip_all,
887 err,
888 fields(
889 nickname = self.state.nickname(),
890 chain_id = ?request.get_ref().chain_id()
891 )
892 )]
893 async fn handle_timeout_certificate(
894 &self,
895 request: Request<api::HandleTimeoutCertificateRequest>,
896 ) -> Result<Response<ChainInfoResult>, Status> {
897 let traffic_type = Self::get_traffic_type(&request);
898 let HandleTimeoutCertificateRequest { certificate } = request.into_inner().try_into()?;
899 trace!(?certificate, "Handling Timeout certificate");
900 match self
901 .state
902 .clone()
903 .handle_timeout_certificate(certificate)
904 .await
905 {
906 Ok((info, _actions)) => {
907 Self::log_request_success("handle_timeout_certificate", traffic_type);
908 Ok(Response::new(info.try_into()?))
909 }
910 Err(error) => {
911 Self::log_request_error(
912 "handle_timeout_certificate",
913 traffic_type,
914 &error.error_type(),
915 );
916 self.log_error(&error, "Failed to handle timeout certificate");
917 Ok(Response::new(NodeError::from(error).try_into()?))
918 }
919 }
920 }
921
922 #[instrument(
923 target = "grpc_server",
924 skip_all,
925 err,
926 fields(
927 nickname = self.state.nickname(),
928 chain_id = ?request.get_ref().chain_id()
929 )
930 )]
931 async fn handle_chain_info_query(
932 &self,
933 request: Request<ChainInfoQuery>,
934 ) -> Result<Response<ChainInfoResult>, Status> {
935 let traffic_type = Self::get_traffic_type(&request);
936 let query = request.into_inner().try_into()?;
937 trace!(?query, "Handling chain info query");
938 match self.state.clone().handle_chain_info_query(query).await {
939 Ok(info) => {
940 Self::log_request_success("handle_chain_info_query", traffic_type);
941 Ok(Response::new(info.try_into()?))
942 }
943 Err(error) => {
944 Self::log_request_error(
945 "handle_chain_info_query",
946 traffic_type,
947 &error.error_type(),
948 );
949 self.log_error(&error, "Failed to handle chain info query");
950 Ok(Response::new(NodeError::from(error).try_into()?))
951 }
952 }
953 }
954
955 #[instrument(
956 target = "grpc_server",
957 skip_all,
958 err,
959 fields(
960 nickname = self.state.nickname(),
961 chain_id = ?request.get_ref().chain_id()
962 )
963 )]
964 async fn download_pending_blob(
965 &self,
966 request: Request<PendingBlobRequest>,
967 ) -> Result<Response<PendingBlobResult>, Status> {
968 let traffic_type = Self::get_traffic_type(&request);
969 let (chain_id, blob_id) = request.into_inner().try_into()?;
970 trace!(?blob_id, "Download pending blob");
971 match self
972 .state
973 .clone()
974 .download_pending_blob(chain_id, blob_id)
975 .await
976 {
977 Ok(blob) => {
978 Self::log_request_success("download_pending_blob", traffic_type);
979 Ok(Response::new(blob.content().clone().try_into()?))
980 }
981 Err(error) => {
982 Self::log_request_error("download_pending_blob", traffic_type, &error.error_type());
983 self.log_error(&error, "Failed to download pending blob");
984 Ok(Response::new(NodeError::from(error).try_into()?))
985 }
986 }
987 }
988
989 #[instrument(
990 target = "grpc_server",
991 skip_all,
992 err,
993 fields(
994 nickname = self.state.nickname(),
995 chain_id = ?request.get_ref().chain_id()
996 )
997 )]
998 async fn handle_pending_blob(
999 &self,
1000 request: Request<HandlePendingBlobRequest>,
1001 ) -> Result<Response<ChainInfoResult>, Status> {
1002 let traffic_type = Self::get_traffic_type(&request);
1003 let (chain_id, blob_content) = request.into_inner().try_into()?;
1004 let blob = Blob::new(blob_content);
1005 let blob_id = blob.id();
1006 trace!(?blob_id, "Handle pending blob");
1007 match self.state.clone().handle_pending_blob(chain_id, blob).await {
1008 Ok(info) => {
1009 Self::log_request_success("handle_pending_blob", traffic_type);
1010 Ok(Response::new(info.try_into()?))
1011 }
1012 Err(error) => {
1013 Self::log_request_error("handle_pending_blob", traffic_type, &error.error_type());
1014 self.log_error(&error, "Failed to handle pending blob");
1015 Ok(Response::new(NodeError::from(error).try_into()?))
1016 }
1017 }
1018 }
1019
1020 #[instrument(
1021 target = "grpc_server",
1022 skip_all,
1023 err,
1024 fields(
1025 nickname = self.state.nickname(),
1026 chain_id = ?request.get_ref().chain_id()
1027 )
1028 )]
1029 async fn handle_cross_chain_request(
1030 &self,
1031 request: Request<CrossChainRequest>,
1032 ) -> Result<Response<()>, Status> {
1033 let traffic_type = Self::get_traffic_type(&request);
1034 let cross_chain_request = request.into_inner().try_into()?;
1035 trace!(?cross_chain_request, "Handling cross-chain request");
1036 match self
1037 .state
1038 .clone()
1039 .handle_cross_chain_request(cross_chain_request)
1040 .await
1041 {
1042 Ok(actions) => {
1043 Self::log_request_success("handle_cross_chain_request", traffic_type);
1044 self.handle_network_actions(actions)
1045 }
1046 Err(error) => {
1047 Self::log_request_error(
1048 "handle_cross_chain_request",
1049 traffic_type,
1050 &error.error_type(),
1051 );
1052 self.log_error(&error, "Failed to handle cross-chain request");
1053 }
1054 }
1055 Ok(Response::new(()))
1056 }
1057}
1058
1059pub trait GrpcProxyable {
1062 fn chain_id(&self) -> Option<ChainId>;
1064}
1065
1066impl GrpcProxyable for BlockProposal {
1067 fn chain_id(&self) -> Option<ChainId> {
1068 self.chain_id.clone()?.try_into().ok()
1069 }
1070}
1071
1072impl GrpcProxyable for LiteCertificate {
1073 fn chain_id(&self) -> Option<ChainId> {
1074 self.chain_id.clone()?.try_into().ok()
1075 }
1076}
1077
1078impl GrpcProxyable for api::HandleConfirmedCertificateRequest {
1079 fn chain_id(&self) -> Option<ChainId> {
1080 self.chain_id.clone()?.try_into().ok()
1081 }
1082}
1083
1084impl GrpcProxyable for api::HandleTimeoutCertificateRequest {
1085 fn chain_id(&self) -> Option<ChainId> {
1086 self.chain_id.clone()?.try_into().ok()
1087 }
1088}
1089
1090impl GrpcProxyable for api::HandleValidatedCertificateRequest {
1091 fn chain_id(&self) -> Option<ChainId> {
1092 self.chain_id.clone()?.try_into().ok()
1093 }
1094}
1095
1096impl GrpcProxyable for ChainInfoQuery {
1097 fn chain_id(&self) -> Option<ChainId> {
1098 self.chain_id.clone()?.try_into().ok()
1099 }
1100}
1101
1102impl GrpcProxyable for PendingBlobRequest {
1103 fn chain_id(&self) -> Option<ChainId> {
1104 self.chain_id.clone()?.try_into().ok()
1105 }
1106}
1107
1108impl GrpcProxyable for HandlePendingBlobRequest {
1109 fn chain_id(&self) -> Option<ChainId> {
1110 self.chain_id.clone()?.try_into().ok()
1111 }
1112}
1113
1114impl GrpcProxyable for CrossChainRequest {
1115 fn chain_id(&self) -> Option<ChainId> {
1116 use super::api::cross_chain_request::Inner;
1117
1118 match self.inner.as_ref()? {
1119 Inner::UpdateRecipient(api::UpdateRecipient { recipient, .. })
1120 | Inner::ConfirmUpdatedRecipient(api::ConfirmUpdatedRecipient { recipient, .. })
1121 | Inner::RevertConfirm(api::RevertConfirm { recipient, .. }) => {
1122 recipient.clone()?.try_into().ok()
1123 }
1124 }
1125 }
1126}