Skip to content

Commit

Permalink
[graphql] implement dryRunTransactionBlock (MystenLabs#15820)
Browse files Browse the repository at this point in the history
## Description 

As titled

## Test Plan 

Added a new test.

---
If your changes are not user-facing and do not break anything, you can
skip the following section. Otherwise, please briefly describe what has
changed under the Release Notes section.

### Type of Change (Check all that apply)

- [ ] protocol change
- [ ] user-visible impact
- [ ] breaking change for a client SDKs
- [ ] breaking change for FNs (FN binary must upgrade)
- [ ] breaking change for validators or node operators (must upgrade
binaries)
- [ ] breaking change for on-chain data layout
- [ ] necessitate either a data wipe or data migration

### Release notes

---------

Co-authored-by: ade <[email protected]>
  • Loading branch information
emmazzz and oxade authored Jan 22, 2024
1 parent d84ba9c commit df3942d
Show file tree
Hide file tree
Showing 15 changed files with 626 additions and 27 deletions.
88 changes: 88 additions & 0 deletions crates/sui-graphql-rpc/schema/current_progress_schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,44 @@ type DisplayEntry {
error: String
}

type DryRunEffect {
"""
Changes made to arguments that were mutably borrowed by each command in this transaction.
"""
mutatedReferences: [DryRunMutation!]
"""
Return results of each command in this transaction.
"""
returnValues: [DryRunReturn!]
}

type DryRunMutation {
input: TransactionArgument!
type: MoveType!
bcs: Base64!
}

type DryRunResult {
"""
The error that occurred during dry run execution, if any.
"""
error: String
"""
The intermediate results for each command of the dry run execution, including
contents of mutated references and return values.
"""
results: [DryRunEffect!]
"""
The transaction block representing the dry run execution.
"""
transaction: TransactionBlock
}

type DryRunReturn {
type: MoveType!
bcs: Base64!
}

"""
Dynamic fields are heterogeneous fields that can be added or removed at runtime,
and can have arbitrary user-assigned names. There are two sub-types of dynamic
Expand Down Expand Up @@ -1904,6 +1942,21 @@ The object's owner type: Immutable, Shared, Parent, or Address.
"""
union ObjectOwner = Immutable | Shared | Parent | AddressOwner

input ObjectRef {
"""
ID of the object.
"""
address: SuiAddress!
"""
Version or sequence number of the object.
"""
version: Int!
"""
Digest of the object.
"""
digest: String!
}

"""
Represents types that could contain references or free type parameters. Such types can appear
as function parameters, in fields of structs, or as actual type parameter.
Expand Down Expand Up @@ -2195,6 +2248,26 @@ type Query {
Configuration for this RPC service
"""
serviceConfig: ServiceConfig!
"""
Simulate running a transaction to inspect its effects without
committing to them on-chain.
`txBytes` either a `TransactionData` struct or a `TransactionKind`
struct, BCS-encoded and then Base64-encoded. The expected
type is controlled by the presence or absence of `txMeta`: If
present, `txBytes` is assumed to be a `TransactionKind`, if
absent, then `TransactionData`.
`txMeta` the data that is missing from a `TransactionKind` to make
a `TransactionData` (sender address and gas information). All
its fields are nullable.
`skipChecks` optional flag to disable 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.
"""
dryRunTransactionBlock(txBytes: String!, txMeta: TransactionMetadata, skipChecks: Boolean): DryRunResult!
owner(address: SuiAddress!): Owner
"""
The object corresponding to the given address at the (optionally) given version.
Expand Down Expand Up @@ -2935,6 +3008,21 @@ type TransactionInputEdge {
cursor: String!
}

"""
The optional extra data a user can provide to a transaction dry run.
`sender` defaults to `0x0`. If gasObjects` is not present, or is an empty list,
it is substituted with a mock Coin object, `gasPrice` defaults to the reference
gas price, `gasBudget` defaults to the max gas budget and `gasSponsor` defaults
to the sender.
"""
input TransactionMetadata {
sender: SuiAddress
gasPrice: Int
gasObjects: [ObjectRef!]
gasBudget: Int
gasSponsor: SuiAddress
}

"""
Transfers `inputs` to `address`. All inputs must have the `store` ability (allows public
transfer) and must not be previously immutable or shared.
Expand Down
9 changes: 4 additions & 5 deletions crates/sui-graphql-rpc/schema/draft_target_schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,10 @@ scalar OpenMoveTypeSignature
# `TransactionData` in a dry-run.
input TransactionMetadata {
sender: SuiAddress
gasPrice: BigInt
gasBudget: BigInt
gasPrice: Int
gasBudget: Int
gasObjects: [ObjectRef!]
gasSponsor: SuiAddress
}

# A reference to a particular version of an object.
Expand Down Expand Up @@ -1275,8 +1276,6 @@ type ExecutionResult {
type DryRunResult {
transaction: TransactionBlock
error: String

events: [Event!]
results: [DryRunEffect!]
}

Expand All @@ -1290,7 +1289,7 @@ type DryRunEffect {
}

type DryRunMutation {
input: TransactionInput
input: TransactionArgument
type: MoveType
bcs: Base64
}
Expand Down
2 changes: 1 addition & 1 deletion crates/sui-graphql-rpc/src/server/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ impl ServerBuilder {
.map_err(|e| Error::Internal(format!("Failed to create SuiClient: {}", e)))?,
)
} else {
warn!("No fullnode url found in config. Mutations will not work");
warn!("No fullnode url found in config. `dryRunTransactionBlock` and `executeTransactionBlock` will not work");
None
};

Expand Down
8 changes: 7 additions & 1 deletion crates/sui-graphql-rpc/src/types/digest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use super::string_input::impl_string_input;
use async_graphql::*;
use fastcrypto::encoding::{Base58, Encoding};
use std::{fmt, str::FromStr};
use sui_types::digests::TransactionDigest;
use sui_types::digests::{ObjectDigest, TransactionDigest};

pub(crate) const BASE58_DIGEST_LENGTH: usize = 32;

Expand Down Expand Up @@ -56,6 +56,12 @@ impl TryFrom<&[u8]> for Digest {
}
}

impl From<Digest> for ObjectDigest {
fn from(digest: Digest) -> Self {
ObjectDigest::new(digest.0)
}
}

impl From<TransactionDigest> for Digest {
fn from(digest: TransactionDigest) -> Self {
Digest(digest.into_inner())
Expand Down
123 changes: 123 additions & 0 deletions crates/sui-graphql-rpc/src/types/dry_run_result.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use super::base64::Base64;
use super::move_type::MoveType;
use super::transaction_block::TransactionBlock;
use super::transaction_block_kind::programmable::TransactionArgument;
use crate::error::Error;
use async_graphql::*;
use sui_json_rpc_types::{DevInspectResults, SuiExecutionResult};
use sui_types::effects::TransactionEffects as NativeTransactionEffects;
use sui_types::transaction::TransactionData as NativeTransactionData;

#[derive(Clone, Debug, SimpleObject)]
pub(crate) struct DryRunResult {
/// The error that occurred during dry run execution, if any.
pub error: Option<String>,
/// The intermediate results for each command of the dry run execution, including
/// contents of mutated references and return values.
pub results: Option<Vec<DryRunEffect>>,
/// The transaction block representing the dry run execution.
pub transaction: Option<TransactionBlock>,
}

#[derive(Clone, Debug, PartialEq, Eq, SimpleObject)]
pub(crate) struct DryRunEffect {
/// Changes made to arguments that were mutably borrowed by each command in this transaction.
pub mutated_references: Option<Vec<DryRunMutation>>,

/// Return results of each command in this transaction.
pub return_values: Option<Vec<DryRunReturn>>,
}

#[derive(Clone, Debug, PartialEq, Eq, SimpleObject)]
pub(crate) struct DryRunMutation {
pub input: TransactionArgument,

pub type_: MoveType,

pub bcs: Base64,
}

#[derive(Clone, Debug, PartialEq, Eq, SimpleObject)]
pub(crate) struct DryRunReturn {
pub type_: MoveType,

pub bcs: Base64,
}

impl TryFrom<DevInspectResults> for DryRunResult {
type Error = crate::error::Error;
fn try_from(results: DevInspectResults) -> Result<Self, Self::Error> {
let execution_results = results
.results
.ok_or_else(|| {
Error::Internal("No execution results returned from dev inspect".to_string())
})?
.into_iter()
.map(DryRunEffect::try_from)
.collect::<Result<Vec<_>, Self::Error>>()?;
let events = results.events.data.into_iter().map(|e| e.into()).collect();
let effects: NativeTransactionEffects =
bcs::from_bytes(&results.raw_effects).map_err(|e| {
Error::Internal(format!("Unable to deserialize transaction effects: {e}"))
})?;
let tx_data: NativeTransactionData = bcs::from_bytes(&results.raw_txn_data)
.map_err(|e| Error::Internal(format!("Unable to deserialize transaction data: {e}")))?;
let transaction = Some(TransactionBlock::DryRun {
tx_data,
effects,
events,
});
Ok(Self {
error: results.error,
results: Some(execution_results),
transaction,
})
}
}

impl TryFrom<SuiExecutionResult> for DryRunEffect {
type Error = crate::error::Error;

fn try_from(result: SuiExecutionResult) -> Result<Self, Self::Error> {
let mutated_references = result
.mutable_reference_outputs
.iter()
.map(|(argument, bcs, type_)| {
Ok(DryRunMutation {
input: (*argument).into(),
type_: MoveType::new(type_.clone().try_into()?),
bcs: bcs.into(),
})
})
.collect::<Result<Vec<_>, anyhow::Error>>()
.map_err(|e| {
Error::Internal(format!(
"Failed to parse results returned from dev inspect: {:?}",
e
))
})?;
let return_values = result
.return_values
.iter()
.map(|(bcs, type_)| {
Ok(DryRunReturn {
type_: MoveType::new(type_.clone().try_into()?),
bcs: bcs.into(),
})
})
.collect::<Result<Vec<_>, anyhow::Error>>()
.map_err(|e| {
Error::Internal(format!(
"Failed to parse results returned from dev inspect: {:?}",
e
))
})?;
Ok(Self {
mutated_references: Some(mutated_references),
return_values: Some(return_values),
})
}
}
17 changes: 17 additions & 0 deletions crates/sui-graphql-rpc/src/types/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use diesel::{BoolExpressionMethods, ExpressionMethods, QueryDsl};
use serde::{Deserialize, Serialize};
use sui_indexer::models_v2::{events::StoredEvent, transactions::StoredTransaction};
use sui_indexer::schema_v2::{events, transactions, tx_senders};
use sui_json_rpc_types::SuiEvent;
use sui_types::base_types::ObjectID;
use sui_types::Identifier;
use sui_types::{
Expand Down Expand Up @@ -230,6 +231,22 @@ impl Event {
}
}

impl From<SuiEvent> for Event {
fn from(event: SuiEvent) -> Self {
let native = NativeEvent {
sender: event.sender,
package_id: event.package_id,
transaction_module: event.transaction_module,
type_: event.type_,
contents: event.bcs,
};
Self {
stored: None,
native,
}
}
}

impl TryFrom<StoredEvent> for Event {
type Error = Error;

Expand Down
2 changes: 2 additions & 0 deletions crates/sui-graphql-rpc/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub(crate) mod cursor;
pub(crate) mod date_time;
pub(crate) mod digest;
pub(crate) mod display;
pub(crate) mod dry_run_result;
pub(crate) mod dynamic_field;
pub(crate) mod epoch;
pub(crate) mod event;
Expand Down Expand Up @@ -48,6 +49,7 @@ pub(crate) mod system_state_summary;
pub(crate) mod transaction_block;
pub(crate) mod transaction_block_effects;
pub(crate) mod transaction_block_kind;
pub(crate) mod transaction_metadata;
pub(crate) mod type_filter;
pub(crate) mod unchanged_shared_object;
pub(crate) mod validator;
Expand Down
11 changes: 11 additions & 0 deletions crates/sui-graphql-rpc/src/types/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use sui_types::TypeTag;
use super::balance;
use super::big_int::BigInt;
use super::cursor::{self, Page, Target};
use super::digest::Digest;
use super::display::{get_rendered_fields, DisplayEntry};
use super::dynamic_field::{DynamicField, DynamicFieldName};
use super::move_object::MoveObject;
Expand Down Expand Up @@ -80,6 +81,16 @@ pub enum ObjectStatus {
WrappedOrDeleted,
}

#[derive(Clone, Debug, PartialEq, Eq, InputObject)]
pub(crate) struct ObjectRef {
/// ID of the object.
pub address: SuiAddress,
/// Version or sequence number of the object.
pub version: u64,
/// Digest of the object.
pub digest: Digest,
}

/// Constrains the set of objects returned. All filters are optional, and the resulting set of
/// objects are ones whose
///
Expand Down
Loading

0 comments on commit df3942d

Please sign in to comment.