sui_rpc_api/
reader.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::HashMap;
5use std::sync::Arc;
6
7use sui_sdk_types::{Address, Object, Version};
8use sui_sdk_types::{CheckpointSequenceNumber, EpochId, SignedTransaction, ValidatorCommittee};
9use sui_types::balance_change::BalanceChange;
10use sui_types::base_types::{ObjectID, ObjectType};
11use sui_types::storage::ObjectKey;
12use sui_types::storage::RpcStateReader;
13use sui_types::storage::error::{Error as StorageError, Result};
14use sui_types::storage::{ObjectStore, TransactionInfo};
15use tap::Pipe;
16
17use crate::Direction;
18
19#[derive(Clone)]
20pub struct StateReader {
21    inner: Arc<dyn RpcStateReader>,
22}
23
24impl StateReader {
25    pub fn new(inner: Arc<dyn RpcStateReader>) -> Self {
26        Self { inner }
27    }
28
29    pub fn inner(&self) -> &Arc<dyn RpcStateReader> {
30        &self.inner
31    }
32
33    #[allow(unused)]
34    #[tracing::instrument(skip(self))]
35    pub fn get_object(&self, object_id: Address) -> crate::Result<Option<Object>> {
36        self.inner
37            .get_object(&object_id.into())
38            .map(TryInto::try_into)
39            .transpose()
40            .map_err(Into::into)
41    }
42
43    #[allow(unused)]
44    #[tracing::instrument(skip(self))]
45    pub fn get_object_with_version(
46        &self,
47        object_id: Address,
48        version: Version,
49    ) -> crate::Result<Option<Object>> {
50        self.inner
51            .get_object_by_key(&object_id.into(), version.into())
52            .map(TryInto::try_into)
53            .transpose()
54            .map_err(Into::into)
55    }
56
57    #[tracing::instrument(skip(self))]
58    pub fn get_committee(&self, epoch: EpochId) -> Option<ValidatorCommittee> {
59        self.inner
60            .get_committee(epoch)
61            .map(|committee| (*committee).clone().into())
62    }
63
64    #[tracing::instrument(skip(self))]
65    pub fn get_system_state(&self) -> Result<sui_types::sui_system_state::SuiSystemState> {
66        sui_types::sui_system_state::get_sui_system_state(self.inner())
67            .map_err(StorageError::custom)
68            .map_err(StorageError::custom)
69    }
70
71    #[tracing::instrument(skip(self))]
72    pub fn get_system_state_summary(
73        &self,
74    ) -> Result<sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary> {
75        use sui_types::sui_system_state::SuiSystemStateTrait;
76
77        let system_state = self.get_system_state()?;
78        let summary = system_state.into_sui_system_state_summary();
79
80        Ok(summary)
81    }
82
83    pub fn get_authenticator_state(
84        &self,
85    ) -> Result<Option<sui_types::authenticator_state::AuthenticatorStateInner>> {
86        sui_types::authenticator_state::get_authenticator_state(self.inner())
87            .map_err(StorageError::custom)
88    }
89
90    #[tracing::instrument(skip(self))]
91    pub fn get_transaction(
92        &self,
93        digest: sui_sdk_types::Digest,
94    ) -> crate::Result<(
95        sui_sdk_types::SignedTransaction,
96        sui_types::effects::TransactionEffects,
97        Option<sui_types::effects::TransactionEvents>,
98    )> {
99        use sui_types::effects::TransactionEffectsAPI;
100
101        let transaction_digest = digest.into();
102
103        let transaction = (*self
104            .inner()
105            .get_transaction(&transaction_digest)
106            .ok_or(TransactionNotFoundError(digest))?)
107        .clone()
108        .into_inner();
109        let effects = self
110            .inner()
111            .get_transaction_effects(&transaction_digest)
112            .ok_or(TransactionNotFoundError(digest))?;
113        let events = if effects.events_digest().is_some() {
114            self.inner()
115                .get_events(effects.transaction_digest())
116                .ok_or(TransactionNotFoundError(digest))?
117                .pipe(Some)
118        } else {
119            None
120        };
121
122        Ok((transaction.try_into()?, effects, events))
123    }
124
125    #[tracing::instrument(skip(self))]
126    pub fn get_transaction_info(
127        &self,
128        digest: &sui_types::digests::TransactionDigest,
129    ) -> Option<TransactionInfo> {
130        self.inner()
131            .indexes()?
132            .get_transaction_info(digest)
133            .ok()
134            .flatten()
135    }
136
137    #[tracing::instrument(skip(self))]
138    pub fn get_transaction_read(
139        &self,
140        digest: sui_sdk_types::Digest,
141    ) -> crate::Result<TransactionRead> {
142        let (
143            SignedTransaction {
144                transaction,
145                signatures,
146            },
147            effects,
148            events,
149        ) = self.get_transaction(digest)?;
150
151        let (checkpoint, balance_changes, object_types) =
152            if let Some(info) = self.get_transaction_info(&(digest.into())) {
153                (
154                    Some(info.checkpoint),
155                    Some(info.balance_changes),
156                    Some(info.object_types),
157                )
158            } else {
159                let checkpoint = self.inner().get_transaction_checkpoint(&(digest.into()));
160                (checkpoint, None, None)
161            };
162        let timestamp_ms = if let Some(checkpoint) = checkpoint {
163            self.inner()
164                .get_checkpoint_by_sequence_number(checkpoint)
165                .map(|checkpoint| checkpoint.timestamp_ms)
166        } else {
167            None
168        };
169
170        let unchanged_loaded_runtime_objects = self
171            .inner()
172            .get_unchanged_loaded_runtime_objects(&(digest.into()));
173
174        Ok(TransactionRead {
175            digest: transaction.digest(),
176            transaction,
177            signatures,
178            effects,
179            events,
180            checkpoint,
181            timestamp_ms,
182            balance_changes,
183            object_types,
184            unchanged_loaded_runtime_objects,
185        })
186    }
187
188    #[allow(unused)]
189    pub fn checkpoint_iter(
190        &self,
191        direction: Direction,
192        start: CheckpointSequenceNumber,
193    ) -> CheckpointIter {
194        CheckpointIter::new(self.clone(), direction, start)
195    }
196
197    #[allow(unused)]
198    pub fn transaction_iter(
199        &self,
200        direction: Direction,
201        cursor: (CheckpointSequenceNumber, Option<usize>),
202    ) -> CheckpointTransactionsIter {
203        CheckpointTransactionsIter::new(self.clone(), direction, cursor)
204    }
205
206    pub fn lookup_address_balance(
207        &self,
208        owner: sui_types::base_types::SuiAddress,
209        coin_type: move_core_types::language_storage::StructTag,
210    ) -> Option<u64> {
211        use sui_types::MoveTypeTagTraitGeneric;
212        use sui_types::SUI_ACCUMULATOR_ROOT_OBJECT_ID;
213        use sui_types::accumulator_root::AccumulatorKey;
214        use sui_types::dynamic_field::DynamicFieldKey;
215
216        let balance_type = sui_types::balance::Balance::type_tag(coin_type.into());
217
218        let key = AccumulatorKey { owner };
219        let key_type_tag = AccumulatorKey::get_type_tag(&[balance_type]);
220
221        DynamicFieldKey(SUI_ACCUMULATOR_ROOT_OBJECT_ID, key, key_type_tag)
222            .into_unbounded_id()
223            .unwrap()
224            .load_object(self.inner())
225            .and_then(|o| o.load_value::<u128>().ok())
226            .map(|balance| balance as u64)
227    }
228}
229
230#[derive(Debug)]
231pub struct TransactionRead {
232    pub digest: sui_sdk_types::Digest,
233    pub transaction: sui_sdk_types::Transaction,
234    pub signatures: Vec<sui_sdk_types::UserSignature>,
235    pub effects: sui_types::effects::TransactionEffects,
236    pub events: Option<sui_types::effects::TransactionEvents>,
237    pub checkpoint: Option<u64>,
238    pub timestamp_ms: Option<u64>,
239    pub balance_changes: Option<Vec<BalanceChange>>,
240    pub object_types: Option<HashMap<ObjectID, ObjectType>>,
241    pub unchanged_loaded_runtime_objects: Option<Vec<ObjectKey>>,
242}
243
244pub struct CheckpointTransactionsIter {
245    reader: StateReader,
246    direction: Direction,
247
248    next_cursor: Option<(CheckpointSequenceNumber, Option<usize>)>,
249    checkpoint: Option<(
250        sui_types::messages_checkpoint::CheckpointSummary,
251        sui_types::messages_checkpoint::CheckpointContents,
252    )>,
253}
254
255impl CheckpointTransactionsIter {
256    #[allow(unused)]
257    pub fn new(
258        reader: StateReader,
259        direction: Direction,
260        start: (CheckpointSequenceNumber, Option<usize>),
261    ) -> Self {
262        Self {
263            reader,
264            direction,
265            next_cursor: Some(start),
266            checkpoint: None,
267        }
268    }
269}
270
271impl Iterator for CheckpointTransactionsIter {
272    type Item = Result<(CursorInfo, sui_types::digests::TransactionDigest)>;
273
274    fn next(&mut self) -> Option<Self::Item> {
275        loop {
276            let (current_checkpoint, transaction_index) = self.next_cursor?;
277
278            let (checkpoint, contents) = if let Some(checkpoint) = &self.checkpoint {
279                if checkpoint.0.sequence_number != current_checkpoint {
280                    self.checkpoint = None;
281                    continue;
282                } else {
283                    checkpoint
284                }
285            } else {
286                let checkpoint = self
287                    .reader
288                    .inner()
289                    .get_checkpoint_by_sequence_number(current_checkpoint)?;
290                let contents = self
291                    .reader
292                    .inner()
293                    .get_checkpoint_contents_by_sequence_number(checkpoint.sequence_number)?;
294
295                self.checkpoint = Some((checkpoint.into_inner().into_data(), contents));
296                self.checkpoint.as_ref().unwrap()
297            };
298
299            let index = transaction_index
300                .map(|idx| idx.clamp(0, contents.size().saturating_sub(1)))
301                .unwrap_or_else(|| match self.direction {
302                    Direction::Ascending => 0,
303                    Direction::Descending => contents.size().saturating_sub(1),
304                });
305
306            self.next_cursor = {
307                let next_index = match self.direction {
308                    Direction::Ascending => {
309                        let next_index = index + 1;
310                        if next_index >= contents.size() {
311                            None
312                        } else {
313                            Some(next_index)
314                        }
315                    }
316                    Direction::Descending => index.checked_sub(1),
317                };
318
319                let next_checkpoint = if next_index.is_some() {
320                    Some(current_checkpoint)
321                } else {
322                    match self.direction {
323                        Direction::Ascending => current_checkpoint.checked_add(1),
324                        Direction::Descending => current_checkpoint.checked_sub(1),
325                    }
326                };
327
328                next_checkpoint.map(|checkpoint| (checkpoint, next_index))
329            };
330
331            if contents.size() == 0 {
332                continue;
333            }
334
335            let digest = contents.inner()[index].transaction;
336
337            let cursor_info = CursorInfo {
338                checkpoint: checkpoint.sequence_number,
339                timestamp_ms: checkpoint.timestamp_ms,
340                index: index as u64,
341                next_cursor: self.next_cursor,
342            };
343
344            return Some(Ok((cursor_info, digest)));
345        }
346    }
347}
348
349#[allow(unused)]
350pub struct CursorInfo {
351    pub checkpoint: CheckpointSequenceNumber,
352    pub timestamp_ms: u64,
353    #[allow(unused)]
354    pub index: u64,
355
356    // None if there are no more transactions in the store
357    pub next_cursor: Option<(CheckpointSequenceNumber, Option<usize>)>,
358}
359
360pub struct CheckpointIter {
361    reader: StateReader,
362    direction: Direction,
363
364    next_cursor: Option<CheckpointSequenceNumber>,
365}
366
367impl CheckpointIter {
368    #[allow(unused)]
369    pub fn new(reader: StateReader, direction: Direction, start: CheckpointSequenceNumber) -> Self {
370        Self {
371            reader,
372            direction,
373            next_cursor: Some(start),
374        }
375    }
376}
377
378impl Iterator for CheckpointIter {
379    type Item = Result<(
380        sui_types::messages_checkpoint::CertifiedCheckpointSummary,
381        sui_types::messages_checkpoint::CheckpointContents,
382    )>;
383
384    fn next(&mut self) -> Option<Self::Item> {
385        let current_checkpoint = self.next_cursor?;
386
387        let checkpoint = self
388            .reader
389            .inner()
390            .get_checkpoint_by_sequence_number(current_checkpoint)?
391            .into_inner();
392        let contents = self
393            .reader
394            .inner()
395            .get_checkpoint_contents_by_sequence_number(checkpoint.sequence_number)?;
396
397        self.next_cursor = match self.direction {
398            Direction::Ascending => current_checkpoint.checked_add(1),
399            Direction::Descending => current_checkpoint.checked_sub(1),
400        };
401
402        Some(Ok((checkpoint, contents)))
403    }
404}
405
406#[derive(Debug)]
407pub struct TransactionNotFoundError(pub sui_sdk_types::Digest);
408
409impl std::fmt::Display for TransactionNotFoundError {
410    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
411        write!(f, "Transaction {} not found", self.0)
412    }
413}
414
415impl std::error::Error for TransactionNotFoundError {}
416
417impl From<TransactionNotFoundError> for crate::RpcError {
418    fn from(value: TransactionNotFoundError) -> Self {
419        Self::new(tonic::Code::NotFound, value.to_string())
420    }
421}