sui_indexer/apis/
read_api.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use async_trait::async_trait;
5use jsonrpsee::RpcModule;
6use jsonrpsee::core::RpcResult;
7use sui_json_rpc::error::SuiRpcInputError;
8use sui_types::error::SuiObjectResponseError;
9use sui_types::object::ObjectRead;
10
11use crate::errors::IndexerError;
12use crate::indexer_reader::IndexerReader;
13use sui_json_rpc::SuiRpcModule;
14use sui_json_rpc_api::{QUERY_MAX_RESULT_LIMIT, ReadApiServer};
15use sui_json_rpc_types::ZkLoginIntentScope;
16use sui_json_rpc_types::ZkLoginVerifyResult;
17use sui_json_rpc_types::{
18    Checkpoint, CheckpointId, CheckpointPage, ProtocolConfigResponse, SuiEvent,
19    SuiGetPastObjectRequest, SuiObjectDataOptions, SuiObjectResponse, SuiPastObjectResponse,
20    SuiTransactionBlockResponse, SuiTransactionBlockResponseOptions,
21};
22use sui_open_rpc::Module;
23use sui_protocol_config::{ProtocolConfig, ProtocolVersion};
24use sui_types::base_types::SuiAddress;
25use sui_types::base_types::{ObjectID, SequenceNumber};
26use sui_types::digests::{ChainIdentifier, TransactionDigest};
27use sui_types::sui_serde::BigInt;
28
29#[derive(Clone)]
30pub struct ReadApi {
31    inner: IndexerReader,
32}
33
34impl ReadApi {
35    pub fn new(inner: IndexerReader) -> Self {
36        Self { inner }
37    }
38
39    async fn get_checkpoint(&self, id: CheckpointId) -> Result<Checkpoint, IndexerError> {
40        match self.inner.get_checkpoint(id).await {
41            Ok(Some(epoch_info)) => Ok(epoch_info),
42            Ok(None) => Err(IndexerError::InvalidArgumentError(format!(
43                "Checkpoint {id:?} not found"
44            ))),
45            Err(e) => Err(e),
46        }
47    }
48
49    async fn get_latest_checkpoint(&self) -> Result<Checkpoint, IndexerError> {
50        self.inner.get_latest_checkpoint().await
51    }
52
53    async fn get_chain_identifier(&self) -> RpcResult<ChainIdentifier> {
54        let genesis_checkpoint = self.get_checkpoint(CheckpointId::SequenceNumber(0)).await?;
55        Ok(ChainIdentifier::from(genesis_checkpoint.digest))
56    }
57}
58
59#[async_trait]
60impl ReadApiServer for ReadApi {
61    async fn get_object(
62        &self,
63        object_id: ObjectID,
64        options: Option<SuiObjectDataOptions>,
65    ) -> RpcResult<SuiObjectResponse> {
66        let object_read = self.inner.get_object_read(object_id).await?;
67        object_read_to_object_response(object_read, options.unwrap_or_default()).await
68    }
69
70    // For ease of implementation we just forward to the single object query, although in the
71    // future we may want to improve the performance by having a more naitive multi_get
72    // functionality
73    async fn multi_get_objects(
74        &self,
75        object_ids: Vec<ObjectID>,
76        options: Option<SuiObjectDataOptions>,
77    ) -> RpcResult<Vec<SuiObjectResponse>> {
78        if object_ids.len() > *QUERY_MAX_RESULT_LIMIT {
79            return Err(
80                SuiRpcInputError::SizeLimitExceeded(QUERY_MAX_RESULT_LIMIT.to_string()).into(),
81            );
82        }
83        let stored_objects = self.inner.multi_get_objects(object_ids).await?;
84        let options = options.unwrap_or_default();
85
86        let futures = stored_objects.into_iter().map(|stored_object| async {
87            let object_read = stored_object
88                .try_into_object_read(self.inner.package_resolver())
89                .await?;
90            object_read_to_object_response(object_read, options.clone()).await
91        });
92
93        let mut objects = futures::future::try_join_all(futures).await?;
94        // Resort the objects by the order of the object id.
95        objects.sort_by_key(|obj| obj.data.as_ref().map(|data| data.object_id));
96
97        Ok(objects)
98    }
99
100    async fn get_total_transaction_blocks(&self) -> RpcResult<BigInt<u64>> {
101        let checkpoint = self.get_latest_checkpoint().await?;
102        Ok(BigInt::from(checkpoint.network_total_transactions))
103    }
104
105    async fn get_transaction_block(
106        &self,
107        digest: TransactionDigest,
108        options: Option<SuiTransactionBlockResponseOptions>,
109    ) -> RpcResult<SuiTransactionBlockResponse> {
110        let mut txn = self
111            .multi_get_transaction_blocks(vec![digest], options)
112            .await?;
113
114        let txn = txn.pop().ok_or_else(|| {
115            IndexerError::InvalidArgumentError(format!("Transaction {digest} not found"))
116        })?;
117
118        Ok(txn)
119    }
120
121    async fn multi_get_transaction_blocks(
122        &self,
123        digests: Vec<TransactionDigest>,
124        options: Option<SuiTransactionBlockResponseOptions>,
125    ) -> RpcResult<Vec<SuiTransactionBlockResponse>> {
126        let num_digests = digests.len();
127        if num_digests > *QUERY_MAX_RESULT_LIMIT {
128            Err(SuiRpcInputError::SizeLimitExceeded(
129                QUERY_MAX_RESULT_LIMIT.to_string(),
130            ))?
131        }
132
133        let options = options.unwrap_or_default();
134        let txns = self
135            .inner
136            .multi_get_transaction_block_response_in_blocking_task(digests, options)
137            .await?;
138
139        Ok(txns)
140    }
141
142    async fn try_get_past_object(
143        &self,
144        _object_id: ObjectID,
145        _version: SequenceNumber,
146        _options: Option<SuiObjectDataOptions>,
147    ) -> RpcResult<SuiPastObjectResponse> {
148        Err(jsonrpsee::types::error::ErrorCode::MethodNotFound.into())
149    }
150
151    async fn try_get_object_before_version(
152        &self,
153        _: ObjectID,
154        _: SequenceNumber,
155    ) -> RpcResult<SuiPastObjectResponse> {
156        Err(jsonrpsee::types::error::ErrorCode::MethodNotFound.into())
157    }
158
159    async fn try_multi_get_past_objects(
160        &self,
161        _past_objects: Vec<SuiGetPastObjectRequest>,
162        _options: Option<SuiObjectDataOptions>,
163    ) -> RpcResult<Vec<SuiPastObjectResponse>> {
164        Err(jsonrpsee::types::error::ErrorCode::MethodNotFound.into())
165    }
166
167    async fn get_latest_checkpoint_sequence_number(&self) -> RpcResult<BigInt<u64>> {
168        let checkpoint = self.get_latest_checkpoint().await?;
169        Ok(BigInt::from(checkpoint.sequence_number))
170    }
171
172    async fn get_checkpoint(&self, id: CheckpointId) -> RpcResult<Checkpoint> {
173        self.get_checkpoint(id).await.map_err(Into::into)
174    }
175
176    async fn get_checkpoints(
177        &self,
178        cursor: Option<BigInt<u64>>,
179        limit: Option<usize>,
180        descending_order: bool,
181    ) -> RpcResult<CheckpointPage> {
182        let cursor = cursor.map(BigInt::into_inner);
183        let limit = sui_json_rpc_api::validate_limit(
184            limit,
185            sui_json_rpc_api::QUERY_MAX_RESULT_LIMIT_CHECKPOINTS,
186        )
187        .map_err(SuiRpcInputError::from)?;
188
189        let mut checkpoints = self
190            .inner
191            .get_checkpoints(cursor, limit + 1, descending_order)
192            .await?;
193
194        let has_next_page = checkpoints.len() > limit;
195        checkpoints.truncate(limit);
196
197        let next_cursor = checkpoints.last().map(|d| d.sequence_number.into());
198
199        Ok(CheckpointPage {
200            data: checkpoints,
201            next_cursor,
202            has_next_page,
203        })
204    }
205
206    async fn get_events(&self, transaction_digest: TransactionDigest) -> RpcResult<Vec<SuiEvent>> {
207        self.inner
208            .get_transaction_events(transaction_digest)
209            .await
210            .map_err(Into::into)
211    }
212
213    async fn get_protocol_config(
214        &self,
215        version: Option<BigInt<u64>>,
216    ) -> RpcResult<ProtocolConfigResponse> {
217        let chain = self.get_chain_identifier().await?.chain();
218        let version = if let Some(version) = version {
219            (*version).into()
220        } else {
221            let latest_epoch = self.inner.get_latest_epoch_info_from_db().await?;
222            (latest_epoch.protocol_version as u64).into()
223        };
224
225        ProtocolConfig::get_for_version_if_supported(version, chain)
226            .ok_or(SuiRpcInputError::ProtocolVersionUnsupported(
227                ProtocolVersion::MIN.as_u64(),
228                ProtocolVersion::MAX.as_u64(),
229            ))
230            .map_err(Into::into)
231            .map(ProtocolConfigResponse::from)
232    }
233
234    async fn get_chain_identifier(&self) -> RpcResult<String> {
235        self.get_chain_identifier().await.map(|id| id.to_string())
236    }
237
238    async fn verify_zklogin_signature(
239        &self,
240        _bytes: String,
241        _signature: String,
242        _intent_scope: ZkLoginIntentScope,
243        _author: SuiAddress,
244    ) -> RpcResult<ZkLoginVerifyResult> {
245        Err(jsonrpsee::types::error::ErrorCode::MethodNotFound.into())
246    }
247}
248
249impl SuiRpcModule for ReadApi {
250    fn rpc(self) -> RpcModule<Self> {
251        self.into_rpc()
252    }
253
254    fn rpc_doc_module() -> Module {
255        sui_json_rpc_api::ReadApiOpenRpc::module_doc()
256    }
257}
258
259async fn object_read_to_object_response(
260    object_read: ObjectRead,
261    options: SuiObjectDataOptions,
262) -> RpcResult<SuiObjectResponse> {
263    match object_read {
264        ObjectRead::NotExists(id) => Ok(SuiObjectResponse::new_with_error(
265            SuiObjectResponseError::NotExists { object_id: id },
266        )),
267        ObjectRead::Exists(object_ref, o, layout) => {
268            if options.show_display {
269                return Err(IndexerError::NotSupportedError(
270                    "Display fields are not supported".to_owned(),
271                )
272                .into());
273            }
274            Ok(SuiObjectResponse::new_with_data(
275                (object_ref, o, layout, options, None)
276                    .try_into()
277                    .map_err(IndexerError::from)?,
278            ))
279        }
280        ObjectRead::Deleted((object_id, version, digest)) => Ok(SuiObjectResponse::new_with_error(
281            SuiObjectResponseError::Deleted {
282                object_id,
283                version,
284                digest,
285            },
286        )),
287    }
288}