Skip to main content

sui_core/
jsonrpc_index.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! IndexStore supports creation of various ancillary indexes of state in SuiDataStore.
5//! The main user of this data is the explorer.
6
7use std::cmp::{max, min};
8use std::collections::{BTreeMap, HashMap, HashSet};
9use std::path::{Path, PathBuf};
10use std::sync::Arc;
11use std::sync::atomic::{AtomicU64, Ordering};
12
13use bincode::Options;
14use itertools::Itertools;
15use move_core_types::language_storage::{ModuleId, StructTag, TypeTag};
16use mysten_common::ZipDebugEqIteratorExt;
17use parking_lot::ArcMutexGuard;
18use prometheus::{
19    IntCounter, IntCounterVec, Registry, register_int_counter_vec_with_registry,
20    register_int_counter_with_registry,
21};
22use serde::{Deserialize, Serialize, de::DeserializeOwned};
23use sui_types::accumulator_event::AccumulatorEvent;
24use typed_store::TypedStoreError;
25use typed_store::rocksdb::compaction_filter::Decision;
26
27use sui_json_rpc_types::{SuiObjectDataFilter, TransactionFilter};
28use sui_storage::mutex_table::MutexTable;
29use sui_storage::sharded_lru::ShardedLruCache;
30use sui_types::base_types::{
31    ObjectDigest, ObjectID, SequenceNumber, SuiAddress, TransactionDigest, TxSequenceNumber,
32};
33use sui_types::base_types::{ObjectInfo, ObjectRef};
34use sui_types::digests::TransactionEventsDigest;
35use sui_types::dynamic_field::{self, DynamicFieldInfo};
36use sui_types::effects::TransactionEvents;
37use sui_types::error::{SuiError, SuiErrorKind, SuiResult, UserInputError};
38use sui_types::inner_temporary_store::TxCoins;
39use sui_types::object::{Object, Owner};
40use sui_types::parse_sui_struct_tag;
41use sui_types::storage::error::Error as StorageError;
42use tracing::{debug, info, instrument, trace};
43use typed_store::DBMapUtils;
44use typed_store::rocks::{
45    DBBatch, DBMap, DBMapTableConfigMap, DBOptions, MetricConf, StagedBatch, default_db_options,
46    read_size_from_env,
47};
48use typed_store::traits::Map;
49
50type OwnerIndexKey = (SuiAddress, ObjectID);
51type DynamicFieldKey = (ObjectID, ObjectID);
52type EventId = (TxSequenceNumber, usize);
53type EventIndex = (TransactionEventsDigest, TransactionDigest, u64);
54type AllBalance = HashMap<TypeTag, TotalBalance>;
55
56#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug)]
57pub struct CoinIndexKey2 {
58    pub owner: SuiAddress,
59    pub coin_type: String,
60    // the balance of the coin inverted `!coin.balance` in order to force sorting of coins to be
61    // from greatest to least
62    pub inverted_balance: u64,
63    pub object_id: ObjectID,
64}
65
66impl CoinIndexKey2 {
67    pub fn new_from_cursor(
68        owner: SuiAddress,
69        coin_type: String,
70        inverted_balance: u64,
71        object_id: ObjectID,
72    ) -> Self {
73        Self {
74            owner,
75            coin_type,
76            inverted_balance,
77            object_id,
78        }
79    }
80
81    pub fn new(owner: SuiAddress, coin_type: String, balance: u64, object_id: ObjectID) -> Self {
82        Self {
83            owner,
84            coin_type,
85            inverted_balance: !balance,
86            object_id,
87        }
88    }
89}
90
91const CURRENT_DB_VERSION: u64 = 0;
92const _CURRENT_COIN_INDEX_VERSION: u64 = 1;
93
94#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
95struct MetadataInfo {
96    /// Version of the Database
97    version: u64,
98    /// Version of each of the column families
99    ///
100    /// This is used to version individual column families to determine if a CF needs to be
101    /// (re)initialized on startup.
102    column_families: BTreeMap<String, ColumnFamilyInfo>,
103}
104
105#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
106struct ColumnFamilyInfo {
107    version: u64,
108}
109
110pub const MAX_TX_RANGE_SIZE: u64 = 4096;
111
112pub const MAX_GET_OWNED_OBJECT_SIZE: usize = 256;
113const ENV_VAR_COIN_INDEX_BLOCK_CACHE_SIZE_MB: &str = "COIN_INDEX_BLOCK_CACHE_MB";
114const ENV_VAR_DISABLE_INDEX_CACHE: &str = "DISABLE_INDEX_CACHE";
115const ENV_VAR_INVALIDATE_INSTEAD_OF_UPDATE: &str = "INVALIDATE_INSTEAD_OF_UPDATE";
116
117#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
118pub struct TotalBalance {
119    pub balance: i128,
120    pub num_coins: i64,
121    pub address_balance: u64,
122}
123
124#[derive(Debug)]
125pub struct ObjectIndexChanges {
126    pub deleted_owners: Vec<OwnerIndexKey>,
127    pub deleted_dynamic_fields: Vec<DynamicFieldKey>,
128    pub new_owners: Vec<(OwnerIndexKey, ObjectInfo)>,
129    pub new_dynamic_fields: Vec<(DynamicFieldKey, DynamicFieldInfo)>,
130}
131
132#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
133pub struct CoinInfo {
134    pub version: SequenceNumber,
135    pub digest: ObjectDigest,
136    pub balance: u64,
137    pub previous_transaction: TransactionDigest,
138}
139
140impl CoinInfo {
141    pub fn from_object(object: &Object) -> Option<CoinInfo> {
142        object.as_coin_maybe().map(|coin| CoinInfo {
143            version: object.version(),
144            digest: object.digest(),
145            previous_transaction: object.previous_transaction,
146            balance: coin.value(),
147        })
148    }
149}
150
151pub struct IndexStoreMetrics {
152    balance_lookup_from_db: IntCounter,
153    balance_lookup_from_total: IntCounter,
154    all_balance_lookup_from_db: IntCounter,
155    all_balance_lookup_from_total: IntCounter,
156}
157
158impl IndexStoreMetrics {
159    pub fn new(registry: &Registry) -> IndexStoreMetrics {
160        Self {
161            balance_lookup_from_db: register_int_counter_with_registry!(
162                "balance_lookup_from_db",
163                "Total number of balance requests served from cache",
164                registry,
165            )
166            .unwrap(),
167            balance_lookup_from_total: register_int_counter_with_registry!(
168                "balance_lookup_from_total",
169                "Total number of balance requests served ",
170                registry,
171            )
172            .unwrap(),
173            all_balance_lookup_from_db: register_int_counter_with_registry!(
174                "all_balance_lookup_from_db",
175                "Total number of all balance requests served from cache",
176                registry,
177            )
178            .unwrap(),
179            all_balance_lookup_from_total: register_int_counter_with_registry!(
180                "all_balance_lookup_from_total",
181                "Total number of all balance requests served",
182                registry,
183            )
184            .unwrap(),
185        }
186    }
187}
188
189pub struct IndexStoreCaches {
190    per_coin_type_balance: ShardedLruCache<(SuiAddress, TypeTag), SuiResult<TotalBalance>>,
191    all_balances: ShardedLruCache<SuiAddress, SuiResult<Arc<HashMap<TypeTag, TotalBalance>>>>,
192    pub locks: MutexTable<SuiAddress>,
193}
194
195type OwnedMutexGuard<T> = ArcMutexGuard<parking_lot::RawMutex, T>;
196
197/// Cache updates with optional locks held. Returned from `index_tx`.
198/// In sync mode, locks are acquired and held until the batch is committed.
199/// In async mode, locks are None and this is converted to `IndexStoreCacheUpdates`
200/// via `into_inner()` before sending across threads.
201pub struct IndexStoreCacheUpdatesWithLocks {
202    pub(crate) _locks: Option<Vec<OwnedMutexGuard<()>>>,
203    pub(crate) inner: IndexStoreCacheUpdates,
204}
205
206impl IndexStoreCacheUpdatesWithLocks {
207    pub fn into_inner(self) -> IndexStoreCacheUpdates {
208        self.inner
209    }
210}
211
212/// Send-safe cache updates without locks, used in the async post-processing channel
213/// and by `commit_index_batch`.
214#[derive(Default)]
215pub struct IndexStoreCacheUpdates {
216    per_coin_type_balance_changes: Vec<((SuiAddress, TypeTag), SuiResult<TotalBalance>)>,
217    all_balance_changes: Vec<(SuiAddress, SuiResult<Arc<AllBalance>>)>,
218}
219
220#[derive(DBMapUtils)]
221pub struct IndexStoreTables {
222    /// A singleton that store metadata information on the DB.
223    ///
224    /// A few uses for this singleton:
225    /// - determining if the DB has been initialized (as some tables could still be empty post
226    ///   initialization)
227    /// - version of each column family and their respective initialization status
228    meta: DBMap<(), MetadataInfo>,
229
230    /// Index from sui address to transactions initiated by that address.
231    transactions_from_addr: DBMap<(SuiAddress, TxSequenceNumber), TransactionDigest>,
232
233    /// Index from sui address to transactions that were sent to that address.
234    transactions_to_addr: DBMap<(SuiAddress, TxSequenceNumber), TransactionDigest>,
235
236    /// Index from object id to transactions that used that object id as input.
237    #[deprecated]
238    transactions_by_input_object_id: DBMap<(ObjectID, TxSequenceNumber), TransactionDigest>,
239
240    /// Index from object id to transactions that modified/created that object id.
241    #[deprecated]
242    transactions_by_mutated_object_id: DBMap<(ObjectID, TxSequenceNumber), TransactionDigest>,
243
244    /// Index from package id, module and function identifier to transactions that used that moce function call as input.
245    transactions_by_move_function:
246        DBMap<(ObjectID, String, String, TxSequenceNumber), TransactionDigest>,
247
248    /// Ordering of all indexed transactions.
249    transaction_order: DBMap<TxSequenceNumber, TransactionDigest>,
250
251    /// Index from transaction digest to sequence number.
252    transactions_seq: DBMap<TransactionDigest, TxSequenceNumber>,
253
254    /// This is an index of object references to currently existing objects, indexed by the
255    /// composite key of the SuiAddress of their owner and the object ID of the object.
256    /// This composite index allows an efficient iterator to list all objected currently owned
257    /// by a specific user, and their object reference.
258    owner_index: DBMap<OwnerIndexKey, ObjectInfo>,
259
260    coin_index_2: DBMap<CoinIndexKey2, CoinInfo>,
261    // Simple index that just tracks the existance of an address balance for an address.
262    address_balances: DBMap<(SuiAddress, TypeTag), ()>,
263
264    /// This is an index of object references to currently existing dynamic field object, indexed by the
265    /// composite key of the object ID of their parent and the object ID of the dynamic field object.
266    /// This composite index allows an efficient iterator to list all objects currently owned
267    /// by a specific object, and their object reference.
268    dynamic_field_index: DBMap<DynamicFieldKey, DynamicFieldInfo>,
269
270    event_order: DBMap<EventId, EventIndex>,
271    event_by_move_module: DBMap<(ModuleId, EventId), EventIndex>,
272    event_by_move_event: DBMap<(StructTag, EventId), EventIndex>,
273    event_by_event_module: DBMap<(ModuleId, EventId), EventIndex>,
274    event_by_sender: DBMap<(SuiAddress, EventId), EventIndex>,
275    event_by_time: DBMap<(u64, EventId), EventIndex>,
276
277    pruner_watermark: DBMap<(), TxSequenceNumber>,
278}
279
280impl IndexStoreTables {
281    pub fn owner_index(&self) -> &DBMap<OwnerIndexKey, ObjectInfo> {
282        &self.owner_index
283    }
284
285    pub fn coin_index(&self) -> &DBMap<CoinIndexKey2, CoinInfo> {
286        &self.coin_index_2
287    }
288
289    pub fn check_databases_equal(&self, other: &IndexStoreTables) {
290        fn assert_tables_equal<K, V>(name: &str, table_a: &DBMap<K, V>, table_b: &DBMap<K, V>)
291        where
292            K: Serialize + DeserializeOwned + PartialEq + std::fmt::Debug,
293            V: Serialize + DeserializeOwned + PartialEq + std::fmt::Debug,
294        {
295            let mut iter_a = table_a.safe_iter();
296            let mut iter_b = table_b.safe_iter();
297            let mut count = 0u64;
298            loop {
299                match (iter_a.next(), iter_b.next()) {
300                    (Some(a), Some(b)) => {
301                        let (ka, va): (K, V) = a.expect("failed to read from table_a");
302                        let (kb, vb): (K, V) = b.expect("failed to read from table_b");
303                        assert!(
304                            ka == kb && va == vb,
305                            "{name}: mismatch at entry {count}:\n  a=({ka:?}, {va:?})\n  b=({kb:?}, {vb:?})"
306                        );
307                        count += 1;
308                    }
309                    (None, None) => break,
310                    (Some(_), None) => {
311                        panic!(
312                            "{name}: table_a has more entries than table_b (diverged after {count} entries)"
313                        );
314                    }
315                    (None, Some(_)) => {
316                        panic!(
317                            "{name}: table_b has more entries than table_a (diverged after {count} entries)"
318                        );
319                    }
320                }
321            }
322            info!("{name}: verified {count} entries are identical");
323        }
324
325        // Tables keyed by TxSequenceNumber may have different sequence numbers between
326        // async and sync post-processing. Compare the (prefix, value) pairs as sorted
327        // vectors instead of lockstep iteration.
328        // Crash recovery can re-index transactions with new sequence numbers while
329        // old entries remain, producing duplicates. Deduplicate before comparing.
330        fn assert_seq_table_equal<P, V>(
331            name: &str,
332            table_a: &DBMap<(P, TxSequenceNumber), V>,
333            table_b: &DBMap<(P, TxSequenceNumber), V>,
334        ) where
335            P: Serialize + DeserializeOwned + Ord + std::fmt::Debug,
336            V: Serialize + DeserializeOwned + Ord + std::fmt::Debug,
337        {
338            let mut entries_a: Vec<(P, V)> = table_a
339                .safe_iter()
340                .map(|r| {
341                    let ((p, _seq), v) = r.expect("failed to read from table_a");
342                    (p, v)
343                })
344                .collect();
345            let mut entries_b: Vec<(P, V)> = table_b
346                .safe_iter()
347                .map(|r| {
348                    let ((p, _seq), v) = r.expect("failed to read from table_b");
349                    (p, v)
350                })
351                .collect();
352            entries_a.sort();
353            entries_a.dedup();
354            entries_b.sort();
355            entries_b.dedup();
356            assert!(
357                entries_a.len() == entries_b.len(),
358                "{name}: different number of unique entries: {} vs {}",
359                entries_a.len(),
360                entries_b.len()
361            );
362            for (i, (a, b)) in entries_a.iter().zip_debug_eq(entries_b.iter()).enumerate() {
363                assert!(
364                    a == b,
365                    "{name}: mismatch at sorted entry {i}:\n  a={a:?}\n  b={b:?}"
366                );
367            }
368            info!(
369                "{name}: verified {} unique entries match (ignoring sequence numbers)",
370                entries_a.len()
371            );
372        }
373
374        // EventIndex contains a wall-clock timestamp that differs between nodes.
375        // Compare only (TransactionEventsDigest, TransactionDigest).
376        type EventKey = (TransactionEventsDigest, TransactionDigest);
377        fn strip_timestamp(ei: EventIndex) -> EventKey {
378            (ei.0, ei.1)
379        }
380
381        // Tables where the key contains an EventId = (TxSequenceNumber, usize). Compare
382        // the (prefix, event_key) pairs as sorted vectors, ignoring both event ids and
383        // timestamps.
384        fn assert_event_table_equal<P>(
385            name: &str,
386            table_a: &DBMap<(P, EventId), EventIndex>,
387            table_b: &DBMap<(P, EventId), EventIndex>,
388        ) where
389            P: Serialize + DeserializeOwned + Ord + std::fmt::Debug,
390        {
391            let mut entries_a: Vec<(P, EventKey)> = table_a
392                .safe_iter()
393                .map(|r| {
394                    let ((p, _eid), v) = r.expect("failed to read from table_a");
395                    (p, strip_timestamp(v))
396                })
397                .collect();
398            let mut entries_b: Vec<(P, EventKey)> = table_b
399                .safe_iter()
400                .map(|r| {
401                    let ((p, _eid), v) = r.expect("failed to read from table_b");
402                    (p, strip_timestamp(v))
403                })
404                .collect();
405            entries_a.sort();
406            entries_a.dedup();
407            entries_b.sort();
408            entries_b.dedup();
409            assert!(
410                entries_a.len() == entries_b.len(),
411                "{name}: different number of unique entries: {} vs {}",
412                entries_a.len(),
413                entries_b.len()
414            );
415            for (i, (a, b)) in entries_a.iter().zip_debug_eq(entries_b.iter()).enumerate() {
416                assert!(
417                    a == b,
418                    "{name}: mismatch at sorted entry {i}:\n  a={a:?}\n  b={b:?}"
419                );
420            }
421            info!(
422                "{name}: verified {} unique entries match (ignoring event ids and timestamps)",
423                entries_a.len()
424            );
425        }
426
427        // Exact comparison — tables with no sequence numbers in keys.
428        assert_tables_equal("owner_index", &self.owner_index, &other.owner_index);
429        assert_tables_equal("coin_index_2", &self.coin_index_2, &other.coin_index_2);
430        assert_tables_equal(
431            "address_balances",
432            &self.address_balances,
433            &other.address_balances,
434        );
435        assert_tables_equal(
436            "dynamic_field_index",
437            &self.dynamic_field_index,
438            &other.dynamic_field_index,
439        );
440
441        // Transaction tables — compare (address, digest) pairs ignoring sequence numbers.
442        assert_seq_table_equal(
443            "transactions_from_addr",
444            &self.transactions_from_addr,
445            &other.transactions_from_addr,
446        );
447        assert_seq_table_equal(
448            "transactions_to_addr",
449            &self.transactions_to_addr,
450            &other.transactions_to_addr,
451        );
452        // transactions_by_move_function has a 4-tuple key: (ObjectID, String, String, TxSequenceNumber)
453        {
454            let mut entries_a: Vec<((ObjectID, String, String), TransactionDigest)> = self
455                .transactions_by_move_function
456                .safe_iter()
457                .map(|r| {
458                    let ((pkg, module, func, _seq), digest) =
459                        r.expect("failed to read from table_a");
460                    ((pkg, module, func), digest)
461                })
462                .collect();
463            let mut entries_b: Vec<((ObjectID, String, String), TransactionDigest)> = other
464                .transactions_by_move_function
465                .safe_iter()
466                .map(|r| {
467                    let ((pkg, module, func, _seq), digest) =
468                        r.expect("failed to read from table_b");
469                    ((pkg, module, func), digest)
470                })
471                .collect();
472            entries_a.sort();
473            entries_a.dedup();
474            entries_b.sort();
475            entries_b.dedup();
476            assert!(
477                entries_a.len() == entries_b.len(),
478                "transactions_by_move_function: different number of unique entries: {} vs {}",
479                entries_a.len(),
480                entries_b.len()
481            );
482            for (i, (a, b)) in entries_a.iter().zip_debug_eq(entries_b.iter()).enumerate() {
483                assert!(
484                    a == b,
485                    "transactions_by_move_function: mismatch at sorted entry {i}:\n  a={a:?}\n  b={b:?}"
486                );
487            }
488            info!(
489                "transactions_by_move_function: verified {} entries match (ignoring sequence numbers)",
490                entries_a.len()
491            );
492        }
493
494        // Event tables — compare event keys ignoring event ids and timestamps.
495        {
496            // event_order: DBMap<EventId, EventIndex> — no prefix, just compare values.
497            let mut vals_a: Vec<EventKey> = self
498                .event_order
499                .safe_iter()
500                .map(|r| strip_timestamp(r.expect("failed to read from table_a").1))
501                .collect();
502            let mut vals_b: Vec<EventKey> = other
503                .event_order
504                .safe_iter()
505                .map(|r| strip_timestamp(r.expect("failed to read from table_b").1))
506                .collect();
507            vals_a.sort();
508            vals_a.dedup();
509            vals_b.sort();
510            vals_b.dedup();
511            assert!(
512                vals_a.len() == vals_b.len(),
513                "event_order: different number of entries: {} vs {}",
514                vals_a.len(),
515                vals_b.len()
516            );
517            for (i, (a, b)) in vals_a.iter().zip_debug_eq(vals_b.iter()).enumerate() {
518                assert!(
519                    a == b,
520                    "event_order: mismatch at sorted entry {i}:\n  a={a:?}\n  b={b:?}"
521                );
522            }
523            info!(
524                "event_order: verified {} entries match (ignoring event ids and timestamps)",
525                vals_a.len()
526            );
527        }
528        assert_event_table_equal(
529            "event_by_move_module",
530            &self.event_by_move_module,
531            &other.event_by_move_module,
532        );
533        assert_event_table_equal(
534            "event_by_move_event",
535            &self.event_by_move_event,
536            &other.event_by_move_event,
537        );
538        assert_event_table_equal(
539            "event_by_event_module",
540            &self.event_by_event_module,
541            &other.event_by_event_module,
542        );
543        assert_event_table_equal(
544            "event_by_sender",
545            &self.event_by_sender,
546            &other.event_by_sender,
547        );
548        // event_by_time: key is (timestamp_ms, EventId) — timestamps differ between
549        // nodes, so just compare the set of EventKey values.
550        {
551            let mut vals_a: Vec<EventKey> = self
552                .event_by_time
553                .safe_iter()
554                .map(|r| strip_timestamp(r.expect("failed to read from table_a").1))
555                .collect();
556            let mut vals_b: Vec<EventKey> = other
557                .event_by_time
558                .safe_iter()
559                .map(|r| strip_timestamp(r.expect("failed to read from table_b").1))
560                .collect();
561            vals_a.sort();
562            vals_a.dedup();
563            vals_b.sort();
564            vals_b.dedup();
565            assert!(
566                vals_a.len() == vals_b.len(),
567                "event_by_time: different number of entries: {} vs {}",
568                vals_a.len(),
569                vals_b.len()
570            );
571            for (i, (a, b)) in vals_a.iter().zip_debug_eq(vals_b.iter()).enumerate() {
572                assert!(
573                    a == b,
574                    "event_by_time: mismatch at sorted entry {i}:\n  a={a:?}\n  b={b:?}"
575                );
576            }
577            info!(
578                "event_by_time: verified {} entries match (ignoring timestamps and event ids)",
579                vals_a.len()
580            );
581        }
582
583        // Skipped tables:
584        // - transaction_order / transactions_seq: sequence numbers differ by design
585        // - transactions_by_input_object_id / transactions_by_mutated_object_id: deprecated
586        // - meta: metadata singleton, not meaningful to compare
587        // - pruner_watermark: operational state
588    }
589
590    #[allow(deprecated)]
591    fn init(&mut self) -> Result<(), StorageError> {
592        let metadata = {
593            match self.meta.get(&()) {
594                Ok(Some(metadata)) => metadata,
595                Ok(None) | Err(_) => MetadataInfo {
596                    version: CURRENT_DB_VERSION,
597                    column_families: BTreeMap::new(),
598                },
599            }
600        };
601
602        // Commit to the DB that the indexes have been initialized
603        self.meta.insert(&(), &metadata)?;
604
605        Ok(())
606    }
607
608    pub fn get_dynamic_fields_iterator(
609        &self,
610        object: ObjectID,
611        cursor: Option<ObjectID>,
612    ) -> impl Iterator<Item = Result<(ObjectID, DynamicFieldInfo), TypedStoreError>> + '_ {
613        debug!(?object, "get_dynamic_fields");
614        // The object id 0 is the smallest possible
615        let iter_lower_bound = (object, cursor.unwrap_or(ObjectID::ZERO));
616        let iter_upper_bound = (object, ObjectID::MAX);
617        self.dynamic_field_index
618            .safe_iter_with_bounds(Some(iter_lower_bound), Some(iter_upper_bound))
619            // skip an extra b/c the cursor is exclusive
620            .skip(usize::from(cursor.is_some()))
621            .take_while(move |result| result.is_err() || (result.as_ref().unwrap().0.0 == object))
622            .map_ok(|((_, c), object_info)| (c, object_info))
623    }
624}
625
626pub struct IndexStore {
627    next_sequence_number: AtomicU64,
628    tables: IndexStoreTables,
629    pub caches: IndexStoreCaches,
630    metrics: Arc<IndexStoreMetrics>,
631    max_type_length: u64,
632    remove_deprecated_tables: bool,
633    pruner_watermark: Arc<AtomicU64>,
634}
635
636struct JsonRpcCompactionMetrics {
637    key_removed: IntCounterVec,
638    key_kept: IntCounterVec,
639    key_error: IntCounterVec,
640}
641
642impl JsonRpcCompactionMetrics {
643    pub fn new(registry: &Registry) -> Arc<Self> {
644        Arc::new(Self {
645            key_removed: register_int_counter_vec_with_registry!(
646                "json_rpc_compaction_filter_key_removed",
647                "Compaction key removed",
648                &["cf"],
649                registry
650            )
651            .unwrap(),
652            key_kept: register_int_counter_vec_with_registry!(
653                "json_rpc_compaction_filter_key_kept",
654                "Compaction key kept",
655                &["cf"],
656                registry
657            )
658            .unwrap(),
659            key_error: register_int_counter_vec_with_registry!(
660                "json_rpc_compaction_filter_key_error",
661                "Compaction error",
662                &["cf"],
663                registry
664            )
665            .unwrap(),
666        })
667    }
668}
669
670fn compaction_filter_config<T: DeserializeOwned>(
671    name: &str,
672    metrics: Arc<JsonRpcCompactionMetrics>,
673    mut db_options: DBOptions,
674    pruner_watermark: Arc<AtomicU64>,
675    extractor: impl Fn(T) -> TxSequenceNumber + Send + Sync + 'static,
676    by_key: bool,
677) -> DBOptions {
678    let cf = name.to_string();
679    db_options
680        .options
681        .set_compaction_filter(name, move |_, key, value| {
682            let bytes = if by_key { key } else { value };
683            let deserializer = bincode::DefaultOptions::new()
684                .with_big_endian()
685                .with_fixint_encoding();
686            match deserializer.deserialize(bytes) {
687                Ok(key_data) => {
688                    let sequence_number = extractor(key_data);
689                    if sequence_number < pruner_watermark.load(Ordering::Relaxed) {
690                        metrics.key_removed.with_label_values(&[&cf]).inc();
691                        Decision::Remove
692                    } else {
693                        metrics.key_kept.with_label_values(&[&cf]).inc();
694                        Decision::Keep
695                    }
696                }
697                Err(_) => {
698                    metrics.key_error.with_label_values(&[&cf]).inc();
699                    Decision::Keep
700                }
701            }
702        });
703    db_options
704}
705
706fn compaction_filter_config_by_key<T: DeserializeOwned>(
707    name: &str,
708    metrics: Arc<JsonRpcCompactionMetrics>,
709    db_options: DBOptions,
710    pruner_watermark: Arc<AtomicU64>,
711    extractor: impl Fn(T) -> TxSequenceNumber + Send + Sync + 'static,
712) -> DBOptions {
713    compaction_filter_config(name, metrics, db_options, pruner_watermark, extractor, true)
714}
715
716fn coin_index_table_default_config() -> DBOptions {
717    default_db_options()
718        .optimize_for_write_throughput()
719        .optimize_for_read(
720            read_size_from_env(ENV_VAR_COIN_INDEX_BLOCK_CACHE_SIZE_MB).unwrap_or(5 * 1024),
721        )
722        .disable_write_throttling()
723}
724
725impl IndexStore {
726    pub fn new_without_init(
727        path: PathBuf,
728        registry: &Registry,
729        max_type_length: Option<u64>,
730        remove_deprecated_tables: bool,
731    ) -> Self {
732        let db_options = default_db_options().disable_write_throttling();
733        let pruner_watermark = Arc::new(AtomicU64::new(0));
734        let compaction_metrics = JsonRpcCompactionMetrics::new(registry);
735        let table_options = DBMapTableConfigMap::new(BTreeMap::from([
736            (
737                "transactions_from_addr".to_string(),
738                compaction_filter_config_by_key(
739                    "transactions_from_addr",
740                    compaction_metrics.clone(),
741                    db_options.clone(),
742                    pruner_watermark.clone(),
743                    |(_, id): (SuiAddress, TxSequenceNumber)| id,
744                ),
745            ),
746            (
747                "transactions_to_addr".to_string(),
748                compaction_filter_config_by_key(
749                    "transactions_to_addr",
750                    compaction_metrics.clone(),
751                    db_options.clone(),
752                    pruner_watermark.clone(),
753                    |(_, sequence_number): (SuiAddress, TxSequenceNumber)| sequence_number,
754                ),
755            ),
756            (
757                "transactions_by_move_function".to_string(),
758                compaction_filter_config_by_key(
759                    "transactions_by_move_function",
760                    compaction_metrics.clone(),
761                    db_options.clone(),
762                    pruner_watermark.clone(),
763                    |(_, _, _, id): (ObjectID, String, String, TxSequenceNumber)| id,
764                ),
765            ),
766            (
767                "transaction_order".to_string(),
768                compaction_filter_config_by_key(
769                    "transaction_order",
770                    compaction_metrics.clone(),
771                    db_options.clone(),
772                    pruner_watermark.clone(),
773                    |sequence_number: TxSequenceNumber| sequence_number,
774                ),
775            ),
776            (
777                "transactions_seq".to_string(),
778                compaction_filter_config(
779                    "transactions_seq",
780                    compaction_metrics.clone(),
781                    db_options.clone(),
782                    pruner_watermark.clone(),
783                    |sequence_number: TxSequenceNumber| sequence_number,
784                    false,
785                ),
786            ),
787            (
788                "coin_index_2".to_string(),
789                coin_index_table_default_config(),
790            ),
791            (
792                "event_order".to_string(),
793                compaction_filter_config_by_key(
794                    "event_order",
795                    compaction_metrics.clone(),
796                    db_options.clone(),
797                    pruner_watermark.clone(),
798                    |event_id: EventId| event_id.0,
799                ),
800            ),
801            (
802                "event_by_move_module".to_string(),
803                compaction_filter_config_by_key(
804                    "event_by_move_module",
805                    compaction_metrics.clone(),
806                    db_options.clone(),
807                    pruner_watermark.clone(),
808                    |(_, event_id): (ModuleId, EventId)| event_id.0,
809                ),
810            ),
811            (
812                "event_by_event_module".to_string(),
813                compaction_filter_config_by_key(
814                    "event_by_event_module",
815                    compaction_metrics.clone(),
816                    db_options.clone(),
817                    pruner_watermark.clone(),
818                    |(_, event_id): (ModuleId, EventId)| event_id.0,
819                ),
820            ),
821            (
822                "event_by_sender".to_string(),
823                compaction_filter_config_by_key(
824                    "event_by_sender",
825                    compaction_metrics.clone(),
826                    db_options.clone(),
827                    pruner_watermark.clone(),
828                    |(_, event_id): (SuiAddress, EventId)| event_id.0,
829                ),
830            ),
831            (
832                "event_by_time".to_string(),
833                compaction_filter_config_by_key(
834                    "event_by_time",
835                    compaction_metrics.clone(),
836                    db_options.clone(),
837                    pruner_watermark.clone(),
838                    |(_, event_id): (u64, EventId)| event_id.0,
839                ),
840            ),
841        ]));
842        let tables = IndexStoreTables::open_tables_read_write_with_deprecation_option(
843            path,
844            MetricConf::new("index"),
845            Some(db_options.options),
846            Some(table_options),
847            remove_deprecated_tables,
848        );
849
850        let metrics = IndexStoreMetrics::new(registry);
851        let caches = IndexStoreCaches {
852            per_coin_type_balance: ShardedLruCache::new(1_000_000, 1000),
853            all_balances: ShardedLruCache::new(1_000_000, 1000),
854            locks: MutexTable::new(128),
855        };
856        let next_sequence_number = tables
857            .transaction_order
858            .reversed_safe_iter_with_bounds(None, None)
859            .expect("failed to initialize indexes")
860            .next()
861            .transpose()
862            .expect("failed to initialize indexes")
863            .map(|(seq, _)| seq + 1)
864            .unwrap_or(0)
865            .into();
866        let pruner_watermark_value = tables
867            .pruner_watermark
868            .get(&())
869            .expect("failed to initialize index tables")
870            .unwrap_or(0);
871        pruner_watermark.store(pruner_watermark_value, Ordering::Relaxed);
872
873        Self {
874            tables,
875            next_sequence_number,
876            caches,
877            metrics: Arc::new(metrics),
878            max_type_length: max_type_length.unwrap_or(128),
879            remove_deprecated_tables,
880            pruner_watermark,
881        }
882    }
883
884    pub fn new(
885        path: PathBuf,
886        registry: &Registry,
887        max_type_length: Option<u64>,
888        remove_deprecated_tables: bool,
889    ) -> Self {
890        let mut store =
891            Self::new_without_init(path, registry, max_type_length, remove_deprecated_tables);
892        store.tables.init().unwrap();
893        store
894    }
895
896    pub fn tables(&self) -> &IndexStoreTables {
897        &self.tables
898    }
899
900    #[instrument(skip_all)]
901    pub fn index_coin(
902        &self,
903        digest: &TransactionDigest,
904        batch: &mut StagedBatch,
905        object_index_changes: &ObjectIndexChanges,
906        tx_coins: Option<TxCoins>,
907        acquire_locks: bool,
908    ) -> SuiResult<IndexStoreCacheUpdatesWithLocks> {
909        // In production if this code path is hit, we should expect `tx_coins` to not be None.
910        // However, in many tests today we do not distinguish validator and/or fullnode, so
911        // we gracefully exist here.
912        if tx_coins.is_none() {
913            return Ok(IndexStoreCacheUpdatesWithLocks {
914                _locks: None,
915                inner: IndexStoreCacheUpdates::default(),
916            });
917        }
918
919        let _locks = if acquire_locks {
920            let mut addresses: HashSet<SuiAddress> = HashSet::new();
921            addresses.extend(
922                object_index_changes
923                    .deleted_owners
924                    .iter()
925                    .map(|(owner, _)| *owner),
926            );
927            addresses.extend(
928                object_index_changes
929                    .new_owners
930                    .iter()
931                    .map(|((owner, _), _)| *owner),
932            );
933            Some(self.caches.locks.acquire_locks(addresses.into_iter()))
934        } else {
935            None
936        };
937
938        let (input_coins, written_coins) = tx_coins.unwrap();
939        let mut balance_changes: HashMap<SuiAddress, HashMap<TypeTag, TotalBalance>> =
940            HashMap::new();
941
942        // 1. Remove old coins from the DB by looking at the set of input coin objects
943        let coin_delete_keys = input_coins
944            .values()
945            .filter_map(|object| {
946                // only process address owned coins
947                let Owner::AddressOwner(owner) = object.owner() else {
948                    return None;
949                };
950
951                // only process coin types
952                let (coin_type, coin) = object.coin_type_maybe().zip(object.as_coin_maybe())?;
953
954                let key = CoinIndexKey2::new(
955                    *owner,
956                    coin_type.to_string(),
957                    coin.balance.value(),
958                    object.id(),
959                );
960
961                let map = balance_changes.entry(*owner).or_default();
962                let entry = map.entry(coin_type).or_insert(TotalBalance {
963                    num_coins: 0,
964                    balance: 0,
965                    address_balance: 0,
966                });
967                entry.num_coins -= 1;
968                entry.balance -= coin.balance.value() as i128;
969
970                Some(key)
971            })
972            .collect::<Vec<_>>();
973        trace!(
974            tx_digset=?digest,
975            "coin_delete_keys: {:?}",
976            coin_delete_keys,
977        );
978        batch.delete_batch(&self.tables.coin_index_2, coin_delete_keys)?;
979
980        // 2. Insert new coins, or new versions of coins, by looking at `written_coins`.
981        let coin_add_keys = written_coins
982            .values()
983            .filter_map(|object| {
984                // only process address owned coins
985                let Owner::AddressOwner(owner) = object.owner() else {
986                    return None;
987                };
988
989                // only process coin types
990                let (coin_type, coin) = object.coin_type_maybe().zip(object.as_coin_maybe())?;
991
992                let key = CoinIndexKey2::new(
993                    *owner,
994                    coin_type.to_string(),
995                    coin.balance.value(),
996                    object.id(),
997                );
998                let value = CoinInfo {
999                    version: object.version(),
1000                    digest: object.digest(),
1001                    balance: coin.balance.value(),
1002                    previous_transaction: object.previous_transaction,
1003                };
1004                let map = balance_changes.entry(*owner).or_default();
1005                let entry = map.entry(coin_type).or_insert(TotalBalance {
1006                    num_coins: 0,
1007                    balance: 0,
1008                    address_balance: 0,
1009                });
1010                entry.num_coins += 1;
1011                entry.balance += coin.balance.value() as i128;
1012
1013                Some((key, value))
1014            })
1015            .collect::<Vec<_>>();
1016        trace!(
1017            tx_digset=?digest,
1018            "coin_add_keys: {:?}",
1019            coin_add_keys,
1020        );
1021
1022        batch.insert_batch(&self.tables.coin_index_2, coin_add_keys)?;
1023
1024        let per_coin_type_balance_changes: Vec<_> = balance_changes
1025            .iter()
1026            .flat_map(|(address, balance_map)| {
1027                balance_map.iter().map(|(type_tag, balance)| {
1028                    (
1029                        (*address, type_tag.clone()),
1030                        Ok::<TotalBalance, SuiError>(*balance),
1031                    )
1032                })
1033            })
1034            .collect();
1035        let all_balance_changes: Vec<_> = balance_changes
1036            .into_iter()
1037            .map(|(address, balance_map)| {
1038                (
1039                    address,
1040                    Ok::<Arc<HashMap<TypeTag, TotalBalance>>, SuiError>(Arc::new(balance_map)),
1041                )
1042            })
1043            .collect();
1044        let cache_updates = IndexStoreCacheUpdatesWithLocks {
1045            _locks,
1046            inner: IndexStoreCacheUpdates {
1047                per_coin_type_balance_changes,
1048                all_balance_changes,
1049            },
1050        };
1051        Ok(cache_updates)
1052    }
1053
1054    pub fn allocate_sequence_number(&self) -> u64 {
1055        self.next_sequence_number.fetch_add(1, Ordering::SeqCst)
1056    }
1057
1058    #[instrument(skip_all)]
1059    pub fn index_tx(
1060        &self,
1061        sequence: u64,
1062        sender: SuiAddress,
1063        active_inputs: impl Iterator<Item = ObjectID>,
1064        mutated_objects: impl Iterator<Item = (ObjectRef, Owner)> + Clone,
1065        move_functions: impl Iterator<Item = (ObjectID, String, String)> + Clone,
1066        events: &TransactionEvents,
1067        object_index_changes: ObjectIndexChanges,
1068        digest: &TransactionDigest,
1069        timestamp_ms: u64,
1070        tx_coins: Option<TxCoins>,
1071        accumulator_events: Vec<AccumulatorEvent>,
1072        acquire_locks: bool,
1073    ) -> SuiResult<(StagedBatch, IndexStoreCacheUpdatesWithLocks)> {
1074        let mut batch = StagedBatch::new();
1075
1076        batch.insert_batch(
1077            &self.tables.transaction_order,
1078            std::iter::once((sequence, *digest)),
1079        )?;
1080
1081        batch.insert_batch(
1082            &self.tables.transactions_seq,
1083            std::iter::once((*digest, sequence)),
1084        )?;
1085
1086        batch.insert_batch(
1087            &self.tables.transactions_from_addr,
1088            std::iter::once(((sender, sequence), *digest)),
1089        )?;
1090
1091        #[allow(deprecated)]
1092        if !self.remove_deprecated_tables {
1093            batch.insert_batch(
1094                &self.tables.transactions_by_input_object_id,
1095                active_inputs.map(|id| ((id, sequence), *digest)),
1096            )?;
1097
1098            batch.insert_batch(
1099                &self.tables.transactions_by_mutated_object_id,
1100                mutated_objects
1101                    .clone()
1102                    .map(|(obj_ref, _)| ((obj_ref.0, sequence), *digest)),
1103            )?;
1104        }
1105
1106        batch.insert_batch(
1107            &self.tables.transactions_by_move_function,
1108            move_functions
1109                .map(|(obj_id, module, function)| ((obj_id, module, function, sequence), *digest)),
1110        )?;
1111
1112        // objects sent to addresses and accumulator events
1113        let affected_addresses = mutated_objects
1114            .filter_map(|(_, owner)| {
1115                owner
1116                    .get_address_owner_address()
1117                    .ok()
1118                    .map(|addr| ((addr, sequence), digest))
1119            })
1120            .chain(
1121                accumulator_events
1122                    .iter()
1123                    .map(|event| ((event.write.address.address, sequence), digest)),
1124            );
1125        batch.insert_batch(&self.tables.transactions_to_addr, affected_addresses)?;
1126
1127        // Coin Index
1128        let cache_updates = self.index_coin(
1129            digest,
1130            &mut batch,
1131            &object_index_changes,
1132            tx_coins,
1133            acquire_locks,
1134        )?;
1135
1136        // update address balances index
1137        let address_balance_updates = accumulator_events.into_iter().filter_map(|event| {
1138            let ty = &event.write.address.ty;
1139            let coin_type = sui_types::balance::Balance::maybe_get_balance_type_param(ty)?;
1140            Some(((event.write.address.address, coin_type), ()))
1141        });
1142        batch.insert_batch(&self.tables.address_balances, address_balance_updates)?;
1143
1144        // Owner index
1145        batch.delete_batch(
1146            &self.tables.owner_index,
1147            object_index_changes.deleted_owners.into_iter(),
1148        )?;
1149        batch.delete_batch(
1150            &self.tables.dynamic_field_index,
1151            object_index_changes.deleted_dynamic_fields.into_iter(),
1152        )?;
1153
1154        batch.insert_batch(
1155            &self.tables.owner_index,
1156            object_index_changes.new_owners.into_iter(),
1157        )?;
1158
1159        batch.insert_batch(
1160            &self.tables.dynamic_field_index,
1161            object_index_changes.new_dynamic_fields.into_iter(),
1162        )?;
1163
1164        // events
1165        let event_digest = events.digest();
1166        batch.insert_batch(
1167            &self.tables.event_order,
1168            events
1169                .data
1170                .iter()
1171                .enumerate()
1172                .map(|(i, _)| ((sequence, i), (event_digest, *digest, timestamp_ms))),
1173        )?;
1174        batch.insert_batch(
1175            &self.tables.event_by_move_module,
1176            events
1177                .data
1178                .iter()
1179                .enumerate()
1180                .map(|(i, e)| {
1181                    (
1182                        i,
1183                        ModuleId::new(e.package_id.into(), e.transaction_module.clone()),
1184                    )
1185                })
1186                .map(|(i, m)| ((m, (sequence, i)), (event_digest, *digest, timestamp_ms))),
1187        )?;
1188        batch.insert_batch(
1189            &self.tables.event_by_sender,
1190            events.data.iter().enumerate().map(|(i, e)| {
1191                (
1192                    (e.sender, (sequence, i)),
1193                    (event_digest, *digest, timestamp_ms),
1194                )
1195            }),
1196        )?;
1197        batch.insert_batch(
1198            &self.tables.event_by_move_event,
1199            events.data.iter().enumerate().map(|(i, e)| {
1200                (
1201                    (e.type_.clone(), (sequence, i)),
1202                    (event_digest, *digest, timestamp_ms),
1203                )
1204            }),
1205        )?;
1206
1207        batch.insert_batch(
1208            &self.tables.event_by_time,
1209            events.data.iter().enumerate().map(|(i, _)| {
1210                (
1211                    (timestamp_ms, (sequence, i)),
1212                    (event_digest, *digest, timestamp_ms),
1213                )
1214            }),
1215        )?;
1216
1217        batch.insert_batch(
1218            &self.tables.event_by_event_module,
1219            events.data.iter().enumerate().map(|(i, e)| {
1220                (
1221                    (
1222                        ModuleId::new(e.type_.address, e.type_.module.clone()),
1223                        (sequence, i),
1224                    ),
1225                    (event_digest, *digest, timestamp_ms),
1226                )
1227            }),
1228        )?;
1229
1230        Ok((batch, cache_updates))
1231    }
1232
1233    /// Write a combined index batch and apply cache updates.
1234    pub fn commit_index_batch(
1235        &self,
1236        batch: DBBatch,
1237        cache_updates: Vec<IndexStoreCacheUpdates>,
1238    ) -> SuiResult {
1239        let invalidate_caches =
1240            read_size_from_env(ENV_VAR_INVALIDATE_INSTEAD_OF_UPDATE).unwrap_or(0) > 0;
1241
1242        if invalidate_caches {
1243            for cu in &cache_updates {
1244                self.invalidate_per_coin_type_cache(
1245                    cu.per_coin_type_balance_changes.iter().map(|x| x.0.clone()),
1246                )?;
1247                self.invalidate_all_balance_cache(cu.all_balance_changes.iter().map(|x| x.0))?;
1248            }
1249        }
1250
1251        batch.write()?;
1252
1253        if !invalidate_caches {
1254            for cu in cache_updates {
1255                self.update_per_coin_type_cache(cu.per_coin_type_balance_changes)?;
1256                self.update_all_balance_cache(cu.all_balance_changes)?;
1257            }
1258        }
1259        Ok(())
1260    }
1261
1262    pub fn new_db_batch(&self) -> DBBatch {
1263        self.tables.transactions_from_addr.batch()
1264    }
1265
1266    pub fn next_sequence_number(&self) -> TxSequenceNumber {
1267        self.next_sequence_number.load(Ordering::SeqCst) + 1
1268    }
1269
1270    #[instrument(skip(self))]
1271    pub fn get_transactions(
1272        &self,
1273        filter: Option<TransactionFilter>,
1274        cursor: Option<TransactionDigest>,
1275        limit: Option<usize>,
1276        reverse: bool,
1277    ) -> SuiResult<Vec<TransactionDigest>> {
1278        // Lookup TransactionDigest sequence number,
1279        let cursor = if let Some(cursor) = cursor {
1280            Some(
1281                self.get_transaction_seq(&cursor)?
1282                    .ok_or(SuiErrorKind::TransactionNotFound { digest: cursor })?,
1283            )
1284        } else {
1285            None
1286        };
1287        match filter {
1288            Some(TransactionFilter::MoveFunction {
1289                package,
1290                module,
1291                function,
1292            }) => Ok(self.get_transactions_by_move_function(
1293                package, module, function, cursor, limit, reverse,
1294            )?),
1295            Some(TransactionFilter::InputObject(object_id)) => {
1296                Ok(self.get_transactions_by_input_object(object_id, cursor, limit, reverse)?)
1297            }
1298            Some(TransactionFilter::ChangedObject(object_id)) => {
1299                Ok(self.get_transactions_by_mutated_object(object_id, cursor, limit, reverse)?)
1300            }
1301            Some(TransactionFilter::FromAddress(address)) => {
1302                Ok(self.get_transactions_from_addr(address, cursor, limit, reverse)?)
1303            }
1304            Some(TransactionFilter::ToAddress(address)) => {
1305                Ok(self.get_transactions_to_addr(address, cursor, limit, reverse)?)
1306            }
1307            // NOTE: filter via checkpoint sequence number is implemented in
1308            // `get_transactions` of authority.rs.
1309            Some(_) => Err(SuiErrorKind::UserInputError {
1310                error: UserInputError::Unsupported(format!("{:?}", filter)),
1311            }
1312            .into()),
1313            None => {
1314                if reverse {
1315                    let iter = self
1316                        .tables
1317                        .transaction_order
1318                        .reversed_safe_iter_with_bounds(
1319                            None,
1320                            Some(cursor.unwrap_or(TxSequenceNumber::MAX)),
1321                        )?
1322                        .skip(usize::from(cursor.is_some()))
1323                        .map(|result| result.map(|(_, digest)| digest));
1324                    if let Some(limit) = limit {
1325                        Ok(iter.take(limit).collect::<Result<Vec<_>, _>>()?)
1326                    } else {
1327                        Ok(iter.collect::<Result<Vec<_>, _>>()?)
1328                    }
1329                } else {
1330                    let iter = self
1331                        .tables
1332                        .transaction_order
1333                        .safe_iter_with_bounds(Some(cursor.unwrap_or(TxSequenceNumber::MIN)), None)
1334                        .skip(usize::from(cursor.is_some()))
1335                        .map(|result| result.map(|(_, digest)| digest));
1336                    if let Some(limit) = limit {
1337                        Ok(iter.take(limit).collect::<Result<Vec<_>, _>>()?)
1338                    } else {
1339                        Ok(iter.collect::<Result<Vec<_>, _>>()?)
1340                    }
1341                }
1342            }
1343        }
1344    }
1345
1346    #[instrument(skip_all)]
1347    fn get_transactions_from_index<KeyT: Clone + Serialize + DeserializeOwned + PartialEq>(
1348        index: &DBMap<(KeyT, TxSequenceNumber), TransactionDigest>,
1349        key: KeyT,
1350        cursor: Option<TxSequenceNumber>,
1351        limit: Option<usize>,
1352        reverse: bool,
1353    ) -> SuiResult<Vec<TransactionDigest>> {
1354        Ok(if reverse {
1355            let iter = index
1356                .reversed_safe_iter_with_bounds(
1357                    None,
1358                    Some((key.clone(), cursor.unwrap_or(TxSequenceNumber::MAX))),
1359                )?
1360                // skip one more if exclusive cursor is Some
1361                .skip(usize::from(cursor.is_some()))
1362                .take_while(|result| {
1363                    result
1364                        .as_ref()
1365                        .map(|((id, _), _)| *id == key)
1366                        .unwrap_or(false)
1367                })
1368                .map(|result| result.map(|(_, digest)| digest));
1369            if let Some(limit) = limit {
1370                iter.take(limit).collect::<Result<Vec<_>, _>>()?
1371            } else {
1372                iter.collect::<Result<Vec<_>, _>>()?
1373            }
1374        } else {
1375            let iter = index
1376                .safe_iter_with_bounds(
1377                    Some((key.clone(), cursor.unwrap_or(TxSequenceNumber::MIN))),
1378                    None,
1379                )
1380                // skip one more if exclusive cursor is Some
1381                .skip(usize::from(cursor.is_some()))
1382                .map(|result| result.expect("iterator db error"))
1383                .take_while(|((id, _), _)| *id == key)
1384                .map(|(_, digest)| digest);
1385            if let Some(limit) = limit {
1386                iter.take(limit).collect()
1387            } else {
1388                iter.collect()
1389            }
1390        })
1391    }
1392
1393    #[instrument(skip(self))]
1394    pub fn get_transactions_by_input_object(
1395        &self,
1396        input_object: ObjectID,
1397        cursor: Option<TxSequenceNumber>,
1398        limit: Option<usize>,
1399        reverse: bool,
1400    ) -> SuiResult<Vec<TransactionDigest>> {
1401        if self.remove_deprecated_tables {
1402            return Ok(vec![]);
1403        }
1404        #[allow(deprecated)]
1405        Self::get_transactions_from_index(
1406            &self.tables.transactions_by_input_object_id,
1407            input_object,
1408            cursor,
1409            limit,
1410            reverse,
1411        )
1412    }
1413
1414    #[instrument(skip(self))]
1415    pub fn get_transactions_by_mutated_object(
1416        &self,
1417        mutated_object: ObjectID,
1418        cursor: Option<TxSequenceNumber>,
1419        limit: Option<usize>,
1420        reverse: bool,
1421    ) -> SuiResult<Vec<TransactionDigest>> {
1422        if self.remove_deprecated_tables {
1423            return Ok(vec![]);
1424        }
1425        #[allow(deprecated)]
1426        Self::get_transactions_from_index(
1427            &self.tables.transactions_by_mutated_object_id,
1428            mutated_object,
1429            cursor,
1430            limit,
1431            reverse,
1432        )
1433    }
1434
1435    #[instrument(skip(self))]
1436    pub fn get_transactions_from_addr(
1437        &self,
1438        addr: SuiAddress,
1439        cursor: Option<TxSequenceNumber>,
1440        limit: Option<usize>,
1441        reverse: bool,
1442    ) -> SuiResult<Vec<TransactionDigest>> {
1443        Self::get_transactions_from_index(
1444            &self.tables.transactions_from_addr,
1445            addr,
1446            cursor,
1447            limit,
1448            reverse,
1449        )
1450    }
1451
1452    #[instrument(skip(self))]
1453    pub fn get_transactions_by_move_function(
1454        &self,
1455        package: ObjectID,
1456        module: Option<String>,
1457        function: Option<String>,
1458        cursor: Option<TxSequenceNumber>,
1459        limit: Option<usize>,
1460        reverse: bool,
1461    ) -> SuiResult<Vec<TransactionDigest>> {
1462        // If we are passed a function with no module return a UserInputError
1463        if function.is_some() && module.is_none() {
1464            return Err(SuiErrorKind::UserInputError {
1465                error: UserInputError::MoveFunctionInputError(
1466                    "Cannot supply function without supplying module".to_string(),
1467                ),
1468            }
1469            .into());
1470        }
1471
1472        // We cannot have a cursor without filling out the other keys.
1473        if cursor.is_some() && (module.is_none() || function.is_none()) {
1474            return Err(SuiErrorKind::UserInputError {
1475                error: UserInputError::MoveFunctionInputError(
1476                    "Cannot supply cursor without supplying module and function".to_string(),
1477                ),
1478            }
1479            .into());
1480        }
1481
1482        let cursor_val = cursor.unwrap_or(if reverse {
1483            TxSequenceNumber::MAX
1484        } else {
1485            TxSequenceNumber::MIN
1486        });
1487
1488        let max_string = "z".repeat(self.max_type_length.try_into().unwrap());
1489        let module_val = module.clone().unwrap_or(if reverse {
1490            max_string.clone()
1491        } else {
1492            "".to_string()
1493        });
1494
1495        let function_val =
1496            function
1497                .clone()
1498                .unwrap_or(if reverse { max_string } else { "".to_string() });
1499
1500        let key = (package, module_val, function_val, cursor_val);
1501        Ok(if reverse {
1502            let iter = self
1503                .tables
1504                .transactions_by_move_function
1505                .reversed_safe_iter_with_bounds(None, Some(key))?
1506                // skip one more if exclusive cursor is Some
1507                .skip(usize::from(cursor.is_some()))
1508                .take_while(|result| {
1509                    result
1510                        .as_ref()
1511                        .map(|((id, m, f, _), _)| {
1512                            *id == package
1513                                && module.as_ref().map(|x| x == m).unwrap_or(true)
1514                                && function.as_ref().map(|x| x == f).unwrap_or(true)
1515                        })
1516                        .unwrap_or(false)
1517                })
1518                .map(|result| result.map(|(_, digest)| digest));
1519            if let Some(limit) = limit {
1520                iter.take(limit).collect::<Result<Vec<_>, _>>()?
1521            } else {
1522                iter.collect::<Result<Vec<_>, _>>()?
1523            }
1524        } else {
1525            let iter = self
1526                .tables
1527                .transactions_by_move_function
1528                .safe_iter_with_bounds(Some(key), None)
1529                .map(|result| result.expect("iterator db error"))
1530                // skip one more if exclusive cursor is Some
1531                .skip(usize::from(cursor.is_some()))
1532                .take_while(|((id, m, f, _), _)| {
1533                    *id == package
1534                        && module.as_ref().map(|x| x == m).unwrap_or(true)
1535                        && function.as_ref().map(|x| x == f).unwrap_or(true)
1536                })
1537                .map(|(_, digest)| digest);
1538            if let Some(limit) = limit {
1539                iter.take(limit).collect()
1540            } else {
1541                iter.collect()
1542            }
1543        })
1544    }
1545
1546    #[instrument(skip(self))]
1547    pub fn get_transactions_to_addr(
1548        &self,
1549        addr: SuiAddress,
1550        cursor: Option<TxSequenceNumber>,
1551        limit: Option<usize>,
1552        reverse: bool,
1553    ) -> SuiResult<Vec<TransactionDigest>> {
1554        Self::get_transactions_from_index(
1555            &self.tables.transactions_to_addr,
1556            addr,
1557            cursor,
1558            limit,
1559            reverse,
1560        )
1561    }
1562
1563    #[instrument(skip(self))]
1564    pub fn get_transaction_seq(
1565        &self,
1566        digest: &TransactionDigest,
1567    ) -> SuiResult<Option<TxSequenceNumber>> {
1568        Ok(self.tables.transactions_seq.get(digest)?)
1569    }
1570
1571    #[instrument(skip(self))]
1572    pub fn all_events(
1573        &self,
1574        tx_seq: TxSequenceNumber,
1575        event_seq: usize,
1576        limit: usize,
1577        descending: bool,
1578    ) -> SuiResult<Vec<(TransactionEventsDigest, TransactionDigest, usize, u64)>> {
1579        Ok(if descending {
1580            self.tables
1581                .event_order
1582                .reversed_safe_iter_with_bounds(None, Some((tx_seq, event_seq)))?
1583                .take(limit)
1584                .map(|result| {
1585                    result.map(|((_, event_seq), (digest, tx_digest, time))| {
1586                        (digest, tx_digest, event_seq, time)
1587                    })
1588                })
1589                .collect::<Result<Vec<_>, _>>()?
1590        } else {
1591            self.tables
1592                .event_order
1593                .safe_iter_with_bounds(Some((tx_seq, event_seq)), None)
1594                .take(limit)
1595                .map(|result| {
1596                    result.map(|((_, event_seq), (digest, tx_digest, time))| {
1597                        (digest, tx_digest, event_seq, time)
1598                    })
1599                })
1600                .collect::<Result<Vec<_>, _>>()?
1601        })
1602    }
1603
1604    #[instrument(skip(self))]
1605    pub fn events_by_transaction(
1606        &self,
1607        digest: &TransactionDigest,
1608        tx_seq: TxSequenceNumber,
1609        event_seq: usize,
1610        limit: usize,
1611        descending: bool,
1612    ) -> SuiResult<Vec<(TransactionEventsDigest, TransactionDigest, usize, u64)>> {
1613        let seq = self
1614            .get_transaction_seq(digest)?
1615            .ok_or(SuiErrorKind::TransactionNotFound { digest: *digest })?;
1616        Ok(if descending {
1617            self.tables
1618                .event_order
1619                .reversed_safe_iter_with_bounds(None, Some((min(tx_seq, seq), event_seq)))?
1620                .take_while(|result| {
1621                    result
1622                        .as_ref()
1623                        .map(|((tx, _), _)| tx == &seq)
1624                        .unwrap_or(false)
1625                })
1626                .take(limit)
1627                .map(|result| {
1628                    result.map(|((_, event_seq), (digest, tx_digest, time))| {
1629                        (digest, tx_digest, event_seq, time)
1630                    })
1631                })
1632                .collect::<Result<Vec<_>, _>>()?
1633        } else {
1634            self.tables
1635                .event_order
1636                .safe_iter_with_bounds(Some((max(tx_seq, seq), event_seq)), None)
1637                .map(|result| result.expect("iterator db error"))
1638                .take_while(|((tx, _), _)| tx == &seq)
1639                .take(limit)
1640                .map(|((_, event_seq), (digest, tx_digest, time))| {
1641                    (digest, tx_digest, event_seq, time)
1642                })
1643                .collect()
1644        })
1645    }
1646
1647    #[instrument(skip_all)]
1648    fn get_event_from_index<KeyT: Clone + PartialEq + Serialize + DeserializeOwned>(
1649        index: &DBMap<(KeyT, EventId), (TransactionEventsDigest, TransactionDigest, u64)>,
1650        key: &KeyT,
1651        tx_seq: TxSequenceNumber,
1652        event_seq: usize,
1653        limit: usize,
1654        descending: bool,
1655    ) -> SuiResult<Vec<(TransactionEventsDigest, TransactionDigest, usize, u64)>> {
1656        Ok(if descending {
1657            index
1658                .reversed_safe_iter_with_bounds(None, Some((key.clone(), (tx_seq, event_seq))))?
1659                .take_while(|result| result.as_ref().map(|((m, _), _)| m == key).unwrap_or(false))
1660                .take(limit)
1661                .map(|result| {
1662                    result.map(|((_, (_, event_seq)), (digest, tx_digest, time))| {
1663                        (digest, tx_digest, event_seq, time)
1664                    })
1665                })
1666                .collect::<Result<Vec<_>, _>>()?
1667        } else {
1668            index
1669                .safe_iter_with_bounds(Some((key.clone(), (tx_seq, event_seq))), None)
1670                .map(|result| result.expect("iterator db error"))
1671                .take_while(|((m, _), _)| m == key)
1672                .take(limit)
1673                .map(|((_, (_, event_seq)), (digest, tx_digest, time))| {
1674                    (digest, tx_digest, event_seq, time)
1675                })
1676                .collect()
1677        })
1678    }
1679
1680    #[instrument(skip(self))]
1681    pub fn events_by_module_id(
1682        &self,
1683        module: &ModuleId,
1684        tx_seq: TxSequenceNumber,
1685        event_seq: usize,
1686        limit: usize,
1687        descending: bool,
1688    ) -> SuiResult<Vec<(TransactionEventsDigest, TransactionDigest, usize, u64)>> {
1689        Self::get_event_from_index(
1690            &self.tables.event_by_move_module,
1691            module,
1692            tx_seq,
1693            event_seq,
1694            limit,
1695            descending,
1696        )
1697    }
1698
1699    #[instrument(skip(self))]
1700    pub fn events_by_move_event_struct_name(
1701        &self,
1702        struct_name: &StructTag,
1703        tx_seq: TxSequenceNumber,
1704        event_seq: usize,
1705        limit: usize,
1706        descending: bool,
1707    ) -> SuiResult<Vec<(TransactionEventsDigest, TransactionDigest, usize, u64)>> {
1708        Self::get_event_from_index(
1709            &self.tables.event_by_move_event,
1710            struct_name,
1711            tx_seq,
1712            event_seq,
1713            limit,
1714            descending,
1715        )
1716    }
1717
1718    #[instrument(skip(self))]
1719    pub fn events_by_move_event_module(
1720        &self,
1721        module_id: &ModuleId,
1722        tx_seq: TxSequenceNumber,
1723        event_seq: usize,
1724        limit: usize,
1725        descending: bool,
1726    ) -> SuiResult<Vec<(TransactionEventsDigest, TransactionDigest, usize, u64)>> {
1727        Self::get_event_from_index(
1728            &self.tables.event_by_event_module,
1729            module_id,
1730            tx_seq,
1731            event_seq,
1732            limit,
1733            descending,
1734        )
1735    }
1736
1737    #[instrument(skip(self))]
1738    pub fn events_by_sender(
1739        &self,
1740        sender: &SuiAddress,
1741        tx_seq: TxSequenceNumber,
1742        event_seq: usize,
1743        limit: usize,
1744        descending: bool,
1745    ) -> SuiResult<Vec<(TransactionEventsDigest, TransactionDigest, usize, u64)>> {
1746        Self::get_event_from_index(
1747            &self.tables.event_by_sender,
1748            sender,
1749            tx_seq,
1750            event_seq,
1751            limit,
1752            descending,
1753        )
1754    }
1755
1756    #[instrument(skip(self))]
1757    pub fn event_iterator(
1758        &self,
1759        start_time: u64,
1760        end_time: u64,
1761        tx_seq: TxSequenceNumber,
1762        event_seq: usize,
1763        limit: usize,
1764        descending: bool,
1765    ) -> SuiResult<Vec<(TransactionEventsDigest, TransactionDigest, usize, u64)>> {
1766        Ok(if descending {
1767            self.tables
1768                .event_by_time
1769                .reversed_safe_iter_with_bounds(None, Some((end_time, (tx_seq, event_seq))))?
1770                .take_while(|result| {
1771                    result
1772                        .as_ref()
1773                        .map(|((m, _), _)| m >= &start_time)
1774                        .unwrap_or(false)
1775                })
1776                .take(limit)
1777                .map(|result| {
1778                    result.map(|((_, (_, event_seq)), (digest, tx_digest, time))| {
1779                        (digest, tx_digest, event_seq, time)
1780                    })
1781                })
1782                .collect::<Result<Vec<_>, _>>()?
1783        } else {
1784            self.tables
1785                .event_by_time
1786                .safe_iter_with_bounds(Some((start_time, (tx_seq, event_seq))), None)
1787                .map(|result| result.expect("iterator db error"))
1788                .take_while(|((m, _), _)| m <= &end_time)
1789                .take(limit)
1790                .map(|((_, (_, event_seq)), (digest, tx_digest, time))| {
1791                    (digest, tx_digest, event_seq, time)
1792                })
1793                .collect()
1794        })
1795    }
1796
1797    pub fn prune(&self, cut_time_ms: u64) -> SuiResult<TxSequenceNumber> {
1798        match self
1799            .tables
1800            .event_by_time
1801            .reversed_safe_iter_with_bounds(
1802                None,
1803                Some((cut_time_ms, (TxSequenceNumber::MAX, usize::MAX))),
1804            )?
1805            .next()
1806            .transpose()?
1807        {
1808            Some(((_, (watermark, _)), _)) => {
1809                if let Some(digest) = self.tables.transaction_order.get(&watermark)? {
1810                    info!(
1811                        "json rpc index pruning. Watermark is {} with digest {}",
1812                        watermark, digest
1813                    );
1814                }
1815                self.pruner_watermark.store(watermark, Ordering::Relaxed);
1816                self.tables.pruner_watermark.insert(&(), &watermark)?;
1817                Ok(watermark)
1818            }
1819            None => Ok(0),
1820        }
1821    }
1822
1823    pub fn get_dynamic_fields_iterator(
1824        &self,
1825        object: ObjectID,
1826        cursor: Option<ObjectID>,
1827    ) -> SuiResult<impl Iterator<Item = Result<(ObjectID, DynamicFieldInfo), TypedStoreError>> + '_>
1828    {
1829        Ok(self.tables.get_dynamic_fields_iterator(object, cursor))
1830    }
1831
1832    #[instrument(skip(self))]
1833    pub fn get_dynamic_field_object_id(
1834        &self,
1835        object: ObjectID,
1836        name_type: TypeTag,
1837        name_bcs_bytes: &[u8],
1838    ) -> SuiResult<Option<ObjectID>> {
1839        debug!(?object, "get_dynamic_field_object_id");
1840        let dynamic_field_id =
1841            dynamic_field::derive_dynamic_field_id(object, &name_type, name_bcs_bytes).map_err(
1842                |e| {
1843                    SuiErrorKind::Unknown(format!(
1844                        "Unable to generate dynamic field id. Got error: {e:?}"
1845                    ))
1846                },
1847            )?;
1848
1849        if let Some(info) = self
1850            .tables
1851            .dynamic_field_index
1852            .get(&(object, dynamic_field_id))?
1853        {
1854            // info.object_id != dynamic_field_id ==> is_wrapper
1855            debug_assert!(
1856                info.object_id == dynamic_field_id
1857                    || matches!(name_type, TypeTag::Struct(tag) if DynamicFieldInfo::is_dynamic_object_field_wrapper(&tag))
1858            );
1859            return Ok(Some(info.object_id));
1860        }
1861
1862        let dynamic_object_field_struct = DynamicFieldInfo::dynamic_object_field_wrapper(name_type);
1863        let dynamic_object_field_type = TypeTag::Struct(Box::new(dynamic_object_field_struct));
1864        let dynamic_object_field_id = dynamic_field::derive_dynamic_field_id(
1865            object,
1866            &dynamic_object_field_type,
1867            name_bcs_bytes,
1868        )
1869        .map_err(|e| {
1870            SuiErrorKind::Unknown(format!(
1871                "Unable to generate dynamic field id. Got error: {e:?}"
1872            ))
1873        })?;
1874        if let Some(info) = self
1875            .tables
1876            .dynamic_field_index
1877            .get(&(object, dynamic_object_field_id))?
1878        {
1879            return Ok(Some(info.object_id));
1880        }
1881
1882        Ok(None)
1883    }
1884
1885    #[instrument(skip(self))]
1886    pub fn get_owner_objects(
1887        &self,
1888        owner: SuiAddress,
1889        cursor: Option<ObjectID>,
1890        limit: usize,
1891        filter: Option<SuiObjectDataFilter>,
1892    ) -> SuiResult<Vec<ObjectInfo>> {
1893        let cursor = match cursor {
1894            Some(cursor) => cursor,
1895            None => ObjectID::ZERO,
1896        };
1897        Ok(self
1898            .get_owner_objects_iterator(owner, cursor, filter)?
1899            .take(limit)
1900            .collect())
1901    }
1902
1903    pub fn get_address_balance_coin_types_iter(
1904        &self,
1905        owner: SuiAddress,
1906    ) -> impl Iterator<Item = TypeTag> {
1907        let start_key = (owner, TypeTag::Bool);
1908        self.tables()
1909            .address_balances
1910            .safe_iter_with_bounds(Some(start_key), None)
1911            .map(|result| result.expect("iterator db error"))
1912            .take_while(move |(key, _)| key.0 == owner)
1913            .map(|(key, _)| key.1)
1914    }
1915
1916    pub fn get_owned_coins_iterator(
1917        coin_index: &DBMap<CoinIndexKey2, CoinInfo>,
1918        owner: SuiAddress,
1919        coin_type_tag: Option<String>,
1920    ) -> SuiResult<impl Iterator<Item = (CoinIndexKey2, CoinInfo)> + '_> {
1921        let all_coins = coin_type_tag.is_none();
1922        let starting_coin_type =
1923            coin_type_tag.unwrap_or_else(|| String::from_utf8([0u8].to_vec()).unwrap());
1924        let start_key =
1925            CoinIndexKey2::new(owner, starting_coin_type.clone(), u64::MAX, ObjectID::ZERO);
1926        Ok(coin_index
1927            .safe_iter_with_bounds(Some(start_key), None)
1928            .map(|result| result.expect("iterator db error"))
1929            .take_while(move |(key, _)| {
1930                if key.owner != owner {
1931                    return false;
1932                }
1933                if !all_coins && starting_coin_type != key.coin_type {
1934                    return false;
1935                }
1936                true
1937            }))
1938    }
1939
1940    pub fn get_owned_coins_iterator_with_cursor(
1941        &self,
1942        owner: SuiAddress,
1943        cursor: (String, u64, ObjectID),
1944        limit: usize,
1945        one_coin_type_only: bool,
1946    ) -> SuiResult<impl Iterator<Item = (CoinIndexKey2, CoinInfo)> + '_> {
1947        let (starting_coin_type, inverted_balance, starting_object_id) = cursor;
1948        let start_key = CoinIndexKey2::new_from_cursor(
1949            owner,
1950            starting_coin_type.clone(),
1951            inverted_balance,
1952            starting_object_id,
1953        );
1954        Ok(self
1955            .tables
1956            .coin_index_2
1957            .safe_iter_with_bounds(Some(start_key), None)
1958            .map(|result| result.expect("iterator db error"))
1959            .filter(move |(key, _)| key.object_id != starting_object_id)
1960            .enumerate()
1961            .take_while(move |(index, (key, _))| {
1962                if *index >= limit {
1963                    return false;
1964                }
1965                if key.owner != owner {
1966                    return false;
1967                }
1968                if one_coin_type_only && starting_coin_type != key.coin_type {
1969                    return false;
1970                }
1971                true
1972            })
1973            .map(|(_index, (key, info))| (key, info)))
1974    }
1975
1976    /// starting_object_id can be used to implement pagination, where a client remembers the last
1977    /// object id of each page, and use it to query the next page.
1978    pub fn get_owner_objects_iterator(
1979        &self,
1980        owner: SuiAddress,
1981        starting_object_id: ObjectID,
1982        filter: Option<SuiObjectDataFilter>,
1983    ) -> SuiResult<impl Iterator<Item = ObjectInfo> + '_> {
1984        Ok(self
1985            .tables
1986            .owner_index
1987            // The object id 0 is the smallest possible
1988            .safe_iter_with_bounds(Some((owner, starting_object_id)), None)
1989            .map(|result| result.expect("iterator db error"))
1990            .skip(usize::from(starting_object_id != ObjectID::ZERO))
1991            .take_while(move |((address_owner, _), _)| address_owner == &owner)
1992            .filter(move |(_, o)| {
1993                if let Some(filter) = filter.as_ref() {
1994                    filter.matches(o)
1995                } else {
1996                    true
1997                }
1998            })
1999            .map(|(_, object_info)| object_info))
2000    }
2001
2002    pub fn insert_genesis_objects(&self, object_index_changes: ObjectIndexChanges) -> SuiResult {
2003        let mut batch = self.tables.owner_index.batch();
2004        batch.insert_batch(&self.tables.owner_index, object_index_changes.new_owners)?;
2005        batch.insert_batch(
2006            &self.tables.dynamic_field_index,
2007            object_index_changes.new_dynamic_fields,
2008        )?;
2009        batch.write()?;
2010        Ok(())
2011    }
2012
2013    pub fn is_empty(&self) -> bool {
2014        self.tables.owner_index.is_empty()
2015    }
2016
2017    pub fn checkpoint_db(&self, path: &Path) -> SuiResult {
2018        // We are checkpointing the whole db
2019        self.tables
2020            .transactions_from_addr
2021            .checkpoint_db(path)
2022            .map_err(Into::into)
2023    }
2024
2025    /// This method first gets the balance from `per_coin_type_balance` cache. On a cache miss, it
2026    /// gets the balance for passed in `coin_type` from the `all_balance` cache. Only on the second
2027    /// cache miss, we go to the database (expensive) and update the cache. Notice that db read is
2028    /// done with `spawn_blocking` as that is expected to block
2029    #[instrument(skip(self))]
2030    pub fn get_coin_object_balance(
2031        &self,
2032        owner: SuiAddress,
2033        coin_type: TypeTag,
2034    ) -> SuiResult<TotalBalance> {
2035        let force_disable_cache = read_size_from_env(ENV_VAR_DISABLE_INDEX_CACHE).unwrap_or(0) > 0;
2036        let cloned_coin_type = coin_type.clone();
2037        let metrics_cloned = self.metrics.clone();
2038        let coin_index_cloned = self.tables.coin_index_2.clone();
2039        if force_disable_cache {
2040            return Self::get_balance_from_db(
2041                metrics_cloned,
2042                coin_index_cloned,
2043                owner,
2044                cloned_coin_type,
2045            )
2046            .map_err(|e| {
2047                SuiErrorKind::ExecutionError(format!("Failed to read balance frm DB: {:?}", e))
2048                    .into()
2049            });
2050        }
2051
2052        self.metrics.balance_lookup_from_total.inc();
2053
2054        let balance = self
2055            .caches
2056            .per_coin_type_balance
2057            .get(&(owner, coin_type.clone()));
2058        if let Some(balance) = balance {
2059            return balance;
2060        }
2061        // cache miss, lookup in all balance cache
2062        let all_balance = self.caches.all_balances.get(&owner.clone());
2063        if let Some(Ok(all_balance)) = all_balance
2064            && let Some(balance) = all_balance.get(&coin_type)
2065        {
2066            return Ok(*balance);
2067        }
2068        let cloned_coin_type = coin_type.clone();
2069        let metrics_cloned = self.metrics.clone();
2070        let coin_index_cloned = self.tables.coin_index_2.clone();
2071        self.caches
2072            .per_coin_type_balance
2073            .get_with((owner, coin_type), move || {
2074                Self::get_balance_from_db(
2075                    metrics_cloned,
2076                    coin_index_cloned,
2077                    owner,
2078                    cloned_coin_type,
2079                )
2080                .map_err(|e| {
2081                    SuiErrorKind::ExecutionError(format!("Failed to read balance frm DB: {:?}", e))
2082                        .into()
2083                })
2084            })
2085    }
2086
2087    /// This method gets the balance for all coin types from the `all_balance` cache. On a cache miss,
2088    /// we go to the database (expensive) and update the cache. This cache is dual purpose in the
2089    /// sense that it not only serves `get_AllBalance()` calls but is also used for serving
2090    /// `get_Balance()` queries. Notice that db read is performed with `spawn_blocking` as that is
2091    /// expected to block
2092    #[instrument(skip(self))]
2093    pub fn get_all_coin_object_balances(
2094        &self,
2095        owner: SuiAddress,
2096    ) -> SuiResult<Arc<HashMap<TypeTag, TotalBalance>>> {
2097        let force_disable_cache = read_size_from_env(ENV_VAR_DISABLE_INDEX_CACHE).unwrap_or(0) > 0;
2098        let metrics_cloned = self.metrics.clone();
2099        let coin_index_cloned = self.tables.coin_index_2.clone();
2100        if force_disable_cache {
2101            return Self::get_all_balances_from_db(metrics_cloned, coin_index_cloned, owner)
2102                .map_err(|e| {
2103                    SuiErrorKind::ExecutionError(format!(
2104                        "Failed to read all balance from DB: {:?}",
2105                        e
2106                    ))
2107                    .into()
2108                });
2109        }
2110
2111        self.metrics.all_balance_lookup_from_total.inc();
2112        let metrics_cloned = self.metrics.clone();
2113        let coin_index_cloned = self.tables.coin_index_2.clone();
2114        self.caches.all_balances.get_with(owner, move || {
2115            Self::get_all_balances_from_db(metrics_cloned, coin_index_cloned, owner).map_err(|e| {
2116                SuiErrorKind::ExecutionError(format!("Failed to read all balance from DB: {:?}", e))
2117                    .into()
2118            })
2119        })
2120    }
2121
2122    /// Read balance for a `SuiAddress` and `CoinType` from the backend database
2123    #[instrument(skip_all)]
2124    pub fn get_balance_from_db(
2125        metrics: Arc<IndexStoreMetrics>,
2126        coin_index: DBMap<CoinIndexKey2, CoinInfo>,
2127        owner: SuiAddress,
2128        coin_type: TypeTag,
2129    ) -> SuiResult<TotalBalance> {
2130        metrics.balance_lookup_from_db.inc();
2131        let coin_type_str = coin_type.to_string();
2132        let coins =
2133            Self::get_owned_coins_iterator(&coin_index, owner, Some(coin_type_str.clone()))?;
2134
2135        let mut balance = 0i128;
2136        let mut num_coins = 0;
2137        for (_key, coin_info) in coins {
2138            balance += coin_info.balance as i128;
2139            num_coins += 1;
2140        }
2141        Ok(TotalBalance {
2142            balance,
2143            num_coins,
2144            address_balance: 0,
2145        })
2146    }
2147
2148    /// Read all balances for a `SuiAddress` from the backend database
2149    #[instrument(skip_all)]
2150    pub fn get_all_balances_from_db(
2151        metrics: Arc<IndexStoreMetrics>,
2152        coin_index: DBMap<CoinIndexKey2, CoinInfo>,
2153        owner: SuiAddress,
2154    ) -> SuiResult<Arc<HashMap<TypeTag, TotalBalance>>> {
2155        metrics.all_balance_lookup_from_db.inc();
2156        let mut balances: HashMap<TypeTag, TotalBalance> = HashMap::new();
2157        let coins = Self::get_owned_coins_iterator(&coin_index, owner, None)?
2158            .chunk_by(|(key, _coin)| key.coin_type.clone());
2159        for (coin_type, coins) in &coins {
2160            let mut total_balance = 0i128;
2161            let mut coin_object_count = 0;
2162            for (_, coin_info) in coins {
2163                total_balance += coin_info.balance as i128;
2164                coin_object_count += 1;
2165            }
2166            let coin_type =
2167                TypeTag::Struct(Box::new(parse_sui_struct_tag(&coin_type).map_err(|e| {
2168                    SuiErrorKind::ExecutionError(format!(
2169                        "Failed to parse event sender address: {:?}",
2170                        e
2171                    ))
2172                })?));
2173            balances.insert(
2174                coin_type,
2175                TotalBalance {
2176                    num_coins: coin_object_count,
2177                    balance: total_balance,
2178                    address_balance: 0,
2179                },
2180            );
2181        }
2182        Ok(Arc::new(balances))
2183    }
2184
2185    fn invalidate_per_coin_type_cache(
2186        &self,
2187        keys: impl IntoIterator<Item = (SuiAddress, TypeTag)>,
2188    ) -> SuiResult {
2189        self.caches.per_coin_type_balance.batch_invalidate(keys);
2190        Ok(())
2191    }
2192
2193    fn invalidate_all_balance_cache(
2194        &self,
2195        addresses: impl IntoIterator<Item = SuiAddress>,
2196    ) -> SuiResult {
2197        self.caches.all_balances.batch_invalidate(addresses);
2198        Ok(())
2199    }
2200
2201    fn update_per_coin_type_cache(
2202        &self,
2203        keys: impl IntoIterator<Item = ((SuiAddress, TypeTag), SuiResult<TotalBalance>)>,
2204    ) -> SuiResult {
2205        self.caches
2206            .per_coin_type_balance
2207            .batch_merge(keys, Self::merge_balance);
2208        Ok(())
2209    }
2210
2211    fn merge_balance(
2212        old_balance: &SuiResult<TotalBalance>,
2213        balance_delta: &SuiResult<TotalBalance>,
2214    ) -> SuiResult<TotalBalance> {
2215        if let Ok(old_balance) = old_balance {
2216            if let Ok(balance_delta) = balance_delta {
2217                Ok(TotalBalance {
2218                    balance: old_balance.balance + balance_delta.balance,
2219                    num_coins: old_balance.num_coins + balance_delta.num_coins,
2220                    address_balance: old_balance.address_balance,
2221                })
2222            } else {
2223                balance_delta.clone()
2224            }
2225        } else {
2226            old_balance.clone()
2227        }
2228    }
2229
2230    fn update_all_balance_cache(
2231        &self,
2232        keys: impl IntoIterator<Item = (SuiAddress, SuiResult<Arc<HashMap<TypeTag, TotalBalance>>>)>,
2233    ) -> SuiResult {
2234        self.caches
2235            .all_balances
2236            .batch_merge(keys, Self::merge_all_balance);
2237        Ok(())
2238    }
2239
2240    fn merge_all_balance(
2241        old_balance: &SuiResult<Arc<HashMap<TypeTag, TotalBalance>>>,
2242        balance_delta: &SuiResult<Arc<HashMap<TypeTag, TotalBalance>>>,
2243    ) -> SuiResult<Arc<HashMap<TypeTag, TotalBalance>>> {
2244        if let Ok(old_balance) = old_balance {
2245            if let Ok(balance_delta) = balance_delta {
2246                let mut new_balance = HashMap::new();
2247                for (key, value) in old_balance.iter() {
2248                    new_balance.insert(key.clone(), *value);
2249                }
2250                for (key, delta) in balance_delta.iter() {
2251                    let old = new_balance.entry(key.clone()).or_insert(TotalBalance {
2252                        balance: 0,
2253                        num_coins: 0,
2254                        address_balance: 0,
2255                    });
2256                    let new_total = TotalBalance {
2257                        balance: old.balance + delta.balance,
2258                        num_coins: old.num_coins + delta.num_coins,
2259                        address_balance: old.address_balance,
2260                    };
2261                    new_balance.insert(key.clone(), new_total);
2262                }
2263                Ok(Arc::new(new_balance))
2264            } else {
2265                balance_delta.clone()
2266            }
2267        } else {
2268            old_balance.clone()
2269        }
2270    }
2271}
2272
2273#[cfg(test)]
2274mod tests {
2275    use super::IndexStore;
2276    use super::ObjectIndexChanges;
2277    use move_core_types::account_address::AccountAddress;
2278    use prometheus::Registry;
2279    use std::collections::BTreeMap;
2280    use sui_types::base_types::{ObjectInfo, ObjectType, SuiAddress};
2281    use sui_types::digests::TransactionDigest;
2282    use sui_types::effects::TransactionEvents;
2283    use sui_types::gas_coin::GAS;
2284    use sui_types::object;
2285    use sui_types::object::Owner;
2286
2287    #[tokio::test]
2288    async fn test_index_cache() -> anyhow::Result<()> {
2289        // This test is going to invoke `index_tx()`where 10 coins each with balance 100
2290        // are going to be added to an address. The balance is then going to be read from the db
2291        // and the cache. It should be 1000. Then, we are going to delete 3 of those coins from
2292        // the address and invoke `index_tx()` again and read balance. The balance should be 700
2293        // and verified from both db and cache.
2294        // This tests make sure we are invalidating entries in the cache and always reading latest
2295        // balance.
2296        let dir = tempfile::tempdir().unwrap();
2297        let index_store = IndexStore::new_without_init(
2298            dir.path().to_path_buf(),
2299            &Registry::default(),
2300            Some(128),
2301            false,
2302        );
2303        let address: SuiAddress = AccountAddress::random().into();
2304        let mut written_objects = BTreeMap::new();
2305        let mut input_objects = BTreeMap::new();
2306        let mut object_map = BTreeMap::new();
2307
2308        let mut new_objects = vec![];
2309        for _i in 0..10 {
2310            let object = object::Object::new_gas_with_balance_and_owner_for_testing(100, address);
2311            new_objects.push((
2312                (address, object.id()),
2313                ObjectInfo {
2314                    object_id: object.id(),
2315                    version: object.version(),
2316                    digest: object.digest(),
2317                    type_: ObjectType::Struct(object.type_().unwrap().clone()),
2318                    owner: Owner::AddressOwner(address),
2319                    previous_transaction: object.previous_transaction,
2320                },
2321            ));
2322            object_map.insert(object.id(), object.clone());
2323            written_objects.insert(object.data.id(), object);
2324        }
2325        let object_index_changes = ObjectIndexChanges {
2326            deleted_owners: vec![],
2327            deleted_dynamic_fields: vec![],
2328            new_owners: new_objects,
2329            new_dynamic_fields: vec![],
2330        };
2331
2332        let tx_coins = (input_objects.clone(), written_objects.clone());
2333        let seq = index_store.allocate_sequence_number();
2334        let (raw_batch, cache_updates) = index_store.index_tx(
2335            seq,
2336            address,
2337            vec![].into_iter(),
2338            vec![].into_iter(),
2339            vec![].into_iter(),
2340            &TransactionEvents { data: vec![] },
2341            object_index_changes,
2342            &TransactionDigest::random(),
2343            1234,
2344            Some(tx_coins),
2345            vec![],
2346            false,
2347        )?;
2348        // Commit the batch so subsequent reads see the data
2349        let mut db_batch = index_store.new_db_batch();
2350        db_batch.concat(vec![raw_batch]).unwrap();
2351        index_store
2352            .commit_index_batch(db_batch, vec![cache_updates.into_inner()])
2353            .unwrap();
2354
2355        let balance_from_db = IndexStore::get_balance_from_db(
2356            index_store.metrics.clone(),
2357            index_store.tables.coin_index_2.clone(),
2358            address,
2359            GAS::type_tag(),
2360        )?;
2361        let balance = index_store.get_coin_object_balance(address, GAS::type_tag())?;
2362        assert_eq!(balance, balance_from_db);
2363        assert_eq!(balance.balance, 1000);
2364        assert_eq!(balance.num_coins, 10);
2365
2366        let all_balance = index_store.get_all_coin_object_balances(address)?;
2367        let balance = all_balance.get(&GAS::type_tag()).unwrap();
2368        assert_eq!(*balance, balance_from_db);
2369        assert_eq!(balance.balance, 1000);
2370        assert_eq!(balance.num_coins, 10);
2371
2372        written_objects.clear();
2373        let mut deleted_objects = vec![];
2374        for (id, object) in object_map.iter().take(3) {
2375            deleted_objects.push((address, *id));
2376            input_objects.insert(*id, object.to_owned());
2377        }
2378        let object_index_changes = ObjectIndexChanges {
2379            deleted_owners: deleted_objects.clone(),
2380            deleted_dynamic_fields: vec![],
2381            new_owners: vec![],
2382            new_dynamic_fields: vec![],
2383        };
2384        let tx_coins = (input_objects, written_objects);
2385        let seq = index_store.allocate_sequence_number();
2386        let (raw_batch, cache_updates) = index_store.index_tx(
2387            seq,
2388            address,
2389            vec![].into_iter(),
2390            vec![].into_iter(),
2391            vec![].into_iter(),
2392            &TransactionEvents { data: vec![] },
2393            object_index_changes,
2394            &TransactionDigest::random(),
2395            1234,
2396            Some(tx_coins),
2397            vec![],
2398            false,
2399        )?;
2400        let mut db_batch = index_store.new_db_batch();
2401        db_batch.concat(vec![raw_batch]).unwrap();
2402        index_store
2403            .commit_index_batch(db_batch, vec![cache_updates.into_inner()])
2404            .unwrap();
2405        let balance_from_db = IndexStore::get_balance_from_db(
2406            index_store.metrics.clone(),
2407            index_store.tables.coin_index_2.clone(),
2408            address,
2409            GAS::type_tag(),
2410        )?;
2411        let balance = index_store.get_coin_object_balance(address, GAS::type_tag())?;
2412        assert_eq!(balance, balance_from_db);
2413        assert_eq!(balance.balance, 700);
2414        assert_eq!(balance.num_coins, 7);
2415        // Invalidate per coin type balance cache and read from all balance cache to ensure
2416        // the balance matches
2417        index_store
2418            .caches
2419            .per_coin_type_balance
2420            .invalidate(&(address, GAS::type_tag()));
2421        let all_balance = index_store.get_all_coin_object_balances(address)?;
2422        assert_eq!(all_balance.get(&GAS::type_tag()).unwrap().balance, 700);
2423        assert_eq!(all_balance.get(&GAS::type_tag()).unwrap().num_coins, 7);
2424        let balance = index_store.get_coin_object_balance(address, GAS::type_tag())?;
2425        assert_eq!(balance, balance_from_db);
2426        assert_eq!(balance.balance, 700);
2427        assert_eq!(balance.num_coins, 7);
2428
2429        Ok(())
2430    }
2431
2432    #[tokio::test]
2433    async fn test_get_transaction_by_move_function() {
2434        use sui_types::base_types::ObjectID;
2435        use typed_store::Map;
2436
2437        let dir = tempfile::tempdir().unwrap();
2438        let index_store = IndexStore::new(
2439            dir.path().to_path_buf(),
2440            &Registry::default(),
2441            Some(128),
2442            false,
2443        );
2444        let db = &index_store.tables.transactions_by_move_function;
2445        db.insert(
2446            &(
2447                ObjectID::new([1; 32]),
2448                "mod".to_string(),
2449                "f".to_string(),
2450                0,
2451            ),
2452            &[0; 32].into(),
2453        )
2454        .unwrap();
2455        db.insert(
2456            &(
2457                ObjectID::new([1; 32]),
2458                "mod".to_string(),
2459                "Z".repeat(128),
2460                0,
2461            ),
2462            &[1; 32].into(),
2463        )
2464        .unwrap();
2465        db.insert(
2466            &(
2467                ObjectID::new([1; 32]),
2468                "mod".to_string(),
2469                "f".repeat(128),
2470                0,
2471            ),
2472            &[2; 32].into(),
2473        )
2474        .unwrap();
2475        db.insert(
2476            &(
2477                ObjectID::new([1; 32]),
2478                "mod".to_string(),
2479                "z".repeat(128),
2480                0,
2481            ),
2482            &[3; 32].into(),
2483        )
2484        .unwrap();
2485
2486        let mut v = index_store
2487            .get_transactions_by_move_function(
2488                ObjectID::new([1; 32]),
2489                Some("mod".to_string()),
2490                None,
2491                None,
2492                None,
2493                false,
2494            )
2495            .unwrap();
2496        let v_rev = index_store
2497            .get_transactions_by_move_function(
2498                ObjectID::new([1; 32]),
2499                Some("mod".to_string()),
2500                None,
2501                None,
2502                None,
2503                true,
2504            )
2505            .unwrap();
2506        v.reverse();
2507        assert_eq!(v, v_rev);
2508    }
2509}