sui_rpc_api/
reader.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::Arc;
5
6use sui_sdk_types::{EpochId, ValidatorCommittee};
7use sui_types::storage::ObjectKey;
8use sui_types::storage::RpcStateReader;
9use sui_types::storage::error::{Error as StorageError, Result};
10use tap::Pipe;
11
12#[derive(Clone)]
13pub struct StateReader {
14    inner: Arc<dyn RpcStateReader>,
15}
16
17impl StateReader {
18    pub fn new(inner: Arc<dyn RpcStateReader>) -> Self {
19        Self { inner }
20    }
21
22    pub fn inner(&self) -> &Arc<dyn RpcStateReader> {
23        &self.inner
24    }
25
26    #[tracing::instrument(skip(self))]
27    pub fn get_committee(&self, epoch: EpochId) -> Option<ValidatorCommittee> {
28        self.inner
29            .get_committee(epoch)
30            .map(|committee| (*committee).clone().into())
31    }
32
33    #[tracing::instrument(skip(self))]
34    pub fn get_system_state(&self) -> Result<sui_types::sui_system_state::SuiSystemState> {
35        sui_types::sui_system_state::get_sui_system_state(self.inner())
36            .map_err(StorageError::custom)
37            .map_err(StorageError::custom)
38    }
39
40    #[tracing::instrument(skip(self))]
41    pub fn get_display_object_v2_by_type(
42        &self,
43        object_type: &move_core_types::language_storage::StructTag,
44    ) -> Option<sui_types::display_registry::Display> {
45        let object_id =
46            sui_types::display_registry::display_object_id(object_type.clone().into()).ok()?;
47
48        let object = self.inner.get_object(&object_id)?;
49
50        let move_object = object.data.try_as_move()?;
51
52        bcs::from_bytes(move_object.contents()).ok()
53    }
54
55    #[tracing::instrument(skip(self))]
56    pub fn get_system_state_summary(
57        &self,
58    ) -> Result<sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary> {
59        use sui_types::sui_system_state::SuiSystemStateTrait;
60
61        let system_state = self.get_system_state()?;
62        let summary = system_state.into_sui_system_state_summary();
63
64        Ok(summary)
65    }
66
67    pub fn get_authenticator_state(
68        &self,
69    ) -> Result<Option<sui_types::authenticator_state::AuthenticatorStateInner>> {
70        sui_types::authenticator_state::get_authenticator_state(self.inner())
71            .map_err(StorageError::custom)
72    }
73
74    #[tracing::instrument(skip(self))]
75    pub fn get_transaction(
76        &self,
77        digest: sui_sdk_types::Digest,
78    ) -> crate::Result<(
79        sui_types::transaction::TransactionData,
80        Vec<sui_types::signature::GenericSignature>,
81        sui_types::effects::TransactionEffects,
82        Option<sui_types::effects::TransactionEvents>,
83    )> {
84        use sui_types::effects::TransactionEffectsAPI;
85
86        let transaction_digest = digest.into();
87
88        let transaction = (*self
89            .inner()
90            .get_transaction(&transaction_digest)
91            .ok_or(TransactionNotFoundError(digest))?)
92        .clone()
93        .into_inner();
94        let effects = self
95            .inner()
96            .get_transaction_effects(&transaction_digest)
97            .ok_or(TransactionNotFoundError(digest))?;
98        let events = if effects.events_digest().is_some() {
99            self.inner()
100                .get_events(effects.transaction_digest())
101                .ok_or(TransactionNotFoundError(digest))?
102                .pipe(Some)
103        } else {
104            None
105        };
106
107        let transaction = transaction.into_data().into_inner();
108        let signatures = transaction.tx_signatures;
109        let transaction = transaction.intent_message.value;
110
111        Ok((transaction, signatures, effects, events))
112    }
113
114    #[tracing::instrument(skip(self))]
115    pub fn get_transaction_read(
116        &self,
117        digest: sui_sdk_types::Digest,
118    ) -> crate::Result<TransactionRead> {
119        let (transaction, signatures, effects, events) = self.get_transaction(digest)?;
120
121        let checkpoint = self.inner().get_transaction_checkpoint(&(digest.into()));
122
123        let timestamp_ms = if let Some(checkpoint) = checkpoint {
124            self.inner()
125                .get_checkpoint_by_sequence_number(checkpoint)
126                .map(|checkpoint| checkpoint.timestamp_ms)
127        } else {
128            None
129        };
130
131        let unchanged_loaded_runtime_objects = self
132            .inner()
133            .get_unchanged_loaded_runtime_objects(&(digest.into()));
134
135        Ok(TransactionRead {
136            digest,
137            transaction,
138            signatures,
139            effects,
140            events,
141            checkpoint,
142            timestamp_ms,
143            unchanged_loaded_runtime_objects,
144        })
145    }
146
147    pub fn lookup_address_balance(
148        &self,
149        owner: sui_types::base_types::SuiAddress,
150        coin_type: move_core_types::language_storage::StructTag,
151    ) -> Option<u64> {
152        use sui_types::MoveTypeTagTraitGeneric;
153        use sui_types::SUI_ACCUMULATOR_ROOT_OBJECT_ID;
154        use sui_types::accumulator_root::AccumulatorKey;
155        use sui_types::dynamic_field::DynamicFieldKey;
156
157        let balance_type = sui_types::balance::Balance::type_tag(coin_type.into());
158
159        let key = AccumulatorKey { owner };
160        let key_type_tag = AccumulatorKey::get_type_tag(&[balance_type]);
161
162        DynamicFieldKey(SUI_ACCUMULATOR_ROOT_OBJECT_ID, key, key_type_tag)
163            .into_unbounded_id()
164            .unwrap()
165            .load_object(self.inner())
166            .and_then(|o| o.load_value::<u128>().ok())
167            .map(|balance| balance as u64)
168    }
169
170    // Return the lowest available checkpoint watermark for which the RPC service can return proper
171    // responses for.
172    pub fn get_lowest_available_checkpoint(&self) -> Result<u64, crate::RpcError> {
173        // This is the lowest lowest_available_checkpoint from the checkpoint store
174        let lowest_available_checkpoint = self.inner().get_lowest_available_checkpoint()?;
175        // This is the lowest lowest_available_checkpoint from the perpetual store
176        let lowest_available_checkpoint_objects =
177            self.inner().get_lowest_available_checkpoint_objects()?;
178
179        // Return the higher of the two for our lower watermark
180        Ok(lowest_available_checkpoint.max(lowest_available_checkpoint_objects))
181    }
182}
183
184#[derive(Debug)]
185pub struct TransactionRead {
186    pub digest: sui_sdk_types::Digest,
187    pub transaction: sui_types::transaction::TransactionData,
188    pub signatures: Vec<sui_types::signature::GenericSignature>,
189    pub effects: sui_types::effects::TransactionEffects,
190    pub events: Option<sui_types::effects::TransactionEvents>,
191    #[allow(unused)]
192    pub checkpoint: Option<u64>,
193    pub timestamp_ms: Option<u64>,
194    pub unchanged_loaded_runtime_objects: Option<Vec<ObjectKey>>,
195}
196
197#[derive(Debug)]
198pub struct TransactionNotFoundError(pub sui_sdk_types::Digest);
199
200impl std::fmt::Display for TransactionNotFoundError {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        write!(f, "Transaction {} not found", self.0)
203    }
204}
205
206impl std::error::Error for TransactionNotFoundError {}
207
208impl From<TransactionNotFoundError> for crate::RpcError {
209    fn from(value: TransactionNotFoundError) -> Self {
210        Self::new(tonic::Code::NotFound, value.to_string())
211    }
212}
213
214pub struct DisplayStore<'s> {
215    state: &'s StateReader,
216}
217
218impl<'s> DisplayStore<'s> {
219    pub fn new(state: &'s StateReader) -> Self {
220        Self { state }
221    }
222}
223
224#[async_trait::async_trait]
225impl sui_display::v2::Store for DisplayStore<'_> {
226    async fn object(
227        &self,
228        id: move_core_types::account_address::AccountAddress,
229    ) -> anyhow::Result<Option<sui_display::v2::OwnedSlice>> {
230        let Some(object) = self.state.inner().get_object(&id.into()) else {
231            return Ok(None);
232        };
233
234        let Some(move_object) = object.data.try_as_move() else {
235            return Ok(None);
236        };
237
238        let object_type = move_object.type_().clone().into();
239
240        let Some(layout) = self.state.inner().get_struct_layout(&object_type)? else {
241            return Ok(None);
242        };
243
244        Ok(Some(sui_display::v2::OwnedSlice {
245            bytes: move_object.contents().to_vec(),
246            layout,
247        }))
248    }
249}