sui_json_rpc/
authority_state.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use arc_swap::Guard;
5use async_trait::async_trait;
6use move_core_types::language_storage::TypeTag;
7use std::collections::{BTreeMap, HashMap};
8use std::sync::Arc;
9use sui_core::accumulators::balances::{get_all_balances_for_owner, get_balance};
10use sui_core::authority::AuthorityState;
11use sui_core::authority::authority_per_epoch_store::AuthorityPerEpochStore;
12use sui_core::execution_cache::ObjectCacheRead;
13use sui_core::jsonrpc_index::{CoinIndexKey2, CoinInfo, TotalBalance};
14use sui_core::subscription_handler::SubscriptionHandler;
15use sui_json_rpc_types::{
16    Coin as SuiCoin, DevInspectResults, DryRunTransactionBlockResponse, EventFilter, SuiEvent,
17    SuiObjectDataFilter, TransactionFilter,
18};
19use sui_storage::key_value_store::{
20    KVStoreTransactionData, TransactionKeyValueStore, TransactionKeyValueStoreTrait,
21};
22use sui_types::accumulator_root::AccumulatorKey;
23use sui_types::balance::Balance;
24use sui_types::base_types::{
25    MoveObjectType, ObjectID, ObjectInfo, ObjectRef, SequenceNumber, SuiAddress,
26};
27use sui_types::bridge::Bridge;
28use sui_types::coin_reservation;
29use sui_types::committee::{Committee, EpochId};
30use sui_types::digests::{ChainIdentifier, TransactionDigest};
31use sui_types::dynamic_field::DynamicFieldInfo;
32use sui_types::effects::TransactionEffects;
33use sui_types::error::{SuiError, SuiErrorKind, SuiResult, UserInputError};
34use sui_types::event::EventID;
35use sui_types::governance::StakedSui;
36use sui_types::messages_checkpoint::{
37    CheckpointContents, CheckpointContentsDigest, CheckpointDigest, CheckpointSequenceNumber,
38    VerifiedCheckpoint,
39};
40use sui_types::object::{MoveObject, Object, ObjectRead, Owner, PastObjectRead};
41use sui_types::storage::{BackingPackageStore, ObjectStore, WriteKind};
42use sui_types::sui_serde::BigInt;
43use sui_types::sui_system_state::SuiSystemState;
44use sui_types::transaction::{Transaction, TransactionData, TransactionKind};
45use thiserror::Error;
46use tokio::task::JoinError;
47
48use crate::ObjectProvider;
49#[cfg(test)]
50use mockall::automock;
51use typed_store_error::TypedStoreError;
52
53pub type StateReadResult<T = ()> = Result<T, StateReadError>;
54
55/// Trait for AuthorityState methods commonly used by at least two api.
56#[cfg_attr(test, automock)]
57#[async_trait]
58pub trait StateRead: Send + Sync {
59    async fn multi_get(
60        &self,
61        transactions: &[TransactionDigest],
62        effects: &[TransactionDigest],
63    ) -> StateReadResult<KVStoreTransactionData>;
64
65    fn get_object_read(&self, object_id: &ObjectID) -> StateReadResult<ObjectRead>;
66
67    fn get_past_object_read(
68        &self,
69        object_id: &ObjectID,
70        version: SequenceNumber,
71    ) -> StateReadResult<PastObjectRead>;
72
73    async fn get_object(&self, object_id: &ObjectID) -> StateReadResult<Option<Object>>;
74
75    fn load_epoch_store_one_call_per_task(&self) -> Guard<Arc<AuthorityPerEpochStore>>;
76
77    fn get_dynamic_fields(
78        &self,
79        owner: ObjectID,
80        cursor: Option<ObjectID>,
81        limit: usize,
82    ) -> StateReadResult<Vec<(ObjectID, DynamicFieldInfo)>>;
83
84    fn get_cache_reader(&self) -> &Arc<dyn ObjectCacheRead>;
85
86    fn get_object_store(&self) -> &Arc<dyn ObjectStore + Send + Sync>;
87
88    fn get_backing_package_store(&self) -> &Arc<dyn BackingPackageStore + Send + Sync>;
89
90    fn get_owner_objects(
91        &self,
92        owner: SuiAddress,
93        cursor: Option<ObjectID>,
94        filter: Option<SuiObjectDataFilter>,
95    ) -> StateReadResult<Vec<ObjectInfo>>;
96
97    async fn query_events(
98        &self,
99        kv_store: &Arc<TransactionKeyValueStore>,
100        query: EventFilter,
101        // If `Some`, the query will start from the next item after the specified cursor
102        cursor: Option<EventID>,
103        limit: usize,
104        descending: bool,
105    ) -> StateReadResult<Vec<SuiEvent>>;
106
107    // transaction_execution_api
108    #[allow(clippy::type_complexity)]
109    async fn dry_exec_transaction(
110        &self,
111        transaction: TransactionData,
112    ) -> StateReadResult<(
113        DryRunTransactionBlockResponse,
114        BTreeMap<ObjectID, (ObjectRef, Object, WriteKind)>,
115        TransactionEffects,
116        Option<ObjectID>,
117    )>;
118
119    async fn dev_inspect_transaction_block(
120        &self,
121        sender: SuiAddress,
122        transaction_kind: TransactionKind,
123        gas_price: Option<u64>,
124        gas_budget: Option<u64>,
125        gas_sponsor: Option<SuiAddress>,
126        gas_objects: Option<Vec<ObjectRef>>,
127        show_raw_txn_data_and_effects: Option<bool>,
128        skip_checks: Option<bool>,
129    ) -> StateReadResult<DevInspectResults>;
130
131    // indexer_api
132    fn get_subscription_handler(&self) -> Arc<SubscriptionHandler>;
133
134    fn get_owner_objects_with_limit(
135        &self,
136        owner: SuiAddress,
137        cursor: Option<ObjectID>,
138        limit: usize,
139        filter: Option<SuiObjectDataFilter>,
140    ) -> StateReadResult<Vec<ObjectInfo>>;
141
142    async fn get_transactions(
143        &self,
144        kv_store: &Arc<TransactionKeyValueStore>,
145        filter: Option<TransactionFilter>,
146        cursor: Option<TransactionDigest>,
147        limit: Option<usize>,
148        reverse: bool,
149    ) -> StateReadResult<Vec<TransactionDigest>>;
150
151    fn get_dynamic_field_object_id(
152        &self,
153        owner: ObjectID,
154        name_type: TypeTag,
155        name_bcs_bytes: &[u8],
156    ) -> StateReadResult<Option<ObjectID>>;
157
158    // governance_api
159    async fn get_staked_sui(&self, owner: SuiAddress) -> StateReadResult<Vec<StakedSui>>;
160    fn get_system_state(&self) -> StateReadResult<SuiSystemState>;
161    fn get_or_latest_committee(&self, epoch: Option<BigInt<u64>>) -> StateReadResult<Committee>;
162
163    // bridge_api
164    fn get_bridge(&self) -> StateReadResult<Bridge>;
165
166    // coin_api
167    fn find_publish_txn_digest(&self, package_id: ObjectID) -> StateReadResult<TransactionDigest>;
168    fn get_owned_coins(
169        &self,
170        owner: SuiAddress,
171        cursor: (String, u64, ObjectID),
172        limit: usize,
173        one_coin_type_only: bool,
174    ) -> StateReadResult<Vec<SuiCoin>>;
175    async fn get_executed_transaction_and_effects(
176        &self,
177        digest: TransactionDigest,
178        kv_store: Arc<TransactionKeyValueStore>,
179    ) -> StateReadResult<(Transaction, TransactionEffects)>;
180    async fn get_balance(
181        &self,
182        owner: SuiAddress,
183        coin_type: TypeTag,
184    ) -> StateReadResult<TotalBalance>;
185    async fn get_all_balance(
186        &self,
187        owner: SuiAddress,
188    ) -> StateReadResult<Arc<HashMap<TypeTag, TotalBalance>>>;
189
190    // read_api
191    fn get_verified_checkpoint_by_sequence_number(
192        &self,
193        sequence_number: CheckpointSequenceNumber,
194    ) -> StateReadResult<VerifiedCheckpoint>;
195
196    fn get_checkpoint_contents(
197        &self,
198        digest: CheckpointContentsDigest,
199    ) -> StateReadResult<CheckpointContents>;
200
201    fn get_verified_checkpoint_summary_by_digest(
202        &self,
203        digest: CheckpointDigest,
204    ) -> StateReadResult<VerifiedCheckpoint>;
205
206    fn deprecated_multi_get_transaction_checkpoint(
207        &self,
208        digests: &[TransactionDigest],
209    ) -> StateReadResult<Vec<Option<(EpochId, CheckpointSequenceNumber)>>>;
210
211    fn deprecated_get_transaction_checkpoint(
212        &self,
213        digest: &TransactionDigest,
214    ) -> StateReadResult<Option<(EpochId, CheckpointSequenceNumber)>>;
215
216    fn multi_get_checkpoint_by_sequence_number(
217        &self,
218        sequence_numbers: &[CheckpointSequenceNumber],
219    ) -> StateReadResult<Vec<Option<VerifiedCheckpoint>>>;
220
221    fn get_total_transaction_blocks(&self) -> StateReadResult<u64>;
222
223    fn get_checkpoint_by_sequence_number(
224        &self,
225        sequence_number: CheckpointSequenceNumber,
226    ) -> StateReadResult<Option<VerifiedCheckpoint>>;
227
228    fn get_latest_checkpoint_sequence_number(&self) -> StateReadResult<CheckpointSequenceNumber>;
229
230    fn get_chain_identifier(&self) -> StateReadResult<ChainIdentifier>;
231}
232
233#[async_trait]
234impl StateRead for AuthorityState {
235    async fn multi_get(
236        &self,
237        transactions: &[TransactionDigest],
238        effects: &[TransactionDigest],
239    ) -> StateReadResult<KVStoreTransactionData> {
240        Ok(
241            <AuthorityState as TransactionKeyValueStoreTrait>::multi_get(
242                self,
243                transactions,
244                effects,
245            )
246            .await?,
247        )
248    }
249
250    fn get_object_read(&self, object_id: &ObjectID) -> StateReadResult<ObjectRead> {
251        let result = self.get_object_read(object_id)?;
252
253        // If object not found and coin reservations are enabled, check if this is a
254        // masked object ID (fake coin request).
255        if let ObjectRead::NotExists(object_id) = result
256            && self
257                .load_epoch_store_one_call_per_task()
258                .protocol_config()
259                .enable_coin_reservation_obj_refs()
260        {
261            let chain_identifier = self.get_chain_identifier();
262            let unmasked_id = coin_reservation::mask_or_unmask_id(object_id, chain_identifier);
263
264            // Try to load the unmasked object (the accumulator)
265            if let ObjectRead::Exists(_, object, _) = self.get_object_read(&unmasked_id)? {
266                let accumulator_version = object.version();
267                let Some(move_object) = object.data.try_as_move() else {
268                    // Not a move object, return original NotExists
269                    return Ok(ObjectRead::NotExists(object_id));
270                };
271                let Some(currency_type) =
272                    move_object.type_().balance_accumulator_field_type_maybe()
273                else {
274                    // Not an accumulator object, return original NotExists
275                    return Ok(ObjectRead::NotExists(object_id));
276                };
277
278                let balance_type = Balance::type_tag(currency_type.clone());
279
280                let (AccumulatorKey { owner }, value) = move_object.try_into()?;
281
282                let Some((object_ref, balance, previous_transaction)) =
283                    self.get_address_balance_coin_info(owner, balance_type)?
284                else {
285                    return Ok(ObjectRead::NotExists(object_id));
286                };
287
288                debug_assert_eq!(balance, value.as_u128().map(|v| v as u64).unwrap_or(0));
289
290                // Create a fake coin object with the masked ID
291                let coin = Object::new_move(
292                    MoveObject::new_coin(currency_type, accumulator_version, object_id, balance),
293                    Owner::AddressOwner(owner),
294                    previous_transaction,
295                );
296
297                let layout = self.get_object_layout(&coin)?;
298                return Ok(ObjectRead::Exists(object_ref, coin, layout));
299            }
300
301            return Ok(ObjectRead::NotExists(object_id));
302        }
303
304        Ok(result)
305    }
306
307    async fn get_object(&self, object_id: &ObjectID) -> StateReadResult<Option<Object>> {
308        Ok(self.get_object(object_id))
309    }
310
311    fn get_past_object_read(
312        &self,
313        object_id: &ObjectID,
314        version: SequenceNumber,
315    ) -> StateReadResult<PastObjectRead> {
316        Ok(self.get_past_object_read(object_id, version)?)
317    }
318
319    fn load_epoch_store_one_call_per_task(&self) -> Guard<Arc<AuthorityPerEpochStore>> {
320        self.load_epoch_store_one_call_per_task()
321    }
322
323    fn get_dynamic_fields(
324        &self,
325        owner: ObjectID,
326        cursor: Option<ObjectID>,
327        limit: usize,
328    ) -> StateReadResult<Vec<(ObjectID, DynamicFieldInfo)>> {
329        Ok(self.get_dynamic_fields(owner, cursor, limit)?)
330    }
331
332    fn get_cache_reader(&self) -> &Arc<dyn ObjectCacheRead> {
333        self.get_object_cache_reader()
334    }
335
336    fn get_object_store(&self) -> &Arc<dyn ObjectStore + Send + Sync> {
337        self.get_object_store()
338    }
339
340    fn get_backing_package_store(&self) -> &Arc<dyn BackingPackageStore + Send + Sync> {
341        self.get_backing_package_store()
342    }
343
344    fn get_owner_objects(
345        &self,
346        owner: SuiAddress,
347        cursor: Option<ObjectID>,
348        filter: Option<SuiObjectDataFilter>,
349    ) -> StateReadResult<Vec<ObjectInfo>> {
350        Ok(self
351            .get_owner_objects_iterator(owner, cursor, filter)?
352            .collect())
353    }
354
355    async fn query_events(
356        &self,
357        kv_store: &Arc<TransactionKeyValueStore>,
358        query: EventFilter,
359        // If `Some`, the query will start from the next item after the specified cursor
360        cursor: Option<EventID>,
361        limit: usize,
362        descending: bool,
363    ) -> StateReadResult<Vec<SuiEvent>> {
364        Ok(self
365            .query_events(kv_store, query, cursor, limit, descending)
366            .await?)
367    }
368
369    #[allow(clippy::type_complexity)]
370    async fn dry_exec_transaction(
371        &self,
372        transaction: TransactionData,
373    ) -> StateReadResult<(
374        DryRunTransactionBlockResponse,
375        BTreeMap<ObjectID, (ObjectRef, Object, WriteKind)>,
376        TransactionEffects,
377        Option<ObjectID>,
378    )> {
379        Ok(self.dry_exec_transaction(transaction).await?)
380    }
381
382    async fn dev_inspect_transaction_block(
383        &self,
384        sender: SuiAddress,
385        transaction_kind: TransactionKind,
386        gas_price: Option<u64>,
387        gas_budget: Option<u64>,
388        gas_sponsor: Option<SuiAddress>,
389        gas_objects: Option<Vec<ObjectRef>>,
390        show_raw_txn_data_and_effects: Option<bool>,
391        skip_checks: Option<bool>,
392    ) -> StateReadResult<DevInspectResults> {
393        Ok(self
394            .dev_inspect_transaction_block(
395                sender,
396                transaction_kind,
397                gas_price,
398                gas_budget,
399                gas_sponsor,
400                gas_objects,
401                show_raw_txn_data_and_effects,
402                skip_checks,
403            )
404            .await?)
405    }
406
407    fn get_subscription_handler(&self) -> Arc<SubscriptionHandler> {
408        self.subscription_handler.clone()
409    }
410
411    fn get_owner_objects_with_limit(
412        &self,
413        owner: SuiAddress,
414        cursor: Option<ObjectID>,
415        limit: usize,
416        filter: Option<SuiObjectDataFilter>,
417    ) -> StateReadResult<Vec<ObjectInfo>> {
418        Ok(self.get_owner_objects(owner, cursor, limit, filter)?)
419    }
420
421    async fn get_transactions(
422        &self,
423        kv_store: &Arc<TransactionKeyValueStore>,
424        filter: Option<TransactionFilter>,
425        cursor: Option<TransactionDigest>,
426        limit: Option<usize>,
427        reverse: bool,
428    ) -> StateReadResult<Vec<TransactionDigest>> {
429        Ok(self
430            .get_transactions(kv_store, filter, cursor, limit, reverse)
431            .await?)
432    }
433
434    fn get_dynamic_field_object_id(
435        // indexer
436        &self,
437        owner: ObjectID,
438        name_type: TypeTag,
439        name_bcs_bytes: &[u8],
440    ) -> StateReadResult<Option<ObjectID>> {
441        Ok(self.get_dynamic_field_object_id(owner, name_type, name_bcs_bytes)?)
442    }
443
444    async fn get_staked_sui(&self, owner: SuiAddress) -> StateReadResult<Vec<StakedSui>> {
445        Ok(self.get_move_objects(owner, MoveObjectType::staked_sui())?)
446    }
447    fn get_system_state(&self) -> StateReadResult<SuiSystemState> {
448        Ok(self
449            .get_object_cache_reader()
450            .get_sui_system_state_object_unsafe()?)
451    }
452    fn get_or_latest_committee(&self, epoch: Option<BigInt<u64>>) -> StateReadResult<Committee> {
453        Ok(self
454            .committee_store()
455            .get_or_latest_committee(epoch.map(|e| *e))?)
456    }
457
458    fn get_bridge(&self) -> StateReadResult<Bridge> {
459        self.get_cache_reader()
460            .get_bridge_object_unsafe()
461            .map_err(|err| err.into())
462    }
463
464    fn find_publish_txn_digest(&self, package_id: ObjectID) -> StateReadResult<TransactionDigest> {
465        Ok(self.find_publish_txn_digest(package_id)?)
466    }
467    fn get_owned_coins(
468        &self,
469        owner: SuiAddress,
470        cursor: (String, u64, ObjectID),
471        limit: usize,
472        one_coin_type_only: bool,
473    ) -> StateReadResult<Vec<SuiCoin>> {
474        // Ordering per coin type: [real[0], fake, real[1], real[2], ...]
475        // The fake coin (address balance) is always at position 1 within its type.
476
477        fn to_sui_coin(key: CoinIndexKey2, info: CoinInfo) -> SuiCoin {
478            SuiCoin {
479                coin_type: key.coin_type,
480                coin_object_id: key.object_id,
481                version: info.version,
482                digest: info.digest,
483                balance: info.balance,
484                previous_transaction: info.previous_transaction,
485            }
486        }
487
488        fn obj_ref_to_sui_coin(
489            coin_type: String,
490            obj_ref: ObjectRef,
491            balance: u64,
492            previous_transaction: TransactionDigest,
493        ) -> SuiCoin {
494            SuiCoin {
495                coin_type,
496                coin_object_id: obj_ref.0,
497                version: obj_ref.1,
498                digest: obj_ref.2,
499                balance,
500                previous_transaction,
501            }
502        }
503
504        // Build fake coins map (only when coin reservations are enabled).
505        let coin_reservations_enabled = self
506            .load_epoch_store_one_call_per_task()
507            .protocol_config()
508            .enable_coin_reservation_obj_refs();
509
510        let fake_coins: HashMap<String, SuiCoin> = if !coin_reservations_enabled {
511            HashMap::new()
512        } else if one_coin_type_only {
513            let balance_type_tag = sui_types::parse_sui_type_tag(&cursor.0)
514                .map_err(|e| anyhow::anyhow!("Invalid coin type: {} - {}", cursor.0, e))?;
515            let balance_type = Balance::type_tag(balance_type_tag);
516            self.get_address_balance_coin_info(owner, balance_type)?
517                .map(|(obj_ref, balance, prev_tx)| {
518                    HashMap::from([(
519                        cursor.0.clone(),
520                        obj_ref_to_sui_coin(cursor.0.clone(), obj_ref, balance, prev_tx),
521                    )])
522                })
523                .unwrap_or_default()
524        } else {
525            self.get_all_address_balance_coin_infos(owner)?
526                .into_iter()
527                .map(|(coin_type, (obj_ref, balance, prev_tx))| {
528                    (
529                        coin_type.clone(),
530                        obj_ref_to_sui_coin(coin_type, obj_ref, balance, prev_tx),
531                    )
532                })
533                .collect()
534        };
535
536        // Determine cursor state.
537        let cursor_at_fake = fake_coins.values().any(|c| c.coin_object_id == cursor.2);
538
539        // If cursor is at fake coin, reset to start of that type and skip real[0].
540        let (real_cursor, skip_first_real) = if cursor_at_fake {
541            ((cursor.0.clone(), 0, ObjectID::ZERO), true)
542        } else {
543            (cursor.clone(), false)
544        };
545
546        let real_coins_iter = self.get_owned_coins_iterator_with_cursor(
547            owner,
548            real_cursor.clone(),
549            limit + 1,
550            one_coin_type_only,
551        )?;
552
553        // Track which types have had their fake coin emitted.
554        let mut fake_emitted: HashMap<String, bool> = HashMap::new();
555
556        // If cursor is a real coin, check if we're past the fake coin slot.
557        // The fake coin is at position 1 (after first real). So if cursor is the
558        // first real coin, fake hasn't been emitted. If cursor is any later real
559        // coin, fake was already emitted.
560        let mut emit_fake_before_reals = false;
561        if cursor.2 != ObjectID::ZERO && !cursor_at_fake && fake_coins.contains_key(&cursor.0) {
562            // Check if cursor is the first real coin by querying from the start
563            let first_real_id = self
564                .get_owned_coins_iterator_with_cursor(
565                    owner,
566                    (cursor.0.clone(), 0, ObjectID::ZERO),
567                    1,
568                    one_coin_type_only,
569                )?
570                .next()
571                .map(|(k, _)| k.object_id);
572
573            if first_real_id == Some(cursor.2) {
574                // Cursor is at first real coin, fake should be emitted next
575                emit_fake_before_reals = true;
576            } else {
577                // Cursor is past first real coin, fake was already emitted
578                fake_emitted.insert(cursor.0.clone(), true);
579            }
580        }
581
582        let mut result = Vec::with_capacity(limit);
583
584        // If cursor is at first real coin, emit fake before continuing with more reals
585        if emit_fake_before_reals && let Some(fake) = fake_coins.get(&cursor.0) {
586            result.push(fake.clone());
587            fake_emitted.insert(cursor.0.clone(), true);
588        }
589
590        let mut seen_first_real: HashMap<String, bool> = HashMap::new();
591        let mut skipped_first = false;
592
593        for (key, info) in real_coins_iter {
594            if result.len() >= limit {
595                break;
596            }
597
598            let coin = to_sui_coin(key, info);
599            let coin_type = &coin.coin_type;
600            let is_first_real = !seen_first_real.get(coin_type).copied().unwrap_or(false);
601
602            // Skip first real coin when resuming from a fake coin cursor.
603            if skip_first_real && !skipped_first && coin_type == &real_cursor.0 {
604                skipped_first = true;
605                seen_first_real.insert(coin_type.clone(), true);
606                continue;
607            }
608
609            // Emit the real coin.
610            result.push(coin.clone());
611            seen_first_real.insert(coin_type.clone(), true);
612
613            // After first real coin of a type, emit its fake coin (if not already emitted).
614            if is_first_real && !fake_emitted.get(coin_type).copied().unwrap_or(false) {
615                if let Some(fake) = fake_coins.get(coin_type)
616                    && result.len() < limit
617                {
618                    result.push(fake.clone());
619                }
620                fake_emitted.insert(coin_type.clone(), true);
621            }
622        }
623
624        // Emit any fake coins for types that had no real coins.
625        // Only do this on the first page (cursor at start) to avoid duplicates.
626        if cursor.2 == ObjectID::ZERO {
627            for (coin_type, fake) in fake_coins {
628                if result.len() >= limit {
629                    break;
630                }
631                if !fake_emitted.get(&coin_type).copied().unwrap_or(false) {
632                    result.push(fake);
633                }
634            }
635        }
636
637        Ok(result)
638    }
639
640    async fn get_executed_transaction_and_effects(
641        &self,
642        digest: TransactionDigest,
643        kv_store: Arc<TransactionKeyValueStore>,
644    ) -> StateReadResult<(Transaction, TransactionEffects)> {
645        Ok(self
646            .get_executed_transaction_and_effects(digest, kv_store)
647            .await?)
648    }
649
650    async fn get_balance(
651        &self,
652        owner: SuiAddress,
653        coin_type: TypeTag,
654    ) -> StateReadResult<TotalBalance> {
655        let indexes = self.indexes.clone();
656        let child_object_resolver = self.get_child_object_resolver().clone();
657        Ok(
658            tokio::task::spawn_blocking(move || -> SuiResult<TotalBalance> {
659                let address_balance =
660                    get_balance(owner, child_object_resolver.as_ref(), coin_type.clone())?;
661                let coin_balance = indexes
662                    .as_ref()
663                    .ok_or(SuiErrorKind::IndexStoreNotAvailable)?
664                    .get_coin_object_balance(owner, coin_type)?;
665                let mut total_balance = coin_balance;
666                if address_balance > 0 {
667                    total_balance.balance += address_balance as i128;
668                    total_balance.num_coins += 1;
669                }
670                total_balance.address_balance = address_balance;
671                Ok(total_balance)
672            })
673            .await
674            .map_err(|e: JoinError| {
675                SuiError(Box::new(SuiErrorKind::ExecutionError(e.to_string())))
676            })??,
677        )
678    }
679
680    async fn get_all_balance(
681        &self,
682        owner: SuiAddress,
683    ) -> StateReadResult<Arc<HashMap<TypeTag, TotalBalance>>> {
684        let indexes = self.indexes.clone();
685        let child_object_resolver = self.get_child_object_resolver().clone();
686        Ok(tokio::task::spawn_blocking(
687            move || -> SuiResult<Arc<HashMap<TypeTag, TotalBalance>>> {
688                let indexes = indexes
689                    .as_ref()
690                    .ok_or(SuiErrorKind::IndexStoreNotAvailable)?;
691                let address_balances =
692                    get_all_balances_for_owner(owner, child_object_resolver.as_ref(), indexes)?;
693                let coin_balances = (*indexes.get_all_coin_object_balances(owner)?).clone();
694                let mut all_balances = coin_balances;
695                for (coin_type, balance) in address_balances {
696                    let existing_balance = all_balances.entry(coin_type).or_insert(TotalBalance {
697                        balance: 0,
698                        num_coins: 0,
699                        address_balance: 0,
700                    });
701                    existing_balance.balance += balance as i128;
702                    existing_balance.num_coins += 1;
703                    existing_balance.address_balance = balance;
704                }
705                Ok(Arc::new(all_balances))
706            },
707        )
708        .await
709        .map_err(|e: JoinError| {
710            SuiError(Box::new(SuiErrorKind::ExecutionError(e.to_string())))
711        })??)
712    }
713
714    fn get_verified_checkpoint_by_sequence_number(
715        &self,
716        sequence_number: CheckpointSequenceNumber,
717    ) -> StateReadResult<VerifiedCheckpoint> {
718        Ok(self.get_verified_checkpoint_by_sequence_number(sequence_number)?)
719    }
720
721    fn get_checkpoint_contents(
722        &self,
723        digest: CheckpointContentsDigest,
724    ) -> StateReadResult<CheckpointContents> {
725        Ok(self.get_checkpoint_contents(digest)?)
726    }
727
728    fn get_verified_checkpoint_summary_by_digest(
729        &self,
730        digest: CheckpointDigest,
731    ) -> StateReadResult<VerifiedCheckpoint> {
732        Ok(self.get_verified_checkpoint_summary_by_digest(digest)?)
733    }
734
735    fn deprecated_multi_get_transaction_checkpoint(
736        &self,
737        digests: &[TransactionDigest],
738    ) -> StateReadResult<Vec<Option<(EpochId, CheckpointSequenceNumber)>>> {
739        Ok(self
740            .get_checkpoint_cache()
741            .deprecated_multi_get_transaction_checkpoint(digests))
742    }
743
744    fn deprecated_get_transaction_checkpoint(
745        &self,
746        digest: &TransactionDigest,
747    ) -> StateReadResult<Option<(EpochId, CheckpointSequenceNumber)>> {
748        Ok(self
749            .get_checkpoint_cache()
750            .deprecated_get_transaction_checkpoint(digest))
751    }
752
753    fn multi_get_checkpoint_by_sequence_number(
754        &self,
755        sequence_numbers: &[CheckpointSequenceNumber],
756    ) -> StateReadResult<Vec<Option<VerifiedCheckpoint>>> {
757        Ok(self.multi_get_checkpoint_by_sequence_number(sequence_numbers)?)
758    }
759
760    fn get_total_transaction_blocks(&self) -> StateReadResult<u64> {
761        Ok(self.get_total_transaction_blocks()?)
762    }
763
764    fn get_checkpoint_by_sequence_number(
765        &self,
766        sequence_number: CheckpointSequenceNumber,
767    ) -> StateReadResult<Option<VerifiedCheckpoint>> {
768        Ok(self.get_checkpoint_by_sequence_number(sequence_number)?)
769    }
770
771    fn get_latest_checkpoint_sequence_number(&self) -> StateReadResult<CheckpointSequenceNumber> {
772        Ok(self.get_latest_checkpoint_sequence_number()?)
773    }
774
775    fn get_chain_identifier(&self) -> StateReadResult<ChainIdentifier> {
776        Ok(self.get_chain_identifier())
777    }
778}
779
780/// This implementation allows `S` to be a dynamically sized type (DST) that implements ObjectProvider
781/// Valid as `S` is referenced only, and memory management is handled by `Arc`
782#[async_trait]
783impl<S: ?Sized + StateRead> ObjectProvider for Arc<S> {
784    type Error = StateReadError;
785
786    async fn get_object(
787        &self,
788        id: &ObjectID,
789        version: &SequenceNumber,
790    ) -> Result<Object, Self::Error> {
791        Ok(self.get_past_object_read(id, *version)?.into_object()?)
792    }
793
794    async fn find_object_lt_or_eq_version(
795        &self,
796        id: &ObjectID,
797        version: &SequenceNumber,
798    ) -> Result<Option<Object>, Self::Error> {
799        Ok(self
800            .get_cache_reader()
801            .find_object_lt_or_eq_version(*id, *version))
802    }
803}
804
805#[async_trait]
806impl<S: ?Sized + StateRead> ObjectProvider for (Arc<S>, Arc<TransactionKeyValueStore>) {
807    type Error = StateReadError;
808
809    async fn get_object(
810        &self,
811        id: &ObjectID,
812        version: &SequenceNumber,
813    ) -> Result<Object, Self::Error> {
814        let object_read = self.0.get_past_object_read(id, *version)?;
815        match object_read {
816            PastObjectRead::ObjectNotExists(_) | PastObjectRead::VersionNotFound(..) => {
817                match self.1.get_object(*id, *version).await? {
818                    Some(object) => Ok(object),
819                    None => Ok(PastObjectRead::VersionNotFound(*id, *version).into_object()?),
820                }
821            }
822            _ => Ok(object_read.into_object()?),
823        }
824    }
825
826    async fn find_object_lt_or_eq_version(
827        &self,
828        id: &ObjectID,
829        version: &SequenceNumber,
830    ) -> Result<Option<Object>, Self::Error> {
831        Ok(self
832            .0
833            .get_cache_reader()
834            .find_object_lt_or_eq_version(*id, *version))
835    }
836}
837
838#[derive(Debug, Error)]
839pub enum StateReadInternalError {
840    #[error(transparent)]
841    SuiError(#[from] SuiError),
842    #[error(transparent)]
843    JoinError(#[from] JoinError),
844    #[error(transparent)]
845    Anyhow(#[from] anyhow::Error),
846}
847
848impl From<SuiErrorKind> for StateReadInternalError {
849    fn from(e: SuiErrorKind) -> Self {
850        StateReadInternalError::SuiError(SuiError::from(e))
851    }
852}
853
854#[derive(Debug, Error)]
855pub enum StateReadClientError {
856    #[error(transparent)]
857    SuiError(#[from] SuiError),
858    #[error(transparent)]
859    UserInputError(#[from] UserInputError),
860}
861
862impl From<SuiErrorKind> for StateReadClientError {
863    fn from(e: SuiErrorKind) -> Self {
864        StateReadClientError::SuiError(SuiError::from(e))
865    }
866}
867
868/// `StateReadError` is the error type for callers to work with.
869/// It captures all possible errors that can occur while reading state, classifying them into two categories.
870/// Unless `StateReadError` is the final error state before returning to caller, the app may still want error context.
871/// This context is preserved in `Internal` and `Client` variants.
872#[derive(Debug, Error)]
873pub enum StateReadError {
874    // sui_json_rpc::Error will do the final conversion to generic error message
875    #[error(transparent)]
876    Internal(#[from] StateReadInternalError),
877
878    // Client errors
879    #[error(transparent)]
880    Client(#[from] StateReadClientError),
881}
882
883impl From<SuiErrorKind> for StateReadError {
884    fn from(e: SuiErrorKind) -> Self {
885        match e {
886            SuiErrorKind::IndexStoreNotAvailable
887            | SuiErrorKind::TransactionNotFound { .. }
888            | SuiErrorKind::UnsupportedFeatureError { .. }
889            | SuiErrorKind::UserInputError { .. }
890            | SuiErrorKind::WrongMessageVersion { .. } => StateReadError::Client(e.into()),
891            _ => StateReadError::Internal(e.into()),
892        }
893    }
894}
895
896impl From<SuiError> for StateReadError {
897    fn from(e: SuiError) -> Self {
898        e.into_inner().into()
899    }
900}
901
902impl From<UserInputError> for StateReadError {
903    fn from(e: UserInputError) -> Self {
904        StateReadError::Client(e.into())
905    }
906}
907
908impl From<JoinError> for StateReadError {
909    fn from(e: JoinError) -> Self {
910        StateReadError::Internal(e.into())
911    }
912}
913
914impl From<anyhow::Error> for StateReadError {
915    fn from(e: anyhow::Error) -> Self {
916        StateReadError::Internal(e.into())
917    }
918}
919
920impl From<TypedStoreError> for StateReadError {
921    fn from(e: TypedStoreError) -> Self {
922        let error: SuiError = e.into();
923        StateReadError::Internal(error.into())
924    }
925}