1use std::{
6 collections::{BTreeMap, BTreeSet, HashSet},
7 fmt,
8};
9
10use async_graphql::SimpleObject;
11use custom_debug_derive::Debug;
12use linera_base::{
13 bcs,
14 crypto::{
15 AccountPublicKey, AccountSecretKey, AccountSignature, BcsHashable, BcsSignable,
16 CryptoError, CryptoHash, ValidatorPublicKey, ValidatorSecretKey, ValidatorSignature,
17 },
18 data_types::{Amount, Blob, BlockHeight, Event, OracleResponse, Round, Timestamp},
19 doc_scalar, ensure, hex_debug,
20 identifiers::{
21 Account, AccountOwner, BlobId, ChainId, ChannelFullName, Destination, GenericApplicationId,
22 MessageId,
23 },
24};
25use linera_execution::{
26 committee::{Committee, Epoch},
27 Message, MessageKind, Operation, OutgoingMessage, SystemMessage,
28};
29use serde::{Deserialize, Serialize};
30
31use crate::{
32 block::{Block, ValidatedBlock},
33 types::{
34 CertificateKind, CertificateValue, GenericCertificate, LiteCertificate,
35 ValidatedBlockCertificate,
36 },
37 ChainError,
38};
39
40#[cfg(test)]
41#[path = "unit_tests/data_types_tests.rs"]
42mod data_types_tests;
43
44#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
52pub struct ProposedBlock {
53 pub chain_id: ChainId,
55 pub epoch: Epoch,
57 #[debug(skip_if = Vec::is_empty)]
60 pub incoming_bundles: Vec<IncomingBundle>,
61 #[debug(skip_if = Vec::is_empty)]
63 pub operations: Vec<Operation>,
64 pub height: BlockHeight,
66 pub timestamp: Timestamp,
69 #[debug(skip_if = Option::is_none)]
74 pub authenticated_signer: Option<AccountOwner>,
75 pub previous_block_hash: Option<CryptoHash>,
78}
79
80impl ProposedBlock {
81 pub fn published_blob_ids(&self) -> BTreeSet<BlobId> {
83 self.operations
84 .iter()
85 .flat_map(Operation::published_blob_ids)
86 .collect()
87 }
88
89 pub fn has_only_rejected_messages(&self) -> bool {
92 self.operations.is_empty()
93 && self
94 .incoming_bundles
95 .iter()
96 .all(|message| message.action == MessageAction::Reject)
97 }
98
99 pub fn incoming_messages(&self) -> impl Iterator<Item = &PostedMessage> {
101 self.incoming_bundles
102 .iter()
103 .flat_map(|incoming_bundle| &incoming_bundle.bundle.messages)
104 }
105
106 pub fn message_count(&self) -> usize {
108 self.incoming_bundles
109 .iter()
110 .map(|im| im.bundle.messages.len())
111 .sum()
112 }
113
114 pub fn transactions(&self) -> impl Iterator<Item = (u32, Transaction<'_>)> {
116 let bundles = self
117 .incoming_bundles
118 .iter()
119 .map(Transaction::ReceiveMessages);
120 let operations = self.operations.iter().map(Transaction::ExecuteOperation);
121 (0u32..).zip(bundles.chain(operations))
122 }
123
124 pub fn check_proposal_size(&self, maximum_block_proposal_size: u64) -> Result<(), ChainError> {
125 let size = bcs::serialized_size(self)?;
126 ensure!(
127 size <= usize::try_from(maximum_block_proposal_size).unwrap_or(usize::MAX),
128 ChainError::BlockProposalTooLarge
129 );
130 Ok(())
131 }
132
133 pub fn message_id(&self, index: u32) -> MessageId {
135 MessageId {
136 chain_id: self.chain_id,
137 height: self.height,
138 index,
139 }
140 }
141}
142
143#[derive(Debug, Clone)]
145pub enum Transaction<'a> {
146 ReceiveMessages(&'a IncomingBundle),
148 ExecuteOperation(&'a Operation),
150}
151
152#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, SimpleObject)]
154pub struct ChainAndHeight {
155 pub chain_id: ChainId,
156 pub height: BlockHeight,
157}
158
159impl ChainAndHeight {
160 pub fn to_message_id(&self, index: u32) -> MessageId {
162 MessageId {
163 chain_id: self.chain_id,
164 height: self.height,
165 index,
166 }
167 }
168}
169
170#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
172pub struct IncomingBundle {
173 pub origin: Origin,
175 pub bundle: MessageBundle,
177 pub action: MessageAction,
179}
180
181impl IncomingBundle {
182 pub fn messages_and_ids(&self) -> impl Iterator<Item = (MessageId, &PostedMessage)> {
184 let chain_and_height = ChainAndHeight {
185 chain_id: self.origin.sender,
186 height: self.bundle.height,
187 };
188 let messages = self.bundle.messages.iter();
189 messages.map(move |posted_message| {
190 let message_id = chain_and_height.to_message_id(posted_message.index);
191 (message_id, posted_message)
192 })
193 }
194
195 pub fn put_openchain_at_front(bundles: &mut [IncomingBundle]) -> bool {
198 let Some(index) = bundles.iter().position(|msg| {
199 matches!(
200 msg.bundle.messages.first(),
201 Some(PostedMessage {
202 message: Message::System(SystemMessage::OpenChain(_)),
203 ..
204 })
205 )
206 }) else {
207 return false;
208 };
209
210 bundles[0..=index].rotate_right(1);
211 true
212 }
213}
214
215impl BcsHashable<'_> for IncomingBundle {}
216
217#[derive(Copy, Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
219pub enum MessageAction {
220 Accept,
222 Reject,
224}
225
226#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
228pub struct Origin {
229 pub sender: ChainId,
231 pub medium: Medium,
233}
234
235#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
237pub struct Target {
238 pub recipient: ChainId,
240 pub medium: Medium,
242}
243
244#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, SimpleObject)]
246pub struct MessageBundle {
247 pub height: BlockHeight,
249 pub timestamp: Timestamp,
251 pub certificate_hash: CryptoHash,
253 pub transaction_index: u32,
255 pub messages: Vec<PostedMessage>,
257}
258
259#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
261pub enum Medium {
262 Direct,
264 Channel(ChannelFullName),
266}
267
268#[derive(Clone, Debug, Serialize, Deserialize)]
272#[cfg_attr(with_testing, derive(Eq, PartialEq))]
273pub struct BlockProposal {
274 pub content: ProposalContent,
275 pub public_key: AccountPublicKey,
276 pub signature: AccountSignature,
277 #[debug(skip_if = Option::is_none)]
278 pub validated_block_certificate: Option<LiteCertificate<'static>>,
279}
280
281#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
283pub struct PostedMessage {
284 #[debug(skip_if = Option::is_none)]
286 pub authenticated_signer: Option<AccountOwner>,
287 #[debug(skip_if = Amount::is_zero)]
289 pub grant: Amount,
290 #[debug(skip_if = Option::is_none)]
292 pub refund_grant_to: Option<Account>,
293 pub kind: MessageKind,
295 pub index: u32,
297 pub message: Message,
299}
300
301pub trait OutgoingMessageExt {
302 fn has_destination(&self, medium: &Medium, recipient: ChainId) -> bool;
306
307 fn into_posted(self, index: u32) -> PostedMessage;
309}
310
311impl OutgoingMessageExt for OutgoingMessage {
312 fn has_destination(&self, medium: &Medium, recipient: ChainId) -> bool {
316 match (&self.destination, medium) {
317 (Destination::Recipient(_), Medium::Channel(_))
318 | (Destination::Subscribers(_), Medium::Direct) => false,
319 (Destination::Recipient(id), Medium::Direct) => *id == recipient,
320 (
321 Destination::Subscribers(dest_name),
322 Medium::Channel(ChannelFullName {
323 application_id,
324 name,
325 }),
326 ) => {
327 GenericApplicationId::User(*application_id) == self.message.application_id()
328 && name == dest_name
329 }
330 }
331 }
332
333 fn into_posted(self, index: u32) -> PostedMessage {
335 let OutgoingMessage {
336 destination: _,
337 authenticated_signer,
338 grant,
339 refund_grant_to,
340 kind,
341 message,
342 } = self;
343 PostedMessage {
344 authenticated_signer,
345 grant,
346 refund_grant_to,
347 kind,
348 index,
349 message,
350 }
351 }
352}
353
354#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
356pub struct OperationResult(
357 #[debug(with = "hex_debug")]
358 #[serde(with = "serde_bytes")]
359 pub Vec<u8>,
360);
361
362impl BcsHashable<'_> for OperationResult {}
363
364doc_scalar!(
365 OperationResult,
366 "The execution result of a single operation."
367);
368
369#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
371#[cfg_attr(with_testing, derive(Default))]
372pub struct BlockExecutionOutcome {
373 pub messages: Vec<Vec<OutgoingMessage>>,
375 pub previous_message_blocks: BTreeMap<ChainId, CryptoHash>,
377 pub state_hash: CryptoHash,
379 pub oracle_responses: Vec<Vec<OracleResponse>>,
381 pub events: Vec<Vec<Event>>,
383 pub blobs: Vec<Vec<Blob>>,
385 pub operation_results: Vec<OperationResult>,
387}
388
389#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
391pub struct LiteValue {
392 pub value_hash: CryptoHash,
393 pub chain_id: ChainId,
394 pub kind: CertificateKind,
395}
396
397impl LiteValue {
398 pub fn new<T: CertificateValue>(value: &T) -> Self {
399 LiteValue {
400 value_hash: value.hash(),
401 chain_id: value.chain_id(),
402 kind: T::KIND,
403 }
404 }
405}
406
407#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
408struct VoteValue(CryptoHash, Round, CertificateKind);
409
410#[derive(Clone, Debug, Serialize, Deserialize)]
412#[serde(bound(deserialize = "T: Deserialize<'de>"))]
413pub struct Vote<T> {
414 pub value: T,
415 pub round: Round,
416 pub public_key: ValidatorPublicKey,
417 pub signature: ValidatorSignature,
418}
419
420impl<T> Vote<T> {
421 pub fn new(value: T, round: Round, key_pair: &ValidatorSecretKey) -> Self
423 where
424 T: CertificateValue,
425 {
426 let hash_and_round = VoteValue(value.hash(), round, T::KIND);
427 let signature = ValidatorSignature::new(&hash_and_round, key_pair);
428 Self {
429 value,
430 round,
431 public_key: key_pair.public(),
432 signature,
433 }
434 }
435
436 pub fn lite(&self) -> LiteVote
438 where
439 T: CertificateValue,
440 {
441 LiteVote {
442 value: LiteValue::new(&self.value),
443 round: self.round,
444 public_key: self.public_key,
445 signature: self.signature,
446 }
447 }
448
449 pub fn value(&self) -> &T {
451 &self.value
452 }
453}
454
455#[derive(Clone, Debug, Serialize, Deserialize)]
457#[cfg_attr(with_testing, derive(Eq, PartialEq))]
458pub struct LiteVote {
459 pub value: LiteValue,
460 pub round: Round,
461 pub public_key: ValidatorPublicKey,
462 pub signature: ValidatorSignature,
463}
464
465impl LiteVote {
466 #[cfg(any(feature = "benchmark", with_testing))]
468 pub fn with_value<T: CertificateValue>(self, value: T) -> Option<Vote<T>> {
469 if self.value.value_hash != value.hash() {
470 return None;
471 }
472 Some(Vote {
473 value,
474 round: self.round,
475 public_key: self.public_key,
476 signature: self.signature,
477 })
478 }
479
480 pub fn kind(&self) -> CertificateKind {
481 self.value.kind
482 }
483}
484
485impl fmt::Display for Origin {
486 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487 match &self.medium {
488 Medium::Direct => write!(f, "{:.8} (direct)", self.sender),
489 Medium::Channel(full_name) => write!(f, "{:.8} via {full_name:.8}", self.sender),
490 }
491 }
492}
493
494impl Origin {
495 pub fn chain(sender: ChainId) -> Self {
496 Self {
497 sender,
498 medium: Medium::Direct,
499 }
500 }
501
502 pub fn channel(sender: ChainId, name: ChannelFullName) -> Self {
503 Self {
504 sender,
505 medium: Medium::Channel(name),
506 }
507 }
508}
509
510impl Target {
511 pub fn chain(recipient: ChainId) -> Self {
512 Self {
513 recipient,
514 medium: Medium::Direct,
515 }
516 }
517
518 pub fn channel(recipient: ChainId, name: ChannelFullName) -> Self {
519 Self {
520 recipient,
521 medium: Medium::Channel(name),
522 }
523 }
524}
525
526impl MessageBundle {
527 pub fn is_skippable(&self) -> bool {
528 self.messages.iter().all(PostedMessage::is_skippable)
529 }
530
531 pub fn is_tracked(&self) -> bool {
532 let mut tracked = false;
533 for posted_message in &self.messages {
534 match posted_message.kind {
535 MessageKind::Simple | MessageKind::Bouncing => {}
536 MessageKind::Protected => return false,
537 MessageKind::Tracked => tracked = true,
538 }
539 }
540 tracked
541 }
542
543 pub fn is_protected(&self) -> bool {
544 self.messages.iter().any(PostedMessage::is_protected)
545 }
546}
547
548impl PostedMessage {
549 pub fn is_skippable(&self) -> bool {
550 match self.kind {
551 MessageKind::Protected | MessageKind::Tracked => false,
552 MessageKind::Simple | MessageKind::Bouncing => self.grant == Amount::ZERO,
553 }
554 }
555
556 pub fn is_protected(&self) -> bool {
557 matches!(self.kind, MessageKind::Protected)
558 }
559
560 pub fn is_tracked(&self) -> bool {
561 matches!(self.kind, MessageKind::Tracked)
562 }
563
564 pub fn is_bouncing(&self) -> bool {
565 matches!(self.kind, MessageKind::Bouncing)
566 }
567}
568
569impl BlockExecutionOutcome {
570 pub fn with(self, block: ProposedBlock) -> Block {
571 Block::new(block, self)
572 }
573
574 pub fn oracle_blob_ids(&self) -> HashSet<BlobId> {
575 let mut required_blob_ids = HashSet::new();
576 for responses in &self.oracle_responses {
577 for response in responses {
578 if let OracleResponse::Blob(blob_id) = response {
579 required_blob_ids.insert(*blob_id);
580 }
581 }
582 }
583
584 required_blob_ids
585 }
586
587 pub fn has_oracle_responses(&self) -> bool {
588 self.oracle_responses
589 .iter()
590 .any(|responses| !responses.is_empty())
591 }
592
593 pub fn iter_created_blobs_ids(&self) -> impl Iterator<Item = BlobId> + '_ {
594 self.blobs.iter().flatten().map(|blob| blob.id())
595 }
596
597 pub fn created_blobs_ids(&self) -> HashSet<BlobId> {
598 self.iter_created_blobs_ids().collect()
599 }
600}
601
602#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
604pub struct ProposalContent {
605 pub block: ProposedBlock,
607 pub round: Round,
609 #[debug(skip_if = Option::is_none)]
611 pub outcome: Option<BlockExecutionOutcome>,
612}
613
614impl BlockProposal {
615 pub fn new_initial(round: Round, block: ProposedBlock, secret: &AccountSecretKey) -> Self {
616 let content = ProposalContent {
617 round,
618 block,
619 outcome: None,
620 };
621 let signature = secret.sign(&content);
622 Self {
623 content,
624 public_key: secret.public(),
625 signature,
626 validated_block_certificate: None,
627 }
628 }
629
630 pub fn new_retry(
631 round: Round,
632 validated_block_certificate: ValidatedBlockCertificate,
633 secret: &AccountSecretKey,
634 ) -> Self {
635 let lite_cert = validated_block_certificate.lite_certificate().cloned();
636 let block = validated_block_certificate.into_inner().into_inner();
637 let (block, outcome) = block.into_proposal();
638 let content = ProposalContent {
639 block,
640 round,
641 outcome: Some(outcome),
642 };
643 let signature = secret.sign(&content);
644 Self {
645 content,
646 public_key: secret.public(),
647 signature,
648 validated_block_certificate: Some(lite_cert),
649 }
650 }
651
652 pub fn check_signature(&self) -> Result<(), CryptoError> {
653 self.signature.verify(&self.content, self.public_key)
654 }
655
656 pub fn required_blob_ids(&self) -> impl Iterator<Item = BlobId> + '_ {
657 self.content.block.published_blob_ids().into_iter().chain(
658 self.content
659 .outcome
660 .iter()
661 .flat_map(|outcome| outcome.oracle_blob_ids()),
662 )
663 }
664
665 pub fn expected_blob_ids(&self) -> impl Iterator<Item = BlobId> + '_ {
666 self.content.block.published_blob_ids().into_iter().chain(
667 self.content.outcome.iter().flat_map(|outcome| {
668 outcome
669 .oracle_blob_ids()
670 .into_iter()
671 .chain(outcome.iter_created_blobs_ids())
672 }),
673 )
674 }
675
676 pub fn check_invariants(&self) -> Result<(), &'static str> {
679 match (&self.validated_block_certificate, &self.content.outcome) {
680 (None, None) => {}
681 (None, Some(_)) | (Some(_), None) => {
682 return Err("Must contain a validation certificate if and only if \
683 it contains the execution outcome from a previous round");
684 }
685 (Some(lite_certificate), Some(outcome)) => {
686 let block = outcome.clone().with(self.content.block.clone());
687 let value = ValidatedBlock::new(block);
688 ensure!(
689 lite_certificate.check_value(&value),
690 "Lite certificate must match the given block and execution outcome"
691 );
692 }
693 }
694 Ok(())
695 }
696}
697
698impl LiteVote {
699 pub fn new(value: LiteValue, round: Round, secret_key: &ValidatorSecretKey) -> Self {
701 let hash_and_round = VoteValue(value.value_hash, round, value.kind);
702 let signature = ValidatorSignature::new(&hash_and_round, secret_key);
703 Self {
704 value,
705 round,
706 public_key: secret_key.public(),
707 signature,
708 }
709 }
710
711 pub fn check(&self) -> Result<(), ChainError> {
713 let hash_and_round = VoteValue(self.value.value_hash, self.round, self.value.kind);
714 Ok(self.signature.check(&hash_and_round, &self.public_key)?)
715 }
716}
717
718pub struct SignatureAggregator<'a, T: CertificateValue> {
719 committee: &'a Committee,
720 weight: u64,
721 used_validators: HashSet<ValidatorPublicKey>,
722 partial: GenericCertificate<T>,
723}
724
725impl<'a, T: CertificateValue> SignatureAggregator<'a, T> {
726 pub fn new(value: T, round: Round, committee: &'a Committee) -> Self {
728 Self {
729 committee,
730 weight: 0,
731 used_validators: HashSet::new(),
732 partial: GenericCertificate::new(value, round, Vec::new()),
733 }
734 }
735
736 pub fn append(
740 &mut self,
741 public_key: ValidatorPublicKey,
742 signature: ValidatorSignature,
743 ) -> Result<Option<GenericCertificate<T>>, ChainError>
744 where
745 T: CertificateValue,
746 {
747 let hash_and_round = VoteValue(self.partial.hash(), self.partial.round, T::KIND);
748 signature.check(&hash_and_round, &public_key)?;
749 ensure!(
751 !self.used_validators.contains(&public_key),
752 ChainError::CertificateValidatorReuse
753 );
754 self.used_validators.insert(public_key);
755 let voting_rights = self.committee.weight(&public_key);
757 ensure!(voting_rights > 0, ChainError::InvalidSigner);
758 self.weight += voting_rights;
759 self.partial.add_signature((public_key, signature));
761
762 if self.weight >= self.committee.quorum_threshold() {
763 self.weight = 0; Ok(Some(self.partial.clone()))
765 } else {
766 Ok(None)
767 }
768 }
769}
770
771pub(crate) fn is_strictly_ordered(values: &[(ValidatorPublicKey, ValidatorSignature)]) -> bool {
774 values.windows(2).all(|pair| pair[0].0 < pair[1].0)
775}
776
777pub(crate) fn check_signatures(
779 value_hash: CryptoHash,
780 certificate_kind: CertificateKind,
781 round: Round,
782 signatures: &[(ValidatorPublicKey, ValidatorSignature)],
783 committee: &Committee,
784) -> Result<(), ChainError> {
785 let mut weight = 0;
787 let mut used_validators = HashSet::new();
788 for (validator, _) in signatures {
789 ensure!(
791 !used_validators.contains(validator),
792 ChainError::CertificateValidatorReuse
793 );
794 used_validators.insert(*validator);
795 let voting_rights = committee.weight(validator);
797 ensure!(voting_rights > 0, ChainError::InvalidSigner);
798 weight += voting_rights;
799 }
800 ensure!(
801 weight >= committee.quorum_threshold(),
802 ChainError::CertificateRequiresQuorum
803 );
804 let hash_and_round = VoteValue(value_hash, round, certificate_kind);
806 ValidatorSignature::verify_batch(&hash_and_round, signatures.iter())?;
807 Ok(())
808}
809
810impl BcsSignable<'_> for ProposalContent {}
811
812impl BcsSignable<'_> for VoteValue {}
813
814doc_scalar!(
815 MessageAction,
816 "Whether an incoming message is accepted or rejected."
817);
818doc_scalar!(
819 Medium,
820 "The origin of a message coming from a particular chain. Used to identify each inbox."
821);
822doc_scalar!(
823 Origin,
824 "The origin of a message, relative to a particular application. Used to identify each inbox."
825);
826doc_scalar!(
827 Target,
828 "The target of a message, relative to a particular application. Used to identify each outbox."
829);
830
831#[cfg(test)]
832mod signing {
833 use linera_base::{
834 crypto::{AccountSecretKey, AccountSignature, CryptoHash, EvmSignature, TestString},
835 data_types::{BlockHeight, Round},
836 identifiers::ChainId,
837 };
838 use linera_execution::committee::Epoch;
839
840 use crate::data_types::{ProposalContent, ProposedBlock};
841
842 #[test]
843 fn proposal_content_singing() {
844 use std::str::FromStr;
845
846 let pk = "f77a21701522a03b01c111ad2d2cdaf2b8403b47507ee0aec3c2e52b765d7a66";
848
849 let signer: AccountSecretKey = AccountSecretKey::EvmSecp256k1(
850 linera_base::crypto::EvmSecretKey::from_str(pk).unwrap(),
851 );
852
853 let proposed_block = ProposedBlock {
854 chain_id: ChainId(CryptoHash::new(&TestString::new("ChainId"))),
855 epoch: Epoch(11),
856 incoming_bundles: vec![],
857 operations: vec![],
858 height: BlockHeight(11),
859 timestamp: 190000000u64.into(),
860 authenticated_signer: None,
861 previous_block_hash: None,
862 };
863
864 let proposal = ProposalContent {
865 block: proposed_block,
866 round: Round::SingleLeader(11),
867 outcome: None,
868 };
869
870 let metamask_signature = AccountSignature::EvmSecp256k1(EvmSignature::from_str("f2d8afcd51d0f947f5c5e31ac1db73ec5306163af7949b3bb265ba53d03374b04b1e909007b555caf098da1aded29c600bee391c6ee8b4d0962a29044555796d1b").unwrap());
873
874 let signature = signer.sign(&proposal);
875 assert_eq!(signature, metamask_signature);
876 }
877}