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, time::Duration};
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, ExecutionCacheType, ExpensiveSafetyCheckConfig, Genesis,
18        GrpcApiConfig, KeyPairWithPath, RunWithRange, StateArchiveConfig, StateSnapshotConfig,
19        default_enable_index_processing, default_end_of_epoch_broadcast_channel_capacity,
20        default_zklogin_oauth_providers,
21    },
22    p2p::{DiscoveryConfig, P2pConfig, SeedPeer, StateSyncConfig},
23    verifier_signing_config::VerifierSigningConfig,
24};
25use iota_names::config::IotaNamesConfig;
26use iota_types::{
27    crypto::{AuthorityKeyPair, AuthorityPublicKeyBytes, IotaKeyPair, NetworkKeyPair},
28    multiaddr::Multiaddr,
29    supported_protocol_versions::SupportedProtocolVersions,
30    traffic_control::{PolicyConfig, RemoteFirewallConfig},
31};
32
33use crate::{
34    genesis_config::{ValidatorGenesisConfig, ValidatorGenesisConfigBuilder},
35    network_config::NetworkConfig,
36};
37
38/// This builder contains information that's not included in
39/// ValidatorGenesisConfig for building a validator NodeConfig. It can be used
40/// to build either a genesis validator or a new validator.
41#[derive(Clone, Default)]
42pub struct ValidatorConfigBuilder {
43    config_directory: Option<PathBuf>,
44    supported_protocol_versions: Option<SupportedProtocolVersions>,
45    force_unpruned_checkpoints: bool,
46    jwk_fetch_interval: Option<Duration>,
47    authority_overload_config: Option<AuthorityOverloadConfig>,
48    execution_cache_type: Option<ExecutionCacheType>,
49    execution_cache_config: Option<ExecutionCacheConfig>,
50    data_ingestion_dir: Option<PathBuf>,
51    policy_config: Option<PolicyConfig>,
52    firewall_config: Option<RemoteFirewallConfig>,
53    max_submit_position: Option<usize>,
54    submit_delay_step_override_millis: Option<u64>,
55    discovery_config: Option<DiscoveryConfig>,
56}
57
58impl ValidatorConfigBuilder {
59    pub fn new() -> Self {
60        Self {
61            ..Default::default()
62        }
63    }
64
65    pub fn with_config_directory(mut self, config_directory: PathBuf) -> Self {
66        assert!(self.config_directory.is_none());
67        self.config_directory = Some(config_directory);
68        self
69    }
70
71    pub fn with_supported_protocol_versions(
72        mut self,
73        supported_protocol_versions: SupportedProtocolVersions,
74    ) -> Self {
75        assert!(self.supported_protocol_versions.is_none());
76        self.supported_protocol_versions = Some(supported_protocol_versions);
77        self
78    }
79
80    pub fn with_unpruned_checkpoints(mut self) -> Self {
81        self.force_unpruned_checkpoints = true;
82        self
83    }
84
85    pub fn with_jwk_fetch_interval(mut self, i: Duration) -> Self {
86        self.jwk_fetch_interval = Some(i);
87        self
88    }
89
90    pub fn with_authority_overload_config(mut self, config: AuthorityOverloadConfig) -> Self {
91        self.authority_overload_config = Some(config);
92        self
93    }
94
95    pub fn with_execution_cache_type(mut self, execution_cache_type: ExecutionCacheType) -> Self {
96        self.execution_cache_type = Some(execution_cache_type);
97        self
98    }
99
100    pub fn with_execution_cache_config(mut self, config: ExecutionCacheConfig) -> Self {
101        self.execution_cache_config = Some(config);
102        self
103    }
104
105    pub fn with_data_ingestion_dir(mut self, path: PathBuf) -> Self {
106        self.data_ingestion_dir = Some(path);
107        self
108    }
109
110    pub fn with_policy_config(mut self, config: Option<PolicyConfig>) -> Self {
111        self.policy_config = config;
112        self
113    }
114
115    pub fn with_firewall_config(mut self, config: Option<RemoteFirewallConfig>) -> Self {
116        self.firewall_config = config;
117        self
118    }
119
120    pub fn with_max_submit_position(mut self, max_submit_position: usize) -> Self {
121        self.max_submit_position = Some(max_submit_position);
122        self
123    }
124
125    pub fn with_submit_delay_step_override_millis(
126        mut self,
127        submit_delay_step_override_millis: u64,
128    ) -> Self {
129        self.submit_delay_step_override_millis = Some(submit_delay_step_override_millis);
130        self
131    }
132
133    pub fn with_discovery_config(mut self, discovery_config: DiscoveryConfig) -> Self {
134        self.discovery_config = Some(discovery_config);
135        self
136    }
137
138    pub fn build_without_genesis(self, validator: ValidatorGenesisConfig) -> NodeConfig {
139        let key_path = get_key_path(&validator.authority_key_pair);
140        let config_directory = self
141            .config_directory
142            .unwrap_or_else(|| tempfile::tempdir().unwrap().keep());
143        let migration_tx_data_path =
144            Some(config_directory.join(IOTA_GENESIS_MIGRATION_TX_DATA_FILENAME));
145        let db_path = config_directory
146            .join(AUTHORITIES_DB_NAME)
147            .join(key_path.clone());
148        let network_address = validator.network_address;
149        let consensus_db_path = config_directory.join(CONSENSUS_DB_NAME).join(key_path);
150        let localhost = local_ip_utils::localhost_for_testing();
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            starfish_parameters: 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 = AuthorityStorePruningConfig::default();
182        if self.force_unpruned_checkpoints {
183            pruning_config.set_num_epochs_to_retain_for_checkpoints(None);
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(IotaKeyPair::Ed25519(
194                validator.network_key_pair,
195            )),
196            account_key_pair: KeyPairWithPath::new(validator.account_key_pair),
197            protocol_key_pair: KeyPairWithPath::new(IotaKeyPair::Ed25519(
198                validator.protocol_key_pair,
199            )),
200            db_path,
201            network_address,
202            metrics_address: validator.metrics_address,
203            admin_interface_address: local_ip_utils::new_tcp_address_for_testing(&localhost)
204                .to_socket_addr()
205                .unwrap(),
206            json_rpc_address: local_ip_utils::new_tcp_address_for_testing(&localhost)
207                .to_socket_addr()
208                .unwrap(),
209            consensus_config: Some(consensus_config),
210            remove_deprecated_tables: false,
211            enable_index_processing: default_enable_index_processing(),
212            genesis: Genesis::new_empty(),
213            migration_tx_data_path,
214            grpc_load_shed: None,
215            grpc_concurrency_limit: Some(DEFAULT_GRPC_CONCURRENCY_LIMIT),
216            p2p_config,
217            authority_store_pruning_config: pruning_config,
218            end_of_epoch_broadcast_channel_capacity:
219                default_end_of_epoch_broadcast_channel_capacity(),
220            checkpoint_executor_config,
221            metrics: None,
222            supported_protocol_versions: self.supported_protocol_versions,
223            db_checkpoint_config: Default::default(),
224            // By default, expensive checks will be enabled in debug build, but not in release
225            // build.
226            expensive_safety_check_config: ExpensiveSafetyCheckConfig::default(),
227            transaction_deny_config: Default::default(),
228            certificate_deny_config: Default::default(),
229            state_debug_dump_config: Default::default(),
230            state_archive_write_config: StateArchiveConfig::default(),
231            state_archive_read_config: vec![],
232            state_snapshot_write_config: StateSnapshotConfig::default(),
233            indexer_max_subscriptions: Default::default(),
234            transaction_kv_store_read_config: Default::default(),
235            transaction_kv_store_write_config: None,
236            enable_rest_api: true,
237            rest: Some(iota_rest_api::Config {
238                enable_unstable_apis: Some(true),
239                ..Default::default()
240            }),
241            jwk_fetch_interval_seconds: self
242                .jwk_fetch_interval
243                .map(|i| i.as_secs())
244                .unwrap_or(3600),
245            zklogin_oauth_providers: default_zklogin_oauth_providers(),
246            authority_overload_config: self.authority_overload_config.unwrap_or_default(),
247            execution_cache: self.execution_cache_type.unwrap_or_default(),
248            execution_cache_config: self.execution_cache_config.unwrap_or_default(),
249            run_with_range: None,
250            jsonrpc_server_type: None,
251            policy_config: self.policy_config,
252            firewall_config: self.firewall_config,
253            enable_validator_tx_finalizer: true,
254            verifier_signing_config: VerifierSigningConfig::default(),
255            enable_db_write_stall: None,
256            iota_names_config: None,
257            enable_grpc_api: false,
258            grpc_api_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::RngCore + 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    db_checkpoint_config: Option<DBCheckpointConfig>,
290    expensive_safety_check_config: Option<ExpensiveSafetyCheckConfig>,
291    db_path: Option<PathBuf>,
292    network_address: Option<Multiaddr>,
293    json_rpc_address: Option<SocketAddr>,
294    metrics_address: Option<SocketAddr>,
295    admin_interface_address: Option<SocketAddr>,
296    genesis: Option<Genesis>,
297    p2p_external_address: Option<Multiaddr>,
298    p2p_listen_address: Option<SocketAddr>,
299    network_key_pair: Option<KeyPairWithPath>,
300    run_with_range: Option<RunWithRange>,
301    policy_config: Option<PolicyConfig>,
302    fw_config: Option<RemoteFirewallConfig>,
303    data_ingestion_dir: Option<PathBuf>,
304    disable_pruning: bool,
305    iota_names_config: Option<IotaNamesConfig>,
306    grpc_api_config: Option<GrpcApiConfig>,
307    discovery_config: Option<DiscoveryConfig>,
308}
309
310impl FullnodeConfigBuilder {
311    pub fn new() -> Self {
312        Self::default()
313    }
314
315    pub fn with_config_directory(mut self, config_directory: PathBuf) -> Self {
316        self.config_directory = Some(config_directory);
317        self
318    }
319
320    pub fn with_rpc_port(mut self, port: u16) -> Self {
321        assert!(self.rpc_addr.is_none() && self.rpc_port.is_none());
322        self.rpc_port = Some(port);
323        self
324    }
325
326    pub fn with_rpc_addr(mut self, addr: impl Into<SocketAddr>) -> Self {
327        assert!(self.rpc_addr.is_none() && self.rpc_port.is_none());
328        self.rpc_addr = Some(addr.into());
329        self
330    }
331
332    pub fn with_supported_protocol_versions(mut self, versions: SupportedProtocolVersions) -> Self {
333        self.supported_protocol_versions = Some(versions);
334        self
335    }
336
337    pub fn with_db_checkpoint_config(mut self, db_checkpoint_config: DBCheckpointConfig) -> Self {
338        self.db_checkpoint_config = Some(db_checkpoint_config);
339        self
340    }
341
342    pub fn with_disable_pruning(mut self, disable_pruning: bool) -> Self {
343        self.disable_pruning = disable_pruning;
344        self
345    }
346
347    pub fn with_expensive_safety_check_config(
348        mut self,
349        expensive_safety_check_config: ExpensiveSafetyCheckConfig,
350    ) -> Self {
351        self.expensive_safety_check_config = Some(expensive_safety_check_config);
352        self
353    }
354
355    pub fn with_db_path(mut self, db_path: PathBuf) -> Self {
356        self.db_path = Some(db_path);
357        self
358    }
359
360    pub fn with_network_address(mut self, network_address: Multiaddr) -> Self {
361        self.network_address = Some(network_address);
362        self
363    }
364
365    pub fn with_json_rpc_address(mut self, json_rpc_address: impl Into<SocketAddr>) -> Self {
366        self.json_rpc_address = Some(json_rpc_address.into());
367        self
368    }
369
370    pub fn with_metrics_address(mut self, metrics_address: impl Into<SocketAddr>) -> Self {
371        self.metrics_address = Some(metrics_address.into());
372        self
373    }
374
375    pub fn with_admin_interface_address(
376        mut self,
377        admin_interface_address: impl Into<SocketAddr>,
378    ) -> Self {
379        self.admin_interface_address = Some(admin_interface_address.into());
380        self
381    }
382
383    pub fn with_genesis(mut self, genesis: Genesis) -> Self {
384        self.genesis = Some(genesis);
385        self
386    }
387
388    pub fn with_p2p_external_address(mut self, p2p_external_address: Multiaddr) -> Self {
389        self.p2p_external_address = Some(p2p_external_address);
390        self
391    }
392
393    pub fn with_p2p_listen_address(mut self, p2p_listen_address: impl Into<SocketAddr>) -> Self {
394        self.p2p_listen_address = Some(p2p_listen_address.into());
395        self
396    }
397
398    pub fn with_network_key_pair(mut self, network_key_pair: Option<NetworkKeyPair>) -> Self {
399        if let Some(network_key_pair) = network_key_pair {
400            self.network_key_pair =
401                Some(KeyPairWithPath::new(IotaKeyPair::Ed25519(network_key_pair)));
402        }
403        self
404    }
405
406    pub fn with_run_with_range(mut self, run_with_range: Option<RunWithRange>) -> Self {
407        if let Some(run_with_range) = run_with_range {
408            self.run_with_range = Some(run_with_range);
409        }
410        self
411    }
412
413    pub fn with_policy_config(mut self, config: Option<PolicyConfig>) -> Self {
414        self.policy_config = config;
415        self
416    }
417
418    pub fn with_fw_config(mut self, config: Option<RemoteFirewallConfig>) -> Self {
419        self.fw_config = config;
420        self
421    }
422
423    pub fn with_data_ingestion_dir(mut self, path: Option<PathBuf>) -> Self {
424        self.data_ingestion_dir = path;
425        self
426    }
427
428    pub fn with_iota_names_config(mut self, config: Option<IotaNamesConfig>) -> Self {
429        self.iota_names_config = config;
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(|| tempfile::tempdir().unwrap().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 checkpoint_executor_config = CheckpointExecutorConfig {
511            data_ingestion_dir: self.data_ingestion_dir,
512            ..Default::default()
513        };
514
515        let mut pruning_config = AuthorityStorePruningConfig::default();
516        if self.disable_pruning {
517            pruning_config.set_num_epochs_to_retain_for_checkpoints(None);
518            pruning_config.set_num_epochs_to_retain(u64::MAX);
519        };
520
521        NodeConfig {
522            authority_key_pair: AuthorityKeyPairWithPath::new(validator_config.authority_key_pair),
523            account_key_pair: KeyPairWithPath::new(validator_config.account_key_pair),
524            protocol_key_pair: KeyPairWithPath::new(IotaKeyPair::Ed25519(
525                validator_config.protocol_key_pair,
526            )),
527            network_key_pair: self.network_key_pair.unwrap_or(KeyPairWithPath::new(
528                IotaKeyPair::Ed25519(validator_config.network_key_pair),
529            )),
530            db_path: self
531                .db_path
532                .unwrap_or(config_directory.join(FULL_NODE_DB_PATH).join(key_path)),
533            network_address: self
534                .network_address
535                .unwrap_or(validator_config.network_address),
536            metrics_address: self
537                .metrics_address
538                .unwrap_or(local_ip_utils::new_local_tcp_socket_for_testing()),
539            admin_interface_address: self
540                .admin_interface_address
541                .unwrap_or(local_ip_utils::new_local_tcp_socket_for_testing()),
542            json_rpc_address: self.json_rpc_address.unwrap_or(json_rpc_address),
543            consensus_config: None,
544            remove_deprecated_tables: false,
545            enable_index_processing: default_enable_index_processing(),
546            genesis,
547            migration_tx_data_path,
548            grpc_load_shed: None,
549            grpc_concurrency_limit: None,
550            p2p_config,
551            authority_store_pruning_config: pruning_config,
552            end_of_epoch_broadcast_channel_capacity:
553                default_end_of_epoch_broadcast_channel_capacity(),
554            checkpoint_executor_config,
555            metrics: None,
556            supported_protocol_versions: self.supported_protocol_versions,
557            db_checkpoint_config: self.db_checkpoint_config.unwrap_or_default(),
558            expensive_safety_check_config: self
559                .expensive_safety_check_config
560                .unwrap_or_else(ExpensiveSafetyCheckConfig::new_enable_all),
561            transaction_deny_config: Default::default(),
562            certificate_deny_config: Default::default(),
563            state_debug_dump_config: Default::default(),
564            state_archive_write_config: StateArchiveConfig::default(),
565            state_archive_read_config: vec![],
566            state_snapshot_write_config: StateSnapshotConfig::default(),
567            indexer_max_subscriptions: Default::default(),
568            transaction_kv_store_read_config: Default::default(),
569            transaction_kv_store_write_config: Default::default(),
570            enable_rest_api: true,
571            rest: Some(iota_rest_api::Config {
572                enable_unstable_apis: Some(true),
573                ..Default::default()
574            }),
575            // note: not used by fullnodes.
576            jwk_fetch_interval_seconds: 3600,
577            zklogin_oauth_providers: default_zklogin_oauth_providers(),
578            authority_overload_config: Default::default(),
579            run_with_range: self.run_with_range,
580            jsonrpc_server_type: None,
581            policy_config: self.policy_config,
582            firewall_config: self.fw_config,
583            execution_cache: ExecutionCacheType::default(),
584            execution_cache_config: ExecutionCacheConfig::default(),
585            // This is a validator specific feature.
586            enable_validator_tx_finalizer: false,
587            verifier_signing_config: VerifierSigningConfig::default(),
588            enable_db_write_stall: None,
589            iota_names_config: self.iota_names_config,
590            enable_grpc_api: self.grpc_api_config.is_some(),
591            grpc_api_config: self.grpc_api_config,
592        }
593    }
594
595    pub fn build<R: rand::RngCore + rand::CryptoRng>(
596        self,
597        rng: &mut R,
598        network_config: &NetworkConfig,
599    ) -> NodeConfig {
600        let genesis = self
601            .genesis
602            .as_ref()
603            .or_else(|| network_config.get_validator_genesis())
604            .cloned()
605            .unwrap_or_else(|| iota_config::node::Genesis::new(network_config.genesis.clone()));
606        self.build_from_parts(rng, network_config.validator_configs(), genesis)
607    }
608}
609
610/// Given a validator keypair, return a path that can be used to identify the
611/// validator.
612fn get_key_path(key_pair: &AuthorityKeyPair) -> String {
613    let public_key: AuthorityPublicKeyBytes = key_pair.public().into();
614    let mut key_path = Hex::encode(public_key);
615    // 12 is rather arbitrary here but it's a nice balance between being short and
616    // being unique.
617    key_path.truncate(12);
618    key_path
619}