sui_json_rpc/
read_api.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::{BTreeMap, HashMap};
5use std::sync::Arc;
6use std::time::Duration;
7
8use anyhow::anyhow;
9use async_trait::async_trait;
10use backoff::ExponentialBackoff;
11use backoff::future::retry;
12use fastcrypto::encoding::Base64;
13use fastcrypto_zkp::bn254::zk_login_api::ZkLoginEnv;
14use futures::future::join_all;
15use im::hashmap::HashMap as ImHashMap;
16use indexmap::map::IndexMap;
17use itertools::Itertools;
18use jsonrpsee::RpcModule;
19use jsonrpsee::core::RpcResult;
20use move_bytecode_utils::module_cache::GetModule;
21use move_core_types::account_address::AccountAddress;
22use move_core_types::annotated_value::{MoveStructLayout, MoveTypeLayout};
23use move_core_types::language_storage::StructTag;
24use mysten_common::ZipDebugEqIteratorExt;
25use once_cell::sync::Lazy;
26use serde_json::Value as Json;
27use shared_crypto::intent::{IntentMessage, PersonalMessage};
28use sui_display::v1::Format;
29use sui_json_rpc_types::ZkLoginIntentScope;
30use sui_types::base_types::SuiAddress;
31use sui_types::signature::{GenericSignature, VerifyParams};
32use sui_types::signature_verification::VerifiedDigestCache;
33use sui_types::storage::ObjectKey;
34use tap::TapFallible;
35use tracing::{debug, error, info, instrument, trace, warn};
36
37use mysten_metrics::add_server_timing;
38use sui_core::authority::AuthorityState;
39use sui_json_rpc_api::{
40    JsonRpcMetrics, QUERY_MAX_RESULT_LIMIT, QUERY_MAX_RESULT_LIMIT_CHECKPOINTS, ReadApiOpenRpc,
41    ReadApiServer, validate_limit,
42};
43use sui_json_rpc_types::{
44    BalanceChange, Checkpoint, CheckpointId, CheckpointPage, DisplayFieldsResponse, EventFilter,
45    ObjectChange, ProtocolConfigResponse, SuiEvent, SuiGetPastObjectRequest, SuiObjectDataOptions,
46    SuiObjectResponse, SuiPastObjectResponse, SuiTransactionBlock, SuiTransactionBlockEvents,
47    SuiTransactionBlockResponse, SuiTransactionBlockResponseOptions,
48};
49use sui_open_rpc::Module;
50use sui_protocol_config::{ProtocolConfig, ProtocolVersion};
51use sui_storage::key_value_store::TransactionKeyValueStore;
52use sui_types::base_types::{ObjectID, SequenceNumber, TransactionDigest};
53use sui_types::display::DisplayVersionUpdatedEvent;
54use sui_types::display_registry;
55use sui_types::effects::{TransactionEffects, TransactionEffectsAPI, TransactionEvents};
56use sui_types::error::{SuiError, SuiObjectResponseError};
57use sui_types::messages_checkpoint::{CheckpointSequenceNumber, CheckpointTimestamp};
58use sui_types::object::{Object, ObjectRead, PastObjectRead};
59use sui_types::sui_serde::BigInt;
60use sui_types::transaction::TransactionDataAPI;
61use sui_types::transaction::{Transaction, TransactionData};
62
63use crate::authority_state::{StateRead, StateReadError, StateReadResult};
64use crate::error::{Error, RpcInterimResult, SuiRpcInputError};
65use crate::{ObjectProvider, with_tracing};
66use crate::{
67    ObjectProviderCache, SuiRpcModule, get_balance_changes_from_effect, get_object_changes,
68};
69use fastcrypto::encoding::Encoding;
70use fastcrypto::traits::ToFromBytes;
71use shared_crypto::intent::Intent;
72use sui_json_rpc_types::ZkLoginVerifyResult;
73use sui_types::authenticator_state::{ActiveJwk, get_authenticator_state};
74
75/// Default max depth used while converting rendered Display values to JSON.
76const DEFAULT_MAX_DISPLAY_MOVE_VALUE_DEPTH: usize = 32;
77
78/// Default budget for Display output size.
79const DEFAULT_MAX_DISPLAY_OUTPUT_SIZE: usize = 1024 * 1024;
80
81/// A field access in a Display string cannot exceed this level of nesting.
82static MAX_DISPLAY_FIELD_DEPTH: Lazy<usize> = Lazy::new(|| {
83    let max_opt = std::env::var("MAX_DISPLAY_FIELD_DEPTH")
84        .ok()
85        .and_then(|s| s.parse().ok());
86
87    if let Some(max) = max_opt {
88        info!("Using custom value for 'MAX_DISPLAY_FIELD_DEPTH': {max}");
89        max
90    } else {
91        sui_display::v2::Limits::default().max_depth
92    }
93});
94
95/// Parser node budget for Display v2.
96static MAX_DISPLAY_FORMAT_NODES: Lazy<usize> = Lazy::new(|| {
97    let max_opt = std::env::var("MAX_DISPLAY_FORMAT_NODES")
98        .ok()
99        .and_then(|s| s.parse().ok());
100
101    if let Some(max) = max_opt {
102        info!("Using custom value for 'MAX_DISPLAY_FORMAT_NODES': {max}");
103        max
104    } else {
105        sui_display::v2::Limits::default().max_nodes
106    }
107});
108
109/// Max object loads budget for Display v2.
110static MAX_DISPLAY_OBJECT_LOADS: Lazy<usize> = Lazy::new(|| {
111    let max_opt = std::env::var("MAX_DISPLAY_OBJECT_LOADS")
112        .ok()
113        .and_then(|s| s.parse().ok());
114
115    if let Some(max) = max_opt {
116        info!("Using custom value for 'MAX_DISPLAY_OBJECT_LOADS': {max}");
117        max
118    } else {
119        sui_display::v2::Limits::default().max_loads
120    }
121});
122
123/// Maximum depth used while converting rendered Display values to JSON.
124static MAX_DISPLAY_MOVE_VALUE_DEPTH: Lazy<usize> = Lazy::new(|| {
125    let max_opt = std::env::var("MAX_MOVE_VALUE_DEPTH")
126        .ok()
127        .and_then(|s| s.parse().ok());
128
129    if let Some(max) = max_opt {
130        info!("Using custom value for 'MAX_MOVE_VALUE_DEPTH': {max}");
131        max
132    } else {
133        DEFAULT_MAX_DISPLAY_MOVE_VALUE_DEPTH
134    }
135});
136
137/// Overall display output cannot exceed this size.
138static MAX_DISPLAY_OUTPUT_SIZE: Lazy<usize> = Lazy::new(|| {
139    let max_opt = std::env::var("MAX_DISPLAY_OUTPUT_SIZE")
140        .ok()
141        .and_then(|s| s.parse().ok());
142
143    if let Some(max) = max_opt {
144        info!("Using custom value for 'MAX_DISPLAY_OUTPUT_SIZE': {max}");
145        max
146    } else {
147        DEFAULT_MAX_DISPLAY_OUTPUT_SIZE
148    }
149});
150
151struct DisplayStore<'s> {
152    state: &'s dyn StateRead,
153}
154
155impl<'s> DisplayStore<'s> {
156    fn new(state: &'s dyn StateRead) -> Self {
157        Self { state }
158    }
159}
160
161#[async_trait]
162impl sui_display::v2::Store for DisplayStore<'_> {
163    async fn latest(
164        &self,
165        id: AccountAddress,
166    ) -> anyhow::Result<Option<(MoveTypeLayout, Vec<u8>)>> {
167        let read = self.state.get_object_read(&id.into())?;
168        let ObjectRead::Exists(_, object, Some(layout)) = read else {
169            return Ok(None);
170        };
171
172        let Some(move_object) = object.data.try_as_move() else {
173            return Ok(None);
174        };
175
176        Ok(Some((
177            MoveTypeLayout::Struct(Box::new(layout)),
178            move_object.contents().to_vec(),
179        )))
180    }
181}
182
183// An implementation of the read portion of the JSON-RPC interface intended for use in
184// Fullnodes.
185#[derive(Clone)]
186pub struct ReadApi {
187    pub state: Arc<dyn StateRead>,
188    pub transaction_kv_store: Arc<TransactionKeyValueStore>,
189    pub metrics: Arc<JsonRpcMetrics>,
190}
191
192// Internal data structure to make it easy to work with data returned from
193// authority store and also enable code sharing between get_transaction_with_options,
194// multi_get_transaction_with_options, etc.
195#[derive(Default)]
196struct IntermediateTransactionResponse {
197    digest: TransactionDigest,
198    transaction: Option<Transaction>,
199    effects: Option<TransactionEffects>,
200    events: Option<SuiTransactionBlockEvents>,
201    checkpoint_seq: Option<CheckpointSequenceNumber>,
202    balance_changes: Option<Vec<BalanceChange>>,
203    object_changes: Option<Vec<ObjectChange>>,
204    timestamp: Option<CheckpointTimestamp>,
205    errors: Vec<String>,
206}
207
208impl IntermediateTransactionResponse {
209    pub fn new(digest: TransactionDigest) -> Self {
210        Self {
211            digest,
212            ..Default::default()
213        }
214    }
215
216    pub fn transaction(&self) -> &Option<Transaction> {
217        &self.transaction
218    }
219}
220
221impl ReadApi {
222    pub fn new(
223        state: Arc<AuthorityState>,
224        transaction_kv_store: Arc<TransactionKeyValueStore>,
225        metrics: Arc<JsonRpcMetrics>,
226    ) -> Self {
227        Self {
228            state,
229            transaction_kv_store,
230            metrics,
231        }
232    }
233
234    async fn get_checkpoint_internal(&self, id: CheckpointId) -> Result<Checkpoint, Error> {
235        Ok(match id {
236            CheckpointId::SequenceNumber(seq) => {
237                let verified_summary = self
238                    .transaction_kv_store
239                    .get_checkpoint_summary(seq)
240                    .await?;
241                let content = self
242                    .transaction_kv_store
243                    .get_checkpoint_contents(verified_summary.sequence_number)
244                    .await?;
245                let signature = verified_summary.auth_sig().signature.clone();
246                (verified_summary.into_data(), content, signature).into()
247            }
248            CheckpointId::Digest(digest) => {
249                let verified_summary = self
250                    .transaction_kv_store
251                    .get_checkpoint_summary_by_digest(digest)
252                    .await?;
253                let content = self
254                    .transaction_kv_store
255                    .get_checkpoint_contents(verified_summary.sequence_number)
256                    .await?;
257                let signature = verified_summary.auth_sig().signature.clone();
258                (verified_summary.into_data(), content, signature).into()
259            }
260        })
261    }
262
263    pub async fn get_checkpoints_internal(
264        state: Arc<dyn StateRead>,
265        transaction_kv_store: Arc<TransactionKeyValueStore>,
266        // If `Some`, the query will start from the next item after the specified cursor
267        cursor: Option<CheckpointSequenceNumber>,
268        limit: u64,
269        descending_order: bool,
270    ) -> StateReadResult<Vec<Checkpoint>> {
271        let max_checkpoint = state.get_latest_checkpoint_sequence_number()?;
272        let checkpoint_numbers =
273            calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);
274
275        let verified_checkpoints = transaction_kv_store
276            .multi_get_checkpoints_summaries(&checkpoint_numbers)
277            .await?;
278        let checkpoint_contents = transaction_kv_store
279            .multi_get_checkpoints_contents(&checkpoint_numbers)
280            .await?;
281
282        // Summaries and contents are resolved from separate tables, and checkpoint
283        // pruning can delete a checkpoint's contents while leaving its
284        // sequence-addressable summary in place. Pair each summary with the
285        // contents for the *same* sequence number by zipping the two `Option`
286        // vectors index-by-index. Independently dropping the `None`s and zipping
287        // the dense remainders would shift later contents onto earlier summaries,
288        // yielding response rows whose summary and transaction list describe
289        // different checkpoints.
290        let mut checkpoints = Vec::with_capacity(checkpoint_numbers.len());
291        for (maybe_summary, maybe_contents) in verified_checkpoints
292            .into_iter()
293            .zip_debug_eq(checkpoint_contents)
294        {
295            // Skip any sequence number whose summary or contents are unavailable
296            // (e.g. pruned) rather than pairing it with another checkpoint's data.
297            let (Some(summary), Some(contents)) = (maybe_summary, maybe_contents) else {
298                continue;
299            };
300            let signature = summary.auth_sig().signature.clone();
301            let summary = summary.into_summary_and_sequence().1;
302            checkpoints.push(Checkpoint::from((summary, contents, signature)));
303        }
304
305        Ok(checkpoints)
306    }
307
308    #[instrument(skip_all)]
309    async fn multi_get_transaction_blocks_internal(
310        &self,
311        digests: Vec<TransactionDigest>,
312        opts: Option<SuiTransactionBlockResponseOptions>,
313    ) -> Result<Vec<SuiTransactionBlockResponse>, Error> {
314        trace!("start");
315
316        let num_digests = digests.len();
317        if num_digests > *QUERY_MAX_RESULT_LIMIT {
318            Err(SuiRpcInputError::SizeLimitExceeded(
319                QUERY_MAX_RESULT_LIMIT.to_string(),
320            ))?
321        }
322        self.metrics
323            .get_tx_blocks_limit
324            .observe(digests.len() as f64);
325
326        let opts = opts.unwrap_or_default();
327
328        // use LinkedHashMap to dedup and can iterate in insertion order.
329        let mut temp_response: IndexMap<&TransactionDigest, IntermediateTransactionResponse> =
330            IndexMap::from_iter(
331                digests
332                    .iter()
333                    .map(|k| (k, IntermediateTransactionResponse::new(*k))),
334            );
335        if temp_response.len() < num_digests {
336            Err(SuiRpcInputError::ContainsDuplicates)?
337        }
338
339        if opts.require_input() {
340            trace!("getting input");
341            let digests_clone = digests.clone();
342            let transactions =
343                self.transaction_kv_store.multi_get_tx(&digests_clone).await.tap_err(
344                    |err| debug!(digests=?digests_clone, "Failed to multi get transactions: {:?}", err),
345                )?;
346
347            for ((_digest, cache_entry), txn) in temp_response
348                .iter_mut()
349                .zip_debug_eq(transactions.into_iter())
350            {
351                cache_entry.transaction = txn;
352            }
353        }
354
355        // Fetch effects when `show_events` is true because events relies on effects
356        if opts.require_effects() {
357            trace!("getting effects");
358            let digests_clone = digests.clone();
359            let effects_list = self.transaction_kv_store
360                .multi_get_fx_by_tx_digest(&digests_clone)
361                .await
362                .tap_err(
363                    |err| debug!(digests=?digests_clone, "Failed to multi get effects for transactions: {:?}", err),
364                )?;
365            for ((_digest, cache_entry), e) in temp_response
366                .iter_mut()
367                .zip_debug_eq(effects_list.into_iter())
368            {
369                cache_entry.effects = e;
370            }
371        }
372
373        trace!("getting checkpoint sequence numbers");
374        let checkpoint_seq_list = self
375            .transaction_kv_store
376            .multi_get_transaction_checkpoint(&digests)
377            .await
378            .tap_err(
379                |err| debug!(digests=?digests, "Failed to multi get checkpoint sequence number: {:?}", err))?;
380        for ((_digest, cache_entry), seq) in temp_response
381            .iter_mut()
382            .zip_debug_eq(checkpoint_seq_list.into_iter())
383        {
384            cache_entry.checkpoint_seq = seq;
385        }
386
387        let unique_checkpoint_numbers = temp_response
388            .values()
389            .filter_map(|cache_entry| cache_entry.checkpoint_seq)
390            // It's likely that many transactions have the same checkpoint, so we don't
391            // need to over-fetch
392            .unique()
393            .collect::<Vec<CheckpointSequenceNumber>>();
394
395        // fetch timestamp from the DB
396        trace!("getting checkpoint summaries");
397        let timestamps = self
398            .transaction_kv_store
399            .multi_get_checkpoints_summaries(&unique_checkpoint_numbers)
400            .await
401            .map_err(|e| {
402                Error::UnexpectedError(format!("Failed to fetch checkpoint summaries by these checkpoint ids: {unique_checkpoint_numbers:?} with error: {e:?}"))
403            })?
404            .into_iter()
405            .map(|c| c.map(|checkpoint| checkpoint.timestamp_ms));
406
407        // construct a hashmap of checkpoint -> timestamp for fast lookup
408        let checkpoint_to_timestamp = unique_checkpoint_numbers
409            .into_iter()
410            .zip_debug_eq(timestamps)
411            .collect::<HashMap<_, _>>();
412
413        // fill cache with the timestamp
414        for (_, cache_entry) in temp_response.iter_mut() {
415            if cache_entry.checkpoint_seq.is_some() {
416                // safe to unwrap because is_some is checked
417                cache_entry.timestamp = *checkpoint_to_timestamp
418                    .get(cache_entry.checkpoint_seq.as_ref().unwrap())
419                    // Safe to unwrap because checkpoint_seq is guaranteed to exist in checkpoint_to_timestamp
420                    .unwrap();
421            }
422        }
423
424        if opts.show_events {
425            trace!("getting events");
426            let mut non_empty_digests = vec![];
427            for cache_entry in temp_response.values() {
428                if let Some(effects) = &cache_entry.effects
429                    && effects.events_digest().is_some()
430                {
431                    non_empty_digests.push(cache_entry.digest);
432                }
433            }
434            // fetch events from the DB with retry, retry each 0.5s for 3s
435            let backoff = ExponentialBackoff {
436                max_elapsed_time: Some(Duration::from_secs(3)),
437                multiplier: 1.0,
438                ..ExponentialBackoff::default()
439            };
440            let mut events = retry(backoff, || async {
441                match self
442                    .transaction_kv_store
443                    .multi_get_events_by_tx_digests(&non_empty_digests)
444                    .await
445                {
446                    // Only return Ok when all the queried transaction events are found, otherwise retry
447                    // until timeout, then return Err.
448                    Ok(events) if !events.contains(&None) => Ok(events),
449                    Ok(_) => Err(backoff::Error::transient(Error::UnexpectedError(
450                        "Events not found, transaction execution may be incomplete.".into(),
451                    ))),
452                    Err(e) => Err(backoff::Error::permanent(Error::UnexpectedError(format!(
453                        "Failed to call multi_get_events: {e:?}"
454                    )))),
455                }
456            })
457            .await
458            .map_err(|e| {
459                Error::UnexpectedError(format!(
460                    "Retrieving events with retry failed for transaction digests {digests:?}: {e:?}"
461                ))
462            })?
463            .into_iter();
464
465            // fill cache with the events
466            for (_, cache_entry) in temp_response.iter_mut() {
467                let transaction_digest = cache_entry.digest;
468                if let Some(events_digest) =
469                    cache_entry.effects.as_ref().and_then(|e| e.events_digest())
470                {
471                    match events.next() {
472                        Some(Some(ev)) => {
473                            cache_entry.events =
474                                Some(to_sui_transaction_events(self, cache_entry.digest, ev)?)
475                        }
476                        None | Some(None) => {
477                            error!(
478                                "Failed to fetch events with event digest {events_digest:?} for txn {transaction_digest}"
479                            );
480                            cache_entry.errors.push(format!(
481                                "Failed to fetch events with event digest {events_digest:?}",
482                            ))
483                        }
484                    }
485                } else {
486                    // events field will be Some if and only if `show_events` is true and
487                    // there is no error in converting fetching events
488                    cache_entry.events = Some(SuiTransactionBlockEvents::default());
489                }
490            }
491        }
492
493        let mut object_cache =
494            ObjectProviderCache::new((self.state.clone(), self.transaction_kv_store.clone()));
495
496        // Prefetch the objects if we need to show balance or object changes
497        if opts.show_balance_changes || opts.show_object_changes {
498            let mut keys = vec![];
499            for resp in temp_response.values() {
500                let effects = resp.effects.as_ref().ok_or_else(|| {
501                    SuiRpcInputError::GenericNotFound(
502                        "unable to derive balance/object changes because effect is empty"
503                            .to_string(),
504                    )
505                })?;
506
507                for change in effects.object_changes() {
508                    if let Some(input_version) = change.input_version {
509                        keys.push(ObjectKey(change.id, input_version));
510                    }
511                    if let Some(output_version) = change.output_version {
512                        keys.push(ObjectKey(change.id, output_version));
513                    }
514                }
515            }
516
517            let objects = self
518                .transaction_kv_store
519                .multi_get_objects(&keys)
520                .await?
521                .into_iter()
522                .flatten()
523                .collect::<Vec<_>>();
524
525            object_cache.insert_objects_into_cache(objects);
526        }
527
528        if opts.show_balance_changes {
529            trace!("getting balance changes");
530
531            let mut results = vec![];
532            for resp in temp_response.values() {
533                let input_objects = if let Some(tx) = resp.transaction() {
534                    tx.data()
535                        .inner()
536                        .intent_message
537                        .value
538                        .input_objects()
539                        .unwrap_or_default()
540                } else {
541                    // don't have the input tx, so not much we can do. perhaps this is an Err?
542                    Vec::new()
543                };
544                results.push(get_balance_changes_from_effect(
545                    &object_cache,
546                    resp.effects.as_ref().ok_or_else(|| {
547                        SuiRpcInputError::GenericNotFound(
548                            "unable to derive balance changes because effect is empty".to_string(),
549                        )
550                    })?,
551                    input_objects,
552                    None,
553                ));
554            }
555            let results = join_all(results).await;
556            for (result, entry) in results.into_iter().zip_debug_eq(temp_response.iter_mut()) {
557                match result {
558                    Ok(balance_changes) => entry.1.balance_changes = Some(balance_changes),
559                    Err(e) => entry
560                        .1
561                        .errors
562                        .push(format!("Failed to fetch balance changes {e:?}")),
563                }
564            }
565        }
566
567        if opts.show_object_changes {
568            trace!("getting object changes");
569
570            let mut results = vec![];
571            for resp in temp_response.values() {
572                let effects = resp.effects.as_ref().ok_or_else(|| {
573                    SuiRpcInputError::GenericNotFound(
574                        "unable to derive object changes because effect is empty".to_string(),
575                    )
576                })?;
577
578                results.push(get_object_changes(
579                    &object_cache,
580                    effects,
581                    resp.transaction
582                        .as_ref()
583                        .ok_or_else(|| {
584                            SuiRpcInputError::GenericNotFound(
585                                "unable to derive object changes because transaction is empty"
586                                    .to_string(),
587                            )
588                        })?
589                        .data()
590                        .intent_message()
591                        .value
592                        .sender(),
593                    effects.modified_at_versions(),
594                    effects.all_changed_objects(),
595                    effects.all_removed_objects(),
596                ));
597            }
598            let results = join_all(results).await;
599            for (result, entry) in results.into_iter().zip_debug_eq(temp_response.iter_mut()) {
600                match result {
601                    Ok(object_changes) => entry.1.object_changes = Some(object_changes),
602                    Err(e) => entry
603                        .1
604                        .errors
605                        .push(format!("Failed to fetch object changes {e:?}")),
606                }
607            }
608        }
609
610        let epoch_store = self.state.load_epoch_store_one_call_per_task();
611        let converted_tx_block_resps = temp_response
612            .into_iter()
613            .map(|c| convert_to_response(c.1, &opts, epoch_store.module_cache()))
614            .collect::<Result<Vec<_>, _>>()?;
615
616        self.metrics
617            .get_tx_blocks_result_size
618            .observe(converted_tx_block_resps.len() as f64);
619        self.metrics
620            .get_tx_blocks_result_size_total
621            .inc_by(converted_tx_block_resps.len() as u64);
622
623        trace!("done");
624
625        Ok(converted_tx_block_resps)
626    }
627}
628
629#[async_trait]
630impl ReadApiServer for ReadApi {
631    #[instrument(skip(self))]
632    async fn get_object(
633        &self,
634        object_id: ObjectID,
635        options: Option<SuiObjectDataOptions>,
636    ) -> RpcResult<SuiObjectResponse> {
637        with_tracing!(async move {
638            let state = self.state.clone();
639            let object_read = state.get_object_read(&object_id).map_err(|e| {
640                warn!(?object_id, "Failed to get object: {:?}", e);
641                Error::from(e)
642            })?;
643            let options = options.unwrap_or_default();
644
645            match object_read {
646                ObjectRead::NotExists(id) => Ok(SuiObjectResponse::new_with_error(
647                    SuiObjectResponseError::NotExists { object_id: id },
648                )),
649                ObjectRead::Exists(object_ref, o, layout) => {
650                    let mut display_fields = None;
651                    if options.show_display {
652                        match get_display_fields(self, &self.transaction_kv_store, &o, &layout)
653                            .await
654                        {
655                            Ok(rendered_fields) => display_fields = Some(rendered_fields),
656                            Err(e) => {
657                                return Ok(SuiObjectResponse::new(
658                                    Some((object_ref, o, layout, options, None).try_into()?),
659                                    Some(SuiObjectResponseError::DisplayError {
660                                        error: e.to_string(),
661                                    }),
662                                ));
663                            }
664                        }
665                    }
666                    Ok(SuiObjectResponse::new_with_data(
667                        (object_ref, o, layout, options, display_fields).try_into()?,
668                    ))
669                }
670                ObjectRead::Deleted((object_id, version, digest)) => Ok(
671                    SuiObjectResponse::new_with_error(SuiObjectResponseError::Deleted {
672                        object_id,
673                        version,
674                        digest,
675                    }),
676                ),
677            }
678        })
679    }
680
681    #[instrument(skip(self))]
682    async fn multi_get_objects(
683        &self,
684        object_ids: Vec<ObjectID>,
685        options: Option<SuiObjectDataOptions>,
686    ) -> RpcResult<Vec<SuiObjectResponse>> {
687        with_tracing!(async move {
688            if object_ids.len() <= *QUERY_MAX_RESULT_LIMIT {
689                self.metrics
690                    .get_objects_limit
691                    .observe(object_ids.len() as f64);
692                let mut futures = vec![];
693                for object_id in object_ids {
694                    futures.push(self.get_object(object_id, options.clone()));
695                }
696                let results = join_all(futures).await;
697
698                let objects_result: Result<Vec<SuiObjectResponse>, String> = results
699                    .into_iter()
700                    .map(|result| match result {
701                        Ok(response) => Ok(response),
702                        Err(error) => {
703                            error!("Failed to fetch object with error: {error:?}");
704                            Err(format!("Error: {}", error))
705                        }
706                    })
707                    .collect();
708
709                let objects = objects_result.map_err(|err| {
710                    Error::UnexpectedError(format!("Failed to fetch objects with error: {}", err))
711                })?;
712
713                self.metrics
714                    .get_objects_result_size
715                    .observe(objects.len() as f64);
716                self.metrics
717                    .get_objects_result_size_total
718                    .inc_by(objects.len() as u64);
719                Ok(objects)
720            } else {
721                Err(SuiRpcInputError::SizeLimitExceeded(
722                    QUERY_MAX_RESULT_LIMIT.to_string(),
723                ))?
724            }
725        })
726    }
727
728    #[instrument(skip(self))]
729    async fn try_get_past_object(
730        &self,
731        object_id: ObjectID,
732        version: SequenceNumber,
733        options: Option<SuiObjectDataOptions>,
734    ) -> RpcResult<SuiPastObjectResponse> {
735        with_tracing!(async move {
736            let state = self.state.clone();
737            let past_read = state
738                .get_past_object_read(&object_id, version)
739                .map_err(|e| {
740                    error!("Failed to call try_get_past_object for object: {object_id:?} version: {version:?} with error: {e:?}");
741                    Error::from(e)
742                })?;
743            let options = options.unwrap_or_default();
744            match past_read {
745                PastObjectRead::ObjectNotExists(id) => {
746                    Ok(SuiPastObjectResponse::ObjectNotExists(id))
747                }
748                PastObjectRead::VersionFound(object_ref, o, layout) => {
749                    let display_fields = if options.show_display {
750                        // TODO (jian): api breaking change to also modify past objects.
751                        Some(
752                            get_display_fields(self, &self.transaction_kv_store, &o, &layout)
753                                .await
754                                .map_err(|e| {
755                                    Error::UnexpectedError(format!(
756                                        "Unable to render object at version {version}: {e}"
757                                    ))
758                                })?,
759                        )
760                    } else {
761                        None
762                    };
763                    Ok(SuiPastObjectResponse::VersionFound(
764                        (object_ref, o, layout, options, display_fields).try_into()?,
765                    ))
766                }
767                PastObjectRead::ObjectDeleted(oref) => {
768                    Ok(SuiPastObjectResponse::ObjectDeleted(oref.into()))
769                }
770                PastObjectRead::VersionNotFound(id, seq_num) => {
771                    Ok(SuiPastObjectResponse::VersionNotFound(id, seq_num))
772                }
773                PastObjectRead::VersionTooHigh {
774                    object_id,
775                    asked_version,
776                    latest_version,
777                } => Ok(SuiPastObjectResponse::VersionTooHigh {
778                    object_id,
779                    asked_version,
780                    latest_version,
781                }),
782            }
783        })
784    }
785
786    #[instrument(skip(self))]
787    async fn try_get_object_before_version(
788        &self,
789        object_id: ObjectID,
790        version: SequenceNumber,
791    ) -> RpcResult<SuiPastObjectResponse> {
792        let version = self
793            .state
794            .find_object_lt_or_eq_version(&object_id, &version)
795            .await
796            .map_err(Error::from)?
797            .map(|obj| obj.version())
798            .unwrap_or_default();
799        self.try_get_past_object(
800            object_id,
801            version,
802            Some(SuiObjectDataOptions::bcs_lossless()),
803        )
804        .await
805    }
806
807    #[instrument(skip(self))]
808    async fn try_multi_get_past_objects(
809        &self,
810        past_objects: Vec<SuiGetPastObjectRequest>,
811        options: Option<SuiObjectDataOptions>,
812    ) -> RpcResult<Vec<SuiPastObjectResponse>> {
813        with_tracing!(async move {
814            if past_objects.len() <= *QUERY_MAX_RESULT_LIMIT {
815                let mut futures = vec![];
816                for past_object in past_objects {
817                    futures.push(self.try_get_past_object(
818                        past_object.object_id,
819                        past_object.version,
820                        options.clone(),
821                    ));
822                }
823                let results = join_all(futures).await;
824
825                let (oks, errs): (Vec<_>, Vec<_>) = results.into_iter().partition(Result::is_ok);
826                let success = oks.into_iter().filter_map(Result::ok).collect();
827                let errors: Vec<_> = errs.into_iter().filter_map(Result::err).collect();
828                if !errors.is_empty() {
829                    let error_string = errors
830                        .iter()
831                        .map(|e| e.to_string())
832                        .collect::<Vec<String>>()
833                        .join("; ");
834                    Err(anyhow!("{error_string}").into()) // Collects errors not related to SuiPastObjectResponse variants
835                } else {
836                    Ok(success)
837                }
838            } else {
839                Err(SuiRpcInputError::SizeLimitExceeded(
840                    QUERY_MAX_RESULT_LIMIT.to_string(),
841                ))?
842            }
843        })
844    }
845
846    #[instrument(skip(self))]
847    async fn get_total_transaction_blocks(&self) -> RpcResult<BigInt<u64>> {
848        with_tracing!(async move {
849            Ok(self
850                .state
851                .get_total_transaction_blocks()
852                .map_err(Error::from)?
853                .into()) // converts into BigInt<u64>
854        })
855    }
856
857    #[instrument(skip(self))]
858    async fn get_transaction_block(
859        &self,
860        digest: TransactionDigest,
861        opts: Option<SuiTransactionBlockResponseOptions>,
862    ) -> RpcResult<SuiTransactionBlockResponse> {
863        with_tracing!(async move {
864            let opts = opts.unwrap_or_default();
865            let mut temp_response = IntermediateTransactionResponse::new(digest);
866
867            // Fetch transaction to determine existence
868            let transaction_kv_store = self.transaction_kv_store.clone();
869            let transaction = async move {
870                let ret = transaction_kv_store.get_tx(digest).await.map_err(|err| {
871                    debug!(tx_digest=?digest, "Failed to get transaction: {}", err);
872                    Error::from(err)
873                });
874                add_server_timing("tx_kv_lookup");
875                ret
876            }
877            .await?;
878            let input_objects = transaction
879                .data()
880                .inner()
881                .intent_message
882                .value
883                .input_objects()
884                .unwrap_or_default();
885
886            // the input is needed for object_changes to retrieve the sender address.
887            if opts.require_input() {
888                temp_response.transaction = Some(transaction);
889            }
890
891            // Fetch effects when `show_events` is true because events relies on effects
892            if opts.require_effects() {
893                let transaction_kv_store = self.transaction_kv_store.clone();
894                temp_response.effects = Some(
895                    transaction_kv_store
896                        .get_fx_by_tx_digest(digest)
897                        .await
898                        .map_err(|err| {
899                            debug!(tx_digest=?digest, "Failed to get effects: {:?}", err);
900                            Error::from(err)
901                        })?,
902                );
903            }
904
905            temp_response.checkpoint_seq = self
906                .transaction_kv_store
907                .deprecated_get_transaction_checkpoint(digest)
908                .await
909                .map_err(|e| {
910                    error!("Failed to retrieve checkpoint sequence for transaction {digest:?} with error: {e:?}");
911                    Error::from(e)
912                })?;
913
914            if let Some(checkpoint_seq) = &temp_response.checkpoint_seq {
915                let kv_store = self.transaction_kv_store.clone();
916                let checkpoint_seq = *checkpoint_seq;
917                let checkpoint = kv_store
918                    // safe to unwrap because we have checked `is_some` above
919                    .get_checkpoint_summary(checkpoint_seq)
920                    .await
921                    .map_err(|e| {
922                        error!("Failed to get checkpoint by sequence number: {checkpoint_seq:?} with error: {e:?}");
923                        Error::from(e)
924                    })?;
925                // TODO(chris): we don't need to fetch the whole checkpoint summary
926                temp_response.timestamp = Some(checkpoint.timestamp_ms);
927            }
928
929            if opts.show_events && temp_response.effects.is_some() {
930                let transaction_kv_store = self.transaction_kv_store.clone();
931                let events = transaction_kv_store
932                    .multi_get_events_by_tx_digests(&[digest])
933                    .await
934                    .map_err(|e| {
935                        error!("Failed to call get transaction events for transaction: {digest:?} with error {e:?}");
936                        Error::from(e)
937                    })?
938                    .pop()
939                    .flatten();
940                match events {
941                    None => temp_response.events = Some(SuiTransactionBlockEvents::default()),
942                    Some(events) => match to_sui_transaction_events(self, digest, events) {
943                        Ok(e) => temp_response.events = Some(e),
944                        Err(e) => temp_response.errors.push(e.to_string()),
945                    },
946                }
947            }
948
949            let object_cache =
950                ObjectProviderCache::new((self.state.clone(), self.transaction_kv_store.clone()));
951            if opts.show_balance_changes
952                && let Some(effects) = &temp_response.effects
953            {
954                let balance_changes =
955                    get_balance_changes_from_effect(&object_cache, effects, input_objects, None)
956                        .await;
957
958                if let Ok(balance_changes) = balance_changes {
959                    temp_response.balance_changes = Some(balance_changes);
960                } else {
961                    temp_response.errors.push(format!(
962                        "Cannot retrieve balance changes: {}",
963                        balance_changes.unwrap_err()
964                    ));
965                }
966            }
967
968            if opts.show_object_changes
969                && let (Some(effects), Some(input)) =
970                    (&temp_response.effects, &temp_response.transaction)
971            {
972                let sender = input.data().intent_message().value.sender();
973                let object_changes = get_object_changes(
974                    &object_cache,
975                    effects,
976                    sender,
977                    effects.modified_at_versions(),
978                    effects.all_changed_objects(),
979                    effects.all_removed_objects(),
980                )
981                .await;
982
983                if let Ok(object_changes) = object_changes {
984                    temp_response.object_changes = Some(object_changes);
985                } else {
986                    temp_response.errors.push(format!(
987                        "Cannot retrieve object changes: {}",
988                        object_changes.unwrap_err()
989                    ));
990                }
991            }
992            let epoch_store = self.state.load_epoch_store_one_call_per_task();
993            convert_to_response(temp_response, &opts, epoch_store.module_cache())
994        })
995    }
996
997    #[instrument(skip(self))]
998    async fn multi_get_transaction_blocks(
999        &self,
1000        digests: Vec<TransactionDigest>,
1001        opts: Option<SuiTransactionBlockResponseOptions>,
1002    ) -> RpcResult<Vec<SuiTransactionBlockResponse>> {
1003        with_tracing!(async move {
1004            self.multi_get_transaction_blocks_internal(digests, opts)
1005                .await
1006        })
1007    }
1008
1009    #[instrument(skip(self))]
1010    async fn get_events(&self, transaction_digest: TransactionDigest) -> RpcResult<Vec<SuiEvent>> {
1011        with_tracing!(async move {
1012            let state = self.state.clone();
1013            let transaction_kv_store = self.transaction_kv_store.clone();
1014            async move {
1015                let store = state.load_epoch_store_one_call_per_task();
1016                let events = transaction_kv_store
1017                    .multi_get_events_by_tx_digests(&[transaction_digest])
1018                    .await
1019                    .map_err(|e| {
1020                        error!("Failed to get transaction events for transaction {transaction_digest:?} with error: {e:?}");
1021                        Error::StateReadError(e.into())
1022                    })?
1023                    .pop()
1024                    .flatten();
1025                Ok(match events {
1026                    Some(events) => events
1027                        .data
1028                        .into_iter()
1029                        .enumerate()
1030                        .map(|(seq, e)| {
1031                            let layout = store
1032                                .executor()
1033                                .type_layout_resolver(
1034                                    store.protocol_config(),
1035                                    Box::new(
1036                                        &state.get_backing_package_store().as_ref(),
1037                                    ))
1038                                .get_annotated_layout(&e.type_)?;
1039                            SuiEvent::try_from(e, transaction_digest, seq as u64, None, layout)
1040                        })
1041                        .collect::<Result<Vec<_>, _>>()
1042                        .map_err(Error::SuiError)?,
1043                    None => vec![],
1044                })
1045            }
1046            .await
1047        })
1048    }
1049
1050    #[instrument(skip(self))]
1051    async fn get_latest_checkpoint_sequence_number(&self) -> RpcResult<BigInt<u64>> {
1052        with_tracing!(async move {
1053            Ok(self
1054                .state
1055                .get_latest_checkpoint_sequence_number()
1056                .map_err(|e| {
1057                    SuiRpcInputError::GenericNotFound(format!(
1058                        "Latest checkpoint sequence number was not found with error :{e}"
1059                    ))
1060                })?
1061                .into())
1062        })
1063    }
1064
1065    #[instrument(skip(self))]
1066    async fn get_checkpoint(&self, id: CheckpointId) -> RpcResult<Checkpoint> {
1067        with_tracing!(self.get_checkpoint_internal(id))
1068    }
1069
1070    #[instrument(skip(self))]
1071    async fn get_checkpoints(
1072        &self,
1073        // If `Some`, the query will start from the next item after the specified cursor
1074        cursor: Option<BigInt<u64>>,
1075        limit: Option<usize>,
1076        descending_order: bool,
1077    ) -> RpcResult<CheckpointPage> {
1078        with_tracing!(async move {
1079            let limit = validate_limit(limit, QUERY_MAX_RESULT_LIMIT_CHECKPOINTS)
1080                .map_err(SuiRpcInputError::from)?;
1081
1082            let state = self.state.clone();
1083            let kv_store = self.transaction_kv_store.clone();
1084
1085            self.metrics.get_checkpoints_limit.observe(limit as f64);
1086
1087            let mut data = Self::get_checkpoints_internal(
1088                state,
1089                kv_store,
1090                cursor.map(|s| *s),
1091                limit as u64 + 1,
1092                descending_order,
1093            )
1094            .await
1095            .map_err(Error::from)?;
1096
1097            let has_next_page = data.len() > limit;
1098            data.truncate(limit);
1099
1100            let next_cursor = if has_next_page {
1101                data.last().cloned().map(|d| d.sequence_number.into())
1102            } else {
1103                None
1104            };
1105
1106            self.metrics
1107                .get_checkpoints_result_size
1108                .observe(data.len() as f64);
1109            self.metrics
1110                .get_checkpoints_result_size_total
1111                .inc_by(data.len() as u64);
1112
1113            Ok(CheckpointPage {
1114                data,
1115                next_cursor,
1116                has_next_page,
1117            })
1118        })
1119    }
1120
1121    #[instrument(skip(self))]
1122    async fn get_protocol_config(
1123        &self,
1124        version: Option<BigInt<u64>>,
1125    ) -> RpcResult<ProtocolConfigResponse> {
1126        with_tracing!(async move {
1127            version
1128                .map(|v| {
1129                    ProtocolConfig::get_for_version_if_supported(
1130                        (*v).into(),
1131                        self.state.get_chain_identifier()?.chain(),
1132                    )
1133                    .ok_or(SuiRpcInputError::ProtocolVersionUnsupported(
1134                        ProtocolVersion::MIN.as_u64(),
1135                        ProtocolVersion::MAX.as_u64(),
1136                    ))
1137                    .map_err(Error::from)
1138                })
1139                .unwrap_or(Ok(self
1140                    .state
1141                    .load_epoch_store_one_call_per_task()
1142                    .protocol_config()
1143                    .clone()))
1144                .map(ProtocolConfigResponse::from)
1145        })
1146    }
1147
1148    #[instrument(skip(self))]
1149    async fn get_chain_identifier(&self) -> RpcResult<String> {
1150        with_tracing!(async move {
1151            let ci = self.state.get_chain_identifier()?;
1152            Ok(ci.to_string())
1153        })
1154    }
1155    #[instrument(skip(self))]
1156    async fn verify_zklogin_signature(
1157        &self,
1158        bytes: String,
1159        signature: String,
1160        intent_scope: ZkLoginIntentScope,
1161        author: SuiAddress,
1162    ) -> RpcResult<ZkLoginVerifyResult> {
1163        let epoch_store = self.state.load_epoch_store_one_call_per_task();
1164        let curr_epoch = epoch_store.epoch();
1165        let zklogin_env_native = match self
1166            .state
1167            .get_chain_identifier()
1168            .expect("get chain identifier should not fail")
1169            .chain()
1170        {
1171            sui_protocol_config::Chain::Mainnet | sui_protocol_config::Chain::Testnet => {
1172                ZkLoginEnv::Prod
1173            }
1174            _ => ZkLoginEnv::Test,
1175        };
1176        let GenericSignature::ZkLoginAuthenticator(zklogin_sig) =
1177            GenericSignature::from_bytes(&Base64::decode(&signature).map_err(Error::from)?)
1178                .map_err(Error::from)?
1179        else {
1180            return Err(SuiRpcInputError::GenericNotFound(
1181                "Endpoint only supports zkLogin signature".to_string(),
1182            )
1183            .into());
1184        };
1185
1186        let new_jwks =
1187            match get_authenticator_state(self.state.get_object_store()).map_err(Error::from)? {
1188                Some(authenticator_state) => authenticator_state.active_jwks,
1189                None => {
1190                    return Err(SuiRpcInputError::GenericNotFound(
1191                        "Authenticator state not found".to_string(),
1192                    )
1193                    .into());
1194                }
1195            };
1196
1197        // construct verify params with active jwks and zklogin_env.
1198        let mut oidc_provider_jwks = ImHashMap::new();
1199        for active_jwk in new_jwks.iter() {
1200            let ActiveJwk { jwk_id, jwk, .. } = active_jwk;
1201            match oidc_provider_jwks.entry(jwk_id.clone()) {
1202                im::hashmap::Entry::Occupied(_) => {
1203                    warn!("JWK with kid {:?} already exists", jwk_id);
1204                }
1205                im::hashmap::Entry::Vacant(entry) => {
1206                    entry.insert(jwk.clone());
1207                }
1208            }
1209        }
1210        let verify_params = VerifyParams::new(
1211            oidc_provider_jwks,
1212            vec![],
1213            zklogin_env_native,
1214            true,
1215            true,
1216            true,
1217            Some(30),
1218            true,
1219            true,
1220        );
1221        match intent_scope {
1222            ZkLoginIntentScope::TransactionData => {
1223                let tx_data: TransactionData =
1224                    bcs::from_bytes(&Base64::decode(&bytes).map_err(Error::from)?)
1225                        .map_err(Error::from)?;
1226                let intent_msg = IntentMessage::new(Intent::sui_transaction(), tx_data.clone());
1227                let sig = GenericSignature::ZkLoginAuthenticator(zklogin_sig);
1228                match sig.verify_authenticator(
1229                    &intent_msg,
1230                    author,
1231                    curr_epoch,
1232                    &verify_params,
1233                    Arc::new(VerifiedDigestCache::new_empty()),
1234                ) {
1235                    Ok(_) => Ok(ZkLoginVerifyResult {
1236                        success: true,
1237                        errors: vec![],
1238                    }),
1239                    Err(e) => Ok(ZkLoginVerifyResult {
1240                        success: false,
1241                        errors: vec![e.to_string()],
1242                    }),
1243                }
1244            }
1245            ZkLoginIntentScope::PersonalMessage => {
1246                let data = PersonalMessage {
1247                    message: Base64::decode(&bytes).map_err(Error::from)?,
1248                };
1249                let intent_msg = IntentMessage::new(Intent::personal_message(), data);
1250
1251                let sig = GenericSignature::ZkLoginAuthenticator(zklogin_sig);
1252                match sig.verify_authenticator(
1253                    &intent_msg,
1254                    author,
1255                    curr_epoch,
1256                    &verify_params,
1257                    Arc::new(VerifiedDigestCache::new_empty()),
1258                ) {
1259                    Ok(_) => Ok(ZkLoginVerifyResult {
1260                        success: true,
1261                        errors: vec![],
1262                    }),
1263                    Err(e) => Ok(ZkLoginVerifyResult {
1264                        success: false,
1265                        errors: vec![e.to_string()],
1266                    }),
1267                }
1268            }
1269        }
1270    }
1271}
1272
1273impl SuiRpcModule for ReadApi {
1274    fn rpc(self) -> RpcModule<Self> {
1275        self.into_rpc()
1276    }
1277
1278    fn rpc_doc_module() -> Module {
1279        ReadApiOpenRpc::module_doc()
1280    }
1281}
1282
1283#[instrument(skip_all)]
1284fn to_sui_transaction_events(
1285    fullnode_api: &ReadApi,
1286    tx_digest: TransactionDigest,
1287    events: TransactionEvents,
1288) -> Result<SuiTransactionBlockEvents, Error> {
1289    let epoch_store = fullnode_api.state.load_epoch_store_one_call_per_task();
1290    let backing_package_store = fullnode_api.state.get_backing_package_store();
1291    let mut layout_resolver = epoch_store.executor().type_layout_resolver(
1292        epoch_store.protocol_config(),
1293        Box::new(backing_package_store.as_ref()),
1294    );
1295    Ok(SuiTransactionBlockEvents::try_from(
1296        events,
1297        tx_digest,
1298        None,
1299        layout_resolver.as_mut(),
1300    )?)
1301}
1302
1303#[derive(Debug, thiserror::Error)]
1304pub enum ObjectDisplayError {
1305    #[error("Not a move struct")]
1306    NotMoveStruct,
1307
1308    #[error("Failed to extract layout")]
1309    Layout,
1310
1311    #[error("Failed to extract Move object")]
1312    MoveObject,
1313
1314    #[error(transparent)]
1315    Deserialization(#[from] SuiError),
1316
1317    #[error("Failed to deserialize 'VersionUpdatedEvent': {0}")]
1318    Bcs(#[from] bcs::Error),
1319
1320    #[error(transparent)]
1321    StateReadError(#[from] StateReadError),
1322
1323    #[error(transparent)]
1324    Internal(#[from] anyhow::Error),
1325}
1326
1327#[instrument(skip(fullnode_api, kv_store))]
1328async fn get_display_fields(
1329    fullnode_api: &ReadApi,
1330    kv_store: &Arc<TransactionKeyValueStore>,
1331    original_object: &Object,
1332    original_layout: &Option<MoveStructLayout>,
1333) -> Result<DisplayFieldsResponse, ObjectDisplayError> {
1334    let (layout, type_) = if let Some(layout) = original_layout {
1335        let type_ = &layout.type_;
1336        let layout = MoveTypeLayout::Struct(Box::new(layout.clone()));
1337        (layout, type_)
1338    } else {
1339        return Ok(DisplayFieldsResponse {
1340            data: None,
1341            error: None,
1342        });
1343    };
1344
1345    let Some(move_object) = original_object.data.try_as_move() else {
1346        return Err(ObjectDisplayError::MoveObject);
1347    };
1348
1349    let display: Vec<(String, Result<Json, anyhow::Error>)> =
1350        if let Some(display_object) = get_display_object_v2_by_type(fullnode_api, type_)? {
1351            let root = sui_display::v2::OwnedSlice::new(layout, move_object.contents().to_owned());
1352            let store = DisplayStore::new(fullnode_api.state.as_ref());
1353            let interpreter = sui_display::v2::Interpreter::new(root, store);
1354            let limits = sui_display::v2::Limits {
1355                max_depth: *MAX_DISPLAY_FIELD_DEPTH,
1356                max_nodes: *MAX_DISPLAY_FORMAT_NODES,
1357                max_loads: *MAX_DISPLAY_OBJECT_LOADS,
1358            };
1359
1360            match sui_display::v2::Display::parse(limits, display_object.fields()) {
1361                Ok(display) => match display
1362                    .display::<Json>(
1363                        *MAX_DISPLAY_MOVE_VALUE_DEPTH,
1364                        *MAX_DISPLAY_OUTPUT_SIZE,
1365                        &interpreter,
1366                    )
1367                    .await
1368                {
1369                    Ok(fields) => fields
1370                        .into_iter()
1371                        .map(|(field, value)| (field, value.map_err(anyhow::Error::from)))
1372                        .collect(),
1373                    Err(e) => {
1374                        return Ok(DisplayFieldsResponse {
1375                            data: None,
1376                            error: Some(SuiObjectResponseError::DisplayError {
1377                                error: e.to_string(),
1378                            }),
1379                        });
1380                    }
1381                },
1382
1383                Err(e) => {
1384                    return Ok(DisplayFieldsResponse {
1385                        data: None,
1386                        error: Some(SuiObjectResponseError::DisplayError {
1387                            error: e.to_string(),
1388                        }),
1389                    });
1390                }
1391            }
1392        } else if let Some(display_object) =
1393            get_display_object_v1_by_type(kv_store, fullnode_api, type_).await?
1394        {
1395            let format = match Format::parse(*MAX_DISPLAY_FIELD_DEPTH, &display_object.fields) {
1396                Ok(format) => format,
1397                Err(e) => {
1398                    return Ok(DisplayFieldsResponse {
1399                        data: None,
1400                        error: Some(SuiObjectResponseError::DisplayError {
1401                            error: e.to_string(),
1402                        }),
1403                    });
1404                }
1405            };
1406
1407            match format.display(*MAX_DISPLAY_OUTPUT_SIZE, move_object.contents(), &layout) {
1408                Ok(fields) => fields
1409                    .into_iter()
1410                    .map(|(field, value)| (field, value.map(Json::String)))
1411                    .collect(),
1412                Err(e) => {
1413                    return Ok(DisplayFieldsResponse {
1414                        data: None,
1415                        error: Some(SuiObjectResponseError::DisplayError {
1416                            error: e.to_string(),
1417                        }),
1418                    });
1419                }
1420            }
1421        } else {
1422            return Ok(DisplayFieldsResponse {
1423                data: None,
1424                error: None,
1425            });
1426        };
1427
1428    let mut fields = BTreeMap::new();
1429    let mut errors = vec![];
1430
1431    for (key, value) in display {
1432        match value {
1433            Ok(v) => {
1434                fields.insert(key, v);
1435            }
1436            Err(e) => {
1437                errors.push(e.to_string());
1438            }
1439        }
1440    }
1441
1442    Ok(DisplayFieldsResponse {
1443        data: (!fields.is_empty()).then_some(fields),
1444        error: (!errors.is_empty()).then(|| SuiObjectResponseError::DisplayError {
1445            error: errors.join("; "),
1446        }),
1447    })
1448}
1449
1450#[instrument(skip(kv_store, fullnode_api))]
1451async fn get_display_object_v1_by_type(
1452    kv_store: &Arc<TransactionKeyValueStore>,
1453    fullnode_api: &ReadApi,
1454    object_type: &StructTag,
1455) -> Result<Option<DisplayVersionUpdatedEvent>, ObjectDisplayError> {
1456    let mut events = fullnode_api
1457        .state
1458        .query_events(
1459            kv_store,
1460            EventFilter::MoveEventType(DisplayVersionUpdatedEvent::type_(object_type)),
1461            None,
1462            1,
1463            true,
1464        )
1465        .await?;
1466
1467    // If there's any recent version of Display, give it to the client.
1468    if let Some(event) = events.pop() {
1469        let display: DisplayVersionUpdatedEvent = bcs::from_bytes(&event.bcs.into_bytes())?;
1470        Ok(Some(display))
1471    } else {
1472        Ok(None)
1473    }
1474}
1475
1476#[instrument(skip(fullnode_api))]
1477fn get_display_object_v2_by_type(
1478    fullnode_api: &ReadApi,
1479    object_type: &StructTag,
1480) -> Result<Option<display_registry::Display>, ObjectDisplayError> {
1481    let object_id = display_registry::display_object_id(object_type.clone().into())?;
1482    let ObjectRead::Exists(_, object, _) = fullnode_api.state.get_object_read(&object_id)? else {
1483        return Ok(None);
1484    };
1485
1486    let Some(move_object) = object.data.try_as_move() else {
1487        return Ok(None);
1488    };
1489
1490    Ok(Some(bcs::from_bytes(move_object.contents())?))
1491}
1492
1493#[instrument(skip_all)]
1494fn convert_to_response(
1495    cache: IntermediateTransactionResponse,
1496    opts: &SuiTransactionBlockResponseOptions,
1497    module_cache: &impl GetModule,
1498) -> RpcInterimResult<SuiTransactionBlockResponse> {
1499    let mut response = SuiTransactionBlockResponse::new(cache.digest);
1500    response.errors = cache.errors;
1501
1502    if let Some(transaction) = cache.transaction {
1503        if opts.show_raw_input {
1504            response.raw_transaction = bcs::to_bytes(transaction.data()).map_err(|e| {
1505                // TODO: is this a client or server error?
1506                anyhow!("Failed to serialize raw transaction with error: {e}")
1507            })?;
1508        }
1509
1510        if opts.show_input {
1511            response.transaction = Some(SuiTransactionBlock::try_from(
1512                transaction.into_data(),
1513                module_cache,
1514            )?);
1515        }
1516    }
1517
1518    if let Some(effects) = cache.effects {
1519        if opts.show_raw_effects {
1520            response.raw_effects = bcs::to_bytes(&effects).map_err(|e| {
1521                // TODO: is this a client or server error?
1522                anyhow!("Failed to serialize transaction block effects with error: {e}")
1523            })?;
1524        }
1525
1526        if opts.show_effects {
1527            response.effects = Some(effects.try_into().map_err(|e| {
1528                // TODO: is this a client or server error?
1529                anyhow!("Failed to convert transaction block effects with error: {e}")
1530            })?);
1531        }
1532    }
1533
1534    response.checkpoint = cache.checkpoint_seq;
1535    response.timestamp_ms = cache.timestamp;
1536
1537    if opts.show_events {
1538        response.events = cache.events;
1539    }
1540
1541    if opts.show_balance_changes {
1542        response.balance_changes = cache.balance_changes;
1543    }
1544
1545    if opts.show_object_changes {
1546        response.object_changes = cache.object_changes;
1547    }
1548
1549    Ok(response)
1550}
1551
1552fn calculate_checkpoint_numbers(
1553    // If `Some`, the query will start from the next item after the specified cursor
1554    cursor: Option<CheckpointSequenceNumber>,
1555    limit: u64,
1556    descending_order: bool,
1557    max_checkpoint: CheckpointSequenceNumber,
1558) -> Vec<CheckpointSequenceNumber> {
1559    let (start_index, end_index) = match cursor {
1560        Some(t) => {
1561            if descending_order {
1562                let start = std::cmp::min(t.saturating_sub(1), max_checkpoint);
1563                let end = start.saturating_sub(limit - 1);
1564                (end, start)
1565            } else {
1566                let start =
1567                    std::cmp::min(t.checked_add(1).unwrap_or(max_checkpoint), max_checkpoint);
1568                let end = std::cmp::min(
1569                    start.checked_add(limit - 1).unwrap_or(max_checkpoint),
1570                    max_checkpoint,
1571                );
1572                (start, end)
1573            }
1574        }
1575        None => {
1576            if descending_order {
1577                (max_checkpoint.saturating_sub(limit - 1), max_checkpoint)
1578            } else {
1579                (0, std::cmp::min(limit - 1, max_checkpoint))
1580            }
1581        }
1582    };
1583
1584    if descending_order {
1585        (start_index..=end_index).rev().collect()
1586    } else {
1587        (start_index..=end_index).collect()
1588    }
1589}
1590
1591#[cfg(test)]
1592mod tests {
1593    use super::*;
1594    use crate::authority_state::MockStateRead;
1595    use mockall::mock;
1596    use roaring::RoaringBitmap;
1597    use std::collections::HashMap;
1598    use sui_storage::key_value_store::{
1599        KVStoreCheckpointData, KVStoreTransactionData, TransactionKeyValueStoreTrait,
1600    };
1601    use sui_storage::key_value_store_metrics::KeyValueStoreMetrics;
1602    use sui_types::base_types::ExecutionDigests;
1603    use sui_types::crypto::AuthorityStrongQuorumSignInfo;
1604    use sui_types::digests::TransactionEffectsDigest;
1605    use sui_types::effects::TransactionEvents;
1606    use sui_types::error::SuiResult;
1607    use sui_types::gas::GasCostSummary;
1608    use sui_types::message_envelope::Envelope;
1609    use sui_types::messages_checkpoint::{
1610        CertifiedCheckpointSummary, CheckpointContents, CheckpointDigest, CheckpointSummary,
1611    };
1612    use sui_types::object::Object;
1613    use sui_types::storage::ObjectKey;
1614
1615    #[test]
1616    fn test_calculate_checkpoint_numbers() {
1617        let cursor = Some(10);
1618        let limit = 5;
1619        let descending_order = true;
1620        let max_checkpoint = 15;
1621
1622        let checkpoint_numbers =
1623            calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);
1624
1625        assert_eq!(checkpoint_numbers, vec![9, 8, 7, 6, 5]);
1626    }
1627
1628    #[test]
1629    fn test_calculate_checkpoint_numbers_descending_no_cursor() {
1630        let cursor = None;
1631        let limit = 5;
1632        let descending_order = true;
1633        let max_checkpoint = 15;
1634
1635        let checkpoint_numbers =
1636            calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);
1637
1638        assert_eq!(checkpoint_numbers, vec![15, 14, 13, 12, 11]);
1639    }
1640
1641    #[test]
1642    fn test_calculate_checkpoint_numbers_ascending_no_cursor() {
1643        let cursor = None;
1644        let limit = 5;
1645        let descending_order = false;
1646        let max_checkpoint = 15;
1647
1648        let checkpoint_numbers =
1649            calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);
1650
1651        assert_eq!(checkpoint_numbers, vec![0, 1, 2, 3, 4]);
1652    }
1653
1654    #[test]
1655    fn test_calculate_checkpoint_numbers_ascending_with_cursor() {
1656        let cursor = Some(10);
1657        let limit = 5;
1658        let descending_order = false;
1659        let max_checkpoint = 15;
1660
1661        let checkpoint_numbers =
1662            calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);
1663
1664        assert_eq!(checkpoint_numbers, vec![11, 12, 13, 14, 15]);
1665    }
1666
1667    #[test]
1668    fn test_calculate_checkpoint_numbers_ascending_limit_exceeds_max() {
1669        let cursor = None;
1670        let limit = 20;
1671        let descending_order = false;
1672        let max_checkpoint = 15;
1673
1674        let checkpoint_numbers =
1675            calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);
1676
1677        assert_eq!(checkpoint_numbers, (0..=15).collect::<Vec<_>>());
1678    }
1679
1680    #[test]
1681    fn test_calculate_checkpoint_numbers_descending_limit_exceeds_max() {
1682        let cursor = None;
1683        let limit = 20;
1684        let descending_order = true;
1685        let max_checkpoint = 15;
1686
1687        let checkpoint_numbers =
1688            calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint);
1689
1690        assert_eq!(checkpoint_numbers, (0..=15).rev().collect::<Vec<_>>());
1691    }
1692
1693    mock! {
1694        CheckpointKvStore {}
1695        #[async_trait]
1696        impl TransactionKeyValueStoreTrait for CheckpointKvStore {
1697            async fn multi_get(
1698                &self,
1699                transactions: &[TransactionDigest],
1700                effects: &[TransactionDigest],
1701            ) -> SuiResult<KVStoreTransactionData>;
1702
1703            async fn multi_get_checkpoints(
1704                &self,
1705                checkpoint_summaries: &[CheckpointSequenceNumber],
1706                checkpoint_contents: &[CheckpointSequenceNumber],
1707                checkpoint_summaries_by_digest: &[CheckpointDigest],
1708            ) -> SuiResult<KVStoreCheckpointData>;
1709
1710            async fn deprecated_get_transaction_checkpoint(
1711                &self,
1712                digest: TransactionDigest,
1713            ) -> SuiResult<Option<CheckpointSequenceNumber>>;
1714
1715            async fn get_object(
1716                &self,
1717                object_id: ObjectID,
1718                version: SequenceNumber,
1719            ) -> SuiResult<Option<Object>>;
1720
1721            async fn multi_get_objects(
1722                &self,
1723                object_keys: &[ObjectKey],
1724            ) -> SuiResult<Vec<Option<Object>>>;
1725
1726            async fn multi_get_transaction_checkpoint(
1727                &self,
1728                digests: &[TransactionDigest],
1729            ) -> SuiResult<Vec<Option<CheckpointSequenceNumber>>>;
1730
1731            async fn multi_get_events_by_tx_digests(
1732                &self,
1733                digests: &[TransactionDigest],
1734            ) -> SuiResult<Vec<Option<TransactionEvents>>>;
1735        }
1736    }
1737
1738    // Builds `CheckpointContents` whose single transaction digest uniquely
1739    // encodes `seq`, so a returned `Checkpoint` can be traced back to the
1740    // sequence number whose contents it actually carries.
1741    fn test_checkpoint_contents(seq: CheckpointSequenceNumber) -> CheckpointContents {
1742        let mut tx = [0u8; 32];
1743        tx[0] = 0xA;
1744        tx[1..9].copy_from_slice(&seq.to_le_bytes());
1745        let mut fx = [0u8; 32];
1746        fx[0] = 0xE;
1747        fx[1..9].copy_from_slice(&seq.to_le_bytes());
1748        CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::new(
1749            TransactionDigest::new(tx),
1750            TransactionEffectsDigest::new(fx),
1751        )])
1752    }
1753
1754    // Builds a certified summary for `seq`. The aggregate signature is a
1755    // placeholder; `get_checkpoints_internal` never verifies it.
1756    fn test_certified_summary(
1757        seq: CheckpointSequenceNumber,
1758        contents: &CheckpointContents,
1759    ) -> CertifiedCheckpointSummary {
1760        let summary = CheckpointSummary::new(
1761            &ProtocolConfig::get_for_max_version_UNSAFE(),
1762            0,
1763            seq,
1764            seq,
1765            contents,
1766            None,
1767            GasCostSummary::default(),
1768            None,
1769            0,
1770            Vec::new(),
1771            Vec::new(),
1772        );
1773        let auth_sig = AuthorityStrongQuorumSignInfo {
1774            epoch: 0,
1775            signature: Default::default(),
1776            signers_map: RoaringBitmap::new(),
1777        };
1778        Envelope::new_from_data_and_sig(summary, auth_sig)
1779    }
1780
1781    // Regression test for a pruning-induced misalignment: when a checkpoint's
1782    // contents are pruned but its sequence-addressable summary survives,
1783    // `get_checkpoints_internal` must not pair that summary (or any later one)
1784    // with a different checkpoint's contents.
1785    #[tokio::test]
1786    async fn test_get_checkpoints_internal_preserves_alignment_across_pruned_contents() {
1787        let max_checkpoint: CheckpointSequenceNumber = 13;
1788        // Contents for this sequence are pruned while its summary remains. It
1789        // sits in the interior of the requested range to show that alignment is
1790        // preserved regardless of where the hole falls.
1791        let pruned_seq: CheckpointSequenceNumber = 11;
1792
1793        let mut all_contents: HashMap<CheckpointSequenceNumber, CheckpointContents> =
1794            HashMap::new();
1795        for seq in 0..=max_checkpoint {
1796            all_contents.insert(seq, test_checkpoint_contents(seq));
1797        }
1798
1799        let mut mock_state = MockStateRead::new();
1800        mock_state
1801            .expect_get_latest_checkpoint_sequence_number()
1802            .returning(move || Ok(max_checkpoint));
1803
1804        let store_contents = all_contents.clone();
1805        let mut mock_kv = MockCheckpointKvStore::new();
1806        mock_kv.expect_multi_get_checkpoints().times(2).returning(
1807            move |summaries, contents, _by_digest| {
1808                // Summaries survive pruning for every requested sequence.
1809                let summaries = summaries
1810                    .iter()
1811                    .map(|seq| Some(test_certified_summary(*seq, &store_contents[seq])))
1812                    .collect();
1813                // Contents are missing for the pruned sequence only.
1814                let contents = contents
1815                    .iter()
1816                    .map(|seq| (*seq != pruned_seq).then(|| store_contents[seq].clone()))
1817                    .collect();
1818                Ok((summaries, contents, vec![]))
1819            },
1820        );
1821
1822        let state: Arc<dyn StateRead> = Arc::new(mock_state);
1823        let kv_store = Arc::new(TransactionKeyValueStore::new(
1824            "test",
1825            KeyValueStoreMetrics::new_for_tests(),
1826            Arc::new(mock_kv),
1827        ));
1828
1829        // cursor = 9, ascending, limit 4 => requested sequences [10, 11, 12, 13].
1830        let checkpoints = ReadApi::get_checkpoints_internal(state, kv_store, Some(9), 4, false)
1831            .await
1832            .unwrap();
1833
1834        // The pruned sequence is omitted; every other sequence is returned once.
1835        assert_eq!(
1836            checkpoints
1837                .iter()
1838                .map(|c| c.sequence_number)
1839                .collect::<Vec<_>>(),
1840            vec![10, 12, 13],
1841        );
1842
1843        // Crucially, each returned checkpoint still carries the transactions of
1844        // its own sequence number rather than a neighbor's.
1845        for checkpoint in &checkpoints {
1846            let expected: Vec<TransactionDigest> = all_contents[&checkpoint.sequence_number]
1847                .iter()
1848                .map(|digests| digests.transaction)
1849                .collect();
1850            assert_eq!(
1851                checkpoint.transactions, expected,
1852                "checkpoint {} was paired with another checkpoint's contents",
1853                checkpoint.sequence_number,
1854            );
1855        }
1856    }
1857}