linera_chain/
lib.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module manages the state of a Linera chain, including cross-chain communication.
5
6#![deny(clippy::large_futures)]
7
8pub mod block;
9mod certificate;
10
11pub mod types {
12    pub use super::{block::*, certificate::*};
13}
14
15mod chain;
16pub mod data_types;
17mod inbox;
18pub mod manager;
19mod outbox;
20mod pending_blobs;
21#[cfg(with_testing)]
22pub mod test;
23
24pub use chain::ChainStateView;
25use data_types::{MessageBundle, Origin, PostedMessage};
26use linera_base::{
27    bcs,
28    crypto::{CryptoError, CryptoHash},
29    data_types::{ArithmeticError, BlockHeight, Round, Timestamp},
30    identifiers::{ApplicationId, BlobId, ChainId},
31};
32use linera_execution::ExecutionError;
33use linera_views::views::ViewError;
34use rand_distr::WeightedError;
35use thiserror::Error;
36
37#[derive(Error, Debug)]
38pub enum ChainError {
39    #[error("Cryptographic error: {0}")]
40    CryptoError(#[from] CryptoError),
41    #[error(transparent)]
42    ArithmeticError(#[from] ArithmeticError),
43    #[error(transparent)]
44    ViewError(ViewError),
45    #[error("Execution error: {0} during {1:?}")]
46    ExecutionError(Box<ExecutionError>, ChainExecutionContext),
47
48    #[error("The chain being queried is not active {0:?}")]
49    InactiveChain(ChainId),
50    #[error(
51        "Cannot vote for block proposal of chain {chain_id:?} because a message \
52         from origin {origin:?} at height {height:?} has not been received yet"
53    )]
54    MissingCrossChainUpdate {
55        chain_id: ChainId,
56        origin: Box<Origin>,
57        height: BlockHeight,
58    },
59    #[error(
60        "Message in block proposed to {chain_id:?} does not match the previously received messages from \
61        origin {origin:?}: was {bundle:?} instead of {previous_bundle:?}"
62    )]
63    UnexpectedMessage {
64        chain_id: ChainId,
65        origin: Box<Origin>,
66        bundle: Box<MessageBundle>,
67        previous_bundle: Box<MessageBundle>,
68    },
69    #[error(
70        "Message in block proposed to {chain_id:?} is out of order compared to previous messages \
71         from origin {origin:?}: {bundle:?}. Block and height should be at least: \
72         {next_height}, {next_index}"
73    )]
74    IncorrectMessageOrder {
75        chain_id: ChainId,
76        origin: Box<Origin>,
77        bundle: Box<MessageBundle>,
78        next_height: BlockHeight,
79        next_index: u32,
80    },
81    #[error(
82        "Block proposed to {chain_id:?} is attempting to reject protected message \
83        {posted_message:?}"
84    )]
85    CannotRejectMessage {
86        chain_id: ChainId,
87        origin: Box<Origin>,
88        posted_message: Box<PostedMessage>,
89    },
90    #[error(
91        "Block proposed to {chain_id:?} is attempting to skip a message bundle \
92         that cannot be skipped: {bundle:?}"
93    )]
94    CannotSkipMessage {
95        chain_id: ChainId,
96        origin: Box<Origin>,
97        bundle: Box<MessageBundle>,
98    },
99    #[error(
100        "Incoming message bundle in block proposed to {chain_id:?} has timestamp \
101        {bundle_timestamp:}, which is later than the block timestamp {block_timestamp:}."
102    )]
103    IncorrectBundleTimestamp {
104        chain_id: ChainId,
105        bundle_timestamp: Timestamp,
106        block_timestamp: Timestamp,
107    },
108    #[error("The signature was not created by a valid entity")]
109    InvalidSigner,
110    #[error(
111        "Was expecting block height {expected_block_height} but found {found_block_height} instead"
112    )]
113    UnexpectedBlockHeight {
114        expected_block_height: BlockHeight,
115        found_block_height: BlockHeight,
116    },
117    #[error("The previous block hash of a new block should match the last block of the chain")]
118    UnexpectedPreviousBlockHash,
119    #[error("Sequence numbers above the maximal value are not usable for blocks")]
120    InvalidBlockHeight,
121    #[error("Block timestamp must not be earlier than the parent block's.")]
122    InvalidBlockTimestamp,
123    #[error("Cannot initiate a new block while the previous one is still pending confirmation")]
124    PreviousBlockMustBeConfirmedFirst,
125    #[error("Round number should be at least {0:?}")]
126    InsufficientRound(Round),
127    #[error("Round number should be greater than {0:?}")]
128    InsufficientRoundStrict(Round),
129    #[error("Round number should be {0:?}")]
130    WrongRound(Round),
131    #[error("Already voted to confirm a different block for height {0:?} at round number {1:?}")]
132    HasIncompatibleConfirmedVote(BlockHeight, Round),
133    #[error("Proposal for height {0:?} is not newer than locking block in round {1:?}")]
134    MustBeNewerThanLockingBlock(BlockHeight, Round),
135    #[error("Cannot confirm a block before its predecessors: {current_block_height:?}")]
136    MissingEarlierBlocks { current_block_height: BlockHeight },
137    #[error("Signatures in a certificate must be from different validators")]
138    CertificateValidatorReuse,
139    #[error("Signatures in a certificate must form a quorum")]
140    CertificateRequiresQuorum,
141    #[error("Certificate signature verification failed: {error}")]
142    CertificateSignatureVerificationFailed { error: String },
143    #[error("Internal error {0}")]
144    InternalError(String),
145    #[error("Block proposal is too large")]
146    BlockProposalTooLarge,
147    #[error(transparent)]
148    BcsError(#[from] bcs::Error),
149    #[error("Insufficient balance to pay the fees")]
150    InsufficientBalance,
151    #[error("Invalid owner weights: {0}")]
152    OwnerWeightError(#[from] WeightedError),
153    #[error("Closed chains cannot have operations, accepted messages or empty blocks")]
154    ClosedChain,
155    #[error("All operations on this chain must be from one of the following applications: {0:?}")]
156    AuthorizedApplications(Vec<ApplicationId>),
157    #[error("Missing operations or messages from mandatory applications: {0:?}")]
158    MissingMandatoryApplications(Vec<ApplicationId>),
159    #[error("Can't use grant across different broadcast messages")]
160    GrantUseOnBroadcast,
161    #[error("Executed block contains fewer oracle responses than requests")]
162    MissingOracleResponseList,
163    #[error("Unexpected hash for CertificateValue! Expected: {expected:?}, Actual: {actual:?}")]
164    CertificateValueHashMismatch {
165        expected: CryptoHash,
166        actual: CryptoHash,
167    },
168    #[error("Blobs not found: {0:?}")]
169    BlobsNotFound(Vec<BlobId>),
170}
171
172impl From<ViewError> for ChainError {
173    fn from(error: ViewError) -> Self {
174        match error {
175            ViewError::BlobsNotFound(blob_ids) => ChainError::BlobsNotFound(blob_ids),
176            error => ChainError::ViewError(error),
177        }
178    }
179}
180
181#[derive(Copy, Clone, Debug)]
182#[cfg_attr(with_testing, derive(Eq, PartialEq))]
183pub enum ChainExecutionContext {
184    Query,
185    DescribeApplication,
186    IncomingBundle(u32),
187    Operation(u32),
188    Block,
189}
190
191pub trait ExecutionResultExt<T> {
192    fn with_execution_context(self, context: ChainExecutionContext) -> Result<T, ChainError>;
193}
194
195impl<T, E> ExecutionResultExt<T> for Result<T, E>
196where
197    E: Into<ExecutionError>,
198{
199    fn with_execution_context(self, context: ChainExecutionContext) -> Result<T, ChainError> {
200        self.map_err(|error| ChainError::ExecutionError(Box::new(error.into()), context))
201    }
202}