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