1use 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
40pub type NotificationStream = BoxStream<'static, Notification>;
42
43pub type BlobStream = BoxStream<'static, Result<BlobContent, NodeError>>;
45
46#[derive(Debug, Default, Clone, Copy)]
48#[allow(missing_docs)]
49pub enum CrossChainMessageDelivery {
50 #[default]
51 NonBlocking,
52 Blocking,
53}
54
55#[allow(async_fn_in_trait)]
57#[cfg_attr(not(web), trait_variant::make(Send))]
58pub trait ValidatorNode {
59 type NotificationStream: Stream<Item = Notification> + Unpin + MaybeSend;
61
62 fn address(&self) -> String;
64
65 async fn handle_block_proposal(
67 &self,
68 proposal: BlockProposal,
69 ) -> Result<ChainInfoResponse, NodeError>;
70
71 async fn handle_lite_certificate(
73 &self,
74 certificate: LiteCertificate<'_>,
75 delivery: CrossChainMessageDelivery,
76 ) -> Result<ChainInfoResponse, NodeError>;
77
78 async fn handle_confirmed_certificate(
80 &self,
81 certificate: CacheArc<GenericCertificate<ConfirmedBlock>>,
82 delivery: CrossChainMessageDelivery,
83 ) -> Result<ChainInfoResponse, NodeError>;
84
85 async fn handle_validated_certificate(
87 &self,
88 certificate: GenericCertificate<ValidatedBlock>,
89 ) -> Result<ChainInfoResponse, NodeError>;
90
91 async fn handle_timeout_certificate(
93 &self,
94 certificate: GenericCertificate<Timeout>,
95 ) -> Result<ChainInfoResponse, NodeError>;
96
97 async fn handle_chain_info_query(
99 &self,
100 query: ChainInfoQuery,
101 ) -> Result<ChainInfoResponse, NodeError>;
102
103 async fn get_version_info(&self) -> Result<VersionInfo, NodeError>;
105
106 async fn get_network_description(&self) -> Result<NetworkDescription, NodeError>;
108
109 async fn subscribe(&self, chains: Vec<ChainId>) -> Result<Self::NotificationStream, NodeError>;
111
112 async fn upload_blob(&self, content: BlobContent) -> Result<BlobId, NodeError>;
115
116 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 async fn download_blob(&self, blob_id: BlobId) -> Result<BlobContent, NodeError>;
133
134 async fn download_blobs(&self, blob_ids: Vec<BlobId>) -> Result<BlobStream, NodeError>;
138
139 async fn download_pending_blob(
141 &self,
142 chain_id: ChainId,
143 blob_id: BlobId,
144 ) -> Result<BlobContent, NodeError>;
145
146 async fn handle_pending_blob(
148 &self,
149 chain_id: ChainId,
150 blob: BlobContent,
151 ) -> Result<ChainInfoResponse, NodeError>;
152
153 async fn download_certificate(
155 &self,
156 hash: CryptoHash,
157 ) -> Result<ConfirmedBlockCertificate, NodeError>;
158
159 async fn download_certificates(
161 &self,
162 hashes: Vec<CryptoHash>,
163 ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError>;
164
165 async fn download_certificates_by_heights(
171 &self,
172 chain_id: ChainId,
173 heights: Vec<BlockHeight>,
174 ) -> Result<Vec<ConfirmedBlockCertificate>, NodeError>;
175
176 async fn blob_last_used_by(&self, blob_id: BlobId) -> Result<CryptoHash, NodeError>;
178
179 async fn missing_blob_ids(&self, blob_ids: Vec<BlobId>) -> Result<Vec<BlobId>, NodeError>;
181
182 async fn blob_last_used_by_certificate(
184 &self,
185 blob_id: BlobId,
186 ) -> Result<ConfirmedBlockCertificate, NodeError>;
187
188 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#[cfg_attr(not(web), trait_variant::make(Send + Sync))]
198pub trait ValidatorNodeProvider: 'static {
199 type Node: ValidatorNode + MaybeSend + MaybeSync + Clone + 'static;
201
202 fn make_node(&self, address: &str) -> Result<Self::Node, NodeError>;
204
205 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 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#[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 #[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 #[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 #[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 #[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#[derive(Debug, Clone, Copy)]
361pub struct InvalidTimestampError {
362 pub block_timestamp: Timestamp,
364 pub validator_local_time: Timestamp,
366}
367
368impl NodeError {
369 pub fn parse_invalid_timestamp(&self) -> Option<InvalidTimestampError> {
375 let NodeError::WorkerError { error } = self else {
376 return None;
377 };
378 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 pub fn is_expected(&self) -> bool {
401 match self {
402 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 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 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 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}