1use 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
75const DEFAULT_MAX_DISPLAY_MOVE_VALUE_DEPTH: usize = 32;
77
78const DEFAULT_MAX_DISPLAY_OUTPUT_SIZE: usize = 1024 * 1024;
80
81static 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
95static 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
109static 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
123static 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
137static 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#[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#[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 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 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 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 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 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 .unique()
393 .collect::<Vec<CheckpointSequenceNumber>>();
394
395 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 let checkpoint_to_timestamp = unique_checkpoint_numbers
409 .into_iter()
410 .zip_debug_eq(timestamps)
411 .collect::<HashMap<_, _>>();
412
413 for (_, cache_entry) in temp_response.iter_mut() {
415 if cache_entry.checkpoint_seq.is_some() {
416 cache_entry.timestamp = *checkpoint_to_timestamp
418 .get(cache_entry.checkpoint_seq.as_ref().unwrap())
419 .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 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 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 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 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 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 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 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()) } 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()) })
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 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 if opts.require_input() {
888 temp_response.transaction = Some(transaction);
889 }
890
891 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 .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 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 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 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 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 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 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 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 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 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 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 #[tokio::test]
1786 async fn test_get_checkpoints_internal_preserves_alignment_across_pruned_contents() {
1787 let max_checkpoint: CheckpointSequenceNumber = 13;
1788 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 let summaries = summaries
1810 .iter()
1811 .map(|seq| Some(test_certified_summary(*seq, &store_contents[seq])))
1812 .collect();
1813 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 let checkpoints = ReadApi::get_checkpoints_internal(state, kv_store, Some(9), 4, false)
1831 .await
1832 .unwrap();
1833
1834 assert_eq!(
1836 checkpoints
1837 .iter()
1838 .map(|c| c.sequence_number)
1839 .collect::<Vec<_>>(),
1840 vec![10, 12, 13],
1841 );
1842
1843 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}