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