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