1use fastcrypto_zkp::bn254::zk_login::JwkId;
5use futures::future::join_all;
6use jsonrpsee::http_client::{HttpClient, HttpClientBuilder};
7use mysten_common::ZipDebugEqIteratorExt;
8use mysten_common::fatal;
9use rand::{Rng, distributions::*, rngs::OsRng, seq::SliceRandom};
10use std::net::SocketAddr;
11use std::num::NonZeroUsize;
12use std::path::PathBuf;
13use std::sync::{Arc, Mutex};
14use std::time::Duration;
15use sui_config::genesis::Genesis;
16use sui_config::node::FundsWithdrawSchedulerType;
17use sui_config::node::{
18 AuthorityOverloadConfig, ConsensusTransactionPoolConfig, DBCheckpointConfig, RunWithRange,
19};
20use sui_config::{Config, ExecutionCacheConfig, SUI_CLIENT_CONFIG, SUI_NETWORK_CONFIG};
21use sui_config::{NodeConfig, PersistedConfig, SUI_KEYSTORE_FILENAME};
22use sui_core::authority_aggregator::AuthorityAggregator;
23use sui_core::authority_client::NetworkAuthorityClient;
24use sui_core::transaction_driver::SubmitTransactionOptions;
25use sui_keys::keystore::{AccountKeystore, FileBasedKeystore, Keystore};
26use sui_node::SuiNodeHandle;
27use sui_protocol_config::{Chain, ProtocolVersion};
28use sui_rpc_api::Client;
29use sui_rpc_api::client::ExecutedTransaction;
30use sui_sdk::sui_client_config::{SuiClientConfig, SuiEnv};
31use sui_sdk::wallet_context::WalletContext;
32use sui_sdk::{SuiClient, SuiClientBuilder};
33use sui_swarm::memory::{Swarm, SwarmBuilder};
34use sui_swarm_config::genesis_config::{
35 AccountConfig, DEFAULT_GAS_AMOUNT, GenesisConfig, ValidatorGenesisConfig,
36};
37use sui_swarm_config::network_config::NetworkConfig;
38use sui_swarm_config::network_config_builder::{
39 FundsWithdrawSchedulerTypeConfig, GlobalStateHashV2EnabledCallback,
40 GlobalStateHashV2EnabledConfig, ProtocolVersionsConfig, SupportedProtocolVersionsCallback,
41 ValidatorObserverConfigCallback,
42};
43use sui_swarm_config::node_config_builder::{FullnodeConfigBuilder, ValidatorConfigBuilder};
44use sui_test_transaction_builder::TestTransactionBuilder;
45use sui_types::authenticator_state::get_authenticator_state;
46use sui_types::base_types::ConciseableName;
47use sui_types::base_types::{AuthorityName, ObjectID, ObjectRef, SuiAddress};
48use sui_types::committee::CommitteeTrait;
49use sui_types::committee::{Committee, EpochId};
50use sui_types::crypto::KeypairTraits;
51use sui_types::crypto::SuiKeyPair;
52use sui_types::digests::{ChainIdentifier, TransactionDigest};
53use sui_types::effects::TransactionEffectsAPI;
54use sui_types::effects::{TransactionEffects, TransactionEvents};
55use sui_types::error::{SuiErrorKind, SuiResult};
56use sui_types::messages_grpc::{
57 RawSubmitTxRequest, SubmitTxRequest, SubmitTxResult, SubmitTxType, WaitForEffectsRequest,
58 WaitForEffectsResponse,
59};
60use sui_types::object::Object;
61use sui_types::sui_system_state::SuiSystemState;
62use sui_types::sui_system_state::SuiSystemStateTrait;
63use sui_types::sui_system_state::epoch_start_sui_system_state::EpochStartSystemStateTrait;
64use sui_types::supported_protocol_versions::SupportedProtocolVersions;
65use sui_types::traffic_control::{PolicyConfig, RemoteFirewallConfig};
66use sui_types::transaction::{Transaction, TransactionData};
67use tokio::sync::broadcast;
68use tokio::time::{Instant, timeout};
69use tokio::{task::JoinHandle, time::sleep};
70use tonic::IntoRequest;
71use tracing::{error, info};
72
73pub mod addr_balance_test_env;
74
75const NUM_VALIDATOR: usize = 4;
76const TRANSACTION_FINALITY_TIMEOUT: Duration = Duration::from_secs(90);
78
79pub struct FullNodeHandle {
80 pub sui_node: SuiNodeHandle,
81 #[deprecated = "use grpc_client"]
82 pub sui_client: SuiClient,
83 #[deprecated = "use grpc_client"]
84 pub rpc_client: HttpClient,
85 pub grpc_client: Client,
86 pub rpc_url: String,
87}
88
89impl FullNodeHandle {
90 pub async fn new(sui_node: SuiNodeHandle, json_rpc_address: SocketAddr) -> Self {
91 let rpc_url = format!("http://{}", json_rpc_address);
92 let rpc_client = HttpClientBuilder::default().build(&rpc_url).unwrap();
93
94 let sui_client = SuiClientBuilder::default().build(&rpc_url).await.unwrap();
95 let grpc_client = Client::new(&rpc_url).unwrap();
96
97 Self {
98 sui_node,
99 #[allow(deprecated)]
100 sui_client,
101 #[allow(deprecated)]
102 rpc_client,
103 grpc_client,
104 rpc_url,
105 }
106 }
107}
108
109pub struct TestCluster {
110 pub swarm: Swarm,
111 pub wallet: WalletContext,
112 pub fullnode_handle: FullNodeHandle,
113}
114
115impl TestCluster {
116 #[deprecated = "use grpc_client()"]
117 pub fn rpc_client(&self) -> &HttpClient {
118 #[allow(deprecated)]
119 &self.fullnode_handle.rpc_client
120 }
121
122 #[deprecated = "use grpc_client()"]
123 pub fn sui_client(&self) -> &SuiClient {
124 #[allow(deprecated)]
125 &self.fullnode_handle.sui_client
126 }
127
128 pub fn grpc_client(&self) -> Client {
129 self.fullnode_handle.grpc_client.clone()
130 }
131
132 pub fn rpc_url(&self) -> &str {
133 &self.fullnode_handle.rpc_url
134 }
135
136 pub fn wallet(&mut self) -> &WalletContext {
137 &self.wallet
138 }
139
140 pub fn wallet_mut(&mut self) -> &mut WalletContext {
141 &mut self.wallet
142 }
143
144 pub fn get_addresses(&self) -> Vec<SuiAddress> {
145 self.wallet.get_addresses()
146 }
147
148 pub fn get_address_0(&self) -> SuiAddress {
150 self.get_addresses()[0]
151 }
152
153 pub fn get_address_1(&self) -> SuiAddress {
155 self.get_addresses()[1]
156 }
157
158 pub fn get_address_2(&self) -> SuiAddress {
160 self.get_addresses()[2]
161 }
162
163 pub fn fullnode_config_builder(&self) -> FullnodeConfigBuilder {
164 self.swarm.get_fullnode_config_builder()
165 }
166
167 pub fn committee(&self) -> Arc<Committee> {
168 self.fullnode_handle
169 .sui_node
170 .with(|node| node.state().epoch_store_for_testing().committee().clone())
171 }
172
173 pub fn get_sui_system_state(&self) -> SuiSystemState {
174 self.fullnode_handle.sui_node.with(|node| {
175 node.state()
176 .get_sui_system_state_object_for_testing()
177 .unwrap()
178 })
179 }
180
181 pub async fn spawn_new_fullnode(&mut self) -> FullNodeHandle {
183 self.start_fullnode_from_config(
184 self.fullnode_config_builder()
185 .build(&mut OsRng, self.swarm.config()),
186 )
187 .await
188 }
189
190 pub fn observer_node(&self) -> Option<&sui_swarm::memory::Node> {
196 self.swarm.observer_nodes().next()
197 }
198
199 pub async fn start_fullnode_from_config(&mut self, config: NodeConfig) -> FullNodeHandle {
200 let json_rpc_address = config.json_rpc_address;
201 let node = self.swarm.spawn_new_node(config).await;
202 FullNodeHandle::new(node, json_rpc_address).await
203 }
204
205 pub fn all_node_handles(&self) -> Vec<SuiNodeHandle> {
206 self.swarm
207 .all_nodes()
208 .flat_map(|n| n.get_node_handle())
209 .collect()
210 }
211
212 pub fn all_validator_handles(&self) -> Vec<SuiNodeHandle> {
213 self.swarm
214 .validator_nodes()
215 .map(|n| n.get_node_handle().unwrap())
216 .collect()
217 }
218
219 pub fn get_validator_pubkeys(&self) -> Vec<AuthorityName> {
220 self.swarm.active_validators().map(|v| v.name()).collect()
221 }
222
223 pub fn get_genesis(&self) -> Genesis {
224 self.swarm.config().genesis.clone()
225 }
226
227 pub fn stop_node(&self, name: &AuthorityName) {
228 self.swarm.node(name).unwrap().stop();
229 }
230
231 pub async fn stop_all_validators(&self) {
232 info!("Stopping all validators in the cluster");
233 self.swarm.active_validators().for_each(|v| v.stop());
234 tokio::time::sleep(Duration::from_secs(3)).await;
235 }
236
237 pub async fn start_all_validators(&self) {
238 info!("Starting all validators in the cluster");
239 for v in self.swarm.validator_nodes() {
240 if v.is_running() {
241 continue;
242 }
243 v.start().await.unwrap();
244 }
245 tokio::time::sleep(Duration::from_secs(3)).await;
246 }
247
248 pub async fn start_node(&self, name: &AuthorityName) {
249 let node = self.swarm.node(name).unwrap();
250 if node.is_running() {
251 return;
252 }
253 node.start().await.unwrap();
254 }
255
256 pub async fn spawn_new_validator(
257 &mut self,
258 genesis_config: ValidatorGenesisConfig,
259 ) -> SuiNodeHandle {
260 let node_config = ValidatorConfigBuilder::new()
261 .build(genesis_config, self.swarm.config().genesis.clone());
262 self.swarm.spawn_new_node(node_config).await
263 }
264
265 pub fn random_node_restarter(self: &Arc<Self>) -> RandomNodeRestarter {
266 RandomNodeRestarter::new(self.clone())
267 }
268
269 pub async fn get_reference_gas_price(&self) -> u64 {
270 self.grpc_client()
271 .get_reference_gas_price()
272 .await
273 .expect("failed to get reference gas price")
274 }
275
276 pub fn get_chain_identifier(&self) -> ChainIdentifier {
277 ChainIdentifier::from(*self.swarm.config().genesis.checkpoint().digest())
278 }
279
280 pub async fn get_object_from_fullnode_store(&self, object_id: &ObjectID) -> Option<Object> {
281 self.fullnode_handle
282 .sui_node
283 .with_async(|node| async { node.state().get_object(object_id) })
284 .await
285 }
286
287 pub async fn get_latest_object_ref(&self, object_id: &ObjectID) -> ObjectRef {
288 self.get_object_from_fullnode_store(object_id)
289 .await
290 .unwrap()
291 .compute_object_reference()
292 }
293
294 pub async fn get_object_or_tombstone_from_fullnode_store(
295 &self,
296 object_id: ObjectID,
297 ) -> ObjectRef {
298 self.fullnode_handle
299 .sui_node
300 .state()
301 .get_object_cache_reader()
302 .get_latest_object_ref_or_tombstone(object_id)
303 .unwrap()
304 }
305
306 pub async fn wait_for_run_with_range_shutdown_signal(&self) -> Option<RunWithRange> {
307 self.wait_for_run_with_range_shutdown_signal_with_timeout(Duration::from_secs(60))
308 .await
309 }
310
311 pub async fn wait_for_run_with_range_shutdown_signal_with_timeout(
312 &self,
313 timeout_dur: Duration,
314 ) -> Option<RunWithRange> {
315 let mut shutdown_channel_rx = self
316 .fullnode_handle
317 .sui_node
318 .with(|node| node.subscribe_to_shutdown_channel());
319
320 timeout(timeout_dur, async move {
321 tokio::select! {
322 msg = shutdown_channel_rx.recv() =>
323 {
324 match msg {
325 Ok(Some(run_with_range)) => Some(run_with_range),
326 Ok(None) => None,
327 Err(e) => {
328 error!("failed recv from sui-node shutdown channel: {}", e);
329 None
330 },
331 }
332 },
333 }
334 })
335 .await
336 .expect("Timed out waiting for cluster to hit target epoch and recv shutdown signal from sui-node")
337 }
338
339 pub async fn wait_for_protocol_version(
340 &self,
341 target_protocol_version: ProtocolVersion,
342 ) -> SuiSystemState {
343 self.wait_for_protocol_version_with_timeout(
344 target_protocol_version,
345 Duration::from_secs(60),
346 )
347 .await
348 }
349
350 pub async fn wait_for_protocol_version_with_timeout(
351 &self,
352 target_protocol_version: ProtocolVersion,
353 timeout_dur: Duration,
354 ) -> SuiSystemState {
355 timeout(timeout_dur, async move {
356 loop {
357 let system_state = self.wait_for_epoch(None).await;
358 if system_state.protocol_version() >= target_protocol_version.as_u64() {
359 return system_state;
360 }
361 }
362 })
363 .await
364 .expect("Timed out waiting for cluster to target protocol version")
365 }
366
367 pub async fn trigger_reconfiguration(&self) {
370 info!("Starting reconfiguration");
371 let start = Instant::now();
372
373 let cur_committee = self
375 .fullnode_handle
376 .sui_node
377 .with(|node| node.state().clone_committee_for_testing());
378 let mut cur_stake = 0;
379 for node in self.swarm.active_validators() {
380 node.get_node_handle()
381 .unwrap()
382 .with_async(|node| async {
383 node.close_epoch_for_testing().await.unwrap_or_else(|_| {
384 fatal!(
385 "Failed to close epoch for validator {:?}",
386 node.state().name
387 );
388 });
389 cur_stake += cur_committee.weight(&node.state().name);
390 })
391 .await;
392 if cur_stake >= cur_committee.quorum_threshold() {
393 break;
394 }
395 }
396 info!("close_epoch complete after {:?}", start.elapsed());
397
398 self.wait_for_epoch(Some(cur_committee.epoch + 1)).await;
399 self.wait_for_epoch_all_nodes(cur_committee.epoch + 1).await;
400
401 info!("reconfiguration complete after {:?}", start.elapsed());
402 }
403
404 pub async fn wait_for_epoch(&self, target_epoch: Option<EpochId>) -> SuiSystemState {
411 self.wait_for_epoch_with_timeout(target_epoch, Duration::from_secs(60))
412 .await
413 }
414
415 pub async fn wait_for_epoch_on_node(
416 &self,
417 handle: &SuiNodeHandle,
418 target_epoch: Option<EpochId>,
419 timeout_dur: Duration,
420 ) -> SuiSystemState {
421 let mut epoch_rx = handle.with(|node| node.subscribe_to_epoch_change());
422
423 let mut state = None;
424 timeout(timeout_dur, async {
425 let epoch = handle.with(|node| node.state().epoch_store_for_testing().epoch());
426 if Some(epoch) == target_epoch {
427 return handle.with(|node| node.state().get_sui_system_state_object_for_testing().unwrap());
428 }
429 while let Ok(system_state) = epoch_rx.recv().await {
430 info!("received epoch {}", system_state.epoch());
431 state = Some(system_state.clone());
432 match target_epoch {
433 Some(target_epoch) if system_state.epoch() >= target_epoch => {
434 return system_state;
435 }
436 None => {
437 return system_state;
438 }
439 _ => (),
440 }
441 }
442 unreachable!("Broken reconfig channel");
443 })
444 .await
445 .unwrap_or_else(|_| {
446 error!("Timed out waiting for cluster to reach epoch {target_epoch:?}");
447 if let Some(state) = state {
448 panic!("Timed out waiting for cluster to reach epoch {target_epoch:?}. Current epoch: {}", state.epoch());
449 }
450 panic!("Timed out waiting for cluster to target epoch {target_epoch:?}")
451 })
452 }
453
454 pub async fn wait_for_epoch_with_timeout(
455 &self,
456 target_epoch: Option<EpochId>,
457 timeout_dur: Duration,
458 ) -> SuiSystemState {
459 self.wait_for_epoch_on_node(&self.fullnode_handle.sui_node, target_epoch, timeout_dur)
460 .await
461 }
462
463 pub async fn wait_for_epoch_all_nodes(&self, target_epoch: EpochId) {
464 let handles: Vec<_> = self
465 .swarm
466 .all_nodes()
467 .map(|node| node.get_node_handle().unwrap())
468 .collect();
469 let tasks: Vec<_> = handles
470 .iter()
471 .map(|handle| {
472 handle.with_async(|node| async {
473 let mut retries = 0;
474 loop {
475 let epoch = node.state().epoch_store_for_testing().epoch();
476 if epoch == target_epoch {
477 if let Some(agg) = node.clone_authority_aggregator() {
478 if agg.committee.epoch() == target_epoch {
480 break;
481 }
482 } else {
483 break;
485 }
486 }
487 tokio::time::sleep(Duration::from_secs(1)).await;
488 retries += 1;
489 if retries % 5 == 0 {
490 tracing::warn!(validator=?node.state().name.concise(), "Waiting for {:?} seconds to reach epoch {:?}. Currently at epoch {:?}", retries, target_epoch, epoch);
491 }
492 }
493 })
494 })
495 .collect();
496
497 timeout(Duration::from_secs(40), join_all(tasks))
498 .await
499 .expect("timed out waiting for reconfiguration to complete");
500 }
501
502 pub async fn wait_for_next_epoch_all_nodes(&self) {
507 let handles: Vec<_> = self
508 .swarm
509 .all_nodes()
510 .map(|node| node.get_node_handle().unwrap())
511 .collect();
512 let tasks: Vec<_> = handles
513 .iter()
514 .map(|handle| {
515 handle.with_async(|node| async {
516 let start_epoch = node.state().epoch_store_for_testing().epoch();
517 let mut retries = 0;
518 loop {
519 let epoch = node.state().epoch_store_for_testing().epoch();
520 if epoch > start_epoch {
521 if let Some(agg) = node.clone_authority_aggregator() {
522 if agg.committee.epoch() > start_epoch {
524 break;
525 }
526 } else {
527 break;
528 }
529 }
530 tokio::time::sleep(Duration::from_secs(1)).await;
531 retries += 1;
532 if retries % 5 == 0 {
533 tracing::warn!(validator=?node.state().name.concise(), "Waiting {:?}s for an epoch beyond {:?}; currently at {:?}", retries, start_epoch, epoch);
534 }
535 }
536 })
537 })
538 .collect();
539
540 timeout(Duration::from_secs(40), join_all(tasks))
541 .await
542 .expect("timed out waiting for all nodes to advance an epoch");
543 }
544
545 pub fn subscribe_to_epoch_change(&self) -> broadcast::Receiver<SuiSystemState> {
546 self.fullnode_handle
548 .sui_node
549 .with(|node| node.subscribe_to_epoch_change())
550 }
551
552 pub async fn update_validator_supported_versions(
557 &self,
558 new_supported_versions: SupportedProtocolVersions,
559 ) {
560 for authority in self.get_validator_pubkeys() {
561 self.stop_node(&authority);
562 tokio::time::sleep(Duration::from_millis(1000)).await;
563 self.swarm
564 .node(&authority)
565 .unwrap()
566 .config()
567 .supported_protocol_versions = Some(new_supported_versions);
568 self.start_node(&authority).await;
569 info!("Restarted validator {}", authority);
570 }
571 }
572
573 pub async fn wait_for_all_nodes_upgrade_to(&self, protocol_version: u64) {
575 for h in self.all_node_handles() {
576 h.with_async(|node| async {
577 while node
578 .state()
579 .epoch_store_for_testing()
580 .epoch_start_state()
581 .protocol_version()
582 .as_u64()
583 != protocol_version
584 {
585 tokio::time::sleep(Duration::from_secs(1)).await;
586 }
587 })
588 .await;
589 }
590 }
591
592 pub async fn wait_for_authenticator_state_update(&self) {
594 self.wait_for_authenticator_state_update_for_providers(&[])
595 .await;
596 }
597
598 pub async fn wait_for_authenticator_state_update_for_providers(&self, jwk_ids: &[JwkId]) {
600 timeout(Duration::from_secs(60), async {
601 loop {
602 let active: Vec<JwkId> = self.fullnode_handle.sui_node.with(|node| {
603 get_authenticator_state(node.state().get_object_store())
604 .ok()
605 .flatten()
606 .map(|state| {
607 state
608 .active_jwks
609 .iter()
610 .map(|active| active.jwk_id.clone())
611 .collect()
612 })
613 .unwrap_or_default()
614 });
615 let ready = if jwk_ids.is_empty() {
616 !active.is_empty()
617 } else {
618 jwk_ids.iter().all(|id| active.contains(id))
619 };
620 if ready {
621 return;
622 }
623 tokio::time::sleep(Duration::from_millis(500)).await;
624 }
625 })
626 .await
627 .expect("Timed out waiting for authenticator state update");
628 }
629
630 pub fn highest_protocol_version(&self) -> ProtocolVersion {
632 self.all_node_handles()
633 .into_iter()
634 .map(|h| {
635 h.with(|node| {
636 node.state()
637 .epoch_store_for_testing()
638 .epoch_start_state()
639 .protocol_version()
640 })
641 })
642 .max()
643 .expect("at least one node must be up to get highest protocol version")
644 }
645
646 pub async fn test_transaction_builder(&self) -> TestTransactionBuilder {
647 let (sender, gas) = self.wallet.get_one_gas_object().await.unwrap().unwrap();
648 self.test_transaction_builder_with_gas_object(sender, gas)
649 .await
650 }
651
652 pub async fn test_transaction_builder_with_sender(
653 &self,
654 sender: SuiAddress,
655 ) -> TestTransactionBuilder {
656 let gas = self
657 .wallet
658 .get_one_gas_object_owned_by_address(sender)
659 .await
660 .unwrap()
661 .unwrap();
662 self.test_transaction_builder_with_gas_object(sender, gas)
663 .await
664 }
665
666 pub async fn test_transaction_builder_with_gas_object(
667 &self,
668 sender: SuiAddress,
669 gas: ObjectRef,
670 ) -> TestTransactionBuilder {
671 let rgp = self.get_reference_gas_price().await;
672 TestTransactionBuilder::new(sender, gas, rgp)
673 }
674
675 pub async fn sign_transaction(&self, tx_data: &TransactionData) -> Transaction {
676 self.wallet.sign_transaction(tx_data).await
677 }
678
679 pub async fn sign_and_execute_transaction(
680 &self,
681 tx_data: &TransactionData,
682 ) -> ExecutedTransaction {
683 let tx = self.wallet.sign_transaction(tx_data).await;
684 self.execute_transaction(tx).await
685 }
686
687 pub async fn sign_and_execute_transaction_directly(
689 &self,
690 tx_data: &TransactionData,
691 ) -> SuiResult<(TransactionDigest, TransactionEffects)> {
692 let mut res = self
693 .sign_and_execute_txns_in_soft_bundle(std::slice::from_ref(tx_data))
694 .await?;
695 assert_eq!(res.len(), 1);
696 Ok(res.pop().unwrap())
697 }
698
699 pub async fn execute_transaction_directly(
701 &self,
702 tx: &Transaction,
703 ) -> SuiResult<(TransactionDigest, TransactionEffects)> {
704 let mut res = self
705 .execute_signed_txns_in_soft_bundle(std::slice::from_ref(tx))
706 .await?;
707 assert_eq!(res.len(), 1);
708 Ok(res.pop().unwrap())
709 }
710
711 pub async fn sign_and_execute_txns_in_soft_bundle(
720 &self,
721 txns: &[TransactionData],
722 ) -> SuiResult<Vec<(TransactionDigest, TransactionEffects)>> {
723 let signed_txs: Vec<Transaction> =
725 futures::future::join_all(txns.iter().map(|tx| self.wallet.sign_transaction(tx))).await;
726
727 self.execute_signed_txns_in_soft_bundle(&signed_txs).await
728 }
729
730 pub async fn execute_signed_txns_in_soft_bundle(
731 &self,
732 signed_txs: &[Transaction],
733 ) -> SuiResult<Vec<(TransactionDigest, TransactionEffects)>> {
734 let digests: Vec<_> = signed_txs.iter().map(|tx| *tx.digest()).collect();
735
736 let request = RawSubmitTxRequest {
737 transactions: signed_txs
738 .iter()
739 .map(|tx| bcs::to_bytes(tx).unwrap().into())
740 .collect(),
741 submit_type: SubmitTxType::SoftBundle.into(),
742 };
743
744 let agg = self.authority_aggregator();
745 let clients = &agg.authority_clients;
746 let index = rand::thread_rng().gen_range(0..clients.len());
748 let (_, safe_client) = clients.iter().nth(index).unwrap();
749 let mut validator_client = safe_client
750 .authority_client()
751 .get_client_for_testing()
752 .unwrap();
753
754 let result = validator_client
755 .submit_transaction(request.into_request())
756 .await
757 .map(tonic::Response::into_inner)?;
758 assert_eq!(result.results.len(), signed_txs.len());
759
760 let mut executed_results = vec![None; signed_txs.len()];
761 let mut submitted_positions = Vec::new();
762 for (index, raw_result) in result.results.into_iter().enumerate() {
763 let submit_result: SubmitTxResult = raw_result.try_into()?;
764 match submit_result {
765 SubmitTxResult::Executed { details, .. } => {
766 let data = details.ok_or_else(|| SuiErrorKind::GenericAuthorityError {
767 error: "Expected execution details".to_string(),
768 })?;
769 executed_results[index] = Some((digests[index], data.effects));
770 }
771 SubmitTxResult::Rejected { error } => {
772 return Err(error);
773 }
774 SubmitTxResult::Submitted { consensus_position } => {
775 submitted_positions.push((index, consensus_position));
776 }
777 }
778 }
779
780 let wait_futures: Vec<_> = submitted_positions
781 .iter()
782 .map(|(index, position)| {
783 let request = WaitForEffectsRequest {
784 transaction_digest: Some(digests[*index]),
785 consensus_position: Some(*position),
786 include_details: true,
787 ping_type: None,
788 };
789 safe_client.wait_for_effects(request, None)
790 })
791 .collect();
792
793 let wait_responses = join_all(wait_futures).await;
794 for ((index, _), response) in submitted_positions.into_iter().zip_debug_eq(wait_responses) {
795 match response? {
796 WaitForEffectsResponse::Executed { details, .. } => {
797 let data = details.ok_or_else(|| SuiErrorKind::GenericAuthorityError {
798 error: "Expected execution details".to_string(),
799 })?;
800 executed_results[index] = Some((digests[index], data.effects));
801 }
802 WaitForEffectsResponse::Rejected { error } => {
803 return Err(error.unwrap_or_else(|| {
804 SuiErrorKind::GenericAuthorityError {
805 error: "Transaction was rejected".to_string(),
806 }
807 .into()
808 }));
809 }
810 WaitForEffectsResponse::Expired { .. } => {
811 return Err(SuiErrorKind::TransactionExpired.into());
812 }
813 }
814 }
815
816 self.wait_for_tx_settlement(&digests).await;
824
825 executed_results
826 .into_iter()
827 .map(|result| {
828 result.ok_or_else(|| {
829 SuiErrorKind::GenericAuthorityError {
830 error: "Missing execution result".to_string(),
831 }
832 .into()
833 })
834 })
835 .collect()
836 }
837
838 pub async fn execute_soft_bundle_with_conflicts(
844 &self,
845 signed_txs: &[Transaction],
846 ) -> SuiResult<Vec<(TransactionDigest, WaitForEffectsResponse)>> {
847 let digests: Vec<_> = signed_txs.iter().map(|tx| *tx.digest()).collect();
848
849 let request = RawSubmitTxRequest {
850 transactions: signed_txs
851 .iter()
852 .map(|tx| bcs::to_bytes(tx).unwrap().into())
853 .collect(),
854 submit_type: SubmitTxType::SoftBundle.into(),
855 };
856
857 let authority_aggregator = self.authority_aggregator();
858 let (_, safe_client) = authority_aggregator
859 .authority_clients
860 .iter()
861 .next()
862 .unwrap();
863 let mut validator_client = safe_client
864 .authority_client()
865 .get_client_for_testing()
866 .unwrap();
867
868 let result = validator_client
869 .submit_transaction(request.into_request())
870 .await
871 .map(tonic::Response::into_inner)?;
872 assert_eq!(result.results.len(), signed_txs.len());
873
874 let mut consensus_positions = Vec::new();
876 for (i, raw_result) in result.results.iter().enumerate() {
877 let submit_result: SubmitTxResult = raw_result.clone().try_into()?;
878 match submit_result {
879 SubmitTxResult::Submitted { consensus_position } => {
880 consensus_positions.push(consensus_position);
881 }
882 SubmitTxResult::Executed { .. } => {
883 panic!(
884 "Transaction {} was already executed during submission",
885 i + 1
886 );
887 }
888 SubmitTxResult::Rejected { error } => {
889 return Err(error);
890 }
891 }
892 }
893
894 let wait_futures: Vec<_> = digests
896 .iter()
897 .zip_debug_eq(consensus_positions.iter())
898 .map(|(digest, position)| {
899 let request = WaitForEffectsRequest {
900 transaction_digest: Some(*digest),
901 consensus_position: Some(*position),
902 include_details: false,
903 ping_type: None,
904 };
905 safe_client.wait_for_effects(request, None)
906 })
907 .collect();
908
909 let responses = futures::future::join_all(wait_futures).await;
910
911 let results: SuiResult<Vec<_>> = digests
912 .into_iter()
913 .zip_debug_eq(responses)
914 .map(|(digest, response)| Ok((digest, response?)))
915 .collect();
916
917 results
918 }
919
920 pub async fn wait_for_tx_settlement(&self, digests: &[TransactionDigest]) {
921 Self::wait_for_tx_settlement_on_handles(
922 std::slice::from_ref(&self.fullnode_handle.sui_node),
923 digests,
924 )
925 .await;
926 }
927
928 pub async fn wait_for_tx_settlement_all_nodes(&self, digests: &[TransactionDigest]) {
935 let handles = self.all_node_handles();
936 Self::wait_for_tx_settlement_on_handles(&handles, digests).await;
937 }
938
939 async fn wait_for_tx_settlement_on_handles(
943 handles: &[SuiNodeHandle],
944 digests: &[TransactionDigest],
945 ) {
946 let waits = handles.iter().map(|handle| async move {
947 let max_checkpoint_seq = handle
948 .with_async(|node| async move {
949 let state = node.state();
950 let checkpoint_seqs = state
952 .epoch_store_for_testing()
953 .transactions_executed_in_checkpoint_notify(digests.to_vec())
954 .await;
955
956 let max_checkpoint_seq = checkpoint_seqs.into_iter().max().unwrap();
958 state
959 .checkpoint_store
960 .notify_read_executed_checkpoint(max_checkpoint_seq)
961 .await;
962 max_checkpoint_seq
963 })
964 .await;
965
966 Self::wait_for_rpc_index_on_handle(handle, max_checkpoint_seq, false).await;
972 });
973 join_all(waits).await;
974 }
975
976 async fn wait_for_rpc_index_on_handle(
984 handle: &SuiNodeHandle,
985 checkpoint: u64,
986 wait_for_history: bool,
987 ) {
988 if handle.with(|node| node.embedded_rpc_store().is_none()) {
990 return;
991 }
992 let deadline = Instant::now() + Duration::from_secs(60);
993 loop {
994 let (live_committed, history_committed) = handle.with(|node| {
995 node.embedded_rpc_store()
996 .map(|embedded| {
997 (
998 embedded.live_committed_checkpoint(),
999 embedded.history_committed_checkpoint(),
1000 )
1001 })
1002 .unwrap_or((None, None))
1003 });
1004 if live_committed.is_some_and(|c| c >= checkpoint)
1005 && (!wait_for_history || history_committed.is_some_and(|c| c >= checkpoint))
1006 {
1007 return;
1008 }
1009 assert!(
1010 Instant::now() < deadline,
1011 "timed out waiting for the embedded rpc-store to index checkpoint \
1012 {checkpoint} (live committed = {live_committed:?}, \
1013 history committed = {history_committed:?})",
1014 );
1015 tokio::time::sleep(Duration::from_millis(50)).await;
1016 }
1017 }
1018
1019 pub async fn wait_for_rpc_index_ready(&self) {
1024 let handle = &self.fullnode_handle.sui_node;
1025 let highest_executed = handle.with(|node| {
1026 node.state()
1027 .get_checkpoint_store()
1028 .get_highest_executed_checkpoint_seq_number()
1029 .expect("db error")
1030 .unwrap_or(0)
1031 });
1032 Self::wait_for_rpc_index_on_handle(handle, highest_executed, true).await;
1033 }
1034
1035 pub async fn execute_transaction(&self, tx: Transaction) -> ExecutedTransaction {
1040 self.wallet.execute_transaction_must_succeed(tx).await
1041 }
1042
1043 pub async fn execute_transaction_return_raw_effects(
1049 &self,
1050 tx: Transaction,
1051 ) -> anyhow::Result<(TransactionEffects, TransactionEvents)> {
1052 let digest = *tx.digest();
1053 let results = self.submit_and_execute(tx, None).await?;
1054 self.wait_for_tx_settlement(&[digest]).await;
1055 Ok(results)
1056 }
1057
1058 pub fn authority_aggregator(&self) -> Arc<AuthorityAggregator<NetworkAuthorityClient>> {
1059 self.fullnode_handle
1060 .sui_node
1061 .with(|node| node.clone_authority_aggregator().unwrap())
1062 }
1063
1064 pub async fn submit_and_execute(
1067 &self,
1068 tx: Transaction,
1069 client_addr: Option<SocketAddr>,
1070 ) -> anyhow::Result<(TransactionEffects, TransactionEvents)> {
1071 let transaction_driver = self.fullnode_handle.sui_node.with(|node| {
1072 node.transaction_orchestrator()
1073 .expect("fullnode must have a transaction orchestrator")
1074 .transaction_driver()
1075 .clone()
1076 });
1077 let response = transaction_driver
1078 .drive_transaction(
1079 SubmitTxRequest::new_transaction(tx),
1080 SubmitTransactionOptions {
1081 forwarded_client_addr: client_addr,
1082 ..Default::default()
1083 },
1084 Some(TRANSACTION_FINALITY_TIMEOUT),
1085 )
1086 .await?;
1087
1088 Ok((
1089 response.effects.effects,
1090 response.events.unwrap_or_default(),
1091 ))
1092 }
1093
1094 pub async fn fund_address_and_return_gas(
1098 &self,
1099 rgp: u64,
1100 amount: Option<u64>,
1101 funding_address: SuiAddress,
1102 ) -> ObjectRef {
1103 let context = &self.wallet;
1104 let (sender, gas) = context.get_one_gas_object().await.unwrap().unwrap();
1105 let tx = context
1106 .sign_transaction(
1107 &TestTransactionBuilder::new(sender, gas, rgp)
1108 .transfer_sui(amount, funding_address)
1109 .build(),
1110 )
1111 .await;
1112 context.execute_transaction_must_succeed(tx).await;
1113
1114 context
1115 .get_one_gas_object_owned_by_address(funding_address)
1116 .await
1117 .unwrap()
1118 .unwrap()
1119 }
1120
1121 pub async fn transfer_sui_must_exceed(
1122 &self,
1123 sender: SuiAddress,
1124 receiver: SuiAddress,
1125 amount: u64,
1126 ) -> ObjectID {
1127 let tx = self
1128 .test_transaction_builder_with_sender(sender)
1129 .await
1130 .transfer_sui(Some(amount), receiver)
1131 .build();
1132 let effects = self.sign_and_execute_transaction(&tx).await.effects;
1133 assert!(effects.status().is_ok());
1134 effects.created().first().unwrap().0.0
1135 }
1136
1137 #[cfg(msim)]
1138 pub fn set_safe_mode_expected(&self, value: bool) {
1139 for n in self.all_node_handles() {
1140 n.with(|node| node.set_safe_mode_expected(value));
1141 }
1142 }
1143}
1144
1145pub struct RandomNodeRestarter {
1146 test_cluster: Arc<TestCluster>,
1147
1148 kill_interval: Uniform<Duration>,
1150 restart_delay: Uniform<Duration>,
1152
1153 task_handle: Mutex<Option<JoinHandle<()>>>,
1154}
1155
1156impl RandomNodeRestarter {
1157 fn new(test_cluster: Arc<TestCluster>) -> Self {
1158 Self {
1159 test_cluster,
1160 kill_interval: Uniform::new(Duration::from_secs(10), Duration::from_secs(11)),
1161 restart_delay: Uniform::new(Duration::from_secs(1), Duration::from_secs(2)),
1162 task_handle: Default::default(),
1163 }
1164 }
1165
1166 pub fn with_kill_interval_secs(mut self, a: u64, b: u64) -> Self {
1167 self.kill_interval = Uniform::new(Duration::from_secs(a), Duration::from_secs(b));
1168 self
1169 }
1170
1171 pub fn with_restart_delay_secs(mut self, a: u64, b: u64) -> Self {
1172 self.restart_delay = Uniform::new(Duration::from_secs(a), Duration::from_secs(b));
1173 self
1174 }
1175
1176 pub fn run(&self) {
1177 let test_cluster = self.test_cluster.clone();
1178 let kill_interval = self.kill_interval;
1179 let restart_delay = self.restart_delay;
1180 let validators = self.test_cluster.get_validator_pubkeys();
1181 let mut task_handle = self.task_handle.lock().unwrap();
1182 assert!(task_handle.is_none());
1183 task_handle.replace(tokio::task::spawn(async move {
1184 loop {
1185 let delay = kill_interval.sample(&mut OsRng);
1186 info!("Sleeping {delay:?} before killing a validator");
1187 sleep(delay).await;
1188
1189 let validator = validators.choose(&mut OsRng).unwrap();
1190 info!("Killing validator {:?}", validator.concise());
1191 test_cluster.stop_node(validator);
1192
1193 let delay = restart_delay.sample(&mut OsRng);
1194 info!("Sleeping {delay:?} before restarting");
1195 sleep(delay).await;
1196 info!("Starting validator {:?}", validator.concise());
1197 test_cluster.start_node(validator).await;
1198 }
1199 }));
1200 }
1201}
1202
1203impl Drop for RandomNodeRestarter {
1204 fn drop(&mut self) {
1205 if let Some(handle) = self.task_handle.lock().unwrap().take() {
1206 handle.abort();
1207 }
1208 }
1209}
1210
1211pub struct TestClusterBuilder {
1212 genesis_config: Option<GenesisConfig>,
1213 network_config: Option<NetworkConfig>,
1214 additional_objects: Vec<Object>,
1215 num_validators: Option<usize>,
1216 validators: Option<Vec<ValidatorGenesisConfig>>,
1217 fullnode_rpc_port: Option<u16>,
1218 enable_fullnode_events: bool,
1219 disable_fullnode_pruning: bool,
1220 validator_supported_protocol_versions_config: ProtocolVersionsConfig,
1221 fullnode_supported_protocol_versions_config: Option<ProtocolVersionsConfig>,
1223 db_checkpoint_config_validators: DBCheckpointConfig,
1224 db_checkpoint_config_fullnodes: DBCheckpointConfig,
1225 num_unpruned_validators: Option<usize>,
1226 jwk_fetch_interval: Option<Duration>,
1227 config_dir: Option<PathBuf>,
1228 default_jwks: bool,
1229 authority_overload_config: Option<AuthorityOverloadConfig>,
1230 consensus_transaction_pool_config: Option<ConsensusTransactionPoolConfig>,
1231 execution_cache_config: Option<ExecutionCacheConfig>,
1232 data_ingestion_dir: Option<PathBuf>,
1233 fullnode_run_with_range: Option<RunWithRange>,
1234 fullnode_policy_config: Option<PolicyConfig>,
1235 fullnode_fw_config: Option<RemoteFirewallConfig>,
1236
1237 validator_global_state_hash_v2_enabled_config: GlobalStateHashV2EnabledConfig,
1238 validator_funds_withdraw_scheduler_type_config: FundsWithdrawSchedulerTypeConfig,
1239
1240 rpc_config: Option<sui_config::RpcConfig>,
1241
1242 chain_override: Option<Chain>,
1243
1244 execution_time_observer_config: Option<sui_config::node::ExecutionTimeObserverConfig>,
1245
1246 validator_observer_config: Option<ValidatorObserverConfigCallback>,
1247
1248 observer_fullnode: bool,
1249
1250 state_sync_config: Option<sui_config::p2p::StateSyncConfig>,
1251
1252 peer_deny_sync_config_callback:
1253 Option<sui_swarm_config::network_config_builder::PeerDenySyncConfigCallback>,
1254
1255 #[cfg(msim)]
1256 inject_synthetic_execution_time: bool,
1257}
1258
1259impl TestClusterBuilder {
1260 pub fn new() -> Self {
1261 TestClusterBuilder {
1262 genesis_config: None,
1263 network_config: None,
1264 chain_override: None,
1265 additional_objects: vec![],
1266 fullnode_rpc_port: None,
1267 num_validators: None,
1268 validators: None,
1269 enable_fullnode_events: false,
1270 disable_fullnode_pruning: false,
1271 validator_supported_protocol_versions_config: ProtocolVersionsConfig::Default,
1272 fullnode_supported_protocol_versions_config: None,
1273 db_checkpoint_config_validators: DBCheckpointConfig::default(),
1274 db_checkpoint_config_fullnodes: DBCheckpointConfig::default(),
1275 num_unpruned_validators: None,
1276 jwk_fetch_interval: None,
1277 config_dir: None,
1278 default_jwks: false,
1279 authority_overload_config: None,
1280 consensus_transaction_pool_config: None,
1281 execution_cache_config: None,
1282 data_ingestion_dir: None,
1283 fullnode_run_with_range: None,
1284 fullnode_policy_config: None,
1285 fullnode_fw_config: None,
1286 validator_global_state_hash_v2_enabled_config: GlobalStateHashV2EnabledConfig::Global(
1287 true,
1288 ),
1289 validator_funds_withdraw_scheduler_type_config:
1290 FundsWithdrawSchedulerTypeConfig::PerValidator(Arc::new(|idx| {
1291 if idx % 2 == 0 {
1292 FundsWithdrawSchedulerType::Eager
1293 } else {
1294 FundsWithdrawSchedulerType::Naive
1295 }
1296 })),
1297 rpc_config: None,
1298 execution_time_observer_config: None,
1299 validator_observer_config: None,
1300 observer_fullnode: false,
1301 state_sync_config: None,
1302 peer_deny_sync_config_callback: None,
1303 #[cfg(msim)]
1304 inject_synthetic_execution_time: false,
1305 }
1306 }
1307
1308 pub fn with_state_sync_config(mut self, config: sui_config::p2p::StateSyncConfig) -> Self {
1309 self.state_sync_config = Some(config);
1310 self
1311 }
1312
1313 pub fn with_peer_deny_sync_config_per_validator(
1318 mut self,
1319 f: sui_swarm_config::network_config_builder::PeerDenySyncConfigCallback,
1320 ) -> Self {
1321 self.peer_deny_sync_config_callback = Some(f);
1322 self
1323 }
1324
1325 pub fn with_execution_time_observer_config(
1326 mut self,
1327 config: sui_config::node::ExecutionTimeObserverConfig,
1328 ) -> Self {
1329 self.execution_time_observer_config = Some(config);
1330 self
1331 }
1332
1333 pub fn with_validator_observer_config(mut self, c: ValidatorObserverConfigCallback) -> Self {
1334 self.validator_observer_config = Some(c);
1335 self
1336 }
1337
1338 pub fn with_observer_fullnode(mut self) -> Self {
1349 self.observer_fullnode = true;
1350 self
1351 }
1352
1353 pub fn with_fullnode_run_with_range(mut self, run_with_range: Option<RunWithRange>) -> Self {
1354 if let Some(run_with_range) = run_with_range {
1355 self.fullnode_run_with_range = Some(run_with_range);
1356 }
1357 self
1358 }
1359
1360 pub fn with_fullnode_policy_config(mut self, config: Option<PolicyConfig>) -> Self {
1361 self.fullnode_policy_config = config;
1362 self
1363 }
1364
1365 pub fn with_fullnode_fw_config(mut self, config: Option<RemoteFirewallConfig>) -> Self {
1366 self.fullnode_fw_config = config;
1367 self
1368 }
1369
1370 pub fn with_fullnode_rpc_port(mut self, rpc_port: u16) -> Self {
1371 self.fullnode_rpc_port = Some(rpc_port);
1372 self
1373 }
1374
1375 pub fn set_genesis_config(mut self, genesis_config: GenesisConfig) -> Self {
1376 assert!(self.genesis_config.is_none() && self.network_config.is_none());
1377 self.genesis_config = Some(genesis_config);
1378 self
1379 }
1380
1381 pub fn set_network_config(mut self, network_config: NetworkConfig) -> Self {
1382 assert!(self.genesis_config.is_none() && self.network_config.is_none());
1383 self.network_config = Some(network_config);
1384 self
1385 }
1386
1387 pub fn with_objects<I: IntoIterator<Item = Object>>(mut self, objects: I) -> Self {
1388 self.additional_objects.extend(objects);
1389 self
1390 }
1391
1392 pub fn with_num_validators(mut self, num: usize) -> Self {
1395 self.num_validators = Some(num);
1396 self
1397 }
1398
1399 pub fn with_validators(mut self, validators: Vec<ValidatorGenesisConfig>) -> Self {
1401 self.validators = Some(validators);
1402 self
1403 }
1404
1405 pub fn enable_fullnode_events(mut self) -> Self {
1406 self.enable_fullnode_events = true;
1407 self
1408 }
1409
1410 pub fn disable_fullnode_pruning(mut self) -> Self {
1411 self.disable_fullnode_pruning = true;
1412 self
1413 }
1414
1415 pub fn with_enable_db_checkpoints_validators(mut self) -> Self {
1416 self.db_checkpoint_config_validators = DBCheckpointConfig {
1417 perform_db_checkpoints_at_epoch_end: true,
1418 checkpoint_path: None,
1419 object_store_config: None,
1420 perform_index_db_checkpoints_at_epoch_end: None,
1421 prune_and_compact_before_upload: None,
1422 };
1423 self
1424 }
1425
1426 pub fn with_enable_db_checkpoints_fullnodes(mut self) -> Self {
1427 self.db_checkpoint_config_fullnodes = DBCheckpointConfig {
1428 perform_db_checkpoints_at_epoch_end: true,
1429 checkpoint_path: None,
1430 object_store_config: None,
1431 perform_index_db_checkpoints_at_epoch_end: None,
1432 prune_and_compact_before_upload: Some(true),
1433 };
1434 self
1435 }
1436
1437 pub fn with_epoch_duration_ms(mut self, epoch_duration_ms: u64) -> Self {
1438 assert!(
1439 epoch_duration_ms >= 10000,
1440 "Epoch duration must be at least 10s (10000ms) to avoid flaky tests. Got {epoch_duration_ms}ms."
1441 );
1442 self.get_or_init_genesis_config()
1443 .parameters
1444 .epoch_duration_ms = epoch_duration_ms;
1445 self
1446 }
1447
1448 pub fn with_stake_subsidy_start_epoch(mut self, stake_subsidy_start_epoch: u64) -> Self {
1449 self.get_or_init_genesis_config()
1450 .parameters
1451 .stake_subsidy_start_epoch = stake_subsidy_start_epoch;
1452 self
1453 }
1454
1455 pub fn with_supported_protocol_versions(mut self, c: SupportedProtocolVersions) -> Self {
1456 self.validator_supported_protocol_versions_config = ProtocolVersionsConfig::Global(c);
1457 self
1458 }
1459
1460 pub fn with_jwk_fetch_interval(mut self, i: Duration) -> Self {
1461 self.jwk_fetch_interval = Some(i);
1462 self
1463 }
1464
1465 pub fn with_fullnode_supported_protocol_versions_config(
1466 mut self,
1467 c: SupportedProtocolVersions,
1468 ) -> Self {
1469 self.fullnode_supported_protocol_versions_config = Some(ProtocolVersionsConfig::Global(c));
1470 self
1471 }
1472
1473 pub fn with_protocol_version(mut self, v: ProtocolVersion) -> Self {
1474 self.get_or_init_genesis_config()
1475 .parameters
1476 .protocol_version = v;
1477 self
1478 }
1479
1480 pub fn with_supported_protocol_version_callback(
1481 mut self,
1482 func: SupportedProtocolVersionsCallback,
1483 ) -> Self {
1484 self.validator_supported_protocol_versions_config =
1485 ProtocolVersionsConfig::PerValidator(func);
1486 self
1487 }
1488
1489 pub fn with_global_state_hash_v2_enabled_callback(
1490 mut self,
1491 func: GlobalStateHashV2EnabledCallback,
1492 ) -> Self {
1493 self.validator_global_state_hash_v2_enabled_config =
1494 GlobalStateHashV2EnabledConfig::PerValidator(func);
1495 self
1496 }
1497
1498 pub fn with_validator_candidates(
1499 mut self,
1500 addresses: impl IntoIterator<Item = SuiAddress>,
1501 ) -> Self {
1502 self.get_or_init_genesis_config()
1503 .accounts
1504 .extend(addresses.into_iter().map(|address| AccountConfig {
1505 address: Some(address),
1506 gas_amounts: vec![DEFAULT_GAS_AMOUNT, DEFAULT_GAS_AMOUNT],
1507 }));
1508 self
1509 }
1510
1511 pub fn with_num_unpruned_validators(mut self, n: usize) -> Self {
1512 self.num_unpruned_validators = Some(n);
1513 self
1514 }
1515
1516 pub fn with_accounts(mut self, accounts: Vec<AccountConfig>) -> Self {
1517 self.get_or_init_genesis_config().accounts = accounts;
1518 self
1519 }
1520
1521 pub fn with_additional_accounts(mut self, accounts: Vec<AccountConfig>) -> Self {
1522 self.get_or_init_genesis_config().accounts.extend(accounts);
1523 self
1524 }
1525
1526 pub fn with_config_dir(mut self, config_dir: PathBuf) -> Self {
1527 self.config_dir = Some(config_dir);
1528 self
1529 }
1530
1531 pub fn with_default_jwks(mut self) -> Self {
1532 self.default_jwks = true;
1533 self
1534 }
1535
1536 pub fn with_authority_overload_config(mut self, config: AuthorityOverloadConfig) -> Self {
1537 assert!(self.network_config.is_none());
1538 self.authority_overload_config = Some(config);
1539 self
1540 }
1541
1542 pub fn with_consensus_transaction_pool_config(
1543 mut self,
1544 config: ConsensusTransactionPoolConfig,
1545 ) -> Self {
1546 assert!(self.network_config.is_none());
1547 self.consensus_transaction_pool_config = Some(config);
1548 self
1549 }
1550
1551 pub fn with_execution_cache_config(mut self, config: ExecutionCacheConfig) -> Self {
1552 assert!(self.network_config.is_none());
1553 self.execution_cache_config = Some(config);
1554 self
1555 }
1556
1557 pub fn with_data_ingestion_dir(mut self, path: PathBuf) -> Self {
1558 self.data_ingestion_dir = Some(path);
1559 self
1560 }
1561
1562 pub fn with_rpc_config(mut self, config: sui_config::RpcConfig) -> Self {
1563 self.rpc_config = Some(config);
1564 self
1565 }
1566
1567 pub fn with_chain_override(mut self, chain: Chain) -> Self {
1568 self.chain_override = Some(chain);
1569 self
1570 }
1571
1572 #[cfg(msim)]
1573 pub fn with_synthetic_execution_time_injection(mut self) -> Self {
1574 self.inject_synthetic_execution_time = true;
1575 self
1576 }
1577
1578 pub async fn build(mut self) -> TestCluster {
1579 #[cfg(msim)]
1583 if !self.default_jwks {
1584 sui_node::set_jwk_injector(Arc::new(|_authority, provider| {
1585 use fastcrypto_zkp::bn254::zk_login::{JWK, JwkId};
1586 use rand::Rng;
1587
1588 let id_num = rand::thread_rng().gen_range(1..=4);
1590 let key_num = rand::thread_rng().gen_range(1..=4);
1591
1592 let id = JwkId {
1593 iss: provider.get_config().iss,
1594 kid: format!("kid{}", id_num),
1595 };
1596
1597 let jwk = JWK {
1598 kty: "kty".to_string(),
1599 e: "e".to_string(),
1600 n: format!("n{}", key_num),
1601 alg: "alg".to_string(),
1602 };
1603
1604 Ok(vec![(id, jwk)])
1605 }));
1606 }
1607
1608 if self.observer_fullnode {
1609 if let Some(network_config) = &self.network_config {
1613 let observer_enabled = network_config
1616 .validator_configs()
1617 .first()
1618 .and_then(|c| c.consensus_config())
1619 .and_then(|c| c.parameters.as_ref())
1620 .and_then(|p| p.observer.server_port)
1621 .is_some();
1622 assert!(
1623 observer_enabled,
1624 "with_observer_fullnode() requires the first validator's observer server \
1625 to be enabled, but the network config passed to set_network_config() does \
1626 not enable it. Enable the observer server on the first validator when \
1627 building the network config."
1628 );
1629 } else if let Some(cb) = &self.validator_observer_config {
1630 assert!(
1631 cb(0).is_some(),
1632 "with_observer_fullnode() requires the validator observer config callback \
1633 to enable the observer server on the first validator (index 0)."
1634 );
1635 } else {
1636 self.validator_observer_config = Some(Arc::new(|idx| {
1637 (idx == 0).then(consensus_config::ObserverParameters::default)
1638 }));
1639 }
1640 }
1641
1642 let mut swarm = self.start_swarm().await.unwrap();
1643 let working_dir = swarm.dir().to_path_buf();
1644
1645 let fullnode = swarm.fullnodes().next().unwrap();
1646 let json_rpc_address = fullnode.config().json_rpc_address;
1647 let fullnode_handle =
1648 FullNodeHandle::new(fullnode.get_node_handle().unwrap(), json_rpc_address).await;
1649
1650 if self.observer_fullnode {
1651 Box::pin(async {
1657 let mut config = swarm
1658 .get_fullnode_config_builder()
1659 .with_observer_subscribed_to_validator(0)
1660 .build(&mut OsRng, swarm.config());
1661 config.run_with_range = None;
1663 swarm.spawn_new_node(config).await;
1667 })
1668 .await;
1669 }
1670
1671 let mut wallet_conf: SuiClientConfig =
1672 PersistedConfig::read(&working_dir.join(SUI_CLIENT_CONFIG)).unwrap();
1673 wallet_conf.envs.push(SuiEnv {
1674 alias: "localnet".to_string(),
1675 rpc: fullnode_handle.rpc_url.clone(),
1676 ws: None,
1677 basic_auth: None,
1678 chain_id: None,
1679 });
1680 wallet_conf.active_env = Some("localnet".to_string());
1681
1682 wallet_conf
1683 .persisted(&working_dir.join(SUI_CLIENT_CONFIG))
1684 .save()
1685 .unwrap();
1686
1687 let wallet_conf = swarm.dir().join(SUI_CLIENT_CONFIG);
1688 let wallet = WalletContext::new(&wallet_conf).unwrap();
1689
1690 let cluster = TestCluster {
1691 swarm,
1692 wallet,
1693 fullnode_handle,
1694 };
1695
1696 cluster.wait_for_rpc_index_ready().await;
1700
1701 cluster
1702 }
1703
1704 async fn start_swarm(&mut self) -> Result<Swarm, anyhow::Error> {
1706 let mut builder: SwarmBuilder = Swarm::builder()
1707 .with_objects(self.additional_objects.clone())
1708 .with_db_checkpoint_config(self.db_checkpoint_config_validators.clone())
1709 .with_supported_protocol_versions_config(
1710 self.validator_supported_protocol_versions_config.clone(),
1711 )
1712 .with_global_state_hash_v2_enabled_config(
1713 self.validator_global_state_hash_v2_enabled_config.clone(),
1714 )
1715 .with_funds_withdraw_scheduler_type_config(
1716 self.validator_funds_withdraw_scheduler_type_config.clone(),
1717 )
1718 .with_fullnode_count(1)
1719 .with_fullnode_supported_protocol_versions_config(
1720 self.fullnode_supported_protocol_versions_config
1721 .clone()
1722 .unwrap_or(self.validator_supported_protocol_versions_config.clone()),
1723 )
1724 .with_db_checkpoint_config(self.db_checkpoint_config_fullnodes.clone())
1725 .with_fullnode_run_with_range(self.fullnode_run_with_range)
1726 .with_fullnode_policy_config(self.fullnode_policy_config.clone())
1727 .with_fullnode_fw_config(self.fullnode_fw_config.clone());
1728
1729 if let Some(validators) = self.validators.take() {
1730 builder = builder.with_validators(validators);
1731 } else {
1732 builder = builder.committee_size(
1733 NonZeroUsize::new(self.num_validators.unwrap_or(NUM_VALIDATOR)).unwrap(),
1734 )
1735 };
1736
1737 if let Some(chain) = self.chain_override {
1738 builder = builder.with_chain_override(chain);
1739 }
1740
1741 if let Some(genesis_config) = self.genesis_config.take() {
1742 builder = builder.with_genesis_config(genesis_config);
1743 }
1744
1745 if let Some(network_config) = self.network_config.take() {
1746 builder = builder.with_network_config(network_config);
1747 }
1748
1749 if let Some(authority_overload_config) = self.authority_overload_config.take() {
1750 builder = builder.with_authority_overload_config(authority_overload_config);
1751 }
1752
1753 if let Some(config) = self.consensus_transaction_pool_config.take() {
1754 builder = builder.with_consensus_transaction_pool_config(config);
1755 }
1756
1757 if let Some(execution_cache_config) = self.execution_cache_config.take() {
1758 builder = builder.with_execution_cache_config(execution_cache_config);
1759 }
1760
1761 if let Some(fullnode_rpc_port) = self.fullnode_rpc_port {
1762 builder = builder.with_fullnode_rpc_port(fullnode_rpc_port);
1763 }
1764
1765 if let Some(rpc_config) = &self.rpc_config {
1766 builder = builder.with_fullnode_rpc_config(rpc_config.clone());
1767 }
1768 if let Some(num_unpruned_validators) = self.num_unpruned_validators {
1769 builder = builder.with_num_unpruned_validators(num_unpruned_validators);
1770 }
1771
1772 if let Some(jwk_fetch_interval) = self.jwk_fetch_interval {
1773 builder = builder.with_jwk_fetch_interval(jwk_fetch_interval);
1774 }
1775
1776 if let Some(config_dir) = self.config_dir.take() {
1777 builder = builder.dir(config_dir);
1778 }
1779
1780 if let Some(data_ingestion_dir) = self.data_ingestion_dir.take() {
1781 builder = builder.with_data_ingestion_dir(data_ingestion_dir);
1782 }
1783
1784 if let Some(state_sync_config) = self.state_sync_config.clone() {
1785 builder = builder.with_state_sync_config(state_sync_config);
1786 }
1787
1788 if let Some(cb) = self.peer_deny_sync_config_callback.clone() {
1789 builder = builder.with_peer_deny_sync_config_per_validator(cb);
1790 }
1791
1792 if self.disable_fullnode_pruning {
1793 builder = builder.with_disable_fullnode_pruning();
1794 }
1795
1796 if let Some(validator_observer_config) = self.validator_observer_config.take() {
1797 builder = builder.with_validator_observer_config(validator_observer_config);
1798 }
1799
1800 #[cfg(msim)]
1801 {
1802 if let Some(mut config) = self.execution_time_observer_config.clone() {
1803 if self.inject_synthetic_execution_time {
1804 config.inject_synthetic_execution_time = Some(true);
1805 }
1806 builder = builder.with_execution_time_observer_config(config);
1807 } else if self.inject_synthetic_execution_time {
1808 use sui_config::node::ExecutionTimeObserverConfig;
1809
1810 let mut config = ExecutionTimeObserverConfig::default();
1811 config.inject_synthetic_execution_time = Some(true);
1812 builder = builder.with_execution_time_observer_config(config);
1813 }
1814 }
1815
1816 let mut swarm = builder.build();
1817 swarm.launch().await?;
1818
1819 let dir = swarm.dir();
1820
1821 let network_path = dir.join(SUI_NETWORK_CONFIG);
1822 let wallet_path = dir.join(SUI_CLIENT_CONFIG);
1823 let keystore_path = dir.join(SUI_KEYSTORE_FILENAME);
1824
1825 swarm.config().save(network_path)?;
1826 let mut keystore = Keystore::from(FileBasedKeystore::load_or_create(&keystore_path)?);
1827 for key in &swarm.config().account_keys {
1828 keystore
1829 .import(None, SuiKeyPair::Ed25519(key.copy()))
1830 .await?;
1831 }
1832
1833 let active_address = keystore.addresses().first().cloned();
1834
1835 SuiClientConfig {
1837 keystore: Keystore::from(FileBasedKeystore::load_or_create(&keystore_path)?),
1838 external_keys: None,
1839 envs: Default::default(),
1840 active_address,
1841 active_env: Default::default(),
1842 }
1843 .save(wallet_path)?;
1844
1845 Ok(swarm)
1847 }
1848
1849 fn get_or_init_genesis_config(&mut self) -> &mut GenesisConfig {
1850 if self.genesis_config.is_none() {
1851 self.genesis_config = Some(GenesisConfig::for_local_testing());
1852 }
1853 self.genesis_config.as_mut().unwrap()
1854 }
1855}
1856
1857impl Default for TestClusterBuilder {
1858 fn default() -> Self {
1859 Self::new()
1860 }
1861}