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