sui_indexer/
types.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use move_core_types::language_storage::StructTag;
5use rand::Rng;
6use serde::{Deserialize, Serialize};
7use serde_with::serde_as;
8use sui_json_rpc_types::{
9    ObjectChange, SuiTransactionBlockResponse, SuiTransactionBlockResponseOptions,
10};
11use sui_types::base_types::{ObjectDigest, SequenceNumber};
12use sui_types::base_types::{ObjectID, SuiAddress};
13use sui_types::crypto::AggregateAuthoritySignature;
14use sui_types::digests::TransactionDigest;
15use sui_types::dynamic_field::DynamicFieldType;
16use sui_types::effects::TransactionEffects;
17use sui_types::messages_checkpoint::{
18    CertifiedCheckpointSummary, CheckpointCommitment, CheckpointContents, CheckpointDigest,
19    CheckpointSequenceNumber, EndOfEpochData,
20};
21use sui_types::move_package::MovePackage;
22use sui_types::object::{Object, Owner};
23use sui_types::sui_serde::SuiStructTag;
24use sui_types::transaction::SenderSignedData;
25
26use crate::errors::IndexerError;
27
28pub type IndexerResult<T> = Result<T, IndexerError>;
29
30#[derive(Debug, Default)]
31pub struct IndexedCheckpoint {
32    // TODO: A lot of fields are now redundant with certified_checkpoint and checkpoint_contents.
33    pub sequence_number: u64,
34    pub checkpoint_digest: CheckpointDigest,
35    pub epoch: u64,
36    pub tx_digests: Vec<TransactionDigest>,
37    pub network_total_transactions: u64,
38    pub previous_checkpoint_digest: Option<CheckpointDigest>,
39    pub timestamp_ms: u64,
40    pub total_gas_cost: i64, // total gas cost could be negative
41    pub computation_cost: u64,
42    pub storage_cost: u64,
43    pub storage_rebate: u64,
44    pub non_refundable_storage_fee: u64,
45    pub checkpoint_commitments: Vec<CheckpointCommitment>,
46    pub validator_signature: AggregateAuthoritySignature,
47    pub successful_tx_num: usize,
48    pub end_of_epoch_data: Option<EndOfEpochData>,
49    pub end_of_epoch: bool,
50    pub min_tx_sequence_number: u64,
51    pub max_tx_sequence_number: u64,
52    // FIXME: Remove the Default derive and make these fields mandatory.
53    pub certified_checkpoint: Option<CertifiedCheckpointSummary>,
54    pub checkpoint_contents: Option<CheckpointContents>,
55}
56
57impl IndexedCheckpoint {
58    pub fn from_sui_checkpoint(
59        checkpoint: &CertifiedCheckpointSummary,
60        contents: &CheckpointContents,
61        successful_tx_num: usize,
62    ) -> Self {
63        let total_gas_cost = checkpoint.epoch_rolling_gas_cost_summary.computation_cost as i64
64            + checkpoint.epoch_rolling_gas_cost_summary.storage_cost as i64
65            - checkpoint.epoch_rolling_gas_cost_summary.storage_rebate as i64;
66        let tx_digests = contents.iter().map(|t| t.transaction).collect::<Vec<_>>();
67        let max_tx_sequence_number = checkpoint.network_total_transactions - 1;
68        // NOTE: + 1u64 first to avoid subtraction with overflow
69        let min_tx_sequence_number = max_tx_sequence_number + 1u64 - tx_digests.len() as u64;
70        let auth_sig = &checkpoint.auth_sig().signature;
71        Self {
72            sequence_number: checkpoint.sequence_number,
73            checkpoint_digest: *checkpoint.digest(),
74            epoch: checkpoint.epoch,
75            tx_digests,
76            previous_checkpoint_digest: checkpoint.previous_digest,
77            end_of_epoch_data: checkpoint.end_of_epoch_data.clone(),
78            end_of_epoch: checkpoint.end_of_epoch_data.clone().is_some(),
79            total_gas_cost,
80            computation_cost: checkpoint.epoch_rolling_gas_cost_summary.computation_cost,
81            storage_cost: checkpoint.epoch_rolling_gas_cost_summary.storage_cost,
82            storage_rebate: checkpoint.epoch_rolling_gas_cost_summary.storage_rebate,
83            non_refundable_storage_fee: checkpoint
84                .epoch_rolling_gas_cost_summary
85                .non_refundable_storage_fee,
86            successful_tx_num,
87            network_total_transactions: checkpoint.network_total_transactions,
88            timestamp_ms: checkpoint.timestamp_ms,
89            validator_signature: auth_sig.clone(),
90            checkpoint_commitments: checkpoint.checkpoint_commitments.clone(),
91            min_tx_sequence_number,
92            max_tx_sequence_number,
93            certified_checkpoint: Some(checkpoint.clone()),
94            checkpoint_contents: Some(contents.clone()),
95        }
96    }
97}
98
99#[derive(Debug, Clone)]
100pub struct IndexedEvent {
101    pub tx_sequence_number: u64,
102    pub event_sequence_number: u64,
103    pub checkpoint_sequence_number: u64,
104    pub transaction_digest: TransactionDigest,
105    pub sender: SuiAddress,
106    pub package: ObjectID,
107    pub module: String,
108    pub event_type: String,
109    pub event_type_package: ObjectID,
110    pub event_type_module: String,
111    /// Struct name of the event, without type parameters.
112    pub event_type_name: String,
113    pub bcs: Vec<u8>,
114    pub timestamp_ms: u64,
115}
116
117impl IndexedEvent {
118    pub fn from_event(
119        tx_sequence_number: u64,
120        event_sequence_number: u64,
121        checkpoint_sequence_number: u64,
122        transaction_digest: TransactionDigest,
123        event: &sui_types::event::Event,
124        timestamp_ms: u64,
125    ) -> Self {
126        Self {
127            tx_sequence_number,
128            event_sequence_number,
129            checkpoint_sequence_number,
130            transaction_digest,
131            sender: event.sender,
132            package: event.package_id,
133            module: event.transaction_module.to_string(),
134            event_type: event.type_.to_canonical_string(/* with_prefix */ true),
135            event_type_package: event.type_.address.into(),
136            event_type_module: event.type_.module.to_string(),
137            event_type_name: event.type_.name.to_string(),
138            bcs: event.contents.clone(),
139            timestamp_ms,
140        }
141    }
142}
143
144#[derive(Debug, Clone)]
145pub struct EventIndex {
146    pub tx_sequence_number: u64,
147    pub event_sequence_number: u64,
148    pub sender: SuiAddress,
149    pub emit_package: ObjectID,
150    pub emit_module: String,
151    pub type_package: ObjectID,
152    pub type_module: String,
153    /// Struct name of the event, without type parameters.
154    pub type_name: String,
155    /// Type instantiation of the event, with type name and type parameters, if any.
156    pub type_instantiation: String,
157}
158
159// for ingestion test
160impl EventIndex {
161    pub fn random() -> Self {
162        let mut rng = rand::thread_rng();
163        EventIndex {
164            tx_sequence_number: rng.r#gen(),
165            event_sequence_number: rng.r#gen(),
166            sender: SuiAddress::random_for_testing_only(),
167            emit_package: ObjectID::random(),
168            emit_module: rng.r#gen::<u64>().to_string(),
169            type_package: ObjectID::random(),
170            type_module: rng.r#gen::<u64>().to_string(),
171            type_name: rng.r#gen::<u64>().to_string(),
172            type_instantiation: rng.r#gen::<u64>().to_string(),
173        }
174    }
175}
176
177impl EventIndex {
178    pub fn from_event(
179        tx_sequence_number: u64,
180        event_sequence_number: u64,
181        event: &sui_types::event::Event,
182    ) -> Self {
183        let type_instantiation = event
184            .type_
185            .to_canonical_string(/* with_prefix */ true)
186            .splitn(3, "::")
187            .collect::<Vec<_>>()[2]
188            .to_string();
189        Self {
190            tx_sequence_number,
191            event_sequence_number,
192            sender: event.sender,
193            emit_package: event.package_id,
194            emit_module: event.transaction_module.to_string(),
195            type_package: event.type_.address.into(),
196            type_module: event.type_.module.to_string(),
197            type_name: event.type_.name.to_string(),
198            type_instantiation,
199        }
200    }
201}
202
203#[derive(Debug, Copy, Clone)]
204pub enum OwnerType {
205    Immutable = 0,
206    Address = 1,
207    Object = 2,
208    Shared = 3,
209}
210
211pub enum ObjectStatus {
212    Active = 0,
213    WrappedOrDeleted = 1,
214}
215
216impl TryFrom<i16> for ObjectStatus {
217    type Error = IndexerError;
218
219    fn try_from(value: i16) -> Result<Self, Self::Error> {
220        Ok(match value {
221            0 => ObjectStatus::Active,
222            1 => ObjectStatus::WrappedOrDeleted,
223            value => {
224                return Err(IndexerError::PersistentStorageDataCorruptionError(format!(
225                    "{value} as ObjectStatus"
226                )));
227            }
228        })
229    }
230}
231
232impl TryFrom<i16> for OwnerType {
233    type Error = IndexerError;
234
235    fn try_from(value: i16) -> Result<Self, Self::Error> {
236        Ok(match value {
237            0 => OwnerType::Immutable,
238            1 => OwnerType::Address,
239            2 => OwnerType::Object,
240            3 => OwnerType::Shared,
241            value => {
242                return Err(IndexerError::PersistentStorageDataCorruptionError(format!(
243                    "{value} as OwnerType"
244                )));
245            }
246        })
247    }
248}
249
250// Returns owner_type, owner_address
251pub fn owner_to_owner_info(owner: &Owner) -> (OwnerType, Option<SuiAddress>) {
252    match owner {
253        Owner::AddressOwner(address) => (OwnerType::Address, Some(*address)),
254        Owner::ObjectOwner(address) => (OwnerType::Object, Some(*address)),
255        Owner::Shared { .. } => (OwnerType::Shared, None),
256        Owner::Immutable => (OwnerType::Immutable, None),
257        Owner::ConsensusAddressOwner { owner, .. } => (OwnerType::Address, Some(*owner)),
258    }
259}
260
261#[derive(Debug, Copy, Clone)]
262pub enum DynamicFieldKind {
263    DynamicField = 0,
264    DynamicObject = 1,
265}
266
267#[derive(Clone, Debug)]
268pub struct IndexedObject {
269    pub checkpoint_sequence_number: CheckpointSequenceNumber,
270    pub object: Object,
271    pub df_kind: Option<DynamicFieldType>,
272}
273
274impl IndexedObject {
275    pub fn random() -> Self {
276        let mut rng = rand::thread_rng();
277        let random_address = SuiAddress::random_for_testing_only();
278        IndexedObject {
279            checkpoint_sequence_number: rng.r#gen(),
280            object: Object::with_owner_for_testing(random_address),
281            df_kind: {
282                let random_value = rng.gen_range(0..3);
283                match random_value {
284                    0 => Some(DynamicFieldType::DynamicField),
285                    1 => Some(DynamicFieldType::DynamicObject),
286                    _ => None,
287                }
288            },
289        }
290    }
291}
292
293impl IndexedObject {
294    pub fn from_object(
295        checkpoint_sequence_number: CheckpointSequenceNumber,
296        object: Object,
297        df_kind: Option<DynamicFieldType>,
298    ) -> Self {
299        Self {
300            checkpoint_sequence_number,
301            object,
302            df_kind,
303        }
304    }
305}
306
307#[derive(Clone, Debug)]
308pub struct IndexedDeletedObject {
309    pub object_id: ObjectID,
310    pub object_version: u64,
311    pub checkpoint_sequence_number: u64,
312}
313
314impl IndexedDeletedObject {
315    pub fn random() -> Self {
316        let mut rng = rand::thread_rng();
317        IndexedDeletedObject {
318            object_id: ObjectID::random(),
319            object_version: rng.r#gen(),
320            checkpoint_sequence_number: rng.r#gen(),
321        }
322    }
323}
324
325#[derive(Debug)]
326pub struct IndexedPackage {
327    pub package_id: ObjectID,
328    pub move_package: MovePackage,
329    pub checkpoint_sequence_number: u64,
330}
331
332#[derive(Debug, Clone)]
333pub enum TransactionKind {
334    SystemTransaction = 0,
335    ProgrammableTransaction = 1,
336}
337
338#[derive(Debug, Clone)]
339pub struct IndexedTransaction {
340    pub tx_sequence_number: u64,
341    pub tx_digest: TransactionDigest,
342    pub sender_signed_data: SenderSignedData,
343    pub effects: TransactionEffects,
344    pub checkpoint_sequence_number: u64,
345    pub timestamp_ms: u64,
346    pub object_changes: Vec<IndexedObjectChange>,
347    pub balance_change: Vec<sui_json_rpc_types::BalanceChange>,
348    pub events: Vec<sui_types::event::Event>,
349    pub transaction_kind: TransactionKind,
350    pub successful_tx_num: u64,
351}
352
353#[derive(Debug, Clone)]
354pub struct TxIndex {
355    pub tx_sequence_number: u64,
356    pub tx_kind: TransactionKind,
357    pub transaction_digest: TransactionDigest,
358    pub checkpoint_sequence_number: u64,
359    pub input_objects: Vec<ObjectID>,
360    pub changed_objects: Vec<ObjectID>,
361    pub affected_objects: Vec<ObjectID>,
362    pub payers: Vec<SuiAddress>,
363    pub sender: SuiAddress,
364    pub recipients: Vec<SuiAddress>,
365    pub move_calls: Vec<(ObjectID, String, String)>,
366}
367
368impl TxIndex {
369    pub fn random() -> Self {
370        let mut rng = rand::thread_rng();
371        TxIndex {
372            tx_sequence_number: rng.r#gen(),
373            tx_kind: if rng.gen_bool(0.5) {
374                TransactionKind::SystemTransaction
375            } else {
376                TransactionKind::ProgrammableTransaction
377            },
378            transaction_digest: TransactionDigest::random(),
379            checkpoint_sequence_number: rng.r#gen(),
380            input_objects: (0..1000).map(|_| ObjectID::random()).collect(),
381            changed_objects: (0..1000).map(|_| ObjectID::random()).collect(),
382            affected_objects: (0..1000).map(|_| ObjectID::random()).collect(),
383            payers: (0..rng.gen_range(0..100))
384                .map(|_| SuiAddress::random_for_testing_only())
385                .collect(),
386            sender: SuiAddress::random_for_testing_only(),
387            recipients: (0..rng.gen_range(0..1000))
388                .map(|_| SuiAddress::random_for_testing_only())
389                .collect(),
390            move_calls: (0..rng.gen_range(0..1000))
391                .map(|_| {
392                    (
393                        ObjectID::random(),
394                        rng.r#gen::<u64>().to_string(),
395                        rng.r#gen::<u64>().to_string(),
396                    )
397                })
398                .collect(),
399        }
400    }
401}
402
403// ObjectChange is not bcs deserializable, IndexedObjectChange is.
404#[serde_as]
405#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
406pub enum IndexedObjectChange {
407    Published {
408        package_id: ObjectID,
409        version: SequenceNumber,
410        digest: ObjectDigest,
411        modules: Vec<String>,
412    },
413    Transferred {
414        sender: SuiAddress,
415        recipient: Owner,
416        #[serde_as(as = "SuiStructTag")]
417        object_type: StructTag,
418        object_id: ObjectID,
419        version: SequenceNumber,
420        digest: ObjectDigest,
421    },
422    /// Object mutated.
423    Mutated {
424        sender: SuiAddress,
425        owner: Owner,
426        #[serde_as(as = "SuiStructTag")]
427        object_type: StructTag,
428        object_id: ObjectID,
429        version: SequenceNumber,
430        previous_version: SequenceNumber,
431        digest: ObjectDigest,
432    },
433    /// Delete object
434    Deleted {
435        sender: SuiAddress,
436        #[serde_as(as = "SuiStructTag")]
437        object_type: StructTag,
438        object_id: ObjectID,
439        version: SequenceNumber,
440    },
441    /// Wrapped object
442    Wrapped {
443        sender: SuiAddress,
444        #[serde_as(as = "SuiStructTag")]
445        object_type: StructTag,
446        object_id: ObjectID,
447        version: SequenceNumber,
448    },
449    /// New object creation
450    Created {
451        sender: SuiAddress,
452        owner: Owner,
453        #[serde_as(as = "SuiStructTag")]
454        object_type: StructTag,
455        object_id: ObjectID,
456        version: SequenceNumber,
457        digest: ObjectDigest,
458    },
459}
460
461impl From<ObjectChange> for IndexedObjectChange {
462    fn from(oc: ObjectChange) -> Self {
463        match oc {
464            ObjectChange::Published {
465                package_id,
466                version,
467                digest,
468                modules,
469            } => Self::Published {
470                package_id,
471                version,
472                digest,
473                modules,
474            },
475            ObjectChange::Transferred {
476                sender,
477                recipient,
478                object_type,
479                object_id,
480                version,
481                digest,
482            } => Self::Transferred {
483                sender,
484                recipient,
485                object_type,
486                object_id,
487                version,
488                digest,
489            },
490            ObjectChange::Mutated {
491                sender,
492                owner,
493                object_type,
494                object_id,
495                version,
496                previous_version,
497                digest,
498            } => Self::Mutated {
499                sender,
500                owner,
501                object_type,
502                object_id,
503                version,
504                previous_version,
505                digest,
506            },
507            ObjectChange::Deleted {
508                sender,
509                object_type,
510                object_id,
511                version,
512            } => Self::Deleted {
513                sender,
514                object_type,
515                object_id,
516                version,
517            },
518            ObjectChange::Wrapped {
519                sender,
520                object_type,
521                object_id,
522                version,
523            } => Self::Wrapped {
524                sender,
525                object_type,
526                object_id,
527                version,
528            },
529            ObjectChange::Created {
530                sender,
531                owner,
532                object_type,
533                object_id,
534                version,
535                digest,
536            } => Self::Created {
537                sender,
538                owner,
539                object_type,
540                object_id,
541                version,
542                digest,
543            },
544        }
545    }
546}
547
548impl From<IndexedObjectChange> for ObjectChange {
549    fn from(val: IndexedObjectChange) -> Self {
550        match val {
551            IndexedObjectChange::Published {
552                package_id,
553                version,
554                digest,
555                modules,
556            } => ObjectChange::Published {
557                package_id,
558                version,
559                digest,
560                modules,
561            },
562            IndexedObjectChange::Transferred {
563                sender,
564                recipient,
565                object_type,
566                object_id,
567                version,
568                digest,
569            } => ObjectChange::Transferred {
570                sender,
571                recipient,
572                object_type,
573                object_id,
574                version,
575                digest,
576            },
577            IndexedObjectChange::Mutated {
578                sender,
579                owner,
580                object_type,
581                object_id,
582                version,
583                previous_version,
584                digest,
585            } => ObjectChange::Mutated {
586                sender,
587                owner,
588                object_type,
589                object_id,
590                version,
591                previous_version,
592                digest,
593            },
594            IndexedObjectChange::Deleted {
595                sender,
596                object_type,
597                object_id,
598                version,
599            } => ObjectChange::Deleted {
600                sender,
601                object_type,
602                object_id,
603                version,
604            },
605            IndexedObjectChange::Wrapped {
606                sender,
607                object_type,
608                object_id,
609                version,
610            } => ObjectChange::Wrapped {
611                sender,
612                object_type,
613                object_id,
614                version,
615            },
616            IndexedObjectChange::Created {
617                sender,
618                owner,
619                object_type,
620                object_id,
621                version,
622                digest,
623            } => ObjectChange::Created {
624                sender,
625                owner,
626                object_type,
627                object_id,
628                version,
629                digest,
630            },
631        }
632    }
633}
634
635// SuiTransactionBlockResponseWithOptions is only used on the reading path
636pub struct SuiTransactionBlockResponseWithOptions {
637    pub response: SuiTransactionBlockResponse,
638    pub options: SuiTransactionBlockResponseOptions,
639}
640
641impl From<SuiTransactionBlockResponseWithOptions> for SuiTransactionBlockResponse {
642    fn from(value: SuiTransactionBlockResponseWithOptions) -> Self {
643        let SuiTransactionBlockResponseWithOptions { response, options } = value;
644
645        SuiTransactionBlockResponse {
646            digest: response.digest,
647            transaction: options.show_input.then_some(response.transaction).flatten(),
648            raw_transaction: if options.show_raw_input {
649                response.raw_transaction
650            } else {
651                vec![]
652            },
653            effects: options.show_effects.then_some(response.effects).flatten(),
654            events: options.show_events.then_some(response.events).flatten(),
655            object_changes: options
656                .show_object_changes
657                .then_some(response.object_changes)
658                .flatten(),
659            balance_changes: options
660                .show_balance_changes
661                .then_some(response.balance_changes)
662                .flatten(),
663            timestamp_ms: response.timestamp_ms,
664            confirmed_local_execution: response.confirmed_local_execution,
665            checkpoint: response.checkpoint,
666            errors: vec![],
667            raw_effects: if options.show_raw_effects {
668                response.raw_effects
669            } else {
670                vec![]
671            },
672        }
673    }
674}