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, hex_debug,
17 identifiers::{Account, AccountOwner, ApplicationId, BlobId, ChainId, StreamId},
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)]
43#[graphql(complex)]
44pub struct ProposedBlock {
45 pub chain_id: ChainId,
47 pub epoch: Epoch,
49 #[debug(skip_if = Vec::is_empty)]
52 #[graphql(skip)]
53 pub transactions: Vec<Transaction>,
54 pub height: BlockHeight,
56 pub timestamp: Timestamp,
59 #[debug(skip_if = Option::is_none)]
64 pub authenticated_signer: Option<AccountOwner>,
65 pub previous_block_hash: Option<CryptoHash>,
68}
69
70impl ProposedBlock {
71 pub fn published_blob_ids(&self) -> BTreeSet<BlobId> {
73 self.operations()
74 .flat_map(Operation::published_blob_ids)
75 .collect()
76 }
77
78 pub fn has_only_rejected_messages(&self) -> bool {
81 self.transactions.iter().all(|txn| {
82 matches!(
83 txn,
84 Transaction::ReceiveMessages(IncomingBundle {
85 action: MessageAction::Reject,
86 ..
87 })
88 )
89 })
90 }
91
92 pub fn incoming_messages(&self) -> impl Iterator<Item = &PostedMessage> {
94 self.incoming_bundles()
95 .flat_map(|incoming_bundle| &incoming_bundle.bundle.messages)
96 }
97
98 pub fn message_count(&self) -> usize {
100 self.incoming_bundles()
101 .map(|im| im.bundle.messages.len())
102 .sum()
103 }
104
105 pub fn transaction_refs(&self) -> impl Iterator<Item = &Transaction> {
107 self.transactions.iter()
108 }
109
110 pub fn operations(&self) -> impl Iterator<Item = &Operation> {
112 self.transactions.iter().filter_map(|tx| match tx {
113 Transaction::ExecuteOperation(operation) => Some(operation),
114 Transaction::ReceiveMessages(_) => None,
115 })
116 }
117
118 pub fn incoming_bundles(&self) -> impl Iterator<Item = &IncomingBundle> {
120 self.transactions.iter().filter_map(|tx| match tx {
121 Transaction::ReceiveMessages(bundle) => Some(bundle),
122 Transaction::ExecuteOperation(_) => None,
123 })
124 }
125
126 pub fn check_proposal_size(&self, maximum_block_proposal_size: u64) -> Result<(), ChainError> {
127 let size = bcs::serialized_size(self)?;
128 ensure!(
129 size <= usize::try_from(maximum_block_proposal_size).unwrap_or(usize::MAX),
130 ChainError::BlockProposalTooLarge(size)
131 );
132 Ok(())
133 }
134}
135
136#[async_graphql::ComplexObject]
137impl ProposedBlock {
138 async fn transaction_metadata(&self) -> Vec<TransactionMetadata> {
140 self.transactions
141 .iter()
142 .map(TransactionMetadata::from_transaction)
143 .collect()
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
149pub enum Transaction {
150 ReceiveMessages(IncomingBundle),
152 ExecuteOperation(Operation),
154}
155
156impl BcsHashable<'_> for Transaction {}
157
158#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
159#[graphql(name = "Operation")]
160pub struct OperationMetadata {
161 pub operation_type: String,
163 pub application_id: Option<ApplicationId>,
165 pub user_bytes_hex: Option<String>,
167 pub system_bytes_hex: Option<String>,
169}
170
171impl From<&Operation> for OperationMetadata {
172 fn from(operation: &Operation) -> Self {
173 match operation {
174 Operation::System(sys_op) => OperationMetadata {
175 operation_type: "System".to_string(),
176 application_id: None,
177 user_bytes_hex: None,
178 system_bytes_hex: Some(hex::encode(
179 bcs::to_bytes(sys_op).expect("System operation should be serializable"),
180 )),
181 },
182 Operation::User {
183 application_id,
184 bytes,
185 } => OperationMetadata {
186 operation_type: "User".to_string(),
187 application_id: Some(*application_id),
188 user_bytes_hex: Some(hex::encode(bytes)),
189 system_bytes_hex: None,
190 },
191 }
192 }
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, SimpleObject)]
197pub struct TransactionMetadata {
198 pub transaction_type: String,
200 pub incoming_bundle: Option<IncomingBundle>,
202 pub operation: Option<OperationMetadata>,
204}
205
206impl TransactionMetadata {
207 pub fn from_transaction(transaction: &Transaction) -> Self {
208 match transaction {
209 Transaction::ReceiveMessages(bundle) => TransactionMetadata {
210 transaction_type: "ReceiveMessages".to_string(),
211 incoming_bundle: Some(bundle.clone()),
212 operation: None,
213 },
214 Transaction::ExecuteOperation(op) => TransactionMetadata {
215 transaction_type: "ExecuteOperation".to_string(),
216 incoming_bundle: None,
217 operation: Some(OperationMetadata::from(op)),
218 },
219 }
220 }
221}
222
223#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, SimpleObject)]
225pub struct ChainAndHeight {
226 pub chain_id: ChainId,
227 pub height: BlockHeight,
228}
229
230#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
232pub struct IncomingBundle {
233 pub origin: ChainId,
235 pub bundle: MessageBundle,
237 pub action: MessageAction,
239}
240
241impl IncomingBundle {
242 pub fn messages(&self) -> impl Iterator<Item = &PostedMessage> {
244 self.bundle.messages.iter()
245 }
246}
247
248impl BcsHashable<'_> for IncomingBundle {}
249
250#[derive(Copy, Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
252pub enum MessageAction {
253 Accept,
255 Reject,
257}
258
259#[derive(Debug, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, SimpleObject)]
261pub struct MessageBundle {
262 pub height: BlockHeight,
264 pub timestamp: Timestamp,
266 pub certificate_hash: CryptoHash,
268 pub transaction_index: u32,
270 pub messages: Vec<PostedMessage>,
272}
273
274#[derive(Clone, Debug, Serialize, Deserialize)]
275#[cfg_attr(with_testing, derive(Eq, PartialEq))]
276pub enum OriginalProposal {
278 Fast(AccountSignature),
280 Regular {
282 certificate: LiteCertificate<'static>,
283 },
284}
285
286#[derive(Clone, Debug, Serialize, Deserialize)]
290#[cfg_attr(with_testing, derive(Eq, PartialEq))]
291pub struct BlockProposal {
292 pub content: ProposalContent,
293 pub signature: AccountSignature,
294 #[debug(skip_if = Option::is_none)]
295 pub original_proposal: Option<OriginalProposal>,
296}
297
298#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
300pub struct PostedMessage {
301 #[debug(skip_if = Option::is_none)]
303 pub authenticated_signer: Option<AccountOwner>,
304 #[debug(skip_if = Amount::is_zero)]
306 pub grant: Amount,
307 #[debug(skip_if = Option::is_none)]
309 pub refund_grant_to: Option<Account>,
310 pub kind: MessageKind,
312 pub index: u32,
314 pub message: Message,
316}
317
318pub trait OutgoingMessageExt {
319 fn into_posted(self, index: u32) -> PostedMessage;
321}
322
323impl OutgoingMessageExt for OutgoingMessage {
324 fn into_posted(self, index: u32) -> PostedMessage {
326 let OutgoingMessage {
327 destination: _,
328 authenticated_signer,
329 grant,
330 refund_grant_to,
331 kind,
332 message,
333 } = self;
334 PostedMessage {
335 authenticated_signer,
336 grant,
337 refund_grant_to,
338 kind,
339 index,
340 message,
341 }
342 }
343}
344
345#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
347pub struct OperationResult(
348 #[debug(with = "hex_debug")]
349 #[serde(with = "serde_bytes")]
350 pub Vec<u8>,
351);
352
353impl BcsHashable<'_> for OperationResult {}
354
355doc_scalar!(
356 OperationResult,
357 "The execution result of a single operation."
358);
359
360#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
362#[cfg_attr(with_testing, derive(Default))]
363pub struct BlockExecutionOutcome {
364 pub messages: Vec<Vec<OutgoingMessage>>,
366 pub previous_message_blocks: BTreeMap<ChainId, (CryptoHash, BlockHeight)>,
368 pub previous_event_blocks: BTreeMap<StreamId, (CryptoHash, BlockHeight)>,
370 pub state_hash: CryptoHash,
372 pub oracle_responses: Vec<Vec<OracleResponse>>,
374 pub events: Vec<Vec<Event>>,
376 pub blobs: Vec<Vec<Blob>>,
378 pub operation_results: Vec<OperationResult>,
380}
381
382#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
384pub struct LiteValue {
385 pub value_hash: CryptoHash,
386 pub chain_id: ChainId,
387 pub kind: CertificateKind,
388}
389
390impl LiteValue {
391 pub fn new<T: CertificateValue>(value: &T) -> Self {
392 LiteValue {
393 value_hash: value.hash(),
394 chain_id: value.chain_id(),
395 kind: T::KIND,
396 }
397 }
398}
399
400#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
401struct VoteValue(CryptoHash, Round, CertificateKind);
402
403#[derive(Clone, Debug, Serialize, Deserialize)]
405#[serde(bound(deserialize = "T: Deserialize<'de>"))]
406pub struct Vote<T> {
407 pub value: T,
408 pub round: Round,
409 pub signature: ValidatorSignature,
410}
411
412impl<T> Vote<T> {
413 pub fn new(value: T, round: Round, key_pair: &ValidatorSecretKey) -> Self
415 where
416 T: CertificateValue,
417 {
418 let hash_and_round = VoteValue(value.hash(), round, T::KIND);
419 let signature = ValidatorSignature::new(&hash_and_round, key_pair);
420 Self {
421 value,
422 round,
423 signature,
424 }
425 }
426
427 pub fn lite(&self) -> LiteVote
429 where
430 T: CertificateValue,
431 {
432 LiteVote {
433 value: LiteValue::new(&self.value),
434 round: self.round,
435 signature: self.signature,
436 }
437 }
438
439 pub fn value(&self) -> &T {
441 &self.value
442 }
443}
444
445#[derive(Clone, Debug, Serialize, Deserialize)]
447#[cfg_attr(with_testing, derive(Eq, PartialEq))]
448pub struct LiteVote {
449 pub value: LiteValue,
450 pub round: Round,
451 pub signature: ValidatorSignature,
452}
453
454impl LiteVote {
455 #[cfg(with_testing)]
457 pub fn with_value<T: CertificateValue>(self, value: T) -> Option<Vote<T>> {
458 if self.value.value_hash != value.hash() {
459 return None;
460 }
461 Some(Vote {
462 value,
463 round: self.round,
464 signature: self.signature,
465 })
466 }
467
468 pub fn kind(&self) -> CertificateKind {
469 self.value.kind
470 }
471}
472
473impl MessageBundle {
474 pub fn is_skippable(&self) -> bool {
475 self.messages.iter().all(PostedMessage::is_skippable)
476 }
477
478 pub fn is_protected(&self) -> bool {
479 self.messages.iter().any(PostedMessage::is_protected)
480 }
481}
482
483impl PostedMessage {
484 pub fn is_skippable(&self) -> bool {
485 match self.kind {
486 MessageKind::Protected | MessageKind::Tracked => false,
487 MessageKind::Simple | MessageKind::Bouncing => self.grant == Amount::ZERO,
488 }
489 }
490
491 pub fn is_protected(&self) -> bool {
492 matches!(self.kind, MessageKind::Protected)
493 }
494
495 pub fn is_tracked(&self) -> bool {
496 matches!(self.kind, MessageKind::Tracked)
497 }
498
499 pub fn is_bouncing(&self) -> bool {
500 matches!(self.kind, MessageKind::Bouncing)
501 }
502}
503
504impl BlockExecutionOutcome {
505 pub fn with(self, block: ProposedBlock) -> Block {
506 Block::new(block, self)
507 }
508
509 pub fn oracle_blob_ids(&self) -> HashSet<BlobId> {
510 let mut required_blob_ids = HashSet::new();
511 for responses in &self.oracle_responses {
512 for response in responses {
513 if let OracleResponse::Blob(blob_id) = response {
514 required_blob_ids.insert(*blob_id);
515 }
516 }
517 }
518
519 required_blob_ids
520 }
521
522 pub fn has_oracle_responses(&self) -> bool {
523 self.oracle_responses
524 .iter()
525 .any(|responses| !responses.is_empty())
526 }
527
528 pub fn iter_created_blobs_ids(&self) -> impl Iterator<Item = BlobId> + '_ {
529 self.blobs.iter().flatten().map(|blob| blob.id())
530 }
531
532 pub fn created_blobs_ids(&self) -> HashSet<BlobId> {
533 self.iter_created_blobs_ids().collect()
534 }
535}
536
537#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
539pub struct ProposalContent {
540 pub block: ProposedBlock,
542 pub round: Round,
544 #[debug(skip_if = Option::is_none)]
546 pub outcome: Option<BlockExecutionOutcome>,
547}
548
549impl BlockProposal {
550 pub async fn new_initial<S: Signer + ?Sized>(
551 owner: AccountOwner,
552 round: Round,
553 block: ProposedBlock,
554 signer: &S,
555 ) -> Result<Self, S::Error> {
556 let content = ProposalContent {
557 round,
558 block,
559 outcome: None,
560 };
561 let signature = signer.sign(&owner, &CryptoHash::new(&content)).await?;
562
563 Ok(Self {
564 content,
565 signature,
566 original_proposal: None,
567 })
568 }
569
570 pub async fn new_retry_fast<S: Signer + ?Sized>(
571 owner: AccountOwner,
572 round: Round,
573 old_proposal: BlockProposal,
574 signer: &S,
575 ) -> Result<Self, S::Error> {
576 let content = ProposalContent {
577 round,
578 block: old_proposal.content.block,
579 outcome: None,
580 };
581 let signature = signer.sign(&owner, &CryptoHash::new(&content)).await?;
582
583 Ok(Self {
584 content,
585 signature,
586 original_proposal: Some(OriginalProposal::Fast(old_proposal.signature)),
587 })
588 }
589
590 pub async fn new_retry_regular<S: Signer>(
591 owner: AccountOwner,
592 round: Round,
593 validated_block_certificate: ValidatedBlockCertificate,
594 signer: &S,
595 ) -> Result<Self, S::Error> {
596 let certificate = validated_block_certificate.lite_certificate().cloned();
597 let block = validated_block_certificate.into_inner().into_inner();
598 let (block, outcome) = block.into_proposal();
599 let content = ProposalContent {
600 block,
601 round,
602 outcome: Some(outcome),
603 };
604 let signature = signer.sign(&owner, &CryptoHash::new(&content)).await?;
605
606 Ok(Self {
607 content,
608 signature,
609 original_proposal: Some(OriginalProposal::Regular { certificate }),
610 })
611 }
612
613 pub fn owner(&self) -> AccountOwner {
615 match self.signature {
616 AccountSignature::Ed25519 { public_key, .. } => public_key.into(),
617 AccountSignature::Secp256k1 { public_key, .. } => public_key.into(),
618 AccountSignature::EvmSecp256k1 { address, .. } => AccountOwner::Address20(address),
619 }
620 }
621
622 pub fn check_signature(&self) -> Result<(), CryptoError> {
623 self.signature.verify(&self.content)
624 }
625
626 pub fn required_blob_ids(&self) -> impl Iterator<Item = BlobId> + '_ {
627 self.content.block.published_blob_ids().into_iter().chain(
628 self.content
629 .outcome
630 .iter()
631 .flat_map(|outcome| outcome.oracle_blob_ids()),
632 )
633 }
634
635 pub fn expected_blob_ids(&self) -> impl Iterator<Item = BlobId> + '_ {
636 self.content.block.published_blob_ids().into_iter().chain(
637 self.content.outcome.iter().flat_map(|outcome| {
638 outcome
639 .oracle_blob_ids()
640 .into_iter()
641 .chain(outcome.iter_created_blobs_ids())
642 }),
643 )
644 }
645
646 pub fn check_invariants(&self) -> Result<(), &'static str> {
648 match (&self.original_proposal, &self.content.outcome) {
649 (None, None) => {}
650 (Some(OriginalProposal::Fast(_)), None) => ensure!(
651 self.content.round > Round::Fast,
652 "The new proposal's round must be greater than the original's"
653 ),
654 (None, Some(_))
655 | (Some(OriginalProposal::Fast(_)), Some(_))
656 | (Some(OriginalProposal::Regular { .. }), None) => {
657 return Err("Must contain a validation certificate if and only if \
658 it contains the execution outcome from a previous round");
659 }
660 (Some(OriginalProposal::Regular { certificate }), Some(outcome)) => {
661 ensure!(
662 self.content.round > certificate.round,
663 "The new proposal's round must be greater than the original's"
664 );
665 let block = outcome.clone().with(self.content.block.clone());
666 let value = ValidatedBlock::new(block);
667 ensure!(
668 certificate.check_value(&value),
669 "Lite certificate must match the given block and execution outcome"
670 );
671 }
672 }
673 Ok(())
674 }
675}
676
677impl LiteVote {
678 pub fn new(value: LiteValue, round: Round, secret_key: &ValidatorSecretKey) -> Self {
680 let hash_and_round = VoteValue(value.value_hash, round, value.kind);
681 let signature = ValidatorSignature::new(&hash_and_round, secret_key);
682 Self {
683 value,
684 round,
685 signature,
686 }
687 }
688
689 pub fn check(&self, public_key: ValidatorPublicKey) -> Result<(), ChainError> {
691 let hash_and_round = VoteValue(self.value.value_hash, self.round, self.value.kind);
692 Ok(self.signature.check(&hash_and_round, public_key)?)
693 }
694}
695
696pub struct SignatureAggregator<'a, T: CertificateValue> {
697 committee: &'a Committee,
698 weight: u64,
699 used_validators: HashSet<ValidatorPublicKey>,
700 partial: GenericCertificate<T>,
701}
702
703impl<'a, T: CertificateValue> SignatureAggregator<'a, T> {
704 pub fn new(value: T, round: Round, committee: &'a Committee) -> Self {
706 Self {
707 committee,
708 weight: 0,
709 used_validators: HashSet::new(),
710 partial: GenericCertificate::new(value, round, Vec::new()),
711 }
712 }
713
714 pub fn append(
718 &mut self,
719 public_key: ValidatorPublicKey,
720 signature: ValidatorSignature,
721 ) -> Result<Option<GenericCertificate<T>>, ChainError>
722 where
723 T: CertificateValue,
724 {
725 let hash_and_round = VoteValue(self.partial.hash(), self.partial.round, T::KIND);
726 signature.check(&hash_and_round, public_key)?;
727 ensure!(
729 !self.used_validators.contains(&public_key),
730 ChainError::CertificateValidatorReuse
731 );
732 self.used_validators.insert(public_key);
733 let voting_rights = self.committee.weight(&public_key);
735 ensure!(voting_rights > 0, ChainError::InvalidSigner);
736 self.weight += voting_rights;
737 self.partial.add_signature((public_key, signature));
739
740 if self.weight >= self.committee.quorum_threshold() {
741 self.weight = 0; Ok(Some(self.partial.clone()))
743 } else {
744 Ok(None)
745 }
746 }
747}
748
749pub(crate) fn is_strictly_ordered(values: &[(ValidatorPublicKey, ValidatorSignature)]) -> bool {
752 values.windows(2).all(|pair| pair[0].0 < pair[1].0)
753}
754
755pub(crate) fn check_signatures(
757 value_hash: CryptoHash,
758 certificate_kind: CertificateKind,
759 round: Round,
760 signatures: &[(ValidatorPublicKey, ValidatorSignature)],
761 committee: &Committee,
762) -> Result<(), ChainError> {
763 let mut weight = 0;
765 let mut used_validators = HashSet::new();
766 for (validator, _) in signatures {
767 ensure!(
769 !used_validators.contains(validator),
770 ChainError::CertificateValidatorReuse
771 );
772 used_validators.insert(*validator);
773 let voting_rights = committee.weight(validator);
775 ensure!(voting_rights > 0, ChainError::InvalidSigner);
776 weight += voting_rights;
777 }
778 ensure!(
779 weight >= committee.quorum_threshold(),
780 ChainError::CertificateRequiresQuorum
781 );
782 let hash_and_round = VoteValue(value_hash, round, certificate_kind);
784 ValidatorSignature::verify_batch(&hash_and_round, signatures.iter())?;
785 Ok(())
786}
787
788impl BcsSignable<'_> for ProposalContent {}
789
790impl BcsSignable<'_> for VoteValue {}
791
792doc_scalar!(
793 MessageAction,
794 "Whether an incoming message is accepted or rejected."
795);
796
797#[cfg(test)]
798mod signing {
799 use linera_base::{
800 crypto::{AccountSecretKey, AccountSignature, CryptoHash, EvmSignature, TestString},
801 data_types::{BlockHeight, Epoch, Round},
802 identifiers::ChainId,
803 };
804
805 use crate::data_types::{BlockProposal, ProposalContent, ProposedBlock};
806
807 #[test]
808 fn proposal_content_signing() {
809 use std::str::FromStr;
810
811 let secret_key = linera_base::crypto::EvmSecretKey::from_str(
813 "f77a21701522a03b01c111ad2d2cdaf2b8403b47507ee0aec3c2e52b765d7a66",
814 )
815 .unwrap();
816 let address = secret_key.address();
817
818 let signer: AccountSecretKey = AccountSecretKey::EvmSecp256k1(secret_key);
819 let public_key = signer.public();
820
821 let proposed_block = ProposedBlock {
822 chain_id: ChainId(CryptoHash::new(&TestString::new("ChainId"))),
823 epoch: Epoch(11),
824 transactions: vec![],
825 height: BlockHeight(11),
826 timestamp: 190000000u64.into(),
827 authenticated_signer: None,
828 previous_block_hash: None,
829 };
830
831 let proposal = ProposalContent {
832 block: proposed_block,
833 round: Round::SingleLeader(11),
834 outcome: None,
835 };
836
837 let signature = EvmSignature::from_str(
840 "d69d31203f59be441fd02cdf68b2504cbcdd7215905c9b7dc3a7ccbf09afe14550\
841 3c93b391810ce9edd6ee36b1e817b2d0e9dabdf4a098da8c2f670ef4198e8a1b",
842 )
843 .unwrap();
844 let metamask_signature = AccountSignature::EvmSecp256k1 {
845 signature,
846 address: address.0 .0,
847 };
848
849 let signature = signer.sign(&proposal);
850 assert_eq!(signature, metamask_signature);
851
852 assert_eq!(signature.owner(), public_key.into());
853
854 let block_proposal = BlockProposal {
855 content: proposal,
856 signature,
857 original_proposal: None,
858 };
859 assert_eq!(block_proposal.owner(), public_key.into(),);
860 }
861}