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