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