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