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