sui_types/
messages_grpc.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::base_types::{ObjectID, SequenceNumber, TransactionDigest};
5use crate::crypto::{AuthoritySignInfo, AuthorityStrongQuorumSignInfo};
6use crate::effects::{
7    SignedTransactionEffects, TransactionEvents, VerifiedSignedTransactionEffects,
8};
9use crate::error::{SuiError, SuiErrorKind};
10use crate::object::Object;
11use crate::transaction::{CertifiedTransaction, SenderSignedData, SignedTransaction, Transaction};
12
13use bytes::Bytes;
14use move_core_types::annotated_value::MoveStructLayout;
15use serde::{Deserialize, Serialize};
16
17use mysten_metrics::TX_TYPE_SHARED_OBJ_TX;
18use mysten_metrics::TX_TYPE_SINGLE_WRITER_TX;
19
20use strum::EnumIter;
21
22#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
23pub enum ObjectInfoRequestKind {
24    /// Request the latest object state.
25    LatestObjectInfo,
26    /// Request a specific version of the object.
27    /// This is used only for debugging purpose and will not work as a generic solution
28    /// since we don't keep around all historic object versions.
29    /// No production code should depend on this kind.
30    PastObjectInfoDebug(SequenceNumber),
31}
32
33/// Layout generation options -- you can either generate or not generate the layout.
34#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
35pub enum LayoutGenerationOption {
36    Generate,
37    None,
38}
39
40/// A request for information about an object and optionally its
41/// parent certificate at a specific version.
42#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
43pub struct ObjectInfoRequest {
44    /// The id of the object to retrieve, at the latest version.
45    pub object_id: ObjectID,
46    /// if true return the layout of the object.
47    pub generate_layout: LayoutGenerationOption,
48    /// The type of request, either latest object info or the past.
49    pub request_kind: ObjectInfoRequestKind,
50}
51
52impl ObjectInfoRequest {
53    pub fn past_object_info_debug_request(
54        object_id: ObjectID,
55        version: SequenceNumber,
56        generate_layout: LayoutGenerationOption,
57    ) -> Self {
58        ObjectInfoRequest {
59            object_id,
60            generate_layout,
61            request_kind: ObjectInfoRequestKind::PastObjectInfoDebug(version),
62        }
63    }
64
65    pub fn latest_object_info_request(
66        object_id: ObjectID,
67        generate_layout: LayoutGenerationOption,
68    ) -> Self {
69        ObjectInfoRequest {
70            object_id,
71            generate_layout,
72            request_kind: ObjectInfoRequestKind::LatestObjectInfo,
73        }
74    }
75}
76
77/// This message provides information about the latest object and its lock.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct ObjectInfoResponse {
80    /// Value of the requested object in this authority
81    pub object: Object,
82    /// Schema of the Move value inside this object.
83    /// None if the object is a Move package, or the request did not ask for the layout
84    pub layout: Option<MoveStructLayout>,
85    /// Transaction the object is locked on in this authority.
86    /// None if the object is not currently locked by this authority.
87    /// This should be only used for debugging purpose, such as from sui-tool. No prod clients should
88    /// rely on it.
89    pub lock_for_debugging: Option<SignedTransaction>,
90}
91
92/// Verified version of `ObjectInfoResponse`. `layout` and `lock_for_debugging` are skipped because they
93/// are not needed and we don't want to verify them.
94#[derive(Debug, Clone)]
95pub struct VerifiedObjectInfoResponse {
96    /// Value of the requested object in this authority
97    pub object: Object,
98}
99
100#[derive(Clone, Debug, Serialize, Deserialize)]
101pub struct TransactionInfoRequest {
102    pub transaction_digest: TransactionDigest,
103}
104
105#[derive(Clone, Debug, Serialize, Deserialize)]
106#[allow(clippy::large_enum_variant)]
107pub enum TransactionStatus {
108    /// Signature over the transaction.
109    Signed(AuthoritySignInfo),
110    /// For executed transaction, we could return an optional certificate signature on the transaction
111    /// (i.e. the signature part of the CertifiedTransaction), as well as the signed effects.
112    /// The certificate signature is optional because for transactions executed in previous
113    /// epochs, we won't keep around the certificate signatures.
114    Executed(
115        Option<AuthorityStrongQuorumSignInfo>,
116        SignedTransactionEffects,
117        TransactionEvents,
118    ),
119}
120
121impl TransactionStatus {
122    pub fn into_signed_for_testing(self) -> AuthoritySignInfo {
123        match self {
124            Self::Signed(s) => s,
125            _ => unreachable!("Incorrect response type"),
126        }
127    }
128
129    pub fn into_effects_for_testing(self) -> SignedTransactionEffects {
130        match self {
131            Self::Executed(_, e, _) => e,
132            _ => unreachable!("Incorrect response type"),
133        }
134    }
135}
136
137impl PartialEq for TransactionStatus {
138    fn eq(&self, other: &Self) -> bool {
139        match self {
140            Self::Signed(s1) => match other {
141                Self::Signed(s2) => s1.epoch == s2.epoch,
142                _ => false,
143            },
144            Self::Executed(c1, e1, ev1) => match other {
145                Self::Executed(c2, e2, ev2) => {
146                    c1.as_ref().map(|a| a.epoch) == c2.as_ref().map(|a| a.epoch)
147                        && e1.epoch() == e2.epoch()
148                        && e1.digest() == e2.digest()
149                        && ev1.digest() == ev2.digest()
150                }
151                _ => false,
152            },
153        }
154    }
155}
156
157#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
158pub struct HandleTransactionResponse {
159    pub status: TransactionStatus,
160}
161
162#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
163pub struct TransactionInfoResponse {
164    pub transaction: SenderSignedData,
165    pub status: TransactionStatus,
166}
167
168#[derive(Clone, Debug, Serialize, Deserialize)]
169pub struct HandleCertificateResponseV2 {
170    pub signed_effects: SignedTransactionEffects,
171    pub events: TransactionEvents,
172    /// Not used. Full node local execution fast path was deprecated.
173    pub fastpath_input_objects: Vec<Object>,
174}
175
176#[derive(Clone, Debug, Serialize, Deserialize)]
177pub struct SubmitCertificateResponse {
178    /// If transaction is already executed, return same result as handle_certificate
179    pub executed: Option<HandleCertificateResponseV2>,
180}
181
182#[derive(Clone, Debug)]
183pub struct VerifiedHandleCertificateResponse {
184    pub signed_effects: VerifiedSignedTransactionEffects,
185    pub events: TransactionEvents,
186}
187
188#[derive(Serialize, Deserialize, Clone, Debug)]
189pub struct SystemStateRequest {
190    // This is needed to make gRPC happy.
191    pub _unused: bool,
192}
193
194/// Response type for version 3 of the handle certificate validator API.
195///
196/// The corresponding version 3 request type allows for a client to request events as well as
197/// input/output objects from a transaction's execution. Given Validators operate with very
198/// aggressive object pruning, the return of input/output objects is only done immediately after
199/// the transaction has been executed locally on the validator and will not be returned for
200/// requests to previously executed transactions.
201#[derive(Clone, Debug, Serialize, Deserialize)]
202pub struct HandleCertificateResponseV3 {
203    pub effects: SignedTransactionEffects,
204    pub events: Option<TransactionEvents>,
205
206    /// If requested, will included all initial versions of objects modified in this transaction.
207    /// This includes owned objects included as input into the transaction as well as the assigned
208    /// versions of shared objects.
209    //
210    // TODO: In the future we may want to include shared objects or child objects which were read
211    // but not modified during execution.
212    pub input_objects: Option<Vec<Object>>,
213
214    /// If requested, will included all changed objects, including mutated, created and unwrapped
215    /// objects. In other words, all objects that still exist in the object state after this
216    /// transaction.
217    pub output_objects: Option<Vec<Object>>,
218    pub auxiliary_data: Option<Vec<u8>>,
219}
220
221#[derive(Clone, Debug, Serialize, Deserialize)]
222pub struct HandleCertificateRequestV3 {
223    pub certificate: CertifiedTransaction,
224
225    pub include_events: bool,
226    pub include_input_objects: bool,
227    pub include_output_objects: bool,
228    pub include_auxiliary_data: bool,
229}
230
231impl From<HandleCertificateResponseV3> for HandleCertificateResponseV2 {
232    fn from(value: HandleCertificateResponseV3) -> Self {
233        Self {
234            signed_effects: value.effects,
235            events: value.events.unwrap_or_default(),
236            fastpath_input_objects: Vec::new(),
237        }
238    }
239}
240
241/// Response type for the handle Soft Bundle certificates validator API.
242/// If `wait_for_effects` is true, it is guaranteed that:
243///  - Number of responses will be equal to the number of input transactions.
244///  - The order of the responses matches the order of the input transactions.
245///
246/// Otherwise, `responses` will be empty.
247#[derive(Clone, Debug, Serialize, Deserialize)]
248pub struct HandleSoftBundleCertificatesResponseV3 {
249    pub responses: Vec<HandleCertificateResponseV3>,
250}
251
252/// Soft Bundle request.  See [SIP-19](https://github.com/sui-foundation/sips/blob/main/sips/sip-19.md).
253#[derive(Clone, Debug, Serialize, Deserialize)]
254pub struct HandleSoftBundleCertificatesRequestV3 {
255    pub certificates: Vec<CertifiedTransaction>,
256
257    pub wait_for_effects: bool,
258    pub include_events: bool,
259    pub include_input_objects: bool,
260    pub include_output_objects: bool,
261    pub include_auxiliary_data: bool,
262}
263
264// =========== ExecutedData ===========
265
266#[derive(Default, Clone)]
267pub struct ExecutedData {
268    pub effects: crate::effects::TransactionEffects,
269    pub events: Option<crate::effects::TransactionEvents>,
270    pub input_objects: Vec<crate::object::Object>,
271    pub output_objects: Vec<crate::object::Object>,
272}
273
274#[derive(Clone, prost::Message)]
275pub struct RawExecutedData {
276    #[prost(bytes = "bytes", tag = "1")]
277    pub effects: Bytes,
278    #[prost(bytes = "bytes", optional, tag = "2")]
279    pub events: Option<Bytes>,
280    #[prost(bytes = "bytes", repeated, tag = "3")]
281    pub input_objects: Vec<Bytes>,
282    #[prost(bytes = "bytes", repeated, tag = "4")]
283    pub output_objects: Vec<Bytes>,
284}
285
286// =========== SubmitTx types ===========
287
288#[derive(Clone, Debug)]
289pub struct SubmitTxRequest {
290    pub transaction: Option<Transaction>,
291    pub ping_type: Option<PingType>,
292}
293
294impl SubmitTxRequest {
295    pub fn new_transaction(transaction: Transaction) -> Self {
296        Self {
297            transaction: Some(transaction),
298            ping_type: None,
299        }
300    }
301
302    pub fn new_ping(ping_type: PingType) -> Self {
303        Self {
304            transaction: None,
305            ping_type: Some(ping_type),
306        }
307    }
308
309    pub fn tx_type(&self) -> TxType {
310        if let Some(ping_type) = self.ping_type {
311            return if ping_type == PingType::FastPath {
312                TxType::SingleWriter
313            } else {
314                TxType::SharedObject
315            };
316        }
317        let transaction = self.transaction.as_ref().unwrap();
318        if transaction.is_consensus_tx() {
319            TxType::SharedObject
320        } else {
321            TxType::SingleWriter
322        }
323    }
324
325    /// Returns the digest of the transaction if it is a transaction request.
326    /// Returns None if it is a ping request.
327    pub fn tx_digest(&self) -> Option<TransactionDigest> {
328        self.transaction.as_ref().map(|t| *t.digest())
329    }
330}
331
332#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
333pub enum TxType {
334    SingleWriter,
335    SharedObject,
336}
337
338impl TxType {
339    pub fn as_str(&self) -> &str {
340        match self {
341            TxType::SingleWriter => TX_TYPE_SINGLE_WRITER_TX,
342            TxType::SharedObject => TX_TYPE_SHARED_OBJ_TX,
343        }
344    }
345}
346
347impl SubmitTxRequest {
348    pub fn into_raw(&self) -> Result<RawSubmitTxRequest, SuiError> {
349        let transactions = if let Some(transaction) = &self.transaction {
350            vec![
351                bcs::to_bytes(&transaction)
352                    .map_err(|e| SuiErrorKind::TransactionSerializationError {
353                        error: e.to_string(),
354                    })?
355                    .into(),
356            ]
357        } else {
358            vec![]
359        };
360
361        let submit_type = if self.ping_type.is_some() {
362            SubmitTxType::Ping
363        } else {
364            SubmitTxType::Default
365        };
366
367        Ok(RawSubmitTxRequest {
368            transactions,
369            submit_type: submit_type.into(),
370        })
371    }
372}
373
374#[derive(Clone)]
375pub enum SubmitTxResult {
376    Submitted {
377        consensus_position: crate::messages_consensus::ConsensusPosition,
378    },
379    Executed {
380        effects_digest: crate::digests::TransactionEffectsDigest,
381        // Response should always include details for executed transactions.
382        // TODO(fastpath): validate this field is always present and return an error during deserialization.
383        details: Option<Box<ExecutedData>>,
384        // Whether the transaction was executed using fast path.
385        fast_path: bool,
386    },
387    Rejected {
388        error: crate::error::SuiError,
389    },
390}
391
392impl std::fmt::Debug for SubmitTxResult {
393    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394        match self {
395            Self::Submitted { consensus_position } => f
396                .debug_struct("Submitted")
397                .field("consensus_position", consensus_position)
398                .finish(),
399            Self::Executed {
400                effects_digest,
401                fast_path,
402                ..
403            } => f
404                .debug_struct("Executed")
405                .field("effects_digest", &format_args!("{}", effects_digest))
406                .field("fast_path", fast_path)
407                .finish(),
408            Self::Rejected { error } => f.debug_struct("Rejected").field("error", &error).finish(),
409        }
410    }
411}
412
413#[derive(Clone, Debug)]
414pub struct SubmitTxResponse {
415    pub results: Vec<SubmitTxResult>,
416}
417
418#[derive(Clone, prost::Message)]
419pub struct RawSubmitTxRequest {
420    /// The transactions to be submitted. When the vector is empty, then this is treated as a ping request.
421    #[prost(bytes = "bytes", repeated, tag = "1")]
422    pub transactions: Vec<Bytes>,
423
424    /// The type of submission.
425    #[prost(enumeration = "SubmitTxType", tag = "2")]
426    pub submit_type: i32,
427}
428
429#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, prost::Enumeration)]
430#[repr(i32)]
431pub enum SubmitTxType {
432    /// Default submission, submitting one or more transactions.
433    /// When there are multiple transactions, allow the transactions to be included separately
434    /// and out of order in blocks (batch).
435    Default = 0,
436    /// Ping request to measure latency, no transactions.
437    Ping = 1,
438    /// When submitting multiple transactions, attempt to include them in
439    /// the same block with the same order (soft bundle).
440    SoftBundle = 2,
441}
442
443#[derive(Clone, prost::Message)]
444pub struct RawSubmitTxResponse {
445    // Results corresponding to each transaction in the request.
446    #[prost(message, repeated, tag = "1")]
447    pub results: Vec<RawSubmitTxResult>,
448}
449
450#[derive(Clone, prost::Message)]
451pub struct RawSubmitTxResult {
452    #[prost(oneof = "RawValidatorSubmitStatus", tags = "1, 2, 3")]
453    pub inner: Option<RawValidatorSubmitStatus>,
454}
455
456#[derive(Clone, prost::Oneof)]
457pub enum RawValidatorSubmitStatus {
458    // Serialized Consensus Position.
459    #[prost(bytes = "bytes", tag = "1")]
460    Submitted(Bytes),
461
462    // Transaction has already been executed (finalized).
463    #[prost(message, tag = "2")]
464    Executed(RawExecutedStatus),
465
466    // Transaction is rejected from consensus submission.
467    #[prost(message, tag = "3")]
468    Rejected(RawRejectedStatus),
469}
470
471#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, prost::Enumeration)]
472#[repr(i32)]
473pub enum PingType {
474    /// Measures the end to end latency from when a transaction is included by a proposed block,
475    /// to when the block is committed by consensus.
476    Consensus = 0,
477    /// Measures the end to end latency from when a transaction is included by a proposed block,
478    /// to when the block is certified.
479    FastPath = 1,
480}
481
482impl PingType {
483    pub fn as_str(&self) -> &str {
484        match self {
485            PingType::FastPath => "fastpath",
486            PingType::Consensus => "consensus",
487        }
488    }
489}
490
491// =========== WaitForEffects types ===========
492
493pub struct WaitForEffectsRequest {
494    pub transaction_digest: Option<crate::digests::TransactionDigest>,
495    /// If consensus position is provided, waits in the server handler for the transaction in it to execute,
496    /// either in fastpath outputs or finalized.
497    /// If it is not provided, only waits for finalized effects of the transaction in the server handler,
498    /// but not for fastpath outputs.
499    pub consensus_position: Option<crate::messages_consensus::ConsensusPosition>,
500    /// Whether to include details of the effects,
501    /// including the effects content, events, input objects, and output objects.
502    pub include_details: bool,
503    /// Type of ping request, or None if this is not a ping request.
504    pub ping_type: Option<PingType>,
505}
506
507#[derive(Clone)]
508pub enum WaitForEffectsResponse {
509    Executed {
510        effects_digest: crate::digests::TransactionEffectsDigest,
511        details: Option<Box<ExecutedData>>,
512        fast_path: bool,
513    },
514    // The transaction was rejected by consensus.
515    Rejected {
516        // The reason of the reject vote casted by the validator.
517        // If None, the validator did not cast a reject vote.
518        error: Option<crate::error::SuiError>,
519    },
520    // The transaction position is expired, with the local epoch and committed round.
521    // When round is None, the expiration is due to lagging epoch in the request.
522    Expired {
523        epoch: u64,
524        round: Option<u32>,
525    },
526}
527
528impl std::fmt::Debug for WaitForEffectsResponse {
529    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
530        match self {
531            Self::Executed {
532                effects_digest,
533                fast_path,
534                ..
535            } => f
536                .debug_struct("Executed")
537                .field("effects_digest", effects_digest)
538                .field("fast_path", fast_path)
539                .finish(),
540            Self::Rejected { error } => f.debug_struct("Rejected").field("error", error).finish(),
541            Self::Expired { epoch, round } => f
542                .debug_struct("Expired")
543                .field("epoch", epoch)
544                .field("round", round)
545                .finish(),
546        }
547    }
548}
549
550#[derive(Clone, prost::Message)]
551pub struct RawWaitForEffectsRequest {
552    /// The transaction's digest. If it's a ping request, then this will practically be ignored.
553    #[prost(bytes = "bytes", optional, tag = "1")]
554    pub transaction_digest: Option<Bytes>,
555
556    /// If provided, wait for the consensus position to execute and wait for fastpath outputs of the transaction,
557    /// in addition to waiting for finalized effects.
558    /// If not provided, only wait for finalized effects.
559    #[prost(bytes = "bytes", optional, tag = "2")]
560    pub consensus_position: Option<Bytes>,
561
562    /// Whether to include details of the effects,
563    /// including the effects content, events, input objects, and output objects.
564    #[prost(bool, tag = "3")]
565    pub include_details: bool,
566
567    /// Set when this is a ping request, to differentiate between fastpath and consensus pings.
568    #[prost(enumeration = "PingType", optional, tag = "4")]
569    pub ping_type: Option<i32>,
570}
571
572impl RawWaitForEffectsRequest {
573    pub fn get_ping_type(&self) -> Option<PingType> {
574        self.ping_type
575            .map(|p| PingType::try_from(p).expect("Invalid ping type"))
576    }
577}
578
579#[derive(Clone, prost::Message)]
580pub struct RawWaitForEffectsResponse {
581    // In order to represent an enum in protobuf, we need to use oneof.
582    // However, oneof also allows the value to be unset, which corresponds to None value.
583    // Hence, we need to use Option type for `inner`.
584    // We expect the value to be set in a valid response.
585    #[prost(oneof = "RawValidatorTransactionStatus", tags = "1, 2, 3")]
586    pub inner: Option<RawValidatorTransactionStatus>,
587}
588
589#[derive(Clone, prost::Oneof)]
590pub enum RawValidatorTransactionStatus {
591    #[prost(message, tag = "1")]
592    Executed(RawExecutedStatus),
593    #[prost(message, tag = "2")]
594    Rejected(RawRejectedStatus),
595    #[prost(message, tag = "3")]
596    Expired(RawExpiredStatus),
597}
598
599#[derive(Clone, prost::Message)]
600pub struct RawExecutedStatus {
601    #[prost(bytes = "bytes", tag = "1")]
602    pub effects_digest: Bytes,
603    #[prost(message, optional, tag = "2")]
604    pub details: Option<RawExecutedData>,
605    #[prost(bool, tag = "3")]
606    pub fast_path: bool,
607}
608
609#[derive(Clone, prost::Message)]
610pub struct RawRejectedStatus {
611    #[prost(bytes = "bytes", optional, tag = "1")]
612    pub error: Option<Bytes>,
613}
614
615#[derive(Clone, prost::Message)]
616pub struct RawExpiredStatus {
617    // Validator's current epoch.
618    #[prost(uint64, tag = "1")]
619    pub epoch: u64,
620    // Validator's current round. 0 if it is not yet checked.
621    #[prost(uint32, optional, tag = "2")]
622    pub round: Option<u32>,
623}
624
625// =========== ValidatorHealth types ===========
626
627/// Request for validator health information (used for latency measurement)
628#[derive(Clone, Debug, Default)]
629pub struct ValidatorHealthRequest {}
630
631/// Response with validator health metrics (data collected but not used for scoring yet)
632#[derive(Clone, Debug, Default)]
633pub struct ValidatorHealthResponse {
634    /// Number of in-flight execution transactions from execution scheduler
635    pub num_inflight_execution_transactions: u64,
636    /// Number of in-flight consensus transactions
637    pub num_inflight_consensus_transactions: u64,
638    /// Last committed leader round from Mysticeti consensus
639    pub last_committed_leader_round: u32,
640    /// Last locally built checkpoint sequence number
641    pub last_locally_built_checkpoint: u64,
642}
643
644/// Raw protobuf request for validator health information (evolvable)
645#[derive(Clone, prost::Message)]
646pub struct RawValidatorHealthRequest {}
647
648/// Raw protobuf response with validator health metrics (evolvable)
649#[derive(Clone, prost::Message)]
650pub struct RawValidatorHealthResponse {
651    /// Number of pending certificates
652    #[prost(uint64, optional, tag = "1")]
653    pub pending_certificates: Option<u64>,
654    /// Number of in-flight consensus messages
655    #[prost(uint64, optional, tag = "2")]
656    pub inflight_consensus_messages: Option<u64>,
657    /// Current consensus round
658    #[prost(uint64, optional, tag = "3")]
659    pub consensus_round: Option<u64>,
660    /// Current checkpoint sequence number
661    #[prost(uint64, optional, tag = "4")]
662    pub checkpoint_sequence: Option<u64>,
663}
664
665// =========== Parse helpers ===========
666
667impl TryFrom<ExecutedData> for RawExecutedData {
668    type Error = crate::error::SuiError;
669
670    fn try_from(value: ExecutedData) -> Result<Self, Self::Error> {
671        let effects = bcs::to_bytes(&value.effects)
672            .map_err(
673                |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
674                    type_info: "ExecutedData.effects".to_string(),
675                    error: err.to_string(),
676                },
677            )?
678            .into();
679        let events = if let Some(events) = &value.events {
680            Some(
681                bcs::to_bytes(events)
682                    .map_err(
683                        |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
684                            type_info: "ExecutedData.events".to_string(),
685                            error: err.to_string(),
686                        },
687                    )?
688                    .into(),
689            )
690        } else {
691            None
692        };
693        let mut input_objects = Vec::with_capacity(value.input_objects.len());
694        for object in value.input_objects {
695            input_objects.push(
696                bcs::to_bytes(&object)
697                    .map_err(
698                        |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
699                            type_info: "ExecutedData.input_objects".to_string(),
700                            error: err.to_string(),
701                        },
702                    )?
703                    .into(),
704            );
705        }
706        let mut output_objects = Vec::with_capacity(value.output_objects.len());
707        for object in value.output_objects {
708            output_objects.push(
709                bcs::to_bytes(&object)
710                    .map_err(
711                        |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
712                            type_info: "ExecutedData.output_objects".to_string(),
713                            error: err.to_string(),
714                        },
715                    )?
716                    .into(),
717            );
718        }
719        Ok(RawExecutedData {
720            effects,
721            events,
722            input_objects,
723            output_objects,
724        })
725    }
726}
727
728impl TryFrom<RawExecutedData> for ExecutedData {
729    type Error = crate::error::SuiError;
730
731    fn try_from(value: RawExecutedData) -> Result<Self, Self::Error> {
732        let effects = bcs::from_bytes(&value.effects).map_err(|err| {
733            crate::error::SuiErrorKind::GrpcMessageDeserializeError {
734                type_info: "RawExecutedData.effects".to_string(),
735                error: err.to_string(),
736            }
737        })?;
738        let events = if let Some(events) = value.events {
739            Some(bcs::from_bytes(&events).map_err(|err| {
740                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
741                    type_info: "RawExecutedData.events".to_string(),
742                    error: err.to_string(),
743                }
744            })?)
745        } else {
746            None
747        };
748        let mut input_objects = Vec::with_capacity(value.input_objects.len());
749        for object in value.input_objects {
750            input_objects.push(bcs::from_bytes(&object).map_err(|err| {
751                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
752                    type_info: "RawExecutedData.input_objects".to_string(),
753                    error: err.to_string(),
754                }
755            })?);
756        }
757        let mut output_objects = Vec::with_capacity(value.output_objects.len());
758        for object in value.output_objects {
759            output_objects.push(bcs::from_bytes(&object).map_err(|err| {
760                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
761                    type_info: "RawExecutedData.output_objects".to_string(),
762                    error: err.to_string(),
763                }
764            })?);
765        }
766        Ok(ExecutedData {
767            effects,
768            events,
769            input_objects,
770            output_objects,
771        })
772    }
773}
774
775impl TryFrom<SubmitTxResult> for RawSubmitTxResult {
776    type Error = crate::error::SuiError;
777
778    fn try_from(value: SubmitTxResult) -> Result<Self, Self::Error> {
779        let inner = match value {
780            SubmitTxResult::Submitted { consensus_position } => {
781                let consensus_position = consensus_position.into_raw()?;
782                RawValidatorSubmitStatus::Submitted(consensus_position)
783            }
784            SubmitTxResult::Executed {
785                effects_digest,
786                details,
787                fast_path,
788            } => {
789                let raw_executed = try_from_response_executed(effects_digest, details, fast_path)?;
790                RawValidatorSubmitStatus::Executed(raw_executed)
791            }
792            SubmitTxResult::Rejected { error } => {
793                RawValidatorSubmitStatus::Rejected(try_from_response_rejected(Some(error))?)
794            }
795        };
796        Ok(RawSubmitTxResult { inner: Some(inner) })
797    }
798}
799
800impl TryFrom<RawSubmitTxResult> for SubmitTxResult {
801    type Error = crate::error::SuiError;
802
803    fn try_from(value: RawSubmitTxResult) -> Result<Self, Self::Error> {
804        match value.inner {
805            Some(RawValidatorSubmitStatus::Submitted(consensus_position)) => {
806                Ok(SubmitTxResult::Submitted {
807                    consensus_position: consensus_position.as_ref().try_into()?,
808                })
809            }
810            Some(RawValidatorSubmitStatus::Executed(executed)) => {
811                let (effects_digest, details, fast_path) = try_from_raw_executed_status(executed)?;
812                Ok(SubmitTxResult::Executed {
813                    effects_digest,
814                    details,
815                    fast_path,
816                })
817            }
818            Some(RawValidatorSubmitStatus::Rejected(error)) => {
819                let error = try_from_raw_rejected_status(error)?.unwrap_or(
820                    crate::error::SuiErrorKind::GrpcMessageDeserializeError {
821                        type_info: "RawSubmitTxResult.inner.Error".to_string(),
822                        error: "RawSubmitTxResult.inner.Error is None".to_string(),
823                    }
824                    .into(),
825                );
826                Ok(SubmitTxResult::Rejected { error })
827            }
828            None => Err(crate::error::SuiErrorKind::GrpcMessageDeserializeError {
829                type_info: "RawSubmitTxResult.inner".to_string(),
830                error: "RawSubmitTxResult.inner is None".to_string(),
831            }
832            .into()),
833        }
834    }
835}
836
837impl TryFrom<RawSubmitTxResponse> for SubmitTxResponse {
838    type Error = crate::error::SuiError;
839
840    fn try_from(value: RawSubmitTxResponse) -> Result<Self, Self::Error> {
841        // TODO(fastpath): handle multiple transactions.
842        if value.results.len() != 1 {
843            return Err(crate::error::SuiErrorKind::GrpcMessageDeserializeError {
844                type_info: "RawSubmitTxResponse.results".to_string(),
845                error: format!("Expected exactly 1 result, got {}", value.results.len()),
846            }
847            .into());
848        }
849
850        let results = value
851            .results
852            .into_iter()
853            .map(|result| result.try_into())
854            .collect::<Result<Vec<SubmitTxResult>, crate::error::SuiError>>()?;
855
856        Ok(Self { results })
857    }
858}
859
860fn try_from_raw_executed_status(
861    executed: RawExecutedStatus,
862) -> Result<
863    (
864        crate::digests::TransactionEffectsDigest,
865        Option<Box<ExecutedData>>,
866        bool,
867    ),
868    crate::error::SuiError,
869> {
870    let effects_digest = bcs::from_bytes(&executed.effects_digest).map_err(|err| {
871        crate::error::SuiErrorKind::GrpcMessageDeserializeError {
872            type_info: "RawWaitForEffectsResponse.effects_digest".to_string(),
873            error: err.to_string(),
874        }
875    })?;
876    let executed_data = if let Some(details) = executed.details {
877        Some(Box::new(details.try_into()?))
878    } else {
879        None
880    };
881    Ok((effects_digest, executed_data, executed.fast_path))
882}
883
884fn try_from_raw_rejected_status(
885    rejected: RawRejectedStatus,
886) -> Result<Option<crate::error::SuiError>, crate::error::SuiError> {
887    match rejected.error {
888        Some(error_bytes) => {
889            let error = bcs::from_bytes(&error_bytes).map_err(|err| {
890                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
891                    type_info: "RawWaitForEffectsResponse.rejected.reason".to_string(),
892                    error: err.to_string(),
893                }
894            })?;
895            Ok(Some(error))
896        }
897        None => Ok(None),
898    }
899}
900
901fn try_from_response_rejected(
902    error: Option<crate::error::SuiError>,
903) -> Result<RawRejectedStatus, crate::error::SuiError> {
904    let error = match error {
905        Some(e) => Some(
906            bcs::to_bytes(&e)
907                .map_err(
908                    |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
909                        type_info: "RawRejectedStatus.error".to_string(),
910                        error: err.to_string(),
911                    },
912                )?
913                .into(),
914        ),
915        None => None,
916    };
917    Ok(RawRejectedStatus { error })
918}
919
920fn try_from_response_executed(
921    effects_digest: crate::digests::TransactionEffectsDigest,
922    details: Option<Box<ExecutedData>>,
923    fast_path: bool,
924) -> Result<RawExecutedStatus, crate::error::SuiError> {
925    let effects_digest = bcs::to_bytes(&effects_digest)
926        .map_err(
927            |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
928                type_info: "RawWaitForEffectsResponse.effects_digest".to_string(),
929                error: err.to_string(),
930            },
931        )?
932        .into();
933    let details = if let Some(details) = details {
934        Some((*details).try_into()?)
935    } else {
936        None
937    };
938    Ok(RawExecutedStatus {
939        effects_digest,
940        details,
941        fast_path,
942    })
943}
944
945impl TryFrom<RawWaitForEffectsRequest> for WaitForEffectsRequest {
946    type Error = crate::error::SuiError;
947
948    fn try_from(value: RawWaitForEffectsRequest) -> Result<Self, Self::Error> {
949        let transaction_digest = match value.transaction_digest {
950            Some(digest) => Some(bcs::from_bytes(&digest).map_err(|err| {
951                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
952                    type_info: "RawWaitForEffectsRequest.transaction_digest".to_string(),
953                    error: err.to_string(),
954                }
955            })?),
956            None => None,
957        };
958        let consensus_position = match value.consensus_position {
959            Some(cp) => Some(cp.as_ref().try_into()?),
960            None => None,
961        };
962        let ping_type = value
963            .ping_type
964            .map(|p| {
965                PingType::try_from(p).map_err(|e| SuiErrorKind::GrpcMessageDeserializeError {
966                    type_info: "RawWaitForEffectsRequest.ping_type".to_string(),
967                    error: e.to_string(),
968                })
969            })
970            .transpose()?;
971        Ok(Self {
972            consensus_position,
973            transaction_digest,
974            include_details: value.include_details,
975            ping_type,
976        })
977    }
978}
979
980impl TryFrom<WaitForEffectsRequest> for RawWaitForEffectsRequest {
981    type Error = crate::error::SuiError;
982
983    fn try_from(value: WaitForEffectsRequest) -> Result<Self, Self::Error> {
984        let transaction_digest = match value.transaction_digest {
985            Some(digest) => Some(
986                bcs::to_bytes(&digest)
987                    .map_err(
988                        |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
989                            type_info: "RawWaitForEffectsRequest.transaction_digest".to_string(),
990                            error: err.to_string(),
991                        },
992                    )?
993                    .into(),
994            ),
995            None => None,
996        };
997        let consensus_position = match value.consensus_position {
998            Some(cp) => Some(cp.into_raw()?),
999            None => None,
1000        };
1001        let ping_type = value.ping_type.map(|p| p.into());
1002        Ok(Self {
1003            consensus_position,
1004            transaction_digest,
1005            include_details: value.include_details,
1006            ping_type,
1007        })
1008    }
1009}
1010
1011impl TryFrom<RawWaitForEffectsResponse> for WaitForEffectsResponse {
1012    type Error = crate::error::SuiError;
1013
1014    fn try_from(value: RawWaitForEffectsResponse) -> Result<Self, Self::Error> {
1015        match value.inner {
1016            Some(RawValidatorTransactionStatus::Executed(executed)) => {
1017                let (effects_digest, details, fast_path) = try_from_raw_executed_status(executed)?;
1018                Ok(Self::Executed {
1019                    effects_digest,
1020                    details,
1021                    fast_path,
1022                })
1023            }
1024            Some(RawValidatorTransactionStatus::Rejected(rejected)) => {
1025                let error = try_from_raw_rejected_status(rejected)?;
1026                Ok(Self::Rejected { error })
1027            }
1028            Some(RawValidatorTransactionStatus::Expired(expired)) => Ok(Self::Expired {
1029                epoch: expired.epoch,
1030                round: expired.round,
1031            }),
1032            None => Err(crate::error::SuiErrorKind::GrpcMessageDeserializeError {
1033                type_info: "RawWaitForEffectsResponse.inner".to_string(),
1034                error: "RawWaitForEffectsResponse.inner is None".to_string(),
1035            }
1036            .into()),
1037        }
1038    }
1039}
1040
1041impl TryFrom<WaitForEffectsResponse> for RawWaitForEffectsResponse {
1042    type Error = crate::error::SuiError;
1043
1044    fn try_from(value: WaitForEffectsResponse) -> Result<Self, Self::Error> {
1045        let inner = match value {
1046            WaitForEffectsResponse::Executed {
1047                effects_digest,
1048                details,
1049                fast_path,
1050            } => {
1051                let raw_executed = try_from_response_executed(effects_digest, details, fast_path)?;
1052                RawValidatorTransactionStatus::Executed(raw_executed)
1053            }
1054            WaitForEffectsResponse::Rejected { error } => {
1055                let raw_rejected = try_from_response_rejected(error)?;
1056                RawValidatorTransactionStatus::Rejected(raw_rejected)
1057            }
1058            WaitForEffectsResponse::Expired { epoch, round } => {
1059                RawValidatorTransactionStatus::Expired(RawExpiredStatus { epoch, round })
1060            }
1061        };
1062        Ok(RawWaitForEffectsResponse { inner: Some(inner) })
1063    }
1064}
1065
1066impl TryFrom<ValidatorHealthRequest> for RawValidatorHealthRequest {
1067    type Error = crate::error::SuiError;
1068
1069    fn try_from(_value: ValidatorHealthRequest) -> Result<Self, Self::Error> {
1070        Ok(Self {})
1071    }
1072}
1073
1074impl TryFrom<RawValidatorHealthRequest> for ValidatorHealthRequest {
1075    type Error = crate::error::SuiError;
1076
1077    fn try_from(_value: RawValidatorHealthRequest) -> Result<Self, Self::Error> {
1078        // Empty request - ignore reserved field for now
1079        Ok(Self {})
1080    }
1081}
1082
1083impl TryFrom<ValidatorHealthResponse> for RawValidatorHealthResponse {
1084    type Error = crate::error::SuiError;
1085
1086    fn try_from(value: ValidatorHealthResponse) -> Result<Self, Self::Error> {
1087        Ok(Self {
1088            pending_certificates: Some(value.num_inflight_execution_transactions),
1089            inflight_consensus_messages: Some(value.num_inflight_consensus_transactions),
1090            consensus_round: Some(value.last_committed_leader_round as u64),
1091            checkpoint_sequence: Some(value.last_locally_built_checkpoint),
1092        })
1093    }
1094}
1095
1096impl TryFrom<RawValidatorHealthResponse> for ValidatorHealthResponse {
1097    type Error = crate::error::SuiError;
1098
1099    fn try_from(value: RawValidatorHealthResponse) -> Result<Self, Self::Error> {
1100        Ok(Self {
1101            num_inflight_consensus_transactions: value.inflight_consensus_messages.unwrap_or(0),
1102            num_inflight_execution_transactions: value.pending_certificates.unwrap_or(0),
1103            last_locally_built_checkpoint: value.checkpoint_sequence.unwrap_or(0),
1104            last_committed_leader_round: value.consensus_round.unwrap_or(0) as u32,
1105        })
1106    }
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111    use crate::{
1112        messages_grpc::{PingType, SubmitTxRequest, SubmitTxType},
1113        transaction::{Transaction, TransactionData},
1114    };
1115
1116    #[tokio::test]
1117    async fn test_submit_tx_request_into_raw() {
1118        println!(
1119            "Case 1. SubmitTxRequest::new_ping should be converted to RawSubmitTxRequest with submit_type set to Ping."
1120        );
1121        {
1122            let request = SubmitTxRequest::new_ping(PingType::Consensus);
1123            let raw_request = request.into_raw().unwrap();
1124
1125            let submit_type = SubmitTxType::try_from(raw_request.submit_type).unwrap();
1126            assert_eq!(submit_type, SubmitTxType::Ping);
1127        }
1128
1129        println!(
1130            "Case 2. SubmitTxRequest::new_transaction should be converted to RawSubmitTxRequest with submit_type set to Default."
1131        );
1132        {
1133            // Create a dummy transaction for testing
1134            let sender = crate::base_types::SuiAddress::random_for_testing_only();
1135            let recipient = crate::base_types::SuiAddress::random_for_testing_only();
1136            let gas_object_ref = crate::base_types::random_object_ref();
1137
1138            let tx_data = TransactionData::new_transfer_sui(
1139                recipient,
1140                sender,
1141                None,
1142                gas_object_ref,
1143                1000,
1144                1000,
1145            );
1146
1147            let (_, keypair) = crate::crypto::get_account_key_pair();
1148            let transaction = Transaction::from_data_and_signer(tx_data, vec![&keypair]);
1149
1150            let request = SubmitTxRequest::new_transaction(transaction);
1151            let raw_request = request.into_raw().unwrap();
1152
1153            let submit_type = SubmitTxType::try_from(raw_request.submit_type).unwrap();
1154            assert_eq!(submit_type, SubmitTxType::Default);
1155            assert_eq!(raw_request.transactions.len(), 1);
1156        }
1157    }
1158}