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