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