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