1use std::collections::{BTreeSet, HashMap};
5use std::sync::Arc;
6
7use mysten_common::ZipDebugEqIteratorExt;
8use sui_sdk_types::{EpochId, ValidatorCommittee};
9use sui_types::base_types::TransactionDigest;
10use sui_types::effects::TransactionEffectsAPI;
11use sui_types::storage::ObjectKey;
12use sui_types::storage::RpcStateReader;
13use sui_types::storage::error::{Error as StorageError, Result};
14use tap::Pipe;
15
16#[derive(Clone)]
17pub struct StateReader {
18 inner: Arc<dyn RpcStateReader>,
19}
20
21impl StateReader {
22 pub fn new(inner: Arc<dyn RpcStateReader>) -> Self {
23 Self { inner }
24 }
25
26 pub fn inner(&self) -> &Arc<dyn RpcStateReader> {
27 &self.inner
28 }
29
30 #[tracing::instrument(skip(self))]
31 pub fn get_committee(&self, epoch: EpochId) -> Option<ValidatorCommittee> {
32 self.inner
33 .get_committee(epoch)
34 .map(|committee| (*committee).clone().into())
35 }
36
37 #[tracing::instrument(skip(self))]
38 pub fn get_system_state(&self) -> Result<sui_types::sui_system_state::SuiSystemState> {
39 sui_types::sui_system_state::get_sui_system_state(self.inner())
40 .map_err(StorageError::custom)
41 .map_err(StorageError::custom)
42 }
43
44 #[tracing::instrument(skip(self))]
45 pub fn get_display_object_v2_by_type(
46 &self,
47 object_type: &move_core_types::language_storage::StructTag,
48 ) -> Option<sui_types::display_registry::Display> {
49 let object_id =
50 sui_types::display_registry::display_object_id(object_type.clone().into()).ok()?;
51
52 let object = self.inner.get_object(&object_id)?;
53
54 let move_object = object.data.try_as_move()?;
55
56 bcs::from_bytes(move_object.contents()).ok()
57 }
58
59 #[tracing::instrument(skip(self))]
60 pub fn get_system_state_summary(
61 &self,
62 ) -> Result<sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary> {
63 use sui_types::sui_system_state::SuiSystemStateTrait;
64
65 let system_state = self.get_system_state()?;
66 let summary = system_state.into_sui_system_state_summary();
67
68 Ok(summary)
69 }
70
71 pub fn get_authenticator_state(
72 &self,
73 ) -> Result<Option<sui_types::authenticator_state::AuthenticatorStateInner>> {
74 sui_types::authenticator_state::get_authenticator_state(self.inner())
75 .map_err(StorageError::custom)
76 }
77
78 #[tracing::instrument(skip(self))]
79 pub fn get_transaction(
80 &self,
81 digest: sui_sdk_types::Digest,
82 ) -> crate::Result<(
83 sui_types::transaction::TransactionData,
84 Vec<sui_types::signature::GenericSignature>,
85 sui_types::effects::TransactionEffects,
86 Option<sui_types::effects::TransactionEvents>,
87 )> {
88 let transaction_digest = digest.into();
89
90 let transaction = (*self
91 .inner()
92 .get_transaction(&transaction_digest)
93 .ok_or(TransactionNotFoundError(digest))?)
94 .clone()
95 .into_inner();
96 let effects = self
97 .inner()
98 .get_transaction_effects(&transaction_digest)
99 .ok_or(TransactionNotFoundError(digest))?;
100 let events = if effects.events_digest().is_some() {
101 self.inner()
102 .get_events(effects.transaction_digest())
103 .ok_or(TransactionNotFoundError(digest))?
104 .pipe(Some)
105 } else {
106 None
107 };
108
109 let transaction = transaction.into_data().into_inner();
110 let signatures = transaction.tx_signatures;
111 let transaction = transaction.intent_message.value;
112
113 Ok((transaction, signatures, effects, events))
114 }
115
116 pub fn multi_get_transaction_reads(
118 &self,
119 items: &[(sui_sdk_types::Digest, u64)],
120 ) -> crate::Result<Vec<TransactionRead>> {
121 let transaction_digests = items
122 .iter()
123 .map(|(digest, _)| (*digest).into())
124 .collect::<Vec<TransactionDigest>>();
125 let transactions = self.inner().multi_get_transactions(&transaction_digests);
126 let effects = self
127 .inner()
128 .multi_get_transaction_effects(&transaction_digests);
129 let events = self.inner().multi_get_events(&transaction_digests);
130 let unchanged_loaded_runtime_objects = self
131 .inner()
132 .multi_get_unchanged_loaded_runtime_objects(&transaction_digests);
133 let timestamps = dedup_checkpoint_timestamps(
134 items.iter().map(|(_, checkpoint)| *checkpoint),
135 |unique| {
136 self.inner()
137 .multi_get_checkpoint_by_sequence_number(unique)
138 .into_iter()
139 .map(|checkpoint| checkpoint.map(|checkpoint| checkpoint.timestamp_ms))
140 .collect()
141 },
142 );
143
144 let mut reads = Vec::with_capacity(items.len());
145 for (
146 ((((digest, checkpoint), _transaction_digest), transaction), (effects, events)),
147 unchanged_loaded_runtime_objects,
148 ) in items
149 .iter()
150 .copied()
151 .zip_debug_eq(transaction_digests)
152 .zip_debug_eq(transactions)
153 .zip_debug_eq(effects.into_iter().zip_debug_eq(events))
154 .zip_debug_eq(unchanged_loaded_runtime_objects)
155 {
156 let transaction = (*transaction.ok_or(TransactionNotFoundError(digest))?)
157 .clone()
158 .into_inner();
159 let effects = effects.ok_or(TransactionNotFoundError(digest))?;
160 let events = if effects.events_digest().is_some() {
161 events.ok_or(TransactionNotFoundError(digest))?.pipe(Some)
162 } else {
163 None
164 };
165
166 let transaction = transaction.into_data().into_inner();
167 let signatures = transaction.tx_signatures;
168 let transaction = transaction.intent_message.value;
169 let timestamp_ms = timestamps[&checkpoint];
170
171 reads.push(TransactionRead {
172 digest,
173 transaction,
174 signatures,
175 effects,
176 events,
177 timestamp_ms,
178 unchanged_loaded_runtime_objects,
179 });
180 }
181
182 Ok(reads)
183 }
184
185 pub fn multi_get_events(
186 &self,
187 digests: &[TransactionDigest],
188 ) -> Vec<Option<sui_types::effects::TransactionEvents>> {
189 self.inner().multi_get_events(digests)
190 }
191
192 #[tracing::instrument(skip(self))]
193 pub fn get_transaction_read(
194 &self,
195 digest: sui_sdk_types::Digest,
196 ) -> crate::Result<TransactionRead> {
197 let (transaction, signatures, effects, events) = self.get_transaction(digest)?;
198
199 let checkpoint = self.inner().get_transaction_checkpoint(&(digest.into()));
200 let timestamp_ms =
201 checkpoint.and_then(|checkpoint| self.checkpoint_timestamp_ms(checkpoint));
202
203 let unchanged_loaded_runtime_objects = self
204 .inner()
205 .get_unchanged_loaded_runtime_objects(&(digest.into()));
206
207 Ok(TransactionRead {
208 digest,
209 transaction,
210 signatures,
211 effects,
212 events,
213 timestamp_ms,
214 unchanged_loaded_runtime_objects,
215 })
216 }
217
218 fn checkpoint_timestamp_ms(&self, checkpoint: u64) -> Option<u64> {
220 self.inner()
221 .get_checkpoint_by_sequence_number(checkpoint)
222 .map(|checkpoint| checkpoint.timestamp_ms)
223 }
224
225 pub fn lookup_address_balance(
226 &self,
227 owner: sui_types::base_types::SuiAddress,
228 coin_type: move_core_types::language_storage::StructTag,
229 ) -> Option<u64> {
230 use sui_types::MoveTypeTagTraitGeneric;
231 use sui_types::SUI_ACCUMULATOR_ROOT_OBJECT_ID;
232 use sui_types::accumulator_root::AccumulatorKey;
233 use sui_types::dynamic_field::DynamicFieldKey;
234
235 let balance_type = sui_types::balance::Balance::type_tag(coin_type.into());
236
237 let key = AccumulatorKey { owner };
238 let key_type_tag = AccumulatorKey::get_type_tag(&[balance_type]);
239
240 DynamicFieldKey(SUI_ACCUMULATOR_ROOT_OBJECT_ID, key, key_type_tag)
241 .into_unbounded_id()
242 .unwrap()
243 .load_object(self.inner())
244 .and_then(|o| o.load_value::<u128>().ok())
245 .map(|balance| balance as u64)
246 }
247
248 pub fn get_lowest_available_checkpoint(&self) -> Result<u64, crate::RpcError> {
251 let lowest_available_checkpoint = self.inner().get_lowest_available_checkpoint()?;
253 let lowest_available_checkpoint_objects =
255 self.inner().get_lowest_available_checkpoint_objects()?;
256
257 Ok(lowest_available_checkpoint.max(lowest_available_checkpoint_objects))
259 }
260}
261
262fn dedup_checkpoint_timestamps(
263 checkpoints: impl IntoIterator<Item = u64>,
264 fetch: impl FnOnce(&[u64]) -> Vec<Option<u64>>,
265) -> HashMap<u64, Option<u64>> {
266 let unique_checkpoints = checkpoints
267 .into_iter()
268 .collect::<BTreeSet<_>>()
269 .into_iter()
270 .collect::<Vec<_>>();
271 if unique_checkpoints.is_empty() {
272 return HashMap::new();
273 }
274
275 let timestamps = fetch(&unique_checkpoints);
276 unique_checkpoints
277 .into_iter()
278 .zip_debug_eq(timestamps)
279 .collect()
280}
281
282#[derive(Debug)]
283pub struct TransactionRead {
284 pub digest: sui_sdk_types::Digest,
285 pub transaction: sui_types::transaction::TransactionData,
286 pub signatures: Vec<sui_types::signature::GenericSignature>,
287 pub effects: sui_types::effects::TransactionEffects,
288 pub events: Option<sui_types::effects::TransactionEvents>,
289 pub timestamp_ms: Option<u64>,
290 pub unchanged_loaded_runtime_objects: Option<Vec<ObjectKey>>,
291}
292
293#[derive(Debug)]
294pub struct TransactionNotFoundError(pub sui_sdk_types::Digest);
295
296impl std::fmt::Display for TransactionNotFoundError {
297 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298 write!(f, "Transaction {} not found", self.0)
299 }
300}
301
302impl std::error::Error for TransactionNotFoundError {}
303
304impl From<TransactionNotFoundError> for crate::RpcError {
305 fn from(value: TransactionNotFoundError) -> Self {
306 Self::new(tonic::Code::NotFound, value.to_string())
307 }
308}
309
310pub struct DisplayStore<'s> {
311 state: &'s StateReader,
312}
313
314impl<'s> DisplayStore<'s> {
315 pub fn new(state: &'s StateReader) -> Self {
316 Self { state }
317 }
318}
319
320#[async_trait::async_trait]
321impl sui_display::v2::Store for DisplayStore<'_> {
322 async fn latest(
323 &self,
324 id: move_core_types::account_address::AccountAddress,
325 ) -> anyhow::Result<Option<(move_core_types::annotated_value::MoveTypeLayout, Vec<u8>)>> {
326 let Some(object) = self.state.inner().get_object(&id.into()) else {
327 return Ok(None);
328 };
329
330 let Some(move_object) = object.data.try_as_move() else {
331 return Ok(None);
332 };
333
334 let object_type = move_object.type_().clone().into();
335
336 let Some(layout) = self.state.inner().get_struct_layout(&object_type)? else {
337 return Ok(None);
338 };
339
340 Ok(Some((layout, move_object.contents().to_vec())))
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use std::cell::Cell;
347
348 use super::*;
349
350 #[test]
351 fn dedup_checkpoint_timestamps_fetches_sorted_unique_checkpoints_once() {
352 let fetch_calls = Cell::new(0);
353 let timestamps = dedup_checkpoint_timestamps([5, 5, 3, 5, 3], |checkpoints| {
354 fetch_calls.set(fetch_calls.get() + 1);
355 assert_eq!(checkpoints, &[3, 5]);
356 vec![Some(300), Some(500)]
357 });
358
359 assert_eq!(fetch_calls.get(), 1);
360 assert_eq!(timestamps.len(), 2);
361 assert_eq!(timestamps[&3], Some(300));
362 assert_eq!(timestamps[&5], Some(500));
363 }
364
365 #[test]
366 fn dedup_checkpoint_timestamps_skips_fetch_for_empty_input() {
367 let fetch_calls = Cell::new(0);
368 let timestamps = dedup_checkpoint_timestamps([], |_| {
369 fetch_calls.set(fetch_calls.get() + 1);
370 Vec::new()
371 });
372
373 assert_eq!(fetch_calls.get(), 0);
374 assert!(timestamps.is_empty());
375 }
376
377 #[test]
378 fn dedup_checkpoint_timestamps_preserves_missing_summary() {
379 let timestamps = dedup_checkpoint_timestamps([2, 1], |checkpoints| {
380 assert_eq!(checkpoints, &[1, 2]);
381 vec![None, Some(200)]
382 });
383
384 assert_eq!(timestamps[&1], None);
385 assert_eq!(timestamps[&2], Some(200));
386 }
387}