From bdfa49ea002b09769334dc55cf36bcfb6073abb1 Mon Sep 17 00:00:00 2001 From: Emma Zhong Date: Tue, 30 Jul 2024 05:48:31 -0700 Subject: [PATCH] [gql][indexer] index chain identifier into its own table (#18825) This PR puts chain identifier into its own table so that queries of chain identifier does not depend on the existence of checkpoint 0 in the db, which may be pruned away. Tested against devnet locally and added a gql e2e test. --- Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [x] GraphQL: Added a way to always have `chainIdentifier` query return the correct chain ID even when pruning is enabled. - [ ] CLI: - [ ] Rust SDK: - [ ] REST API: --------- Co-authored-by: stefan-mysten <135084671+stefan-mysten@users.noreply.github.com> Co-authored-by: Ashok Menon --- .../tests/epoch/chain_identifier.exp | 16 ++ .../tests/epoch/chain_identifier.move | 12 + .../src/types/chain_identifier.rs | 12 +- .../down.sql | 2 + .../2024-07-13-003534_chain_identifier/up.sql | 6 + crates/sui-indexer/src/models/checkpoints.rs | 8 +- crates/sui-indexer/src/schema.rs | 7 + crates/sui-indexer/src/schema/mod.rs | 80 ++++++ crates/sui-indexer/src/schema/mysql.rs | 257 ++++++++++++++++++ .../sui-indexer/src/store/pg_indexer_store.rs | 28 +- 10 files changed, 415 insertions(+), 13 deletions(-) create mode 100644 crates/sui-graphql-e2e-tests/tests/epoch/chain_identifier.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/epoch/chain_identifier.move create mode 100644 crates/sui-indexer/migrations/pg/2024-07-13-003534_chain_identifier/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-07-13-003534_chain_identifier/up.sql create mode 100644 crates/sui-indexer/src/schema/mod.rs create mode 100644 crates/sui-indexer/src/schema/mysql.rs 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