Skip to main content

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, the server handler also watches the transaction's consensus
488    /// status, so it responds promptly if consensus rejected or dropped the transaction.
489    /// Either way, the handler waits for the transaction's finalized effects.
490    pub consensus_position: Option<crate::messages_consensus::ConsensusPosition>,
491    /// Whether to include details of the effects,
492    /// including the effects content, events, input objects, and output objects.
493    pub include_details: bool,
494    /// Type of ping request, or None if this is not a ping request.
495    pub ping_type: Option<PingType>,
496}
497
498#[derive(Clone)]
499pub enum WaitForEffectsResponse {
500    Executed {
501        effects_digest: crate::digests::TransactionEffectsDigest,
502        details: Option<Box<ExecutedData>>,
503    },
504    // The transaction was rejected by consensus.
505    Rejected {
506        // The reason of the reject vote casted by the validator.
507        // If None, the validator did not cast a reject vote.
508        error: Option<crate::error::SuiError>,
509    },
510    // The transaction position is expired, with the local epoch and committed round.
511    // When round is None, the expiration is due to lagging epoch in the request.
512    Expired {
513        epoch: u64,
514        round: Option<u32>,
515    },
516}
517
518impl std::fmt::Debug for WaitForEffectsResponse {
519    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
520        match self {
521            Self::Executed { effects_digest, .. } => f
522                .debug_struct("Executed")
523                .field("effects_digest", effects_digest)
524                .finish(),
525            Self::Rejected { error } => f.debug_struct("Rejected").field("error", error).finish(),
526            Self::Expired { epoch, round } => f
527                .debug_struct("Expired")
528                .field("epoch", epoch)
529                .field("round", round)
530                .finish(),
531        }
532    }
533}
534
535#[derive(Clone, prost::Message)]
536pub struct RawWaitForEffectsRequest {
537    /// The transaction's digest. If it's a ping request, then this will practically be ignored.
538    #[prost(bytes = "bytes", optional, tag = "1")]
539    pub transaction_digest: Option<Bytes>,
540
541    /// If provided, also watch the transaction's consensus status at this position, so a rejected or
542    /// dropped transaction gets a response without waiting for effects that will not be produced.
543    /// Finalized effects are waited for in either case.
544    #[prost(bytes = "bytes", optional, tag = "2")]
545    pub consensus_position: Option<Bytes>,
546
547    /// Whether to include details of the effects,
548    /// including the effects content, events, input objects, and output objects.
549    #[prost(bool, tag = "3")]
550    pub include_details: bool,
551
552    /// Set when this is a ping request.
553    #[prost(enumeration = "PingType", optional, tag = "4")]
554    pub ping_type: Option<i32>,
555}
556
557impl RawWaitForEffectsRequest {
558    pub fn get_ping_type(&self) -> Option<PingType> {
559        self.ping_type
560            .map(|p| PingType::try_from(p).expect("Invalid ping type"))
561    }
562}
563
564#[derive(Clone, prost::Message)]
565pub struct RawWaitForEffectsResponse {
566    // In order to represent an enum in protobuf, we need to use oneof.
567    // However, oneof also allows the value to be unset, which corresponds to None value.
568    // Hence, we need to use Option type for `inner`.
569    // We expect the value to be set in a valid response.
570    #[prost(oneof = "RawValidatorTransactionStatus", tags = "1, 2, 3")]
571    pub inner: Option<RawValidatorTransactionStatus>,
572}
573
574#[derive(Clone, prost::Oneof)]
575pub enum RawValidatorTransactionStatus {
576    #[prost(message, tag = "1")]
577    Executed(RawExecutedStatus),
578    #[prost(message, tag = "2")]
579    Rejected(RawRejectedStatus),
580    #[prost(message, tag = "3")]
581    Expired(RawExpiredStatus),
582}
583
584#[derive(Clone, prost::Message)]
585pub struct RawExecutedStatus {
586    #[prost(bytes = "bytes", tag = "1")]
587    pub effects_digest: Bytes,
588    #[prost(message, optional, tag = "2")]
589    pub details: Option<RawExecutedData>,
590}
591
592#[derive(Clone, prost::Message)]
593pub struct RawRejectedStatus {
594    #[prost(bytes = "bytes", optional, tag = "1")]
595    pub error: Option<Bytes>,
596}
597
598#[derive(Clone, prost::Message)]
599pub struct RawExpiredStatus {
600    // Validator's current epoch.
601    #[prost(uint64, tag = "1")]
602    pub epoch: u64,
603    // Validator's current round. 0 if it is not yet checked.
604    #[prost(uint32, optional, tag = "2")]
605    pub round: Option<u32>,
606}
607
608// =========== ValidatorHealth types ===========
609
610/// Request for validator health information (used for latency measurement)
611#[derive(Clone, Debug, Default)]
612pub struct ValidatorHealthRequest {}
613
614/// Response with validator health metrics (data collected but not used for scoring yet)
615#[derive(Clone, Debug, Default)]
616pub struct ValidatorHealthResponse {
617    /// Number of in-flight execution transactions from execution scheduler
618    pub num_inflight_execution_transactions: u64,
619    /// Number of in-flight consensus transactions
620    pub num_inflight_consensus_transactions: u64,
621    /// Last committed leader round from Mysticeti consensus
622    pub last_committed_leader_round: u32,
623    /// Last locally built checkpoint sequence number
624    pub last_locally_built_checkpoint: u64,
625}
626
627/// Raw protobuf request for validator health information (evolvable)
628#[derive(Clone, prost::Message)]
629pub struct RawValidatorHealthRequest {}
630
631/// Raw protobuf response with validator health metrics (evolvable)
632#[derive(Clone, prost::Message)]
633pub struct RawValidatorHealthResponse {
634    /// Number of pending certificates
635    #[prost(uint64, optional, tag = "1")]
636    pub pending_certificates: Option<u64>,
637    /// Number of in-flight consensus messages
638    #[prost(uint64, optional, tag = "2")]
639    pub inflight_consensus_messages: Option<u64>,
640    /// Current consensus round
641    #[prost(uint64, optional, tag = "3")]
642    pub consensus_round: Option<u64>,
643    /// Current checkpoint sequence number
644    #[prost(uint64, optional, tag = "4")]
645    pub checkpoint_sequence: Option<u64>,
646}
647
648// =========== Parse helpers ===========
649
650impl TryFrom<ExecutedData> for RawExecutedData {
651    type Error = crate::error::SuiError;
652
653    fn try_from(value: ExecutedData) -> Result<Self, Self::Error> {
654        let effects = bcs::to_bytes(&value.effects)
655            .map_err(
656                |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
657                    type_info: "ExecutedData.effects".to_string(),
658                    error: err.to_string(),
659                },
660            )?
661            .into();
662        let events = if let Some(events) = &value.events {
663            Some(
664                bcs::to_bytes(events)
665                    .map_err(
666                        |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
667                            type_info: "ExecutedData.events".to_string(),
668                            error: err.to_string(),
669                        },
670                    )?
671                    .into(),
672            )
673        } else {
674            None
675        };
676        let mut input_objects = Vec::with_capacity(value.input_objects.len());
677        for object in value.input_objects {
678            input_objects.push(
679                bcs::to_bytes(&object)
680                    .map_err(
681                        |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
682                            type_info: "ExecutedData.input_objects".to_string(),
683                            error: err.to_string(),
684                        },
685                    )?
686                    .into(),
687            );
688        }
689        let mut output_objects = Vec::with_capacity(value.output_objects.len());
690        for object in value.output_objects {
691            output_objects.push(
692                bcs::to_bytes(&object)
693                    .map_err(
694                        |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
695                            type_info: "ExecutedData.output_objects".to_string(),
696                            error: err.to_string(),
697                        },
698                    )?
699                    .into(),
700            );
701        }
702        Ok(RawExecutedData {
703            effects,
704            events,
705            input_objects,
706            output_objects,
707        })
708    }
709}
710
711impl TryFrom<RawExecutedData> for ExecutedData {
712    type Error = crate::error::SuiError;
713
714    fn try_from(value: RawExecutedData) -> Result<Self, Self::Error> {
715        let effects = bcs::from_bytes(&value.effects).map_err(|err| {
716            crate::error::SuiErrorKind::GrpcMessageDeserializeError {
717                type_info: "RawExecutedData.effects".to_string(),
718                error: err.to_string(),
719            }
720        })?;
721        let events = if let Some(events) = value.events {
722            Some(bcs::from_bytes(&events).map_err(|err| {
723                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
724                    type_info: "RawExecutedData.events".to_string(),
725                    error: err.to_string(),
726                }
727            })?)
728        } else {
729            None
730        };
731        let mut input_objects = Vec::with_capacity(value.input_objects.len());
732        for object in value.input_objects {
733            input_objects.push(bcs::from_bytes(&object).map_err(|err| {
734                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
735                    type_info: "RawExecutedData.input_objects".to_string(),
736                    error: err.to_string(),
737                }
738            })?);
739        }
740        let mut output_objects = Vec::with_capacity(value.output_objects.len());
741        for object in value.output_objects {
742            output_objects.push(bcs::from_bytes(&object).map_err(|err| {
743                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
744                    type_info: "RawExecutedData.output_objects".to_string(),
745                    error: err.to_string(),
746                }
747            })?);
748        }
749        Ok(ExecutedData {
750            effects,
751            events,
752            input_objects,
753            output_objects,
754        })
755    }
756}
757
758impl TryFrom<SubmitTxResult> for RawSubmitTxResult {
759    type Error = crate::error::SuiError;
760
761    fn try_from(value: SubmitTxResult) -> Result<Self, Self::Error> {
762        let inner = match value {
763            SubmitTxResult::Submitted { consensus_position } => {
764                let consensus_position = consensus_position.into_raw()?;
765                RawValidatorSubmitStatus::Submitted(consensus_position)
766            }
767            SubmitTxResult::Executed {
768                effects_digest,
769                details,
770            } => {
771                let raw_executed = try_from_response_executed(effects_digest, details)?;
772                RawValidatorSubmitStatus::Executed(raw_executed)
773            }
774            SubmitTxResult::Rejected { error } => {
775                RawValidatorSubmitStatus::Rejected(try_from_response_rejected(Some(error))?)
776            }
777        };
778        Ok(RawSubmitTxResult { inner: Some(inner) })
779    }
780}
781
782impl TryFrom<RawSubmitTxResult> for SubmitTxResult {
783    type Error = crate::error::SuiError;
784
785    fn try_from(value: RawSubmitTxResult) -> Result<Self, Self::Error> {
786        match value.inner {
787            Some(RawValidatorSubmitStatus::Submitted(consensus_position)) => {
788                Ok(SubmitTxResult::Submitted {
789                    consensus_position: consensus_position.as_ref().try_into()?,
790                })
791            }
792            Some(RawValidatorSubmitStatus::Executed(executed)) => {
793                let (effects_digest, details) = try_from_raw_executed_status(executed)?;
794                Ok(SubmitTxResult::Executed {
795                    effects_digest,
796                    details,
797                })
798            }
799            Some(RawValidatorSubmitStatus::Rejected(error)) => {
800                let error = try_from_raw_rejected_status(error)?.unwrap_or(
801                    crate::error::SuiErrorKind::GrpcMessageDeserializeError {
802                        type_info: "RawSubmitTxResult.inner.Error".to_string(),
803                        error: "RawSubmitTxResult.inner.Error is None".to_string(),
804                    }
805                    .into(),
806                );
807                Ok(SubmitTxResult::Rejected { error })
808            }
809            None => Err(crate::error::SuiErrorKind::GrpcMessageDeserializeError {
810                type_info: "RawSubmitTxResult.inner".to_string(),
811                error: "RawSubmitTxResult.inner is None".to_string(),
812            }
813            .into()),
814        }
815    }
816}
817
818impl TryFrom<RawSubmitTxResponse> for SubmitTxResponse {
819    type Error = crate::error::SuiError;
820
821    fn try_from(value: RawSubmitTxResponse) -> Result<Self, Self::Error> {
822        // TODO(fastpath): handle multiple transactions.
823        if value.results.len() != 1 {
824            return Err(crate::error::SuiErrorKind::GrpcMessageDeserializeError {
825                type_info: "RawSubmitTxResponse.results".to_string(),
826                error: format!("Expected exactly 1 result, got {}", value.results.len()),
827            }
828            .into());
829        }
830
831        let results = value
832            .results
833            .into_iter()
834            .map(|result| result.try_into())
835            .collect::<Result<Vec<SubmitTxResult>, crate::error::SuiError>>()?;
836
837        Ok(Self { results })
838    }
839}
840
841fn try_from_raw_executed_status(
842    executed: RawExecutedStatus,
843) -> Result<
844    (
845        crate::digests::TransactionEffectsDigest,
846        Option<Box<ExecutedData>>,
847    ),
848    crate::error::SuiError,
849> {
850    let effects_digest = bcs::from_bytes(&executed.effects_digest).map_err(|err| {
851        crate::error::SuiErrorKind::GrpcMessageDeserializeError {
852            type_info: "RawWaitForEffectsResponse.effects_digest".to_string(),
853            error: err.to_string(),
854        }
855    })?;
856    let executed_data = if let Some(details) = executed.details {
857        Some(Box::new(details.try_into()?))
858    } else {
859        None
860    };
861    Ok((effects_digest, executed_data))
862}
863
864fn try_from_raw_rejected_status(
865    rejected: RawRejectedStatus,
866) -> Result<Option<crate::error::SuiError>, crate::error::SuiError> {
867    match rejected.error {
868        Some(error_bytes) => {
869            let error = bcs::from_bytes(&error_bytes).map_err(|err| {
870                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
871                    type_info: "RawWaitForEffectsResponse.rejected.reason".to_string(),
872                    error: err.to_string(),
873                }
874            })?;
875            Ok(Some(error))
876        }
877        None => Ok(None),
878    }
879}
880
881fn try_from_response_rejected(
882    error: Option<crate::error::SuiError>,
883) -> Result<RawRejectedStatus, crate::error::SuiError> {
884    let error = match error {
885        Some(e) => Some(
886            bcs::to_bytes(&e)
887                .map_err(
888                    |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
889                        type_info: "RawRejectedStatus.error".to_string(),
890                        error: err.to_string(),
891                    },
892                )?
893                .into(),
894        ),
895        None => None,
896    };
897    Ok(RawRejectedStatus { error })
898}
899
900fn try_from_response_executed(
901    effects_digest: crate::digests::TransactionEffectsDigest,
902    details: Option<Box<ExecutedData>>,
903) -> Result<RawExecutedStatus, crate::error::SuiError> {
904    let effects_digest = bcs::to_bytes(&effects_digest)
905        .map_err(
906            |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
907                type_info: "RawWaitForEffectsResponse.effects_digest".to_string(),
908                error: err.to_string(),
909            },
910        )?
911        .into();
912    let details = if let Some(details) = details {
913        Some((*details).try_into()?)
914    } else {
915        None
916    };
917    Ok(RawExecutedStatus {
918        effects_digest,
919        details,
920    })
921}
922
923impl TryFrom<RawWaitForEffectsRequest> for WaitForEffectsRequest {
924    type Error = crate::error::SuiError;
925
926    fn try_from(value: RawWaitForEffectsRequest) -> Result<Self, Self::Error> {
927        let transaction_digest = match value.transaction_digest {
928            Some(digest) => Some(bcs::from_bytes(&digest).map_err(|err| {
929                crate::error::SuiErrorKind::GrpcMessageDeserializeError {
930                    type_info: "RawWaitForEffectsRequest.transaction_digest".to_string(),
931                    error: err.to_string(),
932                }
933            })?),
934            None => None,
935        };
936        let consensus_position = match value.consensus_position {
937            Some(cp) => Some(cp.as_ref().try_into()?),
938            None => None,
939        };
940        let ping_type = value
941            .ping_type
942            .map(|p| {
943                PingType::try_from(p).map_err(|e| SuiErrorKind::GrpcMessageDeserializeError {
944                    type_info: "RawWaitForEffectsRequest.ping_type".to_string(),
945                    error: e.to_string(),
946                })
947            })
948            .transpose()?;
949        Ok(Self {
950            consensus_position,
951            transaction_digest,
952            include_details: value.include_details,
953            ping_type,
954        })
955    }
956}
957
958impl TryFrom<WaitForEffectsRequest> for RawWaitForEffectsRequest {
959    type Error = crate::error::SuiError;
960
961    fn try_from(value: WaitForEffectsRequest) -> Result<Self, Self::Error> {
962        let transaction_digest = match value.transaction_digest {
963            Some(digest) => Some(
964                bcs::to_bytes(&digest)
965                    .map_err(
966                        |err| crate::error::SuiErrorKind::GrpcMessageSerializeError {
967                            type_info: "RawWaitForEffectsRequest.transaction_digest".to_string(),
968                            error: err.to_string(),
969                        },
970                    )?
971                    .into(),
972            ),
973            None => None,
974        };
975        let consensus_position = match value.consensus_position {
976            Some(cp) => Some(cp.into_raw()?),
977            None => None,
978        };
979        let ping_type = value.ping_type.map(|p| p.into());
980        Ok(Self {
981            consensus_position,
982            transaction_digest,
983            include_details: value.include_details,
984            ping_type,
985        })
986    }
987}
988
989impl TryFrom<RawWaitForEffectsResponse> for WaitForEffectsResponse {
990    type Error = crate::error::SuiError;
991
992    fn try_from(value: RawWaitForEffectsResponse) -> Result<Self, Self::Error> {
993        match value.inner {
994            Some(RawValidatorTransactionStatus::Executed(executed)) => {
995                let (effects_digest, details) = try_from_raw_executed_status(executed)?;
996                Ok(Self::Executed {
997                    effects_digest,
998                    details,
999                })
1000            }
1001            Some(RawValidatorTransactionStatus::Rejected(rejected)) => {
1002                let error = try_from_raw_rejected_status(rejected)?;
1003                Ok(Self::Rejected { error })
1004            }
1005            Some(RawValidatorTransactionStatus::Expired(expired)) => Ok(Self::Expired {
1006                epoch: expired.epoch,
1007                round: expired.round,
1008            }),
1009            None => Err(crate::error::SuiErrorKind::GrpcMessageDeserializeError {
1010                type_info: "RawWaitForEffectsResponse.inner".to_string(),
1011                error: "RawWaitForEffectsResponse.inner is None".to_string(),
1012            }
1013            .into()),
1014        }
1015    }
1016}
1017
1018impl TryFrom<WaitForEffectsResponse> for RawWaitForEffectsResponse {
1019    type Error = crate::error::SuiError;
1020
1021    fn try_from(value: WaitForEffectsResponse) -> Result<Self, Self::Error> {
1022        let inner = match value {
1023            WaitForEffectsResponse::Executed {
1024                effects_digest,
1025                details,
1026            } => {
1027                let raw_executed = try_from_response_executed(effects_digest, details)?;
1028                RawValidatorTransactionStatus::Executed(raw_executed)
1029            }
1030            WaitForEffectsResponse::Rejected { error } => {
1031                let raw_rejected = try_from_response_rejected(error)?;
1032                RawValidatorTransactionStatus::Rejected(raw_rejected)
1033            }
1034            WaitForEffectsResponse::Expired { epoch, round } => {
1035                RawValidatorTransactionStatus::Expired(RawExpiredStatus { epoch, round })
1036            }
1037        };
1038        Ok(RawWaitForEffectsResponse { inner: Some(inner) })
1039    }
1040}
1041
1042impl TryFrom<ValidatorHealthRequest> for RawValidatorHealthRequest {
1043    type Error = crate::error::SuiError;
1044
1045    fn try_from(_value: ValidatorHealthRequest) -> Result<Self, Self::Error> {
1046        Ok(Self {})
1047    }
1048}
1049
1050impl TryFrom<RawValidatorHealthRequest> for ValidatorHealthRequest {
1051    type Error = crate::error::SuiError;
1052
1053    fn try_from(_value: RawValidatorHealthRequest) -> Result<Self, Self::Error> {
1054        // Empty request - ignore reserved field for now
1055        Ok(Self {})
1056    }
1057}
1058
1059impl TryFrom<ValidatorHealthResponse> for RawValidatorHealthResponse {
1060    type Error = crate::error::SuiError;
1061
1062    fn try_from(value: ValidatorHealthResponse) -> Result<Self, Self::Error> {
1063        Ok(Self {
1064            pending_certificates: Some(value.num_inflight_execution_transactions),
1065            inflight_consensus_messages: Some(value.num_inflight_consensus_transactions),
1066            consensus_round: Some(value.last_committed_leader_round as u64),
1067            checkpoint_sequence: Some(value.last_locally_built_checkpoint),
1068        })
1069    }
1070}
1071
1072impl TryFrom<RawValidatorHealthResponse> for ValidatorHealthResponse {
1073    type Error = crate::error::SuiError;
1074
1075    fn try_from(value: RawValidatorHealthResponse) -> Result<Self, Self::Error> {
1076        Ok(Self {
1077            num_inflight_consensus_transactions: value.inflight_consensus_messages.unwrap_or(0),
1078            num_inflight_execution_transactions: value.pending_certificates.unwrap_or(0),
1079            last_locally_built_checkpoint: value.checkpoint_sequence.unwrap_or(0),
1080            last_committed_leader_round: value.consensus_round.unwrap_or(0) as u32,
1081        })
1082    }
1083}
1084
1085#[cfg(test)]
1086mod tests {
1087    use crate::{
1088        messages_grpc::{SubmitTxRequest, SubmitTxType},
1089        transaction::{Transaction, TransactionData},
1090    };
1091
1092    #[tokio::test]
1093    async fn test_submit_tx_request_into_raw() {
1094        println!(
1095            "Case 1. SubmitTxRequest::new_ping should be converted to RawSubmitTxRequest with submit_type set to Ping."
1096        );
1097        {
1098            let request = SubmitTxRequest::new_ping();
1099            let raw_request = request.into_raw().unwrap();
1100
1101            let submit_type = SubmitTxType::try_from(raw_request.submit_type).unwrap();
1102            assert_eq!(submit_type, SubmitTxType::Ping);
1103        }
1104
1105        println!(
1106            "Case 2. SubmitTxRequest::new_transaction should be converted to RawSubmitTxRequest with submit_type set to Default."
1107        );
1108        {
1109            // Create a dummy transaction for testing
1110            let sender = crate::base_types::SuiAddress::random_for_testing_only();
1111            let recipient = crate::base_types::SuiAddress::random_for_testing_only();
1112            let gas_object_ref = crate::base_types::random_object_ref();
1113
1114            let tx_data = TransactionData::new_transfer_sui(
1115                recipient,
1116                sender,
1117                None,
1118                gas_object_ref,
1119                1000,
1120                1000,
1121            );
1122
1123            let (_, keypair) = crate::crypto::get_account_key_pair();
1124            let transaction = Transaction::from_data_and_signer(tx_data, vec![&keypair]);
1125
1126            let request = SubmitTxRequest::new_transaction(transaction);
1127            let raw_request = request.into_raw().unwrap();
1128
1129            let submit_type = SubmitTxType::try_from(raw_request.submit_type).unwrap();
1130            assert_eq!(submit_type, SubmitTxType::Default);
1131            assert_eq!(raw_request.transactions.len(), 1);
1132        }
1133    }
1134}