Skip to main content

sui_swarm/memory/
swarm.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::Node;
5use anyhow::Result;
6use futures::future::try_join_all;
7use rand::rngs::OsRng;
8use std::collections::HashMap;
9use std::net::SocketAddr;
10use std::num::NonZeroUsize;
11use std::time::Duration;
12use std::{
13    ops,
14    path::{Path, PathBuf},
15};
16
17#[cfg(msim)]
18use sui_config::node::ExecutionTimeObserverConfig;
19use sui_config::node::{
20    AuthorityOverloadConfig, ConsensusTransactionPoolConfig, DBCheckpointConfig, RunWithRange,
21};
22use sui_config::{ExecutionCacheConfig, NodeConfig};
23use sui_macros::nondeterministic;
24use sui_node::SuiNodeHandle;
25use sui_protocol_config::{Chain, ProtocolVersion};
26use sui_swarm_config::genesis_config::{AccountConfig, GenesisConfig, ValidatorGenesisConfig};
27use sui_swarm_config::network_config::NetworkConfig;
28use sui_swarm_config::network_config_builder::{
29    CommitteeConfig, ConfigBuilder, FundsWithdrawSchedulerTypeConfig,
30    GlobalStateHashV2EnabledConfig, ProtocolVersionsConfig, SupportedProtocolVersionsCallback,
31    ValidatorObserverConfigCallback,
32};
33use sui_swarm_config::node_config_builder::FullnodeConfigBuilder;
34use sui_types::base_types::AuthorityName;
35use sui_types::object::Object;
36use sui_types::supported_protocol_versions::SupportedProtocolVersions;
37use tempfile::TempDir;
38use tracing::info;
39
40pub struct SwarmBuilder<R = OsRng> {
41    rng: R,
42    // template: NodeConfig,
43    dir: Option<PathBuf>,
44    committee: CommitteeConfig,
45    genesis_config: Option<GenesisConfig>,
46    network_config: Option<NetworkConfig>,
47    chain_override: Option<Chain>,
48    additional_objects: Vec<Object>,
49    fullnode_count: usize,
50    fullnode_rpc_port: Option<u16>,
51    fullnode_rpc_addr: Option<SocketAddr>,
52    fullnode_rpc_config: Option<sui_config::RpcConfig>,
53    fullnode_config: Option<NodeConfig>,
54    supported_protocol_versions_config: ProtocolVersionsConfig,
55    // Default to supported_protocol_versions_config, but can be overridden.
56    fullnode_supported_protocol_versions_config: Option<ProtocolVersionsConfig>,
57    db_checkpoint_config: DBCheckpointConfig,
58    jwk_fetch_interval: Option<Duration>,
59    num_unpruned_validators: Option<usize>,
60    authority_overload_config: Option<AuthorityOverloadConfig>,
61    consensus_transaction_pool_config: Option<ConsensusTransactionPoolConfig>,
62    execution_cache_config: Option<ExecutionCacheConfig>,
63    data_ingestion_dir: Option<PathBuf>,
64    fullnode_run_with_range: Option<RunWithRange>,
65    global_state_hash_v2_enabled_config: GlobalStateHashV2EnabledConfig,
66    funds_withdraw_scheduler_type_config: Option<FundsWithdrawSchedulerTypeConfig>,
67    disable_fullnode_pruning: bool,
68    state_sync_config: Option<sui_config::p2p::StateSyncConfig>,
69    peer_deny_sync_config:
70        Option<sui_swarm_config::network_config_builder::PeerDenySyncConfigCallback>,
71    #[cfg(msim)]
72    execution_time_observer_config: Option<ExecutionTimeObserverConfig>,
73    validator_observer_config: Option<ValidatorObserverConfigCallback>,
74}
75
76impl SwarmBuilder {
77    #[allow(clippy::new_without_default)]
78    pub fn new() -> Self {
79        Self {
80            rng: OsRng,
81            dir: None,
82            committee: CommitteeConfig::Size(NonZeroUsize::new(1).unwrap()),
83            genesis_config: None,
84            network_config: None,
85            chain_override: None,
86            additional_objects: vec![],
87            fullnode_count: 0,
88            fullnode_rpc_port: None,
89            fullnode_rpc_addr: None,
90            fullnode_rpc_config: None,
91            fullnode_config: None,
92            supported_protocol_versions_config: ProtocolVersionsConfig::Default,
93            fullnode_supported_protocol_versions_config: None,
94            db_checkpoint_config: DBCheckpointConfig::default(),
95            jwk_fetch_interval: None,
96            num_unpruned_validators: None,
97            authority_overload_config: None,
98            consensus_transaction_pool_config: None,
99            execution_cache_config: None,
100            data_ingestion_dir: None,
101            fullnode_run_with_range: None,
102            global_state_hash_v2_enabled_config: GlobalStateHashV2EnabledConfig::Global(true),
103            funds_withdraw_scheduler_type_config: None,
104            disable_fullnode_pruning: false,
105            state_sync_config: None,
106            peer_deny_sync_config: None,
107            #[cfg(msim)]
108            execution_time_observer_config: None,
109            validator_observer_config: None,
110        }
111    }
112}
113
114impl<R> SwarmBuilder<R> {
115    pub fn rng<N: rand::RngCore + rand::CryptoRng>(self, rng: N) -> SwarmBuilder<N> {
116        SwarmBuilder {
117            rng,
118            dir: self.dir,
119            committee: self.committee,
120            genesis_config: self.genesis_config,
121            network_config: self.network_config,
122            chain_override: self.chain_override,
123            additional_objects: self.additional_objects,
124            fullnode_count: self.fullnode_count,
125            fullnode_rpc_port: self.fullnode_rpc_port,
126            fullnode_rpc_addr: self.fullnode_rpc_addr,
127            fullnode_rpc_config: self.fullnode_rpc_config.clone(),
128            fullnode_config: self.fullnode_config,
129            supported_protocol_versions_config: self.supported_protocol_versions_config,
130            fullnode_supported_protocol_versions_config: self
131                .fullnode_supported_protocol_versions_config,
132            db_checkpoint_config: self.db_checkpoint_config,
133            jwk_fetch_interval: self.jwk_fetch_interval,
134            num_unpruned_validators: self.num_unpruned_validators,
135            authority_overload_config: self.authority_overload_config,
136            consensus_transaction_pool_config: self.consensus_transaction_pool_config,
137            execution_cache_config: self.execution_cache_config,
138            data_ingestion_dir: self.data_ingestion_dir,
139            fullnode_run_with_range: self.fullnode_run_with_range,
140            global_state_hash_v2_enabled_config: self.global_state_hash_v2_enabled_config,
141            funds_withdraw_scheduler_type_config: self.funds_withdraw_scheduler_type_config,
142            disable_fullnode_pruning: self.disable_fullnode_pruning,
143            state_sync_config: self.state_sync_config,
144            peer_deny_sync_config: self.peer_deny_sync_config,
145            #[cfg(msim)]
146            execution_time_observer_config: self.execution_time_observer_config,
147            validator_observer_config: self.validator_observer_config,
148        }
149    }
150
151    /// Set the directory that should be used by the Swarm for any on-disk data.
152    ///
153    /// If a directory is provided, it will not be cleaned up when the Swarm is dropped.
154    ///
155    /// Defaults to using a temporary directory that will be cleaned up when the Swarm is dropped.
156    pub fn dir<P: Into<PathBuf>>(mut self, dir: P) -> Self {
157        self.dir = Some(dir.into());
158        self
159    }
160
161    /// Set the committee size (the number of validators in the validator set).
162    ///
163    /// Defaults to 1.
164    pub fn committee_size(mut self, committee_size: NonZeroUsize) -> Self {
165        self.committee = CommitteeConfig::Size(committee_size);
166        self
167    }
168
169    pub fn with_validators(mut self, validators: Vec<ValidatorGenesisConfig>) -> Self {
170        self.committee = CommitteeConfig::Validators(validators);
171        self
172    }
173
174    pub fn with_genesis_config(mut self, genesis_config: GenesisConfig) -> Self {
175        assert!(self.network_config.is_none() && self.genesis_config.is_none());
176        self.genesis_config = Some(genesis_config);
177        self
178    }
179
180    pub fn with_chain_override(mut self, chain: Chain) -> Self {
181        assert!(self.chain_override.is_none());
182        self.chain_override = Some(chain);
183        self
184    }
185
186    pub fn with_num_unpruned_validators(mut self, n: usize) -> Self {
187        assert!(self.network_config.is_none());
188        self.num_unpruned_validators = Some(n);
189        self
190    }
191
192    pub fn with_jwk_fetch_interval(mut self, i: Duration) -> Self {
193        self.jwk_fetch_interval = Some(i);
194        self
195    }
196
197    pub fn with_network_config(mut self, network_config: NetworkConfig) -> Self {
198        assert!(self.network_config.is_none() && self.genesis_config.is_none());
199        self.network_config = Some(network_config);
200        self
201    }
202
203    pub fn with_accounts(mut self, accounts: Vec<AccountConfig>) -> Self {
204        self.get_or_init_genesis_config().accounts = accounts;
205        self
206    }
207
208    pub fn with_objects<I: IntoIterator<Item = Object>>(mut self, objects: I) -> Self {
209        self.additional_objects.extend(objects);
210        self
211    }
212
213    pub fn with_fullnode_count(mut self, fullnode_count: usize) -> Self {
214        self.fullnode_count = fullnode_count;
215        self
216    }
217
218    pub fn with_fullnode_rpc_port(mut self, fullnode_rpc_port: u16) -> Self {
219        assert!(self.fullnode_rpc_addr.is_none());
220        self.fullnode_rpc_port = Some(fullnode_rpc_port);
221        self
222    }
223
224    pub fn with_fullnode_rpc_addr(mut self, fullnode_rpc_addr: SocketAddr) -> Self {
225        assert!(self.fullnode_rpc_port.is_none());
226        self.fullnode_rpc_addr = Some(fullnode_rpc_addr);
227        self
228    }
229
230    pub fn with_fullnode_rpc_config(mut self, fullnode_rpc_config: sui_config::RpcConfig) -> Self {
231        self.fullnode_rpc_config = Some(fullnode_rpc_config);
232        self
233    }
234
235    pub fn with_fullnode_config(mut self, fullnode_config: NodeConfig) -> Self {
236        self.fullnode_config = Some(fullnode_config);
237        self
238    }
239
240    pub fn with_epoch_duration_ms(mut self, epoch_duration_ms: u64) -> Self {
241        assert!(
242            epoch_duration_ms >= 10000,
243            "Epoch duration must be at least 10s (10000ms) to avoid flaky tests. Got {epoch_duration_ms}ms."
244        );
245        self.get_or_init_genesis_config()
246            .parameters
247            .epoch_duration_ms = epoch_duration_ms;
248        self
249    }
250
251    pub fn with_protocol_version(mut self, v: ProtocolVersion) -> Self {
252        self.get_or_init_genesis_config()
253            .parameters
254            .protocol_version = v;
255        self
256    }
257
258    pub fn with_supported_protocol_versions(mut self, c: SupportedProtocolVersions) -> Self {
259        self.supported_protocol_versions_config = ProtocolVersionsConfig::Global(c);
260        self
261    }
262
263    pub fn with_supported_protocol_version_callback(
264        mut self,
265        func: SupportedProtocolVersionsCallback,
266    ) -> Self {
267        self.supported_protocol_versions_config = ProtocolVersionsConfig::PerValidator(func);
268        self
269    }
270
271    pub fn with_supported_protocol_versions_config(mut self, c: ProtocolVersionsConfig) -> Self {
272        self.supported_protocol_versions_config = c;
273        self
274    }
275
276    pub fn with_global_state_hash_v2_enabled_config(
277        mut self,
278        c: GlobalStateHashV2EnabledConfig,
279    ) -> Self {
280        self.global_state_hash_v2_enabled_config = c;
281        self
282    }
283
284    pub fn with_funds_withdraw_scheduler_type_config(
285        mut self,
286        c: FundsWithdrawSchedulerTypeConfig,
287    ) -> Self {
288        self.funds_withdraw_scheduler_type_config = Some(c);
289        self
290    }
291
292    #[cfg(msim)]
293    pub fn with_execution_time_observer_config(mut self, c: ExecutionTimeObserverConfig) -> Self {
294        self.execution_time_observer_config = Some(c);
295        self
296    }
297
298    pub fn with_validator_observer_config(mut self, c: ValidatorObserverConfigCallback) -> Self {
299        self.validator_observer_config = Some(c);
300        self
301    }
302
303    pub fn with_fullnode_supported_protocol_versions_config(
304        mut self,
305        c: ProtocolVersionsConfig,
306    ) -> Self {
307        self.fullnode_supported_protocol_versions_config = Some(c);
308        self
309    }
310
311    pub fn with_db_checkpoint_config(mut self, db_checkpoint_config: DBCheckpointConfig) -> Self {
312        self.db_checkpoint_config = db_checkpoint_config;
313        self
314    }
315
316    pub fn with_authority_overload_config(
317        mut self,
318        authority_overload_config: AuthorityOverloadConfig,
319    ) -> Self {
320        assert!(self.network_config.is_none());
321        self.authority_overload_config = Some(authority_overload_config);
322        self
323    }
324
325    pub fn with_consensus_transaction_pool_config(
326        mut self,
327        consensus_transaction_pool_config: ConsensusTransactionPoolConfig,
328    ) -> Self {
329        assert!(self.network_config.is_none());
330        self.consensus_transaction_pool_config = Some(consensus_transaction_pool_config);
331        self
332    }
333
334    pub fn with_execution_cache_config(
335        mut self,
336        execution_cache_config: ExecutionCacheConfig,
337    ) -> Self {
338        self.execution_cache_config = Some(execution_cache_config);
339        self
340    }
341
342    pub fn with_data_ingestion_dir(mut self, path: PathBuf) -> Self {
343        self.data_ingestion_dir = Some(path);
344        self
345    }
346
347    pub fn with_state_sync_config(mut self, config: sui_config::p2p::StateSyncConfig) -> Self {
348        self.state_sync_config = Some(config);
349        self
350    }
351
352    pub fn with_peer_deny_sync_config_per_validator(
353        mut self,
354        f: sui_swarm_config::network_config_builder::PeerDenySyncConfigCallback,
355    ) -> Self {
356        self.peer_deny_sync_config = Some(f);
357        self
358    }
359
360    pub fn with_fullnode_run_with_range(mut self, run_with_range: Option<RunWithRange>) -> Self {
361        if let Some(run_with_range) = run_with_range {
362            self.fullnode_run_with_range = Some(run_with_range);
363        }
364        self
365    }
366
367    fn get_or_init_genesis_config(&mut self) -> &mut GenesisConfig {
368        if self.genesis_config.is_none() {
369            assert!(self.network_config.is_none());
370            self.genesis_config = Some(GenesisConfig::for_local_testing());
371        }
372        self.genesis_config.as_mut().unwrap()
373    }
374
375    pub fn with_disable_fullnode_pruning(mut self) -> Self {
376        self.disable_fullnode_pruning = true;
377        self
378    }
379}
380
381impl<R: rand::RngCore + rand::CryptoRng> SwarmBuilder<R> {
382    /// Create the configured Swarm.
383    pub fn build(self) -> Swarm {
384        let dir = if let Some(dir) = self.dir {
385            SwarmDirectory::Persistent(dir)
386        } else {
387            SwarmDirectory::new_temporary()
388        };
389
390        let ingest_data = self.data_ingestion_dir.clone();
391
392        let network_config = self.network_config.unwrap_or_else(|| {
393            let mut config_builder = ConfigBuilder::new(dir.as_ref());
394
395            if let Some(genesis_config) = self.genesis_config {
396                config_builder = config_builder.with_genesis_config(genesis_config);
397            }
398
399            if let Some(chain_override) = self.chain_override {
400                config_builder = config_builder.with_chain_override(chain_override);
401            }
402
403            if let Some(num_unpruned_validators) = self.num_unpruned_validators {
404                config_builder =
405                    config_builder.with_num_unpruned_validators(num_unpruned_validators);
406            }
407
408            if let Some(jwk_fetch_interval) = self.jwk_fetch_interval {
409                config_builder = config_builder.with_jwk_fetch_interval(jwk_fetch_interval);
410            }
411
412            if let Some(authority_overload_config) = self.authority_overload_config {
413                config_builder =
414                    config_builder.with_authority_overload_config(authority_overload_config);
415            }
416
417            if let Some(config) = self.consensus_transaction_pool_config {
418                config_builder = config_builder.with_consensus_transaction_pool_config(config);
419            }
420
421            if let Some(execution_cache_config) = self.execution_cache_config {
422                config_builder = config_builder.with_execution_cache_config(execution_cache_config);
423            }
424
425            if let Some(path) = self.data_ingestion_dir {
426                config_builder = config_builder.with_data_ingestion_dir(path);
427            }
428
429            #[allow(unused_mut)]
430            let mut final_builder = config_builder
431                .committee(self.committee)
432                .rng(self.rng)
433                .with_objects(self.additional_objects)
434                .with_supported_protocol_versions_config(
435                    self.supported_protocol_versions_config.clone(),
436                )
437                .with_global_state_hash_v2_enabled_config(
438                    self.global_state_hash_v2_enabled_config.clone(),
439                );
440
441            if let Some(funds_withdraw_scheduler_type_config) =
442                self.funds_withdraw_scheduler_type_config.clone()
443            {
444                final_builder = final_builder.with_funds_withdraw_scheduler_type_config(
445                    funds_withdraw_scheduler_type_config,
446                );
447            }
448
449            if let Some(state_sync_config) = self.state_sync_config.clone() {
450                final_builder = final_builder.with_state_sync_config(state_sync_config);
451            }
452
453            if let Some(cb) = self.peer_deny_sync_config.clone() {
454                final_builder = final_builder.with_peer_deny_sync_config_per_validator(cb);
455            }
456
457            #[cfg(msim)]
458            if let Some(execution_time_observer_config) = self.execution_time_observer_config {
459                final_builder = final_builder
460                    .with_execution_time_observer_config(execution_time_observer_config);
461            }
462
463            if let Some(validator_observer_config) = self.validator_observer_config {
464                final_builder =
465                    final_builder.with_validator_observer_config(validator_observer_config);
466            }
467
468            final_builder.build()
469        });
470
471        let mut nodes: HashMap<_, _> = network_config
472            .validator_configs()
473            .iter()
474            .map(|config| {
475                info!(
476                    "SwarmBuilder configuring validator with name {}",
477                    config.protocol_public_key()
478                );
479                (config.protocol_public_key(), Node::new(config.to_owned()))
480            })
481            .collect();
482
483        let mut fullnode_config_builder = FullnodeConfigBuilder::new()
484            .with_config_directory(dir.as_ref().into())
485            .with_db_checkpoint_config(self.db_checkpoint_config.clone())
486            .with_run_with_range(self.fullnode_run_with_range)
487            .with_data_ingestion_dir(ingest_data)
488            .with_disable_pruning(self.disable_fullnode_pruning);
489
490        if let Some(state_sync_config) = self.state_sync_config.clone() {
491            fullnode_config_builder =
492                fullnode_config_builder.with_state_sync_config(state_sync_config);
493        }
494
495        if let Some(chain) = self.chain_override {
496            fullnode_config_builder = fullnode_config_builder.with_chain_override(chain);
497        }
498
499        if let Some(spvc) = &self.fullnode_supported_protocol_versions_config {
500            let supported_versions = match spvc {
501                ProtocolVersionsConfig::Default => SupportedProtocolVersions::SYSTEM_DEFAULT,
502                ProtocolVersionsConfig::Global(v) => *v,
503                ProtocolVersionsConfig::PerValidator(func) => func(0, None),
504            };
505            fullnode_config_builder =
506                fullnode_config_builder.with_supported_protocol_versions(supported_versions);
507        }
508
509        if self.fullnode_count > 0 {
510            let mut prebuilt_fullnode_config = self.fullnode_config;
511            (0..self.fullnode_count).for_each(|idx| {
512                let config = if idx == 0 && prebuilt_fullnode_config.is_some() {
513                    prebuilt_fullnode_config.take().unwrap()
514                } else {
515                    let mut builder = fullnode_config_builder.clone();
516                    if idx == 0 {
517                        // Only the first fullnode is used as the rpc fullnode, we can only use the
518                        // same address once.
519                        if let Some(rpc_addr) = self.fullnode_rpc_addr {
520                            builder = builder.with_rpc_addr(rpc_addr);
521                        }
522                        if let Some(rpc_port) = self.fullnode_rpc_port {
523                            builder = builder.with_rpc_port(rpc_port);
524                        }
525                        if let Some(rpc_config) = &self.fullnode_rpc_config {
526                            builder = builder.with_rpc_config(rpc_config.clone());
527                        }
528                    }
529                    builder.build(&mut OsRng, &network_config)
530                };
531                info!(
532                    "SwarmBuilder configuring full node with name {}",
533                    config.protocol_public_key()
534                );
535                nodes.insert(config.protocol_public_key(), Node::new(config));
536            });
537        }
538        Swarm {
539            dir,
540            network_config,
541            nodes,
542            fullnode_config_builder,
543        }
544    }
545}
546
547/// A handle to an in-memory Sui Network.
548#[derive(Debug)]
549pub struct Swarm {
550    dir: SwarmDirectory,
551    network_config: NetworkConfig,
552    nodes: HashMap<AuthorityName, Node>,
553    // Save a copy of the fullnode config builder to build future fullnodes.
554    fullnode_config_builder: FullnodeConfigBuilder,
555}
556
557impl Drop for Swarm {
558    fn drop(&mut self) {
559        self.nodes_iter_mut().for_each(|node| node.stop());
560    }
561}
562
563impl Swarm {
564    fn nodes_iter_mut(&mut self) -> impl Iterator<Item = &mut Node> {
565        self.nodes.values_mut()
566    }
567
568    /// Return a new Builder
569    pub fn builder() -> SwarmBuilder {
570        SwarmBuilder::new()
571    }
572
573    /// Start all nodes associated with this Swarm
574    pub async fn launch(&mut self) -> Result<()> {
575        try_join_all(self.nodes_iter_mut().map(|node| node.start())).await?;
576        tracing::info!("Successfully launched Swarm");
577        Ok(())
578    }
579
580    /// Return the path to the directory where this Swarm's on-disk data is kept.
581    pub fn dir(&self) -> &Path {
582        self.dir.as_ref()
583    }
584
585    /// Return a reference to this Swarm's `NetworkConfig`.
586    pub fn config(&self) -> &NetworkConfig {
587        &self.network_config
588    }
589
590    /// Return a mutable reference to this Swarm's `NetworkConfig`.
591    // TODO: It's not ideal to mutate network config. We should consider removing this.
592    pub fn config_mut(&mut self) -> &mut NetworkConfig {
593        &mut self.network_config
594    }
595
596    pub fn all_nodes(&self) -> impl Iterator<Item = &Node> {
597        self.nodes.values()
598    }
599
600    pub fn node(&self, name: &AuthorityName) -> Option<&Node> {
601        self.nodes.get(name)
602    }
603
604    pub fn node_mut(&mut self, name: &AuthorityName) -> Option<&mut Node> {
605        self.nodes.get_mut(name)
606    }
607
608    /// Return an iterator over shared references of all nodes that are set up as validators.
609    /// This however doesn't mean this validator is currently active (i.e. it's not necessarily
610    /// in the validator set at the moment). Note that observer fullnodes also carry a consensus
611    /// config, so the intended node role is what distinguishes a validator.
612    pub fn validator_nodes(&self) -> impl Iterator<Item = &Node> {
613        self.nodes
614            .values()
615            .filter(|node| node.config().intended_node_role().is_validator())
616    }
617
618    pub fn validator_node_handles(&self) -> Vec<SuiNodeHandle> {
619        self.validator_nodes()
620            .map(|node| node.get_node_handle().unwrap())
621            .collect()
622    }
623
624    /// Returns an iterator over all currently active validators.
625    pub fn active_validators(&self) -> impl Iterator<Item = &Node> {
626        self.validator_nodes().filter(|node| {
627            node.get_node_handle().is_some_and(|handle| {
628                let state = handle.state();
629                state.is_validator(&state.epoch_store_for_testing())
630            })
631        })
632    }
633
634    /// Return an iterator over shared references of all Fullnodes.
635    pub fn fullnodes(&self) -> impl Iterator<Item = &Node> {
636        self.nodes
637            .values()
638            .filter(|node| node.config().intended_node_role().is_fullnode())
639    }
640
641    /// Return an iterator over shared references of all fullnodes that sync as
642    /// consensus observers.
643    pub fn observer_nodes(&self) -> impl Iterator<Item = &Node> {
644        use sui_types::node_role::{FullNodeSyncMode, NodeRole};
645        self.nodes.values().filter(|node| {
646            node.config().intended_node_role()
647                == NodeRole::FullNode(FullNodeSyncMode::ConsensusObserver)
648        })
649    }
650
651    pub async fn spawn_new_node(&mut self, config: NodeConfig) -> SuiNodeHandle {
652        let name = config.protocol_public_key();
653        let node = Node::new(config);
654        node.start().await.unwrap();
655        let handle = node.get_node_handle().unwrap();
656        self.nodes.insert(name, node);
657        handle
658    }
659
660    pub fn get_fullnode_config_builder(&self) -> FullnodeConfigBuilder {
661        self.fullnode_config_builder.clone()
662    }
663}
664
665#[derive(Debug)]
666enum SwarmDirectory {
667    Persistent(PathBuf),
668    Temporary(TempDir),
669}
670
671impl SwarmDirectory {
672    fn new_temporary() -> Self {
673        SwarmDirectory::Temporary(nondeterministic!(TempDir::new().unwrap()))
674    }
675}
676
677impl ops::Deref for SwarmDirectory {
678    type Target = Path;
679
680    fn deref(&self) -> &Self::Target {
681        match self {
682            SwarmDirectory::Persistent(dir) => dir.deref(),
683            SwarmDirectory::Temporary(dir) => dir.path(),
684        }
685    }
686}
687
688impl AsRef<Path> for SwarmDirectory {
689    fn as_ref(&self) -> &Path {
690        match self {
691            SwarmDirectory::Persistent(dir) => dir.as_ref(),
692            SwarmDirectory::Temporary(dir) => dir.as_ref(),
693        }
694    }
695}
696
697#[cfg(test)]
698mod test {
699    use super::Swarm;
700    use std::num::NonZeroUsize;
701
702    #[tokio::test]
703    async fn launch() {
704        telemetry_subscribers::init_for_testing();
705        let mut swarm = Swarm::builder()
706            .committee_size(NonZeroUsize::new(4).unwrap())
707            .with_fullnode_count(1)
708            .build();
709
710        swarm.launch().await.unwrap();
711
712        for validator in swarm.validator_nodes() {
713            validator.health_check(true).await.unwrap();
714        }
715
716        for fullnode in swarm.fullnodes() {
717            fullnode.health_check(false).await.unwrap();
718        }
719
720        println!("hello");
721    }
722}