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