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::{BlockExecution, BlockExecutionPhase, ChainIdSet, ChainStateView, ChainTipState};
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 "Message in block proposed to {chain_id} does not match the previously received messages from \
57 origin {origin:?}: was {bundle:?} instead of {previous_bundle:?}"
58 )]
59 UnexpectedMessage {
60 chain_id: ChainId,
61 origin: ChainId,
62 bundle: Box<MessageBundle>,
63 previous_bundle: Box<MessageBundle>,
64 },
65 #[error(
66 "Message in block proposed to {chain_id} is out of order compared to previous messages \
67 from origin {origin:?}: {bundle:?}. Block and height should be at least: \
68 {next_height}, {next_index}"
69 )]
70 IncorrectMessageOrder {
71 chain_id: ChainId,
72 origin: ChainId,
73 bundle: Box<MessageBundle>,
74 next_height: BlockHeight,
75 next_index: u32,
76 },
77 #[error(
78 "Block proposed to {chain_id} is attempting to reject protected message \
79 {posted_message:?}"
80 )]
81 CannotRejectMessage {
82 chain_id: ChainId,
83 origin: ChainId,
84 posted_message: Box<PostedMessage>,
85 },
86 #[error(
87 "Block proposed to {chain_id} is attempting to skip a message bundle \
88 that cannot be skipped: {bundle:?}"
89 )]
90 CannotSkipMessage {
91 chain_id: ChainId,
92 origin: ChainId,
93 bundle: Box<MessageBundle>,
94 },
95 #[error(
96 "Incoming message bundle in block proposed to {chain_id} has timestamp \
97 {bundle_timestamp:}, which is later than the block timestamp {block_timestamp:}."
98 )]
99 IncorrectBundleTimestamp {
100 chain_id: ChainId,
101 bundle_timestamp: Timestamp,
102 block_timestamp: Timestamp,
103 },
104 #[error("The signature was not created by a valid entity")]
105 InvalidSigner,
106 #[error(
107 "Chain is expecting a next block at height {expected_block_height} but the given block \
108 is at height {found_block_height} instead"
109 )]
110 UnexpectedBlockHeight {
111 expected_block_height: BlockHeight,
112 found_block_height: BlockHeight,
113 },
114 #[error("The previous block hash of a new block should match the last block of the chain")]
115 UnexpectedPreviousBlockHash,
116 #[error("Sequence numbers above the maximal value are not usable for blocks")]
117 BlockHeightOverflow,
118 #[error(
119 "Block timestamp {new} must not be earlier than the parent block's timestamp {parent}"
120 )]
121 InvalidBlockTimestamp { parent: Timestamp, new: Timestamp },
122 #[error("Round number should be at least {0:?}")]
123 InsufficientRound(Round),
124 #[error("Round number should be greater than {0:?}")]
125 InsufficientRoundStrict(Round),
126 #[error("Round number should be {0:?}")]
127 WrongRound(Round),
128 #[error("Already voted to confirm a different block for height {0:?} at round number {1:?}")]
129 HasIncompatibleConfirmedVote(BlockHeight, Round),
130 #[error("Proposal for height {0:?} is not newer than locking block in round {1:?}")]
131 MustBeNewerThanLockingBlock(BlockHeight, Round),
132 #[error("Cannot confirm a block before its predecessors: {current_block_height:?}")]
133 MissingEarlierBlocks { current_block_height: BlockHeight },
134 #[error("Signatures in a certificate must be from different validators")]
135 CertificateValidatorReuse,
136 #[error("Signatures in a certificate must form a quorum")]
137 CertificateRequiresQuorum,
138 #[error(
139 "Inbox gap on chain {chain_id} from origin {origin}: \
140 expected height {expected_height}, got {actual_height}"
141 )]
142 InboxGapDetected {
143 chain_id: ChainId,
144 origin: ChainId,
145 expected_height: BlockHeight,
146 actual_height: BlockHeight,
147 },
148 #[error("Internal error {0}")]
149 InternalError(String),
150 #[error("Corrupted chain state: {0}")]
151 CorruptedChainState(String),
152 #[error("Block proposal has size {0} which is too large")]
153 BlockProposalTooLarge(usize),
154 #[error(transparent)]
155 BcsError(#[from] bcs::Error),
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("Executed block contains fewer oracle responses than requests")]
165 MissingOracleResponseList,
166 #[error("Not signing timeout certificate; current round does not time out")]
167 RoundDoesNotTimeOut,
168 #[error("Not signing timeout certificate; current round times out at time {0}")]
169 NotTimedOutYet(Timestamp),
170 #[error(
171 "Cannot vote for block proposal of chain {chain_id} because {} cross-chain message \
172 bundle(s) have not been received yet",
173 bundles.len()
174 )]
175 MissingCrossChainUpdates {
176 chain_id: ChainId,
177 bundles: Vec<(ChainId, BlockHeight)>,
182 },
183}
184
185impl ChainError {
186 pub fn is_local(&self) -> bool {
190 match self {
191 ChainError::CryptoError(_)
192 | ChainError::ArithmeticError(_)
193 | ChainError::ViewError(ViewError::NotFound(_))
194 | ChainError::InactiveChain(_)
195 | ChainError::IncorrectMessageOrder { .. }
196 | ChainError::CannotRejectMessage { .. }
197 | ChainError::CannotSkipMessage { .. }
198 | ChainError::IncorrectBundleTimestamp { .. }
199 | ChainError::InvalidSigner
200 | ChainError::UnexpectedBlockHeight { .. }
201 | ChainError::UnexpectedPreviousBlockHash
202 | ChainError::BlockHeightOverflow
203 | ChainError::InvalidBlockTimestamp { .. }
204 | ChainError::InsufficientRound(_)
205 | ChainError::InsufficientRoundStrict(_)
206 | ChainError::WrongRound(_)
207 | ChainError::HasIncompatibleConfirmedVote(..)
208 | ChainError::MustBeNewerThanLockingBlock(..)
209 | ChainError::MissingEarlierBlocks { .. }
210 | ChainError::CertificateValidatorReuse
211 | ChainError::CertificateRequiresQuorum
212 | ChainError::BlockProposalTooLarge(_)
213 | ChainError::ClosedChain
214 | ChainError::EmptyBlock
215 | ChainError::AuthorizedApplications(_)
216 | ChainError::MissingMandatoryApplications(_)
217 | ChainError::MissingOracleResponseList
218 | ChainError::RoundDoesNotTimeOut
219 | ChainError::NotTimedOutYet(_)
220 | ChainError::MissingCrossChainUpdates { .. } => false,
221 ChainError::ViewError(_)
222 | ChainError::UnexpectedMessage { .. }
223 | ChainError::InboxGapDetected { .. }
224 | ChainError::InternalError(_)
225 | ChainError::CorruptedChainState(_)
226 | ChainError::BcsError(_) => true,
227 ChainError::ExecutionError(execution_error, _) => execution_error.is_local(),
228 }
229 }
230
231 pub fn error_type(&self) -> String {
237 match self {
238 ChainError::ExecutionError(execution_error, _) => execution_error.error_type(),
239 other => {
240 let variant: &'static str = other.into();
241 format!("ChainError::{variant}")
242 }
243 }
244 }
245}
246
247#[derive(Copy, Clone, Debug)]
249#[cfg_attr(with_testing, derive(Eq, PartialEq))]
250#[allow(missing_docs)]
251pub enum ChainExecutionContext {
252 Query,
253 DescribeApplication,
254 IncomingBundle(u32),
255 Operation(u32),
256 Block,
257}
258
259pub trait ExecutionResultExt<T> {
261 fn with_execution_context(self, context: ChainExecutionContext) -> Result<T, ChainError>;
263}
264
265impl<T, E> ExecutionResultExt<T> for Result<T, E>
266where
267 E: Into<ExecutionError>,
268{
269 fn with_execution_context(self, context: ChainExecutionContext) -> Result<T, ChainError> {
270 self.map_err(|error| ChainError::ExecutionError(Box::new(error.into()), context))
271 }
272}