Skip to main content

linera_rpc/grpc/
conversions.rs

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