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