linera_chain/
data_types.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::{borrow::Cow, collections::HashSet, fmt};
6
7use async_graphql::SimpleObject;
8use linera_base::{
9    crypto::{BcsHashable, BcsSignable, CryptoError, CryptoHash, KeyPair, PublicKey, Signature},
10    data_types::{Amount, Blob, BlockHeight, OracleResponse, Round, Timestamp},
11    doc_scalar, ensure,
12    identifiers::{
13        Account, BlobId, BlobType, ChainId, ChannelName, Destination, GenericApplicationId,
14        MessageId, Owner, StreamId,
15    },
16};
17use linera_execution::{
18    committee::{Committee, Epoch, ValidatorName},
19    system::OpenChainConfig,
20    Message, MessageKind, Operation, SystemMessage, SystemOperation,
21};
22use serde::{de::Deserializer, Deserialize, Serialize};
23
24use crate::ChainError;
25
26#[cfg(test)]
27#[path = "unit_tests/data_types_tests.rs"]
28mod data_types_tests;
29
30/// A block containing operations to apply on a given chain, as well as the
31/// acknowledgment of a number of incoming messages from other chains.
32/// * Incoming messages must be selected in the order they were
33///   produced by the sending chain, but can be skipped.
34/// * When a block is proposed to a validator, all cross-chain messages must have been
35///   received ahead of time in the inbox of the chain.
36/// * This constraint does not apply to the execution of confirmed blocks.
37#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
38pub struct Block {
39    /// The chain to which this block belongs.
40    pub chain_id: ChainId,
41    /// The number identifying the current configuration.
42    pub epoch: Epoch,
43    /// A selection of incoming messages to be executed first. Successive messages of same
44    /// sender and height are grouped together for conciseness.
45    pub incoming_bundles: Vec<IncomingBundle>,
46    /// The operations to execute.
47    pub operations: Vec<Operation>,
48    /// The block height.
49    pub height: BlockHeight,
50    /// The timestamp when this block was created. This must be later than all messages received
51    /// in this block, but no later than the current time.
52    pub timestamp: Timestamp,
53    /// The user signing for the operations in the block and paying for their execution
54    /// fees. If set, this must be the `owner` in the block proposal. `None` means that
55    /// the default account of the chain is used. This value is also used as recipient of
56    /// potential refunds for the message grants created by the operations.
57    pub authenticated_signer: Option<Owner>,
58    /// Certified hash (see `Certificate` below) of the previous block in the
59    /// chain, if any.
60    pub previous_block_hash: Option<CryptoHash>,
61}
62
63impl Block {
64    /// Returns all the published blob IDs in this block's operations.
65    pub fn published_blob_ids(&self) -> HashSet<BlobId> {
66        let mut blob_ids = HashSet::new();
67        for operation in &self.operations {
68            if let Operation::System(SystemOperation::PublishDataBlob { blob_hash }) = operation {
69                blob_ids.insert(BlobId::new(*blob_hash, BlobType::Data));
70            }
71            if let Operation::System(SystemOperation::PublishBytecode { bytecode_id }) = operation {
72                blob_ids.extend([
73                    BlobId::new(bytecode_id.contract_blob_hash, BlobType::ContractBytecode),
74                    BlobId::new(bytecode_id.service_blob_hash, BlobType::ServiceBytecode),
75                ]);
76            }
77        }
78
79        blob_ids
80    }
81
82    /// Returns whether the block contains only rejected incoming messages, which
83    /// makes it admissible even on closed chains.
84    pub fn has_only_rejected_messages(&self) -> bool {
85        self.operations.is_empty()
86            && self
87                .incoming_bundles
88                .iter()
89                .all(|message| message.action == MessageAction::Reject)
90    }
91
92    /// Returns an iterator over all incoming [`PostedMessage`]s in this block.
93    pub fn incoming_messages(&self) -> impl Iterator<Item = &PostedMessage> {
94        self.incoming_bundles
95            .iter()
96            .flat_map(|incoming_bundle| &incoming_bundle.bundle.messages)
97    }
98
99    /// Returns the number of incoming messages.
100    pub fn message_count(&self) -> usize {
101        self.incoming_bundles
102            .iter()
103            .map(|im| im.bundle.messages.len())
104            .sum()
105    }
106
107    /// Returns an iterator over all transactions, by index.
108    pub fn transactions(&self) -> impl Iterator<Item = (u32, Transaction<'_>)> {
109        let bundles = self
110            .incoming_bundles
111            .iter()
112            .map(Transaction::ReceiveMessages);
113        let operations = self.operations.iter().map(Transaction::ExecuteOperation);
114        (0u32..).zip(bundles.chain(operations))
115    }
116
117    /// If the block's first message is `OpenChain`, returns the bundle, the message and
118    /// the configuration for the new chain.
119    pub fn starts_with_open_chain_message(
120        &self,
121    ) -> Option<(&IncomingBundle, &PostedMessage, &OpenChainConfig)> {
122        let in_bundle = self.incoming_bundles.first()?;
123        if in_bundle.action != MessageAction::Accept {
124            return None;
125        }
126        let posted_message = in_bundle.bundle.messages.first()?;
127        let config = posted_message.message.matches_open_chain()?;
128        Some((in_bundle, posted_message, config))
129    }
130}
131
132/// A transaction in a block: incoming messages or an operation.
133#[derive(Debug, Clone)]
134pub enum Transaction<'a> {
135    /// Receive a bundle of incoming messages.
136    ReceiveMessages(&'a IncomingBundle),
137    /// Execute an operation.
138    ExecuteOperation(&'a Operation),
139}
140
141/// A chain ID with a block height.
142#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, SimpleObject)]
143pub struct ChainAndHeight {
144    pub chain_id: ChainId,
145    pub height: BlockHeight,
146}
147
148impl ChainAndHeight {
149    /// Returns the ID of the `index`-th message sent by the block at that height.
150    pub fn to_message_id(&self, index: u32) -> MessageId {
151        MessageId {
152            chain_id: self.chain_id,
153            height: self.height,
154            index,
155        }
156    }
157}
158
159/// A bundle of cross-chain messages.
160#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
161pub struct IncomingBundle {
162    /// The origin of the messages (chain and channel if any).
163    pub origin: Origin,
164    /// The messages to be delivered to the inbox identified by `origin`.
165    pub bundle: MessageBundle,
166    /// What to do with the message.
167    pub action: MessageAction,
168}
169
170impl IncomingBundle {
171    /// Returns an iterator over all posted messages in this bundle, together with their ID.
172    pub fn messages_and_ids(&self) -> impl Iterator<Item = (MessageId, &PostedMessage)> {
173        let chain_and_height = ChainAndHeight {
174            chain_id: self.origin.sender,
175            height: self.bundle.height,
176        };
177        let messages = self.bundle.messages.iter();
178        messages.map(move |posted_message| {
179            let message_id = chain_and_height.to_message_id(posted_message.index);
180            (message_id, posted_message)
181        })
182    }
183
184    /// Rearranges the messages in the bundle so that the first message is an `OpenChain` message.
185    /// Returns whether the `OpenChain` message was found at all.
186    pub fn put_openchain_at_front(bundles: &mut [IncomingBundle]) -> bool {
187        let Some(index) = bundles.iter().position(|msg| {
188            matches!(
189                msg.bundle.messages.first(),
190                Some(PostedMessage {
191                    message: Message::System(SystemMessage::OpenChain(_)),
192                    ..
193                })
194            )
195        }) else {
196            return false;
197        };
198
199        bundles[0..=index].rotate_right(1);
200        true
201    }
202}
203
204/// What to do with a message picked from the inbox.
205#[derive(Copy, Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
206pub enum MessageAction {
207    /// Execute the incoming message.
208    Accept,
209    /// Do not execute the incoming message.
210    Reject,
211}
212
213/// The origin of a message, relative to a particular application. Used to identify each inbox.
214#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
215pub struct Origin {
216    /// The chain ID of the sender.
217    pub sender: ChainId,
218    /// The medium.
219    pub medium: Medium,
220}
221
222/// The target of a message, relative to a particular application. Used to identify each outbox.
223#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
224pub struct Target {
225    /// The chain ID of the recipient.
226    pub recipient: ChainId,
227    /// The medium.
228    pub medium: Medium,
229}
230
231/// A set of messages from a single block, for a single destination.
232#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, SimpleObject)]
233pub struct MessageBundle {
234    /// The block height.
235    pub height: BlockHeight,
236    /// The block's timestamp.
237    pub timestamp: Timestamp,
238    /// The confirmed block certificate hash.
239    pub certificate_hash: CryptoHash,
240    /// The index of the transaction in the block that is sending this bundle.
241    pub transaction_index: u32,
242    /// The relevant messages.
243    pub messages: Vec<PostedMessage>,
244}
245
246#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
247/// A channel name together with its application ID.
248pub struct ChannelFullName {
249    /// The application owning the channel.
250    pub application_id: GenericApplicationId,
251    /// The name of the channel.
252    pub name: ChannelName,
253}
254
255impl fmt::Display for ChannelFullName {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        let name = hex::encode(&self.name);
258        match self.application_id {
259            GenericApplicationId::System => write!(f, "system channel {name}"),
260            GenericApplicationId::User(app_id) => write!(f, "user channel {name} for app {app_id}"),
261        }
262    }
263}
264
265/// The origin of a message coming from a particular chain. Used to identify each inbox.
266#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
267pub enum Medium {
268    /// The message is a direct message.
269    Direct,
270    /// The message is a channel broadcast.
271    Channel(ChannelFullName),
272}
273
274/// An authenticated proposal for a new block.
275// TODO(#456): the signature of the block owner is currently lost but it would be useful
276// to have it for auditing purposes.
277#[derive(Clone, Debug, Serialize, Deserialize)]
278#[cfg_attr(with_testing, derive(Eq, PartialEq))]
279pub struct BlockProposal {
280    pub content: ProposalContent,
281    pub owner: Owner,
282    pub signature: Signature,
283    pub blobs: Vec<Blob>,
284    pub validated_block_certificate: Option<LiteCertificate<'static>>,
285}
286
287/// A posted message together with routing information.
288#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
289pub struct OutgoingMessage {
290    /// The destination of the message.
291    pub destination: Destination,
292    /// The user authentication carried by the message, if any.
293    pub authenticated_signer: Option<Owner>,
294    /// A grant to pay for the message execution.
295    pub grant: Amount,
296    /// Where to send a refund for the unused part of the grant after execution, if any.
297    pub refund_grant_to: Option<Account>,
298    /// The kind of message being sent.
299    pub kind: MessageKind,
300    /// The message itself.
301    pub message: Message,
302}
303
304/// A message together with kind, authentication and grant information.
305#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
306pub struct PostedMessage {
307    /// The user authentication carried by the message, if any.
308    pub authenticated_signer: Option<Owner>,
309    /// A grant to pay for the message execution.
310    pub grant: Amount,
311    /// Where to send a refund for the unused part of the grant after execution, if any.
312    pub refund_grant_to: Option<Account>,
313    /// The kind of message being sent.
314    pub kind: MessageKind,
315    /// The index of the message in the sending block.
316    pub index: u32,
317    /// The message itself.
318    pub message: Message,
319}
320
321impl OutgoingMessage {
322    /// Returns whether this message is sent via the given medium to the specified
323    /// recipient. If the medium is a channel, does not verify that the recipient is
324    /// actually subscribed to that channel.
325    pub fn has_destination(&self, medium: &Medium, recipient: ChainId) -> bool {
326        match (&self.destination, medium) {
327            (Destination::Recipient(_), Medium::Channel(_))
328            | (Destination::Subscribers(_), Medium::Direct) => false,
329            (Destination::Recipient(id), Medium::Direct) => *id == recipient,
330            (
331                Destination::Subscribers(dest_name),
332                Medium::Channel(ChannelFullName {
333                    application_id,
334                    name,
335                }),
336            ) => *application_id == self.message.application_id() && name == dest_name,
337        }
338    }
339
340    /// Returns the posted message, i.e. the outgoing message without the destination.
341    pub fn into_posted(self, index: u32) -> PostedMessage {
342        let OutgoingMessage {
343            destination: _,
344            authenticated_signer,
345            grant,
346            refund_grant_to,
347            kind,
348            message,
349        } = self;
350        PostedMessage {
351            authenticated_signer,
352            grant,
353            refund_grant_to,
354            kind,
355            index,
356            message,
357        }
358    }
359}
360
361/// A [`Block`], together with the outcome from its execution.
362#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
363pub struct ExecutedBlock {
364    pub block: Block,
365    pub outcome: BlockExecutionOutcome,
366}
367
368/// The messages and the state hash resulting from a [`Block`]'s execution.
369#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
370#[cfg_attr(with_testing, derive(Default))]
371pub struct BlockExecutionOutcome {
372    /// The list of outgoing messages for each transaction.
373    pub messages: Vec<Vec<OutgoingMessage>>,
374    /// The hash of the chain's execution state after this block.
375    pub state_hash: CryptoHash,
376    /// The record of oracle responses for each transaction.
377    pub oracle_responses: Vec<Vec<OracleResponse>>,
378    /// The list of events produced by each transaction.
379    pub events: Vec<Vec<EventRecord>>,
380}
381
382/// An event recorded in an executed block.
383#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
384pub struct EventRecord {
385    /// The ID of the stream this event belongs to.
386    pub stream_id: StreamId,
387    /// The event key.
388    pub key: Vec<u8>,
389    /// The payload data.
390    pub value: Vec<u8>,
391}
392
393/// A statement to be certified by the validators.
394#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
395pub enum CertificateValue {
396    ValidatedBlock {
397        executed_block: ExecutedBlock,
398    },
399    ConfirmedBlock {
400        executed_block: ExecutedBlock,
401    },
402    Timeout {
403        chain_id: ChainId,
404        height: BlockHeight,
405        epoch: Epoch,
406    },
407}
408
409#[async_graphql::Object(cache_control(no_cache))]
410impl CertificateValue {
411    #[graphql(derived(name = "executed_block"))]
412    async fn _executed_block(&self) -> Option<ExecutedBlock> {
413        self.executed_block().cloned()
414    }
415
416    async fn status(&self) -> String {
417        match self {
418            CertificateValue::ValidatedBlock { .. } => "validated".to_string(),
419            CertificateValue::ConfirmedBlock { .. } => "confirmed".to_string(),
420            CertificateValue::Timeout { .. } => "timeout".to_string(),
421        }
422    }
423}
424
425/// A statement to be certified by the validators, with its hash.
426#[derive(Debug, PartialEq, Eq, Hash, Clone)]
427pub struct HashedCertificateValue {
428    value: CertificateValue,
429    /// Hash of the value (used as key for storage).
430    hash: CryptoHash,
431}
432
433#[async_graphql::Object(cache_control(no_cache))]
434impl HashedCertificateValue {
435    #[graphql(derived(name = "hash"))]
436    async fn _hash(&self) -> CryptoHash {
437        self.hash
438    }
439
440    #[graphql(derived(name = "value"))]
441    async fn _value(&self) -> CertificateValue {
442        self.value.clone()
443    }
444}
445
446/// The hash and chain ID of a `CertificateValue`.
447#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
448pub struct LiteValue {
449    pub value_hash: CryptoHash,
450    pub chain_id: ChainId,
451}
452
453#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
454struct ValueHashAndRound(CryptoHash, Round);
455
456/// A vote on a statement from a validator.
457#[derive(Clone, Debug, Serialize, Deserialize)]
458pub struct Vote {
459    pub value: HashedCertificateValue,
460    pub round: Round,
461    pub validator: ValidatorName,
462    pub signature: Signature,
463}
464
465impl Vote {
466    /// Use signing key to create a signed object.
467    pub fn new(value: HashedCertificateValue, round: Round, key_pair: &KeyPair) -> Self {
468        let hash_and_round = ValueHashAndRound(value.hash, round);
469        let signature = Signature::new(&hash_and_round, key_pair);
470        Self {
471            value,
472            round,
473            validator: ValidatorName(key_pair.public()),
474            signature,
475        }
476    }
477
478    /// Returns the vote, with a `LiteValue` instead of the full value.
479    pub fn lite(&self) -> LiteVote {
480        LiteVote {
481            value: self.value.lite(),
482            round: self.round,
483            validator: self.validator,
484            signature: self.signature,
485        }
486    }
487
488    /// Returns the value this vote is for.
489    pub fn value(&self) -> &CertificateValue {
490        self.value.inner()
491    }
492}
493
494/// A vote on a statement from a validator, represented as a `LiteValue`.
495#[derive(Clone, Debug, Serialize, Deserialize)]
496#[cfg_attr(with_testing, derive(Eq, PartialEq))]
497pub struct LiteVote {
498    pub value: LiteValue,
499    pub round: Round,
500    pub validator: ValidatorName,
501    pub signature: Signature,
502}
503
504impl LiteVote {
505    /// Returns the full vote, with the value, if it matches.
506    pub fn with_value(self, value: HashedCertificateValue) -> Option<Vote> {
507        if self.value != value.lite() {
508            return None;
509        }
510        Some(Vote {
511            value,
512            round: self.round,
513            validator: self.validator,
514            signature: self.signature,
515        })
516    }
517}
518
519/// A certified statement from the committee, without the value.
520#[derive(Clone, Debug, Serialize, Deserialize)]
521#[cfg_attr(with_testing, derive(Eq, PartialEq))]
522pub struct LiteCertificate<'a> {
523    /// Hash and chain ID of the certified value (used as key for storage).
524    pub value: LiteValue,
525    /// The round in which the value was certified.
526    pub round: Round,
527    /// Signatures on the value.
528    pub signatures: Cow<'a, [(ValidatorName, Signature)]>,
529}
530
531impl<'a> LiteCertificate<'a> {
532    pub fn new(
533        value: LiteValue,
534        round: Round,
535        mut signatures: Vec<(ValidatorName, Signature)>,
536    ) -> Self {
537        signatures.sort_by_key(|&(validator_name, _)| validator_name);
538
539        let signatures = Cow::Owned(signatures);
540        Self {
541            value,
542            round,
543            signatures,
544        }
545    }
546
547    /// Creates a `LiteCertificate` from a list of votes, without cryptographically checking the
548    /// signatures. Returns `None` if the votes are empty or don't have matching values and rounds.
549    pub fn try_from_votes(votes: impl IntoIterator<Item = LiteVote>) -> Option<Self> {
550        let mut votes = votes.into_iter();
551        let LiteVote {
552            value,
553            round,
554            validator,
555            signature,
556        } = votes.next()?;
557        let mut signatures = vec![(validator, signature)];
558        for vote in votes {
559            if vote.value.value_hash != value.value_hash || vote.round != round {
560                return None;
561            }
562            signatures.push((vote.validator, vote.signature));
563        }
564        Some(LiteCertificate::new(value, round, signatures))
565    }
566
567    /// Verifies the certificate.
568    pub fn check(&self, committee: &Committee) -> Result<&LiteValue, ChainError> {
569        check_signatures(
570            self.value.value_hash,
571            self.round,
572            &self.signatures,
573            committee,
574        )?;
575        Ok(&self.value)
576    }
577
578    /// Returns the `Certificate` with the specified value, if it matches.
579    pub fn with_value(self, value: HashedCertificateValue) -> Option<Certificate> {
580        if self.value.chain_id != value.inner().chain_id() || self.value.value_hash != value.hash()
581        {
582            return None;
583        }
584        Some(Certificate {
585            value,
586            round: self.round,
587            signatures: self.signatures.into_owned(),
588        })
589    }
590
591    /// Returns a `LiteCertificate` that owns the list of signatures.
592    pub fn cloned(&self) -> LiteCertificate<'static> {
593        LiteCertificate {
594            value: self.value.clone(),
595            round: self.round,
596            signatures: Cow::Owned(self.signatures.clone().into_owned()),
597        }
598    }
599}
600
601/// A certified statement from the committee.
602#[derive(Clone, Debug, Serialize)]
603#[cfg_attr(with_testing, derive(Eq, PartialEq))]
604pub struct Certificate {
605    /// The certified value.
606    pub value: HashedCertificateValue,
607    /// The round in which the value was certified.
608    pub round: Round,
609    /// Signatures on the value.
610    signatures: Vec<(ValidatorName, Signature)>,
611}
612
613impl fmt::Display for Origin {
614    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
615        match &self.medium {
616            Medium::Direct => write!(f, "{:.8} (direct)", self.sender),
617            Medium::Channel(full_name) => write!(f, "{:.8} via {full_name:.8}", self.sender),
618        }
619    }
620}
621
622impl Origin {
623    pub fn chain(sender: ChainId) -> Self {
624        Self {
625            sender,
626            medium: Medium::Direct,
627        }
628    }
629
630    pub fn channel(sender: ChainId, name: ChannelFullName) -> Self {
631        Self {
632            sender,
633            medium: Medium::Channel(name),
634        }
635    }
636}
637
638impl Target {
639    pub fn chain(recipient: ChainId) -> Self {
640        Self {
641            recipient,
642            medium: Medium::Direct,
643        }
644    }
645
646    pub fn channel(recipient: ChainId, name: ChannelFullName) -> Self {
647        Self {
648            recipient,
649            medium: Medium::Channel(name),
650        }
651    }
652}
653
654impl Serialize for HashedCertificateValue {
655    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
656    where
657        S: serde::Serializer,
658    {
659        self.value.serialize(serializer)
660    }
661}
662
663impl<'a> Deserialize<'a> for HashedCertificateValue {
664    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
665    where
666        D: Deserializer<'a>,
667    {
668        Ok(CertificateValue::deserialize(deserializer)?.into())
669    }
670}
671
672impl From<CertificateValue> for HashedCertificateValue {
673    fn from(value: CertificateValue) -> HashedCertificateValue {
674        value.with_hash()
675    }
676}
677
678impl From<HashedCertificateValue> for CertificateValue {
679    fn from(hv: HashedCertificateValue) -> CertificateValue {
680        hv.value
681    }
682}
683
684impl CertificateValue {
685    pub fn chain_id(&self) -> ChainId {
686        match self {
687            CertificateValue::ConfirmedBlock { executed_block, .. }
688            | CertificateValue::ValidatedBlock { executed_block, .. } => {
689                executed_block.block.chain_id
690            }
691            CertificateValue::Timeout { chain_id, .. } => *chain_id,
692        }
693    }
694
695    pub fn height(&self) -> BlockHeight {
696        match self {
697            CertificateValue::ConfirmedBlock { executed_block, .. }
698            | CertificateValue::ValidatedBlock { executed_block, .. } => {
699                executed_block.block.height
700            }
701            CertificateValue::Timeout { height, .. } => *height,
702        }
703    }
704
705    pub fn epoch(&self) -> Epoch {
706        match self {
707            CertificateValue::ConfirmedBlock { executed_block, .. }
708            | CertificateValue::ValidatedBlock { executed_block, .. } => executed_block.block.epoch,
709            CertificateValue::Timeout { epoch, .. } => *epoch,
710        }
711    }
712
713    /// Creates a `HashedCertificateValue` checking that this is the correct hash.
714    pub fn with_hash_checked(self, hash: CryptoHash) -> Result<HashedCertificateValue, ChainError> {
715        let hashed_certificate_value = self.with_hash();
716        ensure!(
717            hashed_certificate_value.hash == hash,
718            ChainError::CertificateValueHashMismatch {
719                expected: hash,
720                actual: hashed_certificate_value.hash
721            }
722        );
723        Ok(hashed_certificate_value)
724    }
725
726    /// Creates a `HashedCertificateValue` by hashing `self`. No hash checks are made!
727    pub fn with_hash(self) -> HashedCertificateValue {
728        let hash = CryptoHash::new(&self);
729        HashedCertificateValue { value: self, hash }
730    }
731
732    /// Creates a `HashedCertificateValue` without checking that this is the correct hash!
733    pub fn with_hash_unchecked(self, hash: CryptoHash) -> HashedCertificateValue {
734        HashedCertificateValue { value: self, hash }
735    }
736
737    /// Returns whether this value contains the message with the specified ID.
738    pub fn has_message(&self, message_id: &MessageId) -> bool {
739        let Some(executed_block) = self.executed_block() else {
740            return false;
741        };
742        let Ok(index) = usize::try_from(message_id.index) else {
743            return false;
744        };
745        self.height() == message_id.height
746            && self.chain_id() == message_id.chain_id
747            && executed_block.messages().len() > index
748    }
749
750    pub fn is_confirmed(&self) -> bool {
751        matches!(self, CertificateValue::ConfirmedBlock { .. })
752    }
753
754    pub fn is_validated(&self) -> bool {
755        matches!(self, CertificateValue::ValidatedBlock { .. })
756    }
757
758    pub fn is_timeout(&self) -> bool {
759        matches!(self, CertificateValue::Timeout { .. })
760    }
761
762    #[cfg(with_testing)]
763    pub fn messages(&self) -> Option<&Vec<Vec<OutgoingMessage>>> {
764        Some(self.executed_block()?.messages())
765    }
766
767    pub fn executed_block(&self) -> Option<&ExecutedBlock> {
768        match self {
769            CertificateValue::ConfirmedBlock { executed_block, .. }
770            | CertificateValue::ValidatedBlock { executed_block, .. } => Some(executed_block),
771            CertificateValue::Timeout { .. } => None,
772        }
773    }
774
775    pub fn block(&self) -> Option<&Block> {
776        self.executed_block()
777            .map(|executed_block| &executed_block.block)
778    }
779
780    pub fn to_log_str(&self) -> &'static str {
781        match self {
782            CertificateValue::ConfirmedBlock { .. } => "confirmed_block",
783            CertificateValue::ValidatedBlock { .. } => "validated_block",
784            CertificateValue::Timeout { .. } => "timeout",
785        }
786    }
787}
788
789impl MessageBundle {
790    pub fn is_skippable(&self) -> bool {
791        self.messages.iter().all(PostedMessage::is_skippable)
792    }
793
794    pub fn is_tracked(&self) -> bool {
795        let mut tracked = false;
796        for posted_message in &self.messages {
797            match posted_message.kind {
798                MessageKind::Simple | MessageKind::Bouncing => {}
799                MessageKind::Protected => return false,
800                MessageKind::Tracked => tracked = true,
801            }
802        }
803        tracked
804    }
805
806    pub fn is_protected(&self) -> bool {
807        self.messages.iter().any(PostedMessage::is_protected)
808    }
809
810    /// Returns whether this bundle must be added to the inbox.
811    ///
812    /// If this is `false`, it gets handled immediately and should never be received in a block.
813    pub fn goes_to_inbox(&self) -> bool {
814        self.messages
815            .iter()
816            .any(|posted_message| posted_message.message.goes_to_inbox())
817    }
818}
819
820impl PostedMessage {
821    pub fn is_skippable(&self) -> bool {
822        use MessageKind::*;
823        match self.kind {
824            Protected | Tracked => false,
825            Simple | Bouncing => self.grant == Amount::ZERO,
826        }
827    }
828
829    pub fn is_protected(&self) -> bool {
830        matches!(self.kind, MessageKind::Protected)
831    }
832
833    pub fn is_tracked(&self) -> bool {
834        matches!(self.kind, MessageKind::Tracked)
835    }
836
837    pub fn is_bouncing(&self) -> bool {
838        matches!(self.kind, MessageKind::Bouncing)
839    }
840}
841
842impl ExecutedBlock {
843    pub fn messages(&self) -> &Vec<Vec<OutgoingMessage>> {
844        &self.outcome.messages
845    }
846
847    /// Returns the bundles of messages sent via the given medium to the specified
848    /// recipient. Messages originating from different transactions of the original block
849    /// are kept in separate bundles. If the medium is a channel, does not verify that the
850    /// recipient is actually subscribed to that channel.
851    pub fn message_bundles_for<'a>(
852        &'a self,
853        medium: &'a Medium,
854        recipient: ChainId,
855        certificate_hash: CryptoHash,
856    ) -> impl Iterator<Item = (Epoch, MessageBundle)> + 'a {
857        let mut index = 0u32;
858        let block_height = self.block.height;
859        let block_timestamp = self.block.timestamp;
860        let block_epoch = self.block.epoch;
861
862        (0u32..)
863            .zip(self.messages())
864            .filter_map(move |(transaction_index, txn_messages)| {
865                let messages = (index..)
866                    .zip(txn_messages)
867                    .filter(|(_, message)| message.has_destination(medium, recipient))
868                    .map(|(idx, message)| message.clone().into_posted(idx))
869                    .collect::<Vec<_>>();
870                index += txn_messages.len() as u32;
871                (!messages.is_empty()).then(|| {
872                    let bundle = MessageBundle {
873                        height: block_height,
874                        timestamp: block_timestamp,
875                        certificate_hash,
876                        transaction_index,
877                        messages,
878                    };
879                    (block_epoch, bundle)
880                })
881            })
882    }
883
884    /// Returns the `message_index`th outgoing message created by the `operation_index`th operation,
885    /// or `None` if there is no such operation or message.
886    pub fn message_id_for_operation(
887        &self,
888        operation_index: usize,
889        message_index: u32,
890    ) -> Option<MessageId> {
891        let block = &self.block;
892        let transaction_index = block.incoming_bundles.len().checked_add(operation_index)?;
893        if message_index
894            >= u32::try_from(self.outcome.messages.get(transaction_index)?.len()).ok()?
895        {
896            return None;
897        }
898        let first_message_index = u32::try_from(
899            self.outcome
900                .messages
901                .iter()
902                .take(transaction_index)
903                .map(Vec::len)
904                .sum::<usize>(),
905        )
906        .ok()?;
907        let index = first_message_index.checked_add(message_index)?;
908        Some(self.message_id(index))
909    }
910
911    pub fn message_by_id(&self, message_id: &MessageId) -> Option<&OutgoingMessage> {
912        let MessageId {
913            chain_id,
914            height,
915            index,
916        } = message_id;
917        if self.block.chain_id != *chain_id || self.block.height != *height {
918            return None;
919        }
920        let mut index = usize::try_from(*index).ok()?;
921        for messages in self.messages() {
922            if let Some(message) = messages.get(index) {
923                return Some(message);
924            }
925            index -= messages.len();
926        }
927        None
928    }
929
930    /// Returns the message ID belonging to the `index`th outgoing message in this block.
931    pub fn message_id(&self, index: u32) -> MessageId {
932        MessageId {
933            chain_id: self.block.chain_id,
934            height: self.block.height,
935            index,
936        }
937    }
938
939    pub fn required_blob_ids(&self) -> HashSet<BlobId> {
940        self.outcome.required_blob_ids()
941    }
942
943    pub fn requires_blob(&self, blob_id: &BlobId) -> bool {
944        self.required_blob_ids().contains(blob_id)
945    }
946}
947
948impl BlockExecutionOutcome {
949    pub fn with(self, block: Block) -> ExecutedBlock {
950        ExecutedBlock {
951            block,
952            outcome: self,
953        }
954    }
955
956    pub fn required_blob_ids(&self) -> HashSet<BlobId> {
957        let mut required_blob_ids = HashSet::new();
958        for responses in &self.oracle_responses {
959            for response in responses {
960                if let OracleResponse::Blob(blob_id) = response {
961                    required_blob_ids.insert(*blob_id);
962                }
963            }
964        }
965
966        required_blob_ids
967    }
968}
969
970impl HashedCertificateValue {
971    /// Creates a [`ConfirmedBlock`](CertificateValue::ConfirmedBlock) value.
972    pub fn new_confirmed(executed_block: ExecutedBlock) -> HashedCertificateValue {
973        CertificateValue::ConfirmedBlock { executed_block }.into()
974    }
975
976    /// Creates a [`ValidatedBlock`](CertificateValue::ValidatedBlock) value.
977    pub fn new_validated(executed_block: ExecutedBlock) -> HashedCertificateValue {
978        CertificateValue::ValidatedBlock { executed_block }.into()
979    }
980
981    /// Creates a [`Timeout`](CertificateValue::Timeout) value.
982    pub fn new_timeout(
983        chain_id: ChainId,
984        height: BlockHeight,
985        epoch: Epoch,
986    ) -> HashedCertificateValue {
987        CertificateValue::Timeout {
988            chain_id,
989            height,
990            epoch,
991        }
992        .into()
993    }
994
995    pub fn hash(&self) -> CryptoHash {
996        self.hash
997    }
998
999    pub fn lite(&self) -> LiteValue {
1000        LiteValue {
1001            value_hash: self.hash(),
1002            chain_id: self.value.chain_id(),
1003        }
1004    }
1005
1006    /// Returns the corresponding `ConfirmedBlock`, if this is a `ValidatedBlock`.
1007    pub fn validated_to_confirmed(&self) -> Option<HashedCertificateValue> {
1008        match &self.value {
1009            CertificateValue::ValidatedBlock { executed_block } => Some(
1010                CertificateValue::ConfirmedBlock {
1011                    executed_block: executed_block.clone(),
1012                }
1013                .into(),
1014            ),
1015            CertificateValue::ConfirmedBlock { .. } | CertificateValue::Timeout { .. } => None,
1016        }
1017    }
1018
1019    pub fn inner(&self) -> &CertificateValue {
1020        &self.value
1021    }
1022
1023    pub fn into_inner(self) -> CertificateValue {
1024        self.value
1025    }
1026}
1027
1028/// The data a block proposer signs.
1029#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
1030pub struct ProposalContent {
1031    /// The proposed block.
1032    pub block: Block,
1033    /// The consensus round in which this proposal is made.
1034    pub round: Round,
1035    /// If this is a retry from an earlier round, the oracle responses from when the block was
1036    /// first validated. These are reused so the execution outcome remains the same.
1037    pub forced_oracle_responses: Option<Vec<Vec<OracleResponse>>>,
1038}
1039
1040impl BlockProposal {
1041    pub fn new_initial(round: Round, block: Block, secret: &KeyPair, blobs: Vec<Blob>) -> Self {
1042        let content = ProposalContent {
1043            round,
1044            block,
1045            forced_oracle_responses: None,
1046        };
1047        let signature = Signature::new(&content, secret);
1048        Self {
1049            content,
1050            owner: secret.public().into(),
1051            signature,
1052            blobs,
1053            validated_block_certificate: None,
1054        }
1055    }
1056
1057    pub fn new_retry(
1058        round: Round,
1059        validated_block_certificate: Certificate,
1060        secret: &KeyPair,
1061        blobs: Vec<Blob>,
1062    ) -> Self {
1063        let lite_cert = validated_block_certificate.lite_certificate().cloned();
1064        let CertificateValue::ValidatedBlock { executed_block } =
1065            validated_block_certificate.value.into_inner()
1066        else {
1067            panic!("called new_retry with a certificate without a validated block");
1068        };
1069        let content = ProposalContent {
1070            block: executed_block.block,
1071            round,
1072            forced_oracle_responses: Some(executed_block.outcome.oracle_responses),
1073        };
1074        let signature = Signature::new(&content, secret);
1075        Self {
1076            content,
1077            owner: secret.public().into(),
1078            signature,
1079            blobs,
1080            validated_block_certificate: Some(lite_cert),
1081        }
1082    }
1083
1084    pub fn check_signature(&self, public_key: PublicKey) -> Result<(), CryptoError> {
1085        self.signature.check(&self.content, public_key)
1086    }
1087}
1088
1089impl LiteVote {
1090    /// Uses the signing key to create a signed object.
1091    pub fn new(value: LiteValue, round: Round, key_pair: &KeyPair) -> Self {
1092        let hash_and_round = ValueHashAndRound(value.value_hash, round);
1093        let signature = Signature::new(&hash_and_round, key_pair);
1094        Self {
1095            value,
1096            round,
1097            validator: ValidatorName(key_pair.public()),
1098            signature,
1099        }
1100    }
1101
1102    /// Verifies the signature in the vote.
1103    pub fn check(&self) -> Result<(), ChainError> {
1104        let hash_and_round = ValueHashAndRound(self.value.value_hash, self.round);
1105        Ok(self.signature.check(&hash_and_round, self.validator.0)?)
1106    }
1107}
1108
1109pub struct SignatureAggregator<'a> {
1110    committee: &'a Committee,
1111    weight: u64,
1112    used_validators: HashSet<ValidatorName>,
1113    partial: Certificate,
1114}
1115
1116impl<'a> SignatureAggregator<'a> {
1117    /// Starts aggregating signatures for the given value into a certificate.
1118    pub fn new(value: HashedCertificateValue, round: Round, committee: &'a Committee) -> Self {
1119        Self {
1120            committee,
1121            weight: 0,
1122            used_validators: HashSet::new(),
1123            partial: Certificate {
1124                value,
1125                round,
1126                signatures: Vec::new(),
1127            },
1128        }
1129    }
1130
1131    /// Tries to append a signature to a (partial) certificate. Returns Some(certificate) if a
1132    /// quorum was reached. The resulting final certificate is guaranteed to be valid in the sense
1133    /// of `check` below. Returns an error if the signed value cannot be aggregated.
1134    pub fn append(
1135        &mut self,
1136        validator: ValidatorName,
1137        signature: Signature,
1138    ) -> Result<Option<Certificate>, ChainError> {
1139        let hash_and_round = ValueHashAndRound(self.partial.hash(), self.partial.round);
1140        signature.check(&hash_and_round, validator.0)?;
1141        // Check that each validator only appears once.
1142        ensure!(
1143            !self.used_validators.contains(&validator),
1144            ChainError::CertificateValidatorReuse
1145        );
1146        self.used_validators.insert(validator);
1147        // Update weight.
1148        let voting_rights = self.committee.weight(&validator);
1149        ensure!(voting_rights > 0, ChainError::InvalidSigner);
1150        self.weight += voting_rights;
1151        // Update certificate.
1152        self.partial.add_signature((validator, signature));
1153
1154        if self.weight >= self.committee.quorum_threshold() {
1155            self.weight = 0; // Prevent from creating the certificate twice.
1156            Ok(Some(self.partial.clone()))
1157        } else {
1158            Ok(None)
1159        }
1160    }
1161}
1162
1163// Checks if the array slice is strictly ordered. That means that if the array
1164// has duplicates, this will return False, even if the array is sorted
1165fn is_strictly_ordered(values: &[(ValidatorName, Signature)]) -> bool {
1166    values.windows(2).all(|pair| pair[0].0 < pair[1].0)
1167}
1168
1169impl<'de> Deserialize<'de> for Certificate {
1170    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1171    where
1172        D: Deserializer<'de>,
1173    {
1174        #[derive(Debug, Deserialize)]
1175        #[serde(rename = "Certificate")]
1176        struct CertificateHelper {
1177            value: HashedCertificateValue,
1178            round: Round,
1179            signatures: Vec<(ValidatorName, Signature)>,
1180        }
1181
1182        let helper: CertificateHelper = Deserialize::deserialize(deserializer)?;
1183        if !is_strictly_ordered(&helper.signatures) {
1184            Err(serde::de::Error::custom("Vector is not strictly sorted"))
1185        } else {
1186            Ok(Self {
1187                value: helper.value,
1188                round: helper.round,
1189                signatures: helper.signatures,
1190            })
1191        }
1192    }
1193}
1194
1195impl Certificate {
1196    pub fn new(
1197        value: HashedCertificateValue,
1198        round: Round,
1199        mut signatures: Vec<(ValidatorName, Signature)>,
1200    ) -> Self {
1201        signatures.sort_by_key(|&(validator_name, _)| validator_name);
1202
1203        Self {
1204            value,
1205            round,
1206            signatures,
1207        }
1208    }
1209
1210    pub fn signatures(&self) -> &Vec<(ValidatorName, Signature)> {
1211        &self.signatures
1212    }
1213
1214    // Adds a signature to the certificate's list of signatures
1215    // It's the responsibility of the caller to not insert duplicates
1216    pub fn add_signature(
1217        &mut self,
1218        signature: (ValidatorName, Signature),
1219    ) -> &Vec<(ValidatorName, Signature)> {
1220        let index = self
1221            .signatures
1222            .binary_search_by(|(name, _)| name.cmp(&signature.0))
1223            .unwrap_or_else(std::convert::identity);
1224        self.signatures.insert(index, signature);
1225        &self.signatures
1226    }
1227
1228    /// Verifies the certificate.
1229    pub fn check<'a>(
1230        &'a self,
1231        committee: &Committee,
1232    ) -> Result<&'a HashedCertificateValue, ChainError> {
1233        check_signatures(
1234            self.lite_value().value_hash,
1235            self.round,
1236            &self.signatures,
1237            committee,
1238        )?;
1239        Ok(&self.value)
1240    }
1241
1242    /// Returns the certificate without the full value.
1243    pub fn lite_certificate(&self) -> LiteCertificate<'_> {
1244        LiteCertificate {
1245            value: self.lite_value(),
1246            round: self.round,
1247            signatures: Cow::Borrowed(&self.signatures),
1248        }
1249    }
1250
1251    /// Returns the `LiteValue` corresponding to the certified value.
1252    pub fn lite_value(&self) -> LiteValue {
1253        LiteValue {
1254            value_hash: self.hash(),
1255            chain_id: self.value().chain_id(),
1256        }
1257    }
1258
1259    /// Returns the certified value.
1260    pub fn value(&self) -> &CertificateValue {
1261        &self.value.value
1262    }
1263
1264    /// Returns the certified value's hash.
1265    pub fn hash(&self) -> CryptoHash {
1266        self.value.hash
1267    }
1268
1269    /// Returns whether the validator is among the signatories of this certificate.
1270    pub fn is_signed_by(&self, validator_name: &ValidatorName) -> bool {
1271        self.signatures
1272            .binary_search_by(|(name, _)| name.cmp(validator_name))
1273            .is_ok()
1274    }
1275
1276    /// Returns the bundles of messages sent via the given medium to the specified
1277    /// recipient. Messages originating from different transactions of the original block
1278    /// are kept in separate bundles. If the medium is a channel, does not verify that the
1279    /// recipient is actually subscribed to that channel.
1280    pub fn message_bundles_for<'a>(
1281        &'a self,
1282        medium: &'a Medium,
1283        recipient: ChainId,
1284    ) -> impl Iterator<Item = (Epoch, MessageBundle)> + 'a {
1285        let certificate_hash = self.hash();
1286        self.value()
1287            .executed_block()
1288            .into_iter()
1289            .flat_map(move |executed_block| {
1290                executed_block.message_bundles_for(medium, recipient, certificate_hash)
1291            })
1292    }
1293
1294    pub fn requires_blob(&self, blob_id: &BlobId) -> bool {
1295        self.value()
1296            .executed_block()
1297            .is_some_and(|executed_block| executed_block.requires_blob(blob_id))
1298    }
1299
1300    #[cfg(with_testing)]
1301    pub fn outgoing_message_count(&self) -> usize {
1302        let Some(executed_block) = self.value().executed_block() else {
1303            return 0;
1304        };
1305        executed_block.messages().iter().map(Vec::len).sum()
1306    }
1307}
1308
1309/// Verifies certificate signatures.
1310fn check_signatures(
1311    value_hash: CryptoHash,
1312    round: Round,
1313    signatures: &[(ValidatorName, Signature)],
1314    committee: &Committee,
1315) -> Result<(), ChainError> {
1316    // Check the quorum.
1317    let mut weight = 0;
1318    let mut used_validators = HashSet::new();
1319    for (validator, _) in signatures {
1320        // Check that each validator only appears once.
1321        ensure!(
1322            !used_validators.contains(validator),
1323            ChainError::CertificateValidatorReuse
1324        );
1325        used_validators.insert(*validator);
1326        // Update weight.
1327        let voting_rights = committee.weight(validator);
1328        ensure!(voting_rights > 0, ChainError::InvalidSigner);
1329        weight += voting_rights;
1330    }
1331    ensure!(
1332        weight >= committee.quorum_threshold(),
1333        ChainError::CertificateRequiresQuorum
1334    );
1335    // All that is left is checking signatures!
1336    let hash_and_round = ValueHashAndRound(value_hash, round);
1337    Signature::verify_batch(&hash_and_round, signatures.iter().map(|(v, s)| (&v.0, s)))?;
1338    Ok(())
1339}
1340
1341impl BcsSignable for ProposalContent {}
1342
1343impl BcsSignable for ValueHashAndRound {}
1344
1345impl BcsHashable for CertificateValue {}
1346
1347doc_scalar!(
1348    MessageAction,
1349    "Whether an incoming message is accepted or rejected."
1350);
1351doc_scalar!(
1352    ChannelFullName,
1353    "A channel name together with its application ID."
1354);
1355doc_scalar!(
1356    Medium,
1357    "The origin of a message coming from a particular chain. Used to identify each inbox."
1358);
1359doc_scalar!(
1360    Origin,
1361    "The origin of a message, relative to a particular application. Used to identify each inbox."
1362);
1363doc_scalar!(
1364    Target,
1365    "The target of a message, relative to a particular application. Used to identify each outbox."
1366);