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