Skip to main content

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 allocative::Allocative;
12use async_graphql::SimpleObject;
13use linera_base::{
14    crypto::{BcsHashable, CryptoHash},
15    data_types::{Blob, BlockHeight, Epoch, Event, OracleResponse, Timestamp},
16    hashed::Hashed,
17    identifiers::{AccountOwner, BlobId, BlobType, ChainId, EventId, StreamId},
18};
19use linera_execution::{BlobOrigin, BlobState, Operation, OutgoingMessage};
20use serde::{ser::SerializeStruct, Deserialize, Serialize};
21use thiserror::Error;
22
23use crate::{
24    data_types::{
25        BlockExecutionOutcome, IncomingBundle, MessageBundle, OperationResult, OutgoingMessageExt,
26        ProposedBlock, Transaction,
27    },
28    types::CertificateValue,
29};
30
31/// Wrapper around a `Block` that has been validated.
32#[derive(Debug, PartialEq, Eq, Clone, Allocative)]
33pub struct ValidatedBlock(Hashed<Block>);
34
35impl Serialize for ValidatedBlock {
36    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
37        self.0.serialize(serializer)
38    }
39}
40
41impl<'de> Deserialize<'de> for ValidatedBlock {
42    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
43        Ok(Self::new(Block::deserialize(deserializer)?))
44    }
45}
46
47impl ValidatedBlock {
48    /// Creates a new `ValidatedBlock` from a `Block`.
49    pub fn new(block: Block) -> Self {
50        let hash = block.hash();
51        Self(Hashed::with_hash(block, hash))
52    }
53
54    /// Creates a `ValidatedBlock` from an already-hashed `Block`.
55    pub fn from_hashed(block: Hashed<Block>) -> Self {
56        Self(block)
57    }
58
59    /// Returns a reference to the hashed [`Block`] contained in this `ValidatedBlock`.
60    pub fn inner(&self) -> &Hashed<Block> {
61        &self.0
62    }
63
64    /// Returns a reference to the [`Block`] contained in this `ValidatedBlock`.
65    pub fn block(&self) -> &Block {
66        self.0.inner()
67    }
68
69    /// Consumes this `ValidatedBlock`, returning the [`Block`] it contains.
70    pub fn into_inner(self) -> Block {
71        self.0.into_inner()
72    }
73
74    /// Returns a static string identifying this value kind, for logging.
75    pub fn to_log_str(&self) -> &'static str {
76        "validated_block"
77    }
78
79    /// Returns the ID of the chain this block belongs to.
80    pub fn chain_id(&self) -> ChainId {
81        self.0.inner().header.chain_id
82    }
83
84    /// Returns the height of this block.
85    pub fn height(&self) -> BlockHeight {
86        self.0.inner().header.height
87    }
88
89    /// Returns the epoch this block belongs to.
90    pub fn epoch(&self) -> Epoch {
91        self.0.inner().header.epoch
92    }
93}
94
95/// Wrapper around a `Block` that has been confirmed.
96#[derive(Debug, PartialEq, Eq, Clone, Allocative)]
97pub struct ConfirmedBlock(Hashed<Block>);
98
99impl Serialize for ConfirmedBlock {
100    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
101        self.0.serialize(serializer)
102    }
103}
104
105impl<'de> Deserialize<'de> for ConfirmedBlock {
106    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
107        Ok(Self::new(Block::deserialize(deserializer)?))
108    }
109}
110
111#[async_graphql::Object(cache_control(no_cache))]
112impl ConfirmedBlock {
113    #[graphql(derived(name = "block"))]
114    async fn _block(&self) -> Block {
115        self.0.inner().clone()
116    }
117
118    async fn status(&self) -> String {
119        "confirmed".to_string()
120    }
121
122    async fn hash(&self) -> CryptoHash {
123        self.0.hash()
124    }
125}
126
127impl ConfirmedBlock {
128    /// Creates a new `ConfirmedBlock` from a `Block`.
129    pub fn new(block: Block) -> Self {
130        let hash = block.hash();
131        Self(Hashed::with_hash(block, hash))
132    }
133
134    /// Creates a `ConfirmedBlock` from an already-hashed `Block`.
135    pub fn from_hashed(block: Hashed<Block>) -> Self {
136        Self(block)
137    }
138
139    /// Returns a reference to the hashed `Block` contained in this `ConfirmedBlock`.
140    pub fn inner(&self) -> &Hashed<Block> {
141        &self.0
142    }
143
144    /// Consumes this `ConfirmedBlock`, returning the hashed `Block` it contains.
145    pub fn into_inner(self) -> Hashed<Block> {
146        self.0
147    }
148
149    /// Returns a reference to the `Block` contained in this `ConfirmedBlock`.
150    pub fn block(&self) -> &Block {
151        self.0.inner()
152    }
153
154    /// Consumes this `ConfirmedBlock`, returning the `Block` it contains.
155    pub fn into_block(self) -> Block {
156        self.0.into_inner()
157    }
158
159    /// Returns the ID of the chain this block belongs to.
160    pub fn chain_id(&self) -> ChainId {
161        self.0.inner().header.chain_id
162    }
163
164    /// Returns the height of this block.
165    pub fn height(&self) -> BlockHeight {
166        self.0.inner().header.height
167    }
168
169    /// Returns the timestamp of this block.
170    pub fn timestamp(&self) -> Timestamp {
171        self.0.inner().header.timestamp
172    }
173
174    /// Returns a static string identifying this value kind, for logging.
175    pub fn to_log_str(&self) -> &'static str {
176        "confirmed_block"
177    }
178
179    /// Returns whether this block matches the proposal.
180    pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
181        self.block().matches_proposed_block(block)
182    }
183
184    /// Returns a blob state that applies to all blobs used by this block.
185    pub fn to_blob_state(&self, is_stored_block: bool) -> BlobState {
186        BlobState {
187            origin: BlobOrigin::Published {
188                chain_id: self.chain_id(),
189                block_height: self.height(),
190            },
191            last_used_by: is_stored_block.then_some(self.0.hash()),
192            epoch: is_stored_block.then_some(self.epoch()),
193        }
194    }
195}
196
197impl From<Hashed<Block>> for ConfirmedBlock {
198    fn from(block: Hashed<Block>) -> Self {
199        Self::from_hashed(block)
200    }
201}
202
203impl From<Hashed<Block>> for ValidatedBlock {
204    fn from(block: Hashed<Block>) -> Self {
205        Self::from_hashed(block)
206    }
207}
208
209/// A request to move on to the next consensus round, certified when no block is confirmed in
210/// time.
211#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Allocative)]
212#[serde(transparent)]
213pub struct Timeout(Hashed<TimeoutInner>);
214
215#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Allocative)]
216#[serde(rename = "Timeout")]
217pub(crate) struct TimeoutInner {
218    chain_id: ChainId,
219    height: BlockHeight,
220    epoch: Epoch,
221}
222
223impl Timeout {
224    /// Creates a new `Timeout` for the given chain, height and epoch.
225    pub fn new(chain_id: ChainId, height: BlockHeight, epoch: Epoch) -> Self {
226        let inner = TimeoutInner {
227            chain_id,
228            height,
229            epoch,
230        };
231        Self(Hashed::new(inner))
232    }
233
234    /// Returns a static string identifying this value kind, for logging.
235    pub fn to_log_str(&self) -> &'static str {
236        "timeout"
237    }
238
239    /// Returns the ID of the chain this timeout applies to.
240    pub fn chain_id(&self) -> ChainId {
241        self.0.inner().chain_id
242    }
243
244    /// Returns the block height this timeout applies to.
245    pub fn height(&self) -> BlockHeight {
246        self.0.inner().height
247    }
248
249    /// Returns the epoch this timeout applies to.
250    pub fn epoch(&self) -> Epoch {
251        self.0.inner().epoch
252    }
253
254    pub(crate) fn inner(&self) -> &Hashed<TimeoutInner> {
255        &self.0
256    }
257}
258
259impl BcsHashable<'_> for Timeout {}
260impl BcsHashable<'_> for TimeoutInner {}
261
262/// Failure to convert a `Certificate` into one of the expected certificate types.
263#[derive(Clone, Copy, Debug, Error)]
264pub enum ConversionError {
265    /// Failure to convert to [`ConfirmedBlock`] certificate.
266    #[error("Expected a `ConfirmedBlockCertificate` value")]
267    ConfirmedBlock,
268
269    /// Failure to convert to [`ValidatedBlock`] certificate.
270    #[error("Expected a `ValidatedBlockCertificate` value")]
271    ValidatedBlock,
272
273    /// Failure to convert to [`Timeout`] certificate.
274    #[error("Expected a `TimeoutCertificate` value")]
275    Timeout,
276}
277
278/// Block defines the atomic unit of growth of the Linera chain.
279///
280/// As part of the block body, contains all the incoming messages
281/// and operations to execute which define a state transition of the chain.
282/// Resulting messages produced by the operations are also included in the block body,
283/// together with oracle responses and events.
284#[derive(Debug, PartialEq, Eq, Hash, Clone, SimpleObject, Allocative)]
285pub struct Block {
286    /// Header of the block containing metadata of the block.
287    pub header: BlockHeader,
288    /// Body of the block containing all of the data.
289    pub body: BlockBody,
290}
291
292impl Serialize for Block {
293    fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
294        let mut state = serializer.serialize_struct("Block", 2)?;
295
296        let header = SerializedHeader {
297            chain_id: self.header.chain_id,
298            epoch: self.header.epoch,
299            height: self.header.height,
300            timestamp: self.header.timestamp,
301            state_hash: self.header.state_hash,
302            previous_block_hash: self.header.previous_block_hash,
303            authenticated_owner: self.header.authenticated_owner,
304        };
305        state.serialize_field("header", &header)?;
306        state.serialize_field("body", &self.body)?;
307        state.end()
308    }
309}
310
311impl<'de> Deserialize<'de> for Block {
312    fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
313        #[derive(Deserialize)]
314        #[serde(rename = "Block")]
315        struct Inner {
316            header: SerializedHeader,
317            body: BlockBody,
318        }
319        let inner = Inner::deserialize(deserializer)?;
320
321        let transactions_hash = hashing::hash_vec(&inner.body.transactions);
322        let messages_hash = hashing::hash_vec_vec(&inner.body.messages);
323        let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
324            inner: Cow::Borrowed(&inner.body.previous_message_blocks),
325        });
326        let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
327            inner: Cow::Borrowed(&inner.body.previous_event_blocks),
328        });
329        let oracle_responses_hash = hashing::hash_vec_vec(&inner.body.oracle_responses);
330        let events_hash = hashing::hash_vec_vec(&inner.body.events);
331        let blobs_hash = hashing::hash_vec_vec(&inner.body.blobs);
332        let operation_results_hash = hashing::hash_vec(&inner.body.operation_results);
333
334        let header = BlockHeader {
335            chain_id: inner.header.chain_id,
336            epoch: inner.header.epoch,
337            height: inner.header.height,
338            timestamp: inner.header.timestamp,
339            state_hash: inner.header.state_hash,
340            previous_block_hash: inner.header.previous_block_hash,
341            authenticated_owner: inner.header.authenticated_owner,
342            transactions_hash,
343            messages_hash,
344            previous_message_blocks_hash,
345            previous_event_blocks_hash,
346            oracle_responses_hash,
347            events_hash,
348            blobs_hash,
349            operation_results_hash,
350        };
351
352        Ok(Self {
353            header,
354            body: inner.body,
355        })
356    }
357}
358
359/// Succinct representation of a block.
360/// Contains all the metadata to follow the chain of blocks or verifying
361/// inclusion (event, message, oracle response, etc.) in the block's body.
362#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
363pub struct BlockHeader {
364    /// The chain to which this block belongs.
365    pub chain_id: ChainId,
366    /// The number identifying the current configuration.
367    pub epoch: Epoch,
368    /// The block height.
369    pub height: BlockHeight,
370    /// The timestamp when this block was created.
371    pub timestamp: Timestamp,
372    /// The hash of the chain's execution state after this block.
373    pub state_hash: CryptoHash,
374    /// Certified hash of the previous block in the chain, if any.
375    pub previous_block_hash: Option<CryptoHash>,
376    /// The user signing for the operations in the block and paying for their execution
377    /// fees. If set, this must be the `owner` in the block proposal. `None` means that
378    /// the default account of the chain is used. This value is also used as recipient of
379    /// potential refunds for the message grants created by the operations.
380    pub authenticated_owner: Option<AccountOwner>,
381
382    // Inputs to the block, chosen by the block proposer.
383    /// Cryptographic hash of all the transactions in the block.
384    pub transactions_hash: CryptoHash,
385
386    // Outcome of the block execution.
387    /// Cryptographic hash of all the messages in the block.
388    pub messages_hash: CryptoHash,
389    /// Cryptographic hash of the lookup table for previous sending blocks.
390    pub previous_message_blocks_hash: CryptoHash,
391    /// Cryptographic hash of the lookup table for previous blocks publishing events.
392    pub previous_event_blocks_hash: CryptoHash,
393    /// Cryptographic hash of all the oracle responses in the block.
394    pub oracle_responses_hash: CryptoHash,
395    /// Cryptographic hash of all the events in the block.
396    pub events_hash: CryptoHash,
397    /// Cryptographic hash of all the created blobs in the block.
398    pub blobs_hash: CryptoHash,
399    /// A cryptographic hash of the execution results of all operations in a block.
400    pub operation_results_hash: CryptoHash,
401}
402
403/// The body of a block containing all the data included in the block.
404#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
405#[graphql(complex)]
406pub struct BlockBody {
407    /// The transactions to execute in this block. Each transaction can be either
408    /// incoming messages or an operation.
409    #[graphql(skip)]
410    pub transactions: Vec<Transaction>,
411    /// The list of outgoing messages for each transaction.
412    pub messages: Vec<Vec<OutgoingMessage>>,
413    /// The hashes and heights of previous blocks that sent messages to the same recipients.
414    pub previous_message_blocks: BTreeMap<ChainId, (CryptoHash, BlockHeight)>,
415    /// The hashes and heights of previous blocks that published events to the same channels.
416    pub previous_event_blocks: BTreeMap<StreamId, (CryptoHash, BlockHeight)>,
417    /// The record of oracle responses for each transaction.
418    pub oracle_responses: Vec<Vec<OracleResponse>>,
419    /// The list of events produced by each transaction.
420    pub events: Vec<Vec<Event>>,
421    /// The list of blobs produced by each transaction.
422    pub blobs: Vec<Vec<Blob>>,
423    /// The execution result for each operation.
424    pub operation_results: Vec<OperationResult>,
425}
426
427impl BlockBody {
428    /// Returns all operations in this block body.
429    pub fn operations(&self) -> impl Iterator<Item = &Operation> {
430        self.transactions.iter().filter_map(|tx| match tx {
431            Transaction::ExecuteOperation(operation) => Some(operation),
432            Transaction::ReceiveMessages(_) => None,
433        })
434    }
435
436    /// Returns all incoming bundles in this block body.
437    pub fn incoming_bundles(&self) -> impl Iterator<Item = &IncomingBundle> {
438        self.transactions.iter().filter_map(|tx| match tx {
439            Transaction::ReceiveMessages(bundle) => Some(bundle),
440            Transaction::ExecuteOperation(_) => None,
441        })
442    }
443
444    /// Returns whether the first transaction in this block body is a
445    /// `SystemOperation::Checkpoint`. See [`ProposedBlock::starts_with_checkpoint`].
446    pub fn starts_with_checkpoint(&self) -> bool {
447        self.transactions
448            .first()
449            .is_some_and(Transaction::is_checkpoint)
450    }
451}
452
453#[async_graphql::ComplexObject]
454impl BlockBody {
455    /// Metadata about the transactions in this block.
456    async fn transaction_metadata(&self) -> Vec<crate::data_types::TransactionMetadata> {
457        self.transactions
458            .iter()
459            .map(crate::data_types::TransactionMetadata::from_transaction)
460            .collect()
461    }
462}
463
464impl Block {
465    /// Creates a new `Block` from a proposed block and its execution outcome.
466    pub fn new(block: ProposedBlock, outcome: BlockExecutionOutcome) -> Self {
467        let transactions_hash = hashing::hash_vec(&block.transactions);
468        let messages_hash = hashing::hash_vec_vec(&outcome.messages);
469        let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
470            inner: Cow::Borrowed(&outcome.previous_message_blocks),
471        });
472        let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
473            inner: Cow::Borrowed(&outcome.previous_event_blocks),
474        });
475        let oracle_responses_hash = hashing::hash_vec_vec(&outcome.oracle_responses);
476        let events_hash = hashing::hash_vec_vec(&outcome.events);
477        let blobs_hash = hashing::hash_vec_vec(&outcome.blobs);
478        let operation_results_hash = hashing::hash_vec(&outcome.operation_results);
479
480        let header = BlockHeader {
481            chain_id: block.chain_id,
482            epoch: block.epoch,
483            height: block.height,
484            timestamp: block.timestamp,
485            state_hash: outcome.state_hash,
486            previous_block_hash: block.previous_block_hash,
487            authenticated_owner: block.authenticated_owner,
488            transactions_hash,
489            messages_hash,
490            previous_message_blocks_hash,
491            previous_event_blocks_hash,
492            oracle_responses_hash,
493            events_hash,
494            blobs_hash,
495            operation_results_hash,
496        };
497
498        let body = BlockBody {
499            transactions: block.transactions,
500            messages: outcome.messages,
501            previous_message_blocks: outcome.previous_message_blocks,
502            previous_event_blocks: outcome.previous_event_blocks,
503            oracle_responses: outcome.oracle_responses,
504            events: outcome.events,
505            blobs: outcome.blobs,
506            operation_results: outcome.operation_results,
507        };
508
509        Self { header, body }
510    }
511
512    /// Returns the hash of this block, which commits to the entire block via its header.
513    pub fn hash(&self) -> CryptoHash {
514        CryptoHash::new(&self.header)
515    }
516
517    /// Returns the bundles of messages sent via the given medium to the specified
518    /// recipient. Messages originating from different transactions of the original block
519    /// are kept in separate bundles. If the medium is a channel, does not verify that the
520    /// recipient is actually subscribed to that channel.
521    pub fn message_bundles_for(
522        &self,
523        recipient: ChainId,
524        certificate_hash: CryptoHash,
525    ) -> impl Iterator<Item = (Epoch, MessageBundle)> + '_ {
526        let block_height = self.header.height;
527        let block_timestamp = self.header.timestamp;
528        let block_epoch = self.header.epoch;
529
530        (0u32..)
531            .zip(self.messages())
532            .filter_map(move |(transaction_index, txn_messages)| {
533                let messages = txn_messages
534                    .iter()
535                    .filter(|message| message.destination == recipient)
536                    .map(|message| message.clone().into_posted())
537                    .collect::<Vec<_>>();
538                (!messages.is_empty()).then(|| {
539                    let bundle = MessageBundle {
540                        height: block_height,
541                        timestamp: block_timestamp,
542                        certificate_hash,
543                        transaction_index,
544                        messages,
545                    };
546                    (block_epoch, bundle)
547                })
548            })
549    }
550
551    /// Returns all the blob IDs required by this block.
552    /// Either as oracle responses or as published blobs.
553    pub fn required_blob_ids(&self) -> BTreeSet<BlobId> {
554        let mut blob_ids = self.oracle_blob_ids();
555        blob_ids.extend(self.published_blob_ids());
556        blob_ids.extend(self.created_blob_ids());
557        if self.header.height == BlockHeight(0) {
558            // the initial block implicitly depends on the chain description blob
559            blob_ids.insert(BlobId::new(
560                self.header.chain_id.0,
561                BlobType::ChainDescription,
562            ));
563        }
564        blob_ids
565    }
566
567    /// Returns whether this block requires the blob with the specified ID.
568    pub fn requires_or_creates_blob(&self, blob_id: &BlobId) -> bool {
569        self.oracle_blob_ids().contains(blob_id)
570            || self.published_blob_ids().contains(blob_id)
571            || self.created_blob_ids().contains(blob_id)
572            || (self.header.height == BlockHeight(0)
573                && (blob_id.blob_type == BlobType::ChainDescription
574                    && blob_id.hash == self.header.chain_id.0))
575    }
576
577    /// Returns all the published blob IDs in this block's transactions.
578    pub fn published_blob_ids(&self) -> BTreeSet<BlobId> {
579        self.body
580            .operations()
581            .flat_map(Operation::published_blob_ids)
582            .collect()
583    }
584
585    /// Returns whether the first transaction in this block is a
586    /// `SystemOperation::Checkpoint`. See [`BlockBody::starts_with_checkpoint`].
587    pub fn starts_with_checkpoint(&self) -> bool {
588        self.body.starts_with_checkpoint()
589    }
590
591    /// Returns all the blob IDs created by the block's transactions.
592    pub fn created_blob_ids(&self) -> BTreeSet<BlobId> {
593        self.body
594            .blobs
595            .iter()
596            .flatten()
597            .map(|blob| blob.id())
598            .collect()
599    }
600
601    /// Returns all the blobs created by the block's transactions.
602    pub fn created_blobs(&self) -> BTreeMap<BlobId, Blob> {
603        self.body
604            .blobs
605            .iter()
606            .flatten()
607            .map(|blob| (blob.id(), blob.clone()))
608            .collect()
609    }
610
611    /// Returns set of blob IDs that were a result of an oracle call.
612    pub fn oracle_blob_ids(&self) -> BTreeSet<BlobId> {
613        let mut required_blob_ids = BTreeSet::new();
614        for responses in &self.body.oracle_responses {
615            for response in responses {
616                match response {
617                    OracleResponse::Blob(blob_id) => {
618                        required_blob_ids.insert(*blob_id);
619                    }
620                    OracleResponse::Checkpoint { used_blobs, .. } => {
621                        required_blob_ids.extend(used_blobs.iter().copied());
622                    }
623                    _ => {}
624                }
625            }
626        }
627
628        required_blob_ids
629    }
630
631    /// Returns reference to the outgoing messages in the block.
632    pub fn messages(&self) -> &Vec<Vec<OutgoingMessage>> {
633        &self.body.messages
634    }
635
636    /// Returns all recipients of messages in this block.
637    pub fn recipients(&self) -> BTreeSet<ChainId> {
638        self.body
639            .messages
640            .iter()
641            .flat_map(|messages| messages.iter().map(|message| message.destination))
642            .collect()
643    }
644
645    /// Returns whether there are any oracle responses in this block.
646    pub fn has_oracle_responses(&self) -> bool {
647        self.body
648            .oracle_responses
649            .iter()
650            .any(|responses| !responses.is_empty())
651    }
652
653    /// Returns whether this block matches the proposal.
654    pub fn matches_proposed_block(&self, block: &ProposedBlock) -> bool {
655        let ProposedBlock {
656            chain_id,
657            epoch,
658            transactions,
659            height,
660            timestamp,
661            authenticated_owner,
662            previous_block_hash,
663        } = block;
664        *chain_id == self.header.chain_id
665            && *epoch == self.header.epoch
666            && *transactions == self.body.transactions
667            && *height == self.header.height
668            && *timestamp == self.header.timestamp
669            && *authenticated_owner == self.header.authenticated_owner
670            && *previous_block_hash == self.header.previous_block_hash
671    }
672
673    /// Returns whether the block's execution produced the given outcome.
674    pub fn outcome_matches(&self, expected: &BlockExecutionOutcome) -> bool {
675        let BlockExecutionOutcome {
676            state_hash,
677            messages,
678            previous_message_blocks,
679            previous_event_blocks,
680            oracle_responses,
681            events,
682            blobs,
683            operation_results,
684        } = expected;
685        self.header.state_hash == *state_hash
686            && self.body.messages == *messages
687            && self.body.previous_message_blocks == *previous_message_blocks
688            && self.body.previous_event_blocks == *previous_event_blocks
689            && self.body.oracle_responses == *oracle_responses
690            && self.body.events == *events
691            && self.body.blobs == *blobs
692            && self.body.operation_results == *operation_results
693    }
694
695    /// Splits this block back into the proposed block and its execution outcome.
696    pub fn into_proposal(self) -> (ProposedBlock, BlockExecutionOutcome) {
697        let proposed_block = ProposedBlock {
698            chain_id: self.header.chain_id,
699            epoch: self.header.epoch,
700            transactions: self.body.transactions,
701            height: self.header.height,
702            timestamp: self.header.timestamp,
703            authenticated_owner: self.header.authenticated_owner,
704            previous_block_hash: self.header.previous_block_hash,
705        };
706        let outcome = BlockExecutionOutcome {
707            state_hash: self.header.state_hash,
708            messages: self.body.messages,
709            previous_message_blocks: self.body.previous_message_blocks,
710            previous_event_blocks: self.body.previous_event_blocks,
711            oracle_responses: self.body.oracle_responses,
712            events: self.body.events,
713            blobs: self.body.blobs,
714            operation_results: self.body.operation_results,
715        };
716        (proposed_block, outcome)
717    }
718
719    /// Returns the IDs of all events in this block.
720    pub fn event_ids(&self) -> impl Iterator<Item = EventId> + '_ {
721        let to_id = |event: &Event| event.id(self.header.chain_id);
722        self.body.events.iter().flatten().map(to_id)
723    }
724}
725
726/// A single field of a [`BlockBody`], paired with enough data to recompute its hash and
727/// check it against the matching hash in a [`BlockHeader`]. This lets a holder of a header
728/// prove that one body field belongs to the block without the rest of the body.
729#[derive(derive_more::From)]
730#[allow(missing_docs)]
731pub enum BlockBodyField {
732    Transactions(Vec<Transaction>),
733    Messages(Vec<Vec<OutgoingMessage>>),
734    PreviousMessageBlocks(BTreeMap<ChainId, (CryptoHash, BlockHeight)>),
735    PreviousEventBlocks(BTreeMap<StreamId, (CryptoHash, BlockHeight)>),
736    OracleResponses(Vec<Vec<OracleResponse>>),
737    Events(Vec<Vec<Event>>),
738    Blobs(Vec<Vec<Blob>>),
739    OperationResults(Vec<OperationResult>),
740}
741
742impl BlockHeader {
743    /// Returns whether `field` is the body field this header commits to.
744    pub fn verifies(&self, field: impl Into<BlockBodyField>) -> bool {
745        match field.into() {
746            BlockBodyField::Transactions(v) => hashing::hash_vec(v) == self.transactions_hash,
747            BlockBodyField::Messages(v) => hashing::hash_vec_vec(v) == self.messages_hash,
748            BlockBodyField::PreviousMessageBlocks(m) => {
749                CryptoHash::new(&PreviousMessageBlocksMap {
750                    inner: Cow::Owned(m),
751                }) == self.previous_message_blocks_hash
752            }
753            BlockBodyField::PreviousEventBlocks(m) => {
754                CryptoHash::new(&PreviousEventBlocksMap {
755                    inner: Cow::Owned(m),
756                }) == self.previous_event_blocks_hash
757            }
758            BlockBodyField::OracleResponses(v) => {
759                hashing::hash_vec_vec(v) == self.oracle_responses_hash
760            }
761            BlockBodyField::Events(v) => hashing::hash_vec_vec(v) == self.events_hash,
762            BlockBodyField::Blobs(v) => hashing::hash_vec_vec(v) == self.blobs_hash,
763            BlockBodyField::OperationResults(v) => {
764                hashing::hash_vec(v) == self.operation_results_hash
765            }
766        }
767    }
768}
769
770impl BcsHashable<'_> for BlockHeader {}
771
772/// Hashable wrapper around the lookup table mapping each recipient chain to the previous
773/// block that sent it messages.
774#[derive(Serialize, Deserialize)]
775pub struct PreviousMessageBlocksMap<'a> {
776    inner: Cow<'a, BTreeMap<ChainId, (CryptoHash, BlockHeight)>>,
777}
778
779impl<'de> BcsHashable<'de> for PreviousMessageBlocksMap<'de> {}
780
781/// Hashable wrapper around the lookup table mapping each stream to the previous block that
782/// published events to it.
783#[derive(Serialize, Deserialize)]
784pub struct PreviousEventBlocksMap<'a> {
785    inner: Cow<'a, BTreeMap<StreamId, (CryptoHash, BlockHeight)>>,
786}
787
788impl<'de> BcsHashable<'de> for PreviousEventBlocksMap<'de> {}
789
790#[derive(Serialize, Deserialize)]
791#[serde(rename = "BlockHeader")]
792struct SerializedHeader {
793    chain_id: ChainId,
794    epoch: Epoch,
795    height: BlockHeight,
796    timestamp: Timestamp,
797    state_hash: CryptoHash,
798    previous_block_hash: Option<CryptoHash>,
799    authenticated_owner: Option<AccountOwner>,
800}
801
802mod hashing {
803    use linera_base::crypto::{BcsHashable, CryptoHash, CryptoHashVec};
804
805    pub(super) fn hash_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[T]>) -> CryptoHash {
806        let v = CryptoHashVec(it.as_ref().iter().map(CryptoHash::new).collect::<Vec<_>>());
807        CryptoHash::new(&v)
808    }
809
810    pub(super) fn hash_vec_vec<'de, T: BcsHashable<'de>>(it: impl AsRef<[Vec<T>]>) -> CryptoHash {
811        let v = CryptoHashVec(it.as_ref().iter().map(hash_vec).collect::<Vec<_>>());
812        CryptoHash::new(&v)
813    }
814}