Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion crates/sui-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,16 @@ use sui_json_rpc_api::{
pub use sui_json_rpc_types as rpc_types;
use sui_json_rpc_types::{
ObjectsPage, SuiObjectDataFilter, SuiObjectDataOptions, SuiObjectResponse,
SuiObjectResponseQuery,
SuiObjectResponseQuery, SuiTransactionBlockResponse,
};
use sui_transaction_builder::{DataReader, TransactionBuilder};
pub use sui_types as types;
use sui_types::base_types::{ObjectID, ObjectInfo, SuiAddress};
use types::transaction::Transaction;

use crate::apis::{CoinReadApi, EventApi, GovernanceApi, QuorumDriverApi, ReadApi};
use crate::error::{Error, SuiRpcResult};
use crate::rpc_types::SuiTransactionBlockResponseOptions;

pub mod apis;
pub mod error;
Expand All @@ -113,6 +115,10 @@ pub const SUI_LOCAL_NETWORK_GAS_URL: &str = "http://127.0.0.1:5003/gas";
pub const SUI_DEVNET_URL: &str = "https://fullnode.devnet.sui.io:443";
pub const SUI_TESTNET_URL: &str = "https://fullnode.testnet.sui.io:443";

const WAIT_FOR_LOCAL_EXECUTION_TIMEOUT: Duration = Duration::from_secs(90);
const WAIT_FOR_LOCAL_EXECUTION_DELAY: Duration = Duration::from_millis(200);
const WAIT_FOR_LOCAL_EXECUTION_INTERVAL: Duration = Duration::from_secs(2);

/// A Sui client builder for connecting to the Sui network
///
/// By default the `maximum concurrent requests` is set to 256 and
Expand Down Expand Up @@ -532,6 +538,58 @@ impl SuiClient {
pub fn ws(&self) -> Option<&WsClient> {
self.api.ws.as_ref()
}

/// Execute a transaction and wait for the effects cert.
/// The transaction execution is not guaranteed to succeed and may fail. This is usually only
/// needed in non-test environment or the caller is explicitly testing some failure behavior.
///
// Used mostly in the CLI as it needs to wait for the effects cert instead of local execution.
// Use the `execute_transaction_may_fail` from WalletContext
// for most cases where read-after write consistency is needed.
pub async fn execute_tx(
&self,
tx: Transaction,
) -> Result<SuiTransactionBlockResponse, anyhow::Error> {
let tx_clone = tx.clone();
let resp = self
.quorum_driver_api()
.execute_transaction_block(
tx,
SuiTransactionBlockResponseOptions::new()
.with_effects()
.with_input()
.with_events()
.with_object_changes()
.with_balance_changes(),
Some(sui_types::quorum_driver_types::ExecuteTransactionRequestType::WaitForEffectsCert),
)
.await?;

// poll for tx to simulate wait for local execution
tokio::time::timeout(WAIT_FOR_LOCAL_EXECUTION_TIMEOUT, async {
// Apply a short delay to give the full node a chance to catch up.
tokio::time::sleep(WAIT_FOR_LOCAL_EXECUTION_DELAY).await;

let mut interval = tokio::time::interval(WAIT_FOR_LOCAL_EXECUTION_INTERVAL);
loop {
interval.tick().await;

if let Ok(poll_response) = self
.read_api()
.get_transaction_with_options(
*tx_clone.digest(),
SuiTransactionBlockResponseOptions::default(),
)
.await
{
break poll_response;
}
}
})
.await?;

Ok(resp)
}
}

#[async_trait]
Expand Down
14 changes: 7 additions & 7 deletions crates/sui/src/client_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1554,7 +1554,8 @@ impl SuiClientCommands {
}
let transaction = Transaction::from_generic_sig_data(data, sigs);

let response = context.execute_transaction_may_fail(transaction).await?;
let client = context.get_client().await?;
let response = client.execute_tx(transaction).await?;
SuiClientCommandResult::TransactionBlock(response)
}
SuiClientCommands::ExecuteCombinedSignedTx { signed_tx_bytes } => {
Expand All @@ -1565,7 +1566,8 @@ impl SuiClientCommands {
.map_err(|_| anyhow!("Invalid Base64 encoding"))?
).map_err(|_| anyhow!("Failed to parse SenderSignedData bytes, check if it matches the output of sui client commands with --serialize-signed-transaction"))?;
let transaction = Envelope::<SenderSignedData, EmptySignInfo>::new(data);
let response = context.execute_transaction_may_fail(transaction).await?;
let client = context.get_client().await?;
let response = client.execute_tx(transaction).await?;
SuiClientCommandResult::TransactionBlock(response)
}
SuiClientCommands::NewEnv {
Expand Down Expand Up @@ -2868,11 +2870,9 @@ pub(crate) async fn dry_run_or_execute_or_serialize(
))
} else {
let transaction = Transaction::new(sender_signed_data);
debug!("Executing transaction: {:?}", transaction);
let mut response = context
.execute_transaction_may_fail(transaction.clone())
.await?;
debug!("Transaction executed: {:?}", transaction);
debug!("Executing transaction: {:?}", &transaction);
let mut response = client.execute_tx(transaction.clone()).await?;
debug!("Transaction executed: {:?}", &transaction);
if let Some(effects) = response.effects.as_mut() {
prerender_clever_errors(effects, client.read_api()).await;
}
Expand Down
23 changes: 14 additions & 9 deletions crates/sui/tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,11 @@ async fn test_ptb_publish_and_complex_arg_resolution() -> Result<(), anyhow::Err
// Print it out to CLI/logs
resp.print(true);

let SuiClientCommandResult::TransactionBlock(response) = resp else {
let SuiClientCommandResult::TransactionBlock(ref response) = resp else {
unreachable!("Invalid response");
};

let SuiTransactionBlockEffects::V1(effects) = response.effects.unwrap();
let SuiTransactionBlockEffects::V1(effects) = response.effects.as_ref().unwrap();

assert!(effects.status.is_ok());
assert_eq!(effects.gas_object().object_id(), gas_obj_id);
Expand Down Expand Up @@ -1072,12 +1072,12 @@ async fn test_receive_argument() -> Result<(), anyhow::Error> {
.execute(context)
.await?;

let owned_obj_ids = if let SuiClientCommandResult::TransactionBlock(response) = resp {
let owned_obj_ids = if let SuiClientCommandResult::TransactionBlock(ref response) = resp {
assert_eq!(
response.effects.as_ref().unwrap().gas_object().object_id(),
gas_obj_id
);
let x = response.effects.unwrap();
let x = response.effects.as_ref().unwrap();
x.created().to_vec()
} else {
unreachable!("Invalid response");
Expand Down Expand Up @@ -1232,8 +1232,8 @@ async fn test_receive_argument_by_immut_ref() -> Result<(), anyhow::Error> {
.await?;

let (parent, child) =
if let SuiClientCommandResult::TransactionBlock(response) = start_call_result {
let created = response.effects.unwrap().created().to_vec();
if let SuiClientCommandResult::TransactionBlock(ref response) = start_call_result {
let created = response.effects.as_ref().unwrap().created().to_vec();
let owners: BTreeSet<ObjectID> = created
.iter()
.flat_map(|refe| {
Expand Down Expand Up @@ -1320,12 +1320,12 @@ async fn test_receive_argument_by_mut_ref() -> Result<(), anyhow::Error> {
.execute(context)
.await?;

let owned_obj_ids = if let SuiClientCommandResult::TransactionBlock(response) = resp {
let owned_obj_ids = if let SuiClientCommandResult::TransactionBlock(ref response) = resp {
assert_eq!(
response.effects.as_ref().unwrap().gas_object().object_id(),
gas_obj_id
);
let x = response.effects.unwrap();
let x = response.effects.as_ref().unwrap();
x.created().to_vec()
} else {
unreachable!("Invalid response");
Expand Down Expand Up @@ -2543,6 +2543,7 @@ async fn test_merge_coin() -> Result<(), anyhow::Error> {
}
.execute(context)
.await?;

let g = if let SuiClientCommandResult::TransactionBlock(r) = resp {
assert!(r.status_ok().unwrap(), "Command failed: {:?}", r);
assert_eq!(r.effects.as_ref().unwrap().gas_object().object_id(), gas);
Expand Down Expand Up @@ -3632,6 +3633,7 @@ async fn test_transfer() -> Result<(), anyhow::Error> {
}
.execute(context)
.await?;

// transfer command will transfer the object_id1 to address2, and use object_id2 as gas
// we check if object1 is owned by address 2 and if the gas object used is object_id2
if let SuiClientCommandResult::TransactionBlock(response) = transfer {
Expand Down Expand Up @@ -3668,8 +3670,8 @@ async fn test_transfer_sui() -> Result<(), anyhow::Error> {
let (mut test_cluster, client, rgp, objects, recipients, addresses) =
test_cluster_helper().await;
let object_id1 = objects[0];
let recipient1 = &recipients[0];
let address2 = addresses[0];
let recipient1 = &recipients[0];
let context = &mut test_cluster.wallet;
let amount = 1000;
let transfer_sui = SuiClientCommands::TransferSui {
Expand Down Expand Up @@ -3712,6 +3714,7 @@ async fn test_transfer_sui() -> Result<(), anyhow::Error> {
} else {
panic!("TransferSui test failed");
}

// transfer the whole object by not passing an amount
let transfer_sui = SuiClientCommands::TransferSui {
to: recipient1.clone(),
Expand All @@ -3721,6 +3724,7 @@ async fn test_transfer_sui() -> Result<(), anyhow::Error> {
}
.execute(context)
.await?;

if let SuiClientCommandResult::TransactionBlock(response) = transfer_sui {
assert!(response.status_ok().unwrap());
assert_eq!(
Expand Down Expand Up @@ -3781,6 +3785,7 @@ async fn test_gas_estimation() -> Result<(), anyhow::Error> {
.execute(context)
.await
.unwrap();

if let SuiClientCommandResult::TransactionBlock(response) = transfer_sui_cmd {
assert!(response.status_ok().unwrap());
let gas_used = response.effects.as_ref().unwrap().gas_object().object_id();
Expand Down
21 changes: 21 additions & 0 deletions crates/sui/tests/snapshots/cli_tests__clever_errors.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
source: crates/sui/tests/cli_tests.rs
expression: error_string
---
Non-clever-abort
---
Error executing transaction 'ELIDED_TRANSACTION_DIGEST': 1st command aborted within function 'ELIDED_ADDRESS::clever_errors::aborter' at instruction 1 with code 0
---
Line-only-abort
---
Error executing transaction 'ELIDED_TRANSACTION_DIGEST': 1st command aborted within function 'ELIDED_ADDRESS::clever_errors::aborter_line_no' at line 18
---
Clever-error-utf8
---
Error executing transaction 'ELIDED_TRANSACTION_DIGEST': 1st command aborted within function 'ELIDED_ADDRESS::clever_errors::clever_aborter' at line 22. Aborted with 'ENotFound' -- 'Element not found in vector 💥 🚀 🌠'
---
Clever-error-non-utf8
---
Error executing transaction 'ELIDED_TRANSACTION_DIGEST': 1st command aborted within function 'ELIDED_ADDRESS::clever_errors::clever_aborter_not_a_string' at line 26. Aborted with 'ENotAString' -- 'BAEAAAAAAAAAAgAAAAAAAAADAAAAAAAAAAQAAAAAAAAA'
---

Loading