Skip to main content

consensus_core/
error.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use consensus_config::{AuthorityIndex, Epoch, Stake};
5use consensus_types::block::{BlockRef, Round};
6use fastcrypto::error::FastCryptoError;
7use strum_macros::IntoStaticStr;
8use thiserror::Error;
9use typed_store::TypedStoreError;
10
11use crate::{
12    commit::{Commit, CommitIndex},
13    network::PeerId,
14};
15
16/// Errors that can occur when processing blocks, reading from storage, or encountering shutdown.
17#[derive(Clone, Debug, Error, IntoStaticStr)]
18pub enum ConsensusError {
19    #[error("Error deserializing block: {0}")]
20    MalformedBlock(bcs::Error),
21
22    #[error("Unexpected block form on the wire")]
23    UnexpectedBlockForm,
24
25    #[error("Error decoding block envelope: {0}")]
26    MalformedBlockEnvelope(prost::DecodeError),
27
28    #[error("Error deserializing commit: {0}")]
29    MalformedCommit(bcs::Error),
30
31    #[error("Error serializing: {0}")]
32    SerializationFailure(bcs::Error),
33
34    #[error("Block contains a transaction that is too large: {size} > {limit}")]
35    TransactionTooLarge { size: usize, limit: usize },
36
37    #[error("Block contains too many transactions: {count} > {limit}")]
38    TooManyTransactions { count: usize, limit: usize },
39
40    #[error("Block contains too many transaction bytes: {size} > {limit}")]
41    TooManyTransactionBytes { size: usize, limit: usize },
42
43    #[error("Unexpected block authority {0} from peer {1}")]
44    UnexpectedAuthority(AuthorityIndex, AuthorityIndex),
45
46    #[error("Block has wrong epoch: expected {expected}, actual {actual}")]
47    WrongEpoch { expected: Epoch, actual: Epoch },
48
49    #[error("Genesis blocks should only be generated from Committee!")]
50    UnexpectedGenesisBlock,
51
52    #[error("Block version does not match the protocol config: {version}")]
53    UnexpectedBlockVersion { version: String },
54
55    #[error(
56        "Transaction vote cutoff round must be lower than the block round: cutoff {cutoff}, block {block}"
57    )]
58    InvalidTransactionVotesCutoff { cutoff: Round, block: Round },
59
60    #[error("Invalid transaction votes: {0}")]
61    InvalidTransactionVotes(String),
62
63    #[error("Genesis blocks should not be queried!")]
64    UnexpectedGenesisBlockRequested,
65
66    #[error("Expected {requested} but received {received} blocks returned from peer {peer}")]
67    UnexpectedNumberOfBlocksFetched {
68        peer: PeerId,
69        requested: usize,
70        received: usize,
71    },
72
73    #[error("Unexpected block returned while fetching missing blocks")]
74    UnexpectedFetchedBlock {
75        index: AuthorityIndex,
76        block_ref: BlockRef,
77    },
78
79    #[error(
80        "Unexpected block {block_ref} returned while fetching last own block from peer {index}"
81    )]
82    UnexpectedLastOwnBlock {
83        index: AuthorityIndex,
84        block_ref: BlockRef,
85    },
86
87    #[error(
88        "Too many blocks have been returned from authority {0} when requesting to fetch missing blocks"
89    )]
90    TooManyFetchedBlocksReturned(AuthorityIndex),
91
92    #[error("Too many authorities have been provided from authority {0}")]
93    TooManyAuthoritiesProvided(AuthorityIndex),
94
95    #[error(
96        "Provided size of highest accepted rounds parameter, {0}, is different than committee size, {1}"
97    )]
98    InvalidSizeOfHighestAcceptedRounds(usize, usize),
99
100    #[error("Invalid fetch blocks request: {0}")]
101    InvalidFetchBlocksRequest(String),
102
103    #[error("Invalid authority index at {loc}: {index} > {max}")]
104    InvalidAuthorityIndex {
105        loc: String,
106        index: AuthorityIndex,
107        max: usize,
108    },
109
110    #[error("Failed to deserialize signature: {0}")]
111    MalformedSignature(FastCryptoError),
112
113    #[error("Failed to verify the block's signature: {0}")]
114    SignatureVerificationFailure(FastCryptoError),
115
116    #[error("Synchronizer for fetching blocks directly from {0} is saturated")]
117    SynchronizerSaturated(String),
118
119    #[error("Peer {0} is unavailable")]
120    PeerUnavailable(String),
121
122    #[error("Peer not found for block synchronization: {0}")]
123    PeerNotFound(String),
124
125    #[error("Block {block_ref:?} rejected: {reason}")]
126    BlockRejected { block_ref: BlockRef, reason: String },
127
128    #[error(
129        "Ancestor is in wrong position: block {block_authority}, ancestor {ancestor_authority}, position {position}"
130    )]
131    InvalidAncestorPosition {
132        block_authority: AuthorityIndex,
133        ancestor_authority: AuthorityIndex,
134        position: usize,
135    },
136
137    #[error("Ancestor's round ({ancestor}) should be lower than the block's round ({block})")]
138    InvalidAncestorRound { ancestor: Round, block: Round },
139
140    #[error("Ancestor {0} not found among genesis blocks!")]
141    InvalidGenesisAncestor(BlockRef),
142
143    #[error("Too many ancestors in the block: {0} > {1}")]
144    TooManyAncestors(usize, usize),
145
146    #[error("Ancestors from the same authority {0}")]
147    DuplicatedAncestorsAuthority(AuthorityIndex),
148
149    #[error("Insufficient stake from parents: {parent_stakes} < {quorum}")]
150    InsufficientParentStakes { parent_stakes: Stake, quorum: Stake },
151
152    #[error("Invalid transaction: {0}")]
153    InvalidTransaction(String),
154
155    #[error("Received no commit from peer {peer}")]
156    NoCommitReceived { peer: PeerId },
157
158    #[error(
159        "Received unexpected start commit from peer {peer}: requested {start}, received {commit:?}"
160    )]
161    UnexpectedStartCommit {
162        peer: PeerId,
163        start: CommitIndex,
164        commit: Box<Commit>,
165    },
166
167    #[error(
168        "Received unexpected commit sequence from peer {peer}: {prev_commit:?}, {curr_commit:?}"
169    )]
170    UnexpectedCommitSequence {
171        peer: PeerId,
172        prev_commit: Box<Commit>,
173        curr_commit: Box<Commit>,
174    },
175
176    #[error("Not enough votes ({stake}) on end commit from peer {peer}: {commit:?}")]
177    NotEnoughCommitVotes {
178        stake: Stake,
179        peer: PeerId,
180        commit: Box<Commit>,
181    },
182
183    #[error("Received unexpected block from peer {peer}: {requested:?} vs {received:?}")]
184    UnexpectedBlockForCommit {
185        peer: PeerId,
186        requested: BlockRef,
187        received: BlockRef,
188    },
189
190    #[error(
191        "Unexpected certified commit index and last committed index. Expected next commit index to be {expected_commit_index}, but found {commit_index}"
192    )]
193    UnexpectedCertifiedCommitIndex {
194        expected_commit_index: CommitIndex,
195        commit_index: CommitIndex,
196    },
197
198    #[error("RocksDB failure: {0}")]
199    RocksDBFailure(#[from] TypedStoreError),
200
201    #[error("Unknown network peer: {0}")]
202    UnknownNetworkPeer(String),
203
204    #[error("Peer {0} is disconnected.")]
205    PeerDisconnected(String),
206
207    #[error("Network config error: {0:?}")]
208    NetworkConfig(String),
209
210    #[error("Failed to connect as client: {0:?}")]
211    NetworkClientConnection(String),
212
213    #[error("Failed to send request: {0:?}")]
214    NetworkRequest(String),
215
216    #[error("Request timeout: {0:?}")]
217    NetworkRequestTimeout(String),
218
219    #[error("Consensus has shut down!")]
220    Shutdown,
221}
222
223impl ConsensusError {
224    /// Returns the error name - only the enun name without any parameters - as a static string.
225    pub fn name(&self) -> &'static str {
226        self.into()
227    }
228}
229
230pub type ConsensusResult<T> = Result<T, ConsensusError>;
231
232#[macro_export]
233macro_rules! bail {
234    ($e:expr) => {
235        return Err($e);
236    };
237}
238
239#[macro_export(local_inner_macros)]
240macro_rules! ensure {
241    ($cond:expr, $e:expr) => {
242        if !($cond) {
243            bail!($e);
244        }
245    };
246}
247
248#[cfg(test)]
249mod test {
250    use super::*;
251
252    /// This test ensures that consensus errors when converted to a static string are the same as the enum name without
253    /// any parameterers included to the result string.
254    #[test]
255    fn test_error_name() {
256        {
257            let error = ConsensusError::InvalidAncestorRound {
258                ancestor: 10,
259                block: 11,
260            };
261            let error: &'static str = error.into();
262
263            assert_eq!(error, "InvalidAncestorRound");
264        }
265
266        {
267            let error = ConsensusError::InvalidAuthorityIndex {
268                loc: "test".to_string(),
269                index: AuthorityIndex::new_for_test(3),
270                max: 10,
271            };
272            assert_eq!(error.name(), "InvalidAuthorityIndex");
273        }
274
275        {
276            let error = ConsensusError::InsufficientParentStakes {
277                parent_stakes: 5,
278                quorum: 20,
279            };
280            assert_eq!(error.name(), "InsufficientParentStakes");
281        }
282    }
283}