diff --git a/crates/sui-graphql-e2e-tests/tests/epoch/chain_identifier.exp b/crates/sui-graphql-e2e-tests/tests/epoch/chain_identifier.exp new file mode 100644 index 00000000000000..c53d33a750392e --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/epoch/chain_identifier.exp @@ -0,0 +1,16 @@ +processed 3 tasks + +init: +C: object(0,0) + +task 1, line 7: +//# create-checkpoint +Checkpoint created: 1 + +task 2, lines 9-12: +//# run-graphql +Response: { + "data": { + "chainIdentifier": "8f58ad28" + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/epoch/chain_identifier.move b/crates/sui-graphql-e2e-tests/tests/epoch/chain_identifier.move new file mode 100644 index 00000000000000..22f11bcf6277e1 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/epoch/chain_identifier.move @@ -0,0 +1,12 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 48 --simulator --accounts C + + +//# create-checkpoint + +//# run-graphql +{ + chainIdentifier +} \ No newline at end of file diff --git a/crates/sui-graphql-rpc/src/types/chain_identifier.rs b/crates/sui-graphql-rpc/src/types/chain_identifier.rs index 1072d0a580f1a1..50fb3d8adaf81a 100644 --- a/crates/sui-graphql-rpc/src/types/chain_identifier.rs +++ b/crates/sui-graphql-rpc/src/types/chain_identifier.rs @@ -6,8 +6,8 @@ use crate::{ error::Error, }; use async_graphql::*; -use diesel::{ExpressionMethods, QueryDsl}; -use sui_indexer::schema::checkpoints; +use diesel::QueryDsl; +use sui_indexer::schema::chain_identifier; use sui_types::{ digests::ChainIdentifier as NativeChainIdentifier, messages_checkpoint::CheckpointDigest, }; @@ -17,15 +17,11 @@ pub(crate) struct ChainIdentifier; impl ChainIdentifier { /// Query the Chain Identifier from the DB. pub(crate) async fn query(db: &Db) -> Result { - use checkpoints::dsl; + use chain_identifier::dsl; let digest_bytes = db .execute(move |conn| { - conn.first(move || { - dsl::checkpoints - .select(dsl::checkpoint_digest) - .order_by(dsl::sequence_number.asc()) - }) + conn.first(move || dsl::chain_identifier.select(dsl::checkpoint_digest)) }) .await .map_err(|e| Error::Internal(format!("Failed to fetch genesis digest: {e}")))?; diff --git a/crates/sui-indexer/migrations/pg/2024-07-13-003534_chain_identifier/down.sql b/crates/sui-indexer/migrations/pg/2024-07-13-003534_chain_identifier/down.sql new file mode 100644 index 00000000000000..57f1de973b1d21 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-07-13-003534_chain_identifier/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE IF EXISTS chain_identifier; diff --git a/crates/sui-indexer/migrations/pg/2024-07-13-003534_chain_identifier/up.sql b/crates/sui-indexer/migrations/pg/2024-07-13-003534_chain_identifier/up.sql new file mode 100644 index 00000000000000..205e3a89f63e5b --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-07-13-003534_chain_identifier/up.sql @@ -0,0 +1,6 @@ +-- Your SQL goes here +CREATE TABLE chain_identifier +( + checkpoint_digest BYTEA NOT NULL, + PRIMARY KEY(checkpoint_digest) +); diff --git a/crates/sui-indexer/src/models/checkpoints.rs b/crates/sui-indexer/src/models/checkpoints.rs index 60735cf498f797..ff4b7de6704b85 100644 --- a/crates/sui-indexer/src/models/checkpoints.rs +++ b/crates/sui-indexer/src/models/checkpoints.rs @@ -9,9 +9,15 @@ use sui_types::digests::CheckpointDigest; use sui_types::gas::GasCostSummary; use crate::errors::IndexerError; -use crate::schema::checkpoints; +use crate::schema::{chain_identifier, checkpoints, pruner_cp_watermark}; use crate::types::IndexedCheckpoint; +#[derive(Queryable, Insertable, Selectable, Debug, Clone, Default)] +#[diesel(table_name = chain_identifier)] +pub struct StoredChainIdentifier { + pub checkpoint_digest: Vec, +} + #[derive(Queryable, Insertable, Selectable, Debug, Clone, Default)] #[diesel(table_name = checkpoints)] pub struct StoredCheckpoint { diff --git a/crates/sui-indexer/src/schema.rs b/crates/sui-indexer/src/schema.rs index 7193ce053355e9..c90603f44b6bfc 100644 --- a/crates/sui-indexer/src/schema.rs +++ b/crates/sui-indexer/src/schema.rs @@ -2,6 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 // @generated automatically by Diesel CLI. +diesel::table! { + chain_identifier (checkpoint_digest) { + checkpoint_digest -> Bytea, + } +} + diesel::table! { checkpoints (sequence_number) { sequence_number -> Int8, @@ -276,6 +282,7 @@ diesel::table! { macro_rules! for_all_tables { ($action:path) => { $action!( + chain_identifier, checkpoints, epochs, events, diff --git a/crates/sui-indexer/src/schema/mod.rs b/crates/sui-indexer/src/schema/mod.rs new file mode 100644 index 00000000000000..d1d408d76a3075 --- /dev/null +++ b/crates/sui-indexer/src/schema/mod.rs @@ -0,0 +1,80 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#![allow(clippy::all)] + +#[cfg(feature = "mysql-feature")] +#[cfg(not(feature = "postgres-feature"))] +mod mysql; + +#[cfg(feature = "postgres-feature")] +mod pg; + +#[cfg(feature = "postgres-feature")] +mod inner { + pub use crate::schema::pg::chain_identifier; + pub use crate::schema::pg::checkpoints; + pub use crate::schema::pg::display; + pub use crate::schema::pg::epochs; + pub use crate::schema::pg::events; + pub use crate::schema::pg::objects; + pub use crate::schema::pg::objects_history; + pub use crate::schema::pg::objects_snapshot; + pub use crate::schema::pg::packages; + pub use crate::schema::pg::pruner_cp_watermark; + pub use crate::schema::pg::transactions; + pub use crate::schema::pg::tx_calls; + pub use crate::schema::pg::tx_changed_objects; + pub use crate::schema::pg::tx_digests; + pub use crate::schema::pg::tx_input_objects; + pub use crate::schema::pg::tx_recipients; + pub use crate::schema::pg::tx_senders; +} + +#[cfg(feature = "mysql-feature")] +#[cfg(not(feature = "postgres-feature"))] +mod inner { + pub use crate::schema::mysql::chain_identifier; + pub use crate::schema::mysql::checkpoints; + pub use crate::schema::mysql::display; + pub use crate::schema::mysql::epochs; + pub use crate::schema::mysql::events; + pub use crate::schema::mysql::objects; + pub use crate::schema::mysql::objects_history; + pub use crate::schema::mysql::objects_snapshot; + pub use crate::schema::mysql::packages; + pub use crate::schema::mysql::pruner_cp_watermark; + pub use crate::schema::mysql::transactions; + pub use crate::schema::mysql::tx_calls; + pub use crate::schema::mysql::tx_changed_objects; + pub use crate::schema::mysql::tx_digests; + pub use crate::schema::mysql::tx_input_objects; + pub use crate::schema::mysql::tx_recipients; + pub use crate::schema::mysql::tx_senders; +} + +pub use inner::chain_identifier; +pub use inner::checkpoints; +pub use inner::display; +pub use inner::epochs; +pub use inner::events; +pub use inner::objects; +pub use inner::objects_history; +pub use inner::objects_snapshot; +pub use inner::packages; +pub use inner::pruner_cp_watermark; +pub use inner::transactions; +pub use inner::tx_calls; +pub use inner::tx_changed_objects; +pub use inner::tx_digests; +pub use inner::tx_input_objects; +pub use inner::tx_recipients; +pub use inner::tx_senders; + +// Postgres only tables +#[cfg(feature = "postgres-feature")] +pub use crate::schema::pg::events_partition_0; +#[cfg(feature = "postgres-feature")] +pub use crate::schema::pg::objects_history_partition_0; +#[cfg(feature = "postgres-feature")] +pub use crate::schema::pg::transactions_partition_0; diff --git a/crates/sui-indexer/src/schema/mysql.rs b/crates/sui-indexer/src/schema/mysql.rs new file mode 100644 index 00000000000000..10cdc089c8884b --- /dev/null +++ b/crates/sui-indexer/src/schema/mysql.rs @@ -0,0 +1,257 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// @generated automatically by Diesel CLI. + +diesel::table! { + chain_identifier (checkpoint_digest) { + checkpoint_digest -> Blob, + } +} + +diesel::table! { + checkpoints (sequence_number) { + sequence_number -> Bigint, + checkpoint_digest -> Blob, + epoch -> Bigint, + network_total_transactions -> Bigint, + previous_checkpoint_digest -> Nullable, + end_of_epoch -> Bool, + tx_digests -> Json, + timestamp_ms -> Bigint, + total_gas_cost -> Bigint, + computation_cost -> Bigint, + storage_cost -> Bigint, + storage_rebate -> Bigint, + non_refundable_storage_fee -> Bigint, + checkpoint_commitments -> Mediumblob, + validator_signature -> Blob, + end_of_epoch_data -> Nullable, + } +} + +diesel::table! { + display (object_type) { + object_type -> Text, + id -> Blob, + version -> Smallint, + bcs -> Mediumblob, + } +} + +diesel::table! { + epochs (epoch) { + epoch -> Bigint, + first_checkpoint_id -> Bigint, + epoch_start_timestamp -> Bigint, + reference_gas_price -> Bigint, + protocol_version -> Bigint, + total_stake -> Bigint, + storage_fund_balance -> Bigint, + system_state -> Mediumblob, + epoch_total_transactions -> Nullable, + last_checkpoint_id -> Nullable, + epoch_end_timestamp -> Nullable, + storage_fund_reinvestment -> Nullable, + storage_charge -> Nullable, + storage_rebate -> Nullable, + stake_subsidy_amount -> Nullable, + total_gas_fees -> Nullable, + total_stake_rewards_distributed -> Nullable, + leftover_storage_fund_inflow -> Nullable, + epoch_commitments -> Nullable, + } +} + +diesel::table! { + events (tx_sequence_number, event_sequence_number, checkpoint_sequence_number) { + tx_sequence_number -> Bigint, + event_sequence_number -> Bigint, + transaction_digest -> Blob, + checkpoint_sequence_number -> Bigint, + senders -> Json, + package -> Blob, + module -> Text, + event_type -> Text, + event_type_package -> Blob, + event_type_module -> Text, + event_type_name -> Text, + timestamp_ms -> Bigint, + bcs -> Mediumblob, + } +} + +diesel::table! { + objects (object_id) { + object_id -> Blob, + object_version -> Bigint, + object_digest -> Blob, + checkpoint_sequence_number -> Bigint, + owner_type -> Smallint, + owner_id -> Nullable, + object_type -> Nullable, + object_type_package -> Nullable, + object_type_module -> Nullable, + object_type_name -> Nullable, + serialized_object -> Mediumblob, + coin_type -> Nullable, + coin_balance -> Nullable, + df_kind -> Nullable, + df_name -> Nullable, + df_object_type -> Nullable, + df_object_id -> Nullable, + } +} + +diesel::table! { + objects_history (checkpoint_sequence_number, object_id, object_version) { + object_id -> Blob, + object_version -> Bigint, + object_status -> Smallint, + object_digest -> Nullable, + checkpoint_sequence_number -> Bigint, + owner_type -> Nullable, + owner_id -> Nullable, + object_type -> Nullable, + object_type_package -> Nullable, + object_type_module -> Nullable, + object_type_name -> Nullable, + serialized_object -> Nullable, + coin_type -> Nullable, + coin_balance -> Nullable, + df_kind -> Nullable, + df_name -> Nullable, + df_object_type -> Nullable, + df_object_id -> Nullable, + } +} + +diesel::table! { + objects_snapshot (object_id) { + object_id -> Blob, + object_version -> Bigint, + object_status -> Smallint, + object_digest -> Nullable, + checkpoint_sequence_number -> Bigint, + owner_type -> Nullable, + owner_id -> Nullable, + object_type -> Nullable, + object_type_package -> Nullable, + object_type_module -> Nullable, + object_type_name -> Nullable, + serialized_object -> Nullable, + coin_type -> Nullable, + coin_balance -> Nullable, + df_kind -> Nullable, + df_name -> Nullable, + df_object_type -> Nullable, + df_object_id -> Nullable, + } +} + +diesel::table! { + packages (package_id) { + package_id -> Blob, + move_package -> Mediumblob, + } +} + +diesel::table! { + pruner_cp_watermark (checkpoint_sequence_number) { + checkpoint_sequence_number -> Bigint, + min_tx_sequence_number -> Bigint, + max_tx_sequence_number -> Bigint, + } +} + +diesel::table! { + transactions (tx_sequence_number, checkpoint_sequence_number) { + tx_sequence_number -> Bigint, + transaction_digest -> Blob, + raw_transaction -> Mediumblob, + raw_effects -> Mediumblob, + checkpoint_sequence_number -> Bigint, + timestamp_ms -> Bigint, + object_changes -> Json, + balance_changes -> Json, + events -> Json, + transaction_kind -> Smallint, + success_command_count -> Smallint, + } +} + +diesel::table! { + tx_calls (package, tx_sequence_number, cp_sequence_number) { + cp_sequence_number -> Bigint, + tx_sequence_number -> Bigint, + package -> Blob, + module -> Text, + func -> Text, + } +} + +diesel::table! { + tx_changed_objects (object_id, tx_sequence_number) { + cp_sequence_number -> Bigint, + tx_sequence_number -> Bigint, + object_id -> Blob, + } +} + +diesel::table! { + tx_digests (tx_digest) { + tx_digest -> Blob, + cp_sequence_number -> Bigint, + tx_sequence_number -> Bigint, + } +} + +diesel::table! { + tx_input_objects (object_id, tx_sequence_number, cp_sequence_number) { + cp_sequence_number -> Bigint, + tx_sequence_number -> Bigint, + object_id -> Blob, + } +} + +diesel::table! { + tx_recipients (recipient, tx_sequence_number) { + cp_sequence_number -> Bigint, + tx_sequence_number -> Bigint, + recipient -> Blob, + } +} + +diesel::table! { + tx_senders (sender, tx_sequence_number, cp_sequence_number) { + cp_sequence_number -> Bigint, + tx_sequence_number -> Bigint, + sender -> Blob, + } +} + +#[macro_export] +macro_rules! for_all_tables { + ($action:path) => { + $action!( + chain_identifier, + checkpoints, + epochs, + events, + objects, + objects_history, + objects_snapshot, + packages, + pruner_cp_watermark, + transactions, + tx_calls, + tx_changed_objects, + tx_digests, + tx_input_objects, + tx_recipients, + tx_senders + ); + }; +} +pub use for_all_tables; + +for_all_tables!(diesel::allow_tables_to_appear_in_same_query); diff --git a/crates/sui-indexer/src/store/pg_indexer_store.rs b/crates/sui-indexer/src/store/pg_indexer_store.rs index 7f2bc9767a1fc8..4716b974c05061 100644 --- a/crates/sui-indexer/src/store/pg_indexer_store.rs +++ b/crates/sui-indexer/src/store/pg_indexer_store.rs @@ -28,6 +28,7 @@ use crate::errors::{Context, IndexerError}; use crate::handlers::EpochToCommit; use crate::handlers::TransactionObjectChangesToCommit; use crate::metrics::IndexerMetrics; +use crate::models::checkpoints::StoredChainIdentifier; use crate::models::checkpoints::StoredCheckpoint; use crate::models::display::StoredDisplay; use crate::models::epoch::StoredEpochInfo; @@ -39,9 +40,9 @@ use crate::models::objects::{ use crate::models::packages::StoredPackage; use crate::models::transactions::StoredTransaction; use crate::schema::{ - checkpoints, display, epochs, events, objects, objects_history, objects_snapshot, packages, - transactions, tx_calls, tx_changed_objects, tx_digests, tx_input_objects, tx_recipients, - tx_senders, + chain_identifier, checkpoints, display, epochs, events, objects, objects_history, + objects_snapshot, packages, transactions, tx_calls, tx_changed_objects, tx_digests, + tx_input_objects, tx_recipients, tx_senders, }; use crate::types::{IndexedCheckpoint, IndexedEvent, IndexedPackage, IndexedTransaction, TxIndex}; use crate::{read_only_blocking, transactional_blocking_with_retry}; @@ -468,8 +469,27 @@ impl PgIndexerStore { } fn persist_checkpoints(&self, checkpoints: Vec) -> Result<(), IndexerError> { - if checkpoints.is_empty() { + let Some(first_checkpoint) = checkpoints.first() else { return Ok(()); + }; + + // If the first checkpoint has sequence number 0, we need to persist the digest as + // chain identifier. + if first_checkpoint.sequence_number == 0 { + transactional_blocking_with_retry!( + &self.blocking_cp, + |conn| { + let checkpoint_digest = + first_checkpoint.checkpoint_digest.into_inner().to_vec(); + insert_or_ignore_into!( + chain_identifier::table, + StoredChainIdentifier { checkpoint_digest }, + conn + ); + Ok::<(), IndexerError>(()) + }, + PG_DB_COMMIT_SLEEP_DURATION + )?; } let guard = self .metrics