sui_rpc_api/client/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use tap::Pipe;
5use tonic::metadata::MetadataMap;
6
7use prost_types::FieldMask;
8use sui_rpc::field::FieldMaskUtil;
9use sui_rpc::proto::TryFromProtoError;
10use sui_rpc::proto::sui::rpc::v2 as proto;
11use sui_types::base_types::{ObjectID, SequenceNumber};
12use sui_types::effects::{TransactionEffects, TransactionEvents};
13use sui_types::full_checkpoint_content::CheckpointData;
14use sui_types::full_checkpoint_content::ObjectSet;
15use sui_types::messages_checkpoint::{CertifiedCheckpointSummary, CheckpointSequenceNumber};
16use sui_types::object::Object;
17use sui_types::transaction::Transaction;
18
19pub use sui_rpc::client::HeadersInterceptor;
20pub use sui_rpc::client::ResponseExt;
21
22pub type Result<T, E = tonic::Status> = std::result::Result<T, E>;
23pub type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
24
25use tonic::Status;
26
27#[derive(Clone)]
28pub struct Client(sui_rpc::Client);
29
30impl Client {
31    pub fn new<T>(uri: T) -> Result<Self>
32    where
33        T: TryInto<http::Uri>,
34        T::Error: Into<BoxError>,
35    {
36        sui_rpc::Client::new(uri).map(Self)
37    }
38
39    pub fn with_headers(self, headers: HeadersInterceptor) -> Self {
40        Self(self.0.with_headers(headers))
41    }
42
43    pub async fn get_latest_checkpoint(&mut self) -> Result<CertifiedCheckpointSummary> {
44        self.get_checkpoint_internal(None).await
45    }
46
47    pub async fn get_checkpoint_summary(
48        &mut self,
49        sequence_number: CheckpointSequenceNumber,
50    ) -> Result<CertifiedCheckpointSummary> {
51        self.get_checkpoint_internal(Some(sequence_number)).await
52    }
53
54    async fn get_checkpoint_internal(
55        &mut self,
56        sequence_number: Option<CheckpointSequenceNumber>,
57    ) -> Result<CertifiedCheckpointSummary> {
58        let mut request = proto::GetCheckpointRequest::default()
59            .with_read_mask(FieldMask::from_paths(["summary.bcs", "signature"]));
60        request.checkpoint_id = sequence_number.map(|sequence_number| {
61            proto::get_checkpoint_request::CheckpointId::SequenceNumber(sequence_number)
62        });
63
64        let (metadata, checkpoint, _extentions) = self
65            .0
66            .ledger_client()
67            .get_checkpoint(request)
68            .await?
69            .into_parts();
70
71        let checkpoint = checkpoint
72            .checkpoint
73            .ok_or_else(|| tonic::Status::not_found("no checkpoint returned"))?;
74        certified_checkpoint_summary_try_from_proto(&checkpoint)
75            .map_err(|e| status_from_error_with_metadata(e, metadata))
76    }
77
78    pub async fn get_full_checkpoint(
79        &mut self,
80        sequence_number: CheckpointSequenceNumber,
81    ) -> Result<CheckpointData> {
82        let request = proto::GetCheckpointRequest::by_sequence_number(sequence_number)
83            .with_read_mask(checkpoint_data_field_mask());
84
85        let (metadata, response, _extentions) = self
86            .0
87            .ledger_client()
88            .max_decoding_message_size(128 * 1024 * 1024)
89            .get_checkpoint(request)
90            .await?
91            .into_parts();
92
93        let checkpoint = response
94            .checkpoint
95            .ok_or_else(|| tonic::Status::not_found("no checkpoint returned"))?;
96        sui_types::full_checkpoint_content::Checkpoint::try_from(&checkpoint)
97            .map(Into::into)
98            .map_err(|e| status_from_error_with_metadata(e, metadata))
99    }
100
101    pub async fn get_object(&mut self, object_id: ObjectID) -> Result<Object> {
102        self.get_object_internal(object_id, None).await
103    }
104
105    pub async fn get_object_with_version(
106        &mut self,
107        object_id: ObjectID,
108        version: SequenceNumber,
109    ) -> Result<Object> {
110        self.get_object_internal(object_id, Some(version.value()))
111            .await
112    }
113
114    async fn get_object_internal(
115        &mut self,
116        object_id: ObjectID,
117        version: Option<u64>,
118    ) -> Result<Object> {
119        let mut request = proto::GetObjectRequest::new(&object_id.into())
120            .with_read_mask(FieldMask::from_paths(["bcs"]));
121        request.version = version;
122
123        let (metadata, object, _extentions) = self
124            .0
125            .ledger_client()
126            .get_object(request)
127            .await?
128            .into_parts();
129
130        let object = object
131            .object
132            .ok_or_else(|| tonic::Status::not_found("no object returned"))?;
133        object_try_from_proto(&object).map_err(|e| status_from_error_with_metadata(e, metadata))
134    }
135
136    pub async fn execute_transaction(
137        &mut self,
138        transaction: &Transaction,
139    ) -> Result<TransactionExecutionResponse> {
140        let signatures = transaction
141            .inner()
142            .tx_signatures
143            .iter()
144            .map(|signature| {
145                let mut message = proto::UserSignature::default();
146                message.bcs = Some(signature.as_ref().to_vec().into());
147                message
148            })
149            .collect();
150
151        let request = proto::ExecuteTransactionRequest::new({
152            let mut tx = proto::Transaction::default();
153            tx.bcs = Some(
154                proto::Bcs::serialize(&transaction.inner().intent_message.value)
155                    .map_err(|e| Status::from_error(e.into()))?,
156            );
157            tx
158        })
159        .with_signatures(signatures)
160        .with_read_mask(FieldMask::from_paths([
161            "effects.bcs",
162            "events.bcs",
163            "balance_changes",
164            "objects.objects.bcs",
165        ]));
166
167        let (metadata, response, _extentions) = self
168            .0
169            .execution_client()
170            .execute_transaction(request)
171            .await?
172            .into_parts();
173
174        execute_transaction_response_try_from_proto(&response)
175            .map_err(|e| status_from_error_with_metadata(e, metadata))
176    }
177}
178
179#[derive(Debug)]
180pub struct TransactionExecutionResponse {
181    pub effects: TransactionEffects,
182    pub events: Option<TransactionEvents>,
183    pub balance_changes: Vec<sui_sdk_types::BalanceChange>,
184    pub objects: ObjectSet,
185}
186
187/// Field mask for checkpoint data requests.
188pub fn checkpoint_data_field_mask() -> FieldMask {
189    FieldMask::from_paths([
190        "sequence_number",
191        "summary.bcs",
192        "signature",
193        "contents.bcs",
194        "transactions.transaction.bcs",
195        "transactions.effects.bcs",
196        "transactions.effects.unchanged_loaded_runtime_objects",
197        "transactions.events.bcs",
198        "objects.objects.bcs",
199    ])
200}
201
202/// Attempts to parse `CertifiedCheckpointSummary` from a proto::Checkpoint
203#[allow(clippy::result_large_err)]
204fn certified_checkpoint_summary_try_from_proto(
205    checkpoint: &proto::Checkpoint,
206) -> Result<CertifiedCheckpointSummary, TryFromProtoError> {
207    let summary = checkpoint
208        .summary
209        .as_ref()
210        .and_then(|summary| summary.bcs.as_ref())
211        .ok_or_else(|| TryFromProtoError::missing("summary.bcs"))?
212        .deserialize()
213        .map_err(|e| TryFromProtoError::invalid("summary.bcs", e))?;
214
215    let signature = sui_types::crypto::AuthorityStrongQuorumSignInfo::from(
216        sui_sdk_types::ValidatorAggregatedSignature::try_from(
217            checkpoint
218                .signature
219                .as_ref()
220                .ok_or_else(|| TryFromProtoError::missing("signature"))?,
221        )
222        .map_err(|e| TryFromProtoError::invalid("signature", e))?,
223    );
224
225    Ok(CertifiedCheckpointSummary::new_from_data_and_sig(
226        summary, signature,
227    ))
228}
229
230/// Attempts to parse `Object` from the bcs fields in `GetObjectResponse`
231#[allow(clippy::result_large_err)]
232fn object_try_from_proto(object: &proto::Object) -> Result<Object, TryFromProtoError> {
233    object
234        .bcs
235        .as_ref()
236        .ok_or_else(|| TryFromProtoError::missing("bcs"))?
237        .deserialize()
238        .map_err(|e| TryFromProtoError::invalid("bcs", e))
239}
240
241/// Attempts to parse `TransactionExecutionResponse` from the fields in `TransactionExecutionResponse`
242#[allow(clippy::result_large_err)]
243fn execute_transaction_response_try_from_proto(
244    response: &proto::ExecuteTransactionResponse,
245) -> Result<TransactionExecutionResponse, TryFromProtoError> {
246    let executed_transaction = response
247        .transaction
248        .as_ref()
249        .ok_or_else(|| TryFromProtoError::missing("transaction"))?;
250
251    let effects = executed_transaction
252        .effects
253        .as_ref()
254        .and_then(|effects| effects.bcs.as_ref())
255        .ok_or_else(|| TryFromProtoError::missing("effects_bcs"))?
256        .deserialize()
257        .map_err(|e| TryFromProtoError::invalid("effects.bcs", e))?;
258    let events = executed_transaction
259        .events
260        .as_ref()
261        .and_then(|events| events.bcs.as_ref())
262        .map(|bcs| bcs.deserialize())
263        .transpose()
264        .map_err(|e| TryFromProtoError::invalid("events.bcs", e))?;
265
266    let balance_changes = executed_transaction
267        .balance_changes
268        .iter()
269        .map(TryInto::try_into)
270        .collect::<Result<_, _>>()?;
271
272    let objects = executed_transaction
273        .objects()
274        .try_into()
275        .map_err(|e| TryFromProtoError::invalid("objects.bcs", e))?;
276
277    TransactionExecutionResponse {
278        effects,
279        events,
280        balance_changes,
281        objects,
282    }
283    .pipe(Ok)
284}
285
286fn status_from_error_with_metadata<T: Into<BoxError>>(err: T, metadata: MetadataMap) -> Status {
287    let mut status = Status::from_error(err.into());
288    *status.metadata_mut() = metadata;
289    status
290}