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