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