1pub 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, strum::IntoStaticStr)]
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(
141 "Inbox gap on chain {chain_id} from origin {origin}: \
142 expected height {expected_height}, got {actual_height}"
143 )]
144 InboxGapDetected {
145 chain_id: ChainId,
146 origin: ChainId,
147 expected_height: BlockHeight,
148 actual_height: BlockHeight,
149 },
150 #[error("Internal error {0}")]
151 InternalError(String),
152 #[error("Corrupted chain state: {0}")]
153 CorruptedChainState(String),
154 #[error("Block proposal has size {0} which is too large")]
155 BlockProposalTooLarge(usize),
156 #[error(transparent)]
157 BcsError(#[from] bcs::Error),
158 #[error("Closed chains cannot have operations, accepted messages or empty blocks")]
159 ClosedChain,
160 #[error("Empty blocks are not allowed")]
161 EmptyBlock,
162 #[error("All operations on this chain must be from one of the following applications: {0:?}")]
163 AuthorizedApplications(Vec<ApplicationId>),
164 #[error("Missing operations or messages from mandatory applications: {0:?}")]
165 MissingMandatoryApplications(Vec<ApplicationId>),
166 #[error("Executed block contains fewer oracle responses than requests")]
167 MissingOracleResponseList,
168 #[error("Not signing timeout certificate; current round does not time out")]
169 RoundDoesNotTimeOut,
170 #[error("Not signing timeout certificate; current round times out at time {0}")]
171 NotTimedOutYet(Timestamp),
172}
173
174impl ChainError {
175 pub fn is_local(&self) -> bool {
179 match self {
180 ChainError::CryptoError(_)
181 | ChainError::ArithmeticError(_)
182 | ChainError::ViewError(ViewError::NotFound(_))
183 | ChainError::InactiveChain(_)
184 | ChainError::IncorrectMessageOrder { .. }
185 | ChainError::CannotRejectMessage { .. }
186 | ChainError::CannotSkipMessage { .. }
187 | ChainError::IncorrectBundleTimestamp { .. }
188 | ChainError::InvalidSigner
189 | ChainError::UnexpectedBlockHeight { .. }
190 | ChainError::UnexpectedPreviousBlockHash
191 | ChainError::BlockHeightOverflow
192 | ChainError::InvalidBlockTimestamp { .. }
193 | ChainError::InsufficientRound(_)
194 | ChainError::InsufficientRoundStrict(_)
195 | ChainError::WrongRound(_)
196 | ChainError::HasIncompatibleConfirmedVote(..)
197 | ChainError::MustBeNewerThanLockingBlock(..)
198 | ChainError::MissingEarlierBlocks { .. }
199 | ChainError::CertificateValidatorReuse
200 | ChainError::CertificateRequiresQuorum
201 | ChainError::BlockProposalTooLarge(_)
202 | ChainError::ClosedChain
203 | ChainError::EmptyBlock
204 | ChainError::AuthorizedApplications(_)
205 | ChainError::MissingMandatoryApplications(_)
206 | ChainError::MissingOracleResponseList
207 | ChainError::RoundDoesNotTimeOut
208 | ChainError::NotTimedOutYet(_)
209 | ChainError::MissingCrossChainUpdate { .. } => false,
210 ChainError::ViewError(_)
211 | ChainError::UnexpectedMessage { .. }
212 | ChainError::InboxGapDetected { .. }
213 | ChainError::InternalError(_)
214 | ChainError::CorruptedChainState(_)
215 | ChainError::BcsError(_) => true,
216 ChainError::ExecutionError(execution_error, _) => execution_error.is_local(),
217 }
218 }
219
220 pub fn error_type(&self) -> String {
226 match self {
227 ChainError::ExecutionError(execution_error, _) => execution_error.error_type(),
228 other => {
229 let variant: &'static str = other.into();
230 format!("ChainError::{variant}")
231 }
232 }
233 }
234}
235
236#[derive(Copy, Clone, Debug)]
237#[cfg_attr(with_testing, derive(Eq, PartialEq))]
238pub enum ChainExecutionContext {
239 Query,
240 DescribeApplication,
241 IncomingBundle(u32),
242 Operation(u32),
243 Block,
244}
245
246pub trait ExecutionResultExt<T> {
247 fn with_execution_context(self, context: ChainExecutionContext) -> Result<T, ChainError>;
248}
249
250impl<T, E> ExecutionResultExt<T> for Result<T, E>
251where
252 E: Into<ExecutionError>,
253{
254 fn with_execution_context(self, context: ChainExecutionContext) -> Result<T, ChainError> {
255 self.map_err(|error| ChainError::ExecutionError(Box::new(error.into()), context))
256 }
257}