Skip to main content

iota_swarm/memory/
swarm.rs

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