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