1use 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#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
38pub struct Block {
39 pub chain_id: ChainId,
41 pub epoch: Epoch,
43 pub incoming_bundles: Vec<IncomingBundle>,
46 pub operations: Vec<Operation>,
48 pub height: BlockHeight,
50 pub timestamp: Timestamp,
53 pub authenticated_signer: Option<Owner>,
58 pub previous_block_hash: Option<CryptoHash>,
61}
62
63impl Block {
64 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 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 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 pub fn message_count(&self) -> usize {
101 self.incoming_bundles
102 .iter()
103 .map(|im| im.bundle.messages.len())
104 .sum()
105 }
106
107 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 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#[derive(Debug, Clone)]
134pub enum Transaction<'a> {
135 ReceiveMessages(&'a IncomingBundle),
137 ExecuteOperation(&'a Operation),
139}
140
141#[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 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#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
161pub struct IncomingBundle {
162 pub origin: Origin,
164 pub bundle: MessageBundle,
166 pub action: MessageAction,
168}
169
170impl IncomingBundle {
171 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 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#[derive(Copy, Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
206pub enum MessageAction {
207 Accept,
209 Reject,
211}
212
213#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
215pub struct Origin {
216 pub sender: ChainId,
218 pub medium: Medium,
220}
221
222#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
224pub struct Target {
225 pub recipient: ChainId,
227 pub medium: Medium,
229}
230
231#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, SimpleObject)]
233pub struct MessageBundle {
234 pub height: BlockHeight,
236 pub timestamp: Timestamp,
238 pub certificate_hash: CryptoHash,
240 pub transaction_index: u32,
242 pub messages: Vec<PostedMessage>,
244}
245
246#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
247pub struct ChannelFullName {
249 pub application_id: GenericApplicationId,
251 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#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
267pub enum Medium {
268 Direct,
270 Channel(ChannelFullName),
272}
273
274#[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#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
289pub struct OutgoingMessage {
290 pub destination: Destination,
292 pub authenticated_signer: Option<Owner>,
294 pub grant: Amount,
296 pub refund_grant_to: Option<Account>,
298 pub kind: MessageKind,
300 pub message: Message,
302}
303
304#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
306pub struct PostedMessage {
307 pub authenticated_signer: Option<Owner>,
309 pub grant: Amount,
311 pub refund_grant_to: Option<Account>,
313 pub kind: MessageKind,
315 pub index: u32,
317 pub message: Message,
319}
320
321impl OutgoingMessage {
322 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 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#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
363pub struct ExecutedBlock {
364 pub block: Block,
365 pub outcome: BlockExecutionOutcome,
366}
367
368#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
370#[cfg_attr(with_testing, derive(Default))]
371pub struct BlockExecutionOutcome {
372 pub messages: Vec<Vec<OutgoingMessage>>,
374 pub state_hash: CryptoHash,
376 pub oracle_responses: Vec<Vec<OracleResponse>>,
378 pub events: Vec<Vec<EventRecord>>,
380}
381
382#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
384pub struct EventRecord {
385 pub stream_id: StreamId,
387 pub key: Vec<u8>,
389 pub value: Vec<u8>,
391}
392
393#[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#[derive(Debug, PartialEq, Eq, Hash, Clone)]
427pub struct HashedCertificateValue {
428 value: CertificateValue,
429 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#[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#[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 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 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 pub fn value(&self) -> &CertificateValue {
490 self.value.inner()
491 }
492}
493
494#[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 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#[derive(Clone, Debug, Serialize, Deserialize)]
521#[cfg_attr(with_testing, derive(Eq, PartialEq))]
522pub struct LiteCertificate<'a> {
523 pub value: LiteValue,
525 pub round: Round,
527 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 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 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 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 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#[derive(Clone, Debug, Serialize)]
603#[cfg_attr(with_testing, derive(Eq, PartialEq))]
604pub struct Certificate {
605 pub value: HashedCertificateValue,
607 pub round: Round,
609 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 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 pub fn with_hash(self) -> HashedCertificateValue {
728 let hash = CryptoHash::new(&self);
729 HashedCertificateValue { value: self, hash }
730 }
731
732 pub fn with_hash_unchecked(self, hash: CryptoHash) -> HashedCertificateValue {
734 HashedCertificateValue { value: self, hash }
735 }
736
737 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 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 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 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 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 pub fn new_confirmed(executed_block: ExecutedBlock) -> HashedCertificateValue {
973 CertificateValue::ConfirmedBlock { executed_block }.into()
974 }
975
976 pub fn new_validated(executed_block: ExecutedBlock) -> HashedCertificateValue {
978 CertificateValue::ValidatedBlock { executed_block }.into()
979 }
980
981 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 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#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
1030pub struct ProposalContent {
1031 pub block: Block,
1033 pub round: Round,
1035 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 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 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 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 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 ensure!(
1143 !self.used_validators.contains(&validator),
1144 ChainError::CertificateValidatorReuse
1145 );
1146 self.used_validators.insert(validator);
1147 let voting_rights = self.committee.weight(&validator);
1149 ensure!(voting_rights > 0, ChainError::InvalidSigner);
1150 self.weight += voting_rights;
1151 self.partial.add_signature((validator, signature));
1153
1154 if self.weight >= self.committee.quorum_threshold() {
1155 self.weight = 0; Ok(Some(self.partial.clone()))
1157 } else {
1158 Ok(None)
1159 }
1160 }
1161}
1162
1163fn 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 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 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 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 pub fn lite_value(&self) -> LiteValue {
1253 LiteValue {
1254 value_hash: self.hash(),
1255 chain_id: self.value().chain_id(),
1256 }
1257 }
1258
1259 pub fn value(&self) -> &CertificateValue {
1261 &self.value.value
1262 }
1263
1264 pub fn hash(&self) -> CryptoHash {
1266 self.value.hash
1267 }
1268
1269 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 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
1309fn check_signatures(
1311 value_hash: CryptoHash,
1312 round: Round,
1313 signatures: &[(ValidatorName, Signature)],
1314 committee: &Committee,
1315) -> Result<(), ChainError> {
1316 let mut weight = 0;
1318 let mut used_validators = HashSet::new();
1319 for (validator, _) in signatures {
1320 ensure!(
1322 !used_validators.contains(validator),
1323 ChainError::CertificateValidatorReuse
1324 );
1325 used_validators.insert(*validator);
1326 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 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);