diff --git a/crates/sui-graphql-client-build/Cargo.toml b/crates/sui-graphql-client-build/Cargo.toml index 583763ea3..12166f0f5 100644 --- a/crates/sui-graphql-client-build/Cargo.toml +++ b/crates/sui-graphql-client-build/Cargo.toml @@ -4,5 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -cynic-codegen = "3.8.0" +cynic-codegen = "3.7.3" diff --git a/crates/sui-graphql-client/src/error.rs b/crates/sui-graphql-client/src/error.rs index 2d04a84c2..c1d6f2a5b 100644 --- a/crates/sui-graphql-client/src/error.rs +++ b/crates/sui-graphql-client/src/error.rs @@ -63,7 +63,7 @@ impl Error { // Private constructors /// Convert the given error into a generic error. - pub(crate) fn from_error>(kind: Kind, error: E) -> Self { + pub fn from_error>(kind: Kind, error: E) -> Self { Self { inner: Box::new(InnerError { kind, @@ -74,7 +74,7 @@ impl Error { } /// Special constructor for queries that expect to return data but it's none. - pub(crate) fn empty_response_error() -> Self { + pub fn empty_response_error() -> Self { Self { inner: Box::new(InnerError { kind: Kind::Query, @@ -85,7 +85,7 @@ impl Error { } /// Create a Query kind of error with the original graphql errors. - pub(crate) fn graphql_error(errors: Vec) -> Self { + pub fn graphql_error(errors: Vec) -> Self { Self { inner: Box::new(InnerError { kind: Kind::Query, diff --git a/crates/sui-graphql-client/src/lib.rs b/crates/sui-graphql-client/src/lib.rs index d1c7c0f81..72339fe43 100644 --- a/crates/sui-graphql-client/src/lib.rs +++ b/crates/sui-graphql-client/src/lib.rs @@ -31,7 +31,9 @@ use query_types::DynamicFieldConnectionArgs; use query_types::DynamicFieldQuery; use query_types::DynamicFieldsOwnerQuery; use query_types::DynamicObjectFieldQuery; -use query_types::EpochSummaryArgs; +use query_types::Epoch; +use query_types::EpochArgs; +use query_types::EpochQuery; use query_types::EpochSummaryQuery; use query_types::EventFilter; use query_types::EventsQuery; @@ -110,6 +112,8 @@ use std::str::FromStr; use crate::error::Kind; use crate::error::Result; use crate::query_types::CheckpointTotalTxQuery; +use query_types::EpochsArgs; +use query_types::EpochsQuery; const DEFAULT_ITEMS_PER_PAGE: i32 = 10; const MAINNET_HOST: &str = "https://sui-mainnet.mystenlabs.com/graphql"; @@ -179,8 +183,8 @@ impl Page { &self.data } - /// Internal function to create a new page with the provided data and page information. - fn new(page_info: PageInfo, data: Vec) -> Self { + /// Create a new page with the provided data and page information. + pub fn new(page_info: PageInfo, data: Vec) -> Self { Self { page_info, data } } @@ -189,8 +193,8 @@ impl Page { self.data.is_empty() } - /// Internal function to create a page with no data. - fn new_empty() -> Self { + /// Create a page with no data. + pub fn new_empty() -> Self { Self::new(PageInfo::default(), vec![]) } @@ -328,7 +332,7 @@ impl Client { /// Internal function to handle pagination filters and return the appropriate values. /// If limit is omitted, it will use the max page size from the service config. - async fn pagination_filter( + pub async fn pagination_filter( &self, pagination_filter: PaginationFilter, ) -> (Option, Option, Option, Option) { @@ -392,7 +396,7 @@ impl Client { /// This will return `Ok(None)` if the epoch requested is not available in the GraphQL service /// (e.g., due to pruning). pub async fn reference_gas_price(&self, epoch: Option) -> Result> { - let operation = EpochSummaryQuery::build(EpochSummaryArgs { id: epoch }); + let operation = EpochSummaryQuery::build(EpochArgs { id: epoch }); let response = self.run_query(&operation).await?; if let Some(errors) = response.errors { @@ -874,6 +878,52 @@ impl Client { // Epoch API // =========================================================================== + /// Return the epoch information for the provided epoch. If no epoch is provided, it will + /// return the last known epoch. + pub async fn epoch(&self, epoch: Option) -> Result> { + let operation = EpochQuery::build(EpochArgs { id: epoch }); + let response = self.run_query(&operation).await?; + + if let Some(errors) = response.errors { + return Err(Error::graphql_error(errors)); + } + + Ok(response.data.and_then(|d| d.epoch)) + } + + /// Return a page of epochs. + pub async fn epochs(&self, pagination_filter: PaginationFilter) -> Result> { + let (after, before, first, last) = self.pagination_filter(pagination_filter).await; + let operation = EpochsQuery::build(EpochsArgs { + after: after.as_deref(), + before: before.as_deref(), + first, + last, + }); + let response = self.run_query(&operation).await?; + + if let Some(errors) = response.errors { + return Err(Error::graphql_error(errors)); + } + + if let Some(epochs) = response.data { + Ok(Page::new(epochs.epochs.page_info, epochs.epochs.nodes)) + } else { + Ok(Page::new_empty()) + } + } + + /// Return a stream of epochs based on the (optional) object filter. + pub async fn epochs_stream( + &self, + streaming_direction: Direction, + ) -> impl Stream> + '_ { + stream_paginated_query( + move |pag_filter| self.epochs(pag_filter), + streaming_direction, + ) + } + /// Return the number of checkpoints in this epoch. This will return `Ok(None)` if the epoch /// requested is not available in the GraphQL service (e.g., due to pruning). pub async fn epoch_total_checkpoints(&self, epoch: Option) -> Result> { @@ -910,7 +960,7 @@ impl Client { &self, epoch: Option, ) -> Result> { - let operation = EpochSummaryQuery::build(EpochSummaryArgs { id: epoch }); + let operation = EpochSummaryQuery::build(EpochArgs { id: epoch }); self.run_query(&operation).await } @@ -1889,6 +1939,24 @@ mod tests { ); } + #[tokio::test] + async fn test_epoch_query() { + let client = test_client(); + let e = client.epoch(None).await; + assert!( + e.is_ok(), + "Epoch query failed for {} network. Error: {}", + client.rpc_server(), + e.unwrap_err() + ); + + assert!( + e.unwrap().is_some(), + "Epoch query returned None for {} network", + client.rpc_server() + ); + } + #[tokio::test] async fn test_epoch_total_checkpoints_query() { let client = test_client(); diff --git a/crates/sui-graphql-client/src/query_types/epoch.rs b/crates/sui-graphql-client/src/query_types/epoch.rs index 1ee12ba5e..634e91a9f 100644 --- a/crates/sui-graphql-client/src/query_types/epoch.rs +++ b/crates/sui-graphql-client/src/query_types/epoch.rs @@ -1,31 +1,60 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 // +use super::PageInfo; use crate::query_types::schema; use crate::query_types::Address; use crate::query_types::BigInt; use crate::query_types::DateTime; +use crate::query_types::ProtocolConfigs; // =========================================================================== // Epoch Queries // =========================================================================== +#[derive(cynic::QueryFragment, Debug)] +#[cynic(schema = "rpc", graphql_type = "Query", variables = "EpochArgs")] +pub struct EpochQuery { + #[arguments(id: $id)] + pub epoch: Option, +} + +#[derive(cynic::QueryFragment, Debug)] +#[cynic(schema = "rpc", graphql_type = "Query", variables = "EpochsArgs")] +pub struct EpochsQuery { + #[arguments(first: $first, after: $after, last: $last, before: $before)] + pub epochs: EpochConnection, +} #[derive(cynic::QueryFragment, Debug)] -#[cynic(schema = "rpc", graphql_type = "Query", variables = "EpochSummaryArgs")] +#[cynic(schema = "rpc", graphql_type = "Query", variables = "EpochArgs")] pub struct EpochSummaryQuery { #[arguments(id: $id)] pub epoch: Option, } +#[derive(cynic::QueryFragment, Debug)] +#[cynic(schema = "rpc", graphql_type = "EpochConnection")] +pub struct EpochConnection { + pub nodes: Vec, + pub page_info: PageInfo, +} // =========================================================================== // Epoch Summary Args // =========================================================================== #[derive(cynic::QueryVariables, Debug)] -pub struct EpochSummaryArgs { +pub struct EpochArgs { pub id: Option, } +#[derive(cynic::QueryVariables, Debug)] +pub struct EpochsArgs<'a> { + pub first: Option, + pub after: Option<&'a str>, + pub last: Option, + pub before: Option<&'a str>, +} + /// A summary of the epoch. #[derive(cynic::QueryFragment, Debug)] #[cynic(schema = "rpc", graphql_type = "Epoch")] @@ -44,38 +73,77 @@ pub struct EpochSummary { // Epoch Types // =========================================================================== -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Debug, Clone)] #[cynic(schema = "rpc", graphql_type = "Epoch")] pub struct Epoch { - pub end_timestamp: Option, + /// The epoch's id as a sequence number that starts at 0 and is incremented by one at every epoch change. pub epoch_id: u64, + /// The storage fees paid for transactions executed during the epoch. pub fund_inflow: Option, + /// The storage fee rebates paid to users who deleted the data associated with past + /// transactions. pub fund_outflow: Option, + /// The storage fund available in this epoch. + /// This fund is used to redistribute storage fees from past transactions + /// to future validators. pub fund_size: Option, + /// A commitment by the committee at the end of epoch on the contents of the live object set at + /// that time. This can be used to verify state snapshots. pub live_object_set_digest: Option, + /// The difference between the fund inflow and outflow, representing + /// the net amount of storage fees accumulated in this epoch. pub net_inflow: Option, + /// The epoch's corresponding protocol configuration, including the feature flags and the + /// configuration options. + pub protocol_configs: Option, + /// The minimum gas price that a quorum of validators are guaranteed to sign a transaction for. pub reference_gas_price: Option, + /// The epoch's starting timestamp. pub start_timestamp: DateTime, + /// The epoch's ending timestamp. Note that this is available only on epochs that have ended. + pub end_timestamp: Option, + /// The value of the `version` field of `0x5`, the `0x3::sui::SuiSystemState` object. This + /// version changes whenever the fields contained in the system state object (held in a dynamic + /// field attached to `0x5`) change. pub system_state_version: Option, + /// The total number of checkpoints in this epoch. pub total_checkpoints: Option, + /// The total amount of gas fees (in MIST) that were paid in this epoch. pub total_gas_fees: Option, + /// The total MIST rewarded as stake. pub total_stake_rewards: Option, + /// The amount added to total gas fees to make up the total stake rewards. pub total_stake_subsidies: Option, + /// The total number of transaction in this epoch. pub total_transactions: Option, + /// Validator related properties. For active validators, see [`active_validators`] API. pub validator_set: Option, } -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Debug, Clone)] #[cynic(schema = "rpc", graphql_type = "ValidatorSet")] pub struct ValidatorSet { + /// Object ID of the `Table` storing the inactive staking pools. pub inactive_pools_id: Option
, + /// Size of the inactive pools `Table`. pub inactive_pools_size: Option, + /// Object ID of the wrapped object `TableVec` storing the pending active validators. pub pending_active_validators_id: Option
, + /// Size of the pending active validators table. pub pending_active_validators_size: Option, + /// Validators that are pending removal from the active validator set, expressed as indices in + /// to `activeValidators`. pub pending_removals: Option>, + /// Object ID of the `Table` storing the mapping from staking pool ids to the addresses + /// of the corresponding validators. This is needed because a validator's address + /// can potentially change but the object ID of its pool will not. pub staking_pool_mappings_id: Option
, + /// Size of the stake pool mappings `Table`. pub staking_pool_mappings_size: Option, + /// Total amount of stake for all active validators at the beginning of the epoch. pub total_stake: Option, + /// Size of the validator candidates `Table`. pub validator_candidates_size: Option, + /// Object ID of the `Table` storing the validator candidates. pub validator_candidates_id: Option
, } diff --git a/crates/sui-graphql-client/src/query_types/mod.rs b/crates/sui-graphql-client/src/query_types/mod.rs index 080800e1e..42dee938d 100644 --- a/crates/sui-graphql-client/src/query_types/mod.rs +++ b/crates/sui-graphql-client/src/query_types/mod.rs @@ -50,8 +50,11 @@ pub use dynamic_fields::DynamicFieldQuery; pub use dynamic_fields::DynamicFieldsOwnerQuery; pub use dynamic_fields::DynamicObjectFieldQuery; pub use epoch::Epoch; -pub use epoch::EpochSummaryArgs; +pub use epoch::EpochArgs; +pub use epoch::EpochQuery; pub use epoch::EpochSummaryQuery; +pub use epoch::EpochsArgs; +pub use epoch::EpochsQuery; pub use events::Event; pub use events::EventConnection; pub use events::EventFilter; @@ -78,6 +81,7 @@ pub use object::ObjectsQuery; pub use object::ObjectsQueryArgs; pub use packages::LatestPackageQuery; pub use packages::MovePackage; +pub use packages::MovePackageConnection; pub use packages::MovePackageVersionFilter; pub use packages::PackageArgs; pub use packages::PackageByNameArgs; diff --git a/crates/sui-graphql-client/src/query_types/protocol_config.rs b/crates/sui-graphql-client/src/query_types/protocol_config.rs index 1441ddaa2..16ff295fc 100644 --- a/crates/sui-graphql-client/src/query_types/protocol_config.rs +++ b/crates/sui-graphql-client/src/query_types/protocol_config.rs @@ -34,7 +34,7 @@ pub struct ProtocolVersionArgs { /// Information about the configuration of the protocol. /// Constants that control how the chain operates. /// These can only change during protocol upgrades which happen on epoch boundaries. -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Clone, Debug)] #[cynic(schema = "rpc", graphql_type = "ProtocolConfigs")] pub struct ProtocolConfigs { /// The protocol is not required to change on every epoch boundary, so the protocol version @@ -51,7 +51,7 @@ pub struct ProtocolConfigs { /// Feature flags are a form of boolean configuration that are usually used to gate features while /// they are in development. Once a lag has been enabled, it is rare for it to be disabled. -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Clone, Debug)] #[cynic(schema = "rpc", graphql_type = "ProtocolConfigFeatureFlag")] pub struct ProtocolConfigFeatureFlag { pub key: String, @@ -59,7 +59,7 @@ pub struct ProtocolConfigFeatureFlag { } /// A key-value protocol configuration attribute. -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Clone, Debug)] #[cynic(schema = "rpc", graphql_type = "ProtocolConfigAttr")] pub struct ProtocolConfigAttr { pub key: String, diff --git a/crates/sui-graphql-client/src/query_types/transaction.rs b/crates/sui-graphql-client/src/query_types/transaction.rs index 29b888014..410f0c887 100644 --- a/crates/sui-graphql-client/src/query_types/transaction.rs +++ b/crates/sui-graphql-client/src/query_types/transaction.rs @@ -88,6 +88,7 @@ pub struct TransactionBlocksQueryArgs<'a> { #[cynic(schema = "rpc", graphql_type = "TransactionBlock")] pub struct TransactionBlock { pub bcs: Option, + pub effects: Option, pub signatures: Option>, }