diff --git a/crates/iota-indexer/migrations/pg/2023-08-19-044044_checkpoints/up.sql b/crates/iota-indexer/migrations/pg/2023-08-19-044044_checkpoints/up.sql index c478b6bac62..ec861450dfc 100644 --- a/crates/iota-indexer/migrations/pg/2023-08-19-044044_checkpoints/up.sql +++ b/crates/iota-indexer/migrations/pg/2023-08-19-044044_checkpoints/up.sql @@ -13,7 +13,6 @@ CREATE TABLE checkpoints timestamp_ms BIGINT NOT NULL, total_gas_cost BIGINT NOT NULL, computation_cost BIGINT NOT NULL, - computation_cost_burned BIGINT NOT NULL, storage_cost BIGINT NOT NULL, storage_rebate BIGINT NOT NULL, non_refundable_storage_fee BIGINT NOT NULL, diff --git a/crates/iota-indexer/migrations/pg/2025-02-18-124652_checkpoints-add-computation-cost-burned/down.sql b/crates/iota-indexer/migrations/pg/2025-02-18-124652_checkpoints-add-computation-cost-burned/down.sql new file mode 100644 index 00000000000..7eedcea3180 --- /dev/null +++ b/crates/iota-indexer/migrations/pg/2025-02-18-124652_checkpoints-add-computation-cost-burned/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE checkpoints +DROP COLUMN computation_cost_burned; diff --git a/crates/iota-indexer/migrations/pg/2025-02-18-124652_checkpoints-add-computation-cost-burned/up.sql b/crates/iota-indexer/migrations/pg/2025-02-18-124652_checkpoints-add-computation-cost-burned/up.sql new file mode 100644 index 00000000000..2b6b9c81e28 --- /dev/null +++ b/crates/iota-indexer/migrations/pg/2025-02-18-124652_checkpoints-add-computation-cost-burned/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE checkpoints +ADD COLUMN computation_cost_burned BIGINT; diff --git a/crates/iota-indexer/src/apis/coin_api.rs b/crates/iota-indexer/src/apis/coin_api.rs index 3ed4d691477..782262f2fc5 100644 --- a/crates/iota-indexer/src/apis/coin_api.rs +++ b/crates/iota-indexer/src/apis/coin_api.rs @@ -17,7 +17,7 @@ use iota_types::{ }; use jsonrpsee::{RpcModule, core::RpcResult}; -use crate::indexer_reader::IndexerReader; +use crate::{indexer_reader::IndexerReader, types::IotaSystemStateSummaryView}; pub(crate) struct CoinReadApi { inner: IndexerReader, @@ -142,7 +142,7 @@ impl CoinReadApiServer for CoinReadApi { .inner .spawn_blocking(|this| this.get_latest_iota_system_state()) .await? - .iota_total_supply, + .iota_total_supply(), }) } else { self.inner diff --git a/crates/iota-indexer/src/apis/governance_api.rs b/crates/iota-indexer/src/apis/governance_api.rs index c4952a303d9..40edef13c5b 100644 --- a/crates/iota-indexer/src/apis/governance_api.rs +++ b/crates/iota-indexer/src/apis/governance_api.rs @@ -21,14 +21,21 @@ use iota_types::{ governance::StakedIota, id::ID, iota_serde::BigInt, - iota_system_state::{PoolTokenExchangeRate, iota_system_state_summary::IotaSystemStateSummary}, + iota_system_state::{ + PoolTokenExchangeRate, + iota_system_state_summary::{ + IotaSystemStateSummary, IotaSystemStateSummaryV1, IotaSystemStateSummaryV2, + }, + }, timelock::timelocked_staked_iota::TimelockedStakedIota, }; use jsonrpsee::{RpcModule, core::RpcResult}; use serde::{Serialize, de::DeserializeOwned}; use tokio::sync::Mutex; -use crate::{errors::IndexerError, indexer_reader::IndexerReader}; +use crate::{ + errors::IndexerError, indexer_reader::IndexerReader, types::IotaSystemStateSummaryView, +}; /// Maximum amount of staked objects for querying. const MAX_QUERY_STAKED_OBJECTS: usize = 1000; @@ -63,9 +70,8 @@ impl GovernanceReadApi { } async fn get_validators_apy(&self) -> Result { - let system_state_summary: IotaSystemStateSummary = - self.get_latest_iota_system_state().await?; - let epoch = system_state_summary.epoch; + let system_state_summary = self.get_latest_iota_system_state().await?; + let epoch = system_state_summary.epoch(); let exchange_rate_table = self.exchange_rates(&system_state_summary).await?; @@ -170,7 +176,7 @@ impl GovernanceReadApi { }); let system_state_summary = self.get_latest_iota_system_state().await?; - let epoch = system_state_summary.epoch; + let epoch = system_state_summary.epoch(); let (candidate_rates, pending_rates) = tokio::try_join!( self.candidate_validators_exchange_rate(&system_state_summary), @@ -236,7 +242,7 @@ impl GovernanceReadApi { }); let system_state_summary = self.get_latest_iota_system_state().await?; - let epoch = system_state_summary.epoch; + let epoch = system_state_summary.epoch(); let rates = self .exchange_rates(&system_state_summary) @@ -359,7 +365,7 @@ impl GovernanceReadApi { &self, system_state_summary: &IotaSystemStateSummary, ) -> Result, IndexerError> { - let epoch = system_state_summary.epoch; + let epoch = system_state_summary.epoch(); let mut cache = self.exchange_rates_cache.lock().await; @@ -399,7 +405,7 @@ impl GovernanceReadApi { system_state_summary: &IotaSystemStateSummary, ) -> Result, IndexerError> { let tables = system_state_summary - .active_validators + .active_validators() .iter() .map(|validator| { ( @@ -422,8 +428,8 @@ impl GovernanceReadApi { ) -> Result, IndexerError> { let tables = self .validator_summary_from_system_state( - system_state_summary.inactive_pools_id, - system_state_summary.inactive_pools_size, + system_state_summary.inactive_pools_id(), + system_state_summary.inactive_pools_size(), |df| bcs::from_bytes::(&df.bcs_name).map_err(Into::into), ) .await?; @@ -470,8 +476,8 @@ impl GovernanceReadApi { ) -> Result, IndexerError> { let tables = self .validator_summary_from_system_state( - system_state_summary.validator_candidates_id, - system_state_summary.validator_candidates_size, + system_state_summary.validator_candidates_id(), + system_state_summary.validator_candidates_size(), |df| bcs::from_bytes::(&df.bcs_name).map_err(Into::into), ) .await?; @@ -502,9 +508,9 @@ impl GovernanceReadApi { /// let system_state_summary = self.get_latest_iota_system_state().await?; /// let _ = self.validator_summary_from_system_state( /// // ID of the object that maps from a staking pool ID to the inactive validator that has that pool as its staking pool - /// system_state_summary.inactive_pools_id, + /// system_state_summary.inactive_pools_id(), /// // Number of inactive staking pools - /// system_state_summary.inactive_pools_size, + /// system_state_summary.inactive_pools_size(), /// // Extract the `ID` of the `Inactive` validator from the `DynamicFieldInfo` in the `system_state_summary.inactive_pools_id` table /// |df| bcs::from_bytes::(&df.bcs_name).map_err(Into::into), /// ).await?; @@ -517,9 +523,9 @@ impl GovernanceReadApi { /// let system_state_summary = self.get_latest_iota_system_state().await?; /// let _ = self.validator_summary_from_system_state( /// // ID of the object that stores preactive validators, mapping their addresses to their Validator structs - /// system_state_summary.validator_candidates_id, + /// system_state_summary.validator_candidates_id(), /// // Number of preactive validators - /// system_state_summary.validator_candidates_size, + /// system_state_summary.validator_candidates_size(), /// // Extract the `IotaAddress` of the `Candidate` validator from the `DynamicFieldInfo` in the `system_state_summary.validator_candidates_id` table /// |df| bcs::from_bytes::(&df.bcs_name).map_err(Into::into), /// ).await?; @@ -683,10 +689,20 @@ impl GovernanceReadApiServer for GovernanceReadApi { Ok(epoch.committee().map_err(IndexerError::from)?.into()) } - async fn get_latest_iota_system_state(&self) -> RpcResult { - self.get_latest_iota_system_state() - .await - .map_err(Into::into) + async fn get_latest_iota_system_state(&self) -> RpcResult { + Ok(self + .get_latest_iota_system_state() + .await? + .try_into() + .map_err(IndexerError::from)?) + } + + async fn get_latest_iota_system_state_v1(&self) -> RpcResult { + Ok(self + .get_latest_iota_system_state() + .await? + .try_into() + .map_err(IndexerError::from)?) } async fn get_reference_gas_price(&self) -> RpcResult> { diff --git a/crates/iota-indexer/src/handlers/checkpoint_handler.rs b/crates/iota-indexer/src/handlers/checkpoint_handler.rs index ee644ac206d..bf66f810035 100644 --- a/crates/iota-indexer/src/handlers/checkpoint_handler.rs +++ b/crates/iota-indexer/src/handlers/checkpoint_handler.rs @@ -19,10 +19,7 @@ use iota_types::{ dynamic_field::{DynamicFieldInfo, DynamicFieldName, DynamicFieldType}, effects::TransactionEffectsAPI, event::{SystemEpochInfoEvent, SystemEpochInfoEventV1, SystemEpochInfoEventV2}, - iota_system_state::{ - IotaSystemStateTrait, get_iota_system_state, - iota_system_state_summary::IotaSystemStateSummary, - }, + iota_system_state::{IotaSystemStateTrait, get_iota_system_state}, messages_checkpoint::{ CertifiedCheckpointSummary, CheckpointContents, CheckpointSequenceNumber, }, @@ -55,7 +52,8 @@ use crate::{ }, types::{ EventIndex, IndexedCheckpoint, IndexedDeletedObject, IndexedEpochInfo, IndexedEvent, - IndexedObject, IndexedPackage, IndexedTransaction, IndexerResult, TransactionKind, TxIndex, + IndexedObject, IndexedPackage, IndexedTransaction, IndexerResult, + IotaSystemStateSummaryView, TransactionKind, TxIndex, }, }; @@ -205,14 +203,14 @@ impl CheckpointHandler { } = data; // Genesis epoch + let system_state = + get_iota_system_state(&checkpoint_object_store)?.into_iota_system_state_summary(); if *checkpoint_summary.sequence_number() == 0 { info!("Processing genesis epoch"); - let system_state: IotaSystemStateSummary = - get_iota_system_state(&checkpoint_object_store)?.into_iota_system_state_summary(); return Ok(Some(EpochToCommit { last_epoch: None, new_epoch: IndexedEpochInfo::from_new_system_state_summary( - system_state, + &system_state, 0, // first_checkpoint_id None, ), @@ -225,9 +223,6 @@ impl CheckpointHandler { return Ok(None); } - let system_state: IotaSystemStateSummary = - get_iota_system_state(&checkpoint_object_store)?.into_iota_system_state_summary(); - let event = transactions .iter() .flat_map(|t| t.events.as_ref().map(|e| &e.data)) @@ -263,11 +258,12 @@ impl CheckpointHandler { // guarantee that the previous epoch's checkpoints have been written to // db. - let network_tx_count_prev_epoch = match system_state.epoch { + let epoch = system_state.epoch(); + let network_tx_count_prev_epoch = match epoch { // If first epoch change, this number is 0 1 => Ok(0), _ => { - let last_epoch = system_state.epoch - 2; + let last_epoch = epoch - 2; state .get_network_total_transactions_by_end_of_epoch(last_epoch) .await @@ -282,7 +278,7 @@ impl CheckpointHandler { network_tx_count_prev_epoch, )), new_epoch: IndexedEpochInfo::from_new_system_state_summary( - system_state, + &system_state, checkpoint_summary.sequence_number + 1, // first_checkpoint_id Some(&event), ), diff --git a/crates/iota-indexer/src/models/checkpoints.rs b/crates/iota-indexer/src/models/checkpoints.rs index ad54289a9f0..5359533397d 100644 --- a/crates/iota-indexer/src/models/checkpoints.rs +++ b/crates/iota-indexer/src/models/checkpoints.rs @@ -31,7 +31,6 @@ pub struct StoredCheckpoint { pub timestamp_ms: i64, pub total_gas_cost: i64, pub computation_cost: i64, - pub computation_cost_burned: i64, pub storage_cost: i64, pub storage_rebate: i64, pub non_refundable_storage_fee: i64, @@ -40,6 +39,7 @@ pub struct StoredCheckpoint { pub end_of_epoch_data: Option>, pub min_tx_sequence_number: Option, pub max_tx_sequence_number: Option, + pub computation_cost_burned: Option, } impl From<&IndexedCheckpoint> for StoredCheckpoint { @@ -61,7 +61,7 @@ impl From<&IndexedCheckpoint> for StoredCheckpoint { timestamp_ms: c.timestamp_ms as i64, total_gas_cost: c.total_gas_cost, computation_cost: c.computation_cost as i64, - computation_cost_burned: c.computation_cost_burned as i64, + computation_cost_burned: Some(c.computation_cost_burned as i64), storage_cost: c.storage_cost as i64, storage_rebate: c.storage_rebate as i64, non_refundable_storage_fee: c.non_refundable_storage_fee as i64, @@ -149,6 +149,9 @@ impl TryFrom for RpcCheckpoint { }) .transpose()?; + let computation_cost_burned = checkpoint + .computation_cost_burned + .unwrap_or(checkpoint.computation_cost) as u64; Ok(RpcCheckpoint { epoch: checkpoint.epoch as u64, sequence_number: checkpoint.sequence_number as u64, @@ -157,7 +160,7 @@ impl TryFrom for RpcCheckpoint { end_of_epoch_data, epoch_rolling_gas_cost_summary: GasCostSummary { computation_cost: checkpoint.computation_cost as u64, - computation_cost_burned: checkpoint.computation_cost_burned as u64, + computation_cost_burned, storage_cost: checkpoint.storage_cost as u64, storage_rebate: checkpoint.storage_rebate as u64, non_refundable_storage_fee: checkpoint.non_refundable_storage_fee as u64, diff --git a/crates/iota-indexer/src/models/epoch.rs b/crates/iota-indexer/src/models/epoch.rs index ade16036ca5..f9a515f7cdf 100644 --- a/crates/iota-indexer/src/models/epoch.rs +++ b/crates/iota-indexer/src/models/epoch.rs @@ -9,7 +9,7 @@ use iota_types::iota_system_state::iota_system_state_summary::IotaSystemStateSum use crate::{ errors::IndexerError, schema::{epochs, feature_flags, protocol_configs}, - types::IndexedEpochInfo, + types::{IndexedEpochInfo, IotaSystemStateSummaryView}, }; #[derive(Queryable, Insertable, Debug, Clone, Default)] @@ -165,7 +165,7 @@ impl TryFrom for EpochInfo { Ok(EpochInfo { epoch: value.epoch as u64, validators: system_state - .map(|s| s.active_validators) + .map(|s| s.active_validators().to_vec()) .unwrap_or_default(), epoch_total_transactions: value.epoch_total_transactions.unwrap_or(0) as u64, first_checkpoint_id: value.first_checkpoint_id as u64, diff --git a/crates/iota-indexer/src/schema.rs b/crates/iota-indexer/src/schema.rs index 4f9f7985a0f..4b7bfeb848f 100644 --- a/crates/iota-indexer/src/schema.rs +++ b/crates/iota-indexer/src/schema.rs @@ -52,7 +52,6 @@ diesel::table! { timestamp_ms -> Int8, total_gas_cost -> Int8, computation_cost -> Int8, - computation_cost_burned -> Int8, storage_cost -> Int8, storage_rebate -> Int8, non_refundable_storage_fee -> Int8, @@ -61,6 +60,7 @@ diesel::table! { end_of_epoch_data -> Nullable, min_tx_sequence_number -> Nullable, max_tx_sequence_number -> Nullable, + computation_cost_burned -> Nullable, } } diff --git a/crates/iota-indexer/src/types.rs b/crates/iota-indexer/src/types.rs index 94f5e64b18d..d5b77d1d4b2 100644 --- a/crates/iota-indexer/src/types.rs +++ b/crates/iota-indexer/src/types.rs @@ -11,9 +11,9 @@ use iota_types::{ digests::TransactionDigest, dynamic_field::DynamicFieldInfo, effects::TransactionEffects, - event::SystemEpochInfoEvent, + event::{SystemEpochInfoEvent, SystemEpochInfoEventV1, SystemEpochInfoEventV2}, iota_serde::IotaStructTag, - iota_system_state::iota_system_state_summary::IotaSystemStateSummary, + iota_system_state::iota_system_state_summary::{IotaSystemStateSummary, IotaValidatorSummary}, messages_checkpoint::{ CertifiedCheckpointSummary, CheckpointCommitment, CheckpointDigest, CheckpointSequenceNumber, EndOfEpochData, @@ -123,9 +123,85 @@ pub struct IndexedEpochInfo { pub tips_amount: Option, } +/// View on the common inner state-summary fields. +/// +/// This exposes whatever makes sense to the scope of this +/// library. +pub(crate) trait IotaSystemStateSummaryView { + fn epoch(&self) -> u64; + fn epoch_start_timestamp_ms(&self) -> u64; + fn reference_gas_price(&self) -> u64; + fn protocol_version(&self) -> u64; + fn iota_total_supply(&self) -> u64; + fn active_validators(&self) -> &[IotaValidatorSummary]; + fn inactive_pools_id(&self) -> ObjectID; + fn inactive_pools_size(&self) -> u64; + fn validator_candidates_id(&self) -> ObjectID; + fn validator_candidates_size(&self) -> u64; +} + +/// Access common fields of the inner variants wrapped by +/// [`IotaSystemStateSummary`]. +/// +/// ## Panics +/// +/// If the `field` identifier does not correspond to an existing field in any +/// of the inner types wrapped in the variants. +macro_rules! state_summary_get { + ($enum:expr, $field:ident) => {{ + match $enum { + IotaSystemStateSummary::V1(ref inner) => &inner.$field, + IotaSystemStateSummary::V2(ref inner) => &inner.$field, + _ => unimplemented!(), + } + }}; +} + +impl IotaSystemStateSummaryView for IotaSystemStateSummary { + fn epoch(&self) -> u64 { + *state_summary_get!(self, epoch) + } + + fn epoch_start_timestamp_ms(&self) -> u64 { + *state_summary_get!(self, epoch_start_timestamp_ms) + } + + fn reference_gas_price(&self) -> u64 { + *state_summary_get!(self, reference_gas_price) + } + + fn protocol_version(&self) -> u64 { + *state_summary_get!(self, protocol_version) + } + + fn iota_total_supply(&self) -> u64 { + *state_summary_get!(self, iota_total_supply) + } + + fn active_validators(&self) -> &[IotaValidatorSummary] { + state_summary_get!(self, active_validators) + } + + fn inactive_pools_id(&self) -> ObjectID { + *state_summary_get!(self, inactive_pools_id) + } + + fn inactive_pools_size(&self) -> u64 { + *state_summary_get!(self, inactive_pools_size) + } + + fn validator_candidates_id(&self) -> ObjectID { + *state_summary_get!(self, validator_candidates_id) + } + + fn validator_candidates_size(&self) -> u64 { + *state_summary_get!(self, validator_candidates_size) + } +} + impl IndexedEpochInfo { pub fn from_new_system_state_summary( - new_system_state_summary: IotaSystemStateSummary, + new_system_state_summary: &IotaSystemStateSummary, first_checkpoint_id: u64, event: Option<&SystemEpochInfoEvent>, ) -> IndexedEpochInfo { @@ -138,14 +214,14 @@ impl IndexedEpochInfo { None => (0, 0), }; Self { - epoch: new_system_state_summary.epoch, + epoch: new_system_state_summary.epoch(), first_checkpoint_id, - epoch_start_timestamp: new_system_state_summary.epoch_start_timestamp_ms, - reference_gas_price: new_system_state_summary.reference_gas_price, - protocol_version: new_system_state_summary.protocol_version, + epoch_start_timestamp: new_system_state_summary.epoch_start_timestamp_ms(), + reference_gas_price: new_system_state_summary.reference_gas_price(), + protocol_version: new_system_state_summary.protocol_version(), total_stake, storage_fund_balance, - system_state: bcs::to_bytes(&new_system_state_summary).unwrap(), + system_state: bcs::to_bytes(new_system_state_summary).unwrap(), ..Default::default() } } @@ -159,46 +235,19 @@ impl IndexedEpochInfo { event: &SystemEpochInfoEvent, network_total_tx_num_at_last_epoch_end: u64, ) -> IndexedEpochInfo { - let ( - storage_charge, - storage_rebate, - total_gas_fees, - total_stake_rewards_distributed, - burnt_tokens_amount, - minted_tokens_amount, - tips_amount, - ) = match event { - SystemEpochInfoEvent::V1(event) => ( - Some(event.storage_charge), - Some(event.storage_rebate), - Some(event.total_gas_fees), - Some(event.total_stake_rewards_distributed), - Some(event.burnt_tokens_amount), - Some(event.minted_tokens_amount), - Some(0), - ), - SystemEpochInfoEvent::V2(event) => ( - Some(event.storage_charge), - Some(event.storage_rebate), - Some(event.total_gas_fees), - Some(event.total_stake_rewards_distributed), - Some(event.burnt_tokens_amount), - Some(event.minted_tokens_amount), - Some(event.tips_amount), - ), - }; + let event = IndexedEpochInfoEvent::from(event); Self { - epoch: last_checkpoint_summary.epoch, + epoch: last_checkpoint_summary.epoch(), epoch_total_transactions: Some( last_checkpoint_summary.network_total_transactions - network_total_tx_num_at_last_epoch_end, ), last_checkpoint_id: Some(*last_checkpoint_summary.sequence_number()), epoch_end_timestamp: Some(last_checkpoint_summary.timestamp_ms), - storage_charge, - storage_rebate, - total_gas_fees, - total_stake_rewards_distributed, + storage_charge: Some(event.storage_charge), + storage_rebate: Some(event.storage_rebate), + total_gas_fees: Some(event.total_gas_fees), + total_stake_rewards_distributed: Some(event.total_stake_rewards_distributed), epoch_commitments: last_checkpoint_summary .end_of_epoch_data .as_ref() @@ -212,9 +261,57 @@ impl IndexedEpochInfo { protocol_version: 0, total_stake: 0, storage_fund_balance: 0, - burnt_tokens_amount, - minted_tokens_amount, - tips_amount, + burnt_tokens_amount: Some(event.burnt_tokens_amount), + minted_tokens_amount: Some(event.minted_tokens_amount), + tips_amount: Some(event.tips_amount), + } + } +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct IndexedEpochInfoEvent { + pub storage_charge: u64, + pub storage_rebate: u64, + pub total_gas_fees: u64, + pub total_stake_rewards_distributed: u64, + pub burnt_tokens_amount: u64, + pub minted_tokens_amount: u64, + pub tips_amount: u64, +} + +impl From<&SystemEpochInfoEventV1> for IndexedEpochInfoEvent { + fn from(event: &SystemEpochInfoEventV1) -> Self { + Self { + storage_charge: event.storage_charge, + storage_rebate: event.storage_rebate, + total_gas_fees: event.total_gas_fees, + total_stake_rewards_distributed: event.total_stake_rewards_distributed, + burnt_tokens_amount: event.burnt_tokens_amount, + minted_tokens_amount: event.minted_tokens_amount, + tips_amount: 0, + } + } +} + +impl From<&SystemEpochInfoEventV2> for IndexedEpochInfoEvent { + fn from(event: &SystemEpochInfoEventV2) -> Self { + Self { + storage_charge: event.storage_charge, + storage_rebate: event.storage_rebate, + total_gas_fees: event.total_gas_fees, + total_stake_rewards_distributed: event.total_stake_rewards_distributed, + burnt_tokens_amount: event.burnt_tokens_amount, + minted_tokens_amount: event.minted_tokens_amount, + tips_amount: event.tips_amount, + } + } +} + +impl From<&SystemEpochInfoEvent> for IndexedEpochInfoEvent { + fn from(event: &SystemEpochInfoEvent) -> Self { + match event { + SystemEpochInfoEvent::V1(inner) => inner.into(), + SystemEpochInfoEvent::V2(inner) => inner.into(), } } }