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