sui_swarm_config/
node_config_builder.rs

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