Skip to main content

sui_rpc_api/
reader.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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    /// Fetch transaction reads using checkpoints supplied by the ledger index rows.
117    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    /// Timestamp of the checkpoint summary, if it is still in the store.
219    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    // Return the lowest available checkpoint watermark for which the RPC service can return proper
249    // responses for.
250    pub fn get_lowest_available_checkpoint(&self) -> Result<u64, crate::RpcError> {
251        // This is the lowest lowest_available_checkpoint from the checkpoint store
252        let lowest_available_checkpoint = self.inner().get_lowest_available_checkpoint()?;
253        // This is the lowest lowest_available_checkpoint from the perpetual store
254        let lowest_available_checkpoint_objects =
255            self.inner().get_lowest_available_checkpoint_objects()?;
256
257        // Return the higher of the two for our lower watermark
258        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}