Skip to main content

linera_rpc/simple/
server.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{pin::Pin, sync::Arc};
5
6use async_trait::async_trait;
7use futures::{channel::mpsc, lock::Mutex, Stream, StreamExt as _};
8use linera_base::{data_types::Blob, identifiers::ChainId, time::Duration};
9use linera_core::{
10    data_types::CrossChainRequest,
11    node::NodeError,
12    worker::{NetworkActions, Notification, WorkerError, WorkerState},
13    JoinSetExt as _, ProcessConfirmedBlockMode,
14};
15use linera_storage::Storage;
16use tokio::{sync, sync::oneshot, task::JoinSet};
17use tokio_stream::wrappers::BroadcastStream;
18use tokio_util::sync::CancellationToken;
19use tracing::{debug, error, info, instrument};
20
21use super::transport::{MessageHandler, ServerHandle, TransportProtocol};
22use crate::{
23    config::{CrossChainConfig, ShardId, ValidatorInternalNetworkPreConfig},
24    cross_chain_message_queue, RpcMessage,
25};
26
27/// A server handling RPC requests over a simple (UDP or TCP) transport.
28#[derive(Clone)]
29pub struct Server<S>
30where
31    S: Storage,
32{
33    network: ValidatorInternalNetworkPreConfig<TransportProtocol>,
34    host: String,
35    port: u16,
36    state: WorkerState<S>,
37    shard_id: ShardId,
38    cross_chain_config: CrossChainConfig,
39    // Stats
40    packets_processed: u64,
41    user_errors: u64,
42}
43
44impl<S> Server<S>
45where
46    S: Storage,
47{
48    /// Creates a new server with the given network configuration and worker state.
49    pub fn new(
50        network: ValidatorInternalNetworkPreConfig<TransportProtocol>,
51        host: String,
52        port: u16,
53        state: WorkerState<S>,
54        shard_id: ShardId,
55        cross_chain_config: CrossChainConfig,
56    ) -> Self {
57        Self {
58            network,
59            host,
60            port,
61            state,
62            shard_id,
63            cross_chain_config,
64            packets_processed: 0,
65            user_errors: 0,
66        }
67    }
68
69    /// Returns the number of packets processed so far.
70    pub fn packets_processed(&self) -> u64 {
71        self.packets_processed
72    }
73
74    /// Returns the number of user errors encountered so far.
75    pub fn user_errors(&self) -> u64 {
76        self.user_errors
77    }
78}
79
80impl<S> Server<S>
81where
82    S: Storage + Clone + Send + Sync + 'static,
83{
84    #[expect(clippy::too_many_arguments)]
85    async fn forward_cross_chain_queries(
86        nickname: String,
87        network: ValidatorInternalNetworkPreConfig<TransportProtocol>,
88        cross_chain_max_retries: u32,
89        cross_chain_retry_delay: Duration,
90        cross_chain_max_backoff: Duration,
91        cross_chain_sender_delay: Duration,
92        cross_chain_sender_failure_rate: f32,
93        this_shard: ShardId,
94        receiver: mpsc::Receiver<(CrossChainRequest, ShardId)>,
95    ) {
96        let pool = Arc::new(Mutex::new(
97            network
98                .protocol
99                .make_outgoing_connection_pool()
100                .await
101                .expect("Initialization should not fail"),
102        ));
103        let handle_request = move |shard_id, request| {
104            let pool = pool.clone();
105            let shard = network.shard(shard_id);
106            let remote_address = format!("{}:{}", shard.host, shard.port);
107            let message = RpcMessage::CrossChainRequest(Box::new(request));
108            async move {
109                pool.lock()
110                    .await
111                    .send_message_to(message.clone(), &remote_address)
112                    .await?;
113                anyhow::Result::<_, anyhow::Error>::Ok(())
114            }
115        };
116        cross_chain_message_queue::forward_cross_chain_queries(
117            nickname,
118            cross_chain_max_retries,
119            cross_chain_retry_delay,
120            cross_chain_max_backoff,
121            cross_chain_sender_delay,
122            cross_chain_sender_failure_rate,
123            this_shard,
124            receiver,
125            handle_request,
126        )
127        .await;
128    }
129
130    /// Spawns the server, returning a handle to track its completion.
131    pub fn spawn(
132        mut self,
133        shutdown_signal: CancellationToken,
134        join_set: &mut JoinSet<()>,
135    ) -> ServerHandle {
136        info!(
137            "Listening to {:?} traffic on {}:{}",
138            self.network.protocol, self.host, self.port
139        );
140        let address = (self.host.clone(), self.port);
141
142        let (cross_chain_sender, cross_chain_receiver) =
143            mpsc::channel(self.cross_chain_config.queue_size);
144
145        let (notification_sender, _) = sync::broadcast::channel(1000);
146
147        // Give the worker a shard-routing sender for cross-chain requests generated
148        // outside the normal `NetworkActions` return path (specifically, the
149        // `RevertConfirm`s emitted after resetting a corrupted chain).
150        {
151            let routing_network = self.network.clone();
152            let routing_sender = cross_chain_sender.clone();
153            self.state = self
154                .state
155                .clone()
156                .with_outbound_cross_chain_sender(Arc::new(move |request| {
157                    let shard_id = routing_network.get_shard_id(request.target_chain_id());
158                    if let Err(error) = routing_sender.clone().try_send((request, shard_id)) {
159                        tracing::error!(%error, "dropping cross-chain request");
160                    }
161                }));
162        }
163
164        join_set.spawn_task(Self::forward_cross_chain_queries(
165            self.state.nickname().to_string(),
166            self.network.clone(),
167            self.cross_chain_config.max_retries,
168            Duration::from_millis(self.cross_chain_config.retry_delay_ms),
169            Duration::from_millis(self.cross_chain_config.max_backoff_ms),
170            Duration::from_millis(self.cross_chain_config.sender_delay_ms),
171            self.cross_chain_config.sender_failure_rate,
172            self.shard_id,
173            cross_chain_receiver,
174        ));
175
176        let protocol = self.network.protocol;
177        let state = RunningServerState {
178            server: self,
179            cross_chain_sender,
180            notification_sender,
181        };
182        // Launch server for the appropriate protocol.
183        protocol.spawn_server(address, state, shutdown_signal, join_set)
184    }
185}
186
187#[derive(Clone)]
188struct RunningServerState<S>
189where
190    S: Storage,
191{
192    server: Server<S>,
193    cross_chain_sender: mpsc::Sender<(CrossChainRequest, ShardId)>,
194    notification_sender: sync::broadcast::Sender<Notification>,
195}
196
197#[async_trait]
198impl<S> MessageHandler for RunningServerState<S>
199where
200    S: Storage + Clone + Send + Sync + 'static,
201{
202    #[instrument(
203        target = "simple_server",
204        skip_all,
205        fields(
206            nickname = self.server.state.nickname(),
207            chain_id = ?message.target_chain_id()
208        )
209    )]
210    async fn handle_message(&mut self, message: RpcMessage) -> Option<RpcMessage> {
211        let reply = match message {
212            RpcMessage::BlockProposal(message) => {
213                let (result, actions) = self.server.state.handle_block_proposal(*message).await;
214                // Dispatch actions whether or not the proposal was accepted: a
215                // rejected proposal can still advance the manager's `current_round`
216                // (via `update_signed_proposal` on the `HasIncompatibleConfirmedVote`
217                // recovery path), and subscribers need the resulting `NewRound`
218                // notification.
219                self.handle_network_actions(actions);
220                match result {
221                    Ok(info) => Ok(Some(RpcMessage::ChainInfoResponse(Box::new(info)))),
222                    Err(error) => {
223                        self.log_error(&error, "Failed to handle block proposal");
224                        Err(error.into())
225                    }
226                }
227            }
228            RpcMessage::LiteCertificate(request) => {
229                let (sender, receiver) = request
230                    .wait_for_outgoing_messages
231                    .then(oneshot::channel)
232                    .unzip();
233                match Box::pin(
234                    self.server
235                        .state
236                        .handle_lite_certificate(request.certificate, sender),
237                )
238                .await
239                {
240                    Ok((info, actions)) => {
241                        // Cross-shard requests
242                        self.handle_network_actions(actions);
243                        if let Some(receiver) = receiver {
244                            if let Err(e) = receiver.await {
245                                error!("Failed to wait for message delivery: {e}");
246                            }
247                        }
248                        // Response
249                        Ok(Some(RpcMessage::ChainInfoResponse(Box::new(info))))
250                    }
251                    Err(error) => {
252                        let nickname = self.server.state.nickname();
253                        if let WorkerError::MissingCertificateValue = &error {
254                            debug!(nickname, %error, "Failed to handle lite certificate");
255                        } else {
256                            error!(nickname, %error, "Failed to handle lite certificate");
257                        }
258                        Err(error.into())
259                    }
260                }
261            }
262            RpcMessage::TimeoutCertificate(request) => {
263                match self
264                    .server
265                    .state
266                    .handle_timeout_certificate(request.certificate)
267                    .await
268                {
269                    Ok((info, actions)) => {
270                        // Cross-shard requests
271                        self.handle_network_actions(actions);
272                        // Response
273                        Ok(Some(RpcMessage::ChainInfoResponse(Box::new(info))))
274                    }
275                    Err(error) => {
276                        self.log_error(&error, "Failed to handle timeout certificate");
277                        Err(error.into())
278                    }
279                }
280            }
281            RpcMessage::ValidatedCertificate(request) => {
282                match self
283                    .server
284                    .state
285                    .handle_validated_certificate(request.certificate)
286                    .await
287                {
288                    Ok((info, actions)) => {
289                        // Cross-shard requests
290                        self.handle_network_actions(actions);
291                        // Response
292                        Ok(Some(RpcMessage::ChainInfoResponse(Box::new(info))))
293                    }
294                    Err(error) => {
295                        self.log_error(&error, "Failed to handle validated certificate");
296                        Err(error.into())
297                    }
298                }
299            }
300            RpcMessage::ConfirmedCertificate(request) => {
301                let (sender, receiver) = request
302                    .wait_for_outgoing_messages
303                    .then(oneshot::channel)
304                    .unzip();
305                match self
306                    .server
307                    .state
308                    .handle_confirmed_certificate(
309                        request.certificate,
310                        ProcessConfirmedBlockMode::Auto,
311                        sender,
312                    )
313                    .await
314                {
315                    Ok((info, actions)) => {
316                        // Cross-shard requests
317                        self.handle_network_actions(actions);
318                        if let Some(receiver) = receiver {
319                            if let Err(e) = receiver.await {
320                                error!("Failed to wait for message delivery: {e}");
321                            }
322                        }
323                        // Response
324                        Ok(Some(RpcMessage::ChainInfoResponse(Box::new(info))))
325                    }
326                    Err(error) => {
327                        self.log_error(&error, "Failed to handle confirmed certificate");
328                        Err(error.into())
329                    }
330                }
331            }
332            RpcMessage::ChainInfoQuery(message) => {
333                match self.server.state.handle_chain_info_query(*message).await {
334                    Ok(info) => Ok(Some(RpcMessage::ChainInfoResponse(Box::new(info)))),
335                    Err(error) => {
336                        self.log_error(&error, "Failed to handle chain info query");
337                        Err(error.into())
338                    }
339                }
340            }
341            RpcMessage::CrossChainRequest(request) => {
342                match self.server.state.handle_cross_chain_request(*request).await {
343                    Ok(actions) => {
344                        self.handle_network_actions(actions);
345                    }
346                    Err(error) => {
347                        self.log_error(&error, "Failed to handle cross-chain request");
348                    }
349                }
350                // No user to respond to.
351                Ok(None)
352            }
353            RpcMessage::DownloadPendingBlob(request) => {
354                let (chain_id, blob_id) = *request;
355                match self
356                    .server
357                    .state
358                    .download_pending_blob(chain_id, blob_id)
359                    .await
360                {
361                    Ok(blob) => Ok(Some(RpcMessage::DownloadPendingBlobResponse(Box::new(
362                        blob.content().clone(),
363                    )))),
364                    Err(error) => {
365                        self.log_error(&error, "Failed to handle pending blob request");
366                        Err(error.into())
367                    }
368                }
369            }
370            RpcMessage::HandlePendingBlob(request) => {
371                let (chain_id, blob_content) = *request;
372                match self
373                    .server
374                    .state
375                    .handle_pending_blob(chain_id, Blob::new(blob_content))
376                    .await
377                {
378                    Ok(info) => Ok(Some(RpcMessage::ChainInfoResponse(Box::new(info)))),
379                    Err(error) => {
380                        self.log_error(&error, "Failed to handle pending blob");
381                        Err(error.into())
382                    }
383                }
384            }
385
386            RpcMessage::VersionInfoQuery => {
387                Ok(Some(RpcMessage::VersionInfoResponse(Box::default())))
388            }
389
390            RpcMessage::SubscribeNotifications(_) | RpcMessage::Notification(_) => {
391                // Subscriptions are handled at the transport level, not here.
392                Err(NodeError::UnexpectedMessage)
393            }
394
395            RpcMessage::Vote(_)
396            | RpcMessage::Error(_)
397            | RpcMessage::ChainInfoResponse(_)
398            | RpcMessage::VersionInfoResponse(_)
399            | RpcMessage::NetworkDescriptionQuery
400            | RpcMessage::NetworkDescriptionResponse(_)
401            | RpcMessage::ShardInfoQuery(_)
402            | RpcMessage::ShardInfoResponse(_)
403            | RpcMessage::DownloadBlob(_)
404            | RpcMessage::DownloadBlobs(_)
405            | RpcMessage::DownloadBlobResponse(_)
406            | RpcMessage::DownloadPendingBlobResponse(_)
407            | RpcMessage::DownloadConfirmedBlock(_)
408            | RpcMessage::DownloadConfirmedBlockResponse(_)
409            | RpcMessage::BlobLastUsedBy(_)
410            | RpcMessage::BlobLastUsedByResponse(_)
411            | RpcMessage::BlobLastUsedByCertificate(_)
412            | RpcMessage::BlobLastUsedByCertificateResponse(_)
413            | RpcMessage::MissingBlobIds(_)
414            | RpcMessage::MissingBlobIdsResponse(_)
415            | RpcMessage::EventBlockHeights(_)
416            | RpcMessage::EventBlockHeightsResponse(_)
417            | RpcMessage::DownloadCertificates(_)
418            | RpcMessage::DownloadCertificatesResponse(_)
419            | RpcMessage::UploadBlob(_)
420            | RpcMessage::UploadBlobResponse(_)
421            | RpcMessage::DownloadCertificatesByHeights(_, _)
422            | RpcMessage::DownloadCertificatesByHeightsResponse(_) => {
423                Err(NodeError::UnexpectedMessage)
424            }
425        };
426
427        self.server.packets_processed += 1;
428        // We allow this because `is_multiple_of` is still unstable in our MSRV.
429        #[allow(unknown_lints)]
430        #[expect(clippy::manual_is_multiple_of)]
431        if self.server.packets_processed % 5000 == 0 {
432            debug!(
433                "[{}] {}:{} (shard {}) has processed {} packets",
434                self.server.state.nickname(),
435                self.server.host,
436                self.server.port,
437                self.server.shard_id,
438                self.server.packets_processed
439            );
440        }
441
442        match reply {
443            Ok(x) => x,
444            Err(error) => {
445                // TODO(#459): Make it a warning or an error again.
446                debug!(
447                    "[{}] User query failed: {}",
448                    self.server.state.nickname(),
449                    error
450                );
451                self.server.user_errors += 1;
452                Some(error.into())
453            }
454        }
455    }
456
457    async fn handle_subscribe(
458        &mut self,
459        chains: Vec<ChainId>,
460    ) -> Option<Pin<Box<dyn Stream<Item = RpcMessage> + Send>>> {
461        RunningServerState::subscribe_to_notifications(self, chains).await
462    }
463}
464
465impl<S> RunningServerState<S>
466where
467    S: Storage + Clone + Send + Sync + 'static,
468{
469    async fn subscribe_to_notifications(
470        &self,
471        chains: Vec<ChainId>,
472    ) -> Option<Pin<Box<dyn Stream<Item = RpcMessage> + Send>>> {
473        let receiver = self.notification_sender.subscribe();
474        let stream = BroadcastStream::new(receiver).filter_map(move |result| {
475            let chains = chains.clone();
476            async move {
477                match result {
478                    Ok(notification) if chains.contains(&notification.chain_id) => {
479                        Some(RpcMessage::Notification(Box::new(notification)))
480                    }
481                    _ => None,
482                }
483            }
484        });
485        Some(Box::pin(stream))
486    }
487}
488
489impl<S> RunningServerState<S>
490where
491    S: Storage + Send,
492{
493    fn handle_network_actions(&mut self, actions: NetworkActions) {
494        for request in actions.cross_chain_requests {
495            let shard_id = self.server.network.get_shard_id(request.target_chain_id());
496            debug!(
497                "[{}] Scheduling cross-chain query: {} -> {}",
498                self.server.state.nickname(),
499                self.server.shard_id,
500                shard_id
501            );
502            if let Err(error) = self.cross_chain_sender.try_send((request, shard_id)) {
503                error!(%error, "dropping cross-chain request");
504                break;
505            }
506        }
507        for notification in actions.notifications {
508            debug!("Scheduling notification query");
509            if let Err(error) = self.notification_sender.send(notification) {
510                debug!(%error, "dropping notification (no receivers)");
511            }
512        }
513    }
514
515    fn log_error(&self, error: &WorkerError, context: &str) {
516        let nickname = self.server.state.nickname();
517        if error.is_local() {
518            error!(nickname, %error, "{}", context);
519        } else {
520            debug!(nickname, %error, "{}", context);
521        }
522    }
523}