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)]
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("Internal error {0}")]
141 InternalError(String),
142 #[error("Block proposal has size {0} which is too large")]
143 BlockProposalTooLarge(usize),
144 #[error(transparent)]
145 BcsError(#[from] bcs::Error),
146 #[error("Closed chains cannot have operations, accepted messages or empty blocks")]
147 ClosedChain,
148 #[error("Empty blocks are not allowed")]
149 EmptyBlock,
150 #[error("All operations on this chain must be from one of the following applications: {0:?}")]
151 AuthorizedApplications(Vec<ApplicationId>),
152 #[error("Missing operations or messages from mandatory applications: {0:?}")]
153 MissingMandatoryApplications(Vec<ApplicationId>),
154 #[error("Executed block contains fewer oracle responses than requests")]
155 MissingOracleResponseList,
156 #[error("Not signing timeout certificate; current round does not time out")]
157 RoundDoesNotTimeOut,
158 #[error("Not signing timeout certificate; current round times out at time {0}")]
159 NotTimedOutYet(Timestamp),
160}
161
162impl ChainError {
163 pub fn is_local(&self) -> bool {
167 match self {
168 ChainError::CryptoError(_)
169 | ChainError::ArithmeticError(_)
170 | ChainError::ViewError(ViewError::NotFound(_))
171 | ChainError::InactiveChain(_)
172 | ChainError::IncorrectMessageOrder { .. }
173 | ChainError::CannotRejectMessage { .. }
174 | ChainError::CannotSkipMessage { .. }
175 | ChainError::IncorrectBundleTimestamp { .. }
176 | ChainError::InvalidSigner
177 | ChainError::UnexpectedBlockHeight { .. }
178 | ChainError::UnexpectedPreviousBlockHash
179 | ChainError::BlockHeightOverflow
180 | ChainError::InvalidBlockTimestamp { .. }
181 | ChainError::InsufficientRound(_)
182 | ChainError::InsufficientRoundStrict(_)
183 | ChainError::WrongRound(_)
184 | ChainError::HasIncompatibleConfirmedVote(..)
185 | ChainError::MustBeNewerThanLockingBlock(..)
186 | ChainError::MissingEarlierBlocks { .. }
187 | ChainError::CertificateValidatorReuse
188 | ChainError::CertificateRequiresQuorum
189 | ChainError::BlockProposalTooLarge(_)
190 | ChainError::ClosedChain
191 | ChainError::EmptyBlock
192 | ChainError::AuthorizedApplications(_)
193 | ChainError::MissingMandatoryApplications(_)
194 | ChainError::MissingOracleResponseList
195 | ChainError::RoundDoesNotTimeOut
196 | ChainError::NotTimedOutYet(_)
197 | ChainError::MissingCrossChainUpdate { .. } => false,
198 ChainError::ViewError(_)
199 | ChainError::UnexpectedMessage { .. }
200 | ChainError::InternalError(_)
201 | ChainError::BcsError(_) => true,
202 ChainError::ExecutionError(execution_error, _) => execution_error.is_local(),
203 }
204 }
205}
206
207#[derive(Copy, Clone, Debug)]
208#[cfg_attr(with_testing, derive(Eq, PartialEq))]
209pub enum ChainExecutionContext {
210 Query,
211 DescribeApplication,
212 IncomingBundle(u32),
213 Operation(u32),
214 Block,
215}
216
217pub trait ExecutionResultExt<T> {
218 fn with_execution_context(self, context: ChainExecutionContext) -> Result<T, ChainError>;
219}
220
221impl<T, E> ExecutionResultExt<T> for Result<T, E>
222where
223 E: Into<ExecutionError>,
224{
225 fn with_execution_context(self, context: ChainExecutionContext) -> Result<T, ChainError> {
226 self.map_err(|error| ChainError::ExecutionError(Box::new(error.into()), context))
227 }
228}