1use 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 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 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 pub fn dir<P: Into<PathBuf>>(mut self, dir: P) -> Self {
163 self.dir = Some(dir.into());
164 self
165 }
166
167 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 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 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 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 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 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#[derive(Debug)]
564pub struct Swarm {
565 dir: SwarmDirectory,
566 network_config: NetworkConfig,
567 nodes: HashMap<AuthorityName, Node>,
568 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 pub fn builder() -> SwarmBuilder {
585 SwarmBuilder::new()
586 }
587
588 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 pub fn dir(&self) -> &Path {
598 self.dir.as_ref()
599 }
600
601 pub fn config(&self) -> &NetworkConfig {
603 &self.network_config
604 }
605
606 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 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 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 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 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}