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