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