Skip to main content

iota_swarm_config/
node_config_builder.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{net::SocketAddr, path::PathBuf};
6
7use fastcrypto::{
8    encoding::{Encoding, Hex},
9    traits::KeyPair,
10};
11use iota_config::{
12    AUTHORITIES_DB_NAME, CONSENSUS_DB_NAME, ConsensusConfig, FULL_NODE_DB_PATH,
13    IOTA_GENESIS_MIGRATION_TX_DATA_FILENAME, NodeConfig, local_ip_utils,
14    node::{
15        AuthorityKeyPairWithPath, AuthorityOverloadConfig, AuthorityStorePruningConfig,
16        CheckpointExecutorConfig, DBCheckpointConfig, DEFAULT_GRPC_CONCURRENCY_LIMIT,
17        ExecutionCacheConfig, ExpensiveSafetyCheckConfig, Genesis, GrpcApiConfig, KeyPairWithPath,
18        RunWithRange, StateArchiveConfig, StateSnapshotConfig, default_enable_index_processing,
19        default_end_of_epoch_broadcast_channel_capacity,
20        default_full_checkpoint_contents_cache_size_mb,
21    },
22    p2p::{DiscoveryConfig, P2pConfig, SeedPeer, StateSyncConfig},
23    verifier_signing_config::VerifierSigningConfig,
24};
25use iota_names::config::IotaNamesConfig;
26use iota_protocol_config::Chain;
27use iota_types::{
28    crypto::{AuthorityKeyPair, AuthorityPublicKeyBytes, IotaKeyPair, NetworkKeyPair},
29    multiaddr::Multiaddr,
30    supported_protocol_versions::SupportedProtocolVersions,
31    traffic_control::{PolicyConfig, RemoteFirewallConfig},
32};
33
34use crate::{
35    genesis_config::{ValidatorGenesisConfig, ValidatorGenesisConfigBuilder},
36    network_config::NetworkConfig,
37};
38
39/// This builder contains information that's not included in
40/// ValidatorGenesisConfig for building a validator NodeConfig. It can be used
41/// to build either a genesis validator or a new validator.
42#[derive(Clone, Default)]
43pub struct ValidatorConfigBuilder {
44    config_directory: Option<PathBuf>,
45    supported_protocol_versions: Option<SupportedProtocolVersions>,
46    force_unpruned_checkpoints: bool,
47    authority_overload_config: Option<AuthorityOverloadConfig>,
48    execution_cache_config: Option<ExecutionCacheConfig>,
49    data_ingestion_dir: Option<PathBuf>,
50    policy_config: Option<PolicyConfig>,
51    firewall_config: Option<RemoteFirewallConfig>,
52    max_submit_position: Option<usize>,
53    submit_delay_step_override_millis: Option<u64>,
54    discovery_config: Option<DiscoveryConfig>,
55    chain_override: Option<Chain>,
56}
57
58impl ValidatorConfigBuilder {
59    pub fn new() -> Self {
60        Self {
61            ..Default::default()
62        }
63    }
64
65    pub fn with_chain_override(mut self, chain: Chain) -> Self {
66        assert!(self.chain_override.is_none(), "Chain override already set");
67        self.chain_override = Some(chain);
68        self
69    }
70
71    pub fn with_config_directory(mut self, config_directory: PathBuf) -> Self {
72        assert!(self.config_directory.is_none());
73        self.config_directory = Some(config_directory);
74        self
75    }
76
77    pub fn with_supported_protocol_versions(
78        mut self,
79        supported_protocol_versions: SupportedProtocolVersions,
80    ) -> Self {
81        assert!(self.supported_protocol_versions.is_none());
82        self.supported_protocol_versions = Some(supported_protocol_versions);
83        self
84    }
85
86    pub fn with_unpruned_checkpoints(mut self) -> Self {
87        self.force_unpruned_checkpoints = true;
88        self
89    }
90
91    pub fn with_authority_overload_config(mut self, config: AuthorityOverloadConfig) -> Self {
92        self.authority_overload_config = Some(config);
93        self
94    }
95
96    pub fn with_execution_cache_config(mut self, config: ExecutionCacheConfig) -> Self {
97        self.execution_cache_config = Some(config);
98        self
99    }
100
101    pub fn with_data_ingestion_dir(mut self, path: PathBuf) -> Self {
102        self.data_ingestion_dir = Some(path);
103        self
104    }
105
106    pub fn with_policy_config(mut self, config: Option<PolicyConfig>) -> Self {
107        self.policy_config = config;
108        self
109    }
110
111    pub fn with_firewall_config(mut self, config: Option<RemoteFirewallConfig>) -> Self {
112        self.firewall_config = config;
113        self
114    }
115
116    pub fn with_max_submit_position(mut self, max_submit_position: usize) -> Self {
117        self.max_submit_position = Some(max_submit_position);
118        self
119    }
120
121    pub fn with_submit_delay_step_override_millis(
122        mut self,
123        submit_delay_step_override_millis: u64,
124    ) -> Self {
125        self.submit_delay_step_override_millis = Some(submit_delay_step_override_millis);
126        self
127    }
128
129    pub fn with_discovery_config(mut self, discovery_config: DiscoveryConfig) -> Self {
130        self.discovery_config = Some(discovery_config);
131        self
132    }
133
134    pub fn build_without_genesis(self, validator: ValidatorGenesisConfig) -> NodeConfig {
135        let key_path = get_key_path(&validator.authority_key_pair);
136        let config_directory = self
137            .config_directory
138            .unwrap_or_else(|| iota_common::tempdir().keep());
139        let migration_tx_data_path =
140            Some(config_directory.join(IOTA_GENESIS_MIGRATION_TX_DATA_FILENAME));
141        let db_path = config_directory
142            .join(AUTHORITIES_DB_NAME)
143            .join(key_path.clone());
144        let network_address = validator.network_address;
145        let consensus_db_path = config_directory.join(CONSENSUS_DB_NAME).join(key_path);
146        let localhost = local_ip_utils::localhost_for_testing();
147        let consensus_config = ConsensusConfig {
148            db_path: consensus_db_path,
149            db_retention_epochs: None,
150            db_pruner_period_secs: None,
151            max_pending_transactions: None,
152            max_submit_position: self.max_submit_position,
153            submit_delay_step_override_millis: self.submit_delay_step_override_millis,
154            parameters: Default::default(),
155            graduated_load_shedding_soft_limit_pct: Default::default(),
156        };
157
158        let p2p_config = P2pConfig {
159            listen_address: validator.p2p_listen_address.unwrap_or_else(|| {
160                validator
161                    .p2p_address
162                    .udp_multiaddr_to_listen_address()
163                    .unwrap()
164            }),
165            external_address: Some(validator.p2p_address),
166            // Set a shorter timeout for checkpoint content download in tests, since
167            // checkpoint pruning also happens much faster, and network is local.
168            state_sync: Some(StateSyncConfig {
169                checkpoint_content_timeout_ms: Some(10_000),
170                ..Default::default()
171            }),
172            // Use discovery config if provided
173            discovery: self.discovery_config,
174            ..Default::default()
175        };
176
177        let mut pruning_config = AuthorityStorePruningConfig::default();
178        if self.force_unpruned_checkpoints {
179            pruning_config.set_num_epochs_to_retain_for_checkpoints(None);
180        }
181        let pruning_config = pruning_config;
182        let checkpoint_executor_config = CheckpointExecutorConfig {
183            data_ingestion_dir: self.data_ingestion_dir,
184            ..Default::default()
185        };
186
187        NodeConfig {
188            authority_key_pair: AuthorityKeyPairWithPath::new(validator.authority_key_pair),
189            network_key_pair: KeyPairWithPath::new(IotaKeyPair::Ed25519(
190                validator.network_key_pair,
191            )),
192            account_key_pair: KeyPairWithPath::new(validator.account_key_pair),
193            protocol_key_pair: KeyPairWithPath::new(IotaKeyPair::Ed25519(
194                validator.protocol_key_pair,
195            )),
196            db_path,
197            network_address,
198            metrics_address: validator.metrics_address,
199            admin_interface_address: validator.admin_interface_address,
200            json_rpc_address: local_ip_utils::new_tcp_address_for_testing(&localhost)
201                .to_socket_addr()
202                .unwrap(),
203            consensus_config: Some(consensus_config),
204            enable_index_processing: default_enable_index_processing(),
205            genesis: Genesis::new_empty(),
206            migration_tx_data_path,
207            grpc_load_shed: None,
208            grpc_concurrency_limit: Some(DEFAULT_GRPC_CONCURRENCY_LIMIT),
209            p2p_config,
210            authority_store_pruning_config: pruning_config,
211            end_of_epoch_broadcast_channel_capacity:
212                default_end_of_epoch_broadcast_channel_capacity(),
213            checkpoint_executor_config,
214            metrics: None,
215            supported_protocol_versions: self.supported_protocol_versions,
216            db_checkpoint_config: Default::default(),
217            // By default, expensive checks will be enabled in debug build, but not in release
218            // build.
219            expensive_safety_check_config: ExpensiveSafetyCheckConfig::default(),
220            transaction_deny_config: Default::default(),
221            certificate_deny_config: Default::default(),
222            state_debug_dump_config: Default::default(),
223            state_archive_write_config: StateArchiveConfig::default(),
224            state_archive_read_config: vec![],
225            state_snapshot_write_config: StateSnapshotConfig::default(),
226            indexer_max_subscriptions: Default::default(),
227            transaction_kv_store_read_config: Default::default(),
228            transaction_kv_store_write_config: None,
229            authority_overload_config: self.authority_overload_config.unwrap_or_default(),
230            execution_cache_config: self.execution_cache_config.unwrap_or_default(),
231            full_checkpoint_contents_cache_size_mb: default_full_checkpoint_contents_cache_size_mb(
232            ),
233            run_with_range: None,
234            jsonrpc_server_type: None,
235            policy_config: self.policy_config,
236            firewall_config: self.firewall_config,
237            enable_validator_tx_finalizer: true,
238            enable_soft_locking: true,
239            verifier_signing_config: VerifierSigningConfig::default(),
240            enable_db_write_stall: None,
241            iota_names_config: None,
242            enable_grpc_api: false,
243            grpc_api_config: None,
244            chain_override_for_testing: self.chain_override,
245            validator_client_monitor_config: None,
246        }
247    }
248
249    pub fn build(
250        self,
251        validator: ValidatorGenesisConfig,
252        genesis: iota_config::genesis::Genesis,
253    ) -> NodeConfig {
254        let mut config = self.build_without_genesis(validator);
255        config.genesis = iota_config::node::Genesis::new(genesis);
256        config
257    }
258
259    pub fn build_new_validator<R: rand::RngCore + rand::CryptoRng>(
260        self,
261        rng: &mut R,
262        network_config: &NetworkConfig,
263    ) -> NodeConfig {
264        let validator_config = ValidatorGenesisConfigBuilder::new().build(rng);
265        self.build(validator_config, network_config.genesis.clone())
266    }
267}
268
269#[derive(Clone, Debug, Default)]
270pub struct FullnodeConfigBuilder {
271    config_directory: Option<PathBuf>,
272    // port for json rpc api
273    rpc_port: Option<u16>,
274    rpc_addr: Option<SocketAddr>,
275    supported_protocol_versions: Option<SupportedProtocolVersions>,
276    db_checkpoint_config: Option<DBCheckpointConfig>,
277    expensive_safety_check_config: Option<ExpensiveSafetyCheckConfig>,
278    db_path: Option<PathBuf>,
279    network_address: Option<Multiaddr>,
280    json_rpc_address: Option<SocketAddr>,
281    metrics_address: Option<SocketAddr>,
282    admin_interface_address: Option<SocketAddr>,
283    genesis: Option<Genesis>,
284    p2p_external_address: Option<Multiaddr>,
285    p2p_listen_address: Option<SocketAddr>,
286    network_key_pair: Option<KeyPairWithPath>,
287    run_with_range: Option<RunWithRange>,
288    policy_config: Option<PolicyConfig>,
289    fw_config: Option<RemoteFirewallConfig>,
290    data_ingestion_dir: Option<PathBuf>,
291    disable_pruning: bool,
292    iota_names_config: Option<IotaNamesConfig>,
293    enable_grpc_api: bool,
294    grpc_api_config: Option<GrpcApiConfig>,
295    discovery_config: Option<DiscoveryConfig>,
296    chain_override: Option<Chain>,
297}
298
299impl FullnodeConfigBuilder {
300    pub fn new() -> Self {
301        Self::default()
302    }
303
304    pub fn with_chain_override(mut self, chain: Chain) -> Self {
305        assert!(self.chain_override.is_none(), "Chain override already set");
306        self.chain_override = Some(chain);
307        self
308    }
309
310    pub fn with_config_directory(mut self, config_directory: PathBuf) -> Self {
311        self.config_directory = Some(config_directory);
312        self
313    }
314
315    pub fn with_rpc_port(mut self, port: u16) -> Self {
316        assert!(self.rpc_addr.is_none() && self.rpc_port.is_none());
317        self.rpc_port = Some(port);
318        self
319    }
320
321    pub fn with_rpc_addr(mut self, addr: impl Into<SocketAddr>) -> Self {
322        assert!(self.rpc_addr.is_none() && self.rpc_port.is_none());
323        self.rpc_addr = Some(addr.into());
324        self
325    }
326
327    pub fn with_supported_protocol_versions(mut self, versions: SupportedProtocolVersions) -> Self {
328        self.supported_protocol_versions = Some(versions);
329        self
330    }
331
332    pub fn with_db_checkpoint_config(mut self, db_checkpoint_config: DBCheckpointConfig) -> Self {
333        self.db_checkpoint_config = Some(db_checkpoint_config);
334        self
335    }
336
337    pub fn with_disable_pruning(mut self, disable_pruning: bool) -> Self {
338        self.disable_pruning = disable_pruning;
339        self
340    }
341
342    pub fn with_expensive_safety_check_config(
343        mut self,
344        expensive_safety_check_config: ExpensiveSafetyCheckConfig,
345    ) -> Self {
346        self.expensive_safety_check_config = Some(expensive_safety_check_config);
347        self
348    }
349
350    pub fn with_db_path(mut self, db_path: PathBuf) -> Self {
351        self.db_path = Some(db_path);
352        self
353    }
354
355    pub fn with_network_address(mut self, network_address: Multiaddr) -> Self {
356        self.network_address = Some(network_address);
357        self
358    }
359
360    pub fn with_json_rpc_address(mut self, json_rpc_address: impl Into<SocketAddr>) -> Self {
361        self.json_rpc_address = Some(json_rpc_address.into());
362        self
363    }
364
365    pub fn with_metrics_address(mut self, metrics_address: impl Into<SocketAddr>) -> Self {
366        self.metrics_address = Some(metrics_address.into());
367        self
368    }
369
370    pub fn with_admin_interface_address(
371        mut self,
372        admin_interface_address: Option<impl Into<SocketAddr>>,
373    ) -> Self {
374        self.admin_interface_address = admin_interface_address.map(|addr| addr.into());
375        self
376    }
377
378    pub fn with_genesis(mut self, genesis: Genesis) -> Self {
379        self.genesis = Some(genesis);
380        self
381    }
382
383    pub fn with_p2p_external_address(mut self, p2p_external_address: Multiaddr) -> Self {
384        self.p2p_external_address = Some(p2p_external_address);
385        self
386    }
387
388    pub fn with_p2p_listen_address(mut self, p2p_listen_address: impl Into<SocketAddr>) -> Self {
389        self.p2p_listen_address = Some(p2p_listen_address.into());
390        self
391    }
392
393    pub fn with_network_key_pair(mut self, network_key_pair: Option<NetworkKeyPair>) -> Self {
394        if let Some(network_key_pair) = network_key_pair {
395            self.network_key_pair =
396                Some(KeyPairWithPath::new(IotaKeyPair::Ed25519(network_key_pair)));
397        }
398        self
399    }
400
401    pub fn with_run_with_range(mut self, run_with_range: Option<RunWithRange>) -> Self {
402        if let Some(run_with_range) = run_with_range {
403            self.run_with_range = Some(run_with_range);
404        }
405        self
406    }
407
408    pub fn with_policy_config(mut self, config: Option<PolicyConfig>) -> Self {
409        self.policy_config = config;
410        self
411    }
412
413    pub fn with_fw_config(mut self, config: Option<RemoteFirewallConfig>) -> Self {
414        self.fw_config = config;
415        self
416    }
417
418    pub fn with_data_ingestion_dir(mut self, path: Option<PathBuf>) -> Self {
419        self.data_ingestion_dir = path;
420        self
421    }
422
423    pub fn with_iota_names_config(mut self, config: Option<IotaNamesConfig>) -> Self {
424        self.iota_names_config = config;
425        self
426    }
427
428    pub fn with_enable_grpc_api(mut self, enable_grpc_api: bool) -> Self {
429        self.enable_grpc_api = enable_grpc_api;
430        self
431    }
432
433    pub fn with_grpc_api_config(mut self, config: GrpcApiConfig) -> Self {
434        self.grpc_api_config = Some(config);
435        self
436    }
437
438    pub fn with_discovery_config(mut self, discovery_config: DiscoveryConfig) -> Self {
439        self.discovery_config = Some(discovery_config);
440        self
441    }
442
443    pub fn build_from_parts<R: rand::RngCore + rand::CryptoRng>(
444        self,
445        rng: &mut R,
446        validator_configs: &[NodeConfig],
447        genesis: iota_config::node::Genesis,
448    ) -> NodeConfig {
449        // Take advantage of ValidatorGenesisConfigBuilder to build the keypairs and
450        // addresses, even though this is a fullnode.
451        let validator_config = ValidatorGenesisConfigBuilder::new().build(rng);
452        let ip = validator_config
453            .network_address
454            .to_socket_addr()
455            .unwrap()
456            .ip()
457            .to_string();
458
459        let key_path = get_key_path(&validator_config.authority_key_pair);
460        let config_directory = self
461            .config_directory
462            .unwrap_or_else(|| iota_common::tempdir().keep());
463
464        let migration_tx_data_path =
465            Some(config_directory.join(IOTA_GENESIS_MIGRATION_TX_DATA_FILENAME));
466
467        let p2p_config = {
468            let seed_peers = validator_configs
469                .iter()
470                .map(|config| SeedPeer {
471                    peer_id: Some(anemo::PeerId(
472                        config.network_key_pair().public().0.to_bytes(),
473                    )),
474                    address: config.p2p_config.external_address.clone().unwrap(),
475                })
476                .collect();
477
478            P2pConfig {
479                listen_address: self.p2p_listen_address.unwrap_or_else(|| {
480                    validator_config.p2p_listen_address.unwrap_or_else(|| {
481                        validator_config
482                            .p2p_address
483                            .udp_multiaddr_to_listen_address()
484                            .unwrap()
485                    })
486                }),
487                external_address: self
488                    .p2p_external_address
489                    .or(Some(validator_config.p2p_address.clone())),
490                seed_peers,
491                // Set a shorter timeout for checkpoint content download in tests, since
492                // checkpoint pruning also happens much faster, and network is local.
493                state_sync: Some(StateSyncConfig {
494                    checkpoint_content_timeout_ms: Some(10_000),
495                    ..Default::default()
496                }),
497                // Use discovery config if provided
498                discovery: self.discovery_config,
499                ..Default::default()
500            }
501        };
502
503        let json_rpc_address = self.rpc_addr.unwrap_or_else(|| {
504            let rpc_port = self
505                .rpc_port
506                .unwrap_or_else(|| local_ip_utils::get_available_port(&ip));
507            format!("{ip}:{rpc_port}").parse().unwrap()
508        });
509
510        let grpc_api_config = self.grpc_api_config.or_else(|| {
511            if self.enable_grpc_api {
512                Some(GrpcApiConfig {
513                    address: format!("{ip}:{}", local_ip_utils::get_available_port(&ip))
514                        .parse()
515                        .unwrap(),
516                    ..Default::default()
517                })
518            } else {
519                None
520            }
521        });
522
523        let checkpoint_executor_config = CheckpointExecutorConfig {
524            data_ingestion_dir: self.data_ingestion_dir,
525            ..Default::default()
526        };
527
528        let mut pruning_config = AuthorityStorePruningConfig::default();
529        if self.disable_pruning {
530            pruning_config.set_num_epochs_to_retain_for_checkpoints(None);
531            pruning_config.set_num_epochs_to_retain(u64::MAX);
532        };
533
534        NodeConfig {
535            authority_key_pair: AuthorityKeyPairWithPath::new(validator_config.authority_key_pair),
536            account_key_pair: KeyPairWithPath::new(validator_config.account_key_pair),
537            protocol_key_pair: KeyPairWithPath::new(IotaKeyPair::Ed25519(
538                validator_config.protocol_key_pair,
539            )),
540            network_key_pair: self.network_key_pair.unwrap_or(KeyPairWithPath::new(
541                IotaKeyPair::Ed25519(validator_config.network_key_pair),
542            )),
543            db_path: self
544                .db_path
545                .unwrap_or(config_directory.join(FULL_NODE_DB_PATH).join(key_path)),
546            network_address: self
547                .network_address
548                .unwrap_or(validator_config.network_address),
549            metrics_address: self
550                .metrics_address
551                .unwrap_or(local_ip_utils::new_local_tcp_socket_for_testing()),
552            admin_interface_address: self
553                .admin_interface_address
554                .unwrap_or(local_ip_utils::new_local_tcp_socket_for_testing()),
555            json_rpc_address: self.json_rpc_address.unwrap_or(json_rpc_address),
556            consensus_config: None,
557            enable_index_processing: default_enable_index_processing(),
558            genesis,
559            migration_tx_data_path,
560            grpc_load_shed: None,
561            grpc_concurrency_limit: None,
562            p2p_config,
563            authority_store_pruning_config: pruning_config,
564            end_of_epoch_broadcast_channel_capacity:
565                default_end_of_epoch_broadcast_channel_capacity(),
566            checkpoint_executor_config,
567            metrics: None,
568            supported_protocol_versions: self.supported_protocol_versions,
569            db_checkpoint_config: self.db_checkpoint_config.unwrap_or_default(),
570            expensive_safety_check_config: self
571                .expensive_safety_check_config
572                .unwrap_or_else(ExpensiveSafetyCheckConfig::new_enable_all),
573            transaction_deny_config: Default::default(),
574            certificate_deny_config: Default::default(),
575            state_debug_dump_config: Default::default(),
576            state_archive_write_config: StateArchiveConfig::default(),
577            state_archive_read_config: vec![],
578            state_snapshot_write_config: StateSnapshotConfig::default(),
579            indexer_max_subscriptions: Default::default(),
580            transaction_kv_store_read_config: Default::default(),
581            transaction_kv_store_write_config: Default::default(),
582            authority_overload_config: Default::default(),
583            run_with_range: self.run_with_range,
584            jsonrpc_server_type: None,
585            policy_config: self.policy_config,
586            firewall_config: self.fw_config,
587            execution_cache_config: ExecutionCacheConfig::default(),
588            full_checkpoint_contents_cache_size_mb: default_full_checkpoint_contents_cache_size_mb(
589            ),
590            // This is a validator specific feature.
591            enable_validator_tx_finalizer: false,
592            // No effect on a fullnode (soft-locking runs only in the validator
593            // submit path); kept at the default so the config mirrors production.
594            enable_soft_locking: true,
595            verifier_signing_config: VerifierSigningConfig::default(),
596            enable_db_write_stall: None,
597            iota_names_config: self.iota_names_config,
598            enable_grpc_api: self.enable_grpc_api,
599            grpc_api_config,
600            chain_override_for_testing: self.chain_override,
601            validator_client_monitor_config: None,
602        }
603    }
604
605    pub fn build<R: rand::RngCore + rand::CryptoRng>(
606        self,
607        rng: &mut R,
608        network_config: &NetworkConfig,
609    ) -> NodeConfig {
610        let genesis = self
611            .genesis
612            .as_ref()
613            .or_else(|| network_config.get_validator_genesis())
614            .cloned()
615            .unwrap_or_else(|| iota_config::node::Genesis::new(network_config.genesis.clone()));
616        self.build_from_parts(rng, network_config.validator_configs(), genesis)
617    }
618}
619
620/// Given a validator keypair, return a path that can be used to identify the
621/// validator.
622fn get_key_path(key_pair: &AuthorityKeyPair) -> String {
623    let public_key: AuthorityPublicKeyBytes = key_pair.public().into();
624    let mut key_path = Hex::encode(public_key);
625    // 12 is rather arbitrary here but it's a nice balance between being short and
626    // being unique.
627    key_path.truncate(12);
628    key_path
629}