1use std::collections::{BTreeMap, BTreeSet, HashSet};
6
7use async_graphql::SimpleObject;
8use custom_debug_derive::Debug;
9use linera_base::{
10 bcs,
11 crypto::{
12 AccountSignature, BcsHashable, BcsSignable, CryptoError, CryptoHash, Signer,
13 ValidatorPublicKey, ValidatorSecretKey, ValidatorSignature,
14 },
15 data_types::{Amount, Blob, BlockHeight, Epoch, Event, OracleResponse, Round, Timestamp},
16 doc_scalar, ensure, hex_debug,
17 identifiers::{Account, AccountOwner, BlobId, ChainId, MessageId},
18};
19use linera_execution::{committee::Committee, Message, MessageKind, Operation, OutgoingMessage};
20use serde::{Deserialize, Serialize};
21
22use crate::{
23 block::{Block, ValidatedBlock},
24 types::{
25 CertificateKind, CertificateValue, GenericCertificate, LiteCertificate,
26 ValidatedBlockCertificate,
27 },
28 ChainError,
29};
30
31#[cfg(test)]
32#[path = "unit_tests/data_types_tests.rs"]
33mod data_types_tests;
34
35#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
43pub struct ProposedBlock {
44 pub chain_id: ChainId,
46 pub epoch: Epoch,
48 #[debug(skip_if = Vec::is_empty)]
51 pub incoming_bundles: Vec<IncomingBundle>,
52 #[debug(skip_if = Vec::is_empty)]
54 pub operations: Vec<Operation>,
55 pub height: BlockHeight,
57 pub timestamp: Timestamp,
60 #[debug(skip_if = Option::is_none)]
65 pub authenticated_signer: Option<AccountOwner>,
66 pub previous_block_hash: Option<CryptoHash>,
69}
70
71impl ProposedBlock {
72 pub fn published_blob_ids(&self) -> BTreeSet<BlobId> {
74 self.operations
75 .iter()
76 .flat_map(Operation::published_blob_ids)
77 .collect()
78 }
79
80 pub fn has_only_rejected_messages(&self) -> bool {
83 self.operations.is_empty()
84 && self
85 .incoming_bundles
86 .iter()
87 .all(|message| message.action == MessageAction::Reject)
88 }
89
90 pub fn incoming_messages(&self) -> impl Iterator<Item = &PostedMessage> {
92 self.incoming_bundles
93 .iter()
94 .flat_map(|incoming_bundle| &incoming_bundle.bundle.messages)
95 }
96
97 pub fn message_count(&self) -> usize {
99 self.incoming_bundles
100 .iter()
101 .map(|im| im.bundle.messages.len())
102 .sum()
103 }
104
105 pub fn transactions(&self) -> impl Iterator<Item = Transaction<'_>> {
109 let bundles = self
110 .incoming_bundles
111 .iter()
112 .map(Transaction::ReceiveMessages);
113 let operations = self.operations.iter().map(Transaction::ExecuteOperation);
114 bundles.chain(operations)
115 }
116
117 pub fn check_proposal_size(&self, maximum_block_proposal_size: u64) -> Result<(), ChainError> {
118 let size = bcs::serialized_size(self)?;
119 ensure!(
120 size <= usize::try_from(maximum_block_proposal_size).unwrap_or(usize::MAX),
121 ChainError::BlockProposalTooLarge(size)
122 );
123 Ok(())
124 }
125}
126
127#[derive(Debug, Clone)]
129pub enum Transaction<'a> {
130 ReceiveMessages(&'a IncomingBundle),
132 ExecuteOperation(&'a Operation),
134}
135
136#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, SimpleObject)]
138pub struct ChainAndHeight {
139 pub chain_id: ChainId,
140 pub height: BlockHeight,
141}
142
143impl ChainAndHeight {
144 pub fn to_message_id(&self, index: u32) -> MessageId {
146 MessageId {
147 chain_id: self.chain_id,
148 height: self.height,
149 index,
150 }
151 }
152}
153
154#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
156pub struct IncomingBundle {
157 pub origin: ChainId,
159 pub bundle: MessageBundle,
161 pub action: MessageAction,
163}
164
165impl IncomingBundle {
166 pub fn messages_and_ids(&self) -> impl Iterator<Item = (MessageId, &PostedMessage)> {
168 let chain_and_height = ChainAndHeight {
169 chain_id: self.origin,
170 height: self.bundle.height,
171 };
172 let messages = self.bundle.messages.iter();
173 messages.map(move |posted_message| {
174 let message_id = chain_and_height.to_message_id(posted_message.index);
175 (message_id, posted_message)
176 })
177 }
178}
179
180impl BcsHashable<'_> for IncomingBundle {}
181
182#[derive(Copy, Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
184pub enum MessageAction {
185 Accept,
187 Reject,
189}
190
191#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, SimpleObject)]
193pub struct MessageBundle {
194 pub height: BlockHeight,
196 pub timestamp: Timestamp,
198 pub certificate_hash: CryptoHash,
200 pub transaction_index: u32,
202 pub messages: Vec<PostedMessage>,
204}
205
206#[derive(Clone, Debug, Serialize, Deserialize)]
207#[cfg_attr(with_testing, derive(Eq, PartialEq))]
208pub enum OriginalProposal {
210 Fast(AccountSignature),
212 Regular {
214 certificate: LiteCertificate<'static>,
215 },
216}
217
218#[derive(Clone, Debug, Serialize, Deserialize)]
222#[cfg_attr(with_testing, derive(Eq, PartialEq))]
223pub struct BlockProposal {
224 pub content: ProposalContent,
225 pub signature: AccountSignature,
226 #[debug(skip_if = Option::is_none)]
227 pub original_proposal: Option<OriginalProposal>,
228}
229
230#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
232pub struct PostedMessage {
233 #[debug(skip_if = Option::is_none)]
235 pub authenticated_signer: Option<AccountOwner>,
236 #[debug(skip_if = Amount::is_zero)]
238 pub grant: Amount,
239 #[debug(skip_if = Option::is_none)]
241 pub refund_grant_to: Option<Account>,
242 pub kind: MessageKind,
244 pub index: u32,
246 pub message: Message,
248}
249
250pub trait OutgoingMessageExt {
251 fn into_posted(self, index: u32) -> PostedMessage;
253}
254
255impl OutgoingMessageExt for OutgoingMessage {
256 fn into_posted(self, index: u32) -> PostedMessage {
258 let OutgoingMessage {
259 destination: _,
260 authenticated_signer,
261 grant,
262 refund_grant_to,
263 kind,
264 message,
265 } = self;
266 PostedMessage {
267 authenticated_signer,
268 grant,
269 refund_grant_to,
270 kind,
271 index,
272 message,
273 }
274 }
275}
276
277#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
279pub struct OperationResult(
280 #[debug(with = "hex_debug")]
281 #[serde(with = "serde_bytes")]
282 pub Vec<u8>,
283);
284
285impl BcsHashable<'_> for OperationResult {}
286
287doc_scalar!(
288 OperationResult,
289 "The execution result of a single operation."
290);
291
292#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
294#[cfg_attr(with_testing, derive(Default))]
295pub struct BlockExecutionOutcome {
296 pub messages: Vec<Vec<OutgoingMessage>>,
298 pub previous_message_blocks: BTreeMap<ChainId, (CryptoHash, BlockHeight)>,
300 pub state_hash: CryptoHash,
302 pub oracle_responses: Vec<Vec<OracleResponse>>,
304 pub events: Vec<Vec<Event>>,
306 pub blobs: Vec<Vec<Blob>>,
308 pub operation_results: Vec<OperationResult>,
310}
311
312#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
314pub struct LiteValue {
315 pub value_hash: CryptoHash,
316 pub chain_id: ChainId,
317 pub kind: CertificateKind,
318}
319
320impl LiteValue {
321 pub fn new<T: CertificateValue>(value: &T) -> Self {
322 LiteValue {
323 value_hash: value.hash(),
324 chain_id: value.chain_id(),
325 kind: T::KIND,
326 }
327 }
328}
329
330#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
331struct VoteValue(CryptoHash, Round, CertificateKind);
332
333#[derive(Clone, Debug, Serialize, Deserialize)]
335#[serde(bound(deserialize = "T: Deserialize<'de>"))]
336pub struct Vote<T> {
337 pub value: T,
338 pub round: Round,
339 pub public_key: ValidatorPublicKey,
340 pub signature: ValidatorSignature,
341}
342
343impl<T> Vote<T> {
344 pub fn new(value: T, round: Round, key_pair: &ValidatorSecretKey) -> Self
346 where
347 T: CertificateValue,
348 {
349 let hash_and_round = VoteValue(value.hash(), round, T::KIND);
350 let signature = ValidatorSignature::new(&hash_and_round, key_pair);
351 Self {
352 value,
353 round,
354 public_key: key_pair.public(),
355 signature,
356 }
357 }
358
359 pub fn lite(&self) -> LiteVote
361 where
362 T: CertificateValue,
363 {
364 LiteVote {
365 value: LiteValue::new(&self.value),
366 round: self.round,
367 public_key: self.public_key,
368 signature: self.signature,
369 }
370 }
371
372 pub fn value(&self) -> &T {
374 &self.value
375 }
376}
377
378#[derive(Clone, Debug, Serialize, Deserialize)]
380#[cfg_attr(with_testing, derive(Eq, PartialEq))]
381pub struct LiteVote {
382 pub value: LiteValue,
383 pub round: Round,
384 pub public_key: ValidatorPublicKey,
385 pub signature: ValidatorSignature,
386}
387
388impl LiteVote {
389 #[cfg(any(feature = "benchmark", with_testing))]
391 pub fn with_value<T: CertificateValue>(self, value: T) -> Option<Vote<T>> {
392 if self.value.value_hash != value.hash() {
393 return None;
394 }
395 Some(Vote {
396 value,
397 round: self.round,
398 public_key: self.public_key,
399 signature: self.signature,
400 })
401 }
402
403 pub fn kind(&self) -> CertificateKind {
404 self.value.kind
405 }
406}
407
408impl MessageBundle {
409 pub fn is_skippable(&self) -> bool {
410 self.messages.iter().all(PostedMessage::is_skippable)
411 }
412
413 pub fn is_tracked(&self) -> bool {
414 let mut tracked = false;
415 for posted_message in &self.messages {
416 match posted_message.kind {
417 MessageKind::Simple | MessageKind::Bouncing => {}
418 MessageKind::Protected => return false,
419 MessageKind::Tracked => tracked = true,
420 }
421 }
422 tracked
423 }
424
425 pub fn is_protected(&self) -> bool {
426 self.messages.iter().any(PostedMessage::is_protected)
427 }
428}
429
430impl PostedMessage {
431 pub fn is_skippable(&self) -> bool {
432 match self.kind {
433 MessageKind::Protected | MessageKind::Tracked => false,
434 MessageKind::Simple | MessageKind::Bouncing => self.grant == Amount::ZERO,
435 }
436 }
437
438 pub fn is_protected(&self) -> bool {
439 matches!(self.kind, MessageKind::Protected)
440 }
441
442 pub fn is_tracked(&self) -> bool {
443 matches!(self.kind, MessageKind::Tracked)
444 }
445
446 pub fn is_bouncing(&self) -> bool {
447 matches!(self.kind, MessageKind::Bouncing)
448 }
449}
450
451impl BlockExecutionOutcome {
452 pub fn with(self, block: ProposedBlock) -> Block {
453 Block::new(block, self)
454 }
455
456 pub fn oracle_blob_ids(&self) -> HashSet<BlobId> {
457 let mut required_blob_ids = HashSet::new();
458 for responses in &self.oracle_responses {
459 for response in responses {
460 if let OracleResponse::Blob(blob_id) = response {
461 required_blob_ids.insert(*blob_id);
462 }
463 }
464 }
465
466 required_blob_ids
467 }
468
469 pub fn has_oracle_responses(&self) -> bool {
470 self.oracle_responses
471 .iter()
472 .any(|responses| !responses.is_empty())
473 }
474
475 pub fn iter_created_blobs_ids(&self) -> impl Iterator<Item = BlobId> + '_ {
476 self.blobs.iter().flatten().map(|blob| blob.id())
477 }
478
479 pub fn created_blobs_ids(&self) -> HashSet<BlobId> {
480 self.iter_created_blobs_ids().collect()
481 }
482}
483
484#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
486pub struct ProposalContent {
487 pub block: ProposedBlock,
489 pub round: Round,
491 #[debug(skip_if = Option::is_none)]
493 pub outcome: Option<BlockExecutionOutcome>,
494}
495
496impl BlockProposal {
497 pub async fn new_initial<S: Signer + ?Sized>(
498 owner: AccountOwner,
499 round: Round,
500 block: ProposedBlock,
501 signer: &S,
502 ) -> Result<Self, S::Error> {
503 let content = ProposalContent {
504 round,
505 block,
506 outcome: None,
507 };
508 let signature = signer.sign(&owner, &CryptoHash::new(&content)).await?;
509
510 Ok(Self {
511 content,
512 signature,
513 original_proposal: None,
514 })
515 }
516
517 pub async fn new_retry_fast<S: Signer + ?Sized>(
518 owner: AccountOwner,
519 round: Round,
520 old_proposal: BlockProposal,
521 signer: &S,
522 ) -> Result<Self, S::Error> {
523 let content = ProposalContent {
524 round,
525 block: old_proposal.content.block,
526 outcome: None,
527 };
528 let signature = signer.sign(&owner, &CryptoHash::new(&content)).await?;
529
530 Ok(Self {
531 content,
532 signature,
533 original_proposal: Some(OriginalProposal::Fast(old_proposal.signature)),
534 })
535 }
536
537 pub async fn new_retry_regular<S: Signer>(
538 owner: AccountOwner,
539 round: Round,
540 validated_block_certificate: ValidatedBlockCertificate,
541 signer: &S,
542 ) -> Result<Self, S::Error> {
543 let certificate = validated_block_certificate.lite_certificate().cloned();
544 let block = validated_block_certificate.into_inner().into_inner();
545 let (block, outcome) = block.into_proposal();
546 let content = ProposalContent {
547 block,
548 round,
549 outcome: Some(outcome),
550 };
551 let signature = signer.sign(&owner, &CryptoHash::new(&content)).await?;
552
553 Ok(Self {
554 content,
555 signature,
556 original_proposal: Some(OriginalProposal::Regular { certificate }),
557 })
558 }
559
560 pub fn owner(&self) -> AccountOwner {
562 match self.signature {
563 AccountSignature::Ed25519 { public_key, .. } => public_key.into(),
564 AccountSignature::Secp256k1 { public_key, .. } => public_key.into(),
565 AccountSignature::EvmSecp256k1 { address, .. } => AccountOwner::Address20(address),
566 }
567 }
568
569 pub fn check_signature(&self) -> Result<(), CryptoError> {
570 self.signature.verify(&self.content)
571 }
572
573 pub fn required_blob_ids(&self) -> impl Iterator<Item = BlobId> + '_ {
574 self.content.block.published_blob_ids().into_iter().chain(
575 self.content
576 .outcome
577 .iter()
578 .flat_map(|outcome| outcome.oracle_blob_ids()),
579 )
580 }
581
582 pub fn expected_blob_ids(&self) -> impl Iterator<Item = BlobId> + '_ {
583 self.content.block.published_blob_ids().into_iter().chain(
584 self.content.outcome.iter().flat_map(|outcome| {
585 outcome
586 .oracle_blob_ids()
587 .into_iter()
588 .chain(outcome.iter_created_blobs_ids())
589 }),
590 )
591 }
592
593 pub fn check_invariants(&self) -> Result<(), &'static str> {
595 match (&self.original_proposal, &self.content.outcome) {
596 (None, None) => {}
597 (Some(OriginalProposal::Fast(_)), None) => ensure!(
598 self.content.round > Round::Fast,
599 "The new proposal's round must be greater than the original's"
600 ),
601 (None, Some(_))
602 | (Some(OriginalProposal::Fast(_)), Some(_))
603 | (Some(OriginalProposal::Regular { .. }), None) => {
604 return Err("Must contain a validation certificate if and only if \
605 it contains the execution outcome from a previous round");
606 }
607 (Some(OriginalProposal::Regular { certificate }), Some(outcome)) => {
608 ensure!(
609 self.content.round > certificate.round,
610 "The new proposal's round must be greater than the original's"
611 );
612 let block = outcome.clone().with(self.content.block.clone());
613 let value = ValidatedBlock::new(block);
614 ensure!(
615 certificate.check_value(&value),
616 "Lite certificate must match the given block and execution outcome"
617 );
618 }
619 }
620 Ok(())
621 }
622}
623
624impl LiteVote {
625 pub fn new(value: LiteValue, round: Round, secret_key: &ValidatorSecretKey) -> Self {
627 let hash_and_round = VoteValue(value.value_hash, round, value.kind);
628 let signature = ValidatorSignature::new(&hash_and_round, secret_key);
629 Self {
630 value,
631 round,
632 public_key: secret_key.public(),
633 signature,
634 }
635 }
636
637 pub fn check(&self) -> Result<(), ChainError> {
639 let hash_and_round = VoteValue(self.value.value_hash, self.round, self.value.kind);
640 Ok(self.signature.check(&hash_and_round, self.public_key)?)
641 }
642}
643
644pub struct SignatureAggregator<'a, T: CertificateValue> {
645 committee: &'a Committee,
646 weight: u64,
647 used_validators: HashSet<ValidatorPublicKey>,
648 partial: GenericCertificate<T>,
649}
650
651impl<'a, T: CertificateValue> SignatureAggregator<'a, T> {
652 pub fn new(value: T, round: Round, committee: &'a Committee) -> Self {
654 Self {
655 committee,
656 weight: 0,
657 used_validators: HashSet::new(),
658 partial: GenericCertificate::new(value, round, Vec::new()),
659 }
660 }
661
662 pub fn append(
666 &mut self,
667 public_key: ValidatorPublicKey,
668 signature: ValidatorSignature,
669 ) -> Result<Option<GenericCertificate<T>>, ChainError>
670 where
671 T: CertificateValue,
672 {
673 let hash_and_round = VoteValue(self.partial.hash(), self.partial.round, T::KIND);
674 signature.check(&hash_and_round, public_key)?;
675 ensure!(
677 !self.used_validators.contains(&public_key),
678 ChainError::CertificateValidatorReuse
679 );
680 self.used_validators.insert(public_key);
681 let voting_rights = self.committee.weight(&public_key);
683 ensure!(voting_rights > 0, ChainError::InvalidSigner);
684 self.weight += voting_rights;
685 self.partial.add_signature((public_key, signature));
687
688 if self.weight >= self.committee.quorum_threshold() {
689 self.weight = 0; Ok(Some(self.partial.clone()))
691 } else {
692 Ok(None)
693 }
694 }
695}
696
697pub(crate) fn is_strictly_ordered(values: &[(ValidatorPublicKey, ValidatorSignature)]) -> bool {
700 values.windows(2).all(|pair| pair[0].0 < pair[1].0)
701}
702
703pub(crate) fn check_signatures(
705 value_hash: CryptoHash,
706 certificate_kind: CertificateKind,
707 round: Round,
708 signatures: &[(ValidatorPublicKey, ValidatorSignature)],
709 committee: &Committee,
710) -> Result<(), ChainError> {
711 let mut weight = 0;
713 let mut used_validators = HashSet::new();
714 for (validator, _) in signatures {
715 ensure!(
717 !used_validators.contains(validator),
718 ChainError::CertificateValidatorReuse
719 );
720 used_validators.insert(*validator);
721 let voting_rights = committee.weight(validator);
723 ensure!(voting_rights > 0, ChainError::InvalidSigner);
724 weight += voting_rights;
725 }
726 ensure!(
727 weight >= committee.quorum_threshold(),
728 ChainError::CertificateRequiresQuorum
729 );
730 let hash_and_round = VoteValue(value_hash, round, certificate_kind);
732 ValidatorSignature::verify_batch(&hash_and_round, signatures.iter())?;
733 Ok(())
734}
735
736impl BcsSignable<'_> for ProposalContent {}
737
738impl BcsSignable<'_> for VoteValue {}
739
740doc_scalar!(
741 MessageAction,
742 "Whether an incoming message is accepted or rejected."
743);
744
745#[cfg(test)]
746mod signing {
747 use linera_base::{
748 crypto::{AccountSecretKey, AccountSignature, CryptoHash, EvmSignature, TestString},
749 data_types::{BlockHeight, Epoch, Round},
750 identifiers::ChainId,
751 };
752
753 use crate::data_types::{BlockProposal, ProposalContent, ProposedBlock};
754
755 #[test]
756 fn proposal_content_signing() {
757 use std::str::FromStr;
758
759 let secret_key = linera_base::crypto::EvmSecretKey::from_str(
761 "f77a21701522a03b01c111ad2d2cdaf2b8403b47507ee0aec3c2e52b765d7a66",
762 )
763 .unwrap();
764 let address = secret_key.address();
765
766 let signer: AccountSecretKey = AccountSecretKey::EvmSecp256k1(secret_key);
767 let public_key = signer.public();
768
769 let proposed_block = ProposedBlock {
770 chain_id: ChainId(CryptoHash::new(&TestString::new("ChainId"))),
771 epoch: Epoch(11),
772 incoming_bundles: vec![],
773 operations: vec![],
774 height: BlockHeight(11),
775 timestamp: 190000000u64.into(),
776 authenticated_signer: None,
777 previous_block_hash: None,
778 };
779
780 let proposal = ProposalContent {
781 block: proposed_block,
782 round: Round::SingleLeader(11),
783 outcome: None,
784 };
785
786 let signature = EvmSignature::from_str(
789 "f2d8afcd51d0f947f5c5e31ac1db73ec5306163af7949b3bb265ba53d03374b0\
790 4b1e909007b555caf098da1aded29c600bee391c6ee8b4d0962a29044555796d1b",
791 )
792 .unwrap();
793 let metamask_signature = AccountSignature::EvmSecp256k1 {
794 signature,
795 address: address.0 .0,
796 };
797
798 let signature = signer.sign(&proposal);
799 assert_eq!(signature, metamask_signature);
800
801 assert_eq!(signature.owner(), public_key.into());
802
803 let block_proposal = BlockProposal {
804 content: proposal,
805 signature,
806 original_proposal: None,
807 };
808 assert_eq!(block_proposal.owner(), public_key.into(),);
809 }
810}