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, Transaction,
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 timestamp(&self) -> Timestamp {
133        self.0.inner().header.timestamp
134    }
135
136    pub fn to_log_str(&self) -> &'static str {
137        "confirmed_block"
138    }
139
140    /// Returns whether this block matches the proposal.
141    pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
142        self.block().matches_proposed_block(block)
143    }
144
145    /// Returns a blob state that applies to all blobs used by this block.
146    pub fn to_blob_state(&self, is_stored_block: bool) -> BlobState {
147        BlobState {
148            last_used_by: is_stored_block.then_some(self.0.hash()),
149            chain_id: self.chain_id(),
150            block_height: self.height(),
151            epoch: is_stored_block.then_some(self.epoch()),
152        }
153    }
154}
155
156#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
157#[serde(transparent)]
158pub struct Timeout(Hashed<TimeoutInner>);
159
160#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
161#[serde(rename = "Timeout")]
162pub(crate) struct TimeoutInner {
163    chain_id: ChainId,
164    height: BlockHeight,
165    epoch: Epoch,
166}
167
168impl Timeout {
169    pub fn new(chain_id: ChainId, height: BlockHeight, epoch: Epoch) -> Self {
170        let inner = TimeoutInner {
171            chain_id,
172            height,
173            epoch,
174        };
175        Self(Hashed::new(inner))
176    }
177
178    pub fn to_log_str(&self) -> &'static str {
179        "timeout"
180    }
181
182    pub fn chain_id(&self) -> ChainId {
183        self.0.inner().chain_id
184    }
185
186    pub fn height(&self) -> BlockHeight {
187        self.0.inner().height
188    }
189
190    pub fn epoch(&self) -> Epoch {
191        self.0.inner().epoch
192    }
193
194    pub(crate) fn inner(&self) -> &Hashed<TimeoutInner> {
195        &self.0
196    }
197}
198
199impl BcsHashable<'_> for Timeout {}
200impl BcsHashable<'_> for TimeoutInner {}
201
202/// Failure to convert a `Certificate` into one of the expected certificate types.
203#[derive(Clone, Copy, Debug, Error)]
204pub enum ConversionError {
205    /// Failure to convert to [`ConfirmedBlock`] certificate.
206    #[error("Expected a `ConfirmedBlockCertificate` value")]
207    ConfirmedBlock,
208
209    /// Failure to convert to [`ValidatedBlock`] certificate.
210    #[error("Expected a `ValidatedBlockCertificate` value")]
211    ValidatedBlock,
212
213    /// Failure to convert to [`Timeout`] certificate.
214    #[error("Expected a `TimeoutCertificate` value")]
215    Timeout,
216}
217
218/// Block defines the atomic unit of growth of the Linera chain.
219///
220/// As part of the block body, contains all the incoming messages
221/// and operations to execute which define a state transition of the chain.
222/// Resulting messages produced by the operations are also included in the block body,
223/// together with oracle responses and events.
224#[derive(Debug, PartialEq, Eq, Hash, Clone, SimpleObject)]
225pub struct Block {
226    /// Header of the block containing metadata of the block.
227    pub header: BlockHeader,
228    /// Body of the block containing all of the data.
229    pub body: BlockBody,
230}
231
232impl Serialize for Block {
233    fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
234        let mut state = serializer.serialize_struct("Block", 2)?;
235
236        let header = SerializedHeader {
237            chain_id: self.header.chain_id,
238            epoch: self.header.epoch,
239            height: self.header.height,
240            timestamp: self.header.timestamp,
241            state_hash: self.header.state_hash,
242            previous_block_hash: self.header.previous_block_hash,
243            authenticated_signer: self.header.authenticated_signer,
244        };
245        state.serialize_field("header", &header)?;
246        state.serialize_field("body", &self.body)?;
247        state.end()
248    }
249}
250
251impl<'de> Deserialize<'de> for Block {
252    fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
253        #[derive(Deserialize)]
254        #[serde(rename = "Block")]
255        struct Inner {
256            header: SerializedHeader,
257            body: BlockBody,
258        }
259        let inner = Inner::deserialize(deserializer)?;
260
261        let transactions_hash = hashing::hash_vec(&inner.body.transactions);
262        let messages_hash = hashing::hash_vec_vec(&inner.body.messages);
263        let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
264            inner: Cow::Borrowed(&inner.body.previous_message_blocks),
265        });
266        let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
267            inner: Cow::Borrowed(&inner.body.previous_event_blocks),
268        });
269        let oracle_responses_hash = hashing::hash_vec_vec(&inner.body.oracle_responses);
270        let events_hash = hashing::hash_vec_vec(&inner.body.events);
271        let blobs_hash = hashing::hash_vec_vec(&inner.body.blobs);
272        let operation_results_hash = hashing::hash_vec(&inner.body.operation_results);
273
274        let header = BlockHeader {
275            chain_id: inner.header.chain_id,
276            epoch: inner.header.epoch,
277            height: inner.header.height,
278            timestamp: inner.header.timestamp,
279            state_hash: inner.header.state_hash,
280            previous_block_hash: inner.header.previous_block_hash,
281            authenticated_signer: inner.header.authenticated_signer,
282            transactions_hash,
283            messages_hash,
284            previous_message_blocks_hash,
285            previous_event_blocks_hash,
286            oracle_responses_hash,
287            events_hash,
288            blobs_hash,
289            operation_results_hash,
290        };
291
292        Ok(Self {
293            header,
294            body: inner.body,
295        })
296    }
297}
298
299/// Succinct representation of a block.
300/// Contains all the metadata to follow the chain of blocks or verifying
301/// inclusion (event, message, oracle response, etc.) in the block's body.
302#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
303pub struct BlockHeader {
304    /// The chain to which this block belongs.
305    pub chain_id: ChainId,
306    /// The number identifying the current configuration.
307    pub epoch: Epoch,
308    /// The block height.
309    pub height: BlockHeight,
310    /// The timestamp when this block was created.
311    pub timestamp: Timestamp,
312    /// The hash of the chain's execution state after this block.
313    pub state_hash: CryptoHash,
314    /// Certified hash of the previous block in the chain, if any.
315    pub previous_block_hash: Option<CryptoHash>,
316    /// The user signing for the operations in the block and paying for their execution
317    /// fees. If set, this must be the `owner` in the block proposal. `None` means that
318    /// the default account of the chain is used. This value is also used as recipient of
319    /// potential refunds for the message grants created by the operations.
320    pub authenticated_signer: Option<AccountOwner>,
321
322    // Inputs to the block, chosen by the block proposer.
323    /// Cryptographic hash of all the transactions in the block.
324    pub transactions_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)]
345#[graphql(complex)]
346pub struct BlockBody {
347    /// The transactions to execute in this block. Each transaction can be either
348    /// incoming messages or an operation.
349    #[graphql(skip)]
350    pub transactions: Vec<Transaction>,
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 BlockBody {
368    /// Returns all operations in this block body.
369    pub fn operations(&self) -> impl Iterator<Item = &Operation> {
370        self.transactions.iter().filter_map(|tx| match tx {
371            Transaction::ExecuteOperation(operation) => Some(operation),
372            Transaction::ReceiveMessages(_) => None,
373        })
374    }
375
376    /// Returns all incoming bundles in this block body.
377    pub fn incoming_bundles(&self) -> impl Iterator<Item = &IncomingBundle> {
378        self.transactions.iter().filter_map(|tx| match tx {
379            Transaction::ReceiveMessages(bundle) => Some(bundle),
380            Transaction::ExecuteOperation(_) => None,
381        })
382    }
383}
384
385#[async_graphql::ComplexObject]
386impl BlockBody {
387    /// Metadata about the transactions in this block.
388    async fn transaction_metadata(&self) -> Vec<crate::data_types::TransactionMetadata> {
389        self.transactions
390            .iter()
391            .map(crate::data_types::TransactionMetadata::from_transaction)
392            .collect()
393    }
394}
395
396impl Block {
397    pub fn new(block: ProposedBlock, outcome: BlockExecutionOutcome) -> Self {
398        let transactions_hash = hashing::hash_vec(&block.transactions);
399        let messages_hash = hashing::hash_vec_vec(&outcome.messages);
400        let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
401            inner: Cow::Borrowed(&outcome.previous_message_blocks),
402        });
403        let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
404            inner: Cow::Borrowed(&outcome.previous_event_blocks),
405        });
406        let oracle_responses_hash = hashing::hash_vec_vec(&outcome.oracle_responses);
407        let events_hash = hashing::hash_vec_vec(&outcome.events);
408        let blobs_hash = hashing::hash_vec_vec(&outcome.blobs);
409        let operation_results_hash = hashing::hash_vec(&outcome.operation_results);
410
411        let header = BlockHeader {
412            chain_id: block.chain_id,
413            epoch: block.epoch,
414            height: block.height,
415            timestamp: block.timestamp,
416            state_hash: outcome.state_hash,
417            previous_block_hash: block.previous_block_hash,
418            authenticated_signer: block.authenticated_signer,
419            transactions_hash,
420            messages_hash,
421            previous_message_blocks_hash,
422            previous_event_blocks_hash,
423            oracle_responses_hash,
424            events_hash,
425            blobs_hash,
426            operation_results_hash,
427        };
428
429        let body = BlockBody {
430            transactions: block.transactions,
431            messages: outcome.messages,
432            previous_message_blocks: outcome.previous_message_blocks,
433            previous_event_blocks: outcome.previous_event_blocks,
434            oracle_responses: outcome.oracle_responses,
435            events: outcome.events,
436            blobs: outcome.blobs,
437            operation_results: outcome.operation_results,
438        };
439
440        Self { header, body }
441    }
442
443    /// Returns the bundles of messages sent via the given medium to the specified
444    /// recipient. Messages originating from different transactions of the original block
445    /// are kept in separate bundles. If the medium is a channel, does not verify that the
446    /// recipient is actually subscribed to that channel.
447    pub fn message_bundles_for(
448        &self,
449        recipient: ChainId,
450        certificate_hash: CryptoHash,
451    ) -> impl Iterator<Item = (Epoch, MessageBundle)> + '_ {
452        let mut index = 0u32;
453        let block_height = self.header.height;
454        let block_timestamp = self.header.timestamp;
455        let block_epoch = self.header.epoch;
456
457        (0u32..)
458            .zip(self.messages())
459            .filter_map(move |(transaction_index, txn_messages)| {
460                let messages = (index..)
461                    .zip(txn_messages)
462                    .filter(|(_, message)| message.destination == recipient)
463                    .map(|(idx, message)| message.clone().into_posted(idx))
464                    .collect::<Vec<_>>();
465                index += txn_messages.len() as u32;
466                (!messages.is_empty()).then(|| {
467                    let bundle = MessageBundle {
468                        height: block_height,
469                        timestamp: block_timestamp,
470                        certificate_hash,
471                        transaction_index,
472                        messages,
473                    };
474                    (block_epoch, bundle)
475                })
476            })
477    }
478
479    /// Returns all the blob IDs required by this block.
480    /// Either as oracle responses or as published blobs.
481    pub fn required_blob_ids(&self) -> BTreeSet<BlobId> {
482        let mut blob_ids = self.oracle_blob_ids();
483        blob_ids.extend(self.published_blob_ids());
484        blob_ids.extend(self.created_blob_ids());
485        if self.header.height == BlockHeight(0) {
486            // the initial block implicitly depends on the chain description blob
487            blob_ids.insert(BlobId::new(
488                self.header.chain_id.0,
489                BlobType::ChainDescription,
490            ));
491        }
492        blob_ids
493    }
494
495    /// Returns whether this block requires the blob with the specified ID.
496    pub fn requires_or_creates_blob(&self, blob_id: &BlobId) -> bool {
497        self.oracle_blob_ids().contains(blob_id)
498            || self.published_blob_ids().contains(blob_id)
499            || self.created_blob_ids().contains(blob_id)
500            || (self.header.height == BlockHeight(0)
501                && (blob_id.blob_type == BlobType::ChainDescription
502                    && blob_id.hash == self.header.chain_id.0))
503    }
504
505    /// Returns all the published blob IDs in this block's operations.
506    pub fn published_blob_ids(&self) -> BTreeSet<BlobId> {
507        self.body
508            .operations()
509            .flat_map(Operation::published_blob_ids)
510            .collect()
511    }
512
513    /// Returns all the blob IDs created by the block's operations.
514    pub fn created_blob_ids(&self) -> BTreeSet<BlobId> {
515        self.body
516            .blobs
517            .iter()
518            .flatten()
519            .map(|blob| blob.id())
520            .collect()
521    }
522
523    /// Returns all the blobs created by the block's operations.
524    pub fn created_blobs(&self) -> BTreeMap<BlobId, Blob> {
525        self.body
526            .blobs
527            .iter()
528            .flatten()
529            .map(|blob| (blob.id(), blob.clone()))
530            .collect()
531    }
532
533    /// Returns set of blob IDs that were a result of an oracle call.
534    pub fn oracle_blob_ids(&self) -> BTreeSet<BlobId> {
535        let mut required_blob_ids = BTreeSet::new();
536        for responses in &self.body.oracle_responses {
537            for response in responses {
538                if let OracleResponse::Blob(blob_id) = response {
539                    required_blob_ids.insert(*blob_id);
540                }
541            }
542        }
543
544        required_blob_ids
545    }
546
547    /// Returns reference to the outgoing messages in the block.
548    pub fn messages(&self) -> &Vec<Vec<OutgoingMessage>> {
549        &self.body.messages
550    }
551
552    /// Returns all recipients of messages in this block.
553    pub fn recipients(&self) -> BTreeSet<ChainId> {
554        self.body
555            .messages
556            .iter()
557            .flat_map(|messages| messages.iter().map(|message| message.destination))
558            .collect()
559    }
560
561    /// Returns whether there are any oracle responses in this block.
562    pub fn has_oracle_responses(&self) -> bool {
563        self.body
564            .oracle_responses
565            .iter()
566            .any(|responses| !responses.is_empty())
567    }
568
569    /// Returns whether this block matches the proposal.
570    pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
571        let ProposedBlock {
572            chain_id,
573            epoch,
574            transactions,
575            height,
576            timestamp,
577            authenticated_signer,
578            previous_block_hash,
579        } = block;
580        *chain_id == self.header.chain_id
581            && *epoch == self.header.epoch
582            && *transactions == self.body.transactions
583            && *height == self.header.height
584            && *timestamp == self.header.timestamp
585            && *authenticated_signer == self.header.authenticated_signer
586            && *previous_block_hash == self.header.previous_block_hash
587    }
588
589    pub fn into_proposal(self) -> (ProposedBlock, BlockExecutionOutcome) {
590        let proposed_block = ProposedBlock {
591            chain_id: self.header.chain_id,
592            epoch: self.header.epoch,
593            transactions: self.body.transactions,
594            height: self.header.height,
595            timestamp: self.header.timestamp,
596            authenticated_signer: self.header.authenticated_signer,
597            previous_block_hash: self.header.previous_block_hash,
598        };
599        let outcome = BlockExecutionOutcome {
600            state_hash: self.header.state_hash,
601            messages: self.body.messages,
602            previous_message_blocks: self.body.previous_message_blocks,
603            previous_event_blocks: self.body.previous_event_blocks,
604            oracle_responses: self.body.oracle_responses,
605            events: self.body.events,
606            blobs: self.body.blobs,
607            operation_results: self.body.operation_results,
608        };
609        (proposed_block, outcome)
610    }
611
612    pub fn iter_created_blobs(&self) -> impl Iterator<Item = (BlobId, Blob)> + '_ {
613        self.body
614            .blobs
615            .iter()
616            .flatten()
617            .map(|blob| (blob.id(), blob.clone()))
618    }
619}
620
621impl BcsHashable<'_> for Block {}
622
623#[derive(Serialize, Deserialize)]
624pub struct PreviousMessageBlocksMap<'a> {
625    inner: Cow<'a, BTreeMap<ChainId, (CryptoHash, BlockHeight)>>,
626}
627
628impl<'de> BcsHashable<'de> for PreviousMessageBlocksMap<'de> {}
629
630#[derive(Serialize, Deserialize)]
631pub struct PreviousEventBlocksMap<'a> {
632    inner: Cow<'a, BTreeMap<StreamId, (CryptoHash, BlockHeight)>>,
633}
634
635impl<'de> BcsHashable<'de> for PreviousEventBlocksMap<'de> {}
636
637#[derive(Serialize, Deserialize)]
638#[serde(rename = "BlockHeader")]
639struct SerializedHeader {
640    chain_id: ChainId,
641    epoch: Epoch,
642    height: BlockHeight,
643    timestamp: Timestamp,
644    state_hash: CryptoHash,
645    previous_block_hash: Option<CryptoHash>,
646    authenticated_signer: Option<AccountOwner>,
647}
648
649mod hashing {
650    use linera_base::crypto::{BcsHashable, CryptoHash, CryptoHashVec};
651
652    pub(super) fn hash_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[T]>) -> CryptoHash {
653        let v = CryptoHashVec(it.as_ref().iter().map(CryptoHash::new).collect::<Vec<_>>());
654        CryptoHash::new(&v)
655    }
656
657    pub(super) fn hash_vec_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[Vec<T>]>) -> CryptoHash {
658        let v = CryptoHashVec(it.as_ref().iter().map(hash_vec).collect::<Vec<_>>());
659        CryptoHash::new(&v)
660    }
661}