Skip to content

Commit

Permalink
sui-graphql-client: add dry run query (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
stefan-mysten authored Oct 9, 2024
1 parent 112bfb3 commit 2699101
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 3 deletions.
105 changes: 104 additions & 1 deletion crates/sui-graphql-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ use query_types::CheckpointQuery;
use query_types::CoinMetadata;
use query_types::CoinMetadataArgs;
use query_types::CoinMetadataQuery;
use query_types::DryRunArgs;
use query_types::DryRunQuery;
use query_types::EpochSummaryArgs;
use query_types::EpochSummaryQuery;
use query_types::EventFilter;
Expand All @@ -40,9 +42,10 @@ use query_types::TransactionBlockArgs;
use query_types::TransactionBlockQuery;
use query_types::TransactionBlocksQuery;
use query_types::TransactionBlocksQueryArgs;
use query_types::TransactionMetadata;
use query_types::TransactionsFilter;
use query_types::Validator;
use reqwest::Url;

use sui_types::types::framework::Coin;
use sui_types::types::Address;
use sui_types::types::CheckpointSequenceNumber;
Expand All @@ -52,6 +55,7 @@ use sui_types::types::Object;
use sui_types::types::SignedTransaction;
use sui_types::types::Transaction;
use sui_types::types::TransactionEffects;
use sui_types::types::TransactionKind;
use sui_types::types::UserSignature;

use anyhow::anyhow;
Expand All @@ -64,6 +68,7 @@ use cynic::MutationBuilder;
use cynic::Operation;
use cynic::QueryBuilder;
use futures::Stream;
use reqwest::Url;
use std::pin::Pin;

const MAINNET_HOST: &str = "https://sui-mainnet.mystenlabs.com/graphql";
Expand All @@ -72,6 +77,12 @@ const DEVNET_HOST: &str = "https://sui-devnet.mystenlabs.com/graphql";
const LOCAL_HOST: &str = "http://localhost:9125/graphql";
static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);

#[derive(Debug)]
pub struct DryRunResult {
pub effects: Option<TransactionEffects>,
pub error: Option<String>,
}

#[derive(Debug)]
/// A page of items returned by the GraphQL server.
pub struct Page<T> {
Expand Down Expand Up @@ -671,6 +682,87 @@ impl Client {
}
}

// ===========================================================================
// Dry Run API
// ===========================================================================

/// Dry run a [`Transaction`] and return the transaction effects and dry run error (if any).
///
/// `skipChecks` optional flag disables the usual verification checks that prevent access to
/// objects that are owned by addresses other than the sender, and calling non-public,
/// non-entry functions, and some other checks. Defaults to false.
pub async fn dry_run_tx(
&self,
tx: &Transaction,
skip_checks: Option<bool>,
) -> Result<DryRunResult, Error> {
let tx_bytes = base64ct::Base64::encode_string(
&bcs::to_bytes(&tx).map_err(|_| Error::msg("Cannot encode Transaction as BCS"))?,
);
self.dry_run(tx_bytes, skip_checks, None).await
}

/// Dry run a [`TransactionKind`] and return the transaction effects and dry run error (if any).
///
/// `skipChecks` optional flag disables the usual verification checks that prevent access to
/// objects that are owned by addresses other than the sender, and calling non-public,
/// non-entry functions, and some other checks. Defaults to false.
///
/// `tx_meta` is the transaction metadata.
pub async fn dry_run_tx_kind(
&self,
tx_kind: &TransactionKind,
skip_checks: Option<bool>,
tx_meta: TransactionMetadata,
) -> Result<DryRunResult, Error> {
let tx_bytes = base64ct::Base64::encode_string(
&bcs::to_bytes(&tx_kind).map_err(|_| Error::msg("Cannot encode Transaction as BCS"))?,
);
self.dry_run(tx_bytes, skip_checks, Some(tx_meta)).await
}

/// Internal implementation of the dry run API.
async fn dry_run(
&self,
tx_bytes: String,
skip_checks: Option<bool>,
tx_meta: Option<TransactionMetadata>,
) -> Result<DryRunResult, Error> {
let skip_checks = skip_checks.unwrap_or(false);
let operation = DryRunQuery::build(DryRunArgs {
tx_bytes,
skip_checks,
tx_meta,
});
let response = self.run_query(&operation).await?;

// Query errors
if let Some(errors) = response.errors {
return Err(Error::msg(format!("{:?}", errors)));
}

// Dry Run errors
let error = response
.data
.as_ref()
.and_then(|tx| tx.dry_run_transaction_block.error.clone());

let effects = response
.data
.map(|tx| tx.dry_run_transaction_block)
.and_then(|tx| tx.transaction)
.and_then(|tx| tx.effects)
.and_then(|bcs| bcs.bcs)
.map(|bcs| base64ct::Base64::decode_vec(bcs.0.as_str()))
.transpose()
.map_err(|_| Error::msg("Cannot decode bcs bytes from Base64 for transaction effects"))?
.map(|bcs| bcs::from_bytes::<TransactionEffects>(&bcs))
.transpose()
.map_err(|_| Error::msg("Cannot decode bcs bytes into TransactionEffects"))?;

Ok(DryRunResult { effects, error })
}

