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