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