Skip to main content

iota_swarm_config/
node_config_builder.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{net::SocketAddr, num::NonZeroUsize, path::PathBuf};
6
7use anyhow::anyhow;
8use fastcrypto::{
9    encoding::{Encoding, Hex},
10    traits::KeyPair,
11};
12use iota_config::{
13    AUTHORITIES_DB_NAME, CONSENSUS_DB_NAME, ConsensusConfig, FULL_NODE_DB_PATH,
14    IOTA_GENESIS_MIGRATION_TX_DATA_FILENAME, NodeConfig, local_ip_utils,
15    node::{
16        AuthorityKeyPairWithPath, AuthorityOverloadConfig, AuthorityStorePruningConfig,
17        CheckpointExecutorConfig, ExecutionCacheConfig, ExpensiveSafetyCheckConfig, Genesis,
18        GrpcApiConfig, KeyPairWithPath, RunWithRange, StateSnapshotConfig,
19        default_enable_index_processing, default_end_of_epoch_broadcast_channel_capacity,
20        default_full_checkpoint_contents_cache_size_mb,
21    },
22    p2p::{DiscoveryConfig, P2pConfig, SeedPeer, StateSyncConfig},
23    transaction_deny_config::TransactionDenyConfig,
24    verifier_signing_config::VerifierSigningConfig,
25};
26use iota_multiaddr::Multiaddr;
27use iota_names::config::IotaNamesConfig;
28use iota_protocol_config::Chain;
29use iota_types::{
30    crypto::{
31        AuthorityKeyPair, AuthorityPublicKeyBytes, NetworkKeyPair, network_to_simple_keypair,
32    },
33    supported_protocol_versions::SupportedProtocolVersions,
34    traffic_control::{PolicyConfig, RemoteFirewallConfig},
35};
36
37use crate::{
38    genesis_config::{ValidatorGenesisConfig, ValidatorGenesisConfigBuilder},
39    network_config::NetworkConfig,
40};
41
42/// This builder contains information that's not included in
43/// ValidatorGenesisConfig for building a validator NodeConfig. It can be used
44/// to build either a genesis validator or a new validator.
45#[derive(Clone, Default)]
46pub struct ValidatorConfigBuilder {
47    config_directory: Option<PathBuf>,
48    supported_protocol_versions: Option<SupportedProtocolVersions>,
49    force_unpruned_checkpoints: bool,
50    authority_overload_config: Option<AuthorityOverloadConfig>,
51    transaction_deny_config: Option<TransactionDenyConfig>,
52    execution_cache_config: Option<ExecutionCacheConfig>,
53    data_ingestion_dir: Option<PathBuf>,
54    policy_config: Option<PolicyConfig>,
55    firewall_config: Option<RemoteFirewallConfig>,
56    max_submit_position: Option<usize>,
57    submit_delay_step_override_millis: Option<u64>,
58    discovery_config: Option<DiscoveryConfig>,
59    chain_override: Option<Chain>,
60}
61
62impl ValidatorConfigBuilder {
63    pub fn new() -> Self {
64        Self {
65            ..Default::default()
66        }
67    }
68
69    pub fn with_chain_override(mut self, chain: Chain) -> Self {
70        assert!(self.chain_override.is_none(), "Chain override already set");
71        self.chain_override = Some(chain);
72        self
73    }
74
75    pub fn with_config_directory(mut self, config_directory: PathBuf) -> Self {
76        assert!(self.config_directory.is_none());
77        self.config_directory = Some(config_directory);
78        self
79    }
80
81    pub fn with_supported_protocol_versions(
82        mut self,
83        supported_protocol_versions: SupportedProtocolVersions,
84    ) -> Self {
85        assert!(self.supported_protocol_versions.is_none());
86        self.supported_protocol_versions = Some(supported_protocol_versions);
87        self
88    }
89
90    pub fn with_unpruned_checkpoints(mut self) -> Self {
91        self.force_unpruned_checkpoints = true;
92        self
93    }
94
95    pub fn with_authority_overload_config(mut self, config: AuthorityOverloadConfig) -> Self {
96        self.authority_overload_config = Some(config);
97        self
98    }
99
100    pub fn with_transaction_deny_config(mut self, config: TransactionDenyConfig) -> Self {
101        self.transaction_deny_config = Some(config);
102        self
103    }
104
105    pub fn with_execution_cache_config(mut self, config: ExecutionCacheConfig) -> Self {
106        self.execution_cache_config = Some(config);
107        self
108    }
109
110    pub fn with_data_ingestion_dir(mut self, path: PathBuf) -> Self {
111        self.data_ingestion_dir = Some(path);
112        self
113    }
114
115    pub fn with_policy_config(mut self, config: Option<PolicyConfig>) -> Self {
116        self.policy_config = config;
117        self
118    }
119
120    pub fn with_firewall_config(mut self, config: Option<RemoteFirewallConfig>) -> Self {
121        self.firewall_config = config;
122        self
123    }
124
125    pub fn with_max_submit_position(mut self, max_submit_position: usize) -> Self {
126        self.max_submit_position = Some(max_submit_position);
127        self
128    }
129
130    pub fn with_submit_delay_step_override_millis(
131        mut self,
132        submit_delay_step_override_millis: u64,
133    ) -> Self {
134        self.submit_delay_step_override_millis = Some(submit_delay_step_override_millis);
135        self
136    }
137
138    pub fn with_discovery_config(mut self, discovery_config: DiscoveryConfig) -> Self {
139        self.discovery_config = Some(discovery_config);
140        self
141    }
142
143    pub fn build_without_genesis(self, validator: ValidatorGenesisConfig) -> NodeConfig {
144        let key_path = get_key_path(&validator.authority_key_pair);
145        let config_directory = self
146            .config_directory
147            .unwrap_or_else(|| iota_common::tempdir().keep());
148        let migration_tx_data_path =
149            Some(config_directory.join(IOTA_GENESIS_MIGRATION_TX_DATA_FILENAME));
150        let db_path = config_directory
151            .join(AUTHORITIES_DB_NAME)
152            .join(key_path.clone());
153        let network_address = validator.network_address;
154        let consensus_db_path = config_directory.join(CONSENSUS_DB_NAME).join(key_path);
155        let localhost = local_ip_utils::localhost_for_testing();
156        let consensus_config = ConsensusConfig {
157            db_path: consensus_db_path,
158            db_retention_epochs: None,
159            db_pruner_period_secs: None,
160            max_pending_transactions: None,
161            max_submit_position: self.max_submit_position,
162            submit_delay_step_override_millis: self.submit_delay_step_override_millis,
163            parameters: Default::default(),
164            graduated_load_shedding_soft_limit_pct: Default::default(),
165        };
166
167        let p2p_config = P2pConfig {
168            listen_address: validator.p2p_listen_address.unwrap_or_else(|| {
169                validator
170                    .p2p_address
171                    .udp_multiaddr_to_listen_address()
172                    .unwrap()
173            }),
174            external_address: Some(validator.p2p_address),
175            // Set a shorter timeout for checkpoint content download in tests, since
176            // checkpoint pruning also happens much faster, and network is local.
177            state_sync: Some(StateSyncConfig {
178                checkpoint_content_timeout_ms: Some(10_000),
179                ..Default::default()
180            }),
181            // Use discovery config if provided
182            discovery: self.discovery_config,
183            ..Default::default()
184        };
185
186        let mut pruning_config = AuthorityStorePruningConfig::default();
187        if self.force_unpruned_checkpoints {
188            pruning_config.set_num_epochs_to_retain_for_checkpoints(None);
189        }
190        let pruning_config = pruning_config;
191        let checkpoint_executor_config = CheckpointExecutorConfig {
192            data_ingestion_dir: self.data_ingestion_dir,
193            ..Default::default()
194        };
195
196        NodeConfig {
197            authority_key_pair: AuthorityKeyPairWithPath::new(validator.authority_key_pair),
198            network_key_pair: KeyPairWithPath::new(network_to_simple_keypair(
199                &validator.network_key_pair,
200            )),
201            account_key_pair: KeyPairWithPath::new(validator.account_key_pair),
202            protocol_key_pair: KeyPairWithPath::new(network_to_simple_keypair(
203                &validator.protocol_key_pair,
204            )),
205            db_path,
206            network_address,
207            metrics_address: validator.metrics_address,
208            admin_interface_address: validator.admin_interface_address,
209            json_rpc_address: local_ip_utils::new_tcp_address_for_testing(&localhost)
210                .to_socket_addr()
211                .unwrap(),
212            consensus_config: Some(consensus_config),
213            enable_index_processing: default_enable_index_processing(),
214            genesis: Genesis::new_empty(),
215            migration_tx_data_path,
216            grpc_load_shed: None,
217            // Effectively unlimited: tests and benchmarks must not be
218            // throttled.
219            grpc_concurrency_limit_per_core: NonZeroUsize::new(500_000_000).unwrap(),
220            p2p_config,
221            authority_store_pruning_config: pruning_config,
222            end_of_epoch_broadcast_channel_capacity:
223                default_end_of_epoch_broadcast_channel_capacity(),
224            checkpoint_executor_config,
225            metrics: None,
226            supported_protocol_versions: self.supported_protocol_versions,
227            // By default, expensive checks will be enabled in debug build, but not in release
228            // build.
229            expensive_safety_check_config: ExpensiveSafetyCheckConfig::default(),
230            transaction_deny_config: self.transaction_deny_config.unwrap_or_default(),
231            certificate_deny_config: Default::default(),
232            state_debug_dump_config: Default::default(),
233            checkpoint_archive_config: None,
234            state_snapshot_write_config: StateSnapshotConfig::default(),
235            indexer_max_subscriptions: Default::default(),
236            transaction_kv_store_read_config: Default::default(),
237            transaction_kv_store_write_config: None,
238            authority_overload_config: self.authority_overload_config.unwrap_or_default(),
239            execution_cache_config: self.execution_cache_config.unwrap_or_default(),
240            full_checkpoint_contents_cache_size_mb: default_full_checkpoint_contents_cache_size_mb(
241            ),
242            run_with_range: None,
243            jsonrpc_server_type: None,
244            policy_config: self.policy_config,
245            firewall_config: self.firewall_config,
246            enable_validator_tx_finalizer: true,
247            enable_soft_locking: true,
248            verifier_signing_config: VerifierSigningConfig::default(),
249            enable_db_write_stall: None,
250            iota_names_config: None,
251            enable_grpc_api: false,
252            grpc_api_config: None,
253            chain_override_for_testing: self.chain_override,
254            validator_client_monitor_config: None,
255        }
256    }
257
258    pub fn build(
259        self,
260        validator: ValidatorGenesisConfig,
261        genesis: iota_config::genesis::Genesis,
262    ) -> NodeConfig {
263        let mut config = self.build_without_genesis(validator);
264        config.genesis = iota_config::node::Genesis::new(genesis);
265        config
266    }
267
268    pub fn build_new_validator<R: rand::RngCore + rand::CryptoRng>(
269        self,
270        rng: &mut R,
271        network_config: &NetworkConfig,
272    ) -> NodeConfig {
273        let validator_config = ValidatorGenesisConfigBuilder::new().build(rng);
274        self.build(validator_config, network_config.genesis.clone())
275    }
276}
277
278#[derive(Clone, Debug, Default)]
279pub struct FullnodeConfigBuilder {
280    config_directory: Option<PathBuf>,
281    // port for json rpc api
282    rpc_port: Option<u16>,
283    rpc_addr: Option<SocketAddr>,
284    supported_protocol_versions: Option<SupportedProtocolVersions>,
285    expensive_safety_check_config: Option<ExpensiveSafetyCheckConfig>,
286    db_path: Option<PathBuf>,
287    network_address: Option<Multiaddr>,
288    json_rpc_address: Option<SocketAddr>,
289    metrics_address: Option<SocketAddr>,
290    admin_interface_address: Option<SocketAddr>,
291    genesis: Option<Genesis>,
292    p2p_external_address: Option<Multiaddr>,
293    p2p_listen_address: Option<SocketAddr>,
294    network_key_pair: Option<KeyPairWithPath>,
295    run_with_range: Option<RunWithRange>,
296    policy_config: Option<PolicyConfig>,
297    fw_config: Option<RemoteFirewallConfig>,
298    data_ingestion_dir: Option<PathBuf>,
299    disable_pruning: bool,
300    iota_names_config: Option<IotaNamesConfig>,
301    enable_grpc_api: bool,
302    grpc_api_config: Option<GrpcApiConfig>,
303    discovery_config: Option<DiscoveryConfig>,
304    chain_override: Option<Chain>,
305    deterministic_port_base: Option<u16>,
306}
307
308impl FullnodeConfigBuilder {
309    pub fn new() -> Self {
310        Self::default()
311    }
312
313    pub fn with_chain_override(mut self, chain: Chain) -> Self {
314        assert!(self.chain_override.is_none(), "Chain override already set");
315        self.chain_override = Some(chain);
316        self
317    }
318
319    pub fn with_config_directory(mut self, config_directory: PathBuf) -> Self {
320        self.config_directory = Some(config_directory);
321        self
322    }
323
324    pub fn with_rpc_port(mut self, port: u16) -> Self {
325        assert!(self.rpc_addr.is_none() && self.rpc_port.is_none());
326        self.rpc_port = Some(port);
327        self
328    }
329
330    pub fn with_rpc_addr(mut self, addr: impl Into<SocketAddr>) -> Self {
331        assert!(self.rpc_addr.is_none() && self.rpc_port.is_none());
332        self.rpc_addr = Some(addr.into());
333        self
334    }
335
336    pub fn with_supported_protocol_versions(mut self, versions: SupportedProtocolVersions) -> Self {
337        self.supported_protocol_versions = Some(versions);
338        self
339    }
340
341    pub fn with_disable_pruning(mut self, disable_pruning: bool) -> Self {
342        self.disable_pruning = disable_pruning;
343        self
344    }
345
346    pub fn with_expensive_safety_check_config(
347        mut self,
348        expensive_safety_check_config: ExpensiveSafetyCheckConfig,
349    ) -> Self {
350        self.expensive_safety_check_config = Some(expensive_safety_check_config);
351        self
352    }
353
354    pub fn with_db_path(mut self, db_path: PathBuf) -> Self {
355        self.db_path = Some(db_path);
356        self
357    }
358
359    pub fn with_network_address(mut self, network_address: Multiaddr) -> Self {
360        self.network_address = Some(network_address);
361        self
362    }
363
364    pub fn with_json_rpc_address(mut self, json_rpc_address: impl Into<SocketAddr>) -> Self {
365        self.json_rpc_address = Some(json_rpc_address.into());
366        self
367    }
368
369    pub fn with_metrics_address(mut self, metrics_address: impl Into<SocketAddr>) -> Self {
370        self.metrics_address = Some(metrics_address.into());
371        self
372    }
373
374    pub fn with_admin_interface_address(
375        mut self,
376        admin_interface_address: Option<impl Into<SocketAddr>>,
377    ) -> Self {
378        self.admin_interface_address = admin_interface_address.map(|addr| addr.into());
379        self
380    }
381
382    pub fn with_genesis(mut self, genesis: Genesis) -> Self {
383        self.genesis = Some(genesis);
384        self
385    }
386
387    pub fn with_p2p_external_address(mut self, p2p_external_address: Multiaddr) -> Self {
388        self.p2p_external_address = Some(p2p_external_address);
389        self
390    }
391
392    pub fn with_p2p_listen_address(mut self, p2p_listen_address: impl Into<SocketAddr>) -> Self {
393        self.p2p_listen_address = Some(p2p_listen_address.into());
394        self
395    }
396
397    pub fn with_network_key_pair(mut self, network_key_pair: Option<NetworkKeyPair>) -> Self {
398        if let Some(network_key_pair) = network_key_pair {
399            self.network_key_pair = Some(KeyPairWithPath::new(network_to_simple_keypair(
400                &network_key_pair,
401            )));
402        }
403        self
404    }
405
406    pub fn with_run_with_range(mut self, run_with_range: Option<RunWithRange>) -> Self {
407        if let Some(run_with_range) = run_with_range {
408            self.run_with_range = Some(run_with_range);
409        }
410        self
411    }
412
413    pub fn with_policy_config(mut self, config: Option<PolicyConfig>) -> Self {
414        self.policy_config = config;
415        self
416    }
417
418    pub fn with_fw_config(mut self, config: Option<RemoteFirewallConfig>) -> Self {
419        self.fw_config = config;
420        self
421    }
422
423    pub fn with_data_ingestion_dir(mut self, path: Option<PathBuf>) -> Self {
424        self.data_ingestion_dir = path;
425        self
426    }
427
428    pub fn with_iota_names_config(mut self, config: Option<IotaNamesConfig>) -> Self {
429        self.iota_names_config = config;
430        self
431    }
432
433    pub fn with_enable_grpc_api(mut self, enable_grpc_api: bool) -> Self {
434        self.enable_grpc_api = enable_grpc_api;
435        self
436    }
437
438    pub fn with_grpc_api_config(mut self, config: GrpcApiConfig) -> Self {
439        self.grpc_api_config = Some(config);
440        self
441    }
442
443    pub fn with_discovery_config(mut self, discovery_config: DiscoveryConfig) -> Self {
444        self.discovery_config = Some(discovery_config);
445        self
446    }
447
448    /// Give the fullnode fixed ports instead of currently-free ones:
449    /// `port_base` for the metrics endpoint, `port_base + 1` for the admin
450    /// interface and `port_base + 2` for p2p, all on the fullnode's own
451    /// address.
452    ///
453    /// Addresses set explicitly on this builder still win.
454    pub fn with_deterministic_ports(mut self, port_base: u16) -> Self {
455        self.deterministic_port_base = Some(port_base);
456        self
457    }
458
459    /// Build the fullnode config against the given validator configs.
460    ///
461    /// # Panics
462    ///
463    /// Panics if [`Self::try_build_from_parts`] returns an error.
464    pub fn build_from_parts<R: rand::RngCore + rand::CryptoRng>(
465        self,
466        rng: &mut R,
467        validator_configs: &[NodeConfig],
468        genesis: iota_config::node::Genesis,
469    ) -> NodeConfig {
470        self.try_build_from_parts(rng, validator_configs, genesis)
471            .unwrap_or_else(|err| panic!("{err:#}"))
472    }
473
474    /// Build the fullnode config against the given validator configs.
475    ///
476    /// Fails if a validator config has no `p2p-config.external-address`,
477    /// which the fullnode's seed peers are derived from.
478    ///
479    /// # Panics
480    ///
481    /// Panics on failures the config cannot be built without: creating the
482    /// temporary config directory, allocating a local port, or parsing a
483    /// generated network address.
484    pub fn try_build_from_parts<R: rand::RngCore + rand::CryptoRng>(
485        self,
486        rng: &mut R,
487        validator_configs: &[NodeConfig],
488        genesis: iota_config::node::Genesis,
489    ) -> anyhow::Result<NodeConfig> {
490        // Take advantage of ValidatorGenesisConfigBuilder to build the keypairs and
491        // addresses, even though this is a fullnode.
492        let validator_config = ValidatorGenesisConfigBuilder::new().build(rng);
493        let node_ip = validator_config
494            .network_address
495            .to_socket_addr()
496            .unwrap()
497            .ip();
498        let ip = node_ip.to_string();
499
500        let key_path = get_key_path(&validator_config.authority_key_pair);
501        let config_directory = self
502            .config_directory
503            .unwrap_or_else(|| iota_common::tempdir().keep());
504
505        let migration_tx_data_path =
506            Some(config_directory.join(IOTA_GENESIS_MIGRATION_TX_DATA_FILENAME));
507
508        let deterministic_port = |offset: u16| {
509            self.deterministic_port_base.map(|port_base| {
510                port_base.checked_add(offset).unwrap_or_else(|| {
511                    panic!("the fullnode port layout does not fit above port {port_base}")
512                })
513            })
514        };
515        let deterministic_metrics_address =
516            deterministic_port(0).map(|port| SocketAddr::new(node_ip, port));
517        let deterministic_admin_interface_address =
518            deterministic_port(1).map(|port| SocketAddr::new(node_ip, port));
519        let deterministic_p2p_address = deterministic_port(2)
520            .map(|port| local_ip_utils::new_deterministic_udp_address_for_testing(&ip, port));
521
522        let p2p_config = {
523            let seed_peers = validator_configs
524                .iter()
525                .enumerate()
526                .map(|(index, config)| {
527                    let address = config.p2p_config.external_address.clone().ok_or_else(|| {
528                        anyhow!(
529                            "validator {index} has no `p2p-config.external-address`, which the \
530                             fullnode needs to derive its seed peers: either its config never set \
531                             one or it was cleared"
532                        )
533                    })?;
534                    Ok(SeedPeer {
535                        peer_id: Some(anemo::PeerId(
536                            config.network_key_pair().public().0.to_bytes(),
537                        )),
538                        address,
539                    })
540                })
541                .collect::<anyhow::Result<Vec<_>>>()?;
542
543            P2pConfig {
544                listen_address: self
545                    .p2p_listen_address
546                    .or_else(|| {
547                        deterministic_p2p_address
548                            .as_ref()
549                            .and_then(|address| address.udp_multiaddr_to_listen_address())
550                    })
551                    .unwrap_or_else(|| {
552                        validator_config.p2p_listen_address.unwrap_or_else(|| {
553                            validator_config
554                                .p2p_address
555                                .udp_multiaddr_to_listen_address()
556                                .unwrap()
557                        })
558                    }),
559                external_address: self
560                    .p2p_external_address
561                    .or(deterministic_p2p_address)
562                    .or(Some(validator_config.p2p_address.clone())),
563                seed_peers,
564                // Set a shorter timeout for checkpoint content download in tests, since
565                // checkpoint pruning also happens much faster, and network is local.
566                state_sync: Some(StateSyncConfig {
567                    checkpoint_content_timeout_ms: Some(10_000),
568                    ..Default::default()
569                }),
570                // Use discovery config if provided
571                discovery: self.discovery_config,
572                ..Default::default()
573            }
574        };
575
576        let json_rpc_address = self.json_rpc_address.unwrap_or_else(|| {
577            self.rpc_addr.unwrap_or_else(|| {
578                let rpc_port = self
579                    .rpc_port
580                    .unwrap_or_else(|| local_ip_utils::get_available_port(&ip));
581                format!("{ip}:{rpc_port}").parse().unwrap()
582            })
583        });
584
585        let grpc_api_config = self.grpc_api_config.or_else(|| {
586            if self.enable_grpc_api {
587                Some(GrpcApiConfig {
588                    address: format!("{ip}:{}", local_ip_utils::get_available_port(&ip))
589                        .parse()
590                        .unwrap(),
591                    ..Default::default()
592                })
593            } else {
594                None
595            }
596        });
597
598        let checkpoint_executor_config = CheckpointExecutorConfig {
599            data_ingestion_dir: self.data_ingestion_dir,
600            ..Default::default()
601        };
602
603        let mut pruning_config = AuthorityStorePruningConfig::default();
604        if self.disable_pruning {
605            pruning_config.set_num_epochs_to_retain_for_checkpoints(None);
606            pruning_config.set_num_epochs_to_retain(u64::MAX);
607        };
608
609        Ok(NodeConfig {
610            authority_key_pair: AuthorityKeyPairWithPath::new(validator_config.authority_key_pair),
611            account_key_pair: KeyPairWithPath::new(validator_config.account_key_pair),
612            protocol_key_pair: KeyPairWithPath::new(network_to_simple_keypair(
613                &validator_config.protocol_key_pair,
614            )),
615            network_key_pair: self.network_key_pair.unwrap_or(KeyPairWithPath::new(
616                network_to_simple_keypair(&validator_config.network_key_pair),
617            )),
618            db_path: self
619                .db_path
620                .unwrap_or(config_directory.join(FULL_NODE_DB_PATH).join(key_path)),
621            network_address: self
622                .network_address
623                .unwrap_or(validator_config.network_address),
624            metrics_address: self
625                .metrics_address
626                .or(deterministic_metrics_address)
627                .unwrap_or_else(local_ip_utils::new_local_tcp_socket_for_testing),
628            admin_interface_address: self
629                .admin_interface_address
630                .or(deterministic_admin_interface_address)
631                .unwrap_or_else(local_ip_utils::new_local_tcp_socket_for_testing),
632            json_rpc_address,
633            consensus_config: None,
634            enable_index_processing: default_enable_index_processing(),
635            genesis,
636            migration_tx_data_path,
637            grpc_load_shed: None,
638            // Effectively unlimited: tests and benchmarks must not be
639            // throttled.
640            grpc_concurrency_limit_per_core: NonZeroUsize::new(500_000_000).unwrap(),
641            p2p_config,
642            authority_store_pruning_config: pruning_config,
643            end_of_epoch_broadcast_channel_capacity:
644                default_end_of_epoch_broadcast_channel_capacity(),
645            checkpoint_executor_config,
646            metrics: None,
647            supported_protocol_versions: self.supported_protocol_versions,
648            expensive_safety_check_config: self
649                .expensive_safety_check_config
650                .unwrap_or_else(ExpensiveSafetyCheckConfig::new_enable_all),
651            transaction_deny_config: Default::default(),
652            certificate_deny_config: Default::default(),
653            state_debug_dump_config: Default::default(),
654            checkpoint_archive_config: None,
655            state_snapshot_write_config: StateSnapshotConfig::default(),
656            indexer_max_subscriptions: Default::default(),
657            transaction_kv_store_read_config: Default::default(),
658            transaction_kv_store_write_config: Default::default(),
659            authority_overload_config: Default::default(),
660            run_with_range: self.run_with_range,
661            jsonrpc_server_type: None,
662            policy_config: self.policy_config,
663            firewall_config: self.fw_config,
664            execution_cache_config: ExecutionCacheConfig::default(),
665            full_checkpoint_contents_cache_size_mb: default_full_checkpoint_contents_cache_size_mb(
666            ),
667            // This is a validator specific feature.
668            enable_validator_tx_finalizer: false,
669            // No effect on a fullnode (soft-locking runs only in the validator
670            // submit path); kept at the default so the config mirrors production.
671            enable_soft_locking: true,
672            verifier_signing_config: VerifierSigningConfig::default(),
673            enable_db_write_stall: None,
674            iota_names_config: self.iota_names_config,
675            enable_grpc_api: self.enable_grpc_api,
676            grpc_api_config,
677            chain_override_for_testing: self.chain_override,
678            validator_client_monitor_config: None,
679        })
680    }
681
682    /// Build the fullnode config against the given network config.
683    ///
684    /// # Panics
685    ///
686    /// Panics if [`Self::try_build`] returns an error.
687    pub fn build<R: rand::RngCore + rand::CryptoRng>(
688        self,
689        rng: &mut R,
690        network_config: &NetworkConfig,
691    ) -> NodeConfig {
692        self.try_build(rng, network_config)
693            .unwrap_or_else(|err| panic!("{err:#}"))
694    }
695
696    /// Build the fullnode config against the given network config.
697    ///
698    /// Fails if a validator config has no `p2p-config.external-address`,
699    /// which the fullnode's seed peers are derived from.
700    ///
701    /// # Panics
702    ///
703    /// Panics on failures the config cannot be built without: creating the
704    /// temporary config directory, allocating a local port, or parsing a
705    /// generated network address.
706    pub fn try_build<R: rand::RngCore + rand::CryptoRng>(
707        self,
708        rng: &mut R,
709        network_config: &NetworkConfig,
710    ) -> anyhow::Result<NodeConfig> {
711        let genesis = self
712            .genesis
713            .as_ref()
714            .or_else(|| network_config.get_validator_genesis())
715            .cloned()
716            .unwrap_or_else(|| iota_config::node::Genesis::new(network_config.genesis.clone()));
717        self.try_build_from_parts(rng, network_config.validator_configs(), genesis)
718    }
719}
720
721/// Given a validator keypair, return a path that can be used to identify the
722/// validator.
723fn get_key_path(key_pair: &AuthorityKeyPair) -> String {
724    let public_key: AuthorityPublicKeyBytes = key_pair.public().into();
725    let mut key_path = Hex::encode(public_key);
726    // 12 is rather arbitrary here but it's a nice balance between being short and
727    // being unique.
728    key_path.truncate(12);
729    key_path
730}
731
732#[cfg(test)]
733mod tests {
734    use std::net::SocketAddr;
735
736    use rand::rngs::OsRng;
737
738    use super::{FullnodeConfigBuilder, Genesis};
739
740    #[test]
741    fn deterministic_ports_fill_the_three_slots_of_a_fullnode() {
742        let config = FullnodeConfigBuilder::new()
743            .with_deterministic_ports(9184)
744            .build_from_parts(&mut OsRng, &[], Genesis::new_empty());
745        let ip = config.network_address.to_socket_addr().unwrap().ip();
746
747        assert_eq!(config.metrics_address, SocketAddr::new(ip, 9184));
748        assert_eq!(config.admin_interface_address, SocketAddr::new(ip, 9185));
749        assert_eq!(
750            config.p2p_config.external_address.unwrap().to_string(),
751            format!("/ip4/{ip}/udp/9186/http")
752        );
753        assert_eq!(config.p2p_config.listen_address, SocketAddr::new(ip, 9186));
754    }
755
756    #[test]
757    fn an_address_set_on_the_builder_wins_over_the_deterministic_ports() {
758        let config = FullnodeConfigBuilder::new()
759            .with_deterministic_ports(9184)
760            .with_admin_interface_address(Some(([127, 0, 0, 1], 1337)))
761            .build_from_parts(&mut OsRng, &[], Genesis::new_empty());
762        let ip = config.network_address.to_socket_addr().unwrap().ip();
763
764        assert_eq!(config.admin_interface_address.to_string(), "127.0.0.1:1337");
765        assert_eq!(config.metrics_address, SocketAddr::new(ip, 9184));
766    }
767}