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