sui_rpc_api/client/
mod.rs1use 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
187pub 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#[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#[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#[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}