Skip to main content

linera_core/
node.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2// Copyright (c) Zefchain Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{collections::BTreeMap, sync::Arc};
6
7#[cfg(not(web))]
8use futures::stream::BoxStream;
9#[cfg(web)]
10use futures::stream::LocalBoxStream as BoxStream;
11use futures::stream::Stream;
12use linera_base::{
13    crypto::{CryptoError, CryptoHash, ValidatorPublicKey},
14    data_types::{
15        ArithmeticError, Blob, BlobContent, BlockHeight, NetworkDescription, Round, Timestamp,
16    },
17    identifiers::{BlobId, ChainId, EventId, StreamId},
18    task::{MaybeSend, MaybeSync},
19};
20use linera_cache::Arc as CacheArc;
21use linera_chain::{
22    data_types::BlockProposal,
23    types::{
24        ConfirmedBlock, ConfirmedBlockCertificate, GenericCertificate, LiteCertificate, Timeout,
25        ValidatedBlock,
26    },
27    ChainError,
28};
29use linera_execution::{committee::Committee, ExecutionError};
30use linera_version::VersionInfo;
31use linera_views::ViewError;
32use serde::{Deserialize, Serialize};
33use thiserror::Error;
34
35use crate::{
36    data_types::{ChainInfoQuery, ChainInfoResponse},
37    worker::{Notification, WorkerError},
38};
39
40/// A pinned [`Stream`] of Notifications.
41pub type NotificationStream = BoxStream<'static, Notification>;
42
43/// A pinned [`Stream`] of blob contents returned by batch downloads.
44pub type BlobStream = BoxStream<'static, Result<BlobContent, NodeError>>;
45
46/// Whether to wait for the delivery of outgoing cross-chain messages.
47#[derive(Debug, Default, Clone, Copy)]
48#[allow(missing_docs)]
49pub enum CrossChainMessageDelivery {
50    #[default]
51    NonBlocking,
52    Blocking,
53}
54
55/// How to communicate with a validator node.
56#[allow(async_fn_in_trait)]
57#[cfg_attr(not(web), trait_variant::make(Send))]
58pub trait ValidatorNode {
59    /// The type of stream of notifications returned when subscribing.
60    type NotificationStream: Stream<Item = Notification> + Unpin + MaybeSend;
61
62    /// Returns the address of this validator node.
63    fn address(&self) -> String;
64
65    /// Proposes a new block.
66    async fn handle_block_proposal(
67        &self,
68        proposal: BlockProposal,
69    ) -> Result<ChainInfoResponse, NodeError>;
70
71    /// Processes a certificate without a value.
72    async fn handle_lite_certificate(
73        &self,
74        certificate: LiteCertificate<'_>,
75        delivery: CrossChainMessageDelivery,
76    ) -> Result<ChainInfoResponse, NodeError>;
77
78    /// Processes a confirmed certificate.
79    async fn handle_confirmed_certificate(
80        &self,
81        certificate: CacheArc<GenericCertificate<ConfirmedBlock>>,
82        delivery: CrossChainMessageDelivery,
83    ) -> Result<ChainInfoResponse, NodeError>;
84
85    /// Processes a validated certificate.
86    async fn handle_validated_certificate(
87        &self,
88        certificate: GenericCertificate<ValidatedBlock>,
89    ) -> Result<ChainInfoResponse, NodeError>;
90
91    /// Processes a timeout certificate.
92    async fn handle_timeout_certificate(
93        &self,
94        certificate: GenericCertificate<Timeout>,
95    ) -> Result<ChainInfoResponse, NodeError>;
96
97    /// Handles information queries for this chain.
98    async fn handle_chain_info_query(
99        &self,
100        query: ChainInfoQuery,
101    ) -> Result<ChainInfoResponse, NodeError>;
102
103    /// Gets the version info for this validator node.
104    async fn get_version_info(&self) -> Result<VersionInfo, NodeError>;
105
106    /// Gets the network's description.
107    async fn get_network_description(&self) -> Result<NetworkDescription, NodeError>;
108
109    /// Subscribes to receiving notifications for a collection of chains.
110    async fn subscribe(&self, chains: Vec<ChainId>) -> Result<Self::NotificationStream, NodeError>;
111
112    /// Uploads a blob. Returns an error if the validator has not seen a
113    /// certificate using this blob.
114    async fn upload_blob(&self, content: BlobContent) -> Result<BlobId, NodeError>;
115
116    /// Uploads the blobs to the validator.
117    // Unfortunately, this doesn't compile as an async function: async functions in traits
118    // don't play well with default implementations, apparently.
119    // See also https://github.com/rust-lang/impl-trait-utils/issues/17
120    fn upload_blobs(
121        &self,
122        blobs: Vec<Arc<Blob>>,
123    ) -> impl futures::Future<Output = Result<Vec<BlobId>, NodeError>> {
124        let tasks: Vec<_> = blobs
125            .into_iter()
126            .map(|blob| self.upload_blob(blob.into()))
127            .collect();
128        futures::future::try_join_all(tasks)
129    }
130
131    /// Downloads a blob. Returns an error if the validator does not have the blob.
132    async fn download_blob(&self, blob_id: BlobId) -> Result<BlobContent, NodeError>;
133
134    /// Downloads a batch of blobs as a stream. The stream yields one blob per
135    /// requested id, in order. On mid-stream errors, the caller can retry the
136    /// remaining blob ids against another validator.
137    async fn download_blobs(&self, blob_ids: Vec<BlobId>) -> Result<BlobStream, NodeError>;
138
139    /// Downloads a blob that belongs to a pending proposal or the locking block on a chain.
140    async fn download_pending_blob(
141        &self,
142        chain_id: ChainId,
143        blob_id: BlobId,
144    ) -> Result<BlobContent, NodeError>;
145
146    /// Handles a blob that belongs to a pending proposal or validated block certificate.
147    async fn handle_pending_blob(
148        &self,
149        chain_id: ChainId,
150        blob: BlobContent,
151    ) -> Result<ChainInfoResponse, NodeError>;
152
153    /// Downloads the confirmed block certificate with the given hash.
154    async fn download_certificate(
155        &self,
156        hash: CryptoHash,
157    ) -> Result<ConfirmedBlockCertificate, NodeError>;
158
159    /// Requests a batch of certificates from the validator.
160    async fn download_certificates(
161        &self,
162        hashes: Vec<CryptoHash>,
163    ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError>;
164
165    /// Requests a batch of certificates from a specific chain by heights.
166    ///
167    /// Returns certificates in ascending order by height. This method does not guarantee
168    /// that all requested heights will be returned; if some certificates are missing,
169    /// the caller must handle that.
170    async fn download_certificates_by_heights(
171        &self,
172        chain_id: ChainId,
173        heights: Vec<BlockHeight>,
174    ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError>;
175
176    /// Returns the hash of the `Certificate` that last used a blob.
177    async fn blob_last_used_by(&self, blob_id: BlobId) -> Result<CryptoHash, NodeError>;
178
179    /// Returns the missing `Blob`s by their IDs.
180    async fn missing_blob_ids(&self, blob_ids: Vec<BlobId>) -> Result<Vec<BlobId>, NodeError>;
181
182    /// Returns the certificate that last used the blob.
183    async fn blob_last_used_by_certificate(
184        &self,
185        blob_id: BlobId,
186    ) -> Result<ConfirmedBlockCertificate, NodeError>;
187
188    /// Returns the previous event blocks for a chain's streams.
189    async fn previous_event_blocks(
190        &self,
191        chain_id: ChainId,
192        stream_ids: Vec<StreamId>,
193    ) -> Result<BTreeMap<StreamId, (BlockHeight, CryptoHash)>, NodeError>;
194}
195
196/// Turn an address into a validator node.
197#[cfg_attr(not(web), trait_variant::make(Send + Sync))]
198pub trait ValidatorNodeProvider: 'static {
199    /// The type of validator node produced by this provider.
200    type Node: ValidatorNode + MaybeSend + MaybeSync + Clone + 'static;
201
202    /// Creates a node from a validator's address.
203    fn make_node(&self, address: &str) -> Result<Self::Node, NodeError>;
204
205    /// Creates a node for each validator in the committee.
206    fn make_nodes(
207        &self,
208        committee: &Committee,
209    ) -> Result<impl Iterator<Item = (ValidatorPublicKey, Self::Node)> + '_, NodeError> {
210        let validator_addresses: Vec<_> = committee
211            .validator_addresses()
212            .map(|(node, name)| (node, name.to_owned()))
213            .collect();
214        self.make_nodes_from_list(validator_addresses)
215    }
216
217    /// Creates a node for each validator in the given list of public keys and addresses.
218    fn make_nodes_from_list<A>(
219        &self,
220        validators: impl IntoIterator<Item = (ValidatorPublicKey, A)>,
221    ) -> Result<impl Iterator<Item = (ValidatorPublicKey, Self::Node)>, NodeError>
222    where
223        A: AsRef<str>,
224    {
225        Ok(validators
226            .into_iter()
227            .map(|(name, address)| Ok((name, self.make_node(address.as_ref())?)))
228            .collect::<Result<Vec<_>, NodeError>>()?
229            .into_iter())
230    }
231}
232
233/// Error type for node queries.
234///
235/// This error is meant to be serialized over the network and aggregated by clients (i.e.
236/// clients will track validator votes on each error value).
237#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Error, Hash)]
238#[allow(missing_docs)]
239pub enum NodeError {
240    #[error("Cryptographic error: {error}")]
241    CryptoError { error: String },
242
243    #[error("Arithmetic error: {error}")]
244    ArithmeticError { error: String },
245
246    #[error("Error while accessing storage: {error}")]
247    ViewError { error: String },
248
249    #[error("Chain error: {error}")]
250    ChainError { error: String },
251
252    #[error("Worker error: {error}")]
253    WorkerError { error: String },
254
255    // This error must be normalized during conversions.
256    #[error("The chain {0} is not active in validator")]
257    InactiveChain(ChainId),
258
259    #[error("Round number should be {0:?}")]
260    WrongRound(Round),
261
262    #[error(
263        "Chain is expecting a next block at height {expected_block_height} but the given block \
264        is at height {found_block_height} instead"
265    )]
266    UnexpectedBlockHeight {
267        expected_block_height: BlockHeight,
268        found_block_height: BlockHeight,
269    },
270
271    // This error must be normalized during conversions.
272    #[error(
273        "Cannot vote for block proposal of chain {chain_id} because a message \
274         from chain {origin} at height {height} has not been received yet"
275    )]
276    MissingCrossChainUpdate {
277        chain_id: ChainId,
278        origin: ChainId,
279        height: BlockHeight,
280    },
281
282    #[error("Blobs not found: {0:?}")]
283    BlobsNotFound(Vec<BlobId>),
284
285    #[error("Events not found: {0:?}")]
286    EventsNotFound(Vec<EventId>),
287
288    // This error must be normalized during conversions.
289    #[error("We don't have the value for the certificate.")]
290    MissingCertificateValue,
291
292    #[error("Response doesn't contain requested certificates: {0:?}")]
293    MissingCertificates(Vec<CryptoHash>),
294
295    #[error("Validator's response failed to include a vote when trying to {0}")]
296    MissingVoteInValidatorResponse(String),
297
298    #[error("The received chain info response is invalid")]
299    InvalidChainInfoResponse,
300    #[error("Unexpected certificate value")]
301    UnexpectedCertificateValue,
302
303    // Networking errors.
304    // TODO(#258): These errors should be defined in linera-rpc.
305    #[error("Cannot deserialize")]
306    InvalidDecoding,
307    #[error("Unexpected message")]
308    UnexpectedMessage,
309    #[error("Grpc error: {error}")]
310    GrpcError { error: String },
311    #[error("Network error while querying service: {error}")]
312    ClientIoError { error: String },
313    #[error("Failed to resolve validator address: {address}")]
314    CannotResolveValidatorAddress { address: String },
315    #[error("Subscription error due to incorrect transport. Was expecting gRPC, instead found: {transport}")]
316    SubscriptionError { transport: String },
317    #[error("Failed to subscribe; tonic status: {status:?}")]
318    SubscriptionFailed { status: String },
319
320    #[error("Node failed to provide a 'last used by' certificate for the blob")]
321    InvalidCertificateForBlob(BlobId),
322    #[error("Node returned a BlobsNotFound error with duplicates")]
323    DuplicatesInBlobsNotFound,
324    #[error("Node returned a BlobsNotFound error with unexpected blob IDs")]
325    UnexpectedEntriesInBlobsNotFound,
326    #[error("Node returned certificates {returned:?}, but we requested {requested:?}")]
327    UnexpectedCertificates {
328        returned: Vec<CryptoHash>,
329        requested: Vec<CryptoHash>,
330    },
331    #[error("Node returned a BlobsNotFound error with an empty list of missing blob IDs")]
332    EmptyBlobsNotFound,
333    #[error("Local error handling validator response: {error}")]
334    ResponseHandlingError { error: String },
335
336    #[error("Missing certificates for chain {chain_id} in heights {heights:?}")]
337    MissingCertificatesByHeights {
338        chain_id: ChainId,
339        heights: Vec<BlockHeight>,
340    },
341
342    #[error("Too many certificates returned for chain {chain_id} from {remote_node}")]
343    TooManyCertificatesReturned {
344        chain_id: ChainId,
345        remote_node: Box<ValidatorPublicKey>,
346    },
347
348    #[error(
349        "Validator is missing {} cross-chain message bundle(s) to validate the block for \
350         chain {chain_id}",
351        bundles.len()
352    )]
353    MissingCrossChainUpdates {
354        chain_id: ChainId,
355        bundles: Vec<(ChainId, BlockHeight)>,
356    },
357}
358
359/// Parsed data from an `InvalidTimestamp` error.
360#[derive(Debug, Clone, Copy)]
361pub struct InvalidTimestampError {
362    /// The block's timestamp that was rejected.
363    pub block_timestamp: Timestamp,
364    /// The validator's local time when it rejected the block.
365    pub validator_local_time: Timestamp,
366}
367
368impl NodeError {
369    /// If this error is an `InvalidTimestamp` error (wrapped in `WorkerError`), parses and
370    /// returns the timestamps. Returns `None` for other error types.
371    ///
372    /// The error string format is expected to contain `[us:{block_timestamp}:{local_time}]`
373    /// where both values are microseconds since epoch.
374    pub fn parse_invalid_timestamp(&self) -> Option<InvalidTimestampError> {
375        let NodeError::WorkerError { error } = self else {
376            return None;
377        };
378        // Look for the marker pattern [us:BLOCK_TS:LOCAL_TS].
379        let marker_start = error.find("[us:")?;
380        let marker_content = &error[marker_start + 4..];
381        let marker_end = marker_content.find(']')?;
382        let timestamps = &marker_content[..marker_end];
383        let mut parts = timestamps.split(':');
384        let block_timestamp_us: u64 = parts.next()?.parse().ok()?;
385        let local_time_us: u64 = parts.next()?.parse().ok()?;
386        Some(InvalidTimestampError {
387            block_timestamp: Timestamp::from(block_timestamp_us),
388            validator_local_time: Timestamp::from(local_time_us),
389        })
390    }
391}
392
393impl NodeError {
394    /// Returns whether this error is an expected part of the protocol flow.
395    ///
396    /// Expected errors are those that validators return during normal operation and that
397    /// the client handles automatically (e.g. by supplying missing data and retrying).
398    /// Unexpected errors indicate genuine network issues, validator misbehavior, or
399    /// internal problems.
400    pub fn is_expected(&self) -> bool {
401        match self {
402            // Expected: validators return these during normal operation and the client
403            // handles them automatically by supplying missing data and retrying.
404            NodeError::BlobsNotFound(_)
405            | NodeError::EventsNotFound(_)
406            | NodeError::MissingCrossChainUpdate { .. }
407            | NodeError::MissingCrossChainUpdates { .. }
408            | NodeError::WrongRound(_)
409            | NodeError::UnexpectedBlockHeight { .. }
410            | NodeError::InactiveChain(_)
411            | NodeError::MissingCertificateValue => true,
412
413            // Unexpected: network issues, validator misbehavior, or internal problems.
414            NodeError::CryptoError { .. }
415            | NodeError::ArithmeticError { .. }
416            | NodeError::ViewError { .. }
417            | NodeError::ChainError { .. }
418            | NodeError::WorkerError { .. }
419            | NodeError::MissingCertificates(_)
420            | NodeError::MissingVoteInValidatorResponse(_)
421            | NodeError::InvalidChainInfoResponse
422            | NodeError::UnexpectedCertificateValue
423            | NodeError::InvalidDecoding
424            | NodeError::UnexpectedMessage
425            | NodeError::GrpcError { .. }
426            | NodeError::ClientIoError { .. }
427            | NodeError::CannotResolveValidatorAddress { .. }
428            | NodeError::SubscriptionError { .. }
429            | NodeError::SubscriptionFailed { .. }
430            | NodeError::InvalidCertificateForBlob(_)
431            | NodeError::DuplicatesInBlobsNotFound
432            | NodeError::UnexpectedEntriesInBlobsNotFound
433            | NodeError::UnexpectedCertificates { .. }
434            | NodeError::EmptyBlobsNotFound
435            | NodeError::ResponseHandlingError { .. }
436            | NodeError::MissingCertificatesByHeights { .. }
437            | NodeError::TooManyCertificatesReturned { .. } => false,
438        }
439    }
440}
441
442impl From<tonic::Status> for NodeError {
443    fn from(status: tonic::Status) -> Self {
444        Self::GrpcError {
445            error: status.to_string(),
446        }
447    }
448}
449
450impl CrossChainMessageDelivery {
451    /// Creates a new value, blocking on outgoing message delivery if requested.
452    pub fn new(wait_for_outgoing_messages: bool) -> Self {
453        if wait_for_outgoing_messages {
454            CrossChainMessageDelivery::Blocking
455        } else {
456            CrossChainMessageDelivery::NonBlocking
457        }
458    }
459
460    /// Returns whether to wait for the delivery of outgoing cross-chain messages.
461    pub fn wait_for_outgoing_messages(self) -> bool {
462        match self {
463            CrossChainMessageDelivery::NonBlocking => false,
464            CrossChainMessageDelivery::Blocking => true,
465        }
466    }
467}
468
469impl From<ViewError> for NodeError {
470    fn from(error: ViewError) -> Self {
471        Self::ViewError {
472            error: error.to_string(),
473        }
474    }
475}
476
477impl From<ArithmeticError> for NodeError {
478    fn from(error: ArithmeticError) -> Self {
479        Self::ArithmeticError {
480            error: error.to_string(),
481        }
482    }
483}
484
485impl From<CryptoError> for NodeError {
486    fn from(error: CryptoError) -> Self {
487        Self::CryptoError {
488            error: error.to_string(),
489        }
490    }
491}
492
493impl From<ChainError> for NodeError {
494    fn from(error: ChainError) -> Self {
495        match error {
496            ChainError::MissingCrossChainUpdates { chain_id, bundles } => {
497                Self::MissingCrossChainUpdates { chain_id, bundles }
498            }
499            ChainError::InactiveChain(chain_id) => Self::InactiveChain(chain_id),
500            ChainError::ExecutionError(execution_error, context) => match *execution_error {
501                ExecutionError::BlobsNotFound(blob_ids) => Self::BlobsNotFound(blob_ids),
502                ExecutionError::EventsNotFound(event_ids) => Self::EventsNotFound(event_ids),
503                _ => Self::ChainError {
504                    error: ChainError::ExecutionError(execution_error, context).to_string(),
505                },
506            },
507            ChainError::UnexpectedBlockHeight {
508                expected_block_height,
509                found_block_height,
510            } => Self::UnexpectedBlockHeight {
511                expected_block_height,
512                found_block_height,
513            },
514            ChainError::WrongRound(round) => Self::WrongRound(round),
515            error => Self::ChainError {
516                error: error.to_string(),
517            },
518        }
519    }
520}
521
522impl From<WorkerError> for NodeError {
523    fn from(error: WorkerError) -> Self {
524        match error {
525            WorkerError::ChainError(error) => (*error).into(),
526            WorkerError::MissingCertificateValue => Self::MissingCertificateValue,
527            WorkerError::BlobsNotFound(blob_ids) => Self::BlobsNotFound(blob_ids),
528            WorkerError::EventsNotFound(event_ids) => Self::EventsNotFound(event_ids),
529            WorkerError::UnexpectedBlockHeight {
530                expected_block_height,
531                found_block_height,
532            } => NodeError::UnexpectedBlockHeight {
533                expected_block_height,
534                found_block_height,
535            },
536            error => Self::WorkerError {
537                error: error.to_string(),
538            },
539        }
540    }
541}