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