iota_swarm_config/
network_config_builder.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    num::NonZeroUsize,
7    path::{Path, PathBuf},
8    sync::Arc,
9    time::Duration,
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_macros::nondeterministic;
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    /// Indicates that a committee should be deterministically generated, using
44    /// the provided rng as a source of randomness as well as generating
45    /// deterministic network port information.
46    Deterministic((NonZeroUsize, Option<Vec<AccountKeyPair>>)),
47}
48
49pub type SupportedProtocolVersionsCallback = Arc<
50    dyn Fn(
51            usize,                 // validator idx
52            Option<AuthorityName>, // None for fullnode
53        ) -> SupportedProtocolVersions
54        + Send
55        + Sync
56        + 'static,
57>;
58
59#[derive(Clone)]
60pub enum ProtocolVersionsConfig {
61    // use SYSTEM_DEFAULT
62    Default,
63    // Use one range for all validators.
64    Global(SupportedProtocolVersions),
65    // A closure that returns the versions for each validator.
66    // TODO: This doesn't apply to fullnodes.
67    PerValidator(SupportedProtocolVersionsCallback),
68}
69
70pub type StateAccumulatorEnabledCallback = Arc<dyn Fn(usize) -> bool + Send + Sync + 'static>;
71
72#[derive(Clone)]
73pub enum StateAccumulatorV1EnabledConfig {
74    Global(bool),
75    PerValidator(StateAccumulatorEnabledCallback),
76}
77
78pub struct ConfigBuilder<R = OsRng> {
79    rng: Option<R>,
80    config_directory: PathBuf,
81    supported_protocol_versions_config: Option<ProtocolVersionsConfig>,
82    committee: CommitteeConfig,
83    genesis_config: Option<GenesisConfig>,
84    reference_gas_price: Option<u64>,
85    additional_objects: Vec<Object>,
86    jwk_fetch_interval: Option<Duration>,
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    state_accumulator_config: Option<StateAccumulatorV1EnabledConfig>,
97    empty_validator_genesis: bool,
98}
99
100impl ConfigBuilder {
101    pub fn new<P: AsRef<Path>>(config_directory: P) -> Self {
102        Self {
103            rng: Some(OsRng),
104            config_directory: config_directory.as_ref().into(),
105            supported_protocol_versions_config: None,
106            // FIXME: A network with only 1 validator does not have liveness.
107            // We need to change this. There are some tests that depend on it though.
108            committee: CommitteeConfig::Size(NonZeroUsize::new(1).unwrap()),
109            genesis_config: None,
110            reference_gas_price: None,
111            additional_objects: vec![],
112            jwk_fetch_interval: None,
113            num_unpruned_validators: None,
114            authority_overload_config: None,
115            execution_cache_type: 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            state_accumulator_config: Some(StateAccumulatorV1EnabledConfig::Global(true)),
123            empty_validator_genesis: false,
124        }
125    }
126
127    pub fn new_with_temp_dir() -> Self {
128        Self::new(nondeterministic!(tempfile::tempdir().unwrap()).keep())
129    }
130}
131
132impl<R> ConfigBuilder<R> {
133    pub fn committee(mut self, committee: CommitteeConfig) -> Self {
134        self.committee = committee;
135        self
136    }
137
138    pub fn committee_size(mut self, committee_size: NonZeroUsize) -> Self {
139        self.committee = CommitteeConfig::Size(committee_size);
140        self
141    }
142
143    pub fn deterministic_committee_size(mut self, committee_size: NonZeroUsize) -> Self {
144        self.committee = CommitteeConfig::Deterministic((committee_size, None));
145        self
146    }
147
148    pub fn deterministic_committee_validators(mut self, keys: Vec<AccountKeyPair>) -> Self {
149        self.committee = CommitteeConfig::Deterministic((
150            NonZeroUsize::new(keys.len()).expect("Validator keys should be non empty"),
151            Some(keys),
152        ));
153        self
154    }
155
156    pub fn with_validator_account_keys(mut self, keys: Vec<AccountKeyPair>) -> Self {
157        self.committee = CommitteeConfig::AccountKeys(keys);
158        self
159    }
160
161    pub fn with_validators(mut self, validators: Vec<ValidatorGenesisConfig>) -> Self {
162        self.committee = CommitteeConfig::Validators(validators);
163        self
164    }
165
166    pub fn with_genesis_config(mut self, genesis_config: GenesisConfig) -> Self {
167        assert!(self.genesis_config.is_none(), "Genesis config already set");
168        self.genesis_config = Some(genesis_config);
169        self
170    }
171
172    pub fn with_num_unpruned_validators(mut self, n: usize) -> Self {
173        self.num_unpruned_validators = Some(n);
174        self
175    }
176
177    pub fn with_jwk_fetch_interval(mut self, i: Duration) -> Self {
178        self.jwk_fetch_interval = Some(i);
179        self
180    }
181
182    pub fn with_data_ingestion_dir(mut self, path: PathBuf) -> Self {
183        self.data_ingestion_dir = Some(path);
184        self
185    }
186
187    pub fn with_reference_gas_price(mut self, reference_gas_price: u64) -> Self {
188        self.reference_gas_price = Some(reference_gas_price);
189        self
190    }
191
192    pub fn with_accounts(mut self, accounts: Vec<AccountConfig>) -> Self {
193        self.get_or_init_genesis_config().accounts = accounts;
194        self
195    }
196
197    pub fn with_chain_start_timestamp_ms(mut self, chain_start_timestamp_ms: u64) -> Self {
198        self.get_or_init_genesis_config()
199            .parameters
200            .chain_start_timestamp_ms = chain_start_timestamp_ms;
201        self
202    }
203
204    pub fn with_objects<I: IntoIterator<Item = Object>>(mut self, objects: I) -> Self {
205        self.additional_objects.extend(objects);
206        self
207    }
208
209    pub fn with_epoch_duration(mut self, epoch_duration_ms: u64) -> Self {
210        self.get_or_init_genesis_config()
211            .parameters
212            .epoch_duration_ms = epoch_duration_ms;
213        self
214    }
215
216    pub fn with_protocol_version(mut self, protocol_version: ProtocolVersion) -> Self {
217        self.get_or_init_genesis_config()
218            .parameters
219            .protocol_version = protocol_version;
220        self
221    }
222
223    pub fn with_supported_protocol_versions(mut self, c: SupportedProtocolVersions) -> Self {
224        self.supported_protocol_versions_config = Some(ProtocolVersionsConfig::Global(c));
225        self
226    }
227
228    pub fn with_supported_protocol_version_callback(
229        mut self,
230        func: SupportedProtocolVersionsCallback,
231    ) -> Self {
232        self.supported_protocol_versions_config = Some(ProtocolVersionsConfig::PerValidator(func));
233        self
234    }
235
236    pub fn with_supported_protocol_versions_config(mut self, c: ProtocolVersionsConfig) -> Self {
237        self.supported_protocol_versions_config = Some(c);
238        self
239    }
240
241    pub fn with_state_accumulator_callback(
242        mut self,
243        func: StateAccumulatorEnabledCallback,
244    ) -> Self {
245        self.state_accumulator_config = Some(StateAccumulatorV1EnabledConfig::PerValidator(func));
246        self
247    }
248
249    pub fn with_state_accumulator_config(mut self, c: StateAccumulatorV1EnabledConfig) -> Self {
250        self.state_accumulator_config = Some(c);
251        self
252    }
253
254    pub fn with_authority_overload_config(mut self, c: AuthorityOverloadConfig) -> Self {
255        self.authority_overload_config = Some(c);
256        self
257    }
258
259    pub fn with_execution_cache_type(mut self, c: ExecutionCacheType) -> Self {
260        self.execution_cache_type = Some(c);
261        self
262    }
263
264    pub fn with_execution_cache_config(mut self, c: ExecutionCacheConfig) -> Self {
265        self.execution_cache_config = Some(c);
266        self
267    }
268
269    pub fn with_policy_config(mut self, config: Option<PolicyConfig>) -> Self {
270        self.policy_config = config;
271        self
272    }
273
274    pub fn with_firewall_config(mut self, config: Option<RemoteFirewallConfig>) -> Self {
275        self.firewall_config = config;
276        self
277    }
278
279    pub fn with_max_submit_position(mut self, max_submit_position: usize) -> Self {
280        self.max_submit_position = Some(max_submit_position);
281        self
282    }
283
284    pub fn with_submit_delay_step_override_millis(
285        mut self,
286        submit_delay_step_override_millis: u64,
287    ) -> Self {
288        self.submit_delay_step_override_millis = Some(submit_delay_step_override_millis);
289        self
290    }
291
292    pub fn rng<N: rand::RngCore + rand::CryptoRng>(self, rng: N) -> ConfigBuilder<N> {
293        ConfigBuilder {
294            rng: Some(rng),
295            config_directory: self.config_directory,
296            supported_protocol_versions_config: self.supported_protocol_versions_config,
297            committee: self.committee,
298            genesis_config: self.genesis_config,
299            reference_gas_price: self.reference_gas_price,
300            additional_objects: self.additional_objects,
301            num_unpruned_validators: self.num_unpruned_validators,
302            jwk_fetch_interval: self.jwk_fetch_interval,
303            authority_overload_config: self.authority_overload_config,
304            execution_cache_type: self.execution_cache_type,
305            execution_cache_config: self.execution_cache_config,
306            data_ingestion_dir: self.data_ingestion_dir,
307            policy_config: self.policy_config,
308            firewall_config: self.firewall_config,
309            max_submit_position: self.max_submit_position,
310            submit_delay_step_override_millis: self.submit_delay_step_override_millis,
311            state_accumulator_config: self.state_accumulator_config,
312            empty_validator_genesis: self.empty_validator_genesis,
313        }
314    }
315
316    fn get_or_init_genesis_config(&mut self) -> &mut GenesisConfig {
317        if self.genesis_config.is_none() {
318            self.genesis_config = Some(GenesisConfig::for_local_testing());
319        }
320        self.genesis_config.as_mut().unwrap()
321    }
322
323    /// Avoid initializing validator genesis in memory.
324    ///
325    /// This allows callers to create the genesis blob,
326    /// and use a file pointer to configure the validators.
327    pub fn with_empty_validator_genesis(mut self) -> Self {
328        self.empty_validator_genesis = true;
329        self
330    }
331}
332
333impl<R: rand::RngCore + rand::CryptoRng> ConfigBuilder<R> {
334    // TODO right now we always randomize ports, we may want to have a default port
335    // configuration
336    pub fn build(self) -> NetworkConfig {
337        let committee = self.committee;
338
339        let mut rng = self.rng.unwrap();
340        let validators = match committee {
341            CommitteeConfig::Size(size) => {
342                // We always get fixed authority keys from this function (which is isolated from
343                // external test randomness because it uses a fixed seed). Necessary because
344                // some tests call `make_tx_certs_and_signed_effects`, which
345                // locally forges a cert using this same committee.
346                let (_, keys) = Committee::new_simple_test_committee_of_size(size.into());
347
348                keys.into_iter()
349                    .map(|authority_key| {
350                        let mut builder = ValidatorGenesisConfigBuilder::new()
351                            .with_authority_key_pair(authority_key);
352                        if let Some(rgp) = self.reference_gas_price {
353                            builder = builder.with_gas_price(rgp);
354                        }
355                        builder.build(&mut rng)
356                    })
357                    .collect::<Vec<_>>()
358            }
359
360            CommitteeConfig::Validators(v) => v,
361
362            CommitteeConfig::AccountKeys(keys) => {
363                // See above re fixed authority keys
364                let (_, authority_keys) = Committee::new_simple_test_committee_of_size(keys.len());
365                keys.into_iter()
366                    .zip(authority_keys)
367                    .map(|(account_key, authority_key)| {
368                        let mut builder = ValidatorGenesisConfigBuilder::new()
369                            .with_authority_key_pair(authority_key)
370                            .with_account_key_pair(account_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            CommitteeConfig::Deterministic((size, keys)) => {
379                // If no keys are provided, generate them.
380                let keys = keys.unwrap_or(
381                    (0..size.get())
382                        .map(|_| get_key_pair_from_rng(&mut rng).1)
383                        .collect(),
384                );
385
386                let mut configs = vec![];
387                for (i, key) in keys.into_iter().enumerate() {
388                    let port_offset = 8000 + i * 10;
389                    let mut builder = ValidatorGenesisConfigBuilder::new()
390                        .with_ip("127.0.0.1".to_owned())
391                        .with_account_key_pair(key)
392                        .with_deterministic_ports(port_offset as u16);
393                    if let Some(rgp) = self.reference_gas_price {
394                        builder = builder.with_gas_price(rgp);
395                    }
396                    configs.push(builder.build(&mut rng));
397                }
398                configs
399            }
400        };
401
402        let mut genesis_config = self
403            .genesis_config
404            .unwrap_or_else(GenesisConfig::for_local_testing);
405
406        let (account_keys, allocations) = genesis_config.generate_accounts(&mut rng).unwrap();
407
408        let token_distribution_schedule = {
409            let mut builder = TokenDistributionScheduleBuilder::new();
410            for allocation in allocations {
411                builder.add_allocation(allocation);
412            }
413            // Add allocations for each validator
414            for validator in &validators {
415                let account_key: PublicKey = validator.account_key_pair.public();
416                let address = IotaAddress::from(&account_key);
417                // Give each validator some gas so they can pay for their transactions.
418                let gas_coin = TokenAllocation {
419                    recipient_address: address,
420                    amount_nanos: DEFAULT_GAS_AMOUNT,
421                    staked_with_validator: None,
422                    staked_with_timelock_expiration: None,
423                };
424                let stake = TokenAllocation {
425                    recipient_address: address,
426                    amount_nanos: validator.stake,
427                    staked_with_validator: Some(address),
428                    staked_with_timelock_expiration: None,
429                };
430                builder.add_allocation(gas_coin);
431                builder.add_allocation(stake);
432            }
433            builder.build()
434        };
435
436        let GenesisBuildEffects {
437            genesis,
438            migration_tx_data,
439        } = {
440            let mut builder = iota_genesis_builder::Builder::new()
441                .with_parameters(genesis_config.parameters)
442                .add_objects(self.additional_objects);
443            for source in std::mem::take(&mut genesis_config.migration_sources) {
444                builder = builder.add_migration_source(source);
445            }
446
447            for (i, validator) in validators.iter().enumerate() {
448                let name = validator
449                    .name
450                    .clone()
451                    .unwrap_or(format!("validator-{i}").to_string());
452                let validator_info = validator.to_validator_info(name);
453                builder =
454                    builder.add_validator(validator_info.info, validator_info.proof_of_possession);
455            }
456
457            builder = builder.with_token_distribution_schedule(token_distribution_schedule);
458
459            // Add delegator to genesis builder.
460            if let Some(delegator) = genesis_config.delegator {
461                builder = builder.with_delegator(delegator);
462            }
463
464            for validator in &validators {
465                builder = builder.add_validator_signature(&validator.authority_key_pair);
466            }
467
468            builder.build()
469        };
470
471        if let Some(migration_tx_data) = migration_tx_data {
472            migration_tx_data
473                .save(
474                    self.config_directory
475                        .join(IOTA_GENESIS_MIGRATION_TX_DATA_FILENAME),
476                )
477                .expect("Should be able to save the migration data");
478        }
479
480        let validator_configs = validators
481            .into_iter()
482            .enumerate()
483            .map(|(idx, validator)| {
484                let mut builder = ValidatorConfigBuilder::new()
485                    .with_config_directory(self.config_directory.clone())
486                    .with_policy_config(self.policy_config.clone())
487                    .with_firewall_config(self.firewall_config.clone());
488
489                if let Some(max_submit_position) = self.max_submit_position {
490                    builder = builder.with_max_submit_position(max_submit_position);
491                }
492
493                if let Some(submit_delay_step_override_millis) =
494                    self.submit_delay_step_override_millis
495                {
496                    builder = builder
497                        .with_submit_delay_step_override_millis(submit_delay_step_override_millis);
498                }
499
500                if let Some(jwk_fetch_interval) = self.jwk_fetch_interval {
501                    builder = builder.with_jwk_fetch_interval(jwk_fetch_interval);
502                }
503
504                if let Some(authority_overload_config) = &self.authority_overload_config {
505                    builder =
506                        builder.with_authority_overload_config(authority_overload_config.clone());
507                }
508
509                if let Some(execution_cache_type) = &self.execution_cache_type {
510                    builder = builder.with_execution_cache_type(*execution_cache_type);
511                }
512
513                if let Some(execution_cache_config) = &self.execution_cache_config {
514                    builder = builder.with_execution_cache_config(execution_cache_config.clone());
515                }
516
517                if let Some(path) = &self.data_ingestion_dir {
518                    builder = builder.with_data_ingestion_dir(path.clone());
519                }
520
521                if let Some(spvc) = &self.supported_protocol_versions_config {
522                    let supported_versions = match spvc {
523                        ProtocolVersionsConfig::Default => {
524                            SupportedProtocolVersions::SYSTEM_DEFAULT
525                        }
526                        ProtocolVersionsConfig::Global(v) => *v,
527                        ProtocolVersionsConfig::PerValidator(func) => {
528                            func(idx, Some(validator.authority_key_pair.public().into()))
529                        }
530                    };
531                    builder = builder.with_supported_protocol_versions(supported_versions);
532                }
533                if let Some(num_unpruned_validators) = self.num_unpruned_validators {
534                    if idx < num_unpruned_validators {
535                        builder = builder.with_unpruned_checkpoints();
536                    }
537                }
538                if self.empty_validator_genesis {
539                    builder.build_without_genesis(validator)
540                } else {
541                    builder.build(validator, genesis.clone())
542                }
543            })
544            .collect();
545        NetworkConfig {
546            validator_configs,
547            genesis,
548            account_keys,
549        }
550    }
551}
552
553#[cfg(test)]
554mod tests {
555    use iota_config::node::Genesis;
556
557    #[test]
558    fn serialize_genesis_config_in_place() {
559        let dir = tempfile::TempDir::new().unwrap();
560        let network_config = crate::network_config_builder::ConfigBuilder::new(&dir).build();
561        let genesis = network_config.genesis;
562
563        let g = Genesis::new(genesis);
564
565        let mut s = serde_yaml::to_string(&g).unwrap();
566        let loaded_genesis: Genesis = serde_yaml::from_str(&s).unwrap();
567        loaded_genesis
568            .genesis()
569            .unwrap()
570            .checkpoint_contents()
571            .digest(); // cache digest before comparing.
572        assert_eq!(g, loaded_genesis);
573
574        // If both in-place and file location are provided, prefer the in-place variant
575        s.push_str("\ngenesis-file-location: path/to/file");
576        let loaded_genesis: Genesis = serde_yaml::from_str(&s).unwrap();
577        loaded_genesis
578            .genesis()
579            .unwrap()
580            .checkpoint_contents()
581            .digest(); // cache digest before comparing.
582        assert_eq!(g, loaded_genesis);
583    }
584
585    #[test]
586    fn load_genesis_config_from_file() {
587        let file = tempfile::NamedTempFile::new().unwrap();
588        let genesis_config = Genesis::new_from_file(file.path());
589
590        let dir = tempfile::TempDir::new().unwrap();
591        let network_config = crate::network_config_builder::ConfigBuilder::new(&dir).build();
592        let genesis = network_config.genesis;
593        genesis.save(file.path()).unwrap();
594
595        let loaded_genesis = genesis_config.genesis().unwrap();
596        loaded_genesis.checkpoint_contents().digest(); // cache digest before comparing.
597        assert_eq!(&genesis, loaded_genesis);
598    }
599}
600
601#[cfg(test)]
602mod test {
603    use std::{collections::HashSet, sync::Arc};
604
605    use iota_config::genesis::Genesis;
606    use iota_protocol_config::{Chain, ProtocolConfig, ProtocolVersion};
607    use iota_types::{
608        epoch_data::EpochData, gas::IotaGasStatus, in_memory_storage::InMemoryStorage,
609        iota_system_state::IotaSystemStateTrait, metrics::LimitsMetrics,
610        transaction::CheckedInputObjects,
611    };
612
613    #[test]
614    fn roundtrip() {
615        let dir = tempfile::TempDir::new().unwrap();
616        let network_config = crate::network_config_builder::ConfigBuilder::new(&dir).build();
617        let genesis = network_config.genesis;
618
619        let s = serde_yaml::to_string(&genesis).unwrap();
620        let from_s: Genesis = serde_yaml::from_str(&s).unwrap();
621        // cache the digest so that the comparison succeeds.
622        from_s.checkpoint_contents().digest();
623        assert_eq!(genesis, from_s);
624    }
625
626    #[test]
627    fn genesis_transaction() {
628        let builder = crate::network_config_builder::ConfigBuilder::new_with_temp_dir();
629        let network_config = builder.build();
630        let genesis = network_config.genesis;
631        let protocol_version =
632            ProtocolVersion::new(genesis.iota_system_object().protocol_version());
633        let protocol_config = ProtocolConfig::get_for_version(protocol_version, Chain::Unknown);
634
635        let genesis_transaction = genesis.transaction().clone();
636
637        let genesis_digest = *genesis_transaction.digest();
638
639        let silent = true;
640        let executor = iota_execution::executor(&protocol_config, silent, None)
641            .expect("Creating an executor should not fail here");
642
643        // Use a throwaway metrics registry for genesis transaction execution.
644        let registry = prometheus::Registry::new();
645        let metrics = Arc::new(LimitsMetrics::new(&registry));
646        let expensive_checks = false;
647        let certificate_deny_set = HashSet::new();
648        let epoch = EpochData::new_test();
649        let transaction_data = &genesis_transaction.data().intent_message().value;
650        let (kind, signer, _) = transaction_data.execution_parts();
651        let input_objects = CheckedInputObjects::new_for_genesis(vec![]);
652
653        let (_inner_temp_store, _, effects, _execution_error) = executor
654            .execute_transaction_to_effects(
655                &InMemoryStorage::new(Vec::new()),
656                &protocol_config,
657                metrics,
658                expensive_checks,
659                &certificate_deny_set,
660                &epoch.epoch_id(),
661                epoch.epoch_start_timestamp(),
662                input_objects,
663                vec![],
664                IotaGasStatus::new_unmetered(),
665                kind,
666                signer,
667                genesis_digest,
668                &mut None,
669            );
670
671        assert_eq!(&effects, genesis.effects());
672    }
673}