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