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