linera_rpc/grpc/
conversions.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use linera_base::{
5    crypto::{
6        AccountPublicKey, AccountSignature, CryptoError, CryptoHash, ValidatorPublicKey,
7        ValidatorSignature,
8    },
9    data_types::{BlobContent, BlockHeight, NetworkDescription},
10    ensure,
11    identifiers::{AccountOwner, BlobId, ChainId, EventId},
12};
13use linera_chain::{
14    data_types::{BlockProposal, LiteValue, ProposalContent},
15    types::{
16        Certificate, CertificateKind, ConfirmedBlock, ConfirmedBlockCertificate, LiteCertificate,
17        Timeout, TimeoutCertificate, ValidatedBlock, ValidatedBlockCertificate,
18    },
19};
20use linera_core::{
21    data_types::{
22        CertificatesByHeightRequest, ChainInfoQuery, ChainInfoResponse, CrossChainRequest,
23    },
24    node::NodeError,
25    worker::Notification,
26};
27use thiserror::Error;
28use tonic::{Code, Status};
29
30use super::api::{self, PendingBlobRequest};
31use crate::{
32    HandleConfirmedCertificateRequest, HandleLiteCertRequest, HandleTimeoutCertificateRequest,
33    HandleValidatedCertificateRequest,
34};
35
36#[derive(Error, Debug)]
37pub enum GrpcProtoConversionError {
38    #[error(transparent)]
39    BincodeError(#[from] bincode::Error),
40    #[error("Conversion failed due to missing field")]
41    MissingField,
42    #[error("Signature error: {0}")]
43    SignatureError(ed25519_dalek::SignatureError),
44    #[error("Cryptographic error: {0}")]
45    CryptoError(#[from] CryptoError),
46    #[error("Inconsistent outer/inner chain IDs")]
47    InconsistentChainId,
48    #[error("Unrecognized certificate type")]
49    InvalidCertificateType,
50}
51
52impl From<ed25519_dalek::SignatureError> for GrpcProtoConversionError {
53    fn from(signature_error: ed25519_dalek::SignatureError) -> Self {
54        GrpcProtoConversionError::SignatureError(signature_error)
55    }
56}
57
58/// Extracts an optional field from a Proto type and tries to map it.
59fn try_proto_convert<S, T>(t: Option<T>) -> Result<S, GrpcProtoConversionError>
60where
61    T: TryInto<S, Error = GrpcProtoConversionError>,
62{
63    t.ok_or(GrpcProtoConversionError::MissingField)?.try_into()
64}
65
66impl From<GrpcProtoConversionError> for Status {
67    fn from(error: GrpcProtoConversionError) -> Self {
68        Status::new(Code::InvalidArgument, error.to_string())
69    }
70}
71
72impl From<GrpcProtoConversionError> for NodeError {
73    fn from(error: GrpcProtoConversionError) -> Self {
74        NodeError::GrpcError {
75            error: error.to_string(),
76        }
77    }
78}
79
80impl From<linera_version::CrateVersion> for api::CrateVersion {
81    fn from(
82        linera_version::CrateVersion {
83            major,
84            minor,
85            patch,
86        }: linera_version::CrateVersion,
87    ) -> Self {
88        Self {
89            major,
90            minor,
91            patch,
92        }
93    }
94}
95
96impl From<api::CrateVersion> for linera_version::CrateVersion {
97    fn from(
98        api::CrateVersion {
99            major,
100            minor,
101            patch,
102        }: api::CrateVersion,
103    ) -> Self {
104        Self {
105            major,
106            minor,
107            patch,
108        }
109    }
110}
111
112impl From<linera_version::VersionInfo> for api::VersionInfo {
113    fn from(version_info: linera_version::VersionInfo) -> api::VersionInfo {
114        api::VersionInfo {
115            crate_version: Some(version_info.crate_version.value.into()),
116            git_commit: version_info.git_commit.into(),
117            git_dirty: version_info.git_dirty,
118            rpc_hash: version_info.rpc_hash.into(),
119            graphql_hash: version_info.graphql_hash.into(),
120            wit_hash: version_info.wit_hash.into(),
121        }
122    }
123}
124
125impl From<api::VersionInfo> for linera_version::VersionInfo {
126    fn from(version_info: api::VersionInfo) -> linera_version::VersionInfo {
127        linera_version::VersionInfo {
128            crate_version: linera_version::Pretty::new(
129                version_info
130                    .crate_version
131                    .unwrap_or(api::CrateVersion {
132                        major: 0,
133                        minor: 0,
134                        patch: 0,
135                    })
136                    .into(),
137            ),
138            git_commit: version_info.git_commit.into(),
139            git_dirty: version_info.git_dirty,
140            rpc_hash: version_info.rpc_hash.into(),
141            graphql_hash: version_info.graphql_hash.into(),
142            wit_hash: version_info.wit_hash.into(),
143        }
144    }
145}
146
147impl From<NetworkDescription> for api::NetworkDescription {
148    fn from(
149        NetworkDescription {
150            name,
151            genesis_config_hash,
152            genesis_timestamp,
153            genesis_committee_blob_hash,
154            admin_chain_id,
155        }: NetworkDescription,
156    ) -> Self {
157        Self {
158            name,
159            genesis_config_hash: Some(genesis_config_hash.into()),
160            genesis_timestamp: genesis_timestamp.micros(),
161            admin_chain_id: Some(admin_chain_id.into()),
162            genesis_committee_blob_hash: Some(genesis_committee_blob_hash.into()),
163        }
164    }
165}
166
167impl TryFrom<api::NetworkDescription> for NetworkDescription {
168    type Error = GrpcProtoConversionError;
169
170    fn try_from(
171        api::NetworkDescription {
172            name,
173            genesis_config_hash,
174            genesis_timestamp,
175            genesis_committee_blob_hash,
176            admin_chain_id,
177        }: api::NetworkDescription,
178    ) -> Result<Self, Self::Error> {
179        Ok(Self {
180            name,
181            genesis_config_hash: try_proto_convert(genesis_config_hash)?,
182            genesis_timestamp: genesis_timestamp.into(),
183            admin_chain_id: try_proto_convert(admin_chain_id)?,
184            genesis_committee_blob_hash: try_proto_convert(genesis_committee_blob_hash)?,
185        })
186    }
187}
188
189impl TryFrom<Notification> for api::Notification {
190    type Error = GrpcProtoConversionError;
191
192    fn try_from(notification: Notification) -> Result<Self, Self::Error> {
193        Ok(Self {
194            chain_id: Some(notification.chain_id.into()),
195            reason: bincode::serialize(&notification.reason)?,
196        })
197    }
198}
199
200impl TryFrom<api::Notification> for Option<Notification> {
201    type Error = GrpcProtoConversionError;
202
203    fn try_from(notification: api::Notification) -> Result<Self, Self::Error> {
204        if notification.chain_id.is_none() && notification.reason.is_empty() {
205            Ok(None)
206        } else {
207            Ok(Some(Notification {
208                chain_id: try_proto_convert(notification.chain_id)?,
209                reason: bincode::deserialize(&notification.reason)?,
210            }))
211        }
212    }
213}
214
215impl TryFrom<ChainInfoResponse> for api::ChainInfoResult {
216    type Error = GrpcProtoConversionError;
217
218    fn try_from(chain_info_response: ChainInfoResponse) -> Result<Self, Self::Error> {
219        let response = chain_info_response.try_into()?;
220        Ok(api::ChainInfoResult {
221            inner: Some(api::chain_info_result::Inner::ChainInfoResponse(response)),
222        })
223    }
224}
225
226impl TryFrom<NodeError> for api::ChainInfoResult {
227    type Error = GrpcProtoConversionError;
228
229    fn try_from(node_error: NodeError) -> Result<Self, Self::Error> {
230        let error = bincode::serialize(&node_error)?;
231        Ok(api::ChainInfoResult {
232            inner: Some(api::chain_info_result::Inner::Error(error)),
233        })
234    }
235}
236
237impl TryFrom<BlockProposal> for api::BlockProposal {
238    type Error = GrpcProtoConversionError;
239
240    fn try_from(block_proposal: BlockProposal) -> Result<Self, Self::Error> {
241        Ok(Self {
242            chain_id: Some(block_proposal.content.block.chain_id.into()),
243            content: bincode::serialize(&block_proposal.content)?,
244            owner: Some(block_proposal.owner().try_into()?),
245            signature: Some(block_proposal.signature.into()),
246            original_proposal: block_proposal
247                .original_proposal
248                .map(|cert| bincode::serialize(&cert))
249                .transpose()?,
250        })
251    }
252}
253
254impl TryFrom<api::BlockProposal> for BlockProposal {
255    type Error = GrpcProtoConversionError;
256
257    fn try_from(block_proposal: api::BlockProposal) -> Result<Self, Self::Error> {
258        let content: ProposalContent = bincode::deserialize(&block_proposal.content)?;
259        ensure!(
260            Some(content.block.chain_id.into()) == block_proposal.chain_id,
261            GrpcProtoConversionError::InconsistentChainId
262        );
263        Ok(Self {
264            content,
265            signature: try_proto_convert(block_proposal.signature)?,
266            original_proposal: block_proposal
267                .original_proposal
268                .map(|bytes| bincode::deserialize(&bytes))
269                .transpose()?,
270        })
271    }
272}
273
274impl TryFrom<api::CrossChainRequest> for CrossChainRequest {
275    type Error = GrpcProtoConversionError;
276
277    fn try_from(cross_chain_request: api::CrossChainRequest) -> Result<Self, Self::Error> {
278        use api::cross_chain_request::Inner;
279
280        let ccr = match cross_chain_request
281            .inner
282            .ok_or(GrpcProtoConversionError::MissingField)?
283        {
284            Inner::UpdateRecipient(api::UpdateRecipient {
285                sender,
286                recipient,
287                bundles,
288                previous_height,
289            }) => CrossChainRequest::UpdateRecipient {
290                sender: try_proto_convert(sender)?,
291                recipient: try_proto_convert(recipient)?,
292                bundles: bincode::deserialize(&bundles)?,
293                previous_height: previous_height.map(Into::into),
294            },
295            Inner::ConfirmUpdatedRecipient(api::ConfirmUpdatedRecipient {
296                sender,
297                recipient,
298                latest_height,
299            }) => CrossChainRequest::ConfirmUpdatedRecipient {
300                sender: try_proto_convert(sender)?,
301                recipient: try_proto_convert(recipient)?,
302                latest_height: latest_height
303                    .ok_or(GrpcProtoConversionError::MissingField)?
304                    .into(),
305            },
306            Inner::RevertConfirm(api::RevertConfirm {
307                sender,
308                recipient,
309                retransmit_from,
310            }) => CrossChainRequest::RevertConfirm {
311                sender: try_proto_convert(sender)?,
312                recipient: try_proto_convert(recipient)?,
313                retransmit_from: retransmit_from
314                    .ok_or(GrpcProtoConversionError::MissingField)?
315                    .into(),
316            },
317        };
318        Ok(ccr)
319    }
320}
321
322impl TryFrom<CrossChainRequest> for api::CrossChainRequest {
323    type Error = GrpcProtoConversionError;
324
325    fn try_from(cross_chain_request: CrossChainRequest) -> Result<Self, Self::Error> {
326        use api::cross_chain_request::Inner;
327
328        let inner = match cross_chain_request {
329            CrossChainRequest::UpdateRecipient {
330                sender,
331                recipient,
332                bundles,
333                previous_height,
334            } => Inner::UpdateRecipient(api::UpdateRecipient {
335                sender: Some(sender.into()),
336                recipient: Some(recipient.into()),
337                bundles: bincode::serialize(&bundles)?,
338                previous_height: previous_height.map(Into::into),
339            }),
340            CrossChainRequest::ConfirmUpdatedRecipient {
341                sender,
342                recipient,
343                latest_height,
344            } => Inner::ConfirmUpdatedRecipient(api::ConfirmUpdatedRecipient {
345                sender: Some(sender.into()),
346                recipient: Some(recipient.into()),
347                latest_height: Some(latest_height.into()),
348            }),
349            CrossChainRequest::RevertConfirm {
350                sender,
351                recipient,
352                retransmit_from,
353            } => Inner::RevertConfirm(api::RevertConfirm {
354                sender: Some(sender.into()),
355                recipient: Some(recipient.into()),
356                retransmit_from: Some(retransmit_from.into()),
357            }),
358        };
359        Ok(Self { inner: Some(inner) })
360    }
361}
362
363impl TryFrom<api::LiteCertificate> for HandleLiteCertRequest<'_> {
364    type Error = GrpcProtoConversionError;
365
366    fn try_from(certificate: api::LiteCertificate) -> Result<Self, Self::Error> {
367        let kind = if certificate.kind == api::CertificateKind::Validated as i32 {
368            CertificateKind::Validated
369        } else if certificate.kind == api::CertificateKind::Confirmed as i32 {
370            CertificateKind::Confirmed
371        } else if certificate.kind == api::CertificateKind::Timeout as i32 {
372            CertificateKind::Timeout
373        } else {
374            return Err(GrpcProtoConversionError::InvalidCertificateType);
375        };
376
377        let value = LiteValue {
378            value_hash: CryptoHash::try_from(certificate.hash.as_slice())?,
379            chain_id: try_proto_convert(certificate.chain_id)?,
380            kind,
381        };
382        let signatures = bincode::deserialize(&certificate.signatures)?;
383        let round = bincode::deserialize(&certificate.round)?;
384        Ok(Self {
385            certificate: LiteCertificate::new(value, round, signatures),
386            wait_for_outgoing_messages: certificate.wait_for_outgoing_messages,
387        })
388    }
389}
390
391impl TryFrom<HandleLiteCertRequest<'_>> for api::LiteCertificate {
392    type Error = GrpcProtoConversionError;
393
394    fn try_from(request: HandleLiteCertRequest) -> Result<Self, Self::Error> {
395        Ok(Self {
396            hash: request.certificate.value.value_hash.as_bytes().to_vec(),
397            round: bincode::serialize(&request.certificate.round)?,
398            chain_id: Some(request.certificate.value.chain_id.into()),
399            signatures: bincode::serialize(&request.certificate.signatures)?,
400            wait_for_outgoing_messages: request.wait_for_outgoing_messages,
401            kind: request.certificate.value.kind as i32,
402        })
403    }
404}
405
406impl TryFrom<api::HandleTimeoutCertificateRequest> for HandleTimeoutCertificateRequest {
407    type Error = GrpcProtoConversionError;
408
409    fn try_from(cert_request: api::HandleTimeoutCertificateRequest) -> Result<Self, Self::Error> {
410        let certificate: TimeoutCertificate = cert_request
411            .certificate
412            .ok_or(GrpcProtoConversionError::MissingField)?
413            .try_into()?;
414
415        let req_chain_id: ChainId = cert_request
416            .chain_id
417            .ok_or(GrpcProtoConversionError::MissingField)?
418            .try_into()?;
419
420        ensure!(
421            certificate.inner().chain_id() == req_chain_id,
422            GrpcProtoConversionError::InconsistentChainId
423        );
424        Ok(HandleTimeoutCertificateRequest { certificate })
425    }
426}
427
428impl TryFrom<api::HandleValidatedCertificateRequest> for HandleValidatedCertificateRequest {
429    type Error = GrpcProtoConversionError;
430
431    fn try_from(cert_request: api::HandleValidatedCertificateRequest) -> Result<Self, Self::Error> {
432        let certificate: ValidatedBlockCertificate = cert_request
433            .certificate
434            .ok_or(GrpcProtoConversionError::MissingField)?
435            .try_into()?;
436
437        let req_chain_id: ChainId = cert_request
438            .chain_id
439            .ok_or(GrpcProtoConversionError::MissingField)?
440            .try_into()?;
441
442        ensure!(
443            certificate.inner().chain_id() == req_chain_id,
444            GrpcProtoConversionError::InconsistentChainId
445        );
446        Ok(HandleValidatedCertificateRequest { certificate })
447    }
448}
449
450impl TryFrom<api::HandleConfirmedCertificateRequest> for HandleConfirmedCertificateRequest {
451    type Error = GrpcProtoConversionError;
452
453    fn try_from(cert_request: api::HandleConfirmedCertificateRequest) -> Result<Self, Self::Error> {
454        let certificate: ConfirmedBlockCertificate = cert_request
455            .certificate
456            .ok_or(GrpcProtoConversionError::MissingField)?
457            .try_into()?;
458
459        let req_chain_id: ChainId = cert_request
460            .chain_id
461            .ok_or(GrpcProtoConversionError::MissingField)?
462            .try_into()?;
463
464        ensure!(
465            certificate.inner().chain_id() == req_chain_id,
466            GrpcProtoConversionError::InconsistentChainId
467        );
468        Ok(HandleConfirmedCertificateRequest {
469            certificate,
470            wait_for_outgoing_messages: cert_request.wait_for_outgoing_messages,
471        })
472    }
473}
474
475impl TryFrom<HandleConfirmedCertificateRequest> for api::HandleConfirmedCertificateRequest {
476    type Error = GrpcProtoConversionError;
477
478    fn try_from(request: HandleConfirmedCertificateRequest) -> Result<Self, Self::Error> {
479        Ok(Self {
480            chain_id: Some(request.certificate.inner().chain_id().into()),
481            certificate: Some(request.certificate.try_into()?),
482            wait_for_outgoing_messages: request.wait_for_outgoing_messages,
483        })
484    }
485}
486
487impl TryFrom<HandleValidatedCertificateRequest> for api::HandleValidatedCertificateRequest {
488    type Error = GrpcProtoConversionError;
489
490    fn try_from(request: HandleValidatedCertificateRequest) -> Result<Self, Self::Error> {
491        Ok(Self {
492            chain_id: Some(request.certificate.inner().chain_id().into()),
493            certificate: Some(request.certificate.try_into()?),
494        })
495    }
496}
497
498impl TryFrom<HandleTimeoutCertificateRequest> for api::HandleTimeoutCertificateRequest {
499    type Error = GrpcProtoConversionError;
500
501    fn try_from(request: HandleTimeoutCertificateRequest) -> Result<Self, Self::Error> {
502        Ok(Self {
503            chain_id: Some(request.certificate.inner().chain_id().into()),
504            certificate: Some(request.certificate.try_into()?),
505        })
506    }
507}
508
509impl TryFrom<api::Certificate> for TimeoutCertificate {
510    type Error = GrpcProtoConversionError;
511
512    fn try_from(certificate: api::Certificate) -> Result<Self, Self::Error> {
513        let round = bincode::deserialize(&certificate.round)?;
514        let signatures = bincode::deserialize(&certificate.signatures)?;
515        let cert_type = certificate.kind;
516
517        if cert_type == api::CertificateKind::Timeout as i32 {
518            let value: Timeout = bincode::deserialize(&certificate.value)?;
519            Ok(TimeoutCertificate::new(value, round, signatures))
520        } else {
521            Err(GrpcProtoConversionError::InvalidCertificateType)
522        }
523    }
524}
525
526impl TryFrom<api::Certificate> for ValidatedBlockCertificate {
527    type Error = GrpcProtoConversionError;
528
529    fn try_from(certificate: api::Certificate) -> Result<Self, Self::Error> {
530        let round = bincode::deserialize(&certificate.round)?;
531        let signatures = bincode::deserialize(&certificate.signatures)?;
532        let cert_type = certificate.kind;
533
534        if cert_type == api::CertificateKind::Validated as i32 {
535            let value: ValidatedBlock = bincode::deserialize(&certificate.value)?;
536            Ok(ValidatedBlockCertificate::new(value, round, signatures))
537        } else {
538            Err(GrpcProtoConversionError::InvalidCertificateType)
539        }
540    }
541}
542
543impl TryFrom<api::Certificate> for ConfirmedBlockCertificate {
544    type Error = GrpcProtoConversionError;
545
546    fn try_from(certificate: api::Certificate) -> Result<Self, Self::Error> {
547        let round = bincode::deserialize(&certificate.round)?;
548        let signatures = bincode::deserialize(&certificate.signatures)?;
549        let cert_type = certificate.kind;
550
551        if cert_type == api::CertificateKind::Confirmed as i32 {
552            let value: ConfirmedBlock = bincode::deserialize(&certificate.value)?;
553            Ok(ConfirmedBlockCertificate::new(value, round, signatures))
554        } else {
555            Err(GrpcProtoConversionError::InvalidCertificateType)
556        }
557    }
558}
559
560impl TryFrom<TimeoutCertificate> for api::Certificate {
561    type Error = GrpcProtoConversionError;
562
563    fn try_from(certificate: TimeoutCertificate) -> Result<Self, Self::Error> {
564        let round = bincode::serialize(&certificate.round)?;
565        let signatures = bincode::serialize(certificate.signatures())?;
566
567        let value = bincode::serialize(certificate.value())?;
568
569        Ok(Self {
570            value,
571            round,
572            signatures,
573            kind: api::CertificateKind::Timeout as i32,
574        })
575    }
576}
577
578impl TryFrom<ConfirmedBlockCertificate> for api::Certificate {
579    type Error = GrpcProtoConversionError;
580
581    fn try_from(certificate: ConfirmedBlockCertificate) -> Result<Self, Self::Error> {
582        let round = bincode::serialize(&certificate.round)?;
583        let signatures = bincode::serialize(certificate.signatures())?;
584
585        let value = bincode::serialize(certificate.value())?;
586
587        Ok(Self {
588            value,
589            round,
590            signatures,
591            kind: api::CertificateKind::Confirmed as i32,
592        })
593    }
594}
595
596impl TryFrom<ValidatedBlockCertificate> for api::Certificate {
597    type Error = GrpcProtoConversionError;
598
599    fn try_from(certificate: ValidatedBlockCertificate) -> Result<Self, Self::Error> {
600        let round = bincode::serialize(&certificate.round)?;
601        let signatures = bincode::serialize(certificate.signatures())?;
602
603        let value = bincode::serialize(certificate.value())?;
604
605        Ok(Self {
606            value,
607            round,
608            signatures,
609            kind: api::CertificateKind::Validated as i32,
610        })
611    }
612}
613
614impl TryFrom<api::ChainInfoQuery> for ChainInfoQuery {
615    type Error = GrpcProtoConversionError;
616
617    fn try_from(chain_info_query: api::ChainInfoQuery) -> Result<Self, Self::Error> {
618        let request_sent_certificate_hashes_by_heights = chain_info_query
619            .request_sent_certificate_hashes_by_heights
620            .map(|heights| bincode::deserialize(&heights))
621            .transpose()?
622            .unwrap_or_default();
623        let request_leader_timeout = chain_info_query
624            .request_leader_timeout
625            .map(|height_and_round| bincode::deserialize(&height_and_round))
626            .transpose()?;
627        let request_previous_event_blocks = chain_info_query
628            .request_previous_event_blocks
629            .map(|stream_ids| bincode::deserialize(&stream_ids))
630            .transpose()?
631            .unwrap_or_default();
632
633        Ok(Self {
634            request_committees: chain_info_query.request_committees,
635            request_owner_balance: try_proto_convert(chain_info_query.request_owner_balance)?,
636            request_pending_message_bundles: chain_info_query.request_pending_message_bundles,
637            chain_id: try_proto_convert(chain_info_query.chain_id)?,
638            request_received_log_excluding_first_n: chain_info_query
639                .request_received_log_excluding_first_n,
640            test_next_block_height: chain_info_query.test_next_block_height.map(Into::into),
641            request_manager_values: chain_info_query.request_manager_values,
642            request_leader_timeout,
643            request_fallback: chain_info_query.request_fallback,
644            request_sent_certificate_hashes_by_heights,
645            request_previous_event_blocks,
646        })
647    }
648}
649
650impl TryFrom<ChainInfoQuery> for api::ChainInfoQuery {
651    type Error = GrpcProtoConversionError;
652
653    fn try_from(chain_info_query: ChainInfoQuery) -> Result<Self, Self::Error> {
654        let request_sent_certificate_hashes_by_heights =
655            bincode::serialize(&chain_info_query.request_sent_certificate_hashes_by_heights)?;
656        let request_owner_balance = Some(chain_info_query.request_owner_balance.try_into()?);
657        let request_leader_timeout = chain_info_query
658            .request_leader_timeout
659            .map(|height_and_round| bincode::serialize(&height_and_round))
660            .transpose()?;
661        let request_previous_event_blocks =
662            bincode::serialize(&chain_info_query.request_previous_event_blocks)?;
663
664        Ok(Self {
665            chain_id: Some(chain_info_query.chain_id.into()),
666            request_committees: chain_info_query.request_committees,
667            request_owner_balance,
668            request_pending_message_bundles: chain_info_query.request_pending_message_bundles,
669            test_next_block_height: chain_info_query.test_next_block_height.map(Into::into),
670            request_sent_certificate_hashes_by_heights: Some(
671                request_sent_certificate_hashes_by_heights,
672            ),
673            request_received_log_excluding_first_n: chain_info_query
674                .request_received_log_excluding_first_n,
675            request_manager_values: chain_info_query.request_manager_values,
676            request_leader_timeout,
677            request_fallback: chain_info_query.request_fallback,
678            request_previous_event_blocks: Some(request_previous_event_blocks),
679        })
680    }
681}
682
683impl From<ChainId> for api::ChainId {
684    fn from(chain_id: ChainId) -> Self {
685        Self {
686            bytes: chain_id.0.as_bytes().to_vec(),
687        }
688    }
689}
690
691impl TryFrom<api::ChainId> for ChainId {
692    type Error = GrpcProtoConversionError;
693
694    fn try_from(chain_id: api::ChainId) -> Result<Self, Self::Error> {
695        Ok(ChainId::try_from(chain_id.bytes.as_slice())?)
696    }
697}
698
699impl From<AccountPublicKey> for api::AccountPublicKey {
700    fn from(public_key: AccountPublicKey) -> Self {
701        Self {
702            bytes: public_key.as_bytes(),
703        }
704    }
705}
706
707impl From<ValidatorPublicKey> for api::ValidatorPublicKey {
708    fn from(public_key: ValidatorPublicKey) -> Self {
709        Self {
710            bytes: public_key.as_bytes().to_vec(),
711        }
712    }
713}
714
715impl TryFrom<api::ValidatorPublicKey> for ValidatorPublicKey {
716    type Error = GrpcProtoConversionError;
717
718    fn try_from(public_key: api::ValidatorPublicKey) -> Result<Self, Self::Error> {
719        Ok(Self::from_bytes(public_key.bytes.as_slice())?)
720    }
721}
722
723impl TryFrom<api::AccountPublicKey> for AccountPublicKey {
724    type Error = GrpcProtoConversionError;
725
726    fn try_from(public_key: api::AccountPublicKey) -> Result<Self, Self::Error> {
727        Ok(Self::from_slice(public_key.bytes.as_slice())?)
728    }
729}
730
731impl From<AccountSignature> for api::AccountSignature {
732    fn from(signature: AccountSignature) -> Self {
733        Self {
734            bytes: signature.to_bytes(),
735        }
736    }
737}
738
739impl From<ValidatorSignature> for api::ValidatorSignature {
740    fn from(signature: ValidatorSignature) -> Self {
741        Self {
742            bytes: signature.as_bytes().to_vec(),
743        }
744    }
745}
746
747impl TryFrom<api::ValidatorSignature> for ValidatorSignature {
748    type Error = GrpcProtoConversionError;
749
750    fn try_from(signature: api::ValidatorSignature) -> Result<Self, Self::Error> {
751        Self::from_slice(signature.bytes.as_slice()).map_err(GrpcProtoConversionError::CryptoError)
752    }
753}
754
755impl TryFrom<api::AccountSignature> for AccountSignature {
756    type Error = GrpcProtoConversionError;
757
758    fn try_from(signature: api::AccountSignature) -> Result<Self, Self::Error> {
759        Ok(Self::from_slice(signature.bytes.as_slice())?)
760    }
761}
762
763impl TryFrom<ChainInfoResponse> for api::ChainInfoResponse {
764    type Error = GrpcProtoConversionError;
765
766    fn try_from(chain_info_response: ChainInfoResponse) -> Result<Self, Self::Error> {
767        Ok(Self {
768            chain_info: bincode::serialize(&chain_info_response.info)?,
769            signature: chain_info_response.signature.map(Into::into),
770        })
771    }
772}
773
774impl TryFrom<api::ChainInfoResponse> for ChainInfoResponse {
775    type Error = GrpcProtoConversionError;
776
777    fn try_from(chain_info_response: api::ChainInfoResponse) -> Result<Self, Self::Error> {
778        let signature = chain_info_response
779            .signature
780            .map(TryInto::try_into)
781            .transpose()?;
782        let info = bincode::deserialize(chain_info_response.chain_info.as_slice())?;
783        Ok(Self { info, signature })
784    }
785}
786
787impl TryFrom<(ChainId, BlobId)> for api::PendingBlobRequest {
788    type Error = GrpcProtoConversionError;
789
790    fn try_from((chain_id, blob_id): (ChainId, BlobId)) -> Result<Self, Self::Error> {
791        Ok(Self {
792            chain_id: Some(chain_id.into()),
793            blob_id: Some(blob_id.try_into()?),
794        })
795    }
796}
797
798impl TryFrom<api::PendingBlobRequest> for (ChainId, BlobId) {
799    type Error = GrpcProtoConversionError;
800
801    fn try_from(request: PendingBlobRequest) -> Result<Self, Self::Error> {
802        Ok((
803            try_proto_convert(request.chain_id)?,
804            try_proto_convert(request.blob_id)?,
805        ))
806    }
807}
808
809impl TryFrom<(ChainId, BlobContent)> for api::HandlePendingBlobRequest {
810    type Error = GrpcProtoConversionError;
811
812    fn try_from((chain_id, blob_content): (ChainId, BlobContent)) -> Result<Self, Self::Error> {
813        Ok(Self {
814            chain_id: Some(chain_id.into()),
815            blob: Some(blob_content.try_into()?),
816        })
817    }
818}
819
820impl TryFrom<api::HandlePendingBlobRequest> for (ChainId, BlobContent) {
821    type Error = GrpcProtoConversionError;
822
823    fn try_from(request: api::HandlePendingBlobRequest) -> Result<Self, Self::Error> {
824        Ok((
825            try_proto_convert(request.chain_id)?,
826            try_proto_convert(request.blob)?,
827        ))
828    }
829}
830
831impl TryFrom<BlobContent> for api::PendingBlobResult {
832    type Error = GrpcProtoConversionError;
833
834    fn try_from(blob: BlobContent) -> Result<Self, Self::Error> {
835        Ok(Self {
836            inner: Some(api::pending_blob_result::Inner::Blob(blob.try_into()?)),
837        })
838    }
839}
840
841impl TryFrom<NodeError> for api::PendingBlobResult {
842    type Error = GrpcProtoConversionError;
843
844    fn try_from(node_error: NodeError) -> Result<Self, Self::Error> {
845        let error = bincode::serialize(&node_error)?;
846        Ok(api::PendingBlobResult {
847            inner: Some(api::pending_blob_result::Inner::Error(error)),
848        })
849    }
850}
851
852impl From<BlockHeight> for api::BlockHeight {
853    fn from(block_height: BlockHeight) -> Self {
854        Self {
855            height: block_height.0,
856        }
857    }
858}
859
860impl From<api::BlockHeight> for BlockHeight {
861    fn from(block_height: api::BlockHeight) -> Self {
862        Self(block_height.height)
863    }
864}
865
866impl TryFrom<AccountOwner> for api::AccountOwner {
867    type Error = GrpcProtoConversionError;
868
869    fn try_from(account_owner: AccountOwner) -> Result<Self, Self::Error> {
870        Ok(Self {
871            bytes: bincode::serialize(&account_owner)?,
872        })
873    }
874}
875
876impl TryFrom<api::AccountOwner> for AccountOwner {
877    type Error = GrpcProtoConversionError;
878
879    fn try_from(account_owner: api::AccountOwner) -> Result<Self, Self::Error> {
880        Ok(bincode::deserialize(&account_owner.bytes)?)
881    }
882}
883
884impl TryFrom<api::BlobId> for BlobId {
885    type Error = GrpcProtoConversionError;
886
887    fn try_from(blob_id: api::BlobId) -> Result<Self, Self::Error> {
888        Ok(bincode::deserialize(blob_id.bytes.as_slice())?)
889    }
890}
891
892impl TryFrom<api::BlobIds> for Vec<BlobId> {
893    type Error = GrpcProtoConversionError;
894
895    fn try_from(blob_ids: api::BlobIds) -> Result<Self, Self::Error> {
896        Ok(blob_ids
897            .bytes
898            .into_iter()
899            .map(|x| bincode::deserialize(x.as_slice()))
900            .collect::<Result<_, _>>()?)
901    }
902}
903
904impl TryFrom<BlobId> for api::BlobId {
905    type Error = GrpcProtoConversionError;
906
907    fn try_from(blob_id: BlobId) -> Result<Self, Self::Error> {
908        Ok(Self {
909            bytes: bincode::serialize(&blob_id)?,
910        })
911    }
912}
913
914impl TryFrom<Vec<BlobId>> for api::BlobIds {
915    type Error = GrpcProtoConversionError;
916
917    fn try_from(blob_ids: Vec<BlobId>) -> Result<Self, Self::Error> {
918        let bytes = blob_ids
919            .into_iter()
920            .map(|blob_id| bincode::serialize(&blob_id))
921            .collect::<Result<_, _>>()?;
922        Ok(Self { bytes })
923    }
924}
925
926impl TryFrom<api::CryptoHash> for CryptoHash {
927    type Error = GrpcProtoConversionError;
928
929    fn try_from(hash: api::CryptoHash) -> Result<Self, Self::Error> {
930        Ok(CryptoHash::try_from(hash.bytes.as_slice())?)
931    }
932}
933
934impl TryFrom<BlobContent> for api::BlobContent {
935    type Error = GrpcProtoConversionError;
936
937    fn try_from(blob: BlobContent) -> Result<Self, Self::Error> {
938        Ok(Self {
939            bytes: bincode::serialize(&blob)?,
940        })
941    }
942}
943
944impl TryFrom<api::BlobContent> for BlobContent {
945    type Error = GrpcProtoConversionError;
946
947    fn try_from(blob: api::BlobContent) -> Result<Self, Self::Error> {
948        Ok(bincode::deserialize(blob.bytes.as_slice())?)
949    }
950}
951
952impl From<CryptoHash> for api::CryptoHash {
953    fn from(hash: CryptoHash) -> Self {
954        Self {
955            bytes: hash.as_bytes().to_vec(),
956        }
957    }
958}
959
960impl From<Vec<CryptoHash>> for api::CertificatesBatchRequest {
961    fn from(certs: Vec<CryptoHash>) -> Self {
962        Self {
963            hashes: certs.into_iter().map(Into::into).collect(),
964        }
965    }
966}
967
968impl TryFrom<Certificate> for api::Certificate {
969    type Error = GrpcProtoConversionError;
970
971    fn try_from(certificate: Certificate) -> Result<Self, Self::Error> {
972        let round = bincode::serialize(&certificate.round())?;
973        let signatures = bincode::serialize(certificate.signatures())?;
974
975        let (kind, value) = match certificate {
976            Certificate::Confirmed(confirmed) => (
977                api::CertificateKind::Confirmed,
978                bincode::serialize(confirmed.value())?,
979            ),
980            Certificate::Validated(validated) => (
981                api::CertificateKind::Validated,
982                bincode::serialize(validated.value())?,
983            ),
984            Certificate::Timeout(timeout) => (
985                api::CertificateKind::Timeout,
986                bincode::serialize(timeout.value())?,
987            ),
988        };
989
990        Ok(Self {
991            value,
992            round,
993            signatures,
994            kind: kind as i32,
995        })
996    }
997}
998
999impl TryFrom<api::Certificate> for Certificate {
1000    type Error = GrpcProtoConversionError;
1001
1002    fn try_from(certificate: api::Certificate) -> Result<Self, Self::Error> {
1003        let round = bincode::deserialize(&certificate.round)?;
1004        let signatures = bincode::deserialize(&certificate.signatures)?;
1005
1006        let value = if certificate.kind == api::CertificateKind::Confirmed as i32 {
1007            let value: ConfirmedBlock = bincode::deserialize(&certificate.value)?;
1008            Certificate::Confirmed(ConfirmedBlockCertificate::new(value, round, signatures))
1009        } else if certificate.kind == api::CertificateKind::Validated as i32 {
1010            let value: ValidatedBlock = bincode::deserialize(&certificate.value)?;
1011            Certificate::Validated(ValidatedBlockCertificate::new(value, round, signatures))
1012        } else if certificate.kind == api::CertificateKind::Timeout as i32 {
1013            let value: Timeout = bincode::deserialize(&certificate.value)?;
1014            Certificate::Timeout(TimeoutCertificate::new(value, round, signatures))
1015        } else {
1016            return Err(GrpcProtoConversionError::InvalidCertificateType);
1017        };
1018
1019        Ok(value)
1020    }
1021}
1022
1023impl TryFrom<Vec<Certificate>> for api::CertificatesBatchResponse {
1024    type Error = GrpcProtoConversionError;
1025
1026    fn try_from(certs: Vec<Certificate>) -> Result<Self, Self::Error> {
1027        Ok(Self {
1028            certificates: certs
1029                .into_iter()
1030                .map(TryInto::try_into)
1031                .collect::<Result<_, _>>()?,
1032        })
1033    }
1034}
1035
1036impl TryFrom<api::CertificatesBatchResponse> for Vec<Certificate> {
1037    type Error = GrpcProtoConversionError;
1038
1039    fn try_from(response: api::CertificatesBatchResponse) -> Result<Self, Self::Error> {
1040        response
1041            .certificates
1042            .into_iter()
1043            .map(Certificate::try_from)
1044            .collect()
1045    }
1046}
1047
1048impl From<CertificatesByHeightRequest> for api::DownloadCertificatesByHeightsRequest {
1049    fn from(request: CertificatesByHeightRequest) -> Self {
1050        Self {
1051            chain_id: Some(request.chain_id.into()),
1052            heights: request.heights.into_iter().map(Into::into).collect(),
1053        }
1054    }
1055}
1056
1057impl TryFrom<api::DownloadCertificatesByHeightsRequest> for CertificatesByHeightRequest {
1058    type Error = GrpcProtoConversionError;
1059
1060    fn try_from(request: api::DownloadCertificatesByHeightsRequest) -> Result<Self, Self::Error> {
1061        Ok(Self {
1062            chain_id: try_proto_convert(request.chain_id)?,
1063            heights: request.heights.into_iter().map(Into::into).collect(),
1064        })
1065    }
1066}
1067
1068impl From<Vec<EventId>> for api::EventBlockHeightsRequest {
1069    fn from(event_ids: Vec<EventId>) -> Self {
1070        Self {
1071            event_ids: bincode::serialize(&event_ids).expect("serialize event_ids"),
1072        }
1073    }
1074}
1075
1076impl TryFrom<api::EventBlockHeightsRequest> for Vec<EventId> {
1077    type Error = GrpcProtoConversionError;
1078
1079    fn try_from(request: api::EventBlockHeightsRequest) -> Result<Self, Self::Error> {
1080        Ok(bincode::deserialize(&request.event_ids)?)
1081    }
1082}
1083
1084impl From<Vec<Option<BlockHeight>>> for api::EventBlockHeightsResponse {
1085    fn from(heights: Vec<Option<BlockHeight>>) -> Self {
1086        Self {
1087            heights: bincode::serialize(&heights).expect("serialize heights"),
1088        }
1089    }
1090}
1091
1092impl TryFrom<api::EventBlockHeightsResponse> for Vec<Option<BlockHeight>> {
1093    type Error = GrpcProtoConversionError;
1094
1095    fn try_from(response: api::EventBlockHeightsResponse) -> Result<Self, Self::Error> {
1096        Ok(bincode::deserialize(&response.heights)?)
1097    }
1098}
1099
1100#[cfg(test)]
1101pub mod tests {
1102    use std::{borrow::Cow, collections::BTreeMap, fmt::Debug};
1103
1104    use linera_base::{
1105        crypto::{AccountSecretKey, BcsSignable, CryptoHash, Secp256k1SecretKey, ValidatorKeypair},
1106        data_types::{Amount, Blob, Epoch, Round, Timestamp},
1107    };
1108    use linera_chain::{
1109        data_types::{BlockExecutionOutcome, OriginalProposal, ProposedBlock},
1110        test::make_first_block,
1111        types::CertificateKind,
1112    };
1113    use linera_core::data_types::ChainInfo;
1114    use serde::{Deserialize, Serialize};
1115
1116    use super::*;
1117
1118    #[derive(Debug, Serialize, Deserialize)]
1119    struct Foo(String);
1120
1121    impl BcsSignable<'_> for Foo {}
1122
1123    fn dummy_chain_id(index: u32) -> ChainId {
1124        ChainId(CryptoHash::test_hash(format!("chain{index}")))
1125    }
1126
1127    fn get_block() -> ProposedBlock {
1128        make_first_block(dummy_chain_id(0))
1129    }
1130
1131    /// A convenience function for testing. It converts a type into its
1132    /// RPC equivalent and back - asserting that the two are equal.
1133    fn round_trip_check<T, M>(value: &T)
1134    where
1135        T: TryFrom<M> + Clone + Debug + Eq,
1136        M: TryFrom<T>,
1137        T::Error: Debug,
1138        M::Error: Debug,
1139    {
1140        let message = M::try_from(value.clone()).unwrap();
1141        let round_trip_value: T = message.try_into().unwrap();
1142        assert_eq!(value, &round_trip_value);
1143    }
1144
1145    #[test]
1146    pub fn test_public_key() {
1147        let account_key = AccountSecretKey::generate().public();
1148        round_trip_check::<_, api::AccountPublicKey>(&account_key);
1149
1150        let validator_key = ValidatorKeypair::generate().public_key;
1151        round_trip_check::<_, api::ValidatorPublicKey>(&validator_key);
1152    }
1153
1154    #[test]
1155    pub fn test_signature() {
1156        let validator_key_pair = ValidatorKeypair::generate();
1157        let validator_signature =
1158            ValidatorSignature::new(&Foo("test".into()), &validator_key_pair.secret_key);
1159        round_trip_check::<_, api::ValidatorSignature>(&validator_signature);
1160
1161        let account_key_pair = AccountSecretKey::generate();
1162        let account_signature = account_key_pair.sign(&Foo("test".into()));
1163        round_trip_check::<_, api::AccountSignature>(&account_signature);
1164    }
1165
1166    #[test]
1167    pub fn test_owner() {
1168        let key_pair = AccountSecretKey::generate();
1169        let owner = AccountOwner::from(key_pair.public());
1170        round_trip_check::<_, api::AccountOwner>(&owner);
1171    }
1172
1173    #[test]
1174    pub fn test_block_height() {
1175        let block_height = BlockHeight::from(10);
1176        round_trip_check::<_, api::BlockHeight>(&block_height);
1177    }
1178
1179    #[test]
1180    pub fn test_chain_id() {
1181        let chain_id = dummy_chain_id(0);
1182        round_trip_check::<_, api::ChainId>(&chain_id);
1183    }
1184
1185    #[test]
1186    pub fn test_chain_info_response() {
1187        let chain_info = Box::new(ChainInfo {
1188            chain_id: dummy_chain_id(0),
1189            epoch: Epoch::ZERO,
1190            description: None,
1191            manager: Box::default(),
1192            chain_balance: Amount::ZERO,
1193            block_hash: None,
1194            timestamp: Timestamp::default(),
1195            next_block_height: BlockHeight::ZERO,
1196            state_hash: None,
1197            requested_committees: None,
1198            requested_owner_balance: None,
1199            requested_pending_message_bundles: vec![],
1200            requested_sent_certificate_hashes: vec![],
1201            count_received_log: 0,
1202            requested_received_log: vec![],
1203            requested_previous_event_blocks: BTreeMap::new(),
1204        });
1205
1206        let chain_info_response_none = ChainInfoResponse {
1207            // `info` is bincode so no need to test conversions extensively
1208            info: chain_info.clone(),
1209            signature: None,
1210        };
1211        round_trip_check::<_, api::ChainInfoResponse>(&chain_info_response_none);
1212
1213        let chain_info_response_some = ChainInfoResponse {
1214            // `info` is bincode so no need to test conversions extensively
1215            info: chain_info,
1216            signature: Some(ValidatorSignature::new(
1217                &Foo("test".into()),
1218                &ValidatorKeypair::generate().secret_key,
1219            )),
1220        };
1221        round_trip_check::<_, api::ChainInfoResponse>(&chain_info_response_some);
1222    }
1223
1224    #[test]
1225    pub fn test_chain_info_query() {
1226        let chain_info_query_none = ChainInfoQuery::new(dummy_chain_id(0));
1227        round_trip_check::<_, api::ChainInfoQuery>(&chain_info_query_none);
1228
1229        let chain_info_query_some = ChainInfoQuery {
1230            chain_id: dummy_chain_id(0),
1231            test_next_block_height: Some(BlockHeight::from(10)),
1232            request_committees: false,
1233            request_owner_balance: AccountOwner::CHAIN,
1234            request_pending_message_bundles: false,
1235            request_received_log_excluding_first_n: None,
1236            request_manager_values: false,
1237            request_leader_timeout: None,
1238            request_fallback: true,
1239            request_sent_certificate_hashes_by_heights: (3..8).map(BlockHeight::from).collect(),
1240            request_previous_event_blocks: Vec::new(),
1241        };
1242        round_trip_check::<_, api::ChainInfoQuery>(&chain_info_query_some);
1243    }
1244
1245    #[test]
1246    pub fn test_pending_blob_request() {
1247        let chain_id = dummy_chain_id(2);
1248        let blob_id = Blob::new(BlobContent::new_data(*b"foo")).id();
1249        let pending_blob_request = (chain_id, blob_id);
1250        round_trip_check::<_, api::PendingBlobRequest>(&pending_blob_request);
1251    }
1252
1253    #[test]
1254    pub fn test_pending_blob_result() {
1255        let blob = BlobContent::new_data(*b"foo");
1256        round_trip_check::<_, api::PendingBlobResult>(&blob);
1257    }
1258
1259    #[test]
1260    pub fn test_handle_pending_blob_request() {
1261        let chain_id = dummy_chain_id(2);
1262        let blob_content = BlobContent::new_data(*b"foo");
1263        let pending_blob_request = (chain_id, blob_content);
1264        round_trip_check::<_, api::HandlePendingBlobRequest>(&pending_blob_request);
1265    }
1266
1267    #[test]
1268    pub fn test_lite_certificate() {
1269        let key_pair = ValidatorKeypair::generate();
1270        let certificate = LiteCertificate {
1271            value: LiteValue {
1272                value_hash: CryptoHash::new(&Foo("value".into())),
1273                chain_id: dummy_chain_id(0),
1274                kind: CertificateKind::Validated,
1275            },
1276            round: Round::MultiLeader(2),
1277            signatures: Cow::Owned(vec![(
1278                key_pair.public_key,
1279                ValidatorSignature::new(&Foo("test".into()), &key_pair.secret_key),
1280            )]),
1281        };
1282        let request = HandleLiteCertRequest {
1283            certificate,
1284            wait_for_outgoing_messages: true,
1285        };
1286
1287        round_trip_check::<_, api::LiteCertificate>(&request);
1288    }
1289
1290    #[test]
1291    pub fn test_certificate() {
1292        let key_pair = ValidatorKeypair::generate();
1293        let certificate = ValidatedBlockCertificate::new(
1294            ValidatedBlock::new(
1295                BlockExecutionOutcome {
1296                    state_hash: CryptoHash::new(&Foo("test".into())),
1297                    ..BlockExecutionOutcome::default()
1298                }
1299                .with(get_block()),
1300            ),
1301            Round::MultiLeader(3),
1302            vec![(
1303                key_pair.public_key,
1304                ValidatorSignature::new(&Foo("test".into()), &key_pair.secret_key),
1305            )],
1306        );
1307        let request = HandleValidatedCertificateRequest { certificate };
1308
1309        round_trip_check::<_, api::HandleValidatedCertificateRequest>(&request);
1310    }
1311
1312    #[test]
1313    pub fn test_cross_chain_request() {
1314        let cross_chain_request_update_recipient = CrossChainRequest::UpdateRecipient {
1315            sender: dummy_chain_id(0),
1316            recipient: dummy_chain_id(0),
1317            bundles: vec![],
1318            previous_height: Some(BlockHeight::from(42)),
1319        };
1320        round_trip_check::<_, api::CrossChainRequest>(&cross_chain_request_update_recipient);
1321
1322        let cross_chain_request_confirm_updated_recipient =
1323            CrossChainRequest::ConfirmUpdatedRecipient {
1324                sender: dummy_chain_id(0),
1325                recipient: dummy_chain_id(0),
1326                latest_height: BlockHeight(1),
1327            };
1328        round_trip_check::<_, api::CrossChainRequest>(
1329            &cross_chain_request_confirm_updated_recipient,
1330        );
1331    }
1332
1333    #[test]
1334    pub fn test_block_proposal() {
1335        let key_pair = ValidatorKeypair::generate();
1336        let outcome = BlockExecutionOutcome {
1337            state_hash: CryptoHash::new(&Foo("validated".into())),
1338            ..BlockExecutionOutcome::default()
1339        };
1340        let certificate = ValidatedBlockCertificate::new(
1341            ValidatedBlock::new(outcome.clone().with(get_block())),
1342            Round::SingleLeader(2),
1343            vec![(
1344                key_pair.public_key,
1345                ValidatorSignature::new(&Foo("signed".into()), &key_pair.secret_key),
1346            )],
1347        )
1348        .lite_certificate()
1349        .cloned();
1350        let key_pair = AccountSecretKey::Secp256k1(Secp256k1SecretKey::generate());
1351        let block_proposal = BlockProposal {
1352            content: ProposalContent {
1353                block: get_block(),
1354                round: Round::SingleLeader(4),
1355                outcome: Some(outcome),
1356            },
1357            signature: key_pair.sign(&Foo("test".into())),
1358            original_proposal: Some(OriginalProposal::Regular { certificate }),
1359        };
1360
1361        round_trip_check::<_, api::BlockProposal>(&block_proposal);
1362    }
1363
1364    #[test]
1365    pub fn test_notification() {
1366        let notification = Notification {
1367            chain_id: dummy_chain_id(0),
1368            reason: linera_core::worker::Reason::NewBlock {
1369                height: BlockHeight(0),
1370                hash: CryptoHash::new(&Foo("".into())),
1371            },
1372        };
1373        let message = api::Notification::try_from(notification.clone()).unwrap();
1374        assert_eq!(
1375            Some(notification),
1376            Option::<Notification>::try_from(message).unwrap()
1377        );
1378
1379        let ack = api::Notification::default();
1380        assert_eq!(None, Option::<Notification>::try_from(ack).unwrap());
1381    }
1382}