// ===========================================================================
// Transaction API
// ===========================================================================
Expand Down Expand Up @@ -1061,4 +1153,15 @@ mod tests {
);
}
}

#[tokio::test]
async fn test_dry_run() {
let client = Client::new_testnet();
// this tx bytes works on testnet
let tx_bytes = "AAACAAiA8PoCAAAAAAAg7q6yDns6nPznaKLd9pUD2K6NFiiibC10pDVQHJKdP2kCAgABAQAAAQECAAABAQBGLuHCJ/xjZfhC4vTJt/Zrvq1gexKLaKf3aVzyIkxRaAFUHzz8ftiZdY25qP4f9zySuT1K/qyTWjbGiTu0i0Z1ZFA4gwUAAAAAILeG86EeQm3qY3ajat3iUnY2Gbrk/NbdwV/d9MZviAwwRi7hwif8Y2X4QuL0ybf2a76tYHsSi2in92lc8iJMUWjoAwAAAAAAAECrPAAAAAAAAA==";

let dry_run = client.dry_run(tx_bytes.to_string(), None, None).await;

assert!(dry_run.is_ok());
}
}
58 changes: 58 additions & 0 deletions crates/sui-graphql-client/src/query_types/dry_run.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use sui_types::types::ObjectReference;

use crate::query_types::schema;
use crate::query_types::Address;
use crate::query_types::TransactionBlock;

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema = "rpc", graphql_type = "Query", variables = "DryRunArgs")]
pub struct DryRunQuery {
#[arguments(txBytes: $tx_bytes, skipChecks: $skip_checks, txMeta: $tx_meta)]
pub dry_run_transaction_block: DryRunResult,
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema = "rpc", graphql_type = "DryRunResult")]
pub struct DryRunResult {
pub error: Option<String>,
pub transaction: Option<TransactionBlock>,
}

#[derive(cynic::QueryVariables, Debug)]
pub struct DryRunArgs {
pub tx_bytes: String,
pub skip_checks: bool,
pub tx_meta: Option<TransactionMetadata>,
}

#[derive(cynic::InputObject, Debug)]
#[cynic(schema = "rpc", graphql_type = "TransactionMetadata")]
pub struct TransactionMetadata {
pub gas_budget: Option<u64>,
pub gas_objects: Option<Vec<ObjectRef>>,
pub gas_price: Option<u64>,
pub gas_sponsor: Option<Address>,
pub sender: Option<Address>,
}

#[derive(cynic::InputObject, Debug)]
#[cynic(schema = "rpc", graphql_type = "ObjectRef")]
pub struct ObjectRef {
pub address: Address,
pub digest: String,
pub version: u64,
}

impl From<ObjectReference> for ObjectRef {
fn from(value: ObjectReference) -> Self {
let address: Address = (*value.object_id()).into();
ObjectRef {
address,
version: value.version(),
digest: value.digest().to_string(),
}
}
}
11 changes: 9 additions & 2 deletions crates/sui-graphql-client/src/query_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod balance;
mod chain;
mod checkpoint;
mod coin;
mod dry_run;
mod epoch;
mod events;
mod execute_tx;
Expand All @@ -20,7 +21,6 @@ pub use active_validators::EpochValidator;
pub use active_validators::Validator;
pub use active_validators::ValidatorConnection;
pub use active_validators::ValidatorSet;
use anyhow::anyhow;
pub use balance::Balance;
pub use balance::BalanceArgs;
pub use balance::BalanceQuery;
Expand All @@ -32,6 +32,10 @@ pub use checkpoint::CheckpointQuery;
pub use coin::CoinMetadata;
pub use coin::CoinMetadataArgs;
pub use coin::CoinMetadataQuery;
pub use dry_run::DryRunArgs;
pub use dry_run::DryRunQuery;
pub use dry_run::DryRunResult;
pub use dry_run::TransactionMetadata;
pub use epoch::Epoch;
pub use epoch::EpochSummaryArgs;
pub use epoch::EpochSummaryQuery;
Expand All @@ -55,13 +59,16 @@ pub use protocol_config::ProtocolVersionArgs;
pub use service_config::Feature;
pub use service_config::ServiceConfig;
pub use service_config::ServiceConfigQuery;
use sui_types::types::Address;
pub use transaction::TransactionBlock;
pub use transaction::TransactionBlockArgs;
pub use transaction::TransactionBlockQuery;
pub use transaction::TransactionBlocksQuery;
pub use transaction::TransactionBlocksQueryArgs;
pub use transaction::TransactionsFilter;

use sui_types::types::Address;

use anyhow::anyhow;
use cynic::impl_scalar;

#[cynic::schema("rpc")]
Expand Down
7 changes: 7 additions & 0 deletions crates/sui-graphql-client/src/query_types/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ pub struct TransactionBlocksQueryArgs {
#[cynic(schema = "rpc", graphql_type = "TransactionBlock")]
pub struct TransactionBlock {
pub bcs: Option<Base64>,
pub effects: Option<TransactionBlockEffects>,
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(schema = "rpc", graphql_type = "TransactionBlockEffects")]
pub struct TransactionBlockEffects {
pub bcs: Option<Base64>,
}

#[derive(cynic::Enum, Clone, Copy, Debug)]
Expand Down

0 comments on commit 2699101

Please sign in to comment.