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