Skip to main content

linera_rpc/grpc/
server.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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
147/// Handles batched forwarding of notifications to proxy and exporters.
148struct 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    /// Spawns batch send tasks up to max_tasks limit.
160    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    /// Returns true if there are no pending notifications and no in-flight tasks.
184    fn is_fully_drained(&self) -> bool {
185        self.pending_notifications.is_empty() && self.futures.is_empty()
186    }
187
188    /// Sends a batch of notifications to the proxy and exporters.
189    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        // Convert to proto notifications, logging any deserialization errors
196        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        // Collect chain_ids for error logging
213        let chain_ids: Vec<_> = batch.iter().map(|n| n.chain_id).collect();
214
215        // Send batch to proxy
216        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        // Send NewBlock notifications to exporters
240        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/// A gRPC server exposing a validator's worker as a network service.
273#[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
285/// A handle to a running [`GrpcServer`] task.
286pub struct GrpcServerHandle {
287    handle: TaskHandle<Result<(), GrpcError>>,
288}
289
290impl GrpcServerHandle {
291    /// Waits for the server task to complete.
292    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/// A Tower layer that records Prometheus metrics for gRPC requests.
316#[derive(Clone)]
317pub struct GrpcPrometheusMetricsMiddlewareLayer;
318
319/// The Tower service produced by [`GrpcPrometheusMetricsMiddlewareLayer`].
320#[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        // Extract traffic type from request extensions (set by OtelContextLayer).
355        // When opentelemetry is enabled but no baggage is set, defaults to "organic".
356        // When opentelemetry is disabled, defaults to "unknown".
357        #[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    /// Spawns the gRPC server on the given host and port, returning a handle to the task.
392    #[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        // Give the worker a shard-routing sender for cross-chain requests generated
413        // outside the normal `NetworkActions` return path (specifically, the
414        // `RevertConfirm`s emitted after resetting a corrupted chain).
415        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    /// Continuously waits for receiver to receive notifications and sends them to
523    /// the proxy in batches for improved throughput.
524    #[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                            // Drain all pending notifications before exiting
592                            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    /// Extracts traffic type from a tonic request's extensions.
704    #[cfg(feature = "opentelemetry")]
705    fn get_traffic_type<R>(request: &Request<R>) -> &'static str {
706        get_traffic_type_from_request(request)
707    }
708
709    /// Returns "unknown" when opentelemetry feature is disabled.
710    #[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        // Dispatch actions whether or not the proposal was accepted: a rejected
748        // proposal can still advance the manager's `current_round` (via
749        // `update_signed_proposal` on the `HasIncompatibleConfirmedVote` recovery
750        // path), and subscribers need the resulting `NewRound` notification.
751        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
1076/// Types which are proxyable and expose the appropriate methods to be handled
1077/// by the `GrpcProxy`
1078pub trait GrpcProxyable {
1079    /// Returns the chain ID this message is destined for, if any.
1080    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}