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