linera_chain/
block.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2// Copyright (c) Zefchain Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    borrow::Cow,
7    collections::{BTreeMap, BTreeSet},
8    fmt::Debug,
9};
10
11use async_graphql::SimpleObject;
12use linera_base::{
13    crypto::{BcsHashable, CryptoHash},
14    data_types::{Blob, BlockHeight, Epoch, Event, OracleResponse, Timestamp},
15    hashed::Hashed,
16    identifiers::{AccountOwner, BlobId, BlobType, ChainId},
17};
18use linera_execution::{BlobState, Operation, OutgoingMessage};
19use serde::{ser::SerializeStruct, Deserialize, Serialize};
20use thiserror::Error;
21
22use crate::{
23    data_types::{
24        BlockExecutionOutcome, IncomingBundle, MessageBundle, OperationResult, OutgoingMessageExt,
25        ProposedBlock,
26    },
27    types::CertificateValue,
28};
29
30/// Wrapper around a `Block` that has been validated.
31#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
32#[serde(transparent)]
33pub struct ValidatedBlock(Hashed<Block>);
34
35impl ValidatedBlock {
36    /// Creates a new `ValidatedBlock` from a `Block`.
37    pub fn new(block: Block) -> Self {
38        Self(Hashed::new(block))
39    }
40
41    pub fn from_hashed(block: Hashed<Block>) -> Self {
42        Self(block)
43    }
44
45    pub fn inner(&self) -> &Hashed<Block> {
46        &self.0
47    }
48
49    /// Returns a reference to the [`Block`] contained in this `ValidatedBlock`.
50    pub fn block(&self) -> &Block {
51        self.0.inner()
52    }
53
54    /// Consumes this `ValidatedBlock`, returning the [`Block`] it contains.
55    pub fn into_inner(self) -> Block {
56        self.0.into_inner()
57    }
58
59    pub fn to_log_str(&self) -> &'static str {
60        "validated_block"
61    }
62
63    pub fn chain_id(&self) -> ChainId {
64        self.0.inner().header.chain_id
65    }
66
67    pub fn height(&self) -> BlockHeight {
68        self.0.inner().header.height
69    }
70
71    pub fn epoch(&self) -> Epoch {
72        self.0.inner().header.epoch
73    }
74}
75
76/// Wrapper around a `Block` that has been confirmed.
77#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
78#[serde(transparent)]
79pub struct ConfirmedBlock(Hashed<Block>);
80
81#[async_graphql::Object(cache_control(no_cache))]
82impl ConfirmedBlock {
83    #[graphql(derived(name = "block"))]
84    async fn _block(&self) -> Block {
85        self.0.inner().clone()
86    }
87
88    async fn status(&self) -> String {
89        "confirmed".to_string()
90    }
91
92    async fn hash(&self) -> CryptoHash {
93        self.0.hash()
94    }
95}
96
97impl ConfirmedBlock {
98    pub fn new(block: Block) -> Self {
99        Self(Hashed::new(block))
100    }
101
102    pub fn from_hashed(block: Hashed<Block>) -> Self {
103        Self(block)
104    }
105
106    pub fn inner(&self) -> &Hashed<Block> {
107        &self.0
108    }
109
110    pub fn into_inner(self) -> Hashed<Block> {
111        self.0
112    }
113
114    /// Returns a reference to the `Block` contained in this `ConfirmedBlock`.
115    pub fn block(&self) -> &Block {
116        self.0.inner()
117    }
118
119    /// Consumes this `ConfirmedBlock`, returning the `Block` it contains.
120    pub fn into_block(self) -> Block {
121        self.0.into_inner()
122    }
123
124    pub fn chain_id(&self) -> ChainId {
125        self.0.inner().header.chain_id
126    }
127
128    pub fn height(&self) -> BlockHeight {
129        self.0.inner().header.height
130    }
131
132    pub fn to_log_str(&self) -> &'static str {
133        "confirmed_block"
134    }
135
136    /// Returns whether this block matches the proposal.
137    pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
138        self.block().matches_proposed_block(block)
139    }
140
141    /// Returns a blob state that applies to all blobs used by this block.
142    pub fn to_blob_state(&self, is_stored_block: bool) -> BlobState {
143        BlobState {
144            last_used_by: is_stored_block.then_some(self.0.hash()),
145            chain_id: self.chain_id(),
146            block_height: self.height(),
147            epoch: is_stored_block.then_some(self.epoch()),
148        }
149    }
150}
151
152#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
153#[serde(transparent)]
154pub struct Timeout(Hashed<TimeoutInner>);
155
156#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
157#[serde(rename = "Timeout")]
158pub struct TimeoutInner {
159    pub chain_id: ChainId,
160    pub height: BlockHeight,
161    pub epoch: Epoch,
162}
163
164impl Timeout {
165    pub fn new(chain_id: ChainId, height: BlockHeight, epoch: Epoch) -> Self {
166        let inner = TimeoutInner {
167            chain_id,
168            height,
169            epoch,
170        };
171        Self(Hashed::new(inner))
172    }
173
174    pub fn to_log_str(&self) -> &'static str {
175        "timeout"
176    }
177
178    pub fn chain_id(&self) -> ChainId {
179        self.0.inner().chain_id
180    }
181
182    pub fn height(&self) -> BlockHeight {
183        self.0.inner().height
184    }
185
186    pub fn epoch(&self) -> Epoch {
187        self.0.inner().epoch
188    }
189
190    pub fn inner(&self) -> &Hashed<TimeoutInner> {
191        &self.0
192    }
193}
194
195impl BcsHashable<'_> for Timeout {}
196impl BcsHashable<'_> for TimeoutInner {}
197
198/// Failure to convert a `Certificate` into one of the expected certificate types.
199#[derive(Clone, Copy, Debug, Error)]
200pub enum ConversionError {
201    /// Failure to convert to [`ConfirmedBlock`] certificate.
202    #[error("Expected a `ConfirmedBlockCertificate` value")]
203    ConfirmedBlock,
204
205    /// Failure to convert to [`ValidatedBlock`] certificate.
206    #[error("Expected a `ValidatedBlockCertificate` value")]
207    ValidatedBlock,
208
209    /// Failure to convert to [`Timeout`] certificate.
210    #[error("Expected a `TimeoutCertificate` value")]
211    Timeout,
212}
213
214/// Block defines the atomic unit of growth of the Linera chain.
215///
216/// As part of the block body, contains all the incoming messages
217/// and operations to execute which define a state transition of the chain.
218/// Resulting messages produced by the operations are also included in the block body,
219/// together with oracle responses and events.
220#[derive(Debug, PartialEq, Eq, Hash, Clone, SimpleObject)]
221pub struct Block {
222    /// Header of the block containing metadata of the block.
223    pub header: BlockHeader,
224    /// Body of the block containing all of the data.
225    pub body: BlockBody,
226}
227
228impl Serialize for Block {
229    fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
230        let mut state = serializer.serialize_struct("Block", 2)?;
231
232        let header = SerializedHeader {
233            chain_id: self.header.chain_id,
234            epoch: self.header.epoch,
235            height: self.header.height,
236            timestamp: self.header.timestamp,
237            state_hash: self.header.state_hash,
238            previous_block_hash: self.header.previous_block_hash,
239            authenticated_signer: self.header.authenticated_signer,
240        };
241        state.serialize_field("header", &header)?;
242        state.serialize_field("body", &self.body)?;
243        state.end()
244    }
245}
246
247impl<'de> Deserialize<'de> for Block {
248    fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
249        #[derive(Deserialize)]
250        #[serde(rename = "Block")]
251        struct Inner {
252            header: SerializedHeader,
253            body: BlockBody,
254        }
255        let inner = Inner::deserialize(deserializer)?;
256
257        let bundles_hash = hashing::hash_vec(&inner.body.incoming_bundles);
258        let messages_hash = hashing::hash_vec_vec(&inner.body.messages);
259        let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
260            inner: Cow::Borrowed(&inner.body.previous_message_blocks),
261        });
262        let operations_hash = hashing::hash_vec(&inner.body.operations);
263        let oracle_responses_hash = hashing::hash_vec_vec(&inner.body.oracle_responses);
264        let events_hash = hashing::hash_vec_vec(&inner.body.events);
265        let blobs_hash = hashing::hash_vec_vec(&inner.body.blobs);
266        let operation_results_hash = hashing::hash_vec(&inner.body.operation_results);
267
268        let header = BlockHeader {
269            chain_id: inner.header.chain_id,
270            epoch: inner.header.epoch,
271            height: inner.header.height,
272            timestamp: inner.header.timestamp,
273            state_hash: inner.header.state_hash,
274            previous_block_hash: inner.header.previous_block_hash,
275            authenticated_signer: inner.header.authenticated_signer,
276            bundles_hash,
277            operations_hash,
278            messages_hash,
279            previous_message_blocks_hash,
280            oracle_responses_hash,
281            events_hash,
282            blobs_hash,
283            operation_results_hash,
284        };
285
286        Ok(Self {
287            header,
288            body: inner.body,
289        })
290    }
291}
292
293/// Succinct representation of a block.
294/// Contains all the metadata to follow the chain of blocks or verifying
295/// inclusion (event, message, oracle response, etc.) in the block's body.
296#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
297pub struct BlockHeader {
298    /// The chain to which this block belongs.
299    pub chain_id: ChainId,
300    /// The number identifying the current configuration.
301    pub epoch: Epoch,
302    /// The block height.
303    pub height: BlockHeight,
304    /// The timestamp when this block was created.
305    pub timestamp: Timestamp,
306    /// The hash of the chain's execution state after this block.
307    pub state_hash: CryptoHash,
308    /// Certified hash of the previous block in the chain, if any.
309    pub previous_block_hash: Option<CryptoHash>,
310    /// The user signing for the operations in the block and paying for their execution
311    /// fees. If set, this must be the `owner` in the block proposal. `None` means that
312    /// the default account of the chain is used. This value is also used as recipient of
313    /// potential refunds for the message grants created by the operations.
314    pub authenticated_signer: Option<AccountOwner>,
315
316    // Inputs to the block, chosen by the block proposer.
317    /// Cryptographic hash of all the incoming bundles in the block.
318    pub bundles_hash: CryptoHash,
319    /// Cryptographic hash of all the operations in the block.
320    pub operations_hash: CryptoHash,
321
322    // Outcome of the block execution.
323    /// Cryptographic hash of all the messages in the block.
324    pub messages_hash: CryptoHash,
325    /// Cryptographic hash of the lookup table for previous sending blocks.
326    pub previous_message_blocks_hash: CryptoHash,
327    /// Cryptographic hash of all the oracle responses in the block.
328    pub oracle_responses_hash: CryptoHash,
329    /// Cryptographic hash of all the events in the block.
330    pub events_hash: CryptoHash,
331    /// Cryptographic hash of all the created blobs in the block.
332    pub blobs_hash: CryptoHash,
333    /// A cryptographic hash of the execution results of all operations in a block.
334    pub operation_results_hash: CryptoHash,
335}
336
337/// The body of a block containing all the data included in the block.
338#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
339pub struct BlockBody {
340    /// A selection of incoming messages to be executed first. Successive messages of the same
341    /// sender and height are grouped together for conciseness.
342    pub incoming_bundles: Vec<IncomingBundle>,
343    /// The operations to execute.
344    pub operations: Vec<Operation>,
345    /// The list of outgoing messages for each transaction.
346    pub messages: Vec<Vec<OutgoingMessage>>,
347    /// The hashes of previous blocks that sent messages to the same recipients.
348    pub previous_message_blocks: BTreeMap<ChainId, CryptoHash>,
349    /// The record of oracle responses for each transaction.
350    pub oracle_responses: Vec<Vec<OracleResponse>>,
351    /// The list of events produced by each transaction.
352    pub events: Vec<Vec<Event>>,
353    /// The list of blobs produced by each transaction.
354    pub blobs: Vec<Vec<Blob>>,
355    /// The execution result for each operation.
356    pub operation_results: Vec<OperationResult>,
357}
358
359impl Block {
360    pub fn new(block: ProposedBlock, outcome: BlockExecutionOutcome) -> Self {
361        let bundles_hash = hashing::hash_vec(&block.incoming_bundles);
362        let messages_hash = hashing::hash_vec_vec(&outcome.messages);
363        let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
364            inner: Cow::Borrowed(&outcome.previous_message_blocks),
365        });
366        let operations_hash = hashing::hash_vec(&block.operations);
367        let oracle_responses_hash = hashing::hash_vec_vec(&outcome.oracle_responses);
368        let events_hash = hashing::hash_vec_vec(&outcome.events);
369        let blobs_hash = hashing::hash_vec_vec(&outcome.blobs);
370        let operation_results_hash = hashing::hash_vec(&outcome.operation_results);
371
372        let header = BlockHeader {
373            chain_id: block.chain_id,
374            epoch: block.epoch,
375            height: block.height,
376            timestamp: block.timestamp,
377            state_hash: outcome.state_hash,
378            previous_block_hash: block.previous_block_hash,
379            authenticated_signer: block.authenticated_signer,
380            bundles_hash,
381            operations_hash,
382            messages_hash,
383            previous_message_blocks_hash,
384            oracle_responses_hash,
385            events_hash,
386            blobs_hash,
387            operation_results_hash,
388        };
389
390        let body = BlockBody {
391            incoming_bundles: block.incoming_bundles,
392            operations: block.operations,
393            messages: outcome.messages,
394            previous_message_blocks: outcome.previous_message_blocks,
395            oracle_responses: outcome.oracle_responses,
396            events: outcome.events,
397            blobs: outcome.blobs,
398            operation_results: outcome.operation_results,
399        };
400
401        Self { header, body }
402    }
403
404    /// Returns the bundles of messages sent via the given medium to the specified
405    /// recipient. Messages originating from different transactions of the original block
406    /// are kept in separate bundles. If the medium is a channel, does not verify that the
407    /// recipient is actually subscribed to that channel.
408    pub fn message_bundles_for(
409        &self,
410        recipient: ChainId,
411        certificate_hash: CryptoHash,
412    ) -> impl Iterator<Item = (Epoch, MessageBundle)> + '_ {
413        let mut index = 0u32;
414        let block_height = self.header.height;
415        let block_timestamp = self.header.timestamp;
416        let block_epoch = self.header.epoch;
417
418        (0u32..)
419            .zip(self.messages())
420            .filter_map(move |(transaction_index, txn_messages)| {
421                let messages = (index..)
422                    .zip(txn_messages)
423                    .filter(|(_, message)| message.destination == recipient)
424                    .map(|(idx, message)| message.clone().into_posted(idx))
425                    .collect::<Vec<_>>();
426                index += txn_messages.len() as u32;
427                (!messages.is_empty()).then(|| {
428                    let bundle = MessageBundle {
429                        height: block_height,
430                        timestamp: block_timestamp,
431                        certificate_hash,
432                        transaction_index,
433                        messages,
434                    };
435                    (block_epoch, bundle)
436                })
437            })
438    }
439
440    /// Returns all the blob IDs required by this block.
441    /// Either as oracle responses or as published blobs.
442    pub fn required_blob_ids(&self) -> BTreeSet<BlobId> {
443        let mut blob_ids = self.oracle_blob_ids();
444        blob_ids.extend(self.published_blob_ids());
445        blob_ids.extend(self.created_blob_ids());
446        if self.header.height == BlockHeight(0) {
447            // the initial block implicitly depends on the chain description blob
448            blob_ids.insert(BlobId::new(
449                self.header.chain_id.0,
450                BlobType::ChainDescription,
451            ));
452        }
453        blob_ids
454    }
455
456    /// Returns whether this block requires the blob with the specified ID.
457    pub fn requires_or_creates_blob(&self, blob_id: &BlobId) -> bool {
458        self.oracle_blob_ids().contains(blob_id)
459            || self.published_blob_ids().contains(blob_id)
460            || self.created_blob_ids().contains(blob_id)
461            || (self.header.height == BlockHeight(0)
462                && (blob_id.blob_type == BlobType::ChainDescription
463                    && blob_id.hash == self.header.chain_id.0))
464    }
465
466    /// Returns all the published blob IDs in this block's operations.
467    pub fn published_blob_ids(&self) -> BTreeSet<BlobId> {
468        self.body
469            .operations
470            .iter()
471            .flat_map(Operation::published_blob_ids)
472            .collect()
473    }
474
475    /// Returns all the blob IDs created by the block's operations.
476    pub fn created_blob_ids(&self) -> BTreeSet<BlobId> {
477        self.body
478            .blobs
479            .iter()
480            .flatten()
481            .map(|blob| blob.id())
482            .collect()
483    }
484
485    /// Returns all the blobs created by the block's operations.
486    pub fn created_blobs(&self) -> BTreeMap<BlobId, Blob> {
487        self.body
488            .blobs
489            .iter()
490            .flatten()
491            .map(|blob| (blob.id(), blob.clone()))
492            .collect()
493    }
494
495    /// Returns set of blob IDs that were a result of an oracle call.
496    pub fn oracle_blob_ids(&self) -> BTreeSet<BlobId> {
497        let mut required_blob_ids = BTreeSet::new();
498        for responses in &self.body.oracle_responses {
499            for response in responses {
500                if let OracleResponse::Blob(blob_id) = response {
501                    required_blob_ids.insert(*blob_id);
502                }
503            }
504        }
505
506        required_blob_ids
507    }
508
509    /// Returns reference to the outgoing messages in the block.
510    pub fn messages(&self) -> &Vec<Vec<OutgoingMessage>> {
511        &self.body.messages
512    }
513
514    /// Returns all recipients of messages in this block.
515    pub fn recipients(&self) -> BTreeSet<ChainId> {
516        self.body
517            .messages
518            .iter()
519            .flat_map(|messages| messages.iter().map(|message| message.destination))
520            .collect()
521    }
522
523    /// Returns whether there are any oracle responses in this block.
524    pub fn has_oracle_responses(&self) -> bool {
525        self.body
526            .oracle_responses
527            .iter()
528            .any(|responses| !responses.is_empty())
529    }
530
531    /// Returns whether this block matches the proposal.
532    pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
533        let ProposedBlock {
534            chain_id,
535            epoch,
536            incoming_bundles,
537            operations,
538            height,
539            timestamp,
540            authenticated_signer,
541            previous_block_hash,
542        } = block;
543        *chain_id == self.header.chain_id
544            && *epoch == self.header.epoch
545            && *incoming_bundles == self.body.incoming_bundles
546            && *operations == self.body.operations
547            && *height == self.header.height
548            && *timestamp == self.header.timestamp
549            && *authenticated_signer == self.header.authenticated_signer
550            && *previous_block_hash == self.header.previous_block_hash
551    }
552
553    pub fn into_proposal(self) -> (ProposedBlock, BlockExecutionOutcome) {
554        let proposed_block = ProposedBlock {
555            chain_id: self.header.chain_id,
556            epoch: self.header.epoch,
557            incoming_bundles: self.body.incoming_bundles,
558            operations: self.body.operations,
559            height: self.header.height,
560            timestamp: self.header.timestamp,
561            authenticated_signer: self.header.authenticated_signer,
562            previous_block_hash: self.header.previous_block_hash,
563        };
564        let outcome = BlockExecutionOutcome {
565            state_hash: self.header.state_hash,
566            messages: self.body.messages,
567            previous_message_blocks: self.body.previous_message_blocks,
568            oracle_responses: self.body.oracle_responses,
569            events: self.body.events,
570            blobs: self.body.blobs,
571            operation_results: self.body.operation_results,
572        };
573        (proposed_block, outcome)
574    }
575
576    pub fn iter_created_blobs(&self) -> impl Iterator<Item = (BlobId, Blob)> + '_ {
577        self.body
578            .blobs
579            .iter()
580            .flatten()
581            .map(|blob| (blob.id(), blob.clone()))
582    }
583}
584
585impl BcsHashable<'_> for Block {}
586
587#[derive(Serialize, Deserialize)]
588pub struct PreviousMessageBlocksMap<'a> {
589    inner: Cow<'a, BTreeMap<ChainId, CryptoHash>>,
590}
591
592impl<'de> BcsHashable<'de> for PreviousMessageBlocksMap<'de> {}
593
594#[derive(Serialize, Deserialize)]
595#[serde(rename = "BlockHeader")]
596struct SerializedHeader {
597    chain_id: ChainId,
598    epoch: Epoch,
599    height: BlockHeight,
600    timestamp: Timestamp,
601    state_hash: CryptoHash,
602    previous_block_hash: Option<CryptoHash>,
603    authenticated_signer: Option<AccountOwner>,
604}
605
606mod hashing {
607    use linera_base::crypto::{BcsHashable, CryptoHash, CryptoHashVec};
608
609    pub(super) fn hash_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[T]>) -> CryptoHash {
610        let v = CryptoHashVec(it.as_ref().iter().map(CryptoHash::new).collect::<Vec<_>>());
611        CryptoHash::new(&v)
612    }
613
614    pub(super) fn hash_vec_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[Vec<T>]>) -> CryptoHash {
615        let v = CryptoHashVec(it.as_ref().iter().map(hash_vec).collect::<Vec<_>>());
616        CryptoHash::new(&v)
617    }
618}