From 756dfd7e91ce87e208f3c460a0e4200a1bd79f3a Mon Sep 17 00:00:00 2001 From: Jordan Jennings Date: Mon, 25 Nov 2024 11:20:36 -0800 Subject: [PATCH] create dry run helper for sui transactional test runner --- .../sui-transactional-test-runner/src/args.rs | 2 + .../sui-transactional-test-runner/src/lib.rs | 29 +++++- .../src/test_adapter.rs | 95 ++++++++++++++----- 3 files changed, 98 insertions(+), 28 deletions(-) diff --git a/crates/sui-transactional-test-runner/src/args.rs b/crates/sui-transactional-test-runner/src/args.rs index c5b0ee74f991eb..1cacfe9e01796f 100644 --- a/crates/sui-transactional-test-runner/src/args.rs +++ b/crates/sui-transactional-test-runner/src/args.rs @@ -124,6 +124,8 @@ pub struct ProgrammableTransactionCommand { pub gas_payment: Option, #[clap(long = "dev-inspect")] pub dev_inspect: bool, + #[clap(long = "dry-run")] + pub dry_run: bool, #[clap( long = "inputs", value_parser = ParsedValue::::parse, diff --git a/crates/sui-transactional-test-runner/src/lib.rs b/crates/sui-transactional-test-runner/src/lib.rs index 760c65d1f3c4ce..949b98e735c0c8 100644 --- a/crates/sui-transactional-test-runner/src/lib.rs +++ b/crates/sui-transactional-test-runner/src/lib.rs @@ -22,8 +22,8 @@ use sui_core::authority::authority_per_epoch_store::CertLockGuard; use sui_core::authority::authority_test_utils::send_and_confirm_transaction_with_execution_error; use sui_core::authority::AuthorityState; use sui_json_rpc::authority_state::StateRead; -use sui_json_rpc_types::DevInspectResults; use sui_json_rpc_types::EventFilter; +use sui_json_rpc_types::{DevInspectResults, DryRunTransactionBlockResponse}; use sui_storage::key_value_store::TransactionKeyValueStore; use sui_types::base_types::ObjectID; use sui_types::base_types::SuiAddress; @@ -45,10 +45,10 @@ use sui_types::storage::ObjectStore; use sui_types::storage::ReadStore; use sui_types::sui_system_state::epoch_start_sui_system_state::EpochStartSystemStateTrait; use sui_types::sui_system_state::SuiSystemStateTrait; -use sui_types::transaction::InputObjects; use sui_types::transaction::Transaction; use sui_types::transaction::TransactionDataAPI; use sui_types::transaction::TransactionKind; +use sui_types::transaction::{InputObjects, TransactionData}; use test_adapter::{SuiTestAdapter, PRE_COMPILED}; #[cfg_attr(not(msim), tokio::main)] @@ -99,6 +99,12 @@ pub trait TransactionalAdapter: Send + Sync + ReadStore { amount: u64, ) -> anyhow::Result; + async fn dry_run_transaction_block( + &self, + transaction_block: TransactionData, + transaction_digest: TransactionDigest, + ) -> SuiResult; + async fn dev_inspect_transaction_block( &self, sender: SuiAddress, @@ -172,6 +178,17 @@ impl TransactionalAdapter for ValidatorWithFullnode { Ok((effects, error)) } + async fn dry_run_transaction_block( + &self, + transaction_block: TransactionData, + transaction_digest: TransactionDigest, + ) -> SuiResult { + self.fullnode + .dry_exec_transaction(transaction_block, transaction_digest) + .await + .map(|result| result.0) + } + async fn dev_inspect_transaction_block( &self, sender: SuiAddress, @@ -409,6 +426,14 @@ impl TransactionalAdapter for Simulacrum { unimplemented!("dev_inspect_transaction_block not supported in simulator mode") } + async fn dry_run_transaction_block( + &self, + _transaction_block: TransactionData, + _transaction_digest: TransactionDigest, + ) -> SuiResult { + unimplemented!("dry_run_transaction_block not supported in simulator mode") + } + async fn query_tx_events_asc( &self, tx_digest: &TransactionDigest, diff --git a/crates/sui-transactional-test-runner/src/test_adapter.rs b/crates/sui-transactional-test-runner/src/test_adapter.rs index 6801ed6d394c5d..10c49eefdefbc3 100644 --- a/crates/sui-transactional-test-runner/src/test_adapter.rs +++ b/crates/sui-transactional-test-runner/src/test_adapter.rs @@ -54,7 +54,10 @@ use sui_core::authority::AuthorityState; use sui_framework::DEFAULT_FRAMEWORK_PATH; use sui_graphql_rpc::test_infra::cluster::{RetentionConfig, SnapshotLagConfig}; use sui_json_rpc_api::QUERY_MAX_RESULT_LIMIT; -use sui_json_rpc_types::{DevInspectResults, SuiExecutionStatus, SuiTransactionBlockEffectsAPI}; +use sui_json_rpc_types::{ + DevInspectResults, DryRunTransactionBlockResponse, SuiExecutionStatus, + SuiTransactionBlockEffects, SuiTransactionBlockEffectsAPI, SuiTransactionBlockEvents, +}; use sui_protocol_config::{Chain, ProtocolConfig}; use sui_storage::{ key_value_store::TransactionKeyValueStore, key_value_store_metrics::KeyValueStoreMetrics, @@ -828,12 +831,17 @@ impl<'a> MoveTestAdapter<'a> for SuiTestAdapter { gas_price, gas_payment, dev_inspect, + dry_run, inputs, }) => { if dev_inspect && self.is_simulator() { bail!("Dev inspect is not supported on simulator mode"); } + if dry_run && dev_inspect { + bail!("Cannot set both dev-inspect and dry-run"); + } + let inputs = self.compiled_state().resolve_args(inputs)?; let inputs: Vec = inputs .into_iter() @@ -869,7 +877,8 @@ impl<'a> MoveTestAdapter<'a> for SuiTestAdapter { ) }) .collect::>>()?; - let summary = if !dev_inspect { + + let summary = if !dev_inspect && !dry_run { let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET); let gas_price = gas_price.unwrap_or(self.gas_price); let transaction = self.sign_sponsor_txn( @@ -888,6 +897,18 @@ impl<'a> MoveTestAdapter<'a> for SuiTestAdapter { }, ); self.execute_txn(transaction).await? + } else if dry_run { + let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET); + let gas_price = gas_price.unwrap_or(self.gas_price); + let sender_address = self.get_sender(sender).address; + let transaction = TransactionData::new_programmable( + sender_address, + vec![], + ProgrammableTransaction { inputs, commands }, + gas_budget, + gas_price, + ); + self.dry_run(transaction).await? } else { assert!( gas_budget.is_none(), @@ -1659,6 +1680,19 @@ impl<'a> SuiTestAdapter { } } + async fn dry_run(&mut self, transaction: TransactionData) -> anyhow::Result { + let digest = transaction.digest(); + let results = self + .executor + .dry_run_transaction_block(transaction, digest) + .await?; + let DryRunTransactionBlockResponse { + effects, events, .. + } = results; + + self.tx_summary_from_effects(effects, events) + } + async fn dev_inspect( &mut self, sender: SuiAddress, @@ -1672,6 +1706,21 @@ impl<'a> SuiTestAdapter { let DevInspectResults { effects, events, .. } = results; + + self.tx_summary_from_effects(effects, events) + } + + fn tx_summary_from_effects( + &mut self, + effects: SuiTransactionBlockEffects, + events: SuiTransactionBlockEvents, + ) -> anyhow::Result { + if let SuiExecutionStatus::Failure { error } = effects.status() { + return Err(anyhow::anyhow!(self.stabilize_str(format!( + "Transaction Effects Status: {error}\nExecution Error: {error}", + )))); + } + let mut created_ids: Vec<_> = effects.created().iter().map(|o| o.object_id()).collect(); let mut mutated_ids: Vec<_> = effects.mutated().iter().map(|o| o.object_id()).collect(); let mut unwrapped_ids: Vec<_> = effects.unwrapped().iter().map(|o| o.object_id()).collect(); @@ -1708,30 +1757,24 @@ impl<'a> SuiTestAdapter { unwrapped_then_deleted_ids.sort_by_key(|id| self.real_to_fake_object_id(id)); wrapped_ids.sort_by_key(|id| self.real_to_fake_object_id(id)); - match effects.status() { - SuiExecutionStatus::Success { .. } => { - let events = events - .data - .into_iter() - .map(|sui_event| sui_event.into()) - .collect(); - Ok(TxnSummary { - events, - gas_summary: gas_summary.clone(), - created: created_ids, - mutated: mutated_ids, - unwrapped: unwrapped_ids, - deleted: deleted_ids, - unwrapped_then_deleted: unwrapped_then_deleted_ids, - wrapped: wrapped_ids, - // TODO: Properly propagate unchanged shared objects in dev_inspect. - unchanged_shared: vec![], - }) - } - SuiExecutionStatus::Failure { error } => Err(anyhow::anyhow!(self.stabilize_str( - format!("Transaction Effects Status: {error}\nExecution Error: {error}",) - ))), - } + let events = events + .data + .into_iter() + .map(|sui_event| sui_event.into()) + .collect(); + + Ok(TxnSummary { + events, + gas_summary: gas_summary.clone(), + created: created_ids, + mutated: mutated_ids, + unwrapped: unwrapped_ids, + deleted: deleted_ids, + unwrapped_then_deleted: unwrapped_then_deleted_ids, + wrapped: wrapped_ids, + // TODO: Properly propagate unchanged shared objects in dev_inspect. + unchanged_shared: vec![], + }) } fn get_object(&self, id: &ObjectID, version: Option) -> anyhow::Result {