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/// Contains either a transaction, or the type of Ping request.
289#[derive(Clone, Debug)]
290pub struct SubmitTxRequest {
291    pub transaction: Option<Transaction>,
292    pub ping_type: Option<PingType>,
293}
294
295impl From<Transaction> for SubmitTxRequest {
296    fn from(transaction: Transaction) -> Self {
297        Self::new_transaction(transaction)
298    }
299}
300
301impl SubmitTxRequest {
302    pub fn new_transaction(transaction: Transaction) -> Self {
303        Self {
304            transaction: Some(transaction),
305            ping_type: None,
306        }
307    }
308
309    pub fn new_ping() -> Self {
310        Self {
311            transaction: None,
312            ping_type: Some(PingType::Consensus),
313        }
314    }
315
316    pub fn tx_type(&self) -> TxType {
317        if self.ping_type.is_some() {
318            return TxType::SharedObject;
319        }
320        let transaction = self.transaction.as_ref().unwrap();
321        if transaction.is_consensus_tx() {
322            TxType::SharedObject
323        } else {
324            TxType::SingleWriter
325        }
326    }
327
328    /// Returns the digest of the transaction if it is a transaction request.
329    /// Returns None if it is a ping request.
330    pub fn tx_digest(&self) -> Option<TransactionDigest> {
331        self.transaction.as_ref().map(|t| *t.digest())
332    }
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumIter)]
336pub enum TxType {
337    SingleWriter,
338    SharedObject,
339}
340
341impl TxType {
342    pub fn as_str(&self) -> &str {
343        match self {
344            TxType::SingleWriter => TX_TYPE_SINGLE_WRITER_TX,
345            TxType::SharedObject => TX_TYPE_SHARED_OBJ_TX,
346        }
347    }
348}
349
350impl SubmitTxRequest {
351    pub fn into_raw(&self) -> Result<RawSubmitTxRequest, SuiError> {
352        let transactions = if let Some(transaction) = &self.transaction {
353            vec![
354                bcs::to_bytes(&transaction)
355                    .map_err(|e| SuiErrorKind::TransactionSerializationError {
356                        error: e.to_string(),
357                    })?
358                    .into(),
359            ]
360        } else {
361            vec![]
362        };
363
364        let submit_type = if self.ping_type.is_some() {
365            SubmitTxType::Ping
366        } else {
367            SubmitTxType::Default
368        };
369
370        Ok(RawSubmitTxRequest {
371            transactions,
372            submit_type: submit_type.into(),
373        })
374    }
375}
376
377#[derive(Clone)]
378pub enum SubmitTxResult {
379    Submitted {
380        consensus_position: crate::messages_consensus::ConsensusPosition,
381    },
382    Executed {
383        effects_digest: crate::digests::TransactionEffectsDigest,
384        // Response should always include details for executed transactions.
385        // TODO(fastpath): validate this field is always present and return an error during deserialization.
386        details: Option<Box<ExecutedData>>,
387    },
388    Rejected {
389        error: crate::error::SuiError,
390    },
391}
392
393impl std::fmt::Debug for SubmitTxResult {
394    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395        match self {
396            Self::Submitted { consensus_position } => f
397                .debug_struct("Submitted")
398                .field("consensus_position", consensus_position)
399                .finish(),
400            Self::Executed { effects_digest, .. } => f
401                .debug_struct("Executed")
402                .field("effects_digest", &format_args!("{}", effects_digest))
403                .finish(),
404            Self::Rejected { error } => f.debug_struct("Rejected").field("error", &error).finish(),
405        }
406    }
407}
408
409#[derive(Clone, Debug)]
410pub struct SubmitTxResponse {
411    pub results: Vec<SubmitTxResult>,
412}
413
414#[derive(Clone, prost::Message)]
415pub struct RawSubmitTxRequest {
416    /// The transactions to be submitted. When the vector is empty, then this is treated as a ping request.
417    #[prost(bytes = "bytes", repeated, tag = "1")]
418    pub transactions: Vec<Bytes>,
419
420    /// The type of submission.
421    #[prost(enumeration = "SubmitTxType", tag = "2")]
422    pub submit_type: i32,
423}
424
425#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, prost::Enumeration)]
426#[repr(i32)]
427pub enum SubmitTxType {
428    /// Default submission, submitting one or more transactions.
429    /// When there are multiple transactions, allow the transactions to be included separately
430    /// and out of order in blocks (batch).
431    Default = 0,
432    /// Ping request to measure latency, no transactions.
433    Ping = 1,
434    /// When submitting multiple transactions, attempt to include them in
435    /// the same block with the same order (soft bundle).
436    SoftBundle = 2,
437}
438
439#[derive(Clone, prost::Message)]
440pub struct RawSubmitTxResponse {
441    // Results corresponding to each transaction in the request.
442    #[prost(message, repeated, tag = "1")]
443    pub results: Vec<RawSubmitTxResult>,
444}
445
446#[derive(Clone, prost::Message)]
447pub struct RawSubmitTxResult {
448    #[prost(oneof = "RawValidatorSubmitStatus", tags = "1, 2, 3")]
449    pub inner: Option<RawValidatorSubmitStatus>,
450}
451
452#[derive(Clone, prost::Oneof)]
453pub enum RawValidatorSubmitStatus {
454    // Serialized Consensus Position.
455    #[prost(bytes = "bytes", tag = "1")]
456    Submitted(Bytes),
457
458    // Transaction has already been executed (finalized).
459    #[prost(message, tag = "2")]
460    Executed(RawExecutedStatus),
461
462    // Transaction is rejected from consensus submission.
463    #[prost(message, tag = "3")]
464    Rejected(RawRejectedStatus),
465}
466
467#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, prost::Enumeration)]
468#[repr(i32)]
469pub enum PingType {
470    /// Measures the end to end latency from when a transaction is included by a proposed block,
471    /// to when the block is committed by consensus.
472    Consensus = 0,
473}
474
475impl PingType {
476    pub fn as_str(&self) -> &str {
477        match self {
478            PingType::Consensus => "consensus",
479        }
480    }
481}
482
483// =========== WaitForEffects types ===========
484
485pub struct WaitForEffectsRequest {
486    pub transaction_digest: Option<crate::digests::TransactionDigest>,
487    /// If consensus position is provided, waits in the server handler for the transaction in it to execute,
488    /// either in fastpath outputs or finalized.
489    /// If it is not provided, only waits for finalized effects of the transaction in the server handler,
490    /// but not for fastpath outputs.
491    pub consensus_position: Option<crate::messages_consensus::ConsensusPosition>,
492    /// Whether to include details of the effects,
493    /// including the effects content, events, input objects, and output objects.
494    pub include_details: bool,
495    /// Type of ping request, or None if this is not a ping request.
496    pub ping_type: Option<PingType>,
497}
498
499#[derive(Clone)]
500pub enum WaitForEffectsResponse {
501    Executed {
502        effects_digest: crate::digests::TransactionEffectsDigest,
503        details: Option<Box<ExecutedData>>,
504    },
505    // The transaction was rejected by consensus.
506    Rejected {
507        // The reason of the reject vote casted by the validator.
508        // If None, the validator did not cast a reject vote.
509        error: Option<crate::error::SuiError>,
510    },
511    // The transaction position is expired, with the local epoch and committed round.
512    // When round is None, the expiration is due to lagging epoch in the request.
513    Expired {
514        epoch: u64,
515        round: Option<u32>,
516    },
517}
518
519impl std::fmt::Debug for WaitForEffectsResponse {
520    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521        match self {
522            Self::Executed { effects_digest, .. } => f
523                .debug_struct("Executed")
524                .field("effects_digest", effects_digest)
525                .finish(),
526            Self::Rejected { error } => f.debug_struct("Rejected").field("error", error).finish(),
527            Self::Expired { epoch, round } => f
528                .debug_struct("Expired")
529                .field("epoch", epoch)
530                .field("round", round)
531                .finish(),
532        }
533    }
534}
535
536#[derive(Clone, prost::Message)]
537pub struct RawWaitForEffectsRequest {
538    /// The transaction's digest. If it's a ping request, then this will practically be ignored.
539    #[prost(bytes = "bytes", optional, tag = "1")]
540    pub transaction_digest: Option<Bytes>,
541
542    /// If provided, wait for the consensus position to execute and wait for fastpath outputs of the transaction,
543    /// in addition to waiting for finalized effects.
544    /// If not provided, only wait for finalized effects.
545    #[prost(bytes = "bytes", optional, tag = "2")]
546    pub consensus_position: Option<Bytes>,
547
548    /// Whether to include details of the effects,
549    /// including the effects content, events, input objects, and output objects.
550    #[prost(bool, tag = "3")]
551    pub include_details: bool,
552
553    /// Set when this is a ping request, to differentiate between fastpath and consensus pings.
554    #[prost(enumeration = "PingType", optional, tag = "4")]
555    pub ping_type: Option<i32>,
556}
557
558impl RawWaitForEffectsRequest {
559    pub fn get_ping_type(&self) -> Option<PingType> {
560        self.ping_type
561            .map(|p| PingType::try_from(p).expect("Invalid ping type"))
562    }
563}
564
565#[derive(Clone, prost::Message)]
566pub struct RawWaitForEffectsResponse {
567    // In order to represent an enum in protobuf, we need to use oneof.
568    // However, oneof also allows the value to be unset, which corresponds to None value.
569    // Hence, we need to use Option type for `inner`.
570    // We expect the value to be set in a valid response.
571    #[prost(oneof = "RawValidatorTransactionStatus", tags = "1, 2, 3")]
572    pub inner: Option<RawValidatorTransactionStatus>,
573}
574
575#[derive(Clone, prost::Oneof)]
576pub enum RawValidatorTransactionStatus {
577    #[prost(message, tag = "1")]
578    Executed(RawExecutedStatus),
579    #[prost(message, tag = "2")]
580    Rejected(RawRejectedStatus),
581    #[prost(message, tag = "3")]
582    Expired(RawExpiredStatus),
583}
584
585#[derive(Clone, prost::Message)]
586pub struct RawExecutedStatus {
587    #[prost(bytes = "bytes", tag = "1")]
588    pub effects_digest: Bytes,
589    #[prost(message, optional, tag = "2")]
590    pub details: Option<RawExecutedData>,
591}
592
593#[derive(Clone, prost::Message)]
594pub struct RawRejectedStatus {
595    #[prost(bytes = "bytes", optional, tag = "1")]
596    pub error: Option<Bytes>,
597}
598
599#[derive(Clone, prost::Message)]
600pub struct RawExpiredStatus {
601    // Validator's current epoch.
602    #[prost(uint64, tag = "1")]
603    pub epoch: u64,
604    // Validator's current round. 0 if it is not yet checked.
605    #[prost(uint32, optional, tag = "2")]
606    pub round: Option<u32>,
607}
608
609// =========== ValidatorHealth types ===========
610
611/// Request for validator health information (used for latency measurement)
612#[derive(Clone, Debug, Default)]
613pub struct ValidatorHealthRequest {}
614
615/// Response with validator health metrics (data collected but not used for scoring yet)
616#[derive(Clone, Debug, Default)]
617pub struct ValidatorHealthResponse {
618    /// Number of in-flight execution transactions from execution scheduler
619    pub num_inflight_execution_transactions: u64,
620    /// Number of in-flight consensus transactions
621    pub num_inflight_consensus_transactions: u64,
622    /// Last committed leader round from Mysticeti consensus
623    pub last_committed_leader_round: u32,
624    /// Last locally built checkpoint sequence number
625    pub last_locally_built_checkpoint: u64,
626}
627
628/// Raw protobuf request for validator health information (evolvable)
629#[derive(Clone, prost::Message)]
630pub struct RawValidatorHealthRequest {}
631
632/// Raw protobuf response with validator health metrics (evolvable)
633#[derive(Clone, prost::Message)]
634pub struct RawValidatorHealthResponse {
635    /// Number of pending certificates
636    #[prost(uint64, optional, tag = "1")]
637    pub pending_certificates: Option<u64>,
638    /// Number of in-flight consensus messages
639    #[prost(uint64, optional, tag = "2")]
640    pub inflight_consensus_messages: Option<u64>,
641    /// Current consensus round
642    #[prost(uint64, optional, tag = "3")]
643    pub consensus_round: Option<u64>,
644    /// Current checkpoint sequence number
645    #[prost(uint64, optional, tag = "4")]
646    pub checkpoint_sequence: Option<u64>,
647}
648
649// =========== Parse helpers ===========
650
651impl TryFrom<ExecutedData> for RawExecutedData {
652    type Error = crate::error::SuiError;
653
654    fn try_from(value: ExecutedData) -> Result<Self, Self::Error> {
655        let effects = bcs::to_bytes(&value.effects)
656            .map_err(
657                |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
658                    type_info: "ExecutedData.effects".to_string(),
659                    error: err.to_string(),
660                },
661            )?
662            .into();
663        let events = if let Some(events) = &value.events {
664            Some(
665                bcs::to_bytes(events)
666                    .map_err(
667                        |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
668                            type_info: "ExecutedData.events".to_string(),
669                            error: err.to_string(),
670                        },
671                    )?
672                    .into(),
673            )
674        } else {
675            None
676        };
677        let mut input_objects = Vec::with_capacity(value.input_objects.len());
678        for object in value.input_objects {
679            input_objects.push(
680                bcs::to_bytes(&object)
681                    .map_err(
682                        |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
683                            type_info: "ExecutedData.input_objects".to_string(),
684                            error: err.to_string(),
685                        },
686                    )?
687                    .into(),
688            );
689        }
690        let mut output_objects = Vec::with_capacity(value.output_objects.len());
691        for object in value.output_objects {
692            output_objects.push(
693                bcs::to_bytes(&object)
694                    .map_err(
695                        |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
696                            type_info: "ExecutedData.output_objects".to_string(),
697                            error: err.to_string(),
698                        },
699                    )?
700                    .into(),
701            );
702        }
703        Ok(RawExecutedData {
704            effects,
705            events,
706            input_objects,
707            output_objects,
708        })
709    }
710}
711
712impl TryFrom<RawExecutedData> for ExecutedData {
713    type Error = crate::error::SuiError;
714
715    fn try_from(value: RawExecutedData) -> Result<Self, Self::Error> {
716        let effects = bcs::from_bytes(&value.effects).map_err(|err| {
717            crate::error::SuiErrorKind::GrpcMessageDeserializeError {
718                type_info: "RawExecutedData.effects".to_string(),
719                error: err.to_string(),
720            }
721        })?;
722        let events = if let Some(events) = value.events {
723            Some(bcs::from_bytes(&events).map_err(|err| {
724                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
725                    type_info: "RawExecutedData.events".to_string(),
726                    error: err.to_string(),
727                }
728            })?)
729        } else {
730            None
731        };
732        let mut input_objects = Vec::with_capacity(value.input_objects.len());
733        for object in value.input_objects {
734            input_objects.push(bcs::from_bytes(&object).map_err(|err| {
735                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
736                    type_info: "RawExecutedData.input_objects".to_string(),
737                    error: err.to_string(),
738                }
739            })?);
740        }
741        let mut output_objects = Vec::with_capacity(value.output_objects.len());
742        for object in value.output_objects {
743            output_objects.push(bcs::from_bytes(&object).map_err(|err| {
744                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
745                    type_info: "RawExecutedData.output_objects".to_string(),
746                    error: err.to_string(),
747                }
748            })?);
749        }
750        Ok(ExecutedData {
751            effects,
752            events,
753            input_objects,
754            output_objects,
755        })
756    }
757}
758
759impl TryFrom<SubmitTxResult> for RawSubmitTxResult {
760    type Error = crate::error::SuiError;
761
762    fn try_from(value: SubmitTxResult) -> Result<Self, Self::Error> {
763        let inner = match value {
764            SubmitTxResult::Submitted { consensus_position } => {
765                let consensus_position = consensus_position.into_raw()?;
766                RawValidatorSubmitStatus::Submitted(consensus_position)
767            }
768            SubmitTxResult::Executed {
769                effects_digest,
770                details,
771            } => {
772                let raw_executed = try_from_response_executed(effects_digest, details)?;
773                RawValidatorSubmitStatus::Executed(raw_executed)
774            }
775            SubmitTxResult::Rejected { error } => {
776                RawValidatorSubmitStatus::Rejected(try_from_response_rejected(Some(error))?)
777            }
778        };
779        Ok(RawSubmitTxResult { inner: Some(inner) })
780    }
781}
782
783impl TryFrom<RawSubmitTxResult> for SubmitTxResult {
784    type Error = crate::error::SuiError;
785
786    fn try_from(value: RawSubmitTxResult) -> Result<Self, Self::Error> {
787        match value.inner {
788            Some(RawValidatorSubmitStatus::Submitted(consensus_position)) => {
789                Ok(SubmitTxResult::Submitted {
790                    consensus_position: consensus_position.as_ref().try_into()?,
791                })
792            }
793            Some(RawValidatorSubmitStatus::Executed(executed)) => {
794                let (effects_digest, details) = try_from_raw_executed_status(executed)?;
795                Ok(SubmitTxResult::Executed {
796                    effects_digest,
797                    details,
798                })
799            }
800            Some(RawValidatorSubmitStatus::Rejected(error)) => {
801                let error = try_from_raw_rejected_status(error)?.unwrap_or(
802                    crate::error::SuiErrorKind::GrpcMessageDeserializeError {
803                        type_info: "RawSubmitTxResult.inner.Error".to_string(),
804                        error: "RawSubmitTxResult.inner.Error is None".to_string(),
805                    }
806                    .into(),
807                );
808                Ok(SubmitTxResult::Rejected { error })
809            }
810            None => Err(crate::error::SuiErrorKind::GrpcMessageDeserializeError {
811                type_info: "RawSubmitTxResult.inner".to_string(),
812                error: "RawSubmitTxResult.inner is None".to_string(),
813            }
814            .into()),
815        }
816    }
817}
818
819impl TryFrom<RawSubmitTxResponse> for SubmitTxResponse {
820    type Error = crate::error::SuiError;
821
822    fn try_from(value: RawSubmitTxResponse) -> Result<Self, Self::Error> {
823        // TODO(fastpath): handle multiple transactions.
824        if value.results.len() != 1 {
825            return Err(crate::error::SuiErrorKind::GrpcMessageDeserializeError {
826                type_info: "RawSubmitTxResponse.results".to_string(),
827                error: format!("Expected exactly 1 result, got {}", value.results.len()),
828            }
829            .into());
830        }
831
832        let results = value
833            .results
834            .into_iter()
835            .map(|result| result.try_into())
836            .collect::<Result<Vec<SubmitTxResult>, crate::error::SuiError>>()?;
837
838        Ok(Self { results })
839    }
840}
841
842fn try_from_raw_executed_status(
843    executed: RawExecutedStatus,
844) -> Result<
845    (
846        crate::digests::TransactionEffectsDigest,
847        Option<Box<ExecutedData>>,
848    ),
849    crate::error::SuiError,
850> {
851    let effects_digest = bcs::from_bytes(&executed.effects_digest).map_err(|err| {
852        crate::error::SuiErrorKind::GrpcMessageDeserializeError {
853            type_info: "RawWaitForEffectsResponse.effects_digest".to_string(),
854            error: err.to_string(),
855        }
856    })?;
857    let executed_data = if let Some(details) = executed.details {
858        Some(Box::new(details.try_into()?))
859    } else {
860        None
861    };
862    Ok((effects_digest, executed_data))
863}
864
865fn try_from_raw_rejected_status(
866    rejected: RawRejectedStatus,
867) -> Result<Option<crate::error::SuiError>, crate::error::SuiError> {
868    match rejected.error {
869        Some(error_bytes) => {
870            let error = bcs::from_bytes(&error_bytes).map_err(|err| {
871                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
872                    type_info: "RawWaitForEffectsResponse.rejected.reason".to_string(),
873                    error: err.to_string(),
874                }
875            })?;
876            Ok(Some(error))
877        }
878        None => Ok(None),
879    }
880}
881
882fn try_from_response_rejected(
883    error: Option<crate::error::SuiError>,
884) -> Result<RawRejectedStatus, crate::error::SuiError> {
885    let error = match error {
886        Some(e) => Some(
887            bcs::to_bytes(&e)
888                .map_err(
889                    |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
890                        type_info: "RawRejectedStatus.error".to_string(),
891                        error: err.to_string(),
892                    },
893                )?
894                .into(),
895        ),
896        None => None,
897    };
898    Ok(RawRejectedStatus { error })
899}
900
901fn try_from_response_executed(
902    effects_digest: crate::digests::TransactionEffectsDigest,
903    details: Option<Box<ExecutedData>>,
904) -> Result<RawExecutedStatus, crate::error::SuiError> {
905    let effects_digest = bcs::to_bytes(&effects_digest)
906        .map_err(
907            |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
908                type_info: "RawWaitForEffectsResponse.effects_digest".to_string(),
909                error: err.to_string(),
910            },
911        )?
912        .into();
913    let details = if let Some(details) = details {
914        Some((*details).try_into()?)
915    } else {
916        None
917    };
918    Ok(RawExecutedStatus {
919        effects_digest,
920        details,
921    })
922}
923
924impl TryFrom<RawWaitForEffectsRequest> for WaitForEffectsRequest {
925    type Error = crate::error::SuiError;
926
927    fn try_from(value: RawWaitForEffectsRequest) -> Result<Self, Self::Error> {
928        let transaction_digest = match value.transaction_digest {
929            Some(digest) => Some(bcs::from_bytes(&digest).map_err(|err| {
930                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
931                    type_info: "RawWaitForEffectsRequest.transaction_digest".to_string(),
932                    error: err.to_string(),
933                }
934            })?),
935            None => None,
936        };
937        let consensus_position = match value.consensus_position {
938            Some(cp) => Some(cp.as_ref().try_into()?),
939            None => None,
940        };
941        let ping_type = value
942            .ping_type
943            .map(|p| {
944                PingType::try_from(p).map_err(|e| SuiErrorKind::GrpcMessageDeserializeError {
945                    type_info: "RawWaitForEffectsRequest.ping_type".to_string(),
946                    error: e.to_string(),
947                })
948            })
949            .transpose()?;
950        Ok(Self {
951            consensus_position,
952            transaction_digest,
953            include_details: value.include_details,
954            ping_type,
955        })
956    }
957}
958
959impl TryFrom<WaitForEffectsRequest> for RawWaitForEffectsRequest {
960    type Error = crate::error::SuiError;
961
962    fn try_from(value: WaitForEffectsRequest) -> Result<Self, Self::Error> {
963        let transaction_digest = match value.transaction_digest {
964            Some(digest) => Some(
965                bcs::to_bytes(&digest)
966                    .map_err(
967                        |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
968                            type_info: "RawWaitForEffectsRequest.transaction_digest".to_string(),
969                            error: err.to_string(),
970                        },
971                    )?
972                    .into(),
973            ),
974            None => None,
975        };
976        let consensus_position = match value.consensus_position {
977            Some(cp) => Some(cp.into_raw()?),
978            None => None,
979        };
980        let ping_type = value.ping_type.map(|p| p.into());
981        Ok(Self {
982            consensus_position,
983            transaction_digest,
984            include_details: value.include_details,
985            ping_type,
986        })
987    }
988}
989
990impl TryFrom<RawWaitForEffectsResponse> for WaitForEffectsResponse {
991    type Error = crate::error::SuiError;
992
993    fn try_from(value: RawWaitForEffectsResponse) -> Result<Self, Self::Error> {
994        match value.inner {
995            Some(RawValidatorTransactionStatus::Executed(executed)) => {
996                let (effects_digest, details) = try_from_raw_executed_status(executed)?;
997                Ok(Self::Executed {
998                    effects_digest,
999                    details,
1000                })
1001            }
1002            Some(RawValidatorTransactionStatus::Rejected(rejected)) => {
1003                let error = try_from_raw_rejected_status(rejected)?;
1004                Ok(Self::Rejected { error })
1005            }
1006            Some(RawValidatorTransactionStatus::Expired(expired)) => Ok(Self::Expired {
1007                epoch: expired.epoch,
1008                round: expired.round,
1009            }),
1010            None => Err(crate::error::SuiErrorKind::GrpcMessageDeserializeError {
1011                type_info: "RawWaitForEffectsResponse.inner".to_string(),
1012                error: "RawWaitForEffectsResponse.inner is None".to_string(),
1013            }
1014            .into()),
1015        }
1016    }
1017}
1018
1019impl TryFrom<WaitForEffectsResponse> for RawWaitForEffectsResponse {
1020    type Error = crate::error::SuiError;
1021
1022    fn try_from(value: WaitForEffectsResponse) -> Result<Self, Self::Error> {
1023        let inner = match value {
1024            WaitForEffectsResponse::Executed {
1025                effects_digest,
1026                details,
1027            } => {
1028                let raw_executed = try_from_response_executed(effects_digest, details)?;
1029                RawValidatorTransactionStatus::Executed(raw_executed)
1030            }
1031            WaitForEffectsResponse::Rejected { error } => {
1032                let raw_rejected = try_from_response_rejected(error)?;
1033                RawValidatorTransactionStatus::Rejected(raw_rejected)
1034            }
1035            WaitForEffectsResponse::Expired { epoch, round } => {
1036                RawValidatorTransactionStatus::Expired(RawExpiredStatus { epoch, round })
1037            }
1038        };
1039        Ok(RawWaitForEffectsResponse { inner: Some(inner) })
1040    }
1041}
1042
1043impl TryFrom<ValidatorHealthRequest> for RawValidatorHealthRequest {
1044    type Error = crate::error::SuiError;
1045
1046    fn try_from(_value: ValidatorHealthRequest) -> Result<Self, Self::Error> {
1047        Ok(Self {})
1048    }
1049}
1050
1051impl TryFrom<RawValidatorHealthRequest> for ValidatorHealthRequest {
1052    type Error = crate::error::SuiError;
1053
1054    fn try_from(_value: RawValidatorHealthRequest) -> Result<Self, Self::Error> {
1055        // Empty request - ignore reserved field for now
1056        Ok(Self {})
1057    }
1058}
1059
1060impl TryFrom<ValidatorHealthResponse> for RawValidatorHealthResponse {
1061    type Error = crate::error::SuiError;
1062
1063    fn try_from(value: ValidatorHealthResponse) -> Result<Self, Self::Error> {
1064        Ok(Self {
1065            pending_certificates: Some(value.num_inflight_execution_transactions),
1066            inflight_consensus_messages: Some(value.num_inflight_consensus_transactions),
1067            consensus_round: Some(value.last_committed_leader_round as u64),
1068            checkpoint_sequence: Some(value.last_locally_built_checkpoint),
1069        })
1070    }
1071}
1072
1073impl TryFrom<RawValidatorHealthResponse> for ValidatorHealthResponse {
1074    type Error = crate::error::SuiError;
1075
1076    fn try_from(value: RawValidatorHealthResponse) -> Result<Self, Self::Error> {
1077        Ok(Self {
1078            num_inflight_consensus_transactions: value.inflight_consensus_messages.unwrap_or(0),
1079            num_inflight_execution_transactions: value.pending_certificates.unwrap_or(0),
1080            last_locally_built_checkpoint: value.checkpoint_sequence.unwrap_or(0),
1081            last_committed_leader_round: value.consensus_round.unwrap_or(0) as u32,
1082        })
1083    }
1084}
1085
1086#[cfg(test)]
1087mod tests {
1088    use crate::{
1089        messages_grpc::{SubmitTxRequest, SubmitTxType},
1090        transaction::{Transaction, TransactionData},
1091    };
1092
1093    #[tokio::test]
1094    async fn test_submit_tx_request_into_raw() {
1095        println!(
1096            "Case 1. SubmitTxRequest::new_ping should be converted to RawSubmitTxRequest with submit_type set to Ping."
1097        );
1098        {
1099            let request = SubmitTxRequest::new_ping();
1100            let raw_request = request.into_raw().unwrap();
1101
1102            let submit_type = SubmitTxType::try_from(raw_request.submit_type).unwrap();
1103            assert_eq!(submit_type, SubmitTxType::Ping);
1104        }
1105
1106        println!(
1107            "Case 2. SubmitTxRequest::new_transaction should be converted to RawSubmitTxRequest with submit_type set to Default."
1108        );
1109        {
1110            // Create a dummy transaction for testing
1111            let sender = crate::base_types::SuiAddress::random_for_testing_only();
1112            let recipient = crate::base_types::SuiAddress::random_for_testing_only();
1113            let gas_object_ref = crate::base_types::random_object_ref();
1114
1115            let tx_data = TransactionData::new_transfer_sui(
1116                recipient,
1117                sender,
1118                None,
1119                gas_object_ref,
1120                1000,
1121                1000,
1122            );
1123
1124            let (_, keypair) = crate::crypto::get_account_key_pair();
1125            let transaction = Transaction::from_data_and_signer(tx_data, vec![&keypair]);
1126
1127            let request = SubmitTxRequest::new_transaction(transaction);
1128            let raw_request = request.into_raw().unwrap();
1129
1130            let submit_type = SubmitTxType::try_from(raw_request.submit_type).unwrap();
1131            assert_eq!(submit_type, SubmitTxType::Default);
1132            assert_eq!(raw_request.transactions.len(), 1);
1133        }
1134    }
1135}