diff --git a/.changeset/late-buses-know.md b/.changeset/late-buses-know.md new file mode 100644 index 00000000000..1a7fe08fe46 --- /dev/null +++ b/.changeset/late-buses-know.md @@ -0,0 +1,9 @@ +--- +'@iota/graphql-transport': patch +'@iota/iota-sdk': patch +--- + +Sync API changes: + +- restore extended api metrics endpoints +- remove nameservice endpoints diff --git a/apps/explorer/src/components/AddressesCardGraph.tsx b/apps/explorer/src/components/AddressesCardGraph.tsx index 612fe8fc368..a008e55e007 100644 --- a/apps/explorer/src/components/AddressesCardGraph.tsx +++ b/apps/explorer/src/components/AddressesCardGraph.tsx @@ -99,8 +99,8 @@ export function AddressesCardGraph(): JSX.Element { data={adjEpochAddressMetrics} height={height} width={width} - getX={({ epoch }) => epoch} - getY={(data) => data[GRAPH_DATA_FIELD]} + getX={({ epoch }) => Number(epoch) || 0} + getY={(data) => Number(data[GRAPH_DATA_FIELD]) || 0} formatY={formatAmount} tooltipContent={TooltipContent} /> diff --git a/apps/explorer/src/lib/utils/getStorageFundFlow.ts b/apps/explorer/src/lib/utils/getStorageFundFlow.ts index ea14a3ca828..856730b1ab4 100644 --- a/apps/explorer/src/lib/utils/getStorageFundFlow.ts +++ b/apps/explorer/src/lib/utils/getStorageFundFlow.ts @@ -10,7 +10,7 @@ interface StorageFundFlow { fundOutflow: bigint | null; } -export function getEpochStorageFundFlow(endOfEpochInfo: EndOfEpochInfo | null): StorageFundFlow { +export function getEpochStorageFundFlow(endOfEpochInfo?: EndOfEpochInfo | null): StorageFundFlow { const fundInflow = endOfEpochInfo ? BigInt(endOfEpochInfo.storageCharge) : null; const fundOutflow = endOfEpochInfo ? BigInt(endOfEpochInfo.storageRebate) : null; diff --git a/apps/explorer/src/lib/utils/getSupplyChangeAfterEpochEnd.ts b/apps/explorer/src/lib/utils/getSupplyChangeAfterEpochEnd.ts index 2dcce33ed81..20305510350 100644 --- a/apps/explorer/src/lib/utils/getSupplyChangeAfterEpochEnd.ts +++ b/apps/explorer/src/lib/utils/getSupplyChangeAfterEpochEnd.ts @@ -3,7 +3,9 @@ import { type EndOfEpochInfo } from '@iota/iota-sdk/src/client'; -export function getSupplyChangeAfterEpochEnd(endOfEpochInfo: EndOfEpochInfo | null): bigint | null { +export function getSupplyChangeAfterEpochEnd( + endOfEpochInfo?: EndOfEpochInfo | null, +): bigint | null { if (endOfEpochInfo?.mintedTokensAmount == null || endOfEpochInfo?.burntTokensAmount == null) return null; diff --git a/crates/iota-graphql-rpc/src/test_infra/cluster.rs b/crates/iota-graphql-rpc/src/test_infra/cluster.rs index 8ad0ffa63aa..f23e774a75f 100644 --- a/crates/iota-graphql-rpc/src/test_infra/cluster.rs +++ b/crates/iota-graphql-rpc/src/test_infra/cluster.rs @@ -5,7 +5,7 @@ use std::{net::SocketAddr, sync::Arc, time::Duration}; use iota_graphql_rpc_client::simple_client::SimpleClient; -pub use iota_indexer::handlers::objects_snapshot_processor::SnapshotLagConfig; +pub use iota_indexer::processors::objects_snapshot_processor::SnapshotLagConfig; use iota_indexer::{ errors::IndexerError, store::{indexer_store::IndexerStore, PgIndexerStore}, diff --git a/crates/iota-indexer/src/apis/extended_api.rs b/crates/iota-indexer/src/apis/extended_api.rs index ce1d8d267ac..95ba2fab704 100644 --- a/crates/iota-indexer/src/apis/extended_api.rs +++ b/crates/iota-indexer/src/apis/extended_api.rs @@ -7,7 +7,8 @@ use iota_json_rpc_api::{ internal_error, validate_limit, ExtendedApiServer, QUERY_MAX_RESULT_LIMIT_CHECKPOINTS, }; use iota_json_rpc_types::{ - CheckpointedObjectID, EpochInfo, EpochPage, IotaObjectResponseQuery, Page, QueryObjectsPage, + AddressMetrics, EpochInfo, EpochMetrics, EpochMetricsPage, EpochPage, MoveCallMetrics, + NetworkMetrics, Page, }; use iota_open_rpc::Module; use iota_types::iota_serde::BigInt; @@ -56,6 +57,46 @@ impl ExtendedApiServer for ExtendedApi { }) } + async fn get_epoch_metrics( + &self, + cursor: Option>, + limit: Option, + descending_order: Option, + ) -> RpcResult { + let limit = + validate_limit(limit, QUERY_MAX_RESULT_LIMIT_CHECKPOINTS).map_err(internal_error)?; + let epochs = self + .inner + .spawn_blocking(move |this| { + this.get_epochs( + cursor.map(|x| *x), + limit + 1, + descending_order.unwrap_or(false), + ) + }) + .await?; + + let mut epoch_metrics = epochs + .into_iter() + .map(|e| EpochMetrics { + epoch: e.epoch, + epoch_total_transactions: e.epoch_total_transactions, + first_checkpoint_id: e.first_checkpoint_id, + epoch_start_timestamp: e.epoch_start_timestamp, + end_of_epoch_info: e.end_of_epoch_info, + }) + .collect::>(); + + let has_next_page = epoch_metrics.len() > limit; + epoch_metrics.truncate(limit); + let next_cursor = epoch_metrics.last().map(|e| e.epoch); + Ok(Page { + data: epoch_metrics, + next_cursor: next_cursor.map(|id| id.into()), + has_next_page, + }) + } + async fn get_current_epoch(&self) -> RpcResult { let stored_epoch = self .inner @@ -64,13 +105,47 @@ impl ExtendedApiServer for ExtendedApi { EpochInfo::try_from(stored_epoch).map_err(Into::into) } - async fn query_objects( + async fn get_network_metrics(&self) -> RpcResult { + let network_metrics = self + .inner + .spawn_blocking(|this| this.get_latest_network_metrics()) + .await?; + Ok(network_metrics) + } + + async fn get_move_call_metrics(&self) -> RpcResult { + let move_call_metrics = self + .inner + .spawn_blocking(|this| this.get_latest_move_call_metrics()) + .await?; + Ok(move_call_metrics) + } + + async fn get_latest_address_metrics(&self) -> RpcResult { + let latest_address_metrics = self + .inner + .spawn_blocking(|this| this.get_latest_address_metrics()) + .await?; + Ok(latest_address_metrics) + } + + async fn get_checkpoint_address_metrics(&self, checkpoint: u64) -> RpcResult { + let checkpoint_address_metrics = self + .inner + .spawn_blocking(move |this| this.get_checkpoint_address_metrics(checkpoint)) + .await?; + Ok(checkpoint_address_metrics) + } + + async fn get_all_epoch_address_metrics( &self, - _query: IotaObjectResponseQuery, - _cursor: Option, - _limit: Option, - ) -> RpcResult { - Err(jsonrpsee::types::error::ErrorCode::MethodNotFound.into()) + descending_order: Option, + ) -> RpcResult> { + let all_epoch_address_metrics = self + .inner + .spawn_blocking(move |this| this.get_all_epoch_address_metrics(descending_order)) + .await?; + Ok(all_epoch_address_metrics) } async fn get_total_transactions(&self) -> RpcResult> { diff --git a/crates/iota-indexer/src/handlers/mod.rs b/crates/iota-indexer/src/handlers/mod.rs index 5839be69eee..aef3b640bcb 100644 --- a/crates/iota-indexer/src/handlers/mod.rs +++ b/crates/iota-indexer/src/handlers/mod.rs @@ -14,7 +14,6 @@ use crate::{ pub mod checkpoint_handler; pub mod committer; -pub mod objects_snapshot_processor; pub mod tx_processor; #[derive(Debug)] diff --git a/crates/iota-indexer/src/indexer.rs b/crates/iota-indexer/src/indexer.rs index 107ff70dc83..edebcf18025 100644 --- a/crates/iota-indexer/src/indexer.rs +++ b/crates/iota-indexer/src/indexer.rs @@ -13,13 +13,14 @@ use crate::{ build_json_rpc_server, errors::IndexerError, framework::fetcher::CheckpointFetcher, - handlers::{ - checkpoint_handler::new_handlers, - objects_snapshot_processor::{ObjectsSnapshotProcessor, SnapshotLagConfig}, - }, + handlers::checkpoint_handler::new_handlers, indexer_reader::IndexerReader, metrics::IndexerMetrics, - store::IndexerStore, + processors::{ + objects_snapshot_processor::{ObjectsSnapshotProcessor, SnapshotLagConfig}, + processor_orchestrator::ProcessorOrchestrator, + }, + store::{IndexerStore, PgIndexerAnalyticalStore}, IndexerConfig, }; @@ -111,4 +112,17 @@ impl Indexer { Ok(()) } + + pub async fn start_analytical_worker( + store: PgIndexerAnalyticalStore, + metrics: IndexerMetrics, + ) -> Result<(), IndexerError> { + info!( + "Iota Indexer Analytical Worker (version {:?}) started...", + env!("CARGO_PKG_VERSION") + ); + let mut processor_orchestrator = ProcessorOrchestrator::new(store, metrics); + processor_orchestrator.run_forever().await; + Ok(()) + } } diff --git a/crates/iota-indexer/src/indexer_reader.rs b/crates/iota-indexer/src/indexer_reader.rs index 064bdae3f74..a9e541cc0a4 100644 --- a/crates/iota-indexer/src/indexer_reader.rs +++ b/crates/iota-indexer/src/indexer_reader.rs @@ -15,9 +15,10 @@ use diesel::{ }; use fastcrypto::encoding::{Encoding, Hex}; use iota_json_rpc_types::{ - Balance, CheckpointId, Coin as IotaCoin, DisplayFieldsResponse, EpochInfo, EventFilter, - IotaCoinMetadata, IotaEvent, IotaObjectDataFilter, IotaTransactionBlockEffects, - IotaTransactionBlockEffectsAPI, IotaTransactionBlockResponse, TransactionFilter, + AddressMetrics, Balance, CheckpointId, Coin as IotaCoin, DisplayFieldsResponse, EpochInfo, + EventFilter, IotaCoinMetadata, IotaEvent, IotaObjectDataFilter, IotaTransactionBlockEffects, + IotaTransactionBlockEffectsAPI, IotaTransactionBlockResponse, MoveCallMetrics, + MoveFunctionName, NetworkMetrics, TransactionFilter, }; use iota_types::{ balance::Supply, @@ -39,17 +40,21 @@ use crate::{ db::{PgConnectionConfig, PgConnectionPoolConfig, PgPoolConnection}, errors::IndexerError, models::{ + address_metrics::StoredAddressMetrics, checkpoints::StoredCheckpoint, display::StoredDisplay, epoch::StoredEpochInfo, events::StoredEvent, + move_call_metrics::QueriedMoveCallMetrics, + network_metrics::StoredNetworkMetrics, objects::{CoinBalance, ObjectRefColumn, StoredObject}, packages::StoredPackage, transactions::StoredTransaction, tx_indices::TxSequenceNumber, }, schema::{ - checkpoints, display, epochs, events, objects, objects_snapshot, packages, transactions, + address_metrics, checkpoints, display, epochs, events, move_call_metrics, objects, + objects_snapshot, packages, transactions, }, types::{IndexerResult, OwnerType}, }; @@ -1539,6 +1544,119 @@ impl IndexerReader { .collect::>>() } + pub fn get_latest_network_metrics(&self) -> IndexerResult { + let metrics = self.run_query(|conn| { + diesel::sql_query("SELECT * FROM network_metrics;") + .get_result::(conn) + })?; + Ok(metrics.into()) + } + + pub fn get_latest_move_call_metrics(&self) -> IndexerResult { + let latest_3d_move_call_metrics = self.run_query(|conn| { + move_call_metrics::table + .filter(move_call_metrics::dsl::day.eq(3)) + .order(move_call_metrics::dsl::id.desc()) + .limit(10) + .load::(conn) + })?; + let latest_7d_move_call_metrics = self.run_query(|conn| { + move_call_metrics::table + .filter(move_call_metrics::dsl::day.eq(7)) + .order(move_call_metrics::dsl::id.desc()) + .limit(10) + .load::(conn) + })?; + let latest_30d_move_call_metrics = self.run_query(|conn| { + move_call_metrics::table + .filter(move_call_metrics::dsl::day.eq(30)) + .order(move_call_metrics::dsl::id.desc()) + .limit(10) + .load::(conn) + })?; + + let latest_3_days: Vec<(MoveFunctionName, usize)> = latest_3d_move_call_metrics + .into_iter() + .map(|m| m.try_into()) + .collect::, _>>()?; + let latest_7_days: Vec<(MoveFunctionName, usize)> = latest_7d_move_call_metrics + .into_iter() + .map(|m| m.try_into()) + .collect::, _>>()?; + let latest_30_days: Vec<(MoveFunctionName, usize)> = latest_30d_move_call_metrics + .into_iter() + .map(|m| m.try_into()) + .collect::, _>>()?; + // sort by call count desc. + let rank_3_days = latest_3_days + .into_iter() + .sorted_by(|a, b| b.1.cmp(&a.1)) + .collect::>(); + let rank_7_days = latest_7_days + .into_iter() + .sorted_by(|a, b| b.1.cmp(&a.1)) + .collect::>(); + let rank_30_days = latest_30_days + .into_iter() + .sorted_by(|a, b| b.1.cmp(&a.1)) + .collect::>(); + Ok(MoveCallMetrics { + rank_3_days, + rank_7_days, + rank_30_days, + }) + } + + pub fn get_latest_address_metrics(&self) -> IndexerResult { + let stored_address_metrics = self.run_query(|conn| { + address_metrics::table + .order(address_metrics::dsl::checkpoint.desc()) + .first::(conn) + })?; + Ok(stored_address_metrics.into()) + } + + pub fn get_checkpoint_address_metrics( + &self, + checkpoint_seq: u64, + ) -> IndexerResult { + let stored_address_metrics = self.run_query(|conn| { + address_metrics::table + .filter(address_metrics::dsl::checkpoint.eq(checkpoint_seq as i64)) + .first::(conn) + })?; + Ok(stored_address_metrics.into()) + } + + pub fn get_all_epoch_address_metrics( + &self, + descending_order: Option, + ) -> IndexerResult> { + let is_descending = descending_order.unwrap_or_default(); + let epoch_address_metrics_query = format!( + "WITH ranked_rows AS ( + SELECT + checkpoint, epoch, timestamp_ms, cumulative_addresses, cumulative_active_addresses, daily_active_addresses, + row_number() OVER(PARTITION BY epoch ORDER BY checkpoint DESC) as row_num + FROM + address_metrics + ) + SELECT + checkpoint, epoch, timestamp_ms, cumulative_addresses, cumulative_active_addresses, daily_active_addresses + FROM ranked_rows + WHERE row_num = 1 ORDER BY epoch {}", + if is_descending { "DESC" } else { "ASC" }, + ); + let epoch_address_metrics = self.run_query(|conn| { + diesel::sql_query(epoch_address_metrics_query).load::(conn) + })?; + + Ok(epoch_address_metrics + .into_iter() + .map(|stored_address_metrics| stored_address_metrics.into()) + .collect()) + } + pub(crate) async fn get_display_fields( &self, original_object: &iota_types::object::Object, diff --git a/crates/iota-indexer/src/lib.rs b/crates/iota-indexer/src/lib.rs index 6431e147a2a..edd882c6db5 100644 --- a/crates/iota-indexer/src/lib.rs +++ b/crates/iota-indexer/src/lib.rs @@ -34,6 +34,7 @@ pub mod indexer; pub mod indexer_reader; pub mod metrics; pub mod models; +pub mod processors; pub mod schema; pub mod store; pub mod test_utils; @@ -74,6 +75,8 @@ pub struct IndexerConfig { pub fullnode_sync_worker: bool, #[clap(long)] pub rpc_server_worker: bool, + #[clap(long)] + pub analytical_worker: bool, } impl IndexerConfig { @@ -136,6 +139,7 @@ impl Default for IndexerConfig { reset_db: false, fullnode_sync_worker: true, rpc_server_worker: true, + analytical_worker: false, } } } diff --git a/crates/iota-indexer/src/main.rs b/crates/iota-indexer/src/main.rs index 05209dc433a..3a79de77524 100644 --- a/crates/iota-indexer/src/main.rs +++ b/crates/iota-indexer/src/main.rs @@ -8,7 +8,7 @@ use iota_indexer::{ errors::IndexerError, indexer::Indexer, metrics::{start_prometheus_server, IndexerMetrics}, - store::PgIndexerStore, + store::{PgIndexerAnalyticalStore, PgIndexerStore}, IndexerConfig, }; use tracing::{error, info}; @@ -92,6 +92,9 @@ async fn main() -> Result<(), IndexerError> { return Indexer::start_writer(&indexer_config, store, indexer_metrics).await; } else if indexer_config.rpc_server_worker { return Indexer::start_reader(&indexer_config, ®istry, db_url).await; + } else if indexer_config.analytical_worker { + let store = PgIndexerAnalyticalStore::new(blocking_cp); + return Indexer::start_analytical_worker(store, indexer_metrics.clone()).await; } Ok(()) } diff --git a/crates/iota-indexer/src/metrics.rs b/crates/iota-indexer/src/metrics.rs index b4102f13f66..13e1f6c0b76 100644 --- a/crates/iota-indexer/src/metrics.rs +++ b/crates/iota-indexer/src/metrics.rs @@ -91,7 +91,11 @@ pub struct IndexerMetrics { pub latest_tx_checkpoint_sequence_number: IntGauge, pub latest_indexer_object_checkpoint_sequence_number: IntGauge, pub latest_object_snapshot_sequence_number: IntGauge, - // checkpoint E2E latency is: + // Analytical + pub latest_move_call_metrics_tx_seq: IntGauge, + pub latest_address_metrics_tx_seq: IntGauge, + pub latest_network_metrics_cp_seq: IntGauge, + // Checkpoint E2E latency is: // fullnode_download_latency + checkpoint_index_latency + db_commit_latency pub checkpoint_download_bytes_size: IntGauge, pub fullnode_checkpoint_data_download_latency: Histogram, @@ -241,6 +245,21 @@ impl IndexerMetrics { "Latest object snapshot sequence number from the Indexer", registry, ).unwrap(), + latest_move_call_metrics_tx_seq: register_int_gauge_with_registry!( + "latest_move_call_metrics_tx_seq", + "Latest move call metrics tx seq", + registry, + ).unwrap(), + latest_address_metrics_tx_seq: register_int_gauge_with_registry!( + "latest_address_metrics_tx_seq", + "Latest address metrics tx seq", + registry, + ).unwrap(), + latest_network_metrics_cp_seq: register_int_gauge_with_registry!( + "latest_network_metrics_cp_seq", + "Latest network metrics cp seq", + registry, + ).unwrap(), checkpoint_download_bytes_size: register_int_gauge_with_registry!( "checkpoint_download_bytes_size", "Size of the downloaded checkpoint in bytes", diff --git a/crates/iota-indexer/src/models/address_metrics.rs b/crates/iota-indexer/src/models/address_metrics.rs new file mode 100644 index 00000000000..2e7406614c7 --- /dev/null +++ b/crates/iota-indexer/src/models/address_metrics.rs @@ -0,0 +1,112 @@ +// Copyright (c) Mysten Labs, Inc. +// Modifications Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; + +use diesel::{prelude::*, sql_types::BigInt}; +use iota_json_rpc_types::AddressMetrics; + +use crate::schema::{active_addresses, address_metrics, addresses}; + +/// Represents a sender or receiver address. +#[derive(Clone, Debug, Queryable, Insertable)] +#[diesel(table_name = addresses)] +pub struct StoredAddress { + pub address: Vec, + pub first_appearance_tx: i64, + pub first_appearance_time: i64, + pub last_appearance_tx: i64, + pub last_appearance_time: i64, +} + +/// Represents a sender address. +#[derive(Clone, Debug, Queryable, Insertable)] +#[diesel(table_name = active_addresses)] +pub struct StoredActiveAddress { + pub address: Vec, + pub first_appearance_tx: i64, + pub first_appearance_time: i64, + pub last_appearance_tx: i64, + pub last_appearance_time: i64, +} + +impl From for StoredActiveAddress { + fn from(address: StoredAddress) -> Self { + StoredActiveAddress { + address: address.address, + first_appearance_tx: address.first_appearance_tx, + first_appearance_time: address.first_appearance_time, + last_appearance_tx: address.last_appearance_tx, + last_appearance_time: address.last_appearance_time, + } + } +} + +#[derive(Clone, Debug, Default, Queryable, Insertable, QueryableByName)] +#[diesel(table_name = address_metrics)] +pub struct StoredAddressMetrics { + #[diesel(sql_type = BigInt)] + pub checkpoint: i64, + #[diesel(sql_type = BigInt)] + pub epoch: i64, + #[diesel(sql_type = BigInt)] + pub timestamp_ms: i64, + #[diesel(sql_type = BigInt)] + pub cumulative_addresses: i64, + #[diesel(sql_type = BigInt)] + pub cumulative_active_addresses: i64, + #[diesel(sql_type = BigInt)] + pub daily_active_addresses: i64, +} + +impl From for AddressMetrics { + fn from(metrics: StoredAddressMetrics) -> Self { + Self { + checkpoint: metrics.checkpoint as u64, + epoch: metrics.epoch as u64, + timestamp_ms: metrics.timestamp_ms as u64, + cumulative_addresses: metrics.cumulative_addresses as u64, + cumulative_active_addresses: metrics.cumulative_active_addresses as u64, + daily_active_addresses: metrics.daily_active_addresses as u64, + } + } +} + +#[derive(Clone, Debug)] +pub struct AddressInfoToCommit { + pub address: Vec, + pub tx_seq: i64, + pub timestamp_ms: i64, +} + +pub fn dedup_addresses(addrs_to_commit: Vec) -> Vec { + let mut compressed_addr_map: HashMap<_, StoredAddress> = HashMap::new(); + for addr_to_commit in addrs_to_commit { + let entry = compressed_addr_map + .entry(addr_to_commit.address.clone()) + .or_insert_with(|| StoredAddress { + address: addr_to_commit.address.clone(), + first_appearance_time: addr_to_commit.timestamp_ms, + first_appearance_tx: addr_to_commit.tx_seq, + last_appearance_time: addr_to_commit.timestamp_ms, + last_appearance_tx: addr_to_commit.tx_seq, + }); + + if addr_to_commit.timestamp_ms < entry.first_appearance_time { + entry.first_appearance_time = addr_to_commit.timestamp_ms; + entry.first_appearance_tx = addr_to_commit.tx_seq; + } + if addr_to_commit.timestamp_ms > entry.last_appearance_time { + entry.last_appearance_time = addr_to_commit.timestamp_ms; + entry.last_appearance_tx = addr_to_commit.tx_seq; + } + } + compressed_addr_map.values().cloned().collect() +} + +#[derive(Clone, Debug)] +pub struct TxTimestampInfo { + pub tx_seq: i64, + pub timestamp_ms: i64, +} diff --git a/crates/iota-indexer/src/models/mod.rs b/crates/iota-indexer/src/models/mod.rs index bddc5d19fa3..2683389b8e6 100644 --- a/crates/iota-indexer/src/models/mod.rs +++ b/crates/iota-indexer/src/models/mod.rs @@ -2,12 +2,16 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +pub mod address_metrics; pub mod checkpoints; pub mod display; pub mod epoch; pub mod events; pub(crate) mod large_objects; +pub mod move_call_metrics; +pub mod network_metrics; pub mod objects; pub mod packages; pub mod transactions; +pub mod tx_count_metrics; pub mod tx_indices; diff --git a/crates/iota-indexer/src/models/move_call_metrics.rs b/crates/iota-indexer/src/models/move_call_metrics.rs new file mode 100644 index 00000000000..be4db560f6b --- /dev/null +++ b/crates/iota-indexer/src/models/move_call_metrics.rs @@ -0,0 +1,127 @@ +// Copyright (c) Mysten Labs, Inc. +// Modifications Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::str::FromStr; + +use diesel::{ + prelude::*, + sql_types::{BigInt, Binary, Text}, + QueryableByName, +}; +use iota_json_rpc_types::MoveFunctionName; +use iota_types::base_types::ObjectID; +use move_core_types::identifier::Identifier; + +use crate::{ + errors::IndexerError, + schema::{move_call_metrics, move_calls}, +}; + +#[derive(Clone, Debug, Queryable, Insertable)] +#[diesel(table_name = move_calls)] +pub struct StoredMoveCall { + pub transaction_sequence_number: i64, + pub checkpoint_sequence_number: i64, + pub epoch: i64, + pub move_package: Vec, + pub move_module: String, + pub move_function: String, +} + +#[derive(Clone, Debug, Insertable)] +#[diesel(table_name = move_call_metrics)] +pub struct StoredMoveCallMetrics { + pub id: Option, + pub epoch: i64, + pub day: i64, + pub move_package: String, + pub move_module: String, + pub move_function: String, + pub count: i64, +} + +impl Default for StoredMoveCallMetrics { + fn default() -> Self { + Self { + id: None, + epoch: -1, + day: -1, + move_package: "".to_string(), + move_module: "".to_string(), + move_function: "".to_string(), + count: -1, + } + } +} + +// for auto-incremented id, the committed id is None, so Option, +// but when querying, the returned type is i64, thus a separate type is needed. +#[derive(Clone, Debug, Queryable)] +#[diesel(table_name = move_call_metrics)] +pub struct QueriedMoveCallMetrics { + pub id: i64, + pub epoch: i64, + pub day: i64, + pub move_package: String, + pub move_module: String, + pub move_function: String, + pub count: i64, +} + +impl TryInto<(MoveFunctionName, usize)> for QueriedMoveCallMetrics { + type Error = IndexerError; + + fn try_into(self) -> Result<(MoveFunctionName, usize), Self::Error> { + let package = ObjectID::from_str(&self.move_package)?; + let module = Identifier::from_str(&self.move_module)?; + let function = Identifier::from_str(&self.move_function)?; + Ok(( + MoveFunctionName { + package, + module, + function, + }, + self.count as usize, + )) + } +} + +impl From for StoredMoveCallMetrics { + fn from(q: QueriedMoveCallMetrics) -> Self { + StoredMoveCallMetrics { + id: Some(q.id), + epoch: q.epoch, + day: q.day, + move_package: q.move_package, + move_module: q.move_module, + move_function: q.move_function, + count: q.count, + } + } +} + +#[derive(QueryableByName, Debug, Clone, Default)] +pub struct QueriedMoveMetrics { + #[diesel(sql_type = BigInt)] + pub epoch: i64, + #[diesel(sql_type = BigInt)] + pub day: i64, + #[diesel(sql_type = Binary)] + pub move_package: Vec, + #[diesel(sql_type = Text)] + pub move_module: String, + #[diesel(sql_type = Text)] + pub move_function: String, + #[diesel(sql_type = BigInt)] + pub count: i64, +} + +pub fn build_move_call_metric_query(epoch: i64, days: i64) -> String { + format!("SELECT {}::BIGINT AS epoch, {}::BIGINT AS day, move_package, move_module, move_function, COUNT(*)::BIGINT AS count + FROM move_calls + WHERE epoch >= {} + GROUP BY move_package, move_module, move_function + ORDER BY count DESC + LIMIT 10;", epoch, days, epoch - days) +} diff --git a/crates/iota-indexer/src/models/network_metrics.rs b/crates/iota-indexer/src/models/network_metrics.rs new file mode 100644 index 00000000000..55681e0aec4 --- /dev/null +++ b/crates/iota-indexer/src/models/network_metrics.rs @@ -0,0 +1,67 @@ +// Copyright (c) Mysten Labs, Inc. +// Modifications Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use diesel::{ + prelude::*, + sql_types::{BigInt, Double, Float8}, +}; +use iota_json_rpc_types::NetworkMetrics; + +use crate::schema::epoch_peak_tps; + +#[derive(Clone, Debug, Queryable, Insertable)] +#[diesel(table_name = epoch_peak_tps)] +pub struct StoredEpochPeakTps { + pub epoch: i64, + pub peak_tps: f64, + pub peak_tps_30d: f64, +} + +impl Default for StoredEpochPeakTps { + fn default() -> Self { + Self { + epoch: -1, + peak_tps: 0.0, + peak_tps_30d: 0.0, + } + } +} + +#[derive(QueryableByName, Debug, Clone, Default)] +pub struct StoredNetworkMetrics { + #[diesel(sql_type = Double)] + pub current_tps: f64, + #[diesel(sql_type = Double)] + pub tps_30_days: f64, + #[diesel(sql_type = BigInt)] + pub total_packages: i64, + #[diesel(sql_type = BigInt)] + pub total_addresses: i64, + #[diesel(sql_type = BigInt)] + pub total_objects: i64, + #[diesel(sql_type = BigInt)] + pub current_epoch: i64, + #[diesel(sql_type = BigInt)] + pub current_checkpoint: i64, +} + +impl From for NetworkMetrics { + fn from(db: StoredNetworkMetrics) -> Self { + Self { + current_tps: db.current_tps, + tps_30_days: db.tps_30_days, + total_packages: db.total_packages as u64, + total_addresses: db.total_addresses as u64, + total_objects: db.total_objects as u64, + current_epoch: db.current_epoch as u64, + current_checkpoint: db.current_checkpoint as u64, + } + } +} + +#[derive(Debug, QueryableByName)] +pub struct Tps { + #[diesel(sql_type = Float8)] + pub peak_tps: f64, +} diff --git a/crates/iota-indexer/src/models/tx_count_metrics.rs b/crates/iota-indexer/src/models/tx_count_metrics.rs new file mode 100644 index 00000000000..b645edb8aa3 --- /dev/null +++ b/crates/iota-indexer/src/models/tx_count_metrics.rs @@ -0,0 +1,31 @@ +// Copyright (c) Mysten Labs, Inc. +// Modifications Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use diesel::prelude::*; + +use crate::schema::tx_count_metrics; + +#[derive(Clone, Debug, Queryable, Insertable)] +#[diesel(table_name = tx_count_metrics)] +pub struct StoredTxCountMetrics { + pub checkpoint_sequence_number: i64, + pub epoch: i64, + pub timestamp_ms: i64, + pub total_transaction_blocks: i64, + pub total_successful_transaction_blocks: i64, + pub total_successful_transactions: i64, +} + +impl Default for StoredTxCountMetrics { + fn default() -> Self { + Self { + checkpoint_sequence_number: -1, + epoch: -1, + timestamp_ms: -1, + total_transaction_blocks: -1, + total_successful_transaction_blocks: -1, + total_successful_transactions: -1, + } + } +} diff --git a/crates/iota-indexer/src/processors/address_metrics_processor.rs b/crates/iota-indexer/src/processors/address_metrics_processor.rs new file mode 100644 index 00000000000..02665a74f97 --- /dev/null +++ b/crates/iota-indexer/src/processors/address_metrics_processor.rs @@ -0,0 +1,122 @@ +// Copyright (c) Mysten Labs, Inc. +// Modifications Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use tap::tap::TapFallible; +use tracing::{error, info}; + +use crate::{metrics::IndexerMetrics, store::IndexerAnalyticalStore, types::IndexerResult}; + +const ADDRESS_PROCESSOR_BATCH_SIZE: usize = 80000; +const PARALLELISM: usize = 10; + +pub struct AddressMetricsProcessor { + pub store: S, + metrics: IndexerMetrics, + pub address_processor_batch_size: usize, + pub address_processor_parallelism: usize, +} + +impl AddressMetricsProcessor +where + S: IndexerAnalyticalStore + Clone + Sync + Send + 'static, +{ + pub fn new(store: S, metrics: IndexerMetrics) -> AddressMetricsProcessor { + let address_processor_batch_size = std::env::var("ADDRESS_PROCESSOR_BATCH_SIZE") + .map(|s| s.parse::().unwrap_or(ADDRESS_PROCESSOR_BATCH_SIZE)) + .unwrap_or(ADDRESS_PROCESSOR_BATCH_SIZE); + let address_processor_parallelism = std::env::var("ADDRESS_PROCESSOR_PARALLELISM") + .map(|s| s.parse::().unwrap_or(PARALLELISM)) + .unwrap_or(PARALLELISM); + Self { + store, + metrics, + address_processor_batch_size, + address_processor_parallelism, + } + } + + pub async fn start(&self) -> IndexerResult<()> { + info!("Indexer address metrics async processor started..."); + let latest_tx_seq = self + .store + .get_address_metrics_last_processed_tx_seq() + .await?; + let mut last_processed_tx_seq = latest_tx_seq.unwrap_or_default().seq; + loop { + let mut latest_tx = self.store.get_latest_stored_transaction().await?; + while if let Some(tx) = latest_tx { + tx.tx_sequence_number + < last_processed_tx_seq + self.address_processor_batch_size as i64 + } else { + true + } { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + latest_tx = self.store.get_latest_stored_transaction().await?; + } + + let mut persist_tasks = vec![]; + let batch_size = self.address_processor_batch_size; + let step_size = batch_size / self.address_processor_parallelism; + for chunk_start_tx_seq in (last_processed_tx_seq + 1 + ..last_processed_tx_seq + batch_size as i64 + 1) + .step_by(step_size) + { + let active_address_store = self.store.clone(); + persist_tasks.push(tokio::task::spawn_blocking(move || { + active_address_store.persist_active_addresses_in_tx_range( + chunk_start_tx_seq, + chunk_start_tx_seq + step_size as i64, + ) + })); + } + for chunk_start_tx_seq in (last_processed_tx_seq + 1 + ..last_processed_tx_seq + batch_size as i64 + 1) + .step_by(step_size) + { + let address_store = self.store.clone(); + persist_tasks.push(tokio::task::spawn_blocking(move || { + address_store.persist_addresses_in_tx_range( + chunk_start_tx_seq, + chunk_start_tx_seq + step_size as i64, + ) + })); + } + futures::future::join_all(persist_tasks) + .await + .into_iter() + .collect::, _>>() + .tap_err(|e| { + error!("Error joining address persist tasks: {:?}", e); + })? + .into_iter() + .collect::, _>>() + .tap_err(|e| { + error!("Error persisting addresses or active addresses: {:?}", e); + })?; + last_processed_tx_seq += self.address_processor_batch_size as i64; + info!( + "Persisted addresses and active addresses for tx seq: {}", + last_processed_tx_seq, + ); + self.metrics + .latest_address_metrics_tx_seq + .set(last_processed_tx_seq); + + let mut last_processed_tx = self.store.get_tx(last_processed_tx_seq).await?; + while last_processed_tx.is_none() { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + last_processed_tx = self.store.get_tx(last_processed_tx_seq).await?; + } + // unwrap is safe here b/c we just checked that it's not None + let last_processed_cp = last_processed_tx.unwrap().checkpoint_sequence_number; + self.store + .calculate_and_persist_address_metrics(last_processed_cp) + .await?; + info!( + "Persisted address metrics for checkpoint: {}", + last_processed_cp + ); + } + } +} diff --git a/crates/iota-indexer/src/processors/mod.rs b/crates/iota-indexer/src/processors/mod.rs new file mode 100644 index 00000000000..a43484977b1 --- /dev/null +++ b/crates/iota-indexer/src/processors/mod.rs @@ -0,0 +1,9 @@ +// Copyright (c) Mysten Labs, Inc. +// Modifications Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +pub mod address_metrics_processor; +pub mod move_call_metrics_processor; +pub mod network_metrics_processor; +pub mod objects_snapshot_processor; +pub mod processor_orchestrator; diff --git a/crates/iota-indexer/src/processors/move_call_metrics_processor.rs b/crates/iota-indexer/src/processors/move_call_metrics_processor.rs new file mode 100644 index 00000000000..721f58a3c45 --- /dev/null +++ b/crates/iota-indexer/src/processors/move_call_metrics_processor.rs @@ -0,0 +1,111 @@ +// Copyright (c) Mysten Labs, Inc. +// Modifications Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use tap::tap::TapFallible; +use tracing::{error, info}; + +use crate::{metrics::IndexerMetrics, store::IndexerAnalyticalStore, types::IndexerResult}; + +const MOVE_CALL_PROCESSOR_BATCH_SIZE: usize = 80000; +const PARALLELISM: usize = 10; + +pub struct MoveCallMetricsProcessor { + pub store: S, + metrics: IndexerMetrics, + pub move_call_processor_batch_size: usize, + pub move_call_processor_parallelism: usize, +} + +impl MoveCallMetricsProcessor +where + S: IndexerAnalyticalStore + Clone + Sync + Send + 'static, +{ + pub fn new(store: S, metrics: IndexerMetrics) -> MoveCallMetricsProcessor { + let move_call_processor_batch_size = std::env::var("MOVE_CALL_PROCESSOR_BATCH_SIZE") + .map(|s| s.parse::().unwrap_or(MOVE_CALL_PROCESSOR_BATCH_SIZE)) + .unwrap_or(MOVE_CALL_PROCESSOR_BATCH_SIZE); + let move_call_processor_parallelism = std::env::var("MOVE_CALL_PROCESSOR_PARALLELISM") + .map(|s| s.parse::().unwrap_or(PARALLELISM)) + .unwrap_or(PARALLELISM); + Self { + store, + metrics, + move_call_processor_batch_size, + move_call_processor_parallelism, + } + } + + pub async fn start(&self) -> IndexerResult<()> { + info!("Indexer move call metrics async processor started..."); + let latest_move_call_tx_seq = self.store.get_latest_move_call_tx_seq().await?; + let mut last_processed_tx_seq = latest_move_call_tx_seq.unwrap_or_default().seq; + let latest_move_call_epoch = self.store.get_latest_move_call_metrics().await?; + let mut last_processed_epoch = latest_move_call_epoch.unwrap_or_default().epoch; + loop { + let mut latest_tx = self.store.get_latest_stored_transaction().await?; + while if let Some(tx) = latest_tx { + tx.tx_sequence_number + < last_processed_tx_seq + self.move_call_processor_batch_size as i64 + } else { + true + } { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + latest_tx = self.store.get_latest_stored_transaction().await?; + } + + let batch_size = self.move_call_processor_batch_size; + let step_size = batch_size / self.move_call_processor_parallelism; + let mut persist_tasks = vec![]; + for chunk_start_tx_seq in (last_processed_tx_seq + 1 + ..last_processed_tx_seq + batch_size as i64 + 1) + .step_by(step_size) + { + let move_call_store = self.store.clone(); + persist_tasks.push(tokio::task::spawn_blocking(move || { + move_call_store.persist_move_calls_in_tx_range( + chunk_start_tx_seq, + chunk_start_tx_seq + step_size as i64, + ) + })); + } + futures::future::join_all(persist_tasks) + .await + .into_iter() + .collect::, _>>() + .tap_err(|e| { + error!("Error joining move call persist tasks: {:?}", e); + })? + .into_iter() + .collect::, _>>() + .tap_err(|e| { + error!("Error persisting move calls: {:?}", e); + })?; + last_processed_tx_seq += batch_size as i64; + info!("Persisted move_calls at tx seq: {}", last_processed_tx_seq); + self.metrics + .latest_move_call_metrics_tx_seq + .set(last_processed_tx_seq); + + let mut tx = self.store.get_tx(last_processed_tx_seq).await?; + while tx.is_none() { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + tx = self.store.get_tx(last_processed_tx_seq).await?; + } + let cp_seq = tx.unwrap().checkpoint_sequence_number; + let mut cp = self.store.get_cp(cp_seq).await?; + while cp.is_none() { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + cp = self.store.get_cp(cp_seq).await?; + } + let end_epoch = cp.unwrap().epoch; + for epoch in last_processed_epoch + 1..end_epoch { + self.store + .calculate_and_persist_move_call_metrics(epoch) + .await?; + info!("Persisted move_call_metrics for epoch: {}", epoch); + } + last_processed_epoch = end_epoch - 1; + } + } +} diff --git a/crates/iota-indexer/src/processors/network_metrics_processor.rs b/crates/iota-indexer/src/processors/network_metrics_processor.rs new file mode 100644 index 00000000000..bee9937d8cf --- /dev/null +++ b/crates/iota-indexer/src/processors/network_metrics_processor.rs @@ -0,0 +1,129 @@ +// Copyright (c) Mysten Labs, Inc. +// Modifications Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use tap::tap::TapFallible; +use tracing::{error, info}; + +use crate::{ + errors::IndexerError, metrics::IndexerMetrics, store::IndexerAnalyticalStore, + types::IndexerResult, +}; + +const NETWORK_METRICS_PROCESSOR_BATCH_SIZE: usize = 10; +const PARALLELISM: usize = 1; + +pub struct NetworkMetricsProcessor { + pub store: S, + metrics: IndexerMetrics, + pub network_processor_metrics_batch_size: usize, + pub network_processor_metrics_parallelism: usize, +} + +impl NetworkMetricsProcessor +where + S: IndexerAnalyticalStore + Clone + Sync + Send + 'static, +{ + pub fn new(store: S, metrics: IndexerMetrics) -> NetworkMetricsProcessor { + let network_processor_metrics_batch_size = + std::env::var("NETWORK_PROCESSOR_METRICS_BATCH_SIZE") + .map(|s| { + s.parse::() + .unwrap_or(NETWORK_METRICS_PROCESSOR_BATCH_SIZE) + }) + .unwrap_or(NETWORK_METRICS_PROCESSOR_BATCH_SIZE); + let network_processor_metrics_parallelism = + std::env::var("NETWORK_PROCESSOR_METRICS_PARALLELISM") + .map(|s| s.parse::().unwrap_or(PARALLELISM)) + .unwrap_or(PARALLELISM); + Self { + store, + metrics, + network_processor_metrics_batch_size, + network_processor_metrics_parallelism, + } + } + + pub async fn start(&self) -> IndexerResult<()> { + info!("Indexer network metrics async processor started..."); + let latest_tx_count_metrics = self + .store + .get_latest_tx_count_metrics() + .await + .unwrap_or_default(); + let latest_epoch_peak_tps = self + .store + .get_latest_epoch_peak_tps() + .await + .unwrap_or_default(); + let mut last_processed_cp_seq = latest_tx_count_metrics + .unwrap_or_default() + .checkpoint_sequence_number; + let mut last_processed_peak_tps_epoch = latest_epoch_peak_tps.unwrap_or_default().epoch; + loop { + let mut latest_stored_checkpoint = self.store.get_latest_stored_checkpoint().await?; + while if let Some(cp) = latest_stored_checkpoint { + cp.sequence_number + < last_processed_cp_seq + self.network_processor_metrics_batch_size as i64 + } else { + true + } { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + latest_stored_checkpoint = self.store.get_latest_stored_checkpoint().await?; + } + + info!( + "Persisting tx count metrics for checkpoint sequence number {}", + last_processed_cp_seq + ); + let batch_size = self.network_processor_metrics_batch_size; + let step_size = batch_size / self.network_processor_metrics_parallelism; + let mut persist_tasks = vec![]; + for chunk_start_cp in (last_processed_cp_seq + 1 + ..last_processed_cp_seq + batch_size as i64 + 1) + .step_by(step_size) + { + let store = self.store.clone(); + persist_tasks.push(tokio::task::spawn_blocking(move || { + store + .persist_tx_count_metrics(chunk_start_cp, chunk_start_cp + step_size as i64) + })); + } + futures::future::join_all(persist_tasks) + .await + .into_iter() + .collect::, _>>() + .tap_err(|e| { + error!("Error joining network persist tasks: {:?}", e); + })? + .into_iter() + .collect::, _>>() + .tap_err(|e| { + error!("Error persisting tx count metrics: {:?}", e); + })?; + last_processed_cp_seq += batch_size as i64; + info!( + "Persisted tx count metrics for checkpoint sequence number {}", + last_processed_cp_seq + ); + self.metrics + .latest_network_metrics_cp_seq + .set(last_processed_cp_seq); + + let end_cp = self + .store + .get_checkpoints_in_range(last_processed_cp_seq, last_processed_cp_seq + 1) + .await? + .first() + .ok_or(IndexerError::PostgresReadError( + "Cannot read checkpoint from PG for epoch peak TPS".to_string(), + ))? + .clone(); + for epoch in last_processed_peak_tps_epoch + 1..end_cp.epoch { + self.store.persist_epoch_peak_tps(epoch).await?; + last_processed_peak_tps_epoch = epoch; + info!("Persisted epoch peak TPS for epoch {}", epoch); + } + } + } +} diff --git a/crates/iota-indexer/src/handlers/objects_snapshot_processor.rs b/crates/iota-indexer/src/processors/objects_snapshot_processor.rs similarity index 95% rename from crates/iota-indexer/src/handlers/objects_snapshot_processor.rs rename to crates/iota-indexer/src/processors/objects_snapshot_processor.rs index 41236720440..4f4758a2d29 100644 --- a/crates/iota-indexer/src/handlers/objects_snapshot_processor.rs +++ b/crates/iota-indexer/src/processors/objects_snapshot_processor.rs @@ -57,11 +57,18 @@ impl Default for SnapshotLagConfig { } } -// NOTE: "handler" impl ObjectsSnapshotProcessor where S: IndexerStore + Clone + Sync + Send + 'static, { + pub fn new(store: S, metrics: IndexerMetrics) -> ObjectsSnapshotProcessor { + Self { + store, + metrics, + config: SnapshotLagConfig::default(), + } + } + pub fn new_with_config( store: S, metrics: IndexerMetrics, diff --git a/crates/iota-indexer/src/processors/processor_orchestrator.rs b/crates/iota-indexer/src/processors/processor_orchestrator.rs new file mode 100644 index 00000000000..67e8783d45c --- /dev/null +++ b/crates/iota-indexer/src/processors/processor_orchestrator.rs @@ -0,0 +1,83 @@ +// Copyright (c) Mysten Labs, Inc. +// Modifications Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use futures::future::try_join_all; +use tracing::{error, info}; + +use super::{ + address_metrics_processor::AddressMetricsProcessor, + move_call_metrics_processor::MoveCallMetricsProcessor, + network_metrics_processor::NetworkMetricsProcessor, +}; +use crate::{metrics::IndexerMetrics, store::IndexerAnalyticalStore}; + +pub struct ProcessorOrchestrator { + store: S, + metrics: IndexerMetrics, +} + +impl ProcessorOrchestrator +where + S: IndexerAnalyticalStore + Clone + Send + Sync + 'static, +{ + pub fn new(store: S, metrics: IndexerMetrics) -> Self { + Self { store, metrics } + } + + pub async fn run_forever(&mut self) { + info!("Processor orchestrator started..."); + let network_metrics_processor = + NetworkMetricsProcessor::new(self.store.clone(), self.metrics.clone()); + let network_metrics_handle = tokio::task::spawn(async move { + loop { + let network_metrics_res = network_metrics_processor.start().await; + if let Err(e) = network_metrics_res { + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + error!( + "Indexer network metrics processor failed with error {:?}, retrying in 5s...", + e + ); + } + } + }); + + let addr_metrics_processor = + AddressMetricsProcessor::new(self.store.clone(), self.metrics.clone()); + let addr_metrics_handle = tokio::task::spawn(async move { + loop { + let addr_metrics_res = addr_metrics_processor.start().await; + if let Err(e) = addr_metrics_res { + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + error!( + "Indexer address metrics processor failed with error {:?}, retrying in 5s...", + e + ); + } + } + }); + + let move_call_metrics_processor = + MoveCallMetricsProcessor::new(self.store.clone(), self.metrics.clone()); + let move_call_metrics_handle = tokio::task::spawn(async move { + loop { + let move_call_metrics_res = move_call_metrics_processor.start().await; + if let Err(e) = move_call_metrics_res { + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + error!( + "Indexer move call metrics processor failed with error {:?}, retrying in 5s...", + e + ); + } + } + }); + + try_join_all(vec![ + network_metrics_handle, + addr_metrics_handle, + move_call_metrics_handle, + ]) + .await + .expect("Processor orchestrator should not run into errors."); + } +} diff --git a/crates/iota-indexer/src/store/indexer_analytics_store.rs b/crates/iota-indexer/src/store/indexer_analytics_store.rs new file mode 100644 index 00000000000..d32116b540e --- /dev/null +++ b/crates/iota-indexer/src/store/indexer_analytics_store.rs @@ -0,0 +1,83 @@ +// Copyright (c) Mysten Labs, Inc. +// Modifications Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use async_trait::async_trait; + +use crate::{ + models::{ + checkpoints::StoredCheckpoint, + move_call_metrics::StoredMoveCallMetrics, + network_metrics::StoredEpochPeakTps, + transactions::{ + StoredTransaction, StoredTransactionCheckpoint, StoredTransactionSuccessCommandCount, + StoredTransactionTimestamp, TxSeq, + }, + tx_count_metrics::StoredTxCountMetrics, + }, + types::IndexerResult, +}; + +/// Provides methods to get and persist metrics. Utility methods for calculating +/// metrics are also provided. +#[async_trait] +pub trait IndexerAnalyticalStore { + async fn get_latest_stored_transaction(&self) -> IndexerResult>; + async fn get_latest_stored_checkpoint(&self) -> IndexerResult>; + async fn get_checkpoints_in_range( + &self, + start_checkpoint: i64, + end_checkpoint: i64, + ) -> IndexerResult>; + async fn get_tx_timestamps_in_checkpoint_range( + &self, + start_checkpoint: i64, + end_checkpoint: i64, + ) -> IndexerResult>; + async fn get_tx_checkpoints_in_checkpoint_range( + &self, + start_checkpoint: i64, + end_checkpoint: i64, + ) -> IndexerResult>; + async fn get_tx_success_cmd_counts_in_checkpoint_range( + &self, + start_checkpoint: i64, + end_checkpoint: i64, + ) -> IndexerResult>; + async fn get_tx(&self, tx_sequence_number: i64) -> IndexerResult>; + async fn get_cp(&self, sequence_number: i64) -> IndexerResult>; + + // for network metrics including TPS and counts of objects etc. + async fn get_latest_tx_count_metrics(&self) -> IndexerResult>; + async fn get_latest_epoch_peak_tps(&self) -> IndexerResult>; + fn persist_tx_count_metrics( + &self, + start_checkpoint: i64, + end_checkpoint: i64, + ) -> IndexerResult<()>; + async fn persist_epoch_peak_tps(&self, epoch: i64) -> IndexerResult<()>; + + // for address metrics + async fn get_address_metrics_last_processed_tx_seq(&self) -> IndexerResult>; + fn persist_addresses_in_tx_range( + &self, + start_tx_seq: i64, + end_tx_seq: i64, + ) -> IndexerResult<()>; + fn persist_active_addresses_in_tx_range( + &self, + start_tx_seq: i64, + end_tx_seq: i64, + ) -> IndexerResult<()>; + async fn calculate_and_persist_address_metrics(&self, checkpoint: i64) -> IndexerResult<()>; + + // for move call metrics + async fn get_latest_move_call_metrics(&self) -> IndexerResult>; + async fn get_latest_move_call_tx_seq(&self) -> IndexerResult>; + fn persist_move_calls_in_tx_range( + &self, + start_tx_seq: i64, + end_tx_seq: i64, + ) -> IndexerResult<()>; + async fn calculate_and_persist_move_call_metrics(&self, epoch: i64) -> IndexerResult<()>; +} diff --git a/crates/iota-indexer/src/store/mod.rs b/crates/iota-indexer/src/store/mod.rs index f0417495fd2..4fda2652722 100644 --- a/crates/iota-indexer/src/store/mod.rs +++ b/crates/iota-indexer/src/store/mod.rs @@ -2,11 +2,15 @@ // Modifications Copyright (c) 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +pub(crate) use indexer_analytics_store::IndexerAnalyticalStore; pub(crate) use indexer_store::*; +pub use pg_indexer_analytical_store::PgIndexerAnalyticalStore; pub use pg_indexer_store::PgIndexerStore; +mod indexer_analytics_store; pub mod indexer_store; pub mod module_resolver; +mod pg_indexer_analytical_store; mod pg_indexer_store; mod pg_partition_manager; pub mod query; diff --git a/crates/iota-indexer/src/store/pg_indexer_analytical_store.rs b/crates/iota-indexer/src/store/pg_indexer_analytical_store.rs new file mode 100644 index 00000000000..496ff491131 --- /dev/null +++ b/crates/iota-indexer/src/store/pg_indexer_analytical_store.rs @@ -0,0 +1,647 @@ +// Copyright (c) Mysten Labs, Inc. +// Modifications Copyright (c) 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::result::Result::Ok; +use std::time::Duration; + +use async_trait::async_trait; +use diesel::{dsl::count, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl}; +use iota_types::base_types::ObjectID; +use tap::tap::TapFallible; +use tracing::{error, info}; + +use super::IndexerAnalyticalStore; +use crate::{ + db::PgConnectionPool, + errors::{Context, IndexerError}, + models::{ + address_metrics::StoredAddressMetrics, + checkpoints::StoredCheckpoint, + move_call_metrics::{ + build_move_call_metric_query, QueriedMoveCallMetrics, QueriedMoveMetrics, + StoredMoveCallMetrics, + }, + network_metrics::{StoredEpochPeakTps, Tps}, + transactions::{ + StoredTransaction, StoredTransactionCheckpoint, StoredTransactionSuccessCommandCount, + StoredTransactionTimestamp, TxSeq, + }, + tx_count_metrics::StoredTxCountMetrics, + }, + schema::{ + active_addresses, address_metrics, addresses, checkpoints, epoch_peak_tps, + move_call_metrics, move_calls, transactions, tx_count_metrics, + }, + store::diesel_macro::{read_only_blocking, transactional_blocking_with_retry}, + types::IndexerResult, +}; + +/// The store for the indexer analytical data. Represents a Postgres +/// implementation of the `IndexerAnalyticalStore` trait. +#[derive(Clone)] +pub struct PgIndexerAnalyticalStore { + blocking_cp: PgConnectionPool, +} + +impl PgIndexerAnalyticalStore { + pub fn new(blocking_cp: PgConnectionPool) -> Self { + Self { blocking_cp } + } +} + +#[async_trait] +impl IndexerAnalyticalStore for PgIndexerAnalyticalStore { + async fn get_latest_stored_checkpoint(&self) -> IndexerResult> { + let latest_cp = read_only_blocking!(&self.blocking_cp, |conn| { + checkpoints::dsl::checkpoints + .order(checkpoints::sequence_number.desc()) + .first::(conn) + .optional() + }) + .context("Failed reading latest checkpoint from PostgresDB")?; + Ok(latest_cp) + } + + async fn get_latest_stored_transaction(&self) -> IndexerResult> { + let latest_tx = read_only_blocking!(&self.blocking_cp, |conn| { + transactions::dsl::transactions + .order(transactions::tx_sequence_number.desc()) + .first::(conn) + .optional() + }) + .context("Failed reading latest transaction from PostgresDB")?; + Ok(latest_tx) + } + + async fn get_checkpoints_in_range( + &self, + start_checkpoint: i64, + end_checkpoint: i64, + ) -> IndexerResult> { + let cps = read_only_blocking!(&self.blocking_cp, |conn| { + checkpoints::dsl::checkpoints + .filter(checkpoints::sequence_number.ge(start_checkpoint)) + .filter(checkpoints::sequence_number.lt(end_checkpoint)) + .order(checkpoints::sequence_number.asc()) + .load::(conn) + }) + .context("Failed reading checkpoints from PostgresDB")?; + Ok(cps) + } + + async fn get_tx_timestamps_in_checkpoint_range( + &self, + start_checkpoint: i64, + end_checkpoint: i64, + ) -> IndexerResult> { + let tx_timestamps = read_only_blocking!(&self.blocking_cp, |conn| { + transactions::dsl::transactions + .filter(transactions::dsl::checkpoint_sequence_number.ge(start_checkpoint)) + .filter(transactions::dsl::checkpoint_sequence_number.lt(end_checkpoint)) + .order(transactions::dsl::tx_sequence_number.asc()) + .select(( + transactions::dsl::tx_sequence_number, + transactions::dsl::timestamp_ms, + )) + .load::(conn) + }) + .context("Failed reading transaction timestamps from PostgresDB")?; + Ok(tx_timestamps) + } + + async fn get_tx_checkpoints_in_checkpoint_range( + &self, + start_checkpoint: i64, + end_checkpoint: i64, + ) -> IndexerResult> { + let tx_checkpoints = read_only_blocking!(&self.blocking_cp, |conn| { + transactions::dsl::transactions + .filter(transactions::dsl::checkpoint_sequence_number.ge(start_checkpoint)) + .filter(transactions::dsl::checkpoint_sequence_number.lt(end_checkpoint)) + .order(transactions::dsl::tx_sequence_number.asc()) + .select(( + transactions::dsl::tx_sequence_number, + transactions::dsl::checkpoint_sequence_number, + )) + .load::(conn) + }) + .context("Failed reading transaction checkpoints from PostgresDB")?; + Ok(tx_checkpoints) + } + + async fn get_tx_success_cmd_counts_in_checkpoint_range( + &self, + start_checkpoint: i64, + end_checkpoint: i64, + ) -> IndexerResult> { + let tx_success_cmd_counts = read_only_blocking!(&self.blocking_cp, |conn| { + transactions::dsl::transactions + .filter(transactions::dsl::checkpoint_sequence_number.ge(start_checkpoint)) + .filter(transactions::dsl::checkpoint_sequence_number.lt(end_checkpoint)) + .order(transactions::dsl::tx_sequence_number.asc()) + .select(( + transactions::dsl::tx_sequence_number, + transactions::dsl::checkpoint_sequence_number, + transactions::dsl::success_command_count, + transactions::dsl::timestamp_ms, + )) + .load::(conn) + }) + .context("Failed reading transaction success command counts from PostgresDB")?; + Ok(tx_success_cmd_counts) + } + async fn get_tx(&self, tx_sequence_number: i64) -> IndexerResult> { + let tx = read_only_blocking!(&self.blocking_cp, |conn| { + transactions::dsl::transactions + .filter(transactions::dsl::tx_sequence_number.eq(tx_sequence_number)) + .first::(conn) + .optional() + }) + .context("Failed reading transaction from PostgresDB")?; + Ok(tx) + } + + async fn get_cp(&self, sequence_number: i64) -> IndexerResult> { + let cp = read_only_blocking!(&self.blocking_cp, |conn| { + checkpoints::dsl::checkpoints + .filter(checkpoints::dsl::sequence_number.eq(sequence_number)) + .first::(conn) + .optional() + }) + .context("Failed reading checkpoint from PostgresDB")?; + Ok(cp) + } + + async fn get_latest_tx_count_metrics(&self) -> IndexerResult> { + let latest_tx_count = read_only_blocking!(&self.blocking_cp, |conn| { + tx_count_metrics::dsl::tx_count_metrics + .order(tx_count_metrics::dsl::checkpoint_sequence_number.desc()) + .first::(conn) + .optional() + }) + .context("Failed reading latest tx count metrics from PostgresDB")?; + Ok(latest_tx_count) + } + + async fn get_latest_epoch_peak_tps(&self) -> IndexerResult> { + let latest_network_metrics = read_only_blocking!(&self.blocking_cp, |conn| { + epoch_peak_tps::dsl::epoch_peak_tps + .order(epoch_peak_tps::dsl::epoch.desc()) + .first::(conn) + .optional() + }) + .context("Failed reading latest epoch peak TPS from PostgresDB")?; + Ok(latest_network_metrics) + } + + fn persist_tx_count_metrics( + &self, + start_checkpoint: i64, + end_checkpoint: i64, + ) -> IndexerResult<()> { + let tx_count_query = construct_checkpoint_tx_count_query(start_checkpoint, end_checkpoint); + info!("Persisting tx count metrics for cp {}", start_checkpoint); + transactional_blocking_with_retry!( + &self.blocking_cp, + |conn| { + diesel::sql_query(tx_count_query.clone()).execute(conn)?; + Ok::<(), IndexerError>(()) + }, + Duration::from_secs(10) + ) + .context("Failed persisting tx count metrics to PostgresDB")?; + info!("Persisted tx count metrics for cp {}", start_checkpoint); + Ok(()) + } + + async fn persist_epoch_peak_tps(&self, epoch: i64) -> IndexerResult<()> { + let epoch_peak_tps_query = construct_peak_tps_query(epoch, 1); + let peak_tps_30d_query = construct_peak_tps_query(epoch, 30); + let epoch_tps: Tps = + read_only_blocking!(&self.blocking_cp, |conn| diesel::RunQueryDsl::get_result( + diesel::sql_query(epoch_peak_tps_query), + conn + )) + .context("Failed reading epoch peak TPS from PostgresDB")?; + let tps_30d: Tps = + read_only_blocking!(&self.blocking_cp, |conn| diesel::RunQueryDsl::get_result( + diesel::sql_query(peak_tps_30d_query), + conn + )) + .context("Failed reading 30d peak TPS from PostgresDB")?; + + let epoch_peak_tps = StoredEpochPeakTps { + epoch, + peak_tps: epoch_tps.peak_tps, + peak_tps_30d: tps_30d.peak_tps, + }; + transactional_blocking_with_retry!( + &self.blocking_cp, + |conn| { + diesel::insert_into(epoch_peak_tps::table) + .values(epoch_peak_tps.clone()) + .on_conflict_do_nothing() + .execute(conn) + }, + Duration::from_secs(10) + ) + .context("Failed persisting epoch peak TPS to PostgresDB.")?; + Ok(()) + } + + async fn get_address_metrics_last_processed_tx_seq(&self) -> IndexerResult> { + let last_processed_tx_seq = read_only_blocking!(&self.blocking_cp, |conn| { + active_addresses::dsl::active_addresses + .order(active_addresses::dsl::last_appearance_tx.desc()) + .select((active_addresses::dsl::last_appearance_tx,)) + .first::(conn) + .optional() + }) + .context("Failed to read address metrics last processed tx sequence.")?; + Ok(last_processed_tx_seq) + } + + fn persist_addresses_in_tx_range( + &self, + start_tx_seq: i64, + end_tx_seq: i64, + ) -> IndexerResult<()> { + let address_persist_query = construct_address_persisting_query(start_tx_seq, end_tx_seq); + transactional_blocking_with_retry!( + &self.blocking_cp, + |conn| { + diesel::sql_query(address_persist_query.clone()).execute(conn)?; + Ok::<(), IndexerError>(()) + }, + Duration::from_secs(10) + ) + .context("Failed persisting addresses to PostgresDB")?; + Ok(()) + } + + fn persist_active_addresses_in_tx_range( + &self, + start_tx_seq: i64, + end_tx_seq: i64, + ) -> IndexerResult<()> { + let active_address_persist_query = + construct_active_address_persisting_query(start_tx_seq, end_tx_seq); + transactional_blocking_with_retry!( + &self.blocking_cp, + |conn| { + diesel::sql_query(active_address_persist_query.clone()).execute(conn)?; + Ok::<(), IndexerError>(()) + }, + Duration::from_secs(10) + ) + .context("Failed persisting active addresses to PostgresDB")?; + Ok(()) + } + + async fn calculate_and_persist_address_metrics(&self, checkpoint: i64) -> IndexerResult<()> { + let mut checkpoint_opt = self + .get_checkpoints_in_range(checkpoint, checkpoint + 1) + .await? + .pop(); + while checkpoint_opt.is_none() { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + checkpoint_opt = self + .get_checkpoints_in_range(checkpoint, checkpoint + 1) + .await? + .pop(); + } + let checkpoint = checkpoint_opt.unwrap(); + let cp_timestamp_ms = checkpoint.timestamp_ms; + let addr_count = read_only_blocking!(&self.blocking_cp, |conn| { + addresses::dsl::addresses + .filter(addresses::first_appearance_time.le(cp_timestamp_ms)) + .count() + .get_result::(conn) + })?; + let active_addr_count = read_only_blocking!(&self.blocking_cp, |conn| { + active_addresses::dsl::active_addresses + .filter(active_addresses::first_appearance_time.le(cp_timestamp_ms)) + .count() + .get_result::(conn) + })?; + let time_one_day_ago = cp_timestamp_ms - 1000 * 60 * 60 * 24; + let daily_active_addresses = read_only_blocking!(&self.blocking_cp, |conn| { + active_addresses::dsl::active_addresses + .filter(active_addresses::first_appearance_time.le(cp_timestamp_ms)) + .filter(active_addresses::last_appearance_time.gt(time_one_day_ago)) + .select(count(active_addresses::address)) + .first(conn) + })?; + let address_metrics_to_commit = StoredAddressMetrics { + checkpoint: checkpoint.sequence_number, + epoch: checkpoint.epoch, + timestamp_ms: checkpoint.timestamp_ms, + cumulative_addresses: addr_count, + cumulative_active_addresses: active_addr_count, + daily_active_addresses, + }; + transactional_blocking_with_retry!( + &self.blocking_cp, + |conn| { + diesel::insert_into(address_metrics::table) + .values(address_metrics_to_commit.clone()) + .on_conflict_do_nothing() + .execute(conn) + }, + Duration::from_secs(60) + ) + .context("Failed persisting address metrics to PostgresDB")?; + Ok(()) + } + + async fn get_latest_move_call_tx_seq(&self) -> IndexerResult> { + let last_processed_tx_seq = read_only_blocking!(&self.blocking_cp, |conn| { + move_calls::dsl::move_calls + .order(move_calls::dsl::transaction_sequence_number.desc()) + .select((move_calls::dsl::transaction_sequence_number,)) + .first::(conn) + .optional() + }) + .unwrap_or_default(); + Ok(last_processed_tx_seq) + } + + async fn get_latest_move_call_metrics(&self) -> IndexerResult> { + let latest_move_call_metrics = read_only_blocking!(&self.blocking_cp, |conn| { + move_call_metrics::dsl::move_call_metrics + .order(move_call_metrics::epoch.desc()) + .first::(conn) + .optional() + }) + .unwrap_or_default(); + Ok(latest_move_call_metrics.map(|m| m.into())) + } + + fn persist_move_calls_in_tx_range( + &self, + start_tx_seq: i64, + end_tx_seq: i64, + ) -> IndexerResult<()> { + let move_call_persist_query = construct_move_call_persist_query(start_tx_seq, end_tx_seq); + transactional_blocking_with_retry!( + &self.blocking_cp, + |conn| { + diesel::sql_query(move_call_persist_query.clone()).execute(conn)?; + Ok::<(), IndexerError>(()) + }, + Duration::from_secs(10) + ) + .context("Failed persisting move calls to PostgresDB")?; + Ok(()) + } + + async fn calculate_and_persist_move_call_metrics(&self, epoch: i64) -> IndexerResult<()> { + let move_call_query_3d = build_move_call_metric_query(epoch, 3); + let move_call_query_7d = build_move_call_metric_query(epoch, 7); + let move_call_query_30d = build_move_call_metric_query(epoch, 30); + + let mut calculate_tasks = vec![]; + let blocking_cp_3d = self.blocking_cp.clone(); + calculate_tasks.push(tokio::task::spawn_blocking(move || { + read_only_blocking!(&blocking_cp_3d, |conn| { + diesel::sql_query(move_call_query_3d).get_results::(conn) + }) + })); + let blocking_cp_7d = self.blocking_cp.clone(); + calculate_tasks.push(tokio::task::spawn_blocking(move || { + read_only_blocking!(&blocking_cp_7d, |conn| { + diesel::sql_query(move_call_query_7d).get_results::(conn) + }) + })); + let blocking_cp_30d = self.blocking_cp.clone(); + calculate_tasks.push(tokio::task::spawn_blocking(move || { + read_only_blocking!(&blocking_cp_30d, |conn| { + diesel::sql_query(move_call_query_30d).get_results::(conn) + }) + })); + let chained = futures::future::join_all(calculate_tasks) + .await + .into_iter() + .collect::, _>>() + .tap_err(|e| { + error!("Error joining move call calculation tasks: {:?}", e); + })? + .into_iter() + .collect::, _>>() + .tap_err(|e| { + error!("Error calculating move call metrics: {:?}", e); + })? + .into_iter() + .flatten() + .collect::>(); + + let move_call_metrics: Vec = chained + .into_iter() + .filter_map(|queried_move_metrics| { + let package = ObjectID::from_bytes(queried_move_metrics.move_package.clone()).ok(); + let package_str = match package { + Some(p) => p.to_canonical_string(/* with_prefix */ true), + None => { + tracing::error!( + "Failed to parse move package ID: {:?}", + queried_move_metrics.move_package + ); + return None; + } + }; + Some(StoredMoveCallMetrics { + id: None, + epoch, + day: queried_move_metrics.day, + move_package: package_str, + move_module: queried_move_metrics.move_module, + move_function: queried_move_metrics.move_function, + count: queried_move_metrics.count, + }) + }) + .collect(); + + transactional_blocking_with_retry!( + &self.blocking_cp, + |conn| { + diesel::insert_into(move_call_metrics::table) + .values(move_call_metrics.clone()) + .on_conflict_do_nothing() + .execute(conn) + }, + Duration::from_secs(60) + ) + .context("Failed persisting move call metrics to PostgresDB")?; + Ok(()) + } +} + +fn construct_checkpoint_tx_count_query(start_checkpoint: i64, end_checkpoint: i64) -> String { + format!( + "With filtered_txns AS ( + SELECT + t.checkpoint_sequence_number, + c.epoch, + t.timestamp_ms, + t.success_command_count + FROM transactions t + LEFT JOIN checkpoints c + ON t.checkpoint_sequence_number = c.sequence_number + WHERE t.checkpoint_sequence_number >= {} AND t.checkpoint_sequence_number < {} + ) + INSERT INTO tx_count_metrics + SELECT + checkpoint_sequence_number, + epoch, + MAX(timestamp_ms) AS timestamp_ms, + COUNT(*) AS total_transaction_blocks, + SUM(CASE WHEN success_command_count > 0 THEN 1 ELSE 0 END) AS total_successful_transaction_blocks, + SUM(success_command_count) AS total_successful_transactions + FROM filtered_txns + GROUP BY checkpoint_sequence_number, epoch ORDER BY checkpoint_sequence_number + ON CONFLICT (checkpoint_sequence_number) DO NOTHING; + ", start_checkpoint, end_checkpoint + ) +} + +fn construct_peak_tps_query(epoch: i64, offset: i64) -> String { + format!( + "WITH filtered_checkpoints AS ( + SELECT + MAX(checkpoint_sequence_number) AS checkpoint_sequence_number, + SUM(total_successful_transactions) AS total_successful_transactions, + timestamp_ms + FROM + tx_count_metrics + WHERE epoch > ({} - {}) AND epoch <= {} + GROUP BY + timestamp_ms + ), + tps_data AS ( + SELECT + checkpoint_sequence_number, + total_successful_transactions, + timestamp_ms - LAG(timestamp_ms) OVER (ORDER BY timestamp_ms) AS time_diff + FROM + filtered_checkpoints + ) + SELECT + MAX(total_successful_transactions * 1000.0 / time_diff)::float8 as peak_tps + FROM + tps_data + WHERE + time_diff IS NOT NULL; + ", + epoch, offset, epoch + ) +} + +fn construct_address_persisting_query(start_tx_seq: i64, end_tx_seq: i64) -> String { + format!( + "WITH senders AS ( + SELECT + s.sender AS address, + s.tx_sequence_number, + t.timestamp_ms + FROM tx_senders s + JOIN transactions t + ON s.tx_sequence_number = t.tx_sequence_number + WHERE s.tx_sequence_number >= {} AND s.tx_sequence_number < {} + ), + recipients AS ( + SELECT + r.recipient AS address, + r.tx_sequence_number, + t.timestamp_ms + FROM tx_recipients r + JOIN transactions t + ON r.tx_sequence_number = t.tx_sequence_number + WHERE r.tx_sequence_number >= {} AND r.tx_sequence_number < {} + ), + union_address AS ( + SELECT + address, + MIN(tx_sequence_number) as first_seq, + MIN(timestamp_ms) AS first_timestamp, + MAX(tx_sequence_number) as last_seq, + MAX(timestamp_ms) AS last_timestamp + FROM recipients GROUP BY address + UNION ALL + SELECT + address, + MIN(tx_sequence_number) as first_seq, + MIN(timestamp_ms) AS first_timestamp, + MAX(tx_sequence_number) as last_seq, + MAX(timestamp_ms) AS last_timestamp + FROM senders GROUP BY address + ) + INSERT INTO addresses + SELECT + address, + MIN(first_seq) AS first_appearance_tx, + MIN(first_timestamp) AS first_appearance_time, + MAX(last_seq) AS last_appearance_tx, + MAX(last_timestamp) AS last_appearance_time + FROM union_address + GROUP BY address + ON CONFLICT (address) DO UPDATE + SET + last_appearance_tx = GREATEST(EXCLUDED.last_appearance_tx, addresses.last_appearance_tx), + last_appearance_time = GREATEST(EXCLUDED.last_appearance_time, addresses.last_appearance_time); + ", + start_tx_seq, end_tx_seq, start_tx_seq, end_tx_seq + ) +} + +fn construct_active_address_persisting_query(start_tx_seq: i64, end_tx_seq: i64) -> String { + format!( + "WITH senders AS ( + SELECT + s.sender AS address, + s.tx_sequence_number, + t.timestamp_ms + FROM tx_senders s + JOIN transactions t + ON s.tx_sequence_number = t.tx_sequence_number + WHERE s.tx_sequence_number >= {} AND s.tx_sequence_number < {} + ) + INSERT INTO active_addresses + SELECT + address, + MIN(tx_sequence_number) AS first_appearance_tx, + MIN(timestamp_ms) AS first_appearance_time, + MAX(tx_sequence_number) AS last_appearance_tx, + MAX(timestamp_ms) AS last_appearance_time + FROM senders + GROUP BY address + ON CONFLICT (address) DO UPDATE + SET + last_appearance_tx = GREATEST(EXCLUDED.last_appearance_tx, active_addresses.last_appearance_tx), + last_appearance_time = GREATEST(EXCLUDED.last_appearance_time, active_addresses.last_appearance_time); + ", + start_tx_seq, end_tx_seq + ) +} + +fn construct_move_call_persist_query(start_tx_seq: i64, end_tx_seq: i64) -> String { + format!( + "INSERT INTO move_calls + SELECT + m.tx_sequence_number AS transaction_sequence_number, + c.sequence_number AS checkpoint_sequence_number, + c.epoch AS epoch, + m.package AS move_package, + m.module AS move_module, + m.func AS move_function + FROM tx_calls m + INNER JOIN transactions t + ON m.tx_sequence_number = t.tx_sequence_number + INNER JOIN checkpoints c + ON t.checkpoint_sequence_number = c.sequence_number + WHERE m.tx_sequence_number >= {} AND m.tx_sequence_number < {} + ON CONFLICT (transaction_sequence_number, move_package, move_module, move_function) DO NOTHING; + ", + start_tx_seq, end_tx_seq + ) +} diff --git a/crates/iota-indexer/src/test_utils.rs b/crates/iota-indexer/src/test_utils.rs index 3ecd5781fd4..58b452ddc29 100644 --- a/crates/iota-indexer/src/test_utils.rs +++ b/crates/iota-indexer/src/test_utils.rs @@ -13,8 +13,8 @@ use tracing::info; use crate::{ db::{new_pg_connection_pool_with_config, reset_database, PgConnectionPoolConfig}, errors::IndexerError, - handlers::objects_snapshot_processor::SnapshotLagConfig, indexer::Indexer, + processors::objects_snapshot_processor::SnapshotLagConfig, store::PgIndexerStore, IndexerConfig, IndexerMetrics, }; diff --git a/crates/iota-json-rpc-api/src/extended.rs b/crates/iota-json-rpc-api/src/extended.rs index 9fea355dc63..943eb3afb72 100644 --- a/crates/iota-json-rpc-api/src/extended.rs +++ b/crates/iota-json-rpc-api/src/extended.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 use iota_json_rpc_types::{ - CheckpointedObjectID, EpochInfo, EpochPage, IotaObjectResponseQuery, QueryObjectsPage, + AddressMetrics, EpochInfo, EpochMetricsPage, EpochPage, MoveCallMetrics, NetworkMetrics, }; use iota_open_rpc_macros::open_rpc; use iota_types::iota_serde::BigInt; @@ -19,30 +19,48 @@ pub trait ExtendedApi { #[method(name = "getEpochs")] async fn get_epochs( &self, - /// optional paging cursor + /// Optional paging cursor cursor: Option>, - /// maximum number of items per page + /// Maximum number of items per page limit: Option, - /// flag to return results in descending order + /// Flag to return results in descending order descending_order: Option, ) -> RpcResult; + /// Return a list of epoch metrics, which is a subset of epoch info + #[method(name = "getEpochMetrics")] + async fn get_epoch_metrics( + &self, + /// Optional paging cursor + cursor: Option>, + /// Maximum number of items per page + limit: Option, + /// Flag to return results in descending order + descending_order: Option, + ) -> RpcResult; + /// Return current epoch info #[method(name = "getCurrentEpoch")] async fn get_current_epoch(&self) -> RpcResult; - /// Return the list of queried objects. Note that this is an enhanced full node only api. - #[rustfmt::skip] - #[method(name = "queryObjects")] - async fn query_objects( + /// Return Network metrics + #[method(name = "getNetworkMetrics")] + async fn get_network_metrics(&self) -> RpcResult; + + /// Return move call metrics + #[method(name = "getMoveCallMetrics")] + async fn get_move_call_metrics(&self) -> RpcResult; + + /// Address related metrics + #[method(name = "getLatestAddressMetrics")] + async fn get_latest_address_metrics(&self) -> RpcResult; + #[method(name = "getCheckpointAddressMetrics")] + async fn get_checkpoint_address_metrics(&self, checkpoint: u64) -> RpcResult; + #[method(name = "getAllEpochAddressMetrics")] + async fn get_all_epoch_address_metrics( &self, - /// the objects query criteria. - query: IotaObjectResponseQuery, - /// An optional paging cursor. If provided, the query will start from the next item after the specified cursor. Default to start from the first item if not specified. - cursor: Option, - /// Max number of items returned per page, default to [QUERY_MAX_RESULT_LIMIT] if not specified. - limit: Option, - ) -> RpcResult; + descending_order: Option, + ) -> RpcResult>; #[method(name = "getTotalTransactions")] async fn get_total_transactions(&self) -> RpcResult>; diff --git a/crates/iota-json-rpc-types/src/iota_extended.rs b/crates/iota-json-rpc-types/src/iota_extended.rs index 63035433ed0..f6c5e6ff476 100644 --- a/crates/iota-json-rpc-types/src/iota_extended.rs +++ b/crates/iota-json-rpc-types/src/iota_extended.rs @@ -20,29 +20,33 @@ use serde_with::{serde_as, DisplayFromStr}; use crate::Page; pub type EpochPage = Page>; +pub type EpochMetricsPage = Page>; #[serde_as] #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct EpochInfo { - /// epoch number + /// Epoch number #[schemars(with = "BigInt")] #[serde_as(as = "BigInt")] pub epoch: EpochId, - /// list of validators included in epoch + /// List of validators included in epoch pub validators: Vec, - /// count of tx in epoch + /// Count of tx in epoch #[schemars(with = "BigInt")] #[serde_as(as = "BigInt")] pub epoch_total_transactions: u64, - /// first, last checkpoint sequence numbers + /// First, last checkpoint sequence numbers #[schemars(with = "BigInt")] #[serde_as(as = "BigInt")] pub first_checkpoint_id: CheckpointSequenceNumber, + /// The timestamp when the epoch started. #[schemars(with = "BigInt")] #[serde_as(as = "BigInt")] pub epoch_start_timestamp: u64, + /// The end of epoch information. pub end_of_epoch_info: Option, + /// The reference gas price for the given epoch. pub reference_gas_price: Option, } @@ -57,6 +61,31 @@ impl EpochInfo { } } +/// A light-weight version of `EpochInfo` for faster loading +#[serde_as] +#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct EpochMetrics { + /// The current epoch ID. + #[schemars(with = "BigInt")] + #[serde_as(as = "BigInt")] + pub epoch: EpochId, + /// The total number of transactions in the epoch. + #[schemars(with = "BigInt")] + #[serde_as(as = "BigInt")] + pub epoch_total_transactions: u64, + /// The first checkpoint ID of the epoch. + #[schemars(with = "BigInt")] + #[serde_as(as = "BigInt")] + pub first_checkpoint_id: CheckpointSequenceNumber, + /// The timestamp when the epoch started. + #[schemars(with = "BigInt")] + #[serde_as(as = "BigInt")] + pub epoch_start_timestamp: u64, + /// The end of epoch information. + pub end_of_epoch_info: Option, +} + #[serde_as] #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] #[serde(rename_all = "camelCase")] @@ -101,14 +130,85 @@ pub struct EndOfEpochInfo { } #[serde_as] -#[derive(Serialize, Deserialize, Debug, JsonSchema)] +#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)] +#[serde(rename_all = "camelCase")] +pub struct NetworkMetrics { + /// Current TPS - Transaction Blocks per Second. + pub current_tps: f64, + /// Peak TPS in the past 30 days + pub tps_30_days: f64, + /// Total number of packages published in the network + #[schemars(with = "BigInt")] + #[serde_as(as = "BigInt")] + pub total_packages: u64, + /// Total number of addresses seen in the network + #[schemars(with = "BigInt")] + #[serde_as(as = "BigInt")] + pub total_addresses: u64, + /// Total number of live objects in the network + #[schemars(with = "BigInt")] + #[serde_as(as = "BigInt")] + pub total_objects: u64, + /// Current epoch number + #[schemars(with = "BigInt")] + #[serde_as(as = "BigInt")] + pub current_epoch: u64, + /// Current checkpoint number + #[schemars(with = "BigInt")] + #[serde_as(as = "BigInt")] + pub current_checkpoint: u64, +} + +#[serde_as] +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct MoveCallMetrics { + /// The count of calls of each function in the last 3 days. + #[schemars(with = "Vec<(MoveFunctionName, BigInt)>")] + #[serde_as(as = "Vec<(_, BigInt)>")] + pub rank_3_days: Vec<(MoveFunctionName, usize)>, + /// The count of calls of each function in the last 7 days. + #[schemars(with = "Vec<(MoveFunctionName, BigInt)>")] + #[serde_as(as = "Vec<(_, BigInt)>")] + pub rank_7_days: Vec<(MoveFunctionName, usize)>, + /// The count of calls of each function in the last 30 days. + #[schemars(with = "Vec<(MoveFunctionName, BigInt)>")] + #[serde_as(as = "Vec<(_, BigInt)>")] + pub rank_30_days: Vec<(MoveFunctionName, usize)>, +} + +/// Identifies a Move function. +#[serde_as] +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct MoveFunctionName { + /// The package ID to which the function belongs. pub package: ObjectID, + /// The module name to which the function belongs. #[schemars(with = "String")] #[serde_as(as = "DisplayFromStr")] pub module: Identifier, + /// The function name. #[schemars(with = "String")] #[serde_as(as = "DisplayFromStr")] pub function: Identifier, } + +/// Provides metrics about the addresses. +#[serde_as] +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct AddressMetrics { + /// The checkpoint sequence number at which the metrics were computed. + pub checkpoint: u64, + /// The epoch to which the checkpoint is assigned. + pub epoch: u64, + /// The checkpoint timestamp. + pub timestamp_ms: u64, + /// The count of sender and recipient addresses. + pub cumulative_addresses: u64, + /// The count of sender addresses. + pub cumulative_active_addresses: u64, + /// The count of daily unique sender addresses. + pub daily_active_addresses: u64, +} diff --git a/crates/iota-open-rpc/spec/openrpc.json b/crates/iota-open-rpc/spec/openrpc.json index 17e86bb2aa6..fc6b527fe02 100644 --- a/crates/iota-open-rpc/spec/openrpc.json +++ b/crates/iota-open-rpc/spec/openrpc.json @@ -3259,6 +3259,32 @@ } ] }, + { + "name": "iotax_getAllEpochAddressMetrics", + "tags": [ + { + "name": "Extended API" + } + ], + "params": [ + { + "name": "descending_order", + "schema": { + "type": "boolean" + } + } + ], + "result": { + "name": "Vec", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AddressMetrics" + } + } + } + }, { "name": "iotax_getBalance", "tags": [ @@ -3316,6 +3342,32 @@ } ] }, + { + "name": "iotax_getCheckpointAddressMetrics", + "tags": [ + { + "name": "Extended API" + } + ], + "params": [ + { + "name": "checkpoint", + "required": true, + "schema": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + ], + "result": { + "name": "AddressMetrics", + "required": true, + "schema": { + "$ref": "#/components/schemas/AddressMetrics" + } + } + }, { "name": "iotax_getCoinMetadata", "tags": [ @@ -3528,6 +3580,23 @@ } ] }, + { + "name": "iotax_getCurrentEpoch", + "tags": [ + { + "name": "Extended API" + } + ], + "description": "Return current epoch info", + "params": [], + "result": { + "name": "EpochInfo", + "required": true, + "schema": { + "$ref": "#/components/schemas/EpochInfo" + } + } + }, { "name": "iotax_getDynamicFieldObject", "tags": [ @@ -3708,6 +3777,105 @@ } ] }, + { + "name": "iotax_getEpochMetrics", + "tags": [ + { + "name": "Extended API" + } + ], + "description": "Return a list of epoch metrics, which is a subset of epoch info", + "params": [ + { + "name": "cursor", + "description": "Optional paging cursor", + "schema": { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + }, + { + "name": "limit", + "description": "Maximum number of items per page", + "schema": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + }, + { + "name": "descending_order", + "description": "Flag to return results in descending order", + "schema": { + "type": "boolean" + } + } + ], + "result": { + "name": "EpochMetricsPage", + "required": true, + "schema": { + "$ref": "#/components/schemas/Page_for_EpochMetrics_and_BigInt_for_uint64" + } + } + }, + { + "name": "iotax_getEpochs", + "tags": [ + { + "name": "Extended API" + } + ], + "description": "Return a list of epoch info", + "params": [ + { + "name": "cursor", + "description": "Optional paging cursor", + "schema": { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + }, + { + "name": "limit", + "description": "Maximum number of items per page", + "schema": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + }, + { + "name": "descending_order", + "description": "Flag to return results in descending order", + "schema": { + "type": "boolean" + } + } + ], + "result": { + "name": "EpochPage", + "required": true, + "schema": { + "$ref": "#/components/schemas/Page_for_EpochInfo_and_BigInt_for_uint64" + } + } + }, + { + "name": "iotax_getLatestAddressMetrics", + "tags": [ + { + "name": "Extended API" + } + ], + "description": "Address related metrics", + "params": [], + "result": { + "name": "AddressMetrics", + "required": true, + "schema": { + "$ref": "#/components/schemas/AddressMetrics" + } + } + }, { "name": "iotax_getLatestIotaSystemState", "tags": [ @@ -3735,6 +3903,40 @@ } ] }, + { + "name": "iotax_getMoveCallMetrics", + "tags": [ + { + "name": "Extended API" + } + ], + "description": "Return move call metrics", + "params": [], + "result": { + "name": "MoveCallMetrics", + "required": true, + "schema": { + "$ref": "#/components/schemas/MoveCallMetrics" + } + } + }, + { + "name": "iotax_getNetworkMetrics", + "tags": [ + { + "name": "Extended API" + } + ], + "description": "Return Network metrics", + "params": [], + "result": { + "name": "NetworkMetrics", + "required": true, + "schema": { + "$ref": "#/components/schemas/NetworkMetrics" + } + } + }, { "name": "iotax_getOwnedObjects", "tags": [ @@ -4154,6 +4356,22 @@ } ] }, + { + "name": "iotax_getTotalTransactions", + "tags": [ + { + "name": "Extended API" + } + ], + "params": [], + "result": { + "name": "BigInt", + "required": true, + "schema": { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + } + }, { "name": "iotax_getValidatorsApy", "tags": [ @@ -5423,6 +5641,56 @@ ], "components": { "schemas": { + "AddressMetrics": { + "description": "Provides metrics about the addresses.", + "type": "object", + "required": [ + "checkpoint", + "cumulativeActiveAddresses", + "cumulativeAddresses", + "dailyActiveAddresses", + "epoch", + "timestampMs" + ], + "properties": { + "checkpoint": { + "description": "The checkpoint sequence number at which the metrics were computed.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "cumulativeActiveAddresses": { + "description": "The count of sender addresses.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "cumulativeAddresses": { + "description": "The count of sender and recipient addresses.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "dailyActiveAddresses": { + "description": "The count of daily unique sender addresses.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "epoch": { + "description": "The epoch to which the checkpoint is assigned.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "timestampMs": { + "description": "The checkpoint timestamp.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, "AuthorityPublicKeyBytes": { "description": "Defines the compressed version of the public key that we pass around in Iota", "allOf": [ @@ -5491,6 +5759,9 @@ "description": "Base64 encoding", "type": "string" }, + "BigInt_for_uint": { + "type": "string" + }, "BigInt_for_uint128": { "type": "string" }, @@ -6128,81 +6399,267 @@ "type": { "type": "string" }, - "value": true - } - }, - "DynamicFieldType": { - "type": "string", - "enum": [ - "DynamicField", - "DynamicObject" - ] - }, - "ECMHLiveObjectSetDigest": { - "description": "The Sha256 digest of an EllipticCurveMultisetHash committing to the live object set.", - "type": "object", - "required": [ - "digest" - ], - "properties": { - "digest": { + "value": true + } + }, + "DynamicFieldType": { + "type": "string", + "enum": [ + "DynamicField", + "DynamicObject" + ] + }, + "ECMHLiveObjectSetDigest": { + "description": "The Sha256 digest of an EllipticCurveMultisetHash committing to the live object set.", + "type": "object", + "required": [ + "digest" + ], + "properties": { + "digest": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "maxItems": 32, + "minItems": 32 + } + } + }, + "Ed25519IotaSignature": { + "$ref": "#/components/schemas/Base64" + }, + "EndOfEpochData": { + "type": "object", + "required": [ + "epochCommitments", + "epochSupplyChange", + "nextEpochCommittee", + "nextEpochProtocolVersion" + ], + "properties": { + "epochCommitments": { + "description": "Commitments to epoch specific state (e.g. live object set)", + "type": "array", + "items": { + "$ref": "#/components/schemas/CheckpointCommitment" + } + }, + "epochSupplyChange": { + "description": "The number of tokens that were minted (if positive) or burnt (if negative) in this epoch.", + "type": "integer", + "format": "int64" + }, + "nextEpochCommittee": { + "description": "next_epoch_committee is `Some` if and only if the current checkpoint is the last checkpoint of an epoch. Therefore next_epoch_committee can be used to pick the last checkpoint of an epoch, which is often useful to get epoch level summary stats like total gas cost of an epoch, or the total number of transactions from genesis to the end of an epoch. The committee is stored as a vector of validator pub key and stake pairs. The vector should be sorted based on the Committee data structure.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/components/schemas/AuthorityPublicKeyBytes" + }, + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "nextEpochProtocolVersion": { + "description": "The protocol version that is in effect during the epoch that starts immediately after this checkpoint.", + "allOf": [ + { + "$ref": "#/components/schemas/ProtocolVersion" + } + ] + } + } + }, + "EndOfEpochInfo": { + "type": "object", + "required": [ + "burntTokensAmount", + "epochEndTimestamp", + "lastCheckpointId", + "mintedTokensAmount", + "protocolVersion", + "referenceGasPrice", + "storageCharge", + "storageFundBalance", + "storageRebate", + "totalGasFees", + "totalStake", + "totalStakeRewardsDistributed" + ], + "properties": { + "burntTokensAmount": { + "$ref": "#/components/schemas/BigInt_for_uint64" + }, + "epochEndTimestamp": { + "$ref": "#/components/schemas/BigInt_for_uint64" + }, + "lastCheckpointId": { + "$ref": "#/components/schemas/BigInt_for_uint64" + }, + "mintedTokensAmount": { + "$ref": "#/components/schemas/BigInt_for_uint64" + }, + "protocolVersion": { + "description": "existing fields from `SystemEpochInfoEvent` (without epoch)", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] + }, + "referenceGasPrice": { + "$ref": "#/components/schemas/BigInt_for_uint64" + }, + "storageCharge": { + "$ref": "#/components/schemas/BigInt_for_uint64" + }, + "storageFundBalance": { + "$ref": "#/components/schemas/BigInt_for_uint64" + }, + "storageRebate": { + "$ref": "#/components/schemas/BigInt_for_uint64" + }, + "totalGasFees": { + "$ref": "#/components/schemas/BigInt_for_uint64" + }, + "totalStake": { + "$ref": "#/components/schemas/BigInt_for_uint64" + }, + "totalStakeRewardsDistributed": { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + } + }, + "EpochInfo": { + "type": "object", + "required": [ + "epoch", + "epochStartTimestamp", + "epochTotalTransactions", + "firstCheckpointId", + "validators" + ], + "properties": { + "endOfEpochInfo": { + "description": "The end of epoch information.", + "anyOf": [ + { + "$ref": "#/components/schemas/EndOfEpochInfo" + }, + { + "type": "null" + } + ] + }, + "epoch": { + "description": "Epoch number", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] + }, + "epochStartTimestamp": { + "description": "The timestamp when the epoch started.", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] + }, + "epochTotalTransactions": { + "description": "Count of tx in epoch", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] + }, + "firstCheckpointId": { + "description": "First, last checkpoint sequence numbers", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] + }, + "referenceGasPrice": { + "description": "The reference gas price for the given epoch.", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "validators": { + "description": "List of validators included in epoch", "type": "array", "items": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - }, - "maxItems": 32, - "minItems": 32 + "$ref": "#/components/schemas/IotaValidatorSummary" + } } } }, - "Ed25519IotaSignature": { - "$ref": "#/components/schemas/Base64" - }, - "EndOfEpochData": { + "EpochMetrics": { + "description": "A light-weight version of `EpochInfo` for faster loading", "type": "object", "required": [ - "epochCommitments", - "epochSupplyChange", - "nextEpochCommittee", - "nextEpochProtocolVersion" + "epoch", + "epochStartTimestamp", + "epochTotalTransactions", + "firstCheckpointId" ], "properties": { - "epochCommitments": { - "description": "Commitments to epoch specific state (e.g. live object set)", - "type": "array", - "items": { - "$ref": "#/components/schemas/CheckpointCommitment" - } + "endOfEpochInfo": { + "description": "The end of epoch information.", + "anyOf": [ + { + "$ref": "#/components/schemas/EndOfEpochInfo" + }, + { + "type": "null" + } + ] }, - "epochSupplyChange": { - "description": "The number of tokens that were minted (if positive) or burnt (if negative) in this epoch.", - "type": "integer", - "format": "int64" + "epoch": { + "description": "The current epoch ID.", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] }, - "nextEpochCommittee": { - "description": "next_epoch_committee is `Some` if and only if the current checkpoint is the last checkpoint of an epoch. Therefore next_epoch_committee can be used to pick the last checkpoint of an epoch, which is often useful to get epoch level summary stats like total gas cost of an epoch, or the total number of transactions from genesis to the end of an epoch. The committee is stored as a vector of validator pub key and stake pairs. The vector should be sorted based on the Committee data structure.", - "type": "array", - "items": { - "type": "array", - "items": [ - { - "$ref": "#/components/schemas/AuthorityPublicKeyBytes" - }, - { - "$ref": "#/components/schemas/BigInt_for_uint64" - } - ], - "maxItems": 2, - "minItems": 2 - } + "epochStartTimestamp": { + "description": "The timestamp when the epoch started.", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] }, - "nextEpochProtocolVersion": { - "description": "The protocol version that is in effect during the epoch that starts immediately after this checkpoint.", + "epochTotalTransactions": { + "description": "The total number of transactions in the epoch.", "allOf": [ { - "$ref": "#/components/schemas/ProtocolVersion" + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] + }, + "firstCheckpointId": { + "description": "The first checkpoint ID of the epoch.", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" } ] } @@ -8524,6 +8981,67 @@ } } }, + "MoveCallMetrics": { + "type": "object", + "required": [ + "rank30Days", + "rank3Days", + "rank7Days" + ], + "properties": { + "rank30Days": { + "description": "The count of calls of each function in the last 30 days.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/components/schemas/MoveFunctionName" + }, + { + "$ref": "#/components/schemas/BigInt_for_uint" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "rank3Days": { + "description": "The count of calls of each function in the last 3 days.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/components/schemas/MoveFunctionName" + }, + { + "$ref": "#/components/schemas/BigInt_for_uint" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "rank7Days": { + "description": "The count of calls of each function in the last 7 days.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/components/schemas/MoveFunctionName" + }, + { + "$ref": "#/components/schemas/BigInt_for_uint" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + } + }, "MoveCallParams": { "type": "object", "required": [ @@ -8579,6 +9097,33 @@ } ] }, + "MoveFunctionName": { + "description": "Identifies a Move function.", + "type": "object", + "required": [ + "function", + "module", + "package" + ], + "properties": { + "function": { + "description": "The function name.", + "type": "string" + }, + "module": { + "description": "The module name to which the function belongs.", + "type": "string" + }, + "package": { + "description": "The package ID to which the function belongs.", + "allOf": [ + { + "$ref": "#/components/schemas/ObjectID" + } + ] + } + } + }, "MoveStruct": { "anyOf": [ { @@ -8797,6 +9342,70 @@ } } }, + "NetworkMetrics": { + "type": "object", + "required": [ + "currentCheckpoint", + "currentEpoch", + "currentTps", + "totalAddresses", + "totalObjects", + "totalPackages", + "tps30Days" + ], + "properties": { + "currentCheckpoint": { + "description": "Current checkpoint number", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] + }, + "currentEpoch": { + "description": "Current epoch number", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] + }, + "currentTps": { + "description": "Current TPS - Transaction Blocks per Second.", + "type": "number", + "format": "double" + }, + "totalAddresses": { + "description": "Total number of addresses seen in the network", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] + }, + "totalObjects": { + "description": "Total number of live objects in the network", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] + }, + "totalPackages": { + "description": "Total number of packages published in the network", + "allOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + } + ] + }, + "tps30Days": { + "description": "Peak TPS in the past 30 days", + "type": "number", + "format": "double" + } + } + }, "ObjectChange": { "description": "ObjectChange are derived from the object mutations in the TransactionEffect to provide richer object information.", "oneOf": [ @@ -9636,6 +10245,64 @@ } } }, + "Page_for_EpochInfo_and_BigInt_for_uint64": { + "description": "`next_cursor` points to the last item in the page; Reading with `next_cursor` will start from the next item after `next_cursor` if `next_cursor` is `Some`, otherwise it will start from the first item.", + "type": "object", + "required": [ + "data", + "hasNextPage" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EpochInfo" + } + }, + "hasNextPage": { + "type": "boolean" + }, + "nextCursor": { + "anyOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + }, + { + "type": "null" + } + ] + } + } + }, + "Page_for_EpochMetrics_and_BigInt_for_uint64": { + "description": "`next_cursor` points to the last item in the page; Reading with `next_cursor` will start from the next item after `next_cursor` if `next_cursor` is `Some`, otherwise it will start from the first item.", + "type": "object", + "required": [ + "data", + "hasNextPage" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EpochMetrics" + } + }, + "hasNextPage": { + "type": "boolean" + }, + "nextCursor": { + "anyOf": [ + { + "$ref": "#/components/schemas/BigInt_for_uint64" + }, + { + "type": "null" + } + ] + } + } + }, "Page_for_Event_and_EventID": { "description": "`next_cursor` points to the last item in the page; Reading with `next_cursor` will start from the next item after `next_cursor` if `next_cursor` is `Some`, otherwise it will start from the first item.", "type": "object", diff --git a/crates/iota-open-rpc/src/generate_json_rpc_spec.rs b/crates/iota-open-rpc/src/generate_json_rpc_spec.rs index e8a5f245308..30624f4fe15 100644 --- a/crates/iota-open-rpc/src/generate_json_rpc_spec.rs +++ b/crates/iota-open-rpc/src/generate_json_rpc_spec.rs @@ -13,7 +13,7 @@ use iota_json_rpc::{ transaction_builder_api::TransactionBuilderApi, transaction_execution_api::TransactionExecutionApi, IotaRpcModule, }; -use iota_json_rpc_api::{IndexerApiOpenRpc, MoveUtilsOpenRpc}; +use iota_json_rpc_api::{ExtendedApiOpenRpc, IndexerApiOpenRpc, MoveUtilsOpenRpc}; use pretty_assertions::assert_str_eq; use crate::examples::RpcExampleProvider; @@ -53,8 +53,7 @@ async fn main() { open_rpc.add_module(TransactionExecutionApi::rpc_doc_module()); open_rpc.add_module(TransactionBuilderApi::rpc_doc_module()); open_rpc.add_module(GovernanceReadApi::rpc_doc_module()); - // temporarily remove api ref content for indexer methods - // open_rpc.add_module(ExtendedApiOpenRpc::module_doc()); + open_rpc.add_module(ExtendedApiOpenRpc::module_doc()); open_rpc.add_module(MoveUtilsOpenRpc::module_doc()); open_rpc.add_examples(RpcExampleProvider::new().examples()); diff --git a/docker/pg-services-local/docker-compose.yaml b/docker/pg-services-local/docker-compose.yaml index 17707574935..16b85dcc5b5 100644 --- a/docker/pg-services-local/docker-compose.yaml +++ b/docker/pg-services-local/docker-compose.yaml @@ -84,6 +84,30 @@ services: - postgres - indexer-sync + indexer-analytics: + image: iota-indexer + container_name: indexer-analytics + hostname: indexer-analytics + restart: on-failure + networks: + iota-network: + environment: + - RUST_BACKTRACE=1 + - RUST_LOG=info + - RPC_WORKER_THREAD=12 + command: + - /usr/local/bin/iota-indexer + - --db-url=postgres://iota_indexer:iota_indexer@postgres:5432/iota_indexer + - --rpc-client-url=http://local-network:9000 + - --client-metric-port=9181 + - --analytical-worker + ports: + - "127.0.0.1:9184:9181/tcp" + depends_on: + - local-network + - postgres + - indexer-sync + graphql-server: image: iota-graphql-rpc build: diff --git a/sdk/graphql-transport/src/generated/queries.ts b/sdk/graphql-transport/src/generated/queries.ts index c2ba411eb1b..9df5eb93c14 100644 --- a/sdk/graphql-transport/src/generated/queries.ts +++ b/sdk/graphql-transport/src/generated/queries.ts @@ -164,16 +164,6 @@ export type Address = IOwner & { * `0x2::iota::IOTA`. */ coins: CoinConnection; - /** - * The domain explicitly configured as the default domain pointing to this - * address. - */ - defaultIotansName?: Maybe; - /** - * The IotansRegistration NFTs owned by this address. These grant the owner - * the capability to manage the associated domain. - */ - iotansRegistrations: IotansRegistrationConnection; /** Objects owned by this address, optionally `filter`-ed. */ objects: MoveObjectConnection; /** The `0x3::staking_pool::StakedIota` objects owned by this address. */ @@ -221,27 +211,6 @@ export type AddressCoinsArgs = { }; -/** - * The 32-byte address that is an account address (corresponding to a public - * key). - */ -export type AddressDefaultIotansNameArgs = { - format?: InputMaybe; -}; - - -/** - * The 32-byte address that is an account address (corresponding to a public - * key). - */ -export type AddressIotansRegistrationsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - /** * The 32-byte address that is an account address (corresponding to a public * key). @@ -612,11 +581,6 @@ export type Coin = IMoveObject & IObject & IOwner & { * signature, and the BCS of the corresponding data. */ contents?: Maybe; - /** - * The domain explicitly configured as the default domain pointing to this - * object. - */ - defaultIotansName?: Maybe; /** * 32-byte hash that identifies the object's contents, encoded as a Base58 * string. @@ -662,11 +626,6 @@ export type Coin = IMoveObject & IObject & IOwner & { * have the `key` and `store` abilities. */ hasPublicTransfer: Scalars['Boolean']['output']; - /** - * The IotansRegistration NFTs owned by this object. These grant the owner - * the capability to manage the associated domain. - */ - iotansRegistrations: IotansRegistrationConnection; /** Objects owned by this object, optionally `filter`-ed. */ objects: MoveObjectConnection; /** The owner type of this object: Immutable, Shared, Parent, Address */ @@ -724,12 +683,6 @@ export type CoinCoinsArgs = { }; -/** Some 0x2::coin::Coin Move object. */ -export type CoinDefaultIotansNameArgs = { - format?: InputMaybe; -}; - - /** Some 0x2::coin::Coin Move object. */ export type CoinDynamicFieldArgs = { name: DynamicFieldName; @@ -751,15 +704,6 @@ export type CoinDynamicObjectFieldArgs = { }; -/** Some 0x2::coin::Coin Move object. */ -export type CoinIotansRegistrationsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - /** Some 0x2::coin::Coin Move object. */ export type CoinObjectsArgs = { after?: InputMaybe; @@ -841,11 +785,6 @@ export type CoinMetadata = IMoveObject & IObject & IOwner & { contents?: Maybe; /** The number of decimal places used to represent the token. */ decimals?: Maybe; - /** - * The domain explicitly configured as the default domain pointing to this - * object. - */ - defaultIotansName?: Maybe; /** Optional description of the token, provided by the creator of the token. */ description?: Maybe; /** @@ -894,11 +833,6 @@ export type CoinMetadata = IMoveObject & IObject & IOwner & { */ hasPublicTransfer: Scalars['Boolean']['output']; iconUrl?: Maybe; - /** - * The IotansRegistration NFTs owned by this object. These grant the owner - * the capability to manage the associated domain. - */ - iotansRegistrations: IotansRegistrationConnection; /** Full, official name of the token. */ name?: Maybe; /** Objects owned by this object, optionally `filter`-ed. */ @@ -962,12 +896,6 @@ export type CoinMetadataCoinsArgs = { }; -/** The metadata for a coin type. */ -export type CoinMetadataDefaultIotansNameArgs = { - format?: InputMaybe; -}; - - /** The metadata for a coin type. */ export type CoinMetadataDynamicFieldArgs = { name: DynamicFieldName; @@ -989,15 +917,6 @@ export type CoinMetadataDynamicObjectFieldArgs = { }; -/** The metadata for a coin type. */ -export type CoinMetadataIotansRegistrationsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - /** The metadata for a coin type. */ export type CoinMetadataObjectsArgs = { after?: InputMaybe; @@ -1080,11 +999,6 @@ export type DisplayEntry = { value?: Maybe; }; -export enum DomainFormat { - At = 'AT', - Dot = 'DOT' -} - export type DryRunEffect = { __typename?: 'DryRunEffect'; /** @@ -1479,8 +1393,6 @@ export enum Feature { Coins = 'COINS', /** Querying an object's dynamic fields. */ DynamicFields = 'DYNAMIC_FIELDS', - /** IotaNS name and reverse name look-up. */ - NameService = 'NAME_SERVICE', /** Transaction and Event subscriptions. */ Subscriptions = 'SUBSCRIPTIONS', /** @@ -1705,10 +1617,6 @@ export type IOwner = { * `type` is a filter on the coin's type parameter, defaulting to `0x2::iota::IOTA`. */ coins: CoinConnection; - /** The domain explicitly configured as the default domain pointing to this object or address. */ - defaultIotansName?: Maybe; - /** The IotansRegistration NFTs owned by this object or address. These grant the owner the capability to manage the associated domain. */ - iotansRegistrations: IotansRegistrationConnection; /** Objects owned by this object or address, optionally `filter`-ed. */ objects: MoveObjectConnection; /** The `0x3::staking_pool::StakedIota` objects owned by this object or address. */ @@ -1759,33 +1667,6 @@ export type IOwnerCoinsArgs = { }; -/** - * Interface implemented by GraphQL types representing entities that can own - * objects. Object owners are identified by an address which can represent - * either the public key of an account or another object. The same address can - * only refer to an account or an object, never both, but it is not possible to - * know which up-front. - */ -export type IOwnerDefaultIotansNameArgs = { - format?: InputMaybe; -}; - - -/** - * Interface implemented by GraphQL types representing entities that can own - * objects. Object owners are identified by an address which can represent - * either the public key of an account or another object. The same address can - * only refer to an account or an object, never both, but it is not possible to - * know which up-front. - */ -export type IOwnerIotansRegistrationsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - /** * Interface implemented by GraphQL types representing entities that can own * objects. Object owners are identified by an address which can represent @@ -1835,217 +1716,6 @@ export type Input = { ix: Scalars['Int']['output']; }; -export type IotansRegistration = IMoveObject & IObject & IOwner & { - __typename?: 'IotansRegistration'; - address: Scalars['IotaAddress']['output']; - /** - * Total balance of all coins with marker type owned by this object. If - * type is not supplied, it defaults to `0x2::iota::IOTA`. - */ - balance?: Maybe; - /** The balances of all coin types owned by this object. */ - balances: BalanceConnection; - /** The Base64-encoded BCS serialization of the object's content. */ - bcs?: Maybe; - /** - * The coin objects for this object. - * - * `type` is a filter on the coin's type parameter, defaulting to - * `0x2::iota::IOTA`. - */ - coins: CoinConnection; - /** - * Displays the contents of the Move object in a JSON string and through - * GraphQL types. Also provides the flat representation of the type - * signature, and the BCS of the corresponding data. - */ - contents?: Maybe; - /** - * The domain explicitly configured as the default domain pointing to this - * object. - */ - defaultIotansName?: Maybe; - /** - * 32-byte hash that identifies the object's contents, encoded as a Base58 - * string. - */ - digest?: Maybe; - /** - * The set of named templates defined on-chain for the type of this object, - * to be handled off-chain. The server substitutes data from the object - * into these templates to generate a display string per template. - */ - display?: Maybe>; - /** Domain name of the IotansRegistration object */ - domain: Scalars['String']['output']; - /** - * Access a dynamic field on an object using its name. Names are arbitrary - * Move values whose type have `copy`, `drop`, and `store`, and are - * specified using their type, and their BCS contents, Base64 encoded. - * - * Dynamic fields on wrapped objects can be accessed by using the same API - * under the Owner type. - */ - dynamicField?: Maybe; - /** - * The dynamic fields and dynamic object fields on an object. - * - * Dynamic fields on wrapped objects can be accessed by using the same API - * under the Owner type. - */ - dynamicFields: DynamicFieldConnection; - /** - * Access a dynamic object field on an object using its name. Names are - * arbitrary Move values whose type have `copy`, `drop`, and `store`, - * and are specified using their type, and their BCS contents, Base64 - * encoded. The value of a dynamic object field can also be accessed - * off-chain directly via its address (e.g. using `Query.object`). - * - * Dynamic fields on wrapped objects can be accessed by using the same API - * under the Owner type. - */ - dynamicObjectField?: Maybe; - /** - * Determines whether a transaction can transfer this object, using the - * TransferObjects transaction command or - * `iota::transfer::public_transfer`, both of which require the object to - * have the `key` and `store` abilities. - */ - hasPublicTransfer: Scalars['Boolean']['output']; - /** - * The IotansRegistration NFTs owned by this object. These grant the owner - * the capability to manage the associated domain. - */ - iotansRegistrations: IotansRegistrationConnection; - /** Objects owned by this object, optionally `filter`-ed. */ - objects: MoveObjectConnection; - /** The owner type of this object: Immutable, Shared, Parent, Address */ - owner?: Maybe; - /** The transaction block that created this version of the object. */ - previousTransactionBlock?: Maybe; - /** The transaction blocks that sent objects to this object. */ - receivedTransactionBlocks: TransactionBlockConnection; - /** The `0x3::staking_pool::StakedIota` objects owned by this object. */ - stakedIotas: StakedIotaConnection; - /** - * The current status of the object as read from the off-chain store. The - * possible states are: NOT_INDEXED, the object is loaded from - * serialized data, such as the contents of a genesis or system package - * upgrade transaction. LIVE, the version returned is the most recent for - * the object, and it is not deleted or wrapped at that version. - * HISTORICAL, the object was referenced at a specific version or - * checkpoint, so is fetched from historical tables and may not be the - * latest version of the object. WRAPPED_OR_DELETED, the object is deleted - * or wrapped and only partial information can be loaded." - */ - status: ObjectKind; - /** - * The amount of IOTA we would rebate if this object gets deleted or - * mutated. This number is recalculated based on the present storage - * gas price. - */ - storageRebate?: Maybe; - version: Scalars['Int']['output']; -}; - - -export type IotansRegistrationBalanceArgs = { - type?: InputMaybe; -}; - - -export type IotansRegistrationBalancesArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - -export type IotansRegistrationCoinsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; - type?: InputMaybe; -}; - - -export type IotansRegistrationDefaultIotansNameArgs = { - format?: InputMaybe; -}; - - -export type IotansRegistrationDynamicFieldArgs = { - name: DynamicFieldName; -}; - - -export type IotansRegistrationDynamicFieldsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - -export type IotansRegistrationDynamicObjectFieldArgs = { - name: DynamicFieldName; -}; - - -export type IotansRegistrationIotansRegistrationsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - -export type IotansRegistrationObjectsArgs = { - after?: InputMaybe; - before?: InputMaybe; - filter?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - -export type IotansRegistrationReceivedTransactionBlocksArgs = { - after?: InputMaybe; - before?: InputMaybe; - filter?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - -export type IotansRegistrationStakedIotasArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - -export type IotansRegistrationConnection = { - __typename?: 'IotansRegistrationConnection'; - /** A list of edges. */ - edges: Array; - /** A list of nodes. */ - nodes: Array; - /** Information to aid in pagination. */ - pageInfo: PageInfo; -}; - -/** An edge in a connection. */ -export type IotansRegistrationEdge = { - __typename?: 'IotansRegistrationEdge'; - /** A cursor for use in pagination */ - cursor: Scalars['String']['output']; - /** The item at the end of the edge */ - node: IotansRegistration; -}; - /** * Information used by a package to link to a specific version of its * dependency. @@ -2294,8 +1964,6 @@ export type MoveObject = IMoveObject & IObject & IOwner & { asCoin?: Maybe; /** Attempts to convert the Move object into a `0x2::coin::CoinMetadata`. */ asCoinMetadata?: Maybe; - /** Attempts to convert the Move object into a `IotansRegistration` object. */ - asIotansRegistration?: Maybe; /** * Attempts to convert the Move object into a * `0x3::staking_pool::StakedIota`. @@ -2323,11 +1991,6 @@ export type MoveObject = IMoveObject & IObject & IOwner & { * signature, and the BCS of the corresponding data. */ contents?: Maybe; - /** - * The domain explicitly configured as the default domain pointing to this - * object. - */ - defaultIotansName?: Maybe; /** * 32-byte hash that identifies the object's contents, encoded as a Base58 * string. @@ -2373,11 +2036,6 @@ export type MoveObject = IMoveObject & IObject & IOwner & { * have the `key` and `store` abilities. */ hasPublicTransfer: Scalars['Boolean']['output']; - /** - * The IotansRegistration NFTs owned by this object. These grant the owner - * the capability to manage the associated domain. - */ - iotansRegistrations: IotansRegistrationConnection; /** Objects owned by this object, optionally `filter`-ed. */ objects: MoveObjectConnection; /** The owner type of this object: Immutable, Shared, Parent, Address */ @@ -2447,16 +2105,6 @@ export type MoveObjectCoinsArgs = { }; -/** - * The representation of an object as a Move Object, which exposes additional - * information (content, module that governs it, version, is transferrable, - * etc.) about this object. - */ -export type MoveObjectDefaultIotansNameArgs = { - format?: InputMaybe; -}; - - /** * The representation of an object as a Move Object, which exposes additional * information (content, module that governs it, version, is transferrable, @@ -2490,19 +2138,6 @@ export type MoveObjectDynamicObjectFieldArgs = { }; -/** - * The representation of an object as a Move Object, which exposes additional - * information (content, module that governs it, version, is transferrable, - * etc.) about this object. - */ -export type MoveObjectIotansRegistrationsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - /** * The representation of an object as a Move Object, which exposes additional * information (content, module that governs it, version, is transferrable, @@ -2597,24 +2232,11 @@ export type MovePackage = IObject & IOwner & { * are immutable and cannot be owned by an address. */ coins: CoinConnection; - /** - * The domain explicitly configured as the default domain pointing to this - * object. - */ - defaultIotansName?: Maybe; /** * 32-byte hash that identifies the package's contents, encoded as a Base58 * string. */ digest?: Maybe; - /** - * The IotansRegistration NFTs owned by this package. These grant the owner - * the capability to manage the associated domain. - * - * Note that objects owned by a package are inaccessible, because packages - * are immutable and cannot be owned by an address. - */ - iotansRegistrations: IotansRegistrationConnection; /** The transitive dependencies of this package. */ linkage?: Maybe>; /** @@ -2721,29 +2343,6 @@ export type MovePackageCoinsArgs = { }; -/** - * A MovePackage is a kind of Move object that represents code that has been - * published on chain. It exposes information about its modules, type - * definitions, functions, and dependencies. - */ -export type MovePackageDefaultIotansNameArgs = { - format?: InputMaybe; -}; - - -/** - * A MovePackage is a kind of Move object that represents code that has been - * published on chain. It exposes information about its modules, type - * definitions, functions, and dependencies. - */ -export type MovePackageIotansRegistrationsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - /** * A MovePackage is a kind of Move object that represents code that has been * published on chain. It exposes information about its modules, type @@ -2994,11 +2593,6 @@ export type Object = IObject & IOwner & { * `0x2::iota::IOTA`. */ coins: CoinConnection; - /** - * The domain explicitly configured as the default domain pointing to this - * object. - */ - defaultIotansName?: Maybe; /** * 32-byte hash that identifies the object's current contents, encoded as a * Base58 string. @@ -3037,11 +2631,6 @@ export type Object = IObject & IOwner & { * under the Owner type. */ dynamicObjectField?: Maybe; - /** - * The IotansRegistration NFTs owned by this object. These grant the owner - * the capability to manage the associated domain. - */ - iotansRegistrations: IotansRegistrationConnection; /** Objects owned by this object, optionally `filter`-ed. */ objects: MoveObjectConnection; /** @@ -3117,17 +2706,6 @@ export type ObjectCoinsArgs = { }; -/** - * An object in Iota is a package (set of Move bytecode modules) or object - * (typed data structure with fields) with additional metadata detailing its - * id, version, transaction digest, owner field indicating how this object can - * be accessed. - */ -export type ObjectDefaultIotansNameArgs = { - format?: InputMaybe; -}; - - /** * An object in Iota is a package (set of Move bytecode modules) or object * (typed data structure with fields) with additional metadata detailing its @@ -3164,20 +2742,6 @@ export type ObjectDynamicObjectFieldArgs = { }; -/** - * An object in Iota is a package (set of Move bytecode modules) or object - * (typed data structure with fields) with additional metadata detailing its - * id, version, transaction digest, owner field indicating how this object can - * be accessed. - */ -export type ObjectIotansRegistrationsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - /** * An object in Iota is a package (set of Move bytecode modules) or object * (typed data structure with fields) with additional metadata detailing its @@ -3398,11 +2962,6 @@ export type Owner = IOwner & { * `0x2::iota::IOTA`. */ coins: CoinConnection; - /** - * The domain explicitly configured as the default domain pointing to this - * object or address. - */ - defaultIotansName?: Maybe; /** * Access a dynamic field on an object using its name. Names are arbitrary * Move values whose type have `copy`, `drop`, and `store`, and are @@ -3430,11 +2989,6 @@ export type Owner = IOwner & { * wrapped object. */ dynamicObjectField?: Maybe; - /** - * The IotansRegistration NFTs owned by this object or address. These grant - * the owner the capability to manage the associated domain. - */ - iotansRegistrations: IotansRegistrationConnection; /** Objects owned by this object or address, optionally `filter`-ed. */ objects: MoveObjectConnection; /** @@ -3485,17 +3039,6 @@ export type OwnerCoinsArgs = { }; -/** - * An Owner is an entity that can own an object. Each Owner is identified by a - * IotaAddress which represents either an Address (corresponding to a public - * key of an account) or an Object, but never both (it is not known up-front - * whether a given Owner is an Address or an Object). - */ -export type OwnerDefaultIotansNameArgs = { - format?: InputMaybe; -}; - - /** * An Owner is an entity that can own an object. Each Owner is identified by a * IotaAddress which represents either an Address (corresponding to a public @@ -3532,20 +3075,6 @@ export type OwnerDynamicObjectFieldArgs = { }; -/** - * An Owner is an entity that can own an object. Each Owner is identified by a - * IotaAddress which represents either an Address (corresponding to a public - * key of an account) or an Object, but never both (it is not known up-front - * whether a given Owner is an Address or an Object). - */ -export type OwnerIotansRegistrationsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - /** * An Owner is an entity that can own an object. Each Owner is identified by a * IotaAddress which represents either an Address (corresponding to a public @@ -3815,8 +3344,6 @@ export type Query = { * protocol version known to the GraphQL service). */ protocolConfig: ProtocolConfigs; - /** Resolves a IotaNS `domain` name to an address, if it has been bound. */ - resolveIotansAddress?: Maybe
; /** Configuration for this RPC service */ serviceConfig: ServiceConfig; /** Fetch a transaction block by its transaction digest. */ @@ -3926,11 +3453,6 @@ export type QueryProtocolConfigArgs = { }; -export type QueryResolveIotansAddressArgs = { - domain: Scalars['String']['input']; -}; - - export type QueryTransactionBlockArgs = { digest: Scalars['String']['input']; }; @@ -4209,11 +3731,6 @@ export type StakedIota = IMoveObject & IObject & IOwner & { * signature, and the BCS of the corresponding data. */ contents?: Maybe; - /** - * The domain explicitly configured as the default domain pointing to this - * object. - */ - defaultIotansName?: Maybe; /** * 32-byte hash that identifies the object's contents, encoded as a Base58 * string. @@ -4273,11 +3790,6 @@ export type StakedIota = IMoveObject & IObject & IOwner & { * have the `key` and `store` abilities. */ hasPublicTransfer: Scalars['Boolean']['output']; - /** - * The IotansRegistration NFTs owned by this object. These grant the owner - * the capability to manage the associated domain. - */ - iotansRegistrations: IotansRegistrationConnection; /** Objects owned by this object, optionally `filter`-ed. */ objects: MoveObjectConnection; /** The owner type of this object: Immutable, Shared, Parent, Address */ @@ -4343,12 +3855,6 @@ export type StakedIotaCoinsArgs = { }; -/** Represents a `0x3::staking_pool::StakedIota` Move object on-chain. */ -export type StakedIotaDefaultIotansNameArgs = { - format?: InputMaybe; -}; - - /** Represents a `0x3::staking_pool::StakedIota` Move object on-chain. */ export type StakedIotaDynamicFieldArgs = { name: DynamicFieldName; @@ -4370,15 +3876,6 @@ export type StakedIotaDynamicObjectFieldArgs = { }; -/** Represents a `0x3::staking_pool::StakedIota` Move object on-chain. */ -export type StakedIotaIotansRegistrationsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - /** Represents a `0x3::staking_pool::StakedIota` Move object on-chain. */ export type StakedIotaObjectsArgs = { after?: InputMaybe; @@ -5231,22 +4728,6 @@ export type GetValidatorsApyQueryVariables = Exact<{ [key: string]: never; }>; export type GetValidatorsApyQuery = { __typename?: 'Query', epoch?: { __typename?: 'Epoch', epochId: number, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', apy?: number | null, address: { __typename?: 'Address', address: any } }> } } | null } | null }; -export type ResolveNameServiceAddressQueryVariables = Exact<{ - domain: Scalars['String']['input']; -}>; - - -export type ResolveNameServiceAddressQuery = { __typename?: 'Query', resolveIotansAddress?: { __typename?: 'Address', address: any } | null }; - -export type ResolveNameServiceNamesQueryVariables = Exact<{ - address: Scalars['IotaAddress']['input']; - limit?: InputMaybe; - cursor?: InputMaybe; -}>; - - -export type ResolveNameServiceNamesQuery = { __typename?: 'Query', address?: { __typename?: 'Address', iotansRegistrations: { __typename?: 'IotansRegistrationConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'IotansRegistration', domain: string }> } } | null }; - export type GetOwnedObjectsQueryVariables = Exact<{ owner: Scalars['IotaAddress']['input']; limit?: InputMaybe; @@ -7685,28 +7166,6 @@ export const GetValidatorsApyDocument = new TypedDocumentString(` } } `) as unknown as TypedDocumentString; -export const ResolveNameServiceAddressDocument = new TypedDocumentString(` - query resolveNameServiceAddress($domain: String!) { - resolveIotansAddress(domain: $domain) { - address - } -} - `) as unknown as TypedDocumentString; -export const ResolveNameServiceNamesDocument = new TypedDocumentString(` - query resolveNameServiceNames($address: IotaAddress!, $limit: Int, $cursor: String) { - address(address: $address) { - iotansRegistrations(first: $limit, after: $cursor) { - pageInfo { - hasNextPage - endCursor - } - nodes { - domain - } - } - } -} - `) as unknown as TypedDocumentString; export const GetOwnedObjectsDocument = new TypedDocumentString(` query getOwnedObjects($owner: IotaAddress!, $limit: Int, $cursor: String, $showBcs: Boolean = false, $showContent: Boolean = false, $showDisplay: Boolean = false, $showType: Boolean = false, $showOwner: Boolean = false, $showPreviousTransaction: Boolean = false, $showStorageRebate: Boolean = false, $filter: ObjectFilter) { address(address: $owner) { @@ -8803,4 +8262,4 @@ fragment PAGINATE_TRANSACTION_LISTS on TransactionBlock { } } } -}`) as unknown as TypedDocumentString; +}`) as unknown as TypedDocumentString; \ No newline at end of file diff --git a/sdk/graphql-transport/src/methods.ts b/sdk/graphql-transport/src/methods.ts index 541fd744c00..72b521f5d23 100644 --- a/sdk/graphql-transport/src/methods.ts +++ b/sdk/graphql-transport/src/methods.ts @@ -60,8 +60,6 @@ import { PaginateTransactionBlockListsDocument, QueryEventsDocument, QueryTransactionBlocksDocument, - ResolveNameServiceAddressDocument, - ResolveNameServiceNamesDocument, TransactionBlockKindInput, TryGetPastObjectDocument, } from './generated/queries.js'; @@ -1272,7 +1270,7 @@ export const RPC_METHODS: { epochTotalTransactions: '0', // TODO firstCheckpointId: epoch.firstCheckpoint?.nodes[0]?.sequenceNumber.toString()!, endOfEpochInfo: null, - referenceGasPrice: Number.parseInt(epoch.referenceGasPrice, 10), + referenceGasPrice: epoch.referenceGasPrice, epochStartTimestamp: new Date(epoch.startTimestamp).getTime().toString(), }; }, @@ -1391,35 +1389,6 @@ export const RPC_METHODS: { featureFlags, }; }, - async resolveNameServiceAddress(transport, [name]): Promise { - const data = await transport.graphqlQuery({ - query: ResolveNameServiceAddressDocument, - variables: { - domain: name, - }, - }); - - return data.resolveIotansAddress?.address ?? null; - }, - async resolveNameServiceNames(transport, [address, cursor, limit]) { - const iotansRegistrations = await transport.graphqlQuery( - { - query: ResolveNameServiceNamesDocument, - variables: { - address: address, - cursor, - limit, - }, - }, - (data) => data.address?.iotansRegistrations, - ); - - return { - hasNextPage: iotansRegistrations.pageInfo.hasNextPage, - nextCursor: iotansRegistrations.pageInfo.endCursor ?? null, - data: iotansRegistrations?.nodes.map((node) => node.domain) ?? [], - }; - }, }; export class UnsupportedParamError extends Error { diff --git a/sdk/graphql-transport/test/compatability.test.ts b/sdk/graphql-transport/test/compatability.test.ts index 0f83c49b3e5..a12ba95142f 100644 --- a/sdk/graphql-transport/test/compatability.test.ts +++ b/sdk/graphql-transport/test/compatability.test.ts @@ -687,6 +687,13 @@ describe('GraphQL IotaClient compatibility', () => { expect(graphql).toEqual(rpc); }); + test.skip('getCheckpointAddressMetrics', async () => { + const rpc = await toolbox.client.getCheckpointAddressMetrics({ checkpoint: '3' }); + const graphql = await graphQLClient!.getCheckpointAddressMetrics({ checkpoint: '3' }); + + expect(graphql).toEqual(rpc); + }); + test.skip('getEpochs', async () => { const rpc = await toolbox.client.getEpochs(); const graphql = await graphQLClient!.getEpochs(); @@ -701,6 +708,13 @@ describe('GraphQL IotaClient compatibility', () => { expect(graphql).toEqual(rpc); }); + test.skip('getTotalTransactions', async () => { + const rpc = await toolbox.client.getTotalTransactions(); + const graphql = await graphQLClient!.getTotalTransactions(); + + expect(graphql).toEqual(rpc); + }); + test('getValidatorsApy', async () => { const rpc = await toolbox.client.getValidatorsApy(); const graphql = await graphQLClient!.getValidatorsApy(); diff --git a/sdk/typescript/src/client/client.ts b/sdk/typescript/src/client/client.ts index 0229e4f81fb..f2800aa5389 100644 --- a/sdk/typescript/src/client/client.ts +++ b/sdk/typescript/src/client/client.ts @@ -70,8 +70,6 @@ import type { QueryEventsParams, QueryTransactionBlocksParams, ResolvedNameServiceNames, - ResolveNameServiceAddressParams, - ResolveNameServiceNamesParams, SubscribeEventParams, SubscribeTransactionParams, IotaEvent, @@ -734,6 +732,13 @@ export class IotaClient { }); } + async getCheckpointAddressMetrics(input?: { checkpoint: string }): Promise { + return await this.transport.request({ + method: 'iotax_getCheckpointAddressMetrics', + params: [input?.checkpoint], + }); + } + /** * Return the committee information for the asked epoch */ @@ -762,6 +767,14 @@ export class IotaClient { return await this.transport.request({ method: 'iotax_getCurrentEpoch', params: [] }); } + async getTotalTransactions(): Promise { + const resp = await this.transport.request({ + method: 'iotax_getTotalTransactions', + params: [], + }); + return String(resp); + } + /** * Return the Validators APYs */ @@ -776,22 +789,16 @@ export class IotaClient { return toHEX(bytes.slice(0, 4)); } - async resolveNameServiceAddress( - input: ResolveNameServiceAddressParams, - ): Promise { - return await this.transport.request({ - method: 'iotax_resolveNameServiceAddress', - params: [input.name], - }); + async resolveNameServiceAddress(_input: any): Promise { + return 'remove_me'; } - async resolveNameServiceNames( - input: ResolveNameServiceNamesParams, - ): Promise { - return await this.transport.request({ - method: 'iotax_resolveNameServiceNames', - params: [input.address, input.cursor, input.limit], - }); + async resolveNameServiceNames(_input: any): Promise { + return { + data: [], + hasNextPage: false, + nextCursor: null, + }; } async getProtocolConfig(input?: GetProtocolConfigParams): Promise { diff --git a/sdk/typescript/src/client/types/chain.ts b/sdk/typescript/src/client/types/chain.ts index 595ba4519fb..8124c20121c 100644 --- a/sdk/typescript/src/client/types/chain.ts +++ b/sdk/typescript/src/client/types/chain.ts @@ -3,13 +3,15 @@ // SPDX-License-Identifier: Apache-2.0 import type { + AddressMetrics, Checkpoint, DynamicFieldInfo, + EpochInfo, + EpochMetrics, IotaCallArg, IotaMoveNormalizedModule, IotaParsedData, IotaTransaction, - IotaValidatorSummary, } from './generated.js'; export type ResolvedNameServiceNames = { @@ -18,24 +20,6 @@ export type ResolvedNameServiceNames = { nextCursor: string | null; }; -export type EpochInfo = { - epoch: string; - validators: IotaValidatorSummary[]; - epochTotalTransactions: string; - firstCheckpointId: string; - epochStartTimestamp: string; - endOfEpochInfo: EndOfEpochInfo | null; - referenceGasPrice: number | null; -}; - -export type EpochMetrics = { - epoch: string; - epochTotalTransactions: string; - firstCheckpointId: string; - epochStartTimestamp: string; - endOfEpochInfo: EndOfEpochInfo | null; -}; - export type EpochPage = { data: EpochInfo[]; nextCursor: string | null; @@ -48,54 +32,14 @@ export type EpochMetricsPage = { hasNextPage: boolean; }; -export type EndOfEpochInfo = { - lastCheckpointId: string; - epochEndTimestamp: string; - protocolVersion: string; - referenceGasPrice: string; - totalStake: string; - storageCharge: string; - storageRebate: string; - storageFundBalance: string; - totalGasFees: string; - totalStakeRewardsDistributed: string; - burntTokensAmount: string; - mintedTokensAmount: string; -}; - export type CheckpointPage = { data: Checkpoint[]; nextCursor: string | null; hasNextPage: boolean; }; -export type NetworkMetrics = { - currentTps: number; - tps30Days: number; - currentCheckpoint: string; - currentEpoch: string; - totalAddresses: string; - totalObjects: string; - totalPackages: string; -}; - -export type AddressMetrics = { - checkpoint: number; - epoch: number; - timestampMs: number; - cumulativeAddresses: number; - cumulativeActiveAddresses: number; - dailyActiveAddresses: number; -}; - export type AllEpochsAddressMetrics = AddressMetrics[]; -export type MoveCallMetrics = { - rank3Days: MoveCallMetric[]; - rank7Days: MoveCallMetric[]; - rank30Days: MoveCallMetric[]; -}; - export type MoveCallMetric = [ { module: string; diff --git a/sdk/typescript/src/client/types/generated.ts b/sdk/typescript/src/client/types/generated.ts index 448f4f6e65c..7142e999490 100644 --- a/sdk/typescript/src/client/types/generated.ts +++ b/sdk/typescript/src/client/types/generated.ts @@ -11,6 +11,21 @@ * /crates/iota-open-rpc/spec/openrpc.json */ +/** Provides metrics about the addresses. */ +export interface AddressMetrics { + /** The checkpoint sequence number at which the metrics were computed. */ + checkpoint: string; + /** The count of sender addresses. */ + cumulativeActiveAddresses: string; + /** The count of sender and recipient addresses. */ + cumulativeAddresses: string; + /** The count of daily unique sender addresses. */ + dailyActiveAddresses: string; + /** The epoch to which the checkpoint is assigned. */ + epoch: string; + /** The checkpoint timestamp. */ + timestampMs: string; +} export interface Balance { coinObjectCount: number; coinType: string; @@ -204,6 +219,50 @@ export interface EndOfEpochData { */ nextEpochProtocolVersion: string; } +export interface EndOfEpochInfo { + burntTokensAmount: string; + epochEndTimestamp: string; + lastCheckpointId: string; + mintedTokensAmount: string; + /** existing fields from `SystemEpochInfoEvent` (without epoch) */ + protocolVersion: string; + referenceGasPrice: string; + storageCharge: string; + storageFundBalance: string; + storageRebate: string; + totalGasFees: string; + totalStake: string; + totalStakeRewardsDistributed: string; +} +export interface EpochInfo { + /** The end of epoch information. */ + endOfEpochInfo?: EndOfEpochInfo | null; + /** Epoch number */ + epoch: string; + /** The timestamp when the epoch started. */ + epochStartTimestamp: string; + /** Count of tx in epoch */ + epochTotalTransactions: string; + /** First, last checkpoint sequence numbers */ + firstCheckpointId: string; + /** The reference gas price for the given epoch. */ + referenceGasPrice?: string | null; + /** List of validators included in epoch */ + validators: IotaValidatorSummary[]; +} +/** A light-weight version of `EpochInfo` for faster loading */ +export interface EpochMetrics { + /** The end of epoch information. */ + endOfEpochInfo?: EndOfEpochInfo | null; + /** The current epoch ID. */ + epoch: string; + /** The timestamp when the epoch started. */ + epochStartTimestamp: string; + /** The total number of transactions in the epoch. */ + epochTotalTransactions: string; + /** The first checkpoint ID of the epoch. */ + firstCheckpointId: string; +} export interface IotaEvent { /** Base 58 encoded bcs bytes of the move event */ bcs: string; @@ -789,6 +848,14 @@ export interface LoadedChildObject { export interface LoadedChildObjectsResponse { loadedChildObjects: LoadedChildObject[]; } +export interface MoveCallMetrics { + /** The count of calls of each function in the last 30 days. */ + rank30Days: [MoveFunctionName, string][]; + /** The count of calls of each function in the last 3 days. */ + rank3Days: [MoveFunctionName, string][]; + /** The count of calls of each function in the last 7 days. */ + rank7Days: [MoveFunctionName, string][]; +} export interface MoveCallParams { arguments: unknown[]; function: string; @@ -801,6 +868,15 @@ export type IotaMoveFunctionArgType = | { Object: ObjectValueKind; }; +/** Identifies a Move function. */ +export interface MoveFunctionName { + /** The function name. */ + function: string; + /** The module name to which the function belongs. */ + module: string; + /** The package ID to which the function belongs. */ + package: string; +} export type MoveStruct = | MoveValue[] | { @@ -873,6 +949,22 @@ export interface MultiSigPublicKeyLegacy { */ threshold: number; } +export interface NetworkMetrics { + /** Current checkpoint number */ + currentCheckpoint: string; + /** Current epoch number */ + currentEpoch: string; + /** Current TPS - Transaction Blocks per Second. */ + currentTps: number; + /** Total number of addresses seen in the network */ + totalAddresses: string; + /** Total number of live objects in the network */ + totalObjects: string; + /** Total number of packages published in the network */ + totalPackages: string; + /** Peak TPS in the past 30 days */ + tps30Days: number; +} /** * ObjectChange are derived from the object mutations in the TransactionEffect to provide richer object * information. @@ -1106,18 +1198,18 @@ export interface PaginatedDynamicFieldInfos { * next item after `next_cursor` if `next_cursor` is `Some`, otherwise it will start from the first * item. */ -export interface PaginatedEvents { - data: IotaEvent[]; +export interface PaginatedEpochInfos { + data: EpochInfo[]; hasNextPage: boolean; - nextCursor?: EventId | null; + nextCursor?: string | null; } /** * `next_cursor` points to the last item in the page; Reading with `next_cursor` will start from the * next item after `next_cursor` if `next_cursor` is `Some`, otherwise it will start from the first * item. */ -export interface PaginatedObjectsResponse { - data: IotaObjectResponse[]; +export interface PaginatedEpochMetricss { + data: EpochMetrics[]; hasNextPage: boolean; nextCursor?: string | null; } @@ -1126,8 +1218,18 @@ export interface PaginatedObjectsResponse { * next item after `next_cursor` if `next_cursor` is `Some`, otherwise it will start from the first * item. */ -export interface PaginatedStrings { - data: string[]; +export interface PaginatedEvents { + data: IotaEvent[]; + hasNextPage: boolean; + nextCursor?: EventId | null; +} +/** + * `next_cursor` points to the last item in the page; Reading with `next_cursor` will start from the + * next item after `next_cursor` if `next_cursor` is `Some`, otherwise it will start from the first + * item. + */ +export interface PaginatedObjectsResponse { + data: IotaObjectResponse[]; hasNextPage: boolean; nextCursor?: string | null; } diff --git a/sdk/typescript/src/client/types/params.ts b/sdk/typescript/src/client/types/params.ts index a2a674dd196..47e698171ab 100644 --- a/sdk/typescript/src/client/types/params.ts +++ b/sdk/typescript/src/client/types/params.ts @@ -197,6 +197,9 @@ export interface GetAllCoinsParams { /** maximum number of items per page */ limit?: number | null | undefined; } +export interface GetAllEpochAddressMetricsParams { + descendingOrder?: boolean | null | undefined; +} /** Return the total coin balance for one coin type, owned by the address owner. */ export interface GetBalanceParams { /** the owner's Iota address */ @@ -207,7 +210,10 @@ export interface GetBalanceParams { */ coinType?: string | null | undefined; } -/** Return metadata(e.g., symbol, decimals) for a coin */ +export interface GetCheckpointAddressMetricsParams { + checkpoint: string; +} +/** Return metadata (e.g., symbol, decimals) for a coin. */ export interface GetCoinMetadataParams { /** type name for the coin (e.g., 0x168da5bf1f48dafc111b0a488fa454aca95e0b5e::usdc::USDC) */ coinType: string; @@ -231,6 +237,8 @@ export interface GetCommitteeInfoParams { /** The epoch of interest. If None, default to the latest epoch */ epoch?: string | null | undefined; } +/** Return current epoch info */ +export interface GetCurrentEpochParams {} /** Return the dynamic field object information for a specified object */ export interface GetDynamicFieldObjectParams { /** The ID of the queried parent object */ @@ -250,8 +258,32 @@ export interface GetDynamicFieldsParams { /** Maximum item returned per page, default to [QUERY_MAX_RESULT_LIMIT] if not specified. */ limit?: number | null | undefined; } +/** Return a list of epoch metrics, which is a subset of epoch info */ +export interface GetEpochMetricsParams { + /** Optional paging cursor */ + cursor?: string | null | undefined; + /** Maximum number of items per page */ + limit?: number | null | undefined; + /** Flag to return results in descending order */ + descendingOrder?: boolean | null | undefined; +} +/** Return a list of epoch info */ +export interface GetEpochsParams { + /** Optional paging cursor */ + cursor?: string | null | undefined; + /** Maximum number of items per page */ + limit?: number | null | undefined; + /** Flag to return results in descending order */ + descendingOrder?: boolean | null | undefined; +} +/** Address related metrics */ +export interface GetLatestAddressMetricsParams {} /** Return the latest IOTA system state object on-chain. */ export interface GetLatestIotaSystemStateParams {} +/** Return move call metrics */ +export interface GetMoveCallMetricsParams {} +/** Return Network metrics */ +export interface GetNetworkMetricsParams {} /** * Return the list of objects owned by an address. Note that if the address owns more than * `QUERY_MAX_RESULT_LIMIT` objects, the pagination is not accurate, because previous page may have @@ -286,11 +318,12 @@ export interface GetTimelockedStakesParams { export interface GetTimelockedStakesByIdsParams { timelockedStakedIotaIds: string[]; } -/** Return total supply for a coin */ +/** Return total supply for a coin. */ export interface GetTotalSupplyParams { /** type name for the coin (e.g., 0x168da5bf1f48dafc111b0a488fa454aca95e0b5e::usdc::USDC) */ coinType: string; } +export interface GetTotalTransactionsParams {} /** Return the validator APY */ export interface GetValidatorsApyParams {} /** Return list of events for a specified query criteria. */ @@ -319,21 +352,6 @@ export type QueryTransactionBlocksParams = { /** query result ordering, default to false (ascending order), oldest record first. */ order?: 'ascending' | 'descending' | null | undefined; } & RpcTypes.IotaTransactionBlockResponseQuery; -/** Return the resolved address given resolver and name */ -export interface ResolveNameServiceAddressParams { - /** The name to resolve */ - name: string; -} -/** - * Return the resolved names given address, if multiple names are resolved, the first one is the - * primary name. - */ -export interface ResolveNameServiceNamesParams { - /** The address to resolve */ - address: string; - cursor?: string | null | undefined; - limit?: number | null | undefined; -} /** Subscribe to a stream of Iota event */ export interface SubscribeEventParams { /**