1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Copyright (c) Zefchain Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! This module manages the state of a Linera chain, including cross-chain communication.

#![deny(clippy::large_futures)]

pub mod block;
mod certificate;

pub mod types {
    pub use super::{block::*, certificate::*};
}

mod chain;
pub mod data_types;
mod inbox;
pub mod manager;
mod outbox;
mod pending_blobs;
#[cfg(with_testing)]
pub mod test;

pub use chain::ChainStateView;
use data_types::{MessageBundle, Origin, PostedMessage};
use linera_base::{
    bcs,
    crypto::{CryptoError, CryptoHash},
    data_types::{ArithmeticError, BlockHeight, Round, Timestamp},
    identifiers::{ApplicationId, BlobId, ChainId},
};
use linera_execution::ExecutionError;
use linera_views::views::ViewError;
use rand_distr::WeightedError;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ChainError {
    #[error("Cryptographic error: {0}")]
    CryptoError(#[from] CryptoError),
    #[error(transparent)]
    ArithmeticError(#[from] ArithmeticError),
    #[error(transparent)]
    ViewError(ViewError),
    #[error("Execution error: {0} during {1:?}")]
    ExecutionError(Box<ExecutionError>, ChainExecutionContext),

    #[error("The chain being queried is not active {0:?}")]
    InactiveChain(ChainId),
    #[error(
        "Cannot vote for block proposal of chain {chain_id:?} because a message \
         from origin {origin:?} at height {height:?} has not been received yet"
    )]
    MissingCrossChainUpdate {
        chain_id: ChainId,
        origin: Box<Origin>,
        height: BlockHeight,
    },
    #[error(
        "Message in block proposed to {chain_id:?} does not match the previously received messages from \
        origin {origin:?}: was {bundle:?} instead of {previous_bundle:?}"
    )]
    UnexpectedMessage {
        chain_id: ChainId,
        origin: Box<Origin>,
        bundle: Box<MessageBundle>,
        previous_bundle: Box<MessageBundle>,
    },
    #[error(
        "Message in block proposed to {chain_id:?} is out of order compared to previous messages \
         from origin {origin:?}: {bundle:?}. Block and height should be at least: \
         {next_height}, {next_index}"
    )]
    IncorrectMessageOrder {
        chain_id: ChainId,
        origin: Box<Origin>,
        bundle: Box<MessageBundle>,
        next_height: BlockHeight,
        next_index: u32,
    },
    #[error(
        "Block proposed to {chain_id:?} is attempting to reject protected message \
        {posted_message:?}"
    )]
    CannotRejectMessage {
        chain_id: ChainId,
        origin: Box<Origin>,
        posted_message: Box<PostedMessage>,
    },
    #[error(
        "Block proposed to {chain_id:?} is attempting to skip a message bundle \
         that cannot be skipped: {bundle:?}"
    )]
    CannotSkipMessage {
        chain_id: ChainId,
        origin: Box<Origin>,
        bundle: Box<MessageBundle>,
    },
    #[error(
        "Incoming message bundle in block proposed to {chain_id:?} has timestamp \
        {bundle_timestamp:}, which is later than the block timestamp {block_timestamp:}."
    )]
    IncorrectBundleTimestamp {
        chain_id: ChainId,
        bundle_timestamp: Timestamp,
        block_timestamp: Timestamp,
    },
    #[error("The signature was not created by a valid entity")]
    InvalidSigner,
    #[error(
        "Was expecting block height {expected_block_height} but found {found_block_height} instead"
    )]
    UnexpectedBlockHeight {
        expected_block_height: BlockHeight,
        found_block_height: BlockHeight,
    },
    #[error("The previous block hash of a new block should match the last block of the chain")]
    UnexpectedPreviousBlockHash,
    #[error("Sequence numbers above the maximal value are not usable for blocks")]
    InvalidBlockHeight,
    #[error("Block timestamp must not be earlier than the parent block's.")]
    InvalidBlockTimestamp,
    #[error("Cannot initiate a new block while the previous one is still pending confirmation")]
    PreviousBlockMustBeConfirmedFirst,
    #[error("Round number should be at least {0:?}")]
    InsufficientRound(Round),
    #[error("Round number should be greater than {0:?}")]
    InsufficientRoundStrict(Round),
    #[error("Round number should be {0:?}")]
    WrongRound(Round),
    #[error("Already voted to confirm a different block for height {0:?} at round number {1:?}")]
    HasIncompatibleConfirmedVote(BlockHeight, Round),
    #[error("Proposal for height {0:?} is not newer than locking block in round {1:?}")]
    MustBeNewerThanLockingBlock(BlockHeight, Round),
    #[error("Cannot confirm a block before its predecessors: {current_block_height:?}")]
    MissingEarlierBlocks { current_block_height: BlockHeight },
    #[error("Signatures in a certificate must be from different validators")]
    CertificateValidatorReuse,
    #[error("Signatures in a certificate must form a quorum")]
    CertificateRequiresQuorum,
    #[error("Certificate signature verification failed: {error}")]
    CertificateSignatureVerificationFailed { error: String },
    #[error("Internal error {0}")]
    InternalError(String),
    #[error("Block proposal is too large")]
    BlockProposalTooLarge,
    #[error(transparent)]
    BcsError(#[from] bcs::Error),
    #[error("Insufficient balance to pay the fees")]
    InsufficientBalance,
    #[error("Invalid owner weights: {0}")]
    OwnerWeightError(#[from] WeightedError),
    #[error("Closed chains cannot have operations, accepted messages or empty blocks")]
    ClosedChain,
    #[error("All operations on this chain must be from one of the following applications: {0:?}")]
    AuthorizedApplications(Vec<ApplicationId>),
    #[error("Missing operations or messages from mandatory applications: {0:?}")]
    MissingMandatoryApplications(Vec<ApplicationId>),
    #[error("Can't use grant across different broadcast messages")]
    GrantUseOnBroadcast,
    #[error("ExecutedBlock contains fewer oracle responses than requests")]
    MissingOracleResponseList,
    #[error("Unexpected hash for CertificateValue! Expected: {expected:?}, Actual: {actual:?}")]
    CertificateValueHashMismatch {
        expected: CryptoHash,
        actual: CryptoHash,
    },
    #[error("Blobs not found: {0:?}")]
    BlobsNotFound(Vec<BlobId>),
}

impl From<ViewError> for ChainError {
    fn from(error: ViewError) -> Self {
        match error {
            ViewError::BlobsNotFound(blob_ids) => ChainError::BlobsNotFound(blob_ids),
            error => ChainError::ViewError(error),
        }
    }
}

#[derive(Copy, Clone, Debug)]
pub enum ChainExecutionContext {
    Query,
    DescribeApplication,
    IncomingBundle(u32),
    Operation(u32),
    Block,
}

pub trait ExecutionResultExt<T> {
    fn with_execution_context(self, context: ChainExecutionContext) -> Result<T, ChainError>;
}

impl<T, E> ExecutionResultExt<T> for Result<T, E>
where
    E: Into<ExecutionError>,
{
    fn with_execution_context(self, context: ChainExecutionContext) -> Result<T, ChainError> {
        self.map_err(|error| ChainError::ExecutionError(Box::new(error.into()), context))
    }
}