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, StreamId},
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 previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
263            inner: Cow::Borrowed(&inner.body.previous_event_blocks),
264        });
265        let operations_hash = hashing::hash_vec(&inner.body.operations);
266        let oracle_responses_hash = hashing::hash_vec_vec(&inner.body.oracle_responses);
267        let events_hash = hashing::hash_vec_vec(&inner.body.events);
268        let blobs_hash = hashing::hash_vec_vec(&inner.body.blobs);
269        let operation_results_hash = hashing::hash_vec(&inner.body.operation_results);
270
271        let header = BlockHeader {
272            chain_id: inner.header.chain_id,
273            epoch: inner.header.epoch,
274            height: inner.header.height,
275            timestamp: inner.header.timestamp,
276            state_hash: inner.header.state_hash,
277            previous_block_hash: inner.header.previous_block_hash,
278            authenticated_signer: inner.header.authenticated_signer,
279            bundles_hash,
280            operations_hash,
281            messages_hash,
282            previous_message_blocks_hash,
283            previous_event_blocks_hash,
284            oracle_responses_hash,
285            events_hash,
286            blobs_hash,
287            operation_results_hash,
288        };
289
290        Ok(Self {
291            header,
292            body: inner.body,
293        })
294    }
295}
296
297/// Succinct representation of a block.
298/// Contains all the metadata to follow the chain of blocks or verifying
299/// inclusion (event, message, oracle response, etc.) in the block's body.
300#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
301pub struct BlockHeader {
302    /// The chain to which this block belongs.
303    pub chain_id: ChainId,
304    /// The number identifying the current configuration.
305    pub epoch: Epoch,
306    /// The block height.
307    pub height: BlockHeight,
308    /// The timestamp when this block was created.
309    pub timestamp: Timestamp,
310    /// The hash of the chain's execution state after this block.
311    pub state_hash: CryptoHash,
312    /// Certified hash of the previous block in the chain, if any.
313    pub previous_block_hash: Option<CryptoHash>,
314    /// The user signing for the operations in the block and paying for their execution
315    /// fees. If set, this must be the `owner` in the block proposal. `None` means that
316    /// the default account of the chain is used. This value is also used as recipient of
317    /// potential refunds for the message grants created by the operations.
318    pub authenticated_signer: Option<AccountOwner>,
319
320    // Inputs to the block, chosen by the block proposer.
321    /// Cryptographic hash of all the incoming bundles in the block.
322    pub bundles_hash: CryptoHash,
323    /// Cryptographic hash of all the operations in the block.
324    pub operations_hash: CryptoHash,
325
326    // Outcome of the block execution.
327    /// Cryptographic hash of all the messages in the block.
328    pub messages_hash: CryptoHash,
329    /// Cryptographic hash of the lookup table for previous sending blocks.
330    pub previous_message_blocks_hash: CryptoHash,
331    /// Cryptographic hash of the lookup table for previous blocks publishing events.
332    pub previous_event_blocks_hash: CryptoHash,
333    /// Cryptographic hash of all the oracle responses in the block.
334    pub oracle_responses_hash: CryptoHash,
335    /// Cryptographic hash of all the events in the block.
336    pub events_hash: CryptoHash,
337    /// Cryptographic hash of all the created blobs in the block.
338    pub blobs_hash: CryptoHash,
339    /// A cryptographic hash of the execution results of all operations in a block.
340    pub operation_results_hash: CryptoHash,
341}
342
343/// The body of a block containing all the data included in the block.
344#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
345pub struct BlockBody {
346    /// A selection of incoming messages to be executed first. Successive messages of the same
347    /// sender and height are grouped together for conciseness.
348    pub incoming_bundles: Vec<IncomingBundle>,
349    /// The operations to execute.
350    pub operations: Vec<Operation>,
351    /// The list of outgoing messages for each transaction.
352    pub messages: Vec<Vec<OutgoingMessage>>,
353    /// The hashes and heights of previous blocks that sent messages to the same recipients.
354    pub previous_message_blocks: BTreeMap<ChainId, (CryptoHash, BlockHeight)>,
355    /// The hashes and heights of previous blocks that published events to the same channels.
356    pub previous_event_blocks: BTreeMap<StreamId, (CryptoHash, BlockHeight)>,
357    /// The record of oracle responses for each transaction.
358    pub oracle_responses: Vec<Vec<OracleResponse>>,
359    /// The list of events produced by each transaction.
360    pub events: Vec<Vec<Event>>,
361    /// The list of blobs produced by each transaction.
362    pub blobs: Vec<Vec<Blob>>,
363    /// The execution result for each operation.
364    pub operation_results: Vec<OperationResult>,
365}
366
367impl Block {
368    pub fn new(block: ProposedBlock, outcome: BlockExecutionOutcome) -> Self {
369        let bundles_hash = hashing::hash_vec(&block.incoming_bundles);
370        let messages_hash = hashing::hash_vec_vec(&outcome.messages);
371        let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
372            inner: Cow::Borrowed(&outcome.previous_message_blocks),
373        });
374        let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
375            inner: Cow::Borrowed(&outcome.previous_event_blocks),
376        });
377        let operations_hash = hashing::hash_vec(&block.operations);
378        let oracle_responses_hash = hashing::hash_vec_vec(&outcome.oracle_responses);
379        let events_hash = hashing::hash_vec_vec(&outcome.events);
380        let blobs_hash = hashing::hash_vec_vec(&outcome.blobs);
381        let operation_results_hash = hashing::hash_vec(&outcome.operation_results);
382
383        let header = BlockHeader {
384            chain_id: block.chain_id,
385            epoch: block.epoch,
386            height: block.height,
387            timestamp: block.timestamp,
388            state_hash: outcome.state_hash,
389            previous_block_hash: block.previous_block_hash,
390            authenticated_signer: block.authenticated_signer,
391            bundles_hash,
392            operations_hash,
393            messages_hash,
394            previous_message_blocks_hash,
395            previous_event_blocks_hash,
396            oracle_responses_hash,
397            events_hash,
398            blobs_hash,
399            operation_results_hash,
400        };
401
402        let body = BlockBody {
403            incoming_bundles: block.incoming_bundles,
404            operations: block.operations,
405            messages: outcome.messages,
406            previous_message_blocks: outcome.previous_message_blocks,
407            previous_event_blocks: outcome.previous_event_blocks,
408            oracle_responses: outcome.oracle_responses,
409            events: outcome.events,
410            blobs: outcome.blobs,
411            operation_results: outcome.operation_results,
412        };
413
414        Self { header, body }
415    }
416
417    /// Returns the bundles of messages sent via the given medium to the specified
418    /// recipient. Messages originating from different transactions of the original block
419    /// are kept in separate bundles. If the medium is a channel, does not verify that the
420    /// recipient is actually subscribed to that channel.
421    pub fn message_bundles_for(
422        &self,
423        recipient: ChainId,
424        certificate_hash: CryptoHash,
425    ) -> impl Iterator<Item = (Epoch, MessageBundle)> + '_ {
426        let mut index = 0u32;
427        let block_height = self.header.height;
428        let block_timestamp = self.header.timestamp;
429        let block_epoch = self.header.epoch;
430
431        (0u32..)
432            .zip(self.messages())
433            .filter_map(move |(transaction_index, txn_messages)| {
434                let messages = (index..)
435                    .zip(txn_messages)
436                    .filter(|(_, message)| message.destination == recipient)
437                    .map(|(idx, message)| message.clone().into_posted(idx))
438                    .collect::<Vec<_>>();
439                index += txn_messages.len() as u32;
440                (!messages.is_empty()).then(|| {
441                    let bundle = MessageBundle {
442                        height: block_height,
443                        timestamp: block_timestamp,
444                        certificate_hash,
445                        transaction_index,
446                        messages,
447                    };
448                    (block_epoch, bundle)
449                })
450            })
451    }
452
453    /// Returns all the blob IDs required by this block.
454    /// Either as oracle responses or as published blobs.
455    pub fn required_blob_ids(&self) -> BTreeSet<BlobId> {
456        let mut blob_ids = self.oracle_blob_ids();
457        blob_ids.extend(self.published_blob_ids());
458        blob_ids.extend(self.created_blob_ids());
459        if self.header.height == BlockHeight(0) {
460            // the initial block implicitly depends on the chain description blob
461            blob_ids.insert(BlobId::new(
462                self.header.chain_id.0,
463                BlobType::ChainDescription,
464            ));
465        }
466        blob_ids
467    }
468
469    /// Returns whether this block requires the blob with the specified ID.
470    pub fn requires_or_creates_blob(&self, blob_id: &BlobId) -> bool {
471        self.oracle_blob_ids().contains(blob_id)
472            || self.published_blob_ids().contains(blob_id)
473            || self.created_blob_ids().contains(blob_id)
474            || (self.header.height == BlockHeight(0)
475                && (blob_id.blob_type == BlobType::ChainDescription
476                    && blob_id.hash == self.header.chain_id.0))
477    }
478
479    /// Returns all the published blob IDs in this block's operations.
480    pub fn published_blob_ids(&self) -> BTreeSet<BlobId> {
481        self.body
482            .operations
483            .iter()
484            .flat_map(Operation::published_blob_ids)
485            .collect()
486    }
487
488    /// Returns all the blob IDs created by the block's operations.
489    pub fn created_blob_ids(&self) -> BTreeSet<BlobId> {
490        self.body
491            .blobs
492            .iter()
493            .flatten()
494            .map(|blob| blob.id())
495            .collect()
496    }
497
498    /// Returns all the blobs created by the block's operations.
499    pub fn created_blobs(&self) -> BTreeMap<BlobId, Blob> {
500        self.body
501            .blobs
502            .iter()
503            .flatten()
504            .map(|blob| (blob.id(), blob.clone()))
505            .collect()
506    }
507
508    /// Returns set of blob IDs that were a result of an oracle call.
509    pub fn oracle_blob_ids(&self) -> BTreeSet<BlobId> {
510        let mut required_blob_ids = BTreeSet::new();
511        for responses in &self.body.oracle_responses {
512            for response in responses {
513                if let OracleResponse::Blob(blob_id) = response {
514                    required_blob_ids.insert(*blob_id);
515                }
516            }
517        }
518
519        required_blob_ids
520    }
521
522    /// Returns reference to the outgoing messages in the block.
523    pub fn messages(&self) -> &Vec<Vec<OutgoingMessage>> {
524        &self.body.messages
525    }
526
527    /// Returns all recipients of messages in this block.
528    pub fn recipients(&self) -> BTreeSet<ChainId> {
529        self.body
530            .messages
531            .iter()
532            .flat_map(|messages| messages.iter().map(|message| message.destination))
533            .collect()
534    }
535
536    /// Returns whether there are any oracle responses in this block.
537    pub fn has_oracle_responses(&self) -> bool {
538        self.body
539            .oracle_responses
540            .iter()
541            .any(|responses| !responses.is_empty())
542    }
543
544    /// Returns whether this block matches the proposal.
545    pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
546        let ProposedBlock {
547            chain_id,
548            epoch,
549            incoming_bundles,
550            operations,
551            height,
552            timestamp,
553            authenticated_signer,
554            previous_block_hash,
555        } = block;
556        *chain_id == self.header.chain_id
557            && *epoch == self.header.epoch
558            && *incoming_bundles == self.body.incoming_bundles
559            && *operations == self.body.operations
560            && *height == self.header.height
561            && *timestamp == self.header.timestamp
562            && *authenticated_signer == self.header.authenticated_signer
563            && *previous_block_hash == self.header.previous_block_hash
564    }
565
566    pub fn into_proposal(self) -> (ProposedBlock, BlockExecutionOutcome) {
567        let proposed_block = ProposedBlock {
568            chain_id: self.header.chain_id,
569            epoch: self.header.epoch,
570            incoming_bundles: self.body.incoming_bundles,
571            operations: self.body.operations,
572            height: self.header.height,
573            timestamp: self.header.timestamp,
574            authenticated_signer: self.header.authenticated_signer,
575            previous_block_hash: self.header.previous_block_hash,
576        };
577        let outcome = BlockExecutionOutcome {
578            state_hash: self.header.state_hash,
579            messages: self.body.messages,
580            previous_message_blocks: self.body.previous_message_blocks,
581            previous_event_blocks: self.body.previous_event_blocks,
582            oracle_responses: self.body.oracle_responses,
583            events: self.body.events,
584            blobs: self.body.blobs,
585            operation_results: self.body.operation_results,
586        };
587        (proposed_block, outcome)
588    }
589
590    pub fn iter_created_blobs(&self) -> impl Iterator<Item = (BlobId, Blob)> + '_ {
591        self.body
592            .blobs
593            .iter()
594            .flatten()
595            .map(|blob| (blob.id(), blob.clone()))
596    }
597}
598
599impl BcsHashable<'_> for Block {}
600
601#[derive(Serialize, Deserialize)]
602pub struct PreviousMessageBlocksMap<'a> {
603    inner: Cow<'a, BTreeMap<ChainId, (CryptoHash, BlockHeight)>>,
604}
605
606impl<'de> BcsHashable<'de> for PreviousMessageBlocksMap<'de> {}
607
608#[derive(Serialize, Deserialize)]
609pub struct PreviousEventBlocksMap<'a> {
610    inner: Cow<'a, BTreeMap<StreamId, (CryptoHash, BlockHeight)>>,
611}
612
613impl<'de> BcsHashable<'de> for PreviousEventBlocksMap<'de> {}
614
615#[derive(Serialize, Deserialize)]
616#[serde(rename = "BlockHeader")]
617struct SerializedHeader {
618    chain_id: ChainId,
619    epoch: Epoch,
620    height: BlockHeight,
621    timestamp: Timestamp,
622    state_hash: CryptoHash,
623    previous_block_hash: Option<CryptoHash>,
624    authenticated_signer: Option<AccountOwner>,
625}
626
627mod hashing {
628    use linera_base::crypto::{BcsHashable, CryptoHash, CryptoHashVec};
629
630    pub(super) fn hash_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[T]>) -> CryptoHash {
631        let v = CryptoHashVec(it.as_ref().iter().map(CryptoHash::new).collect::<Vec<_>>());
632        CryptoHash::new(&v)
633    }
634
635    pub(super) fn hash_vec_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[Vec<T>]>) -> CryptoHash {
636        let v = CryptoHashVec(it.as_ref().iter().map(hash_vec).collect::<Vec<_>>());
637        CryptoHash::new(&v)
638    }
639}