1use 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 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: u64,
98 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
197pub 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#[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 meta: DBMap<(), MetadataInfo>,
229
230 transactions_from_addr: DBMap<(SuiAddress, TxSequenceNumber), TransactionDigest>,
232
233 transactions_to_addr: DBMap<(SuiAddress, TxSequenceNumber), TransactionDigest>,
235
236 #[deprecated]
238 transactions_by_input_object_id: DBMap<(ObjectID, TxSequenceNumber), TransactionDigest>,
239
240 #[deprecated]
242 transactions_by_mutated_object_id: DBMap<(ObjectID, TxSequenceNumber), TransactionDigest>,
243
244 transactions_by_move_function:
246 DBMap<(ObjectID, String, String, TxSequenceNumber), TransactionDigest>,
247
248 transaction_order: DBMap<TxSequenceNumber, TransactionDigest>,
250
251 transactions_seq: DBMap<TransactionDigest, TxSequenceNumber>,
253
254 owner_index: DBMap<OwnerIndexKey, ObjectInfo>,
259
260 coin_index_2: DBMap<CoinIndexKey2, CoinInfo>,
261 address_balances: DBMap<(SuiAddress, TypeTag), ()>,
263
264 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 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 type EventKey = (TransactionEventsDigest, TransactionDigest);
377 fn strip_timestamp(ei: EventIndex) -> EventKey {
378 (ei.0, ei.1)
379 }
380
381 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 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 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 {
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 {
496 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 {
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 }
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 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 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(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 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 let coin_delete_keys = input_coins
944 .values()
945 .filter_map(|object| {
946 let Owner::AddressOwner(owner) = object.owner() else {
948 return None;
949 };
950
951 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 let coin_add_keys = written_coins
982 .values()
983 .filter_map(|object| {
984 let Owner::AddressOwner(owner) = object.owner() else {
986 return None;
987 };
988
989 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 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 let cache_updates = self.index_coin(
1129 digest,
1130 &mut batch,
1131 &object_index_changes,
1132 tx_coins,
1133 acquire_locks,
1134 )?;
1135
1136 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 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 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 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 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 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(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(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 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 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(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(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 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 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 .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 self.tables
2020 .transactions_from_addr
2021 .checkpoint_db(path)
2022 .map_err(Into::into)
2023 }
2024
2025 #[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 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 #[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 #[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 #[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 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 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 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}