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