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