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
6pub mod block;
7mod certificate;
8
9pub mod types {
10    pub use super::{block::*, certificate::*};
11}
12
13mod block_tracker;
14mod chain;
15pub mod data_types;
16mod inbox;
17pub mod manager;
18mod outbox;
19mod pending_blobs;
20#[cfg(with_testing)]
21pub mod test;
22
23pub use chain::ChainStateView;
24use data_types::{MessageBundle, PostedMessage};
25use linera_base::{
26    bcs,
27    crypto::CryptoError,
28    data_types::{ArithmeticError, BlockHeight, Round, Timestamp},
29    identifiers::{ApplicationId, ChainId},
30};
31use linera_execution::ExecutionError;
32use linera_views::ViewError;
33use thiserror::Error;
34
35#[derive(Error, Debug)]
36pub enum ChainError {
37    #[error("Cryptographic error: {0}")]
38    CryptoError(#[from] CryptoError),
39    #[error(transparent)]
40    ArithmeticError(#[from] ArithmeticError),
41    #[error(transparent)]
42    ViewError(#[from] ViewError),
43    #[error("Execution error: {0} during {1:?}")]
44    ExecutionError(Box<ExecutionError>, ChainExecutionContext),
45
46    #[error("The chain being queried is not active {0}")]
47    InactiveChain(ChainId),
48    #[error(
49        "Cannot vote for block proposal of chain {chain_id} because a message \
50         from chain {origin} at height {height} has not been received yet"
51    )]
52    MissingCrossChainUpdate {
53        chain_id: ChainId,
54        origin: ChainId,
55        height: BlockHeight,
56    },
57    #[error(
58        "Message in block proposed to {chain_id} does not match the previously received messages from \
59        origin {origin:?}: was {bundle:?} instead of {previous_bundle:?}"
60    )]
61    UnexpectedMessage {
62        chain_id: ChainId,
63        origin: ChainId,
64        bundle: Box<MessageBundle>,
65        previous_bundle: Box<MessageBundle>,
66    },
67    #[error(
68        "Message in block proposed to {chain_id} is out of order compared to previous messages \
69         from origin {origin:?}: {bundle:?}. Block and height should be at least: \
70         {next_height}, {next_index}"
71    )]
72    IncorrectMessageOrder {
73        chain_id: ChainId,
74        origin: ChainId,
75        bundle: Box<MessageBundle>,
76        next_height: BlockHeight,
77        next_index: u32,
78    },
79    #[error(
80        "Block proposed to {chain_id} is attempting to reject protected message \
81        {posted_message:?}"
82    )]
83    CannotRejectMessage {
84        chain_id: ChainId,
85        origin: ChainId,
86        posted_message: Box<PostedMessage>,
87    },
88    #[error(
89        "Block proposed to {chain_id} is attempting to skip a message bundle \
90         that cannot be skipped: {bundle:?}"
91    )]
92    CannotSkipMessage {
93        chain_id: ChainId,
94        origin: ChainId,
95        bundle: Box<MessageBundle>,
96    },
97    #[error(
98        "Incoming message bundle in block proposed to {chain_id} has timestamp \
99        {bundle_timestamp:}, which is later than the block timestamp {block_timestamp:}."
100    )]
101    IncorrectBundleTimestamp {
102        chain_id: ChainId,
103        bundle_timestamp: Timestamp,
104        block_timestamp: Timestamp,
105    },
106    #[error("The signature was not created by a valid entity")]
107    InvalidSigner,
108    #[error(
109        "Chain is expecting a next block at height {expected_block_height} but the given block \
110        is at height {found_block_height} instead"
111    )]
112    UnexpectedBlockHeight {
113        expected_block_height: BlockHeight,
114        found_block_height: BlockHeight,
115    },
116    #[error("The previous block hash of a new block should match the last block of the chain")]
117    UnexpectedPreviousBlockHash,
118    #[error("Sequence numbers above the maximal value are not usable for blocks")]
119    BlockHeightOverflow,
120    #[error(
121        "Block timestamp {new} must not be earlier than the parent block's timestamp {parent}"
122    )]
123    InvalidBlockTimestamp { parent: Timestamp, new: Timestamp },
124    #[error("Round number should be at least {0:?}")]
125    InsufficientRound(Round),
126    #[error("Round number should be greater than {0:?}")]
127    InsufficientRoundStrict(Round),
128    #[error("Round number should be {0:?}")]
129    WrongRound(Round),
130    #[error("Already voted to confirm a different block for height {0:?} at round number {1:?}")]
131    HasIncompatibleConfirmedVote(BlockHeight, Round),
132    #[error("Proposal for height {0:?} is not newer than locking block in round {1:?}")]
133    MustBeNewerThanLockingBlock(BlockHeight, Round),
134    #[error("Cannot confirm a block before its predecessors: {current_block_height:?}")]
135    MissingEarlierBlocks { current_block_height: BlockHeight },
136    #[error("Signatures in a certificate must be from different validators")]
137    CertificateValidatorReuse,
138    #[error("Signatures in a certificate must form a quorum")]
139    CertificateRequiresQuorum,
140    #[error("Internal error {0}")]
141    InternalError(String),
142    #[error("Block proposal has size {0} which is too large")]
143    BlockProposalTooLarge(usize),
144    #[error(transparent)]
145    BcsError(#[from] bcs::Error),
146    #[error("Closed chains cannot have operations, accepted messages or empty blocks")]
147    ClosedChain,
148    #[error("Empty blocks are not allowed")]
149    EmptyBlock,
150    #[error("All operations on this chain must be from one of the following applications: {0:?}")]
151    AuthorizedApplications(Vec<ApplicationId>),
152    #[error("Missing operations or messages from mandatory applications: {0:?}")]
153    MissingMandatoryApplications(Vec<ApplicationId>),
154    #[error("Executed block contains fewer oracle responses than requests")]
155    MissingOracleResponseList,
156    #[error("Not signing timeout certificate; current round does not time out")]
157    RoundDoesNotTimeOut,
158    #[error("Not signing timeout certificate; current round times out at time {0}")]
159    NotTimedOutYet(Timestamp),
160}
161
162impl ChainError {
163    /// Returns whether this error is caused by an issue in the local node.
164    ///
165    /// Returns `false` whenever the error could be caused by a bad message from a peer.
166    pub fn is_local(&self) -> bool {
167        match self {
168            ChainError::CryptoError(_)
169            | ChainError::ArithmeticError(_)
170            | ChainError::ViewError(ViewError::NotFound(_))
171            | ChainError::InactiveChain(_)
172            | ChainError::IncorrectMessageOrder { .. }
173            | ChainError::CannotRejectMessage { .. }
174            | ChainError::CannotSkipMessage { .. }
175            | ChainError::IncorrectBundleTimestamp { .. }
176            | ChainError::InvalidSigner
177            | ChainError::UnexpectedBlockHeight { .. }
178            | ChainError::UnexpectedPreviousBlockHash
179            | ChainError::BlockHeightOverflow
180            | ChainError::InvalidBlockTimestamp { .. }
181            | ChainError::InsufficientRound(_)
182            | ChainError::InsufficientRoundStrict(_)
183            | ChainError::WrongRound(_)
184            | ChainError::HasIncompatibleConfirmedVote(..)
185            | ChainError::MustBeNewerThanLockingBlock(..)
186            | ChainError::MissingEarlierBlocks { .. }
187            | ChainError::CertificateValidatorReuse
188            | ChainError::CertificateRequiresQuorum
189            | ChainError::BlockProposalTooLarge(_)
190            | ChainError::ClosedChain
191            | ChainError::EmptyBlock
192            | ChainError::AuthorizedApplications(_)
193            | ChainError::MissingMandatoryApplications(_)
194            | ChainError::MissingOracleResponseList
195            | ChainError::RoundDoesNotTimeOut
196            | ChainError::NotTimedOutYet(_)
197            | ChainError::MissingCrossChainUpdate { .. } => false,
198            ChainError::ViewError(_)
199            | ChainError::UnexpectedMessage { .. }
200            | ChainError::InternalError(_)
201            | ChainError::BcsError(_) => true,
202            ChainError::ExecutionError(execution_error, _) => execution_error.is_local(),
203        }
204    }
205}
206
207#[derive(Copy, Clone, Debug)]
208#[cfg_attr(with_testing, derive(Eq, PartialEq))]
209pub enum ChainExecutionContext {
210    Query,
211    DescribeApplication,
212    IncomingBundle(u32),
213    Operation(u32),
214    Block,
215}
216
217pub trait ExecutionResultExt<T> {
218    fn with_execution_context(self, context: ChainExecutionContext) -> Result<T, ChainError>;
219}
220
221impl<T, E> ExecutionResultExt<T> for Result<T, E>
222where
223    E: Into<ExecutionError>,
224{
225    fn with_execution_context(self, context: ChainExecutionContext) -> Result<T, ChainError> {
226        self.map_err(|error| ChainError::ExecutionError(Box::new(error.into()), context))
227    }
228}