diff --git a/crates/sui-config/src/node.rs b/crates/sui-config/src/node.rs index ceff5d499494a..701c99db5811a 100644 --- a/crates/sui-config/src/node.rs +++ b/crates/sui-config/src/node.rs @@ -150,7 +150,7 @@ impl ValidatorInfo { } } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)] pub struct Genesis { #[serde(flatten)] location: GenesisLocation, @@ -188,7 +188,7 @@ impl Genesis { } } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq)] #[serde(untagged)] enum GenesisLocation { InPlace { diff --git a/crates/sui-core/src/authority_active.rs b/crates/sui-core/src/authority_active.rs index f32f7882d1077..1a0b4cf846314 100644 --- a/crates/sui-core/src/authority_active.rs +++ b/crates/sui-core/src/authority_active.rs @@ -133,7 +133,6 @@ impl ActiveAuthority { }) } - #[cfg(test)] pub fn new_with_ephemeral_follower_store( authority: Arc, authority_clients: BTreeMap, @@ -205,11 +204,17 @@ where A: AuthorityAPI + Send + Sync + 'static + Clone, { pub async fn spawn_all_active_processes(self) { - self.spawn_active_processes(true, true).await + self.spawn_active_processes(true, true, CheckpointProcessControl::default()) + .await } /// Spawn all active tasks. - pub async fn spawn_active_processes(self, gossip: bool, checkpoint: bool) { + pub async fn spawn_active_processes( + self, + gossip: bool, + checkpoint: bool, + checkpoint_process_control: CheckpointProcessControl, + ) { let active = Arc::new(self); // Spawn a task to take care of gossip let gossip_locals = active.clone(); @@ -223,7 +228,7 @@ where let checkpoint_locals = active; // .clone(); let _checkpoint_join = tokio::task::spawn(async move { if checkpoint { - checkpoint_process(&checkpoint_locals, &CheckpointProcessControl::default()).await; + checkpoint_process(&checkpoint_locals, &checkpoint_process_control).await; } }); diff --git a/crates/sui-core/src/authority_active/checkpoint_driver/tests.rs b/crates/sui-core/src/authority_active/checkpoint_driver/tests.rs index 43835d4afedc8..72e10af79713a 100644 --- a/crates/sui-core/src/authority_active/checkpoint_driver/tests.rs +++ b/crates/sui-core/src/authority_active/checkpoint_driver/tests.rs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - authority_active::ActiveAuthority, authority_client::LocalAuthorityClient, - checkpoints::checkpoint_tests::TestSetup, safe_client::SafeClient, + authority_active::{checkpoint_driver::CheckpointProcessControl, ActiveAuthority}, + authority_client::LocalAuthorityClient, + checkpoints::checkpoint_tests::TestSetup, + safe_client::SafeClient, }; use std::{collections::BTreeSet, time::Duration}; @@ -107,7 +109,9 @@ async fn checkpoint_active_flow_crash_client_with_gossip() { ) .unwrap(); // Spin the gossip service. - active_state.spawn_active_processes(true, true).await; + active_state + .spawn_active_processes(true, true, CheckpointProcessControl::default()) + .await; }); } @@ -193,7 +197,9 @@ async fn checkpoint_active_flow_crash_client_no_gossip() { ) .unwrap(); // Spin the gossip service. - active_state.spawn_active_processes(false, true).await; + active_state + .spawn_active_processes(false, true, CheckpointProcessControl::default()) + .await; }); } diff --git a/crates/sui-core/src/authority_client.rs b/crates/sui-core/src/authority_client.rs index bb87c2c30b996..7fcbb33f9133b 100644 --- a/crates/sui-core/src/authority_client.rs +++ b/crates/sui-core/src/authority_client.rs @@ -378,7 +378,6 @@ impl LocalAuthorityClient { client } - #[cfg(test)] pub fn new_from_authority(state: Arc) -> Self { Self { state, diff --git a/crates/sui-faucet/src/errors.rs b/crates/sui-faucet/src/errors.rs index fe56880be12f8..54f9638a24030 100644 --- a/crates/sui-faucet/src/errors.rs +++ b/crates/sui-faucet/src/errors.rs @@ -3,7 +3,7 @@ use thiserror::Error; -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug, PartialEq, Eq)] pub enum FaucetError { #[error("Faucet does not have enough balance")] InsuffientBalance, diff --git a/crates/sui-transactional-test-runner/src/test_adapter.rs b/crates/sui-transactional-test-runner/src/test_adapter.rs index 89fb56153678d..ddbb5e533dc37 100644 --- a/crates/sui-transactional-test-runner/src/test_adapter.rs +++ b/crates/sui-transactional-test-runner/src/test_adapter.rs @@ -31,6 +31,7 @@ use move_vm_runtime::{ }; use once_cell::sync::Lazy; use rand::{rngs::StdRng, Rng, SeedableRng}; +use std::fmt::Write; use std::{ collections::{BTreeMap, BTreeSet}, path::Path, @@ -184,7 +185,7 @@ impl<'a> MoveTestAdapter<'a> for SuiTestAdapter<'a> { if !output.is_empty() { output.push_str(", ") } - output.push_str(&format!("{}: object({})", account, fake)) + write!(output, "{}: object({})", account, fake).unwrap() } for object_id in object_ids { test_adapter.enumerate_fake(object_id); @@ -564,26 +565,26 @@ impl<'a> SuiTestAdapter<'a> { if events.is_empty() { out += "No events" } else { - out += &format!("events: {}", self.list_events(events)) + write!(out, "events: {}", self.list_events(events)).unwrap(); } } if !created.is_empty() { if !out.is_empty() { out.push('\n') } - out += &format!("created: {}", self.list_objs(created)); + write!(out, "created: {}", self.list_objs(created)).unwrap(); } if !written.is_empty() { if !out.is_empty() { out.push('\n') } - out += &format!("written: {}", self.list_objs(written)); + write!(out, "written: {}", self.list_objs(written)).unwrap(); } if !deleted.is_empty() { if !out.is_empty() { out.push('\n') } - out += &format!("deleted: {}", self.list_objs(deleted)); + write!(out, "deleted: {}", self.list_objs(deleted)).unwrap(); } if out.is_empty() { diff --git a/crates/sui-types/src/event.rs b/crates/sui-types/src/event.rs index 3de2032347a13..ab86023edb400 100644 --- a/crates/sui-types/src/event.rs +++ b/crates/sui-types/src/event.rs @@ -20,7 +20,7 @@ use crate::{ }; /// A universal Sui event type encapsulating different types of events -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct EventEnvelope { /// UTC timestamp in milliseconds since epoch (1/1/1970) timestamp: u64, diff --git a/crates/sui-types/src/storage.rs b/crates/sui-types/src/storage.rs index 58270bc89bcba..45f1e2ebfc5db 100644 --- a/crates/sui-types/src/storage.rs +++ b/crates/sui-types/src/storage.rs @@ -10,7 +10,7 @@ use crate::{ object::Object, }; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum DeleteKind { /// An object is provided in the call input, and gets deleted. Normal, diff --git a/crates/sui-types/src/waypoint.rs b/crates/sui-types/src/waypoint.rs index 4396c2776406d..24f990e687341 100644 --- a/crates/sui-types/src/waypoint.rs +++ b/crates/sui-types/src/waypoint.rs @@ -198,7 +198,7 @@ where } } -impl<'a, K, I> WaypointDiff +impl WaypointDiff where I: 'static + Ord + IntoPoint, K: 'static, diff --git a/crates/sui-verifier/src/id_leak_verifier.rs b/crates/sui-verifier/src/id_leak_verifier.rs index 2c93feb0c0fdc..2492708ce833d 100644 --- a/crates/sui-verifier/src/id_leak_verifier.rs +++ b/crates/sui-verifier/src/id_leak_verifier.rs @@ -90,7 +90,7 @@ fn verify_id_leak(module: &CompiledModule) -> SuiResult { Ok(()) } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct AbstractState { locals: BTreeMap, } diff --git a/crates/sui/src/benchmark/bench_types.rs b/crates/sui/src/benchmark/bench_types.rs index 3eea5379acda6..6ce9cc1639452 100644 --- a/crates/sui/src/benchmark/bench_types.rs +++ b/crates/sui/src/benchmark/bench_types.rs @@ -61,7 +61,7 @@ pub struct Benchmark { pub bench_type: BenchmarkType, } -#[derive(Parser, Debug, Clone, PartialEq, EnumString)] +#[derive(Parser, Debug, Clone, PartialEq, EnumString, Eq)] #[clap(rename_all = "kebab-case")] pub enum BenchmarkType { #[clap(name = "microbench")] diff --git a/crates/sui/tests/checkpoints_tests.rs b/crates/sui/tests/checkpoints_tests.rs index 75e7fcc57a8fc..311c63f9d6c76 100644 --- a/crates/sui/tests/checkpoints_tests.rs +++ b/crates/sui/tests/checkpoints_tests.rs @@ -1,11 +1,54 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use sui_types::base_types::ExecutionDigests; -use test_utils::authority::{spawn_test_authorities, test_authority_configs}; -use tokio::time::sleep; -use tokio::time::Duration; +use rand::{rngs::StdRng, SeedableRng}; +use std::collections::HashSet; +use sui_core::{ + authority::AuthorityState, + authority_active::{checkpoint_driver::CheckpointProcessControl, ActiveAuthority}, +}; +use sui_types::{ + base_types::{ExecutionDigests, TransactionDigest}, + crypto::get_key_pair_from_rng, + messages::{CallArg, ExecutionStatus}, +}; +use test_utils::{ + authority::{ + publish_counter_package, spawn_test_authorities, submit_shared_object_transaction, + test_authority_aggregator, test_authority_configs, + }, + messages::{move_transaction, test_transactions}, + objects::test_gas_objects, +}; +use tokio::time::{sleep, Duration}; use typed_store::Map; +/// Helper function determining whether the checkpoint store of an authority contains the input +/// transactions' digests. +fn checkpoint_contains_digests( + authority: &AuthorityState, + transaction_digests: &HashSet, +) -> bool { + let checkpoints_store = authority.checkpoints().unwrap(); + + // Get all transactions in the first 10 checkpoints. + (0..10) + .flat_map(|checkpoint_sequence| { + // Get enough sequence numbers (one or two are enough). + (0..10) + .filter_map(|i| { + checkpoints_store + .lock() + .checkpoint_contents + .get(&(checkpoint_sequence, i)) + .unwrap() + }) + .map(|x| x.transaction) + .collect::>() + }) + .collect::>() + .is_superset(transaction_digests) +} + #[tokio::test] async fn sequence_fragments() { // Spawn a quorum of authorities. @@ -80,3 +123,211 @@ async fn sequence_fragments() { sleep(Duration::from_millis(10)).await; } } + +#[tokio::test] +async fn end_to_end() { + // Make a few test transactions. + let total_transactions = 3; + let mut rng = StdRng::from_seed([0; 32]); + let keys = (0..total_transactions).map(|_| get_key_pair_from_rng(&mut rng).1); + let (transactions, input_objects) = test_transactions(keys); + let transaction_digests: HashSet<_> = transactions.iter().map(|x| *x.digest()).collect(); + + // Spawn a quorum of authorities. + let configs = test_authority_configs(); + let handles = spawn_test_authorities(input_objects, &configs).await; + + // Make an authority's aggregator. + let aggregator = test_authority_aggregator(&configs); + + // Start active part of each authority. + for authority in &handles { + let state = authority.state().clone(); + let clients = aggregator.clone_inner_clients(); + let _active_authority_handle = tokio::spawn(async move { + let active_state = + ActiveAuthority::new_with_ephemeral_follower_store(state, clients).unwrap(); + let checkpoint_process_control = CheckpointProcessControl { + long_pause_between_checkpoints: Duration::from_millis(10), + ..CheckpointProcessControl::default() + }; + active_state + .spawn_active_processes(true, true, checkpoint_process_control) + .await + }); + } + + // Send the transactions for execution. + for transaction in &transactions { + let (_, effects) = aggregator + .clone() + .execute_transaction(transaction) + .await + .unwrap(); + + // If this check fails the transactions will not be included in the checkpoint. + assert!(matches!(effects.status, ExecutionStatus::Success { .. })); + + // Add some delay between transactions + tokio::time::sleep(Duration::from_millis(5)).await; + } + + // Wait for the transactions to be executed and end up in a checkpoint. Ensure all authorities + // moved to the next checkpoint sequence number. + loop { + let ok = handles + .iter() + .map(|authority| { + authority + .state() + .checkpoints() + .unwrap() + .lock() + .get_locals() + .next_checkpoint + }) + .all(|sequence| sequence >= 1); + + match ok { + true => break, + false => tokio::time::sleep(Duration::from_millis(10)).await, + } + } + + // Ensure all submitted transactions are in the checkpoint. + for authority in &handles { + let ok = checkpoint_contains_digests(&authority.state(), &transaction_digests); + assert!(ok); + } +} + +#[tokio::test] +async fn end_to_end_with_shared_objects() { + // Get some gas objects to submit shared-objects transactions. + let mut gas_objects = test_gas_objects(); + + // Make a few test transactions. + let total_transactions = 3; + let mut rng = StdRng::from_seed([0; 32]); + let keys = (0..total_transactions).map(|_| get_key_pair_from_rng(&mut rng).1); + let (transactions, input_objects) = test_transactions(keys); + + // Spawn a quorum of authorities. + let configs = test_authority_configs(); + let initialization_objects = input_objects.into_iter().chain(gas_objects.iter().cloned()); + let handles = spawn_test_authorities(initialization_objects, &configs).await; + + // Make an authority's aggregator. + let aggregator = test_authority_aggregator(&configs); + + // Start active part of each authority. + for authority in &handles { + let state = authority.state().clone(); + let clients = aggregator.clone_inner_clients(); + let _active_authority_handle = tokio::spawn(async move { + let active_state = + ActiveAuthority::new_with_ephemeral_follower_store(state, clients).unwrap(); + let checkpoint_process_control = CheckpointProcessControl { + long_pause_between_checkpoints: Duration::from_millis(10), + ..CheckpointProcessControl::default() + }; + active_state + .spawn_active_processes(true, true, checkpoint_process_control) + .await + }); + } + + // Publish the move package to all authorities and get the new package ref. + tokio::task::yield_now().await; + let gas = gas_objects.pop().unwrap(); + let package_ref = publish_counter_package(gas, configs.validator_set()).await; + + // Make a transaction to create a counter. + tokio::task::yield_now().await; + let create_counter_transaction = move_transaction( + gas_objects.pop().unwrap(), + "Counter", + "create", + package_ref, + /* arguments */ Vec::default(), + ); + let (_, effects) = aggregator + .execute_transaction(&create_counter_transaction) + .await + .unwrap(); + assert!(matches!(effects.status, ExecutionStatus::Success { .. })); + let ((counter_id, _, _), _) = effects.created[0]; + + // We can finally make a valid shared-object transaction (incrementing the counter). + tokio::task::yield_now().await; + let increment_counter_transaction = move_transaction( + gas_objects.pop().unwrap(), + "Counter", + "increment", + package_ref, + vec![CallArg::SharedObject(counter_id)], + ); + let replies = submit_shared_object_transaction( + increment_counter_transaction.clone(), + configs.validator_set(), + ) + .await; + for reply in replies { + match reply { + Ok(info) => { + let effects = info.signed_effects.unwrap().effects; + assert!(matches!(effects.status, ExecutionStatus::Success { .. })); + } + Err(error) => panic!("{error}"), + } + } + + // Now send a few single-writer transactions. + for transaction in &transactions { + let (_, effects) = aggregator + .clone() + .execute_transaction(transaction) + .await + .unwrap(); + + // If this check fails the transactions will not be included in the checkpoint. + assert!(matches!(effects.status, ExecutionStatus::Success { .. })); + + // Add some delay between transactions + tokio::time::sleep(Duration::from_millis(5)).await; + } + + // Record the transactions digests we expect to see in the checkpoint. Note that there is also + // an extra transaction to register the move module that we don't consider here. + let mut transaction_digests: HashSet<_> = transactions.iter().map(|x| *x.digest()).collect(); + transaction_digests.insert(*create_counter_transaction.digest()); + transaction_digests.insert(*increment_counter_transaction.digest()); + + // Wait for the transactions to be executed and end up in a checkpoint. Ensure all authorities + // moved to the next checkpoint sequence number. + loop { + let ok = handles + .iter() + .map(|authority| { + authority + .state() + .checkpoints() + .unwrap() + .lock() + .get_locals() + .next_checkpoint + }) + .all(|sequence| sequence >= 2); + + match ok { + true => break, + false => tokio::time::sleep(Duration::from_millis(10)).await, + } + } + + // Ensure all submitted transactions are in the checkpoint. + for authority in &handles { + let ok = checkpoint_contains_digests(&authority.state(), &transaction_digests); + assert!(ok); + } +} diff --git a/crates/sui/tests/shared_objects_tests.rs b/crates/sui/tests/shared_objects_tests.rs index 299d9292603c8..cc542f8101e97 100644 --- a/crates/sui/tests/shared_objects_tests.rs +++ b/crates/sui/tests/shared_objects_tests.rs @@ -1,106 +1,18 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 use std::sync::Arc; -use sui_config::ValidatorInfo; -use sui_core::{ - authority_client::{AuthorityAPI, NetworkAuthorityClient}, - gateway_state::{GatewayAPI, GatewayState}, -}; +use sui_core::gateway_state::{GatewayAPI, GatewayState}; +use sui_types::messages::{CallArg, ExecutionStatus}; use sui_types::object::OBJECT_START_VERSION; -use sui_types::{ - base_types::ObjectRef, - error::SuiResult, - messages::{ - CallArg, ConfirmationTransaction, ConsensusTransaction, ExecutionStatus, Transaction, - TransactionInfoResponse, - }, - object::Object, -}; use test_utils::{ - authority::{create_authority_aggregator, spawn_test_authorities, test_authority_configs}, - messages::{ - make_certificates, move_transaction, parse_package_ref, publish_move_package_transaction, - test_shared_object_transactions, + authority::{ + publish_counter_package, spawn_test_authorities, submit_shared_object_transaction, + submit_single_owner_transaction, test_authority_aggregator, test_authority_configs, }, + messages::{move_transaction, test_shared_object_transactions}, objects::{test_gas_objects, test_shared_object}, }; -/// Submit a certificate containing only owned-objects to all authorities. -async fn submit_single_owner_transaction( - transaction: Transaction, - configs: &[ValidatorInfo], -) -> Vec { - let certificate = make_certificates(vec![transaction]).pop().unwrap(); - let txn = ConfirmationTransaction { certificate }; - - let mut responses = Vec::new(); - for config in configs { - let client = get_client(config); - let reply = client - .handle_confirmation_transaction(txn.clone()) - .await - .unwrap(); - responses.push(reply); - } - responses -} - -fn get_client(config: &ValidatorInfo) -> NetworkAuthorityClient { - NetworkAuthorityClient::connect_lazy(config.network_address()).unwrap() -} - -/// Keep submitting the certificates of a shared-object transaction until it is sequenced by -/// at least one consensus node. We use the loop since some consensus protocols (like Tusk) -/// may drop transactions. The certificate is submitted to every Sui authority. -async fn submit_shared_object_transaction( - transaction: Transaction, - configs: &[ValidatorInfo], -) -> Vec> { - let certificate = make_certificates(vec![transaction]).pop().unwrap(); - let message = ConsensusTransaction::UserTransaction(Box::new(certificate)); - - loop { - let futures: Vec<_> = configs - .iter() - .map(|config| { - let client = get_client(config); - let txn = message.clone(); - async move { client.handle_consensus_transaction(txn).await } - }) - .collect(); - - let replies: Vec<_> = futures::future::join_all(futures) - .await - .into_iter() - // Remove all `FailedToHearBackFromConsensus` replies. Note that the original Sui error type - // `SuiError::FailedToHearBackFromConsensus(..)` is lost when the message is sent through the - // network (it is replaced by `RpcError`). As a result, the following filter doesn't work: - // `.filter(|result| !matches!(result, Err(SuiError::FailedToHearBackFromConsensus(..))))`. - .filter(|result| match result { - Err(e) => !e.to_string().contains("deadline has elapsed"), - _ => true, - }) - .collect(); - - if !replies.is_empty() { - break replies; - } - } -} - -/// Helper function to publish the move package of a simple shared counter. -async fn publish_counter_package(gas_object: Object, configs: &[ValidatorInfo]) -> ObjectRef { - let transaction = publish_move_package_transaction(gas_object); - let replies = submit_single_owner_transaction(transaction, configs).await; - let mut package_refs = Vec::new(); - for reply in replies { - let effects = reply.signed_effects.unwrap().effects; - assert!(matches!(effects.status, ExecutionStatus::Success { .. })); - package_refs.push(parse_package_ref(&effects).unwrap()); - } - package_refs.pop().unwrap() -} - /// Send a simple shared object transaction to Sui and ensures the client gets back a response. #[tokio::test] async fn shared_object_transaction() { @@ -495,7 +407,7 @@ async fn shared_object_on_gateway() { // the handles (or the authorities will stop). let configs = test_authority_configs(); let _handles = spawn_test_authorities(gas_objects.clone(), &configs).await; - let clients = create_authority_aggregator(configs.validator_set()); + let clients = test_authority_aggregator(&configs); let path = tempfile::tempdir().unwrap().into_path(); let gateway = Arc::new(GatewayState::new_with_authorities(path, clients).unwrap()); diff --git a/crates/test-utils/src/authority.rs b/crates/test-utils/src/authority.rs index d5e33d85ddff9..74cc0147c40fb 100644 --- a/crates/test-utils/src/authority.rs +++ b/crates/test-utils/src/authority.rs @@ -1,16 +1,28 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 - -use crate::TEST_COMMITTEE_SIZE; +use crate::{ + messages::{make_certificates, parse_package_ref, publish_move_package_transaction}, + TEST_COMMITTEE_SIZE, +}; use rand::{prelude::StdRng, SeedableRng}; use std::collections::BTreeMap; use std::time::Duration; use sui_config::{NetworkConfig, ValidatorInfo}; use sui_core::{ - authority_aggregator::AuthorityAggregator, authority_client::NetworkAuthorityClient, + authority_aggregator::AuthorityAggregator, authority_client::AuthorityAPI, + authority_client::NetworkAuthorityClient, }; use sui_node::SuiNode; -use sui_types::{committee::Committee, object::Object}; +use sui_types::{ + base_types::ObjectRef, + committee::Committee, + error::SuiResult, + messages::{ + ConfirmationTransaction, ConsensusTransaction, ExecutionStatus, Transaction, + TransactionInfoResponse, + }, + object::Object, +}; /// The default network buffer size of a test authority. pub const NETWORK_BUFFER_SIZE: usize = 65_000; @@ -51,15 +63,17 @@ where handles } -pub fn create_authority_aggregator( - authority_configs: &[ValidatorInfo], +/// Create a test authority aggregator. +pub fn test_authority_aggregator( + config: &NetworkConfig, ) -> AuthorityAggregator { - let voting_rights: BTreeMap<_, _> = authority_configs + let validators_info = config.validator_set(); + let voting_rights: BTreeMap<_, _> = validators_info .iter() .map(|config| (config.public_key(), config.stake())) .collect(); let committee = Committee::new(0, voting_rights); - let clients: BTreeMap<_, _> = authority_configs + let clients: BTreeMap<_, _> = validators_info .iter() .map(|config| { ( @@ -70,3 +84,80 @@ pub fn create_authority_aggregator( .collect(); AuthorityAggregator::new(committee, clients) } + +/// Get a network client to communicate with the consensus. +pub fn get_client(config: &ValidatorInfo) -> NetworkAuthorityClient { + NetworkAuthorityClient::connect_lazy(config.network_address()).unwrap() +} + +/// Submit a certificate containing only owned-objects to all authorities. +pub async fn submit_single_owner_transaction( + transaction: Transaction, + configs: &[ValidatorInfo], +) -> Vec { + let certificate = make_certificates(vec![transaction]).pop().unwrap(); + let txn = ConfirmationTransaction { certificate }; + + let mut responses = Vec::new(); + for config in configs { + let client = get_client(config); + let reply = client + .handle_confirmation_transaction(txn.clone()) + .await + .unwrap(); + responses.push(reply); + } + responses +} + +/// Keep submitting the certificates of a shared-object transaction until it is sequenced by +/// at least one consensus node. We use the loop since some consensus protocols (like Tusk) +/// may drop transactions. The certificate is submitted to every Sui authority. +pub async fn submit_shared_object_transaction( + transaction: Transaction, + configs: &[ValidatorInfo], +) -> Vec> { + let certificate = make_certificates(vec![transaction]).pop().unwrap(); + let message = ConsensusTransaction::UserTransaction(Box::new(certificate)); + + loop { + let futures: Vec<_> = configs + .iter() + .map(|config| { + let client = get_client(config); + let txn = message.clone(); + async move { client.handle_consensus_transaction(txn).await } + }) + .collect(); + + let replies: Vec<_> = futures::future::join_all(futures) + .await + .into_iter() + // Remove all `FailedToHearBackFromConsensus` replies. Note that the original Sui error type + // `SuiError::FailedToHearBackFromConsensus(..)` is lost when the message is sent through the + // network (it is replaced by `RpcError`). As a result, the following filter doesn't work: + // `.filter(|result| !matches!(result, Err(SuiError::FailedToHearBackFromConsensus(..))))`. + .filter(|result| match result { + Err(e) => !e.to_string().contains("deadline has elapsed"), + _ => true, + }) + .collect(); + + if !replies.is_empty() { + break replies; + } + } +} + +/// Publish the move package of a simple shared counter. +pub async fn publish_counter_package(gas_object: Object, configs: &[ValidatorInfo]) -> ObjectRef { + let transaction = publish_move_package_transaction(gas_object); + let replies = submit_single_owner_transaction(transaction, configs).await; + let mut package_refs = Vec::new(); + for reply in replies { + let effects = reply.signed_effects.unwrap().effects; + assert!(matches!(effects.status, ExecutionStatus::Success { .. })); + package_refs.push(parse_package_ref(&effects).unwrap()); + } + package_refs.pop().unwrap() +} diff --git a/crates/test-utils/src/messages.rs b/crates/test-utils/src/messages.rs index 13a3ee5db7fc8..691f6f1a979fe 100644 --- a/crates/test-utils/src/messages.rs +++ b/crates/test-utils/src/messages.rs @@ -1,7 +1,7 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::objects::test_gas_objects; use crate::objects::test_shared_object; +use crate::objects::{test_gas_objects, test_gas_objects_with_owners}; use crate::test_committee; use crate::test_keys; use move_core_types::account_address::AccountAddress; @@ -10,16 +10,60 @@ use move_package::BuildConfig; use std::path::PathBuf; use sui_adapter::genesis; use sui_types::base_types::ObjectRef; -use sui_types::crypto::Signature; -use sui_types::messages::{CallArg, TransactionEffects}; use sui_types::messages::{ CertifiedTransaction, SignatureAggregator, SignedTransaction, Transaction, TransactionData, }; use sui_types::object::{Object, Owner}; +use sui_types::{base_types::SuiAddress, crypto::Signature}; +use sui_types::{ + crypto::KeyPair, + messages::{CallArg, TransactionEffects}, +}; /// The maximum gas per transaction. pub const MAX_GAS: u64 = 10_000; +/// Make a few different single-writer test transactions owned by specific addresses. +pub fn test_transactions(keys: K) -> (Vec, Vec) +where + K: Iterator, +{ + // The key pair of the recipient of the transaction. + let (recipient, _) = test_keys().pop().unwrap(); + + // The gas objects and the objects used in the transfer transactions. Ever two + // consecutive objects must have the same owner for the transaction to be valid. + let mut addresses_two_by_two = Vec::new(); + let mut keypairs = Vec::new(); // Keys are not copiable, move them here. + for keypair in keys { + let address = SuiAddress::from(keypair.public_key_bytes()); + addresses_two_by_two.push(address); + addresses_two_by_two.push(address); + keypairs.push(keypair); + } + let gas_objects = test_gas_objects_with_owners(addresses_two_by_two); + + // Make one transaction for every two gas objects. + let mut transactions = Vec::new(); + for (objects, keypair) in gas_objects.chunks(2).zip(keypairs) { + let [o1, o2]: &[Object; 2] = match objects.try_into() { + Ok(x) => x, + Err(_) => break, + }; + + let data = TransactionData::new_transfer( + recipient, + o1.compute_object_reference(), + /* sender */ o1.owner.get_owner_address().unwrap(), + /* gas_object_ref */ o2.compute_object_reference(), + MAX_GAS, + ); + let signature = Signature::new(&data, &keypair); + transactions.push(Transaction::new(data, signature)); + } + (transactions, gas_objects) +} + /// Make a few different test transaction containing the same shared object. pub fn test_shared_object_transactions() -> Vec { // Helper function to load genesis packages. diff --git a/crates/test-utils/src/objects.rs b/crates/test-utils/src/objects.rs index 3b1b63478b3e2..4edde1894838b 100644 --- a/crates/test-utils/src/objects.rs +++ b/crates/test-utils/src/objects.rs @@ -1,18 +1,34 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 use crate::test_keys; -use sui_types::base_types::{ObjectID, TransactionDigest}; +use sui_types::base_types::{ObjectID, SuiAddress, TransactionDigest}; use sui_types::gas_coin::GasCoin; use sui_types::object::{MoveObject, Object, Owner, OBJECT_START_VERSION}; -/// Make a few test gas objects. +/// Make a few test gas objects (all with the same owner). pub fn test_gas_objects() -> Vec { (0..9) .map(|i| { + let seed = format!("0x444444444444444{i}"); + let gas_object_id = ObjectID::from_hex_literal(&seed).unwrap(); + let (owner, _) = test_keys().pop().unwrap(); + Object::with_id_owner_for_testing(gas_object_id, owner) + }) + .collect() +} + +/// Make a few test gas objects with a specific owners. +pub fn test_gas_objects_with_owners(owners: O) -> Vec +where + O: IntoIterator, +{ + owners + .into_iter() + .enumerate() + .map(|(i, owner)| { let seed = format!("0x555555555555555{i}"); let gas_object_id = ObjectID::from_hex_literal(&seed).unwrap(); - let (sender, _) = test_keys().pop().unwrap(); - Object::with_id_owner_for_testing(gas_object_id, sender) + Object::with_id_owner_for_testing(gas_object_id, owner) }) .collect() }