Skip to main content

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