1use std::path::PathBuf;
5use std::time::Duration;
6use std::{num::NonZeroUsize, path::Path, sync::Arc};
7
8use rand::rngs::OsRng;
9use sui_config::ExecutionCacheConfig;
10use sui_config::genesis::{TokenAllocation, TokenDistributionScheduleBuilder};
11use sui_config::node::AuthorityOverloadConfig;
12#[cfg(msim)]
13use sui_config::node::ExecutionTimeObserverConfig;
14use sui_protocol_config::Chain;
15use sui_types::base_types::{AuthorityName, SuiAddress};
16use sui_types::committee::{Committee, ProtocolVersion};
17use sui_types::crypto::{
18 AccountKeyPair, AuthorityKeyPair, KeypairTraits, PublicKey, get_key_pair_from_rng,
19};
20use sui_types::object::Object;
21use sui_types::supported_protocol_versions::SupportedProtocolVersions;
22use sui_types::traffic_control::{PolicyConfig, RemoteFirewallConfig};
23
24use crate::genesis_config::{AccountConfig, DEFAULT_GAS_AMOUNT, ValidatorGenesisConfigBuilder};
25use crate::genesis_config::{GenesisConfig, ValidatorGenesisConfig};
26use crate::network_config::NetworkConfig;
27use crate::node_config_builder::ValidatorConfigBuilder;
28
29pub struct KeyPairWrapper {
30 pub account_key_pair: AccountKeyPair,
31 pub protocol_key_pair: Option<AuthorityKeyPair>,
32}
33
34impl Clone for KeyPairWrapper {
35 fn clone(&self) -> Self {
36 Self {
37 account_key_pair: self.account_key_pair.copy(),
38 protocol_key_pair: self.protocol_key_pair.as_ref().map(|k| k.copy()),
39 }
40 }
41}
42
43pub enum CommitteeConfig {
44 Size(NonZeroUsize),
45 Validators(Vec<ValidatorGenesisConfig>),
46 AccountKeys(Vec<AccountKeyPair>),
47 Deterministic((NonZeroUsize, Option<Vec<KeyPairWrapper>>)),
50}
51
52pub type SupportedProtocolVersionsCallback = Arc<
53 dyn Fn(
54 usize, Option<AuthorityName>, ) -> SupportedProtocolVersions
57 + Send
58 + Sync
59 + 'static,
60>;
61
62#[derive(Clone)]
63pub enum ProtocolVersionsConfig {
64 Default,
66 Global(SupportedProtocolVersions),
68 PerValidator(SupportedProtocolVersionsCallback),
71}
72
73pub type GlobalStateHashV2EnabledCallback = Arc<dyn Fn(usize) -> bool + Send + Sync + 'static>;
74
75#[derive(Clone)]
76pub enum GlobalStateHashV2EnabledConfig {
77 Global(bool),
78 PerValidator(GlobalStateHashV2EnabledCallback),
79}
80
81pub struct ConfigBuilder<R = OsRng> {
82 rng: Option<R>,
83 config_directory: PathBuf,
84 supported_protocol_versions_config: Option<ProtocolVersionsConfig>,
85 chain_override: Option<Chain>,
86 committee: CommitteeConfig,
87 genesis_config: Option<GenesisConfig>,
88 reference_gas_price: Option<u64>,
89 additional_objects: Vec<Object>,
90 jwk_fetch_interval: Option<Duration>,
91 num_unpruned_validators: Option<usize>,
92 authority_overload_config: Option<AuthorityOverloadConfig>,
93 execution_cache_config: Option<ExecutionCacheConfig>,
94 data_ingestion_dir: Option<PathBuf>,
95 policy_config: Option<PolicyConfig>,
96 firewall_config: Option<RemoteFirewallConfig>,
97 max_submit_position: Option<usize>,
98 submit_delay_step_override_millis: Option<u64>,
99 global_state_hash_v2_enabled_config: Option<GlobalStateHashV2EnabledConfig>,
100 state_sync_config: Option<sui_config::p2p::StateSyncConfig>,
101 #[cfg(msim)]
102 execution_time_observer_config: Option<ExecutionTimeObserverConfig>,
103}
104
105impl ConfigBuilder {
106 pub fn new<P: AsRef<Path>>(config_directory: P) -> Self {
107 Self {
108 rng: Some(OsRng),
109 config_directory: config_directory.as_ref().into(),
110 supported_protocol_versions_config: None,
111 chain_override: None,
112 committee: CommitteeConfig::Size(NonZeroUsize::new(1).unwrap()),
115 genesis_config: None,
116 reference_gas_price: None,
117 additional_objects: vec![],
118 jwk_fetch_interval: None,
119 num_unpruned_validators: None,
120 authority_overload_config: None,
121 execution_cache_config: None,
122 data_ingestion_dir: None,
123 policy_config: None,
124 firewall_config: None,
125 max_submit_position: None,
126 submit_delay_step_override_millis: None,
127 global_state_hash_v2_enabled_config: None,
128 state_sync_config: None,
129 #[cfg(msim)]
130 execution_time_observer_config: None,
131 }
132 }
133
134 pub fn new_with_temp_dir() -> Self {
135 Self::new(mysten_common::tempdir().unwrap().keep())
136 }
137}
138
139impl<R> ConfigBuilder<R> {
140 pub fn committee(mut self, committee: CommitteeConfig) -> Self {
141 self.committee = committee;
142 self
143 }
144
145 pub fn committee_size(mut self, committee_size: NonZeroUsize) -> Self {
146 self.committee = CommitteeConfig::Size(committee_size);
147 self
148 }
149
150 pub fn deterministic_committee_size(mut self, committee_size: NonZeroUsize) -> Self {
151 self.committee = CommitteeConfig::Deterministic((committee_size, None));
152 self
153 }
154
155 pub fn deterministic_committee_validators(mut self, keys: Vec<KeyPairWrapper>) -> Self {
156 self.committee = CommitteeConfig::Deterministic((
157 NonZeroUsize::new(keys.len()).expect("Validator keys should be non empty"),
158 Some(keys),
159 ));
160 self
161 }
162
163 pub fn with_validator_account_keys(mut self, keys: Vec<AccountKeyPair>) -> Self {
164 self.committee = CommitteeConfig::AccountKeys(keys);
165 self
166 }
167
168 pub fn with_validators(mut self, validators: Vec<ValidatorGenesisConfig>) -> Self {
169 self.committee = CommitteeConfig::Validators(validators);
170 self
171 }
172
173 pub fn with_genesis_config(mut self, genesis_config: GenesisConfig) -> Self {
174 assert!(self.genesis_config.is_none(), "Genesis config already set");
175 self.genesis_config = Some(genesis_config);
176 self
177 }
178
179 pub fn with_chain_override(mut self, chain: Chain) -> Self {
180 assert!(self.chain_override.is_none(), "Chain override already set");
181 self.chain_override = Some(chain);
182 self
183 }
184
185 pub fn with_num_unpruned_validators(mut self, n: usize) -> Self {
186 self.num_unpruned_validators = Some(n);
187 self
188 }
189
190 pub fn with_jwk_fetch_interval(mut self, i: Duration) -> Self {
191 self.jwk_fetch_interval = Some(i);
192 self
193 }
194
195 pub fn with_data_ingestion_dir(mut self, path: PathBuf) -> Self {
196 self.data_ingestion_dir = Some(path);
197 self
198 }
199
200 pub fn with_reference_gas_price(mut self, reference_gas_price: u64) -> Self {
201 self.reference_gas_price = Some(reference_gas_price);
202 self
203 }
204
205 pub fn with_accounts(mut self, accounts: Vec<AccountConfig>) -> Self {
206 self.get_or_init_genesis_config().accounts = accounts;
207 self
208 }
209
210 pub fn with_chain_start_timestamp_ms(mut self, chain_start_timestamp_ms: u64) -> Self {
211 self.get_or_init_genesis_config()
212 .parameters
213 .chain_start_timestamp_ms = chain_start_timestamp_ms;
214 self
215 }
216
217 pub fn with_objects<I: IntoIterator<Item = Object>>(mut self, objects: I) -> Self {
218 self.additional_objects.extend(objects);
219 self
220 }
221
222 pub fn with_epoch_duration(mut self, epoch_duration_ms: u64) -> Self {
223 self.get_or_init_genesis_config()
224 .parameters
225 .epoch_duration_ms = epoch_duration_ms;
226 self
227 }
228
229 pub fn with_protocol_version(mut self, protocol_version: ProtocolVersion) -> Self {
230 self.get_or_init_genesis_config()
231 .parameters
232 .protocol_version = protocol_version;
233 self
234 }
235
236 pub fn with_supported_protocol_versions(mut self, c: SupportedProtocolVersions) -> Self {
237 self.supported_protocol_versions_config = Some(ProtocolVersionsConfig::Global(c));
238 self
239 }
240
241 pub fn with_supported_protocol_version_callback(
242 mut self,
243 func: SupportedProtocolVersionsCallback,
244 ) -> Self {
245 self.supported_protocol_versions_config = Some(ProtocolVersionsConfig::PerValidator(func));
246 self
247 }
248
249 pub fn with_supported_protocol_versions_config(mut self, c: ProtocolVersionsConfig) -> Self {
250 self.supported_protocol_versions_config = Some(c);
251 self
252 }
253
254 pub fn with_global_state_hash_v2_enabled(mut self, enabled: bool) -> Self {
255 self.global_state_hash_v2_enabled_config =
256 Some(GlobalStateHashV2EnabledConfig::Global(enabled));
257 self
258 }
259
260 pub fn with_global_state_hash_v2_enabled_callback(
261 mut self,
262 func: GlobalStateHashV2EnabledCallback,
263 ) -> Self {
264 self.global_state_hash_v2_enabled_config =
265 Some(GlobalStateHashV2EnabledConfig::PerValidator(func));
266 self
267 }
268
269 pub fn with_global_state_hash_v2_enabled_config(
270 mut self,
271 c: GlobalStateHashV2EnabledConfig,
272 ) -> Self {
273 self.global_state_hash_v2_enabled_config = Some(c);
274 self
275 }
276
277 #[cfg(msim)]
278 pub fn with_execution_time_observer_config(mut self, c: ExecutionTimeObserverConfig) -> Self {
279 self.execution_time_observer_config = Some(c);
280 self
281 }
282
283 pub fn with_authority_overload_config(mut self, c: AuthorityOverloadConfig) -> Self {
284 self.authority_overload_config = Some(c);
285 self
286 }
287
288 pub fn with_execution_cache_config(mut self, c: ExecutionCacheConfig) -> Self {
289 self.execution_cache_config = Some(c);
290 self
291 }
292
293 pub fn with_policy_config(mut self, config: Option<PolicyConfig>) -> Self {
294 self.policy_config = config;
295 self
296 }
297
298 pub fn with_firewall_config(mut self, config: Option<RemoteFirewallConfig>) -> Self {
299 self.firewall_config = config;
300 self
301 }
302
303 pub fn with_max_submit_position(mut self, max_submit_position: usize) -> Self {
304 self.max_submit_position = Some(max_submit_position);
305 self
306 }
307
308 pub fn with_submit_delay_step_override_millis(
309 mut self,
310 submit_delay_step_override_millis: u64,
311 ) -> Self {
312 self.submit_delay_step_override_millis = Some(submit_delay_step_override_millis);
313 self
314 }
315
316 pub fn rng<N: rand::RngCore + rand::CryptoRng>(self, rng: N) -> ConfigBuilder<N> {
317 ConfigBuilder {
318 rng: Some(rng),
319 config_directory: self.config_directory,
320 supported_protocol_versions_config: self.supported_protocol_versions_config,
321 committee: self.committee,
322 genesis_config: self.genesis_config,
323 chain_override: self.chain_override,
324 reference_gas_price: self.reference_gas_price,
325 additional_objects: self.additional_objects,
326 num_unpruned_validators: self.num_unpruned_validators,
327 jwk_fetch_interval: self.jwk_fetch_interval,
328 authority_overload_config: self.authority_overload_config,
329 execution_cache_config: self.execution_cache_config,
330 data_ingestion_dir: self.data_ingestion_dir,
331 policy_config: self.policy_config,
332 firewall_config: self.firewall_config,
333 max_submit_position: self.max_submit_position,
334 submit_delay_step_override_millis: self.submit_delay_step_override_millis,
335 global_state_hash_v2_enabled_config: self.global_state_hash_v2_enabled_config,
336 state_sync_config: self.state_sync_config,
337 #[cfg(msim)]
338 execution_time_observer_config: self.execution_time_observer_config,
339 }
340 }
341
342 pub fn with_state_sync_config(mut self, config: sui_config::p2p::StateSyncConfig) -> Self {
343 self.state_sync_config = Some(config);
344 self
345 }
346
347 fn get_or_init_genesis_config(&mut self) -> &mut GenesisConfig {
348 if self.genesis_config.is_none() {
349 self.genesis_config = Some(GenesisConfig::for_local_testing());
350 }
351 self.genesis_config.as_mut().unwrap()
352 }
353}
354
355impl<R: rand::RngCore + rand::CryptoRng> ConfigBuilder<R> {
356 pub fn build(self) -> NetworkConfig {
358 let committee = self.committee;
359
360 let mut rng = self.rng.unwrap();
361 let validators = match committee {
362 CommitteeConfig::Size(size) => {
363 let (_, keys) = Committee::new_simple_test_committee_of_size(size.into());
368
369 keys.into_iter()
370 .map(|authority_key| {
371 let mut builder = ValidatorGenesisConfigBuilder::new()
372 .with_protocol_key_pair(authority_key);
373 if let Some(rgp) = self.reference_gas_price {
374 builder = builder.with_gas_price(rgp);
375 }
376 builder.build(&mut rng)
377 })
378 .collect::<Vec<_>>()
379 }
380
381 CommitteeConfig::Validators(v) => v,
382
383 CommitteeConfig::AccountKeys(keys) => {
384 let (_, protocol_keys) = Committee::new_simple_test_committee_of_size(keys.len());
386 keys.into_iter()
387 .zip(protocol_keys)
388 .map(|(account_key, protocol_key)| {
389 let mut builder = ValidatorGenesisConfigBuilder::new()
390 .with_protocol_key_pair(protocol_key)
391 .with_account_key_pair(account_key);
392 if let Some(rgp) = self.reference_gas_price {
393 builder = builder.with_gas_price(rgp);
394 }
395 builder.build(&mut rng)
396 })
397 .collect::<Vec<_>>()
398 }
399 CommitteeConfig::Deterministic((size, key_pair_wrappers)) => {
400 let keys = key_pair_wrappers.unwrap_or_else(|| {
402 (0..size.get())
403 .map(|_| KeyPairWrapper {
404 account_key_pair: get_key_pair_from_rng(&mut rng).1,
405 protocol_key_pair: None,
406 })
407 .collect()
408 });
409
410 let mut configs = vec![];
411 for (i, key) in keys.into_iter().enumerate() {
412 let port_offset = 8000 + i * 10;
413 let mut builder = ValidatorGenesisConfigBuilder::new()
414 .with_ip("127.0.0.1".to_owned())
415 .with_account_key_pair(key.account_key_pair)
416 .with_deterministic_ports(port_offset as u16);
417 if let Some(protocol_key_pair) = key.protocol_key_pair {
418 builder = builder.with_protocol_key_pair(protocol_key_pair);
419 }
420 if let Some(rgp) = self.reference_gas_price {
421 builder = builder.with_gas_price(rgp);
422 }
423 configs.push(builder.build(&mut rng));
424 }
425 configs
426 }
427 };
428
429 let genesis_config = self
430 .genesis_config
431 .unwrap_or_else(GenesisConfig::for_local_testing);
432
433 let (account_keys, allocations) = genesis_config.generate_accounts(&mut rng).unwrap();
434
435 let token_distribution_schedule = {
436 let mut builder = TokenDistributionScheduleBuilder::new();
437 for allocation in allocations {
438 builder.add_allocation(allocation);
439 }
440 for validator in &validators {
442 let account_key: PublicKey = validator.account_key_pair.public();
443 let address = SuiAddress::from(&account_key);
444 let gas_coin = TokenAllocation {
446 recipient_address: address,
447 amount_mist: DEFAULT_GAS_AMOUNT,
448 staked_with_validator: None,
449 };
450 let stake = TokenAllocation {
451 recipient_address: address,
452 amount_mist: validator.stake,
453 staked_with_validator: Some(address),
454 };
455 builder.add_allocation(gas_coin);
456 builder.add_allocation(stake);
457 }
458 builder.build()
459 };
460
461 let genesis = {
462 let mut builder = sui_genesis_builder::Builder::new()
463 .with_parameters(genesis_config.parameters)
464 .add_objects(self.additional_objects);
465
466 for (i, validator) in validators.iter().enumerate() {
467 let name = validator
468 .name
469 .clone()
470 .unwrap_or(format!("validator-{i}").to_string());
471 let validator_info = validator.to_validator_info(name);
472 builder =
473 builder.add_validator(validator_info.info, validator_info.proof_of_possession);
474 }
475
476 builder = builder.with_token_distribution_schedule(token_distribution_schedule);
477
478 for validator in &validators {
479 builder = builder.add_validator_signature(&validator.key_pair);
480 }
481
482 builder.build()
483 };
484
485 let validator_configs = validators
486 .into_iter()
487 .enumerate()
488 .map(|(idx, validator)| {
489 let mut builder = ValidatorConfigBuilder::new()
490 .with_config_directory(self.config_directory.clone())
491 .with_policy_config(self.policy_config.clone())
492 .with_firewall_config(self.firewall_config.clone());
493
494 if let Some(chain) = self.chain_override {
495 builder = builder.with_chain_override(chain);
496 }
497
498 if let Some(max_submit_position) = self.max_submit_position {
499 builder = builder.with_max_submit_position(max_submit_position);
500 }
501
502 if let Some(submit_delay_step_override_millis) =
503 self.submit_delay_step_override_millis
504 {
505 builder = builder
506 .with_submit_delay_step_override_millis(submit_delay_step_override_millis);
507 }
508
509 if let Some(jwk_fetch_interval) = self.jwk_fetch_interval {
510 builder = builder.with_jwk_fetch_interval(jwk_fetch_interval);
511 }
512
513 if let Some(authority_overload_config) = &self.authority_overload_config {
514 builder =
515 builder.with_authority_overload_config(authority_overload_config.clone());
516 }
517
518 if let Some(execution_cache_config) = &self.execution_cache_config {
519 builder = builder.with_execution_cache_config(execution_cache_config.clone());
520 }
521
522 if let Some(path) = &self.data_ingestion_dir {
523 builder = builder.with_data_ingestion_dir(path.clone());
524 }
525
526 if let Some(state_sync_config) = &self.state_sync_config {
527 builder = builder.with_state_sync_config(state_sync_config.clone());
528 }
529
530 #[cfg(msim)]
531 if let Some(execution_time_observer_config) = &self.execution_time_observer_config {
532 builder = builder.with_execution_time_observer_config(
533 execution_time_observer_config.clone(),
534 );
535 }
536
537 if let Some(spvc) = &self.supported_protocol_versions_config {
538 let supported_versions = match spvc {
539 ProtocolVersionsConfig::Default => {
540 SupportedProtocolVersions::SYSTEM_DEFAULT
541 }
542 ProtocolVersionsConfig::Global(v) => *v,
543 ProtocolVersionsConfig::PerValidator(func) => {
544 func(idx, Some(validator.key_pair.public().into()))
545 }
546 };
547 builder = builder.with_supported_protocol_versions(supported_versions);
548 }
549 if let Some(acc_v2_config) = &self.global_state_hash_v2_enabled_config {
550 let global_state_hash_v2_enabled: bool = match acc_v2_config {
551 GlobalStateHashV2EnabledConfig::Global(enabled) => *enabled,
552 GlobalStateHashV2EnabledConfig::PerValidator(func) => func(idx),
553 };
554 builder =
555 builder.with_global_state_hash_v2_enabled(global_state_hash_v2_enabled);
556 }
557 if let Some(num_unpruned_validators) = self.num_unpruned_validators
558 && idx < num_unpruned_validators
559 {
560 builder = builder.with_unpruned_checkpoints();
561 }
562 builder.build(validator, genesis.clone())
563 })
564 .collect();
565 NetworkConfig {
566 validator_configs,
567 genesis,
568 account_keys,
569 }
570 }
571}
572
573#[cfg(test)]
574mod tests {
575 use sui_config::node::Genesis;
576
577 #[test]
578 fn serialize_genesis_config_in_place() {
579 let dir = tempfile::TempDir::new().unwrap();
580 let network_config = crate::network_config_builder::ConfigBuilder::new(&dir).build();
581 let genesis = network_config.genesis;
582
583 let g = Genesis::new(genesis);
584
585 let mut s = serde_yaml::to_string(&g).unwrap();
586 let loaded_genesis: Genesis = serde_yaml::from_str(&s).unwrap();
587 loaded_genesis
588 .genesis()
589 .unwrap()
590 .checkpoint_contents()
591 .digest(); assert_eq!(g, loaded_genesis);
593
594 s.push_str("\ngenesis-file-location: path/to/file");
596 let loaded_genesis: Genesis = serde_yaml::from_str(&s).unwrap();
597 loaded_genesis
598 .genesis()
599 .unwrap()
600 .checkpoint_contents()
601 .digest(); assert_eq!(g, loaded_genesis);
603 }
604
605 #[test]
606 fn load_genesis_config_from_file() {
607 let file = tempfile::NamedTempFile::new().unwrap();
608 let genesis_config = Genesis::new_from_file(file.path());
609
610 let dir = tempfile::TempDir::new().unwrap();
611 let network_config = crate::network_config_builder::ConfigBuilder::new(&dir).build();
612 let genesis = network_config.genesis;
613 genesis.save(file.path()).unwrap();
614
615 let loaded_genesis = genesis_config.genesis().unwrap();
616 loaded_genesis.checkpoint_contents().digest(); assert_eq!(&genesis, loaded_genesis);
618 }
619}
620
621#[cfg(test)]
622mod test {
623 use std::sync::Arc;
624 use sui_config::genesis::Genesis;
625 use sui_protocol_config::{Chain, ProtocolConfig, ProtocolVersion};
626 use sui_types::epoch_data::EpochData;
627 use sui_types::execution_params::ExecutionOrEarlyError;
628 use sui_types::gas::SuiGasStatus;
629 use sui_types::in_memory_storage::InMemoryStorage;
630 use sui_types::metrics::LimitsMetrics;
631 use sui_types::sui_system_state::SuiSystemStateTrait;
632 use sui_types::transaction::CheckedInputObjects;
633
634 #[test]
635 fn roundtrip() {
636 let dir = tempfile::TempDir::new().unwrap();
637 let network_config = crate::network_config_builder::ConfigBuilder::new(&dir).build();
638 let genesis = network_config.genesis;
639
640 let s = serde_yaml::to_string(&genesis).unwrap();
641 let from_s: Genesis = serde_yaml::from_str(&s).unwrap();
642 from_s.checkpoint_contents().digest();
644 assert_eq!(genesis, from_s);
645 }
646
647 #[test]
648 fn genesis_transaction() {
649 let builder = crate::network_config_builder::ConfigBuilder::new_with_temp_dir();
650 let network_config = builder.build();
651 let genesis = network_config.genesis;
652 let protocol_version = ProtocolVersion::new(genesis.sui_system_object().protocol_version());
653 let protocol_config = ProtocolConfig::get_for_version(protocol_version, Chain::Unknown);
654
655 let genesis_transaction = genesis.transaction().clone();
656
657 let genesis_digest = *genesis_transaction.digest();
658
659 let silent = true;
660 let executor = sui_execution::executor(&protocol_config, silent)
661 .expect("Creating an executor should not fail here");
662
663 let registry = prometheus::Registry::new();
665 let metrics = Arc::new(LimitsMetrics::new(®istry));
666 let expensive_checks = false;
667 let epoch = EpochData::new_test();
668 let transaction_data = &genesis_transaction.data().intent_message().value;
669 let (kind, signer, mut gas_data) = transaction_data.execution_parts();
670 gas_data.payment = vec![];
671 let input_objects = CheckedInputObjects::new_for_genesis(vec![]);
672
673 let (_inner_temp_store, _, effects, _timings, _execution_error) = executor
674 .execute_transaction_to_effects(
675 &InMemoryStorage::new(Vec::new()),
676 &protocol_config,
677 metrics,
678 expensive_checks,
679 ExecutionOrEarlyError::Ok(()),
680 &epoch.epoch_id(),
681 epoch.epoch_start_timestamp(),
682 input_objects,
683 gas_data,
684 SuiGasStatus::new_unmetered(),
685 kind,
686 signer,
687 genesis_digest,
688 &mut None,
689 );
690
691 assert_eq!(&effects, genesis.effects());
692 }
693}