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::sui::rpc::v2 as proto;
10use sui_rpc::proto::TryFromProtoError;
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::AuthInterceptor;
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::v2::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::v2::Client::new(uri).map(Self)
37    }
38
39    pub fn with_auth(self, auth: AuthInterceptor) -> Self {
40        Self(self.0.with_auth(auth))
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(FieldMask::from_paths([
84                "summary.bcs",
85                "signature",
86                "contents.bcs",
87                "transactions.transaction.bcs",
88                "transactions.effects.bcs",
89                "transactions.effects.unchanged_loaded_runtime_objects",
90                "transactions.events.bcs",
91                "objects.objects.bcs",
92            ]));
93
94        let (metadata, response, _extentions) = self
95            .0
96            .ledger_client()
97            .max_decoding_message_size(128 * 1024 * 1024)
98            .get_checkpoint(request)
99            .await?
100            .into_parts();
101
102        let checkpoint = response
103            .checkpoint
104            .ok_or_else(|| tonic::Status::not_found("no checkpoint returned"))?;
105        sui_types::full_checkpoint_content::Checkpoint::try_from(&checkpoint)
106            .map(Into::into)
107            .map_err(|e| status_from_error_with_metadata(e, metadata))
108    }
109
110    pub async fn get_object(&mut self, object_id: ObjectID) -> Result<Object> {
111        self.get_object_internal(object_id, None).await
112    }
113
114    pub async fn get_object_with_version(
115        &mut self,
116        object_id: ObjectID,
117        version: SequenceNumber,
118    ) -> Result<Object> {
119        self.get_object_internal(object_id, Some(version.value()))
120            .await
121    }
122
123    async fn get_object_internal(
124        &mut self,
125        object_id: ObjectID,
126        version: Option<u64>,
127    ) -> Result<Object> {
128        let mut request = proto::GetObjectRequest::new(&object_id.into())
129            .with_read_mask(FieldMask::from_paths(["bcs"]));
130        request.version = version;
131
132        let (metadata, object, _extentions) = self
133            .0
134            .ledger_client()
135            .get_object(request)
136            .await?
137            .into_parts();
138
139        let object = object
140            .object
141            .ok_or_else(|| tonic::Status::not_found("no object returned"))?;
142        object_try_from_proto(&object).map_err(|e| status_from_error_with_metadata(e, metadata))
143    }
144
145    pub async fn execute_transaction(
146        &mut self,
147        transaction: &Transaction,
148    ) -> Result<TransactionExecutionResponse> {
149        let signatures = transaction
150            .inner()
151            .tx_signatures
152            .iter()
153            .map(|signature| {
154                let mut message = proto::UserSignature::default();
155                message.bcs = Some(signature.as_ref().to_vec().into());
156                message
157            })
158            .collect();
159
160        let request = proto::ExecuteTransactionRequest::new({
161            let mut tx = proto::Transaction::default();
162            tx.bcs = Some(
163                proto::Bcs::serialize(&transaction.inner().intent_message.value)
164                    .map_err(|e| Status::from_error(e.into()))?,
165            );
166            tx
167        })
168        .with_signatures(signatures)
169        .with_read_mask(FieldMask::from_paths([
170            "effects.bcs",
171            "events.bcs",
172            "balance_changes",
173            "objects.objects.bcs",
174        ]));
175
176        let (metadata, response, _extentions) = self
177            .0
178            .execution_client()
179            .execute_transaction(request)
180            .await?
181            .into_parts();
182
183        execute_transaction_response_try_from_proto(&response)
184            .map_err(|e| status_from_error_with_metadata(e, metadata))
185    }
186}
187
188#[derive(Debug)]
189pub struct TransactionExecutionResponse {
190    pub effects: TransactionEffects,
191    pub events: Option<TransactionEvents>,
192    pub balance_changes: Vec<sui_sdk_types::BalanceChange>,
193    pub objects: ObjectSet,
194}
195
196/// Attempts to parse `CertifiedCheckpointSummary` from a proto::Checkpoint
197#[allow(clippy::result_large_err)]
198fn certified_checkpoint_summary_try_from_proto(
199    checkpoint: &proto::Checkpoint,
200) -> Result<CertifiedCheckpointSummary, TryFromProtoError> {
201    let summary = checkpoint
202        .summary
203        .as_ref()
204        .and_then(|summary| summary.bcs.as_ref())
205        .ok_or_else(|| TryFromProtoError::missing("summary.bcs"))?
206        .deserialize()
207        .map_err(|e| TryFromProtoError::invalid("summary.bcs", e))?;
208
209    let signature = sui_types::crypto::AuthorityStrongQuorumSignInfo::from(
210        sui_sdk_types::ValidatorAggregatedSignature::try_from(
211            checkpoint
212                .signature
213                .as_ref()
214                .ok_or_else(|| TryFromProtoError::missing("signature"))?,
215        )
216        .map_err(|e| TryFromProtoError::invalid("signature", e))?,
217    );
218
219    Ok(CertifiedCheckpointSummary::new_from_data_and_sig(
220        summary, signature,
221    ))
222}
223
224/// Attempts to parse `Object` from the bcs fields in `GetObjectResponse`
225#[allow(clippy::result_large_err)]
226fn object_try_from_proto(object: &proto::Object) -> Result<Object, TryFromProtoError> {
227    object
228        .bcs
229        .as_ref()
230        .ok_or_else(|| TryFromProtoError::missing("bcs"))?
231        .deserialize()
232        .map_err(|e| TryFromProtoError::invalid("bcs", e))
233}
234
235/// Attempts to parse `TransactionExecutionResponse` from the fields in `TransactionExecutionResponse`
236#[allow(clippy::result_large_err)]
237fn execute_transaction_response_try_from_proto(
238    response: &proto::ExecuteTransactionResponse,
239) -> Result<TransactionExecutionResponse, TryFromProtoError> {
240    let executed_transaction = response
241        .transaction
242        .as_ref()
243        .ok_or_else(|| TryFromProtoError::missing("transaction"))?;
244
245    let effects = executed_transaction
246        .effects
247        .as_ref()
248        .and_then(|effects| effects.bcs.as_ref())
249        .ok_or_else(|| TryFromProtoError::missing("effects_bcs"))?
250        .deserialize()
251        .map_err(|e| TryFromProtoError::invalid("effects.bcs", e))?;
252    let events = executed_transaction
253        .events
254        .as_ref()
255        .and_then(|events| events.bcs.as_ref())
256        .map(|bcs| bcs.deserialize())
257        .transpose()
258        .map_err(|e| TryFromProtoError::invalid("events.bcs", e))?;
259
260    let balance_changes = executed_transaction
261        .balance_changes
262        .iter()
263        .map(TryInto::try_into)
264        .collect::<Result<_, _>>()?;
265
266    let objects = executed_transaction
267        .objects()
268        .try_into()
269        .map_err(|e| TryFromProtoError::invalid("objects.bcs", e))?;
270
271    TransactionExecutionResponse {
272        effects,
273        events,
274        balance_changes,
275        objects,
276    }
277    .pipe(Ok)
278}
279
280fn status_from_error_with_metadata<T: Into<BoxError>>(err: T, metadata: MetadataMap) -> Status {
281    let mut status = Status::from_error(err.into());
282    *status.metadata_mut() = metadata;
283    status
284}