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::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    state_snapshot_write_config: Option<StateSnapshotConfig>,
317}
318
319impl FullnodeConfigBuilder {
320    pub fn new() -> Self {
321        Self::default()
322    }
323
324    /// Publishes formal state snapshots to the store the config names.
325    pub fn with_state_snapshot_config(mut self, config: StateSnapshotConfig) -> Self {
326        self.state_snapshot_write_config = Some(config);
327        self
328    }
329
330    pub fn with_chain_override(mut self, chain: Chain) -> Self {
331        assert!(self.chain_override.is_none(), "Chain override already set");
332        self.chain_override = Some(chain);
333        self
334    }
335
336    pub fn with_config_directory(mut self, config_directory: PathBuf) -> Self {
337        self.config_directory = Some(config_directory);
338        self
339    }
340
341    pub fn with_rpc_port(mut self, port: u16) -> Self {
342        assert!(self.rpc_addr.is_none() && self.rpc_port.is_none());
343        self.rpc_port = Some(port);
344        self
345    }
346
347    pub fn with_rpc_addr(mut self, addr: impl Into<SocketAddr>) -> Self {
348        assert!(self.rpc_addr.is_none() && self.rpc_port.is_none());
349        self.rpc_addr = Some(addr.into());
350        self
351    }
352
353    pub fn with_supported_protocol_versions(mut self, versions: SupportedProtocolVersions) -> Self {
354        self.supported_protocol_versions = Some(versions);
355        self
356    }
357
358    pub fn with_disable_pruning(mut self, disable_pruning: bool) -> Self {
359        self.disable_pruning = disable_pruning;
360        self
361    }
362
363    pub fn with_expensive_safety_check_config(
364        mut self,
365        expensive_safety_check_config: ExpensiveSafetyCheckConfig,
366    ) -> Self {
367        self.expensive_safety_check_config = Some(expensive_safety_check_config);
368        self
369    }
370
371    pub fn with_db_path(mut self, db_path: PathBuf) -> Self {
372        self.db_path = Some(db_path);
373        self
374    }
375
376    pub fn with_network_address(mut self, network_address: Multiaddr) -> Self {
377        self.network_address = Some(network_address);
378        self
379    }
380
381    pub fn with_json_rpc_address(mut self, json_rpc_address: impl Into<SocketAddr>) -> Self {
382        self.json_rpc_address = Some(json_rpc_address.into());
383        self
384    }
385
386    pub fn with_metrics_address(mut self, metrics_address: impl Into<SocketAddr>) -> Self {
387        self.metrics_address = Some(metrics_address.into());
388        self
389    }
390
391    pub fn with_admin_interface_address(
392        mut self,
393        admin_interface_address: Option<impl Into<SocketAddr>>,
394    ) -> Self {
395        self.admin_interface_address = admin_interface_address.map(|addr| addr.into());
396        self
397    }
398
399    pub fn with_genesis(mut self, genesis: Genesis) -> Self {
400        self.genesis = Some(genesis);
401        self
402    }
403
404    pub fn with_p2p_external_address(mut self, p2p_external_address: Multiaddr) -> Self {
405        self.p2p_external_address = Some(p2p_external_address);
406        self
407    }
408
409    pub fn with_p2p_listen_address(mut self, p2p_listen_address: impl Into<SocketAddr>) -> Self {
410        self.p2p_listen_address = Some(p2p_listen_address.into());
411        self
412    }
413
414    pub fn with_network_key_pair(mut self, network_key_pair: Option<NetworkKeyPair>) -> Self {
415        if let Some(network_key_pair) = network_key_pair {
416            self.network_key_pair = Some(KeyPairWithPath::new(network_to_simple_keypair(
417                &network_key_pair,
418            )));
419        }
420        self
421    }
422
423    pub fn with_run_with_range(mut self, run_with_range: Option<RunWithRange>) -> Self {
424        if let Some(run_with_range) = run_with_range {
425            self.run_with_range = Some(run_with_range);
426        }
427        self
428    }
429
430    pub fn with_policy_config(mut self, config: Option<PolicyConfig>) -> Self {
431        self.policy_config = config;
432        self
433    }
434
435    pub fn with_fw_config(mut self, config: Option<RemoteFirewallConfig>) -> Self {
436        self.fw_config = config;
437        self
438    }
439
440    pub fn with_data_ingestion_dir(mut self, path: Option<PathBuf>) -> Self {
441        self.data_ingestion_dir = path;
442        self
443    }
444
445    pub fn with_iota_names_config(mut self, config: Option<IotaNamesConfig>) -> Self {
446        self.iota_names_config = config;
447        self
448    }
449
450    pub fn with_enable_grpc_api(mut self, enable_grpc_api: bool) -> Self {
451        self.enable_grpc_api = enable_grpc_api;
452        self
453    }
454
455    pub fn with_grpc_api_config(mut self, config: GrpcApiConfig) -> Self {
456        self.grpc_api_config = Some(config);
457        self
458    }
459
460    pub fn with_discovery_config(mut self, discovery_config: DiscoveryConfig) -> Self {
461        self.discovery_config = Some(discovery_config);
462        self
463    }
464
465    /// Build the fullnode config against the given validator configs.
466    ///
467    /// # Panics
468    ///
469    /// Panics if [`Self::try_build_from_parts`] returns an error.
470    pub fn build_from_parts<R: rand::CryptoRng>(
471        self,
472        rng: &mut R,
473        validator_configs: &[NodeConfig],
474        genesis: iota_config::node::Genesis,
475    ) -> NodeConfig {
476        self.try_build_from_parts(rng, validator_configs, genesis)
477            .unwrap_or_else(|err| panic!("{err:#}"))
478    }
479
480    /// Build the fullnode config against the given validator configs, giving
481    /// the fullnode freshly generated key pairs and addresses.
482    ///
483    /// Fails and panics as [`Self::try_build_from_genesis_config`] does.
484    pub fn try_build_from_parts<R: rand::CryptoRng>(
485        self,
486        rng: &mut R,
487        validator_configs: &[NodeConfig],
488        genesis: iota_config::node::Genesis,
489    ) -> anyhow::Result<NodeConfig> {
490        // ValidatorGenesisConfig is the swarm config's type for a node's key
491        // pairs and addresses, on a fullnode as well as on a validator.
492        let genesis_config = ValidatorGenesisConfigBuilder::new().build(rng);
493        self.try_build_from_genesis_config(genesis_config, validator_configs, genesis)
494    }
495
496    /// Build the fullnode config against the given validator configs, taking
497    /// the fullnode's key pairs and addresses from `genesis_config`.
498    ///
499    /// Addresses set on this builder still win. A caller that persists
500    /// `genesis_config` gets the same node config, and the same db path, on
501    /// every build.
502    ///
503    /// Fails if a validator config has no `p2p-config.external-address`,
504    /// which the fullnode's seed peers are derived from.
505    ///
506    /// # Panics
507    ///
508    /// Panics on failures the config cannot be built without: creating the
509    /// temporary config directory, allocating a local port, or parsing a
510    /// generated network address.
511    pub fn try_build_from_genesis_config(
512        self,
513        genesis_config: ValidatorGenesisConfig,
514        validator_configs: &[NodeConfig],
515        genesis: iota_config::node::Genesis,
516    ) -> anyhow::Result<NodeConfig> {
517        let ip = genesis_config
518            .network_address
519            .to_socket_addr()
520            .unwrap()
521            .ip()
522            .to_string();
523
524        let key_path = get_key_path(&genesis_config.authority_key_pair);
525        let config_directory = self
526            .config_directory
527            .unwrap_or_else(|| iota_common::tempdir().keep());
528
529        let migration_tx_data_path =
530            Some(config_directory.join(IOTA_GENESIS_MIGRATION_TX_DATA_FILENAME));
531
532        let p2p_config = {
533            let seed_peers = validator_configs
534                .iter()
535                .enumerate()
536                .map(|(index, config)| {
537                    let address = config.p2p_config.external_address.clone().ok_or_else(|| {
538                        anyhow!(
539                            "validator {index} has no `p2p-config.external-address`, which the \
540                             fullnode needs to derive its seed peers: either its config never set \
541                             one or it was cleared"
542                        )
543                    })?;
544                    Ok(SeedPeer {
545                        peer_id: Some(anemo::PeerId(
546                            config.network_key_pair().public().0.to_bytes(),
547                        )),
548                        address,
549                    })
550                })
551                .collect::<anyhow::Result<Vec<_>>>()?;
552
553            P2pConfig {
554                listen_address: self.p2p_listen_address.unwrap_or_else(|| {
555                    genesis_config.p2p_listen_address.unwrap_or_else(|| {
556                        genesis_config
557                            .p2p_address
558                            .udp_multiaddr_to_listen_address()
559                            .unwrap()
560                    })
561                }),
562                external_address: self
563                    .p2p_external_address
564                    .or(Some(genesis_config.p2p_address.clone())),
565                seed_peers,
566                // Set a shorter timeout for checkpoint content download in tests, since
567                // checkpoint pruning also happens much faster, and network is local.
568                state_sync: Some(StateSyncConfig {
569                    checkpoint_content_timeout_ms: Some(10_000),
570                    ..Default::default()
571                }),
572                // Use discovery config if provided
573                discovery: self.discovery_config,
574                ..Default::default()
575            }
576        };
577
578        let json_rpc_address = self.json_rpc_address.unwrap_or_else(|| {
579            self.rpc_addr.unwrap_or_else(|| {
580                let rpc_port = self
581                    .rpc_port
582                    .unwrap_or_else(|| local_ip_utils::get_available_port(&ip));
583                format!("{ip}:{rpc_port}").parse().unwrap()
584            })
585        });
586
587        let grpc_api_config = self.grpc_api_config.or_else(|| {
588            if self.enable_grpc_api {
589                Some(GrpcApiConfig {
590                    address: format!("{ip}:{}", local_ip_utils::get_available_port(&ip))
591                        .parse()
592                        .unwrap(),
593                    ..Default::default()
594                })
595            } else {
596                // The default config, as a file without the key deserializes
597                // to. A later `--node-config-override` that turns the API on
598                // then needs nothing else.
599                Some(GrpcApiConfig::default())
600            }
601        });
602
603        let checkpoint_executor_config = CheckpointExecutorConfig {
604            data_ingestion_dir: self.data_ingestion_dir,
605            ..Default::default()
606        };
607
608        let mut pruning_config = AuthorityStorePruningConfig::default();
609        if self.disable_pruning {
610            pruning_config.set_num_epochs_to_retain_for_checkpoints(None);
611            pruning_config.set_num_epochs_to_retain(u64::MAX);
612        };
613
614        Ok(NodeConfig {
615            authority_key_pair: AuthorityKeyPairWithPath::new(genesis_config.authority_key_pair),
616            account_key_pair: KeyPairWithPath::new(genesis_config.account_key_pair),
617            protocol_key_pair: KeyPairWithPath::new(network_to_simple_keypair(
618                &genesis_config.protocol_key_pair,
619            )),
620            network_key_pair: self.network_key_pair.unwrap_or(KeyPairWithPath::new(
621                network_to_simple_keypair(&genesis_config.network_key_pair),
622            )),
623            db_path: self
624                .db_path
625                .unwrap_or(config_directory.join(FULL_NODE_DB_PATH).join(key_path)),
626            network_address: self
627                .network_address
628                .unwrap_or(genesis_config.network_address),
629            metrics_address: self
630                .metrics_address
631                .unwrap_or(genesis_config.metrics_address),
632            admin_interface_address: self
633                .admin_interface_address
634                .unwrap_or(genesis_config.admin_interface_address),
635            json_rpc_address,
636            consensus_config: None,
637            enable_index_processing: default_enable_index_processing(),
638            genesis,
639            migration_tx_data_path,
640            grpc_load_shed: None,
641            // Effectively unlimited: tests and benchmarks must not be
642            // throttled.
643            grpc_concurrency_limit_per_core: NonZeroUsize::new(500_000_000).unwrap(),
644            p2p_config,
645            authority_store_pruning_config: pruning_config,
646            end_of_epoch_broadcast_channel_capacity:
647                default_end_of_epoch_broadcast_channel_capacity(),
648            checkpoint_executor_config,
649            metrics: None,
650            supported_protocol_versions: self.supported_protocol_versions,
651            expensive_safety_check_config: self
652                .expensive_safety_check_config
653                .unwrap_or_else(ExpensiveSafetyCheckConfig::new_enable_all),
654            transaction_deny_config: Default::default(),
655            certificate_deny_config: Default::default(),
656            state_debug_dump_config: Default::default(),
657            checkpoint_archive_config: None,
658            state_snapshot_write_config: self.state_snapshot_write_config.unwrap_or_default(),
659            indexer_max_subscriptions: Default::default(),
660            transaction_kv_store_read_config: Default::default(),
661            transaction_kv_store_write_config: Default::default(),
662            authority_overload_config: Default::default(),
663            run_with_range: self.run_with_range,
664            jsonrpc_server_type: None,
665            policy_config: self.policy_config,
666            firewall_config: self.fw_config,
667            execution_cache_config: ExecutionCacheConfig::default(),
668            full_checkpoint_contents_cache_size_mb: default_full_checkpoint_contents_cache_size_mb(
669            ),
670            // This is a validator specific feature.
671            enable_validator_tx_finalizer: false,
672            // No effect on a fullnode (soft-locking runs only in the validator
673            // submit path); kept at the default so the config mirrors production.
674            enable_soft_locking: true,
675            verifier_signing_config: VerifierSigningConfig::default(),
676            enable_db_write_stall: None,
677            iota_names_config: self.iota_names_config,
678            enable_grpc_api: self.enable_grpc_api,
679            grpc_api_config,
680            chain_override_for_testing: self.chain_override,
681            validator_client_monitor_config: None,
682        })
683    }
684
685    /// Build the fullnode config against the given network config.
686    ///
687    /// # Panics
688    ///
689    /// Panics if [`Self::try_build`] returns an error.
690    pub fn build<R: rand::CryptoRng>(
691        self,
692        rng: &mut R,
693        network_config: &NetworkConfig,
694    ) -> NodeConfig {
695        self.try_build(rng, network_config)
696            .unwrap_or_else(|err| panic!("{err:#}"))
697    }
698
699    /// Build the fullnode config against the given network config.
700    ///
701    /// Fails and panics as [`Self::try_build_from_genesis_config`] does.
702    pub fn try_build<R: rand::CryptoRng>(
703        self,
704        rng: &mut R,
705        network_config: &NetworkConfig,
706    ) -> anyhow::Result<NodeConfig> {
707        let genesis = self.genesis_for(network_config);
708        self.try_build_from_parts(rng, network_config.validator_configs(), genesis)
709    }
710
711    /// Build the fullnode config against the given network config, taking the
712    /// fullnode's key pairs and addresses from `genesis_config`.
713    ///
714    /// Fails and panics as [`Self::try_build_from_genesis_config`] does.
715    pub fn try_build_with_genesis_config(
716        self,
717        genesis_config: ValidatorGenesisConfig,
718        network_config: &NetworkConfig,
719    ) -> anyhow::Result<NodeConfig> {
720        let genesis = self.genesis_for(network_config);
721        self.try_build_from_genesis_config(
722            genesis_config,
723            network_config.validator_configs(),
724            genesis,
725        )
726    }
727
728    fn genesis_for(&self, network_config: &NetworkConfig) -> Genesis {
729        self.genesis
730            .as_ref()
731            .or_else(|| network_config.get_validator_genesis())
732            .cloned()
733            .unwrap_or_else(|| Genesis::new(network_config.genesis.clone()))
734    }
735}
736
737/// Given a validator keypair, return a path that can be used to identify the
738/// validator.
739fn get_key_path(key_pair: &AuthorityKeyPair) -> String {
740    let public_key: AuthorityPublicKeyBytes = key_pair.public().into();
741    let mut key_path = Hex::encode(public_key);
742    // 12 is rather arbitrary here but it's a nice balance between being short and
743    // being unique.
744    key_path.truncate(12);
745    key_path
746}
747
748#[cfg(test)]
749mod tests {
750    use rand::{rand_core::UnwrapErr, rngs::SysRng};
751
752    use super::{FullnodeConfigBuilder, Genesis, ValidatorGenesisConfigBuilder};
753
754    /// The addresses and key pairs of a fullnode built from a genesis config
755    /// entry, which is what makes them the same on every build.
756    #[test]
757    fn a_fullnode_takes_its_addresses_from_its_genesis_config() {
758        let dir = tempfile::TempDir::new().unwrap();
759        let mut genesis_config = ValidatorGenesisConfigBuilder::new()
760            .with_ip("127.0.0.1".to_owned())
761            .build(&mut UnwrapErr(SysRng));
762        genesis_config.metrics_address = ([127, 0, 0, 1], 9184).into();
763        genesis_config.admin_interface_address = ([127, 0, 0, 1], 9185).into();
764        genesis_config.p2p_address = "/ip4/127.0.0.1/udp/9186/http".parse().unwrap();
765
766        let config = FullnodeConfigBuilder::new()
767            .with_config_directory(dir.path().to_path_buf())
768            .try_build_from_genesis_config(
769                genesis_config.copy_with_private_keys(),
770                &[],
771                Genesis::new_empty(),
772            )
773            .unwrap();
774
775        assert_eq!(config.metrics_address.to_string(), "127.0.0.1:9184");
776        assert_eq!(config.admin_interface_address.to_string(), "127.0.0.1:9185");
777        assert_eq!(
778            config.p2p_config.external_address.as_ref().unwrap(),
779            &genesis_config.p2p_address
780        );
781        assert_eq!(
782            config.p2p_config.listen_address.to_string(),
783            "127.0.0.1:9186"
784        );
785
786        // The same entry gives the same node config, db path included.
787        let same = FullnodeConfigBuilder::new()
788            .with_config_directory(dir.path().to_path_buf())
789            .try_build_from_genesis_config(genesis_config, &[], Genesis::new_empty())
790            .unwrap();
791        assert_eq!(config.db_path, same.db_path);
792        assert_eq!(
793            config.authority_public_key(),
794            same.authority_public_key(),
795            "the fullnode's identity is not the one its genesis config gives it"
796        );
797    }
798
799    #[test]
800    fn an_address_set_on_the_builder_wins_over_the_genesis_config() {
801        let mut genesis_config = ValidatorGenesisConfigBuilder::new()
802            .with_ip("127.0.0.1".to_owned())
803            .build(&mut UnwrapErr(SysRng));
804        genesis_config.metrics_address = ([127, 0, 0, 1], 9184).into();
805        genesis_config.admin_interface_address = ([127, 0, 0, 1], 9185).into();
806
807        let config = FullnodeConfigBuilder::new()
808            .with_admin_interface_address(Some(([127, 0, 0, 1], 1337)))
809            .try_build_from_genesis_config(genesis_config, &[], Genesis::new_empty())
810            .unwrap();
811
812        assert_eq!(config.admin_interface_address.to_string(), "127.0.0.1:1337");
813        assert_eq!(config.metrics_address.to_string(), "127.0.0.1:9184");
814    }
815}