1use std::{
6 net::{Ipv4Addr, SocketAddr},
7 num::NonZeroUsize,
8 path::{Path, PathBuf},
9 sync::Arc,
10};
11
12use fastcrypto::traits::KeyPair;
13use iota_config::{
14 ExecutionCacheConfig,
15 genesis::{TokenAllocation, TokenDistributionScheduleBuilder},
16 node::AuthorityOverloadConfig,
17 transaction_deny_config::TransactionDenyConfig,
18};
19use iota_protocol_config::Chain;
20use iota_sdk_types::Address;
21use iota_types::{
22 base_types::AuthorityName,
23 committee::{Committee, ProtocolVersion},
24 crypto::{AccountPrivateKey, PublicKey, get_key_pair_from_rng},
25 object::Object,
26 supported_protocol_versions::SupportedProtocolVersions,
27 traffic_control::PolicyConfig,
28};
29use rand::{rand_core::UnwrapErr, rngs::SysRng};
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
40const DETERMINISTIC_PORTS_PER_VALIDATOR: u16 = 10;
42
43pub enum CommitteeConfig {
44 Size(NonZeroUsize),
45 Validators(Vec<ValidatorGenesisConfig>),
46 AccountKeys(Vec<AccountPrivateKey>),
47 Deterministic((NonZeroUsize, Option<Vec<AccountPrivateKey>>)),
51}
52
53fn place_on_deterministic_ports(
54 builder: ValidatorGenesisConfigBuilder,
55 port_base: u16,
56 index: usize,
57) -> ValidatorGenesisConfigBuilder {
58 let port_offset = u16::try_from(index)
59 .ok()
60 .and_then(|index| index.checked_mul(DETERMINISTIC_PORTS_PER_VALIDATOR))
61 .and_then(|offset| port_base.checked_add(offset))
62 .unwrap_or_else(|| {
63 panic!(
64 "committee of {} does not fit above port {port_base}",
65 index + 1
66 )
67 });
68
69 builder
70 .with_deterministic_ports(port_offset)
71 .with_metrics_ip_address(Ipv4Addr::LOCALHOST.into())
72}
73
74pub type SupportedProtocolVersionsCallback = Arc<
75 dyn Fn(
76 usize, Option<AuthorityName>, ) -> SupportedProtocolVersions
79 + Send
80 + Sync
81 + 'static,
82>;
83
84#[derive(Clone)]
85pub enum ProtocolVersionsConfig {
86 Default,
88 Global(SupportedProtocolVersions),
90 PerValidator(SupportedProtocolVersionsCallback),
93}
94
95pub type GlobalStateHashV1EnabledCallback = Arc<dyn Fn(usize) -> bool + Send + Sync + 'static>;
96
97#[derive(Clone)]
98pub enum GlobalStateHashV1EnabledConfig {
99 Global(bool),
100 PerValidator(GlobalStateHashV1EnabledCallback),
101}
102
103pub struct ConfigBuilder<R = UnwrapErr<SysRng>> {
104 rng: Option<R>,
105 config_directory: PathBuf,
106 supported_protocol_versions_config: Option<ProtocolVersionsConfig>,
107 chain_override: Option<Chain>,
108 committee: CommitteeConfig,
109 genesis_config: Option<GenesisConfig>,
110 reference_gas_price: Option<u64>,
111 additional_objects: Vec<Object>,
112 num_unpruned_validators: Option<usize>,
113 authority_overload_config: Option<AuthorityOverloadConfig>,
114 transaction_deny_config: Option<TransactionDenyConfig>,
115 execution_cache_config: Option<ExecutionCacheConfig>,
116 data_ingestion_dir: Option<PathBuf>,
117 policy_config: Option<PolicyConfig>,
118 max_submit_position: Option<usize>,
119 submit_delay_step_override_millis: Option<u64>,
120 global_state_hash_v1_enabled_config: Option<GlobalStateHashV1EnabledConfig>,
121 empty_validator_genesis: bool,
122 admin_interface_address: Option<SocketAddr>,
123 deterministic_port_base: Option<u16>,
124}
125
126impl ConfigBuilder {
127 pub fn new<P: AsRef<Path>>(config_directory: P) -> Self {
128 Self {
129 rng: Some(UnwrapErr(SysRng)),
130 config_directory: config_directory.as_ref().into(),
131 supported_protocol_versions_config: None,
132 chain_override: None,
133 committee: CommitteeConfig::Size(NonZeroUsize::new(1).unwrap()),
136 genesis_config: None,
137 reference_gas_price: None,
138 additional_objects: vec![],
139 num_unpruned_validators: None,
140 authority_overload_config: None,
141 transaction_deny_config: None,
142 execution_cache_config: None,
143 data_ingestion_dir: None,
144 policy_config: None,
145 max_submit_position: None,
146 submit_delay_step_override_millis: None,
147 global_state_hash_v1_enabled_config: Some(GlobalStateHashV1EnabledConfig::Global(true)),
148 empty_validator_genesis: false,
149 admin_interface_address: None,
150 deterministic_port_base: None,
151 }
152 }
153
154 pub fn new_with_temp_dir() -> Self {
155 Self::new(iota_common::tempdir().keep())
156 }
157}
158
159impl<R> ConfigBuilder<R> {
160 pub fn committee(mut self, committee: CommitteeConfig) -> Self {
161 self.committee = committee;
162 self
163 }
164
165 pub fn committee_size(mut self, committee_size: NonZeroUsize) -> Self {
166 self.committee = CommitteeConfig::Size(committee_size);
167 self
168 }
169
170 pub fn deterministic_committee_size(mut self, committee_size: NonZeroUsize) -> Self {
171 self.committee = CommitteeConfig::Deterministic((committee_size, None));
172 self
173 }
174
175 pub fn deterministic_committee_validators(mut self, keys: Vec<AccountPrivateKey>) -> Self {
176 self.committee = CommitteeConfig::Deterministic((
177 NonZeroUsize::new(keys.len()).expect("Validator keys should be non empty"),
178 Some(keys),
179 ));
180 self
181 }
182
183 pub fn with_validator_account_keys(mut self, keys: Vec<AccountPrivateKey>) -> Self {
184 self.committee = CommitteeConfig::AccountKeys(keys);
185 self
186 }
187
188 pub fn with_validators(mut self, validators: Vec<ValidatorGenesisConfig>) -> Self {
189 self.committee = CommitteeConfig::Validators(validators);
190 self
191 }
192
193 pub fn with_deterministic_ports(mut self, port_base: u16) -> Self {
206 self.deterministic_port_base = Some(port_base);
207 self
208 }
209
210 pub fn with_genesis_config(mut self, genesis_config: GenesisConfig) -> Self {
211 assert!(self.genesis_config.is_none(), "Genesis config already set");
212 self.genesis_config = Some(genesis_config);
213 self
214 }
215
216 pub fn with_chain_override(mut self, chain: Chain) -> Self {
217 assert!(self.chain_override.is_none(), "Chain override already set");
218 self.chain_override = Some(chain);
219 self
220 }
221
222 pub fn with_num_unpruned_validators(mut self, n: usize) -> Self {
223 self.num_unpruned_validators = Some(n);
224 self
225 }
226
227 pub fn with_data_ingestion_dir(mut self, path: PathBuf) -> Self {
228 self.data_ingestion_dir = Some(path);
229 self
230 }
231
232 pub fn with_reference_gas_price(mut self, reference_gas_price: u64) -> Self {
233 self.reference_gas_price = Some(reference_gas_price);
234 self
235 }
236
237 pub fn with_accounts(mut self, accounts: Vec<AccountConfig>) -> Self {
238 self.get_or_init_genesis_config().accounts = accounts;
239 self
240 }
241
242 pub fn with_chain_start_timestamp_ms(mut self, chain_start_timestamp_ms: u64) -> Self {
243 self.get_or_init_genesis_config()
244 .parameters
245 .chain_start_timestamp_ms = chain_start_timestamp_ms;
246 self
247 }
248
249 pub fn with_objects<I: IntoIterator<Item = Object>>(mut self, objects: I) -> Self {
250 self.additional_objects.extend(objects);
251 self
252 }
253
254 pub fn with_epoch_duration(mut self, epoch_duration_ms: u64) -> Self {
255 self.get_or_init_genesis_config()
256 .parameters
257 .epoch_duration_ms = epoch_duration_ms;
258 self
259 }
260
261 pub fn with_protocol_version(mut self, protocol_version: ProtocolVersion) -> Self {
262 self.get_or_init_genesis_config()
263 .parameters
264 .protocol_version = protocol_version;
265 self
266 }
267
268 pub fn with_supported_protocol_versions(mut self, c: SupportedProtocolVersions) -> Self {
269 self.supported_protocol_versions_config = Some(ProtocolVersionsConfig::Global(c));
270 self
271 }
272
273 pub fn with_supported_protocol_version_callback(
274 mut self,
275 func: SupportedProtocolVersionsCallback,
276 ) -> Self {
277 self.supported_protocol_versions_config = Some(ProtocolVersionsConfig::PerValidator(func));
278 self
279 }
280
281 pub fn with_supported_protocol_versions_config(mut self, c: ProtocolVersionsConfig) -> Self {
282 self.supported_protocol_versions_config = Some(c);
283 self
284 }
285
286 pub fn with_global_state_hash_v1_enabled_callback(
287 mut self,
288 func: GlobalStateHashV1EnabledCallback,
289 ) -> Self {
290 self.global_state_hash_v1_enabled_config =
291 Some(GlobalStateHashV1EnabledConfig::PerValidator(func));
292 self
293 }
294
295 pub fn with_global_state_hash_v1_enabled_config(
296 mut self,
297 c: GlobalStateHashV1EnabledConfig,
298 ) -> Self {
299 self.global_state_hash_v1_enabled_config = Some(c);
300 self
301 }
302
303 pub fn with_authority_overload_config(mut self, c: AuthorityOverloadConfig) -> Self {
304 self.authority_overload_config = Some(c);
305 self
306 }
307
308 pub fn with_transaction_deny_config(mut self, c: TransactionDenyConfig) -> Self {
309 self.transaction_deny_config = Some(c);
310 self
311 }
312
313 pub fn with_execution_cache_config(mut self, c: ExecutionCacheConfig) -> Self {
314 self.execution_cache_config = Some(c);
315 self
316 }
317
318 pub fn with_policy_config(mut self, config: Option<PolicyConfig>) -> Self {
319 self.policy_config = config;
320 self
321 }
322
323 pub fn with_max_submit_position(mut self, max_submit_position: usize) -> Self {
324 self.max_submit_position = Some(max_submit_position);
325 self
326 }
327
328 pub fn with_submit_delay_step_override_millis(
329 mut self,
330 submit_delay_step_override_millis: u64,
331 ) -> Self {
332 self.submit_delay_step_override_millis = Some(submit_delay_step_override_millis);
333 self
334 }
335
336 pub fn with_admin_interface_address(mut self, admin_interface_address: SocketAddr) -> Self {
337 self.admin_interface_address = Some(admin_interface_address);
338 self
339 }
340
341 pub fn rng<N: rand::CryptoRng>(self, rng: N) -> ConfigBuilder<N> {
342 ConfigBuilder {
343 rng: Some(rng),
344 config_directory: self.config_directory,
345 supported_protocol_versions_config: self.supported_protocol_versions_config,
346 committee: self.committee,
347 genesis_config: self.genesis_config,
348 chain_override: self.chain_override,
349 reference_gas_price: self.reference_gas_price,
350 additional_objects: self.additional_objects,
351 num_unpruned_validators: self.num_unpruned_validators,
352 authority_overload_config: self.authority_overload_config,
353 transaction_deny_config: self.transaction_deny_config,
354 execution_cache_config: self.execution_cache_config,
355 data_ingestion_dir: self.data_ingestion_dir,
356 policy_config: self.policy_config,
357 max_submit_position: self.max_submit_position,
358 submit_delay_step_override_millis: self.submit_delay_step_override_millis,
359 global_state_hash_v1_enabled_config: self.global_state_hash_v1_enabled_config,
360 empty_validator_genesis: self.empty_validator_genesis,
361 admin_interface_address: self.admin_interface_address,
362 deterministic_port_base: self.deterministic_port_base,
363 }
364 }
365
366 fn get_or_init_genesis_config(&mut self) -> &mut GenesisConfig {
367 if self.genesis_config.is_none() {
368 self.genesis_config = Some(GenesisConfig::for_local_testing());
369 }
370 self.genesis_config.as_mut().unwrap()
371 }
372
373 pub fn with_empty_validator_genesis(mut self) -> Self {
378 self.empty_validator_genesis = true;
379 self
380 }
381}
382
383impl<R: rand::CryptoRng> ConfigBuilder<R> {
384 pub fn build(self) -> NetworkConfig {
387 self.build_with_genesis_config().0
388 }
389
390 pub fn build_with_genesis_config(self) -> (NetworkConfig, GenesisConfig) {
398 let committee = self.committee;
399
400 let mut rng = self.rng.unwrap();
401 let protocol_version = match &self.supported_protocol_versions_config {
404 Some(ProtocolVersionsConfig::Global(versions)) => {
405 versions.max.min(ProtocolVersion::MAX)
406 }
407 _ => ProtocolVersion::MAX,
408 };
409 let validators = match committee {
410 CommitteeConfig::Size(size) => {
411 let (_, keys) = Committee::new_simple_test_committee_of_size(size.into());
416
417 keys.into_iter()
418 .enumerate()
419 .map(|(i, authority_key)| {
420 let mut builder = ValidatorGenesisConfigBuilder::new()
421 .with_authority_key_pair(authority_key)
422 .with_protocol_version(protocol_version);
423 if let Some(rgp) = self.reference_gas_price {
424 builder = builder.with_gas_price(rgp);
425 }
426 if let Some(port_base) = self.deterministic_port_base {
427 builder = place_on_deterministic_ports(builder, port_base, i);
428 }
429 builder.build(&mut rng)
430 })
431 .collect::<Vec<_>>()
432 }
433
434 CommitteeConfig::Validators(v) => v,
435
436 CommitteeConfig::AccountKeys(keys) => {
437 let (_, authority_keys) = Committee::new_simple_test_committee_of_size(keys.len());
439 keys.into_iter()
440 .zip(authority_keys)
441 .enumerate()
442 .map(|(i, (account_key, authority_key))| {
443 let mut builder = ValidatorGenesisConfigBuilder::new()
444 .with_authority_key_pair(authority_key)
445 .with_account_private_key(account_key)
446 .with_protocol_version(protocol_version);
447 if let Some(rgp) = self.reference_gas_price {
448 builder = builder.with_gas_price(rgp);
449 }
450 if let Some(port_base) = self.deterministic_port_base {
451 builder = place_on_deterministic_ports(builder, port_base, i);
452 }
453 builder.build(&mut rng)
454 })
455 .collect::<Vec<_>>()
456 }
457 CommitteeConfig::Deterministic((size, keys)) => {
458 let keys = keys.unwrap_or(
460 (0..size.get())
461 .map(|_| get_key_pair_from_rng(&mut rng).1)
462 .collect(),
463 );
464
465 let mut configs = vec![];
466 for (i, key) in keys.into_iter().enumerate() {
467 let port_offset = 8000 + i * 10;
468 let mut builder = ValidatorGenesisConfigBuilder::new()
469 .with_ip("127.0.0.1".to_owned())
470 .with_account_private_key(key)
471 .with_deterministic_ports(port_offset as u16)
472 .with_protocol_version(protocol_version);
473 if let Some(rgp) = self.reference_gas_price {
474 builder = builder.with_gas_price(rgp);
475 }
476 configs.push(builder.build(&mut rng));
477 }
478 configs
479 }
480 };
481
482 let mut validators = validators;
483 if let Some(admin_interface_address) = self.admin_interface_address {
484 for validator in &mut validators {
485 validator.admin_interface_address = admin_interface_address;
486 }
487 }
488
489 let mut genesis_config = self
490 .genesis_config
491 .unwrap_or_else(GenesisConfig::for_local_testing);
492 genesis_config.validator_config_info = Some(
493 validators
494 .iter()
495 .map(ValidatorGenesisConfig::copy_with_private_keys)
496 .collect(),
497 );
498
499 let (account_keys, allocations) = genesis_config.generate_accounts(&mut rng).unwrap();
500
501 let token_distribution_schedule = {
502 let mut builder = TokenDistributionScheduleBuilder::new();
503 for allocation in allocations {
504 builder.add_allocation(allocation);
505 }
506 for validator in &validators {
508 let account_key = PublicKey::from(&validator.account_key_pair);
509 let address = Address::from(&account_key);
510 let gas_coin = TokenAllocation {
512 recipient_address: address,
513 amount_nanos: DEFAULT_GAS_AMOUNT,
514 staked_with_validator: None,
515 staked_with_timelock_expiration: None,
516 };
517 let stake = TokenAllocation {
518 recipient_address: address,
519 amount_nanos: validator.stake,
520 staked_with_validator: Some(address),
521 staked_with_timelock_expiration: None,
522 };
523 builder.add_allocation(gas_coin);
524 builder.add_allocation(stake);
525 }
526 builder.build()
527 };
528
529 let genesis = {
530 let mut builder = iota_genesis_builder::Builder::new()
531 .with_parameters(genesis_config.parameters.clone())
532 .add_objects(self.additional_objects);
533
534 for (i, validator) in validators.iter().enumerate() {
535 let name = validator
536 .name
537 .clone()
538 .unwrap_or(format!("validator-{i}").to_string());
539 let validator_info = validator.to_validator_info(name);
540 builder =
541 builder.add_validator(validator_info.info, validator_info.proof_of_possession);
542 }
543
544 builder = builder.with_token_distribution_schedule(token_distribution_schedule);
545
546 for validator in &validators {
547 builder = builder.add_validator_signature(&validator.authority_key_pair);
548 }
549
550 builder.build()
551 };
552
553 let validator_configs = validators
554 .into_iter()
555 .enumerate()
556 .map(|(idx, validator)| {
557 let mut builder = ValidatorConfigBuilder::new()
558 .with_config_directory(self.config_directory.clone())
559 .with_policy_config(self.policy_config.clone());
560
561 if let Some(chain) = self.chain_override {
562 builder = builder.with_chain_override(chain);
563 }
564
565 if let Some(max_submit_position) = self.max_submit_position {
566 builder = builder.with_max_submit_position(max_submit_position);
567 }
568
569 if let Some(submit_delay_step_override_millis) =
570 self.submit_delay_step_override_millis
571 {
572 builder = builder
573 .with_submit_delay_step_override_millis(submit_delay_step_override_millis);
574 }
575
576 if let Some(authority_overload_config) = &self.authority_overload_config {
577 builder =
578 builder.with_authority_overload_config(authority_overload_config.clone());
579 }
580
581 if let Some(transaction_deny_config) = &self.transaction_deny_config {
582 builder = builder.with_transaction_deny_config(transaction_deny_config.clone());
583 }
584
585 if let Some(execution_cache_config) = &self.execution_cache_config {
586 builder = builder.with_execution_cache_config(execution_cache_config.clone());
587 }
588
589 if let Some(path) = &self.data_ingestion_dir {
590 builder = builder.with_data_ingestion_dir(path.clone());
591 }
592
593 if let Some(spvc) = &self.supported_protocol_versions_config {
594 let supported_versions = match spvc {
595 ProtocolVersionsConfig::Default => {
596 SupportedProtocolVersions::SYSTEM_DEFAULT
597 }
598 ProtocolVersionsConfig::Global(v) => *v,
599 ProtocolVersionsConfig::PerValidator(func) => {
600 func(idx, Some(validator.authority_key_pair.public().into()))
601 }
602 };
603 builder = builder.with_supported_protocol_versions(supported_versions);
604 }
605 if let Some(num_unpruned_validators) = self.num_unpruned_validators {
606 if idx < num_unpruned_validators {
607 builder = builder.with_unpruned_checkpoints();
608 }
609 }
610 if self.empty_validator_genesis {
611 builder.build_without_genesis(validator)
612 } else {
613 builder.build(validator, genesis.clone())
614 }
615 })
616 .collect();
617 (
618 NetworkConfig {
619 validator_configs,
620 genesis,
621 account_keys,
622 },
623 genesis_config,
624 )
625 }
626}
627
628#[cfg(test)]
629mod tests {
630 use iota_config::node::Genesis;
631
632 #[test]
633 fn serialize_genesis_config_in_place() {
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 g = Genesis::new(genesis);
639
640 let mut s = serde_yaml::to_string(&g).unwrap();
641 let loaded_genesis: Genesis = serde_yaml::from_str(&s).unwrap();
642 loaded_genesis
643 .genesis()
644 .unwrap()
645 .checkpoint_contents()
646 .digest(); assert_eq!(g, loaded_genesis);
648
649 s.push_str("\ngenesis-file-location: path/to/file");
651 let loaded_genesis: Genesis = serde_yaml::from_str(&s).unwrap();
652 loaded_genesis
653 .genesis()
654 .unwrap()
655 .checkpoint_contents()
656 .digest(); assert_eq!(g, loaded_genesis);
658 }
659
660 #[test]
661 fn load_genesis_config_from_file() {
662 let file = tempfile::NamedTempFile::new().unwrap();
663 let genesis_config = Genesis::new_from_file(file.path());
664
665 let dir = tempfile::TempDir::new().unwrap();
666 let network_config = crate::network_config_builder::ConfigBuilder::new(&dir).build();
667 let genesis = network_config.genesis;
668 genesis.save(file.path()).unwrap();
669
670 let loaded_genesis = genesis_config.genesis().unwrap();
671 loaded_genesis.checkpoint_contents().digest(); assert_eq!(&genesis, loaded_genesis);
673 }
674}
675
676#[cfg(test)]
677mod test {
678 use std::{collections::HashSet, sync::Arc};
679
680 use iota_config::genesis::Genesis;
681 use iota_protocol_config::{Chain, ProtocolConfig, ProtocolVersion};
682 use iota_types::{
683 epoch_data::EpochData,
684 gas::IotaGasStatus,
685 in_memory_storage::InMemoryStorage,
686 iota_system_state::IotaSystemStateTrait,
687 metrics::LimitsMetrics,
688 transaction::{CheckedInputObjects, TransactionAPI},
689 };
690
691 #[test]
692 fn roundtrip() {
693 let dir = tempfile::TempDir::new().unwrap();
694 let network_config = crate::network_config_builder::ConfigBuilder::new(&dir).build();
695 let genesis = network_config.genesis;
696
697 let s = serde_yaml::to_string(&genesis).unwrap();
698 let from_s: Genesis = serde_yaml::from_str(&s).unwrap();
699 from_s.checkpoint_contents().digest();
701 assert_eq!(genesis, from_s);
702 }
703
704 #[test]
705 fn genesis_transaction() {
706 let builder = crate::network_config_builder::ConfigBuilder::new_with_temp_dir();
707 let network_config = builder.build();
708 let genesis = network_config.genesis;
709 let protocol_version =
710 ProtocolVersion::new(genesis.iota_system_object().protocol_version());
711 let protocol_config = ProtocolConfig::get_for_version(protocol_version, Chain::Unknown);
712
713 let genesis_transaction = genesis.transaction().clone();
714
715 let genesis_digest = *genesis_transaction.digest();
716
717 let silent = true;
718 let executor = iota_execution::executor(&protocol_config, silent, None)
719 .expect("Creating an executor should not fail here");
720
721 let registry = prometheus_filtered::Registry::new();
723 let metrics = Arc::new(LimitsMetrics::new(®istry));
724 let expensive_checks = false;
725 let certificate_deny_set = HashSet::new();
726 let epoch = EpochData::new_test();
727 let transaction = genesis_transaction.data().transaction();
728 let (kind, signer, mut gas_data) = transaction.execution_parts();
729 gas_data.objects = vec![];
730 let input_objects = CheckedInputObjects::new_for_genesis(vec![]);
731
732 let (_inner_temp_store, _, effects, _timings, _execution_error) = executor
733 .execute_transaction_to_effects(
734 &InMemoryStorage::new(Vec::new()),
735 &protocol_config,
736 metrics,
737 expensive_checks,
738 &certificate_deny_set,
739 &epoch.epoch_id(),
740 epoch.epoch_start_timestamp(),
741 input_objects,
742 gas_data,
743 IotaGasStatus::new_unmetered(),
744 kind,
745 signer,
746 genesis_digest,
747 &mut None,
748 );
749
750 assert_eq!(&effects, genesis.effects());
751 }
752}