Skip to main content

iota_swarm_config/
genesis_config.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::net::{IpAddr, SocketAddr};
6
7use anyhow::Result;
8use fastcrypto::traits::KeyPair;
9use iota_config::{
10    Config,
11    genesis::{GenesisCeremonyParameters, PRE_V32_VALIDATOR_LOW_STAKE_THRESHOLD, TokenAllocation},
12    local_ip_utils,
13    node::{DEFAULT_COMMISSION_RATE, DEFAULT_VALIDATOR_GAS_PRICE},
14};
15use iota_genesis_builder::{
16    SnapshotSource,
17    validator_info::{GenesisValidatorInfo, ValidatorInfo},
18};
19use iota_protocol_config::{Chain, ProtocolConfig};
20use iota_sdk_types::Address;
21use iota_types::{
22    committee::ProtocolVersion,
23    crypto::{
24        AccountKeyPair, AuthorityKeyPair, AuthorityPublicKeyBytes, IotaKeyPair, NetworkKeyPair,
25        NetworkPublicKey, PublicKey, generate_proof_of_possession, get_key_pair_from_rng,
26    },
27    multiaddr::Multiaddr,
28};
29use rand::{SeedableRng, rngs::StdRng};
30use serde::{Deserialize, Serialize};
31use tracing::info;
32
33// All information needed to build a NodeConfig for a state sync fullnode.
34#[derive(Serialize, Deserialize, Debug)]
35pub struct SsfnGenesisConfig {
36    pub p2p_address: Multiaddr,
37    pub network_key_pair: Option<NetworkKeyPair>,
38}
39
40// All information needed to build a NodeConfig for a validator.
41#[derive(Serialize, Deserialize)]
42pub struct ValidatorGenesisConfig {
43    #[serde(default = "default_bls12381_key_pair")]
44    pub authority_key_pair: AuthorityKeyPair,
45    #[serde(default = "default_ed25519_key_pair")]
46    pub protocol_key_pair: NetworkKeyPair,
47    #[serde(default = "default_iota_key_pair")]
48    pub account_key_pair: IotaKeyPair,
49    #[serde(default = "default_ed25519_key_pair")]
50    pub network_key_pair: NetworkKeyPair,
51    pub network_address: Multiaddr,
52    pub p2p_address: Multiaddr,
53    pub p2p_listen_address: Option<SocketAddr>,
54    #[serde(default = "default_socket_address")]
55    pub metrics_address: SocketAddr,
56    #[serde(default = "default_socket_address")]
57    pub admin_interface_address: SocketAddr,
58    pub gas_price: u64,
59    pub commission_rate: u64,
60    pub primary_address: Multiaddr,
61    #[serde(default = "default_stake")]
62    pub stake: u64,
63    pub name: Option<String>,
64}
65
66impl ValidatorGenesisConfig {
67    pub fn to_validator_info(&self, name: String) -> GenesisValidatorInfo {
68        let authority_key: AuthorityPublicKeyBytes = self.authority_key_pair.public().into();
69        let account_key: PublicKey = self.account_key_pair.public();
70        let network_key: NetworkPublicKey = self.network_key_pair.public().clone();
71        let protocol_key: NetworkPublicKey = self.protocol_key_pair.public().clone();
72        let network_address = self.network_address.clone();
73
74        let info = ValidatorInfo {
75            name,
76            authority_key,
77            protocol_key,
78            network_key,
79            account_address: Address::from(&account_key),
80            gas_price: self.gas_price,
81            commission_rate: self.commission_rate,
82            network_address,
83            p2p_address: self.p2p_address.clone(),
84            primary_address: self.primary_address.clone(),
85            description: String::new(),
86            image_url: String::new(),
87            project_url: String::new(),
88        };
89        let proof_of_possession = generate_proof_of_possession(
90            &self.authority_key_pair,
91            (&self.account_key_pair.public()).into(),
92        );
93        GenesisValidatorInfo {
94            info,
95            proof_of_possession,
96        }
97    }
98
99    /// Use validator public key as validator name.
100    pub fn to_validator_info_with_random_name(&self) -> GenesisValidatorInfo {
101        self.to_validator_info(self.authority_key_pair.public().to_string())
102    }
103}
104
105#[derive(Default)]
106pub struct ValidatorGenesisConfigBuilder {
107    authority_key_pair: Option<AuthorityKeyPair>,
108    account_key_pair: Option<AccountKeyPair>,
109    ip: Option<String>,
110    gas_price: Option<u64>,
111    /// If set, the validator will use deterministic addresses based on the port
112    /// offset. This is useful for benchmarking.
113    port_offset: Option<u16>,
114    /// Whether to use a specific p2p listen ip address. This is useful for
115    /// testing on AWS.
116    p2p_listen_ip_address: Option<IpAddr>,
117}
118
119impl ValidatorGenesisConfigBuilder {
120    pub fn new() -> Self {
121        Self::default()
122    }
123
124    pub fn with_authority_key_pair(mut self, key_pair: AuthorityKeyPair) -> Self {
125        self.authority_key_pair = Some(key_pair);
126        self
127    }
128
129    pub fn with_account_key_pair(mut self, key_pair: AccountKeyPair) -> Self {
130        self.account_key_pair = Some(key_pair);
131        self
132    }
133
134    pub fn with_ip(mut self, ip: String) -> Self {
135        self.ip = Some(ip);
136        self
137    }
138
139    pub fn with_gas_price(mut self, gas_price: u64) -> Self {
140        self.gas_price = Some(gas_price);
141        self
142    }
143
144    pub fn with_deterministic_ports(mut self, port_offset: u16) -> Self {
145        self.port_offset = Some(port_offset);
146        self
147    }
148
149    pub fn with_p2p_listen_ip_address(mut self, p2p_listen_ip_address: IpAddr) -> Self {
150        self.p2p_listen_ip_address = Some(p2p_listen_ip_address);
151        self
152    }
153
154    pub fn build<R: rand::RngCore + rand::CryptoRng>(self, rng: &mut R) -> ValidatorGenesisConfig {
155        let ip = self.ip.unwrap_or_else(local_ip_utils::get_new_ip);
156        let localhost = local_ip_utils::localhost_for_testing();
157
158        let authority_key_pair = self
159            .authority_key_pair
160            .unwrap_or_else(|| get_key_pair_from_rng(rng).1);
161        let account_key_pair = self
162            .account_key_pair
163            .unwrap_or_else(|| get_key_pair_from_rng(rng).1);
164        let gas_price = self.gas_price.unwrap_or(DEFAULT_VALIDATOR_GAS_PRICE);
165
166        let (protocol_key_pair, network_key_pair): (NetworkKeyPair, NetworkKeyPair) =
167            (get_key_pair_from_rng(rng).1, get_key_pair_from_rng(rng).1);
168
169        let (
170            network_address,
171            p2p_address,
172            metrics_address,
173            primary_address,
174            admin_interface_address,
175        ) = if let Some(offset) = self.port_offset {
176            (
177                local_ip_utils::new_deterministic_tcp_address_for_testing(&ip, offset),
178                local_ip_utils::new_deterministic_udp_address_for_testing(&ip, offset + 1),
179                local_ip_utils::new_deterministic_tcp_address_for_testing(&ip, offset + 2)
180                    .with_zero_ip(),
181                local_ip_utils::new_deterministic_udp_address_for_testing(&ip, offset + 3),
182                local_ip_utils::new_deterministic_tcp_address_for_testing(&ip, offset + 4),
183            )
184        } else {
185            (
186                local_ip_utils::new_tcp_address_for_testing(&ip),
187                local_ip_utils::new_udp_address_for_testing(&ip),
188                local_ip_utils::new_tcp_address_for_testing(&localhost),
189                local_ip_utils::new_udp_address_for_testing(&ip),
190                local_ip_utils::new_tcp_address_for_testing(&localhost),
191            )
192        };
193
194        let p2p_listen_address = self
195            .p2p_listen_ip_address
196            .map(|ip| SocketAddr::new(ip, p2p_address.port().unwrap()));
197
198        ValidatorGenesisConfig {
199            authority_key_pair,
200            protocol_key_pair,
201            account_key_pair: account_key_pair.into(),
202            network_key_pair,
203            network_address,
204            p2p_address,
205            p2p_listen_address,
206            metrics_address: metrics_address.to_socket_addr().unwrap(),
207            admin_interface_address: admin_interface_address.to_socket_addr().unwrap(),
208            gas_price,
209            commission_rate: DEFAULT_COMMISSION_RATE,
210            primary_address,
211            // A test-wide protocol config override can replace even the MAX lookup
212            // with a pre-version-32 config where the threshold is absent, so fall
213            // back to the historical value.
214            stake: ProtocolConfig::get_for_version(ProtocolVersion::MAX, Chain::Unknown)
215                .validator_low_stake_threshold_as_option()
216                .unwrap_or(PRE_V32_VALIDATOR_LOW_STAKE_THRESHOLD),
217            name: None,
218        }
219    }
220}
221
222#[derive(Serialize, Deserialize, Default)]
223pub struct GenesisConfig {
224    pub ssfn_config_info: Option<Vec<SsfnGenesisConfig>>,
225    pub validator_config_info: Option<Vec<ValidatorGenesisConfig>>,
226    pub parameters: GenesisCeremonyParameters,
227    pub accounts: Vec<AccountConfig>,
228    pub migration_sources: Vec<SnapshotSource>,
229    pub delegator: Option<Address>,
230}
231
232impl Config for GenesisConfig {}
233
234impl GenesisConfig {
235    /// The protocol config for the version this genesis will be built at.
236    pub fn protocol_config(&self) -> ProtocolConfig {
237        ProtocolConfig::get_for_version(self.parameters.protocol_version, Chain::Unknown)
238    }
239
240    pub fn generate_accounts<R: rand::RngCore + rand::CryptoRng>(
241        &self,
242        mut rng: R,
243    ) -> Result<(Vec<AccountKeyPair>, Vec<TokenAllocation>)> {
244        let mut addresses = Vec::new();
245        let mut allocations = Vec::new();
246
247        info!("Creating accounts and token allocations...");
248
249        let mut keys = Vec::new();
250        for account in &self.accounts {
251            let address = if let Some(address) = account.address {
252                address
253            } else {
254                let (address, keypair) = get_key_pair_from_rng(&mut rng);
255                keys.push(keypair);
256                address
257            };
258
259            addresses.push(address);
260
261            // Populate gas itemized objects
262            account.gas_amounts.iter().for_each(|a| {
263                allocations.push(TokenAllocation {
264                    recipient_address: address,
265                    amount_nanos: *a,
266                    staked_with_validator: None,
267                    staked_with_timelock_expiration: None,
268                });
269            });
270        }
271
272        Ok((keys, allocations))
273    }
274}
275
276fn default_socket_address() -> SocketAddr {
277    local_ip_utils::new_local_tcp_socket_for_testing()
278}
279
280fn default_stake() -> u64 {
281    // A test-wide protocol config override can replace even the MAX lookup with a
282    // pre-version-32 config where the threshold is absent, so fall back to the
283    // historical value.
284    ProtocolConfig::get_for_version(ProtocolVersion::MAX, Chain::Unknown)
285        .validator_low_stake_threshold_as_option()
286        .unwrap_or(PRE_V32_VALIDATOR_LOW_STAKE_THRESHOLD)
287}
288
289fn default_bls12381_key_pair() -> AuthorityKeyPair {
290    get_key_pair_from_rng(&mut rand::rngs::OsRng).1
291}
292
293fn default_ed25519_key_pair() -> NetworkKeyPair {
294    get_key_pair_from_rng(&mut rand::rngs::OsRng).1
295}
296
297fn default_iota_key_pair() -> IotaKeyPair {
298    IotaKeyPair::Ed25519(get_key_pair_from_rng(&mut rand::rngs::OsRng).1)
299}
300
301#[derive(Serialize, Deserialize, Debug, Clone)]
302pub struct AccountConfig {
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub address: Option<Address>,
305    pub gas_amounts: Vec<u64>,
306}
307
308pub const DEFAULT_GAS_AMOUNT: u64 = 30_000_000_000_000_000;
309pub const DEFAULT_NUMBER_OF_AUTHORITIES: usize = 4;
310const DEFAULT_NUMBER_OF_ACCOUNT: usize = 5;
311pub const DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT: usize = 5;
312
313impl GenesisConfig {
314    /// A predictable rng seed used to generate benchmark configs. This seed may
315    /// also be needed by other crates (e.g. the load generators).
316    pub const BENCHMARKS_RNG_SEED: u64 = 0;
317    /// Port offset for benchmarks' genesis configs.
318    pub const BENCHMARKS_PORT_OFFSET: u16 = 2000;
319    /// Trigger epoch change every hour.
320    const BENCHMARK_EPOCH_DURATION_MS: u64 = 60 * 60 * 1000;
321
322    pub fn for_local_testing() -> Self {
323        Self::custom_genesis(
324            DEFAULT_NUMBER_OF_ACCOUNT,
325            DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT,
326        )
327    }
328
329    pub fn for_local_testing_with_addresses(addresses: Vec<Address>) -> Self {
330        Self::custom_genesis_with_addresses(addresses, DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT)
331    }
332
333    pub fn custom_genesis(num_accounts: usize, num_objects_per_account: usize) -> Self {
334        let mut accounts = Vec::new();
335        for _ in 0..num_accounts {
336            accounts.push(AccountConfig {
337                address: None,
338                gas_amounts: vec![DEFAULT_GAS_AMOUNT; num_objects_per_account],
339            })
340        }
341
342        Self {
343            accounts,
344            ..Default::default()
345        }
346    }
347
348    pub fn custom_genesis_with_addresses(
349        addresses: Vec<Address>,
350        num_objects_per_account: usize,
351    ) -> Self {
352        let mut accounts = Vec::new();
353        for address in addresses {
354            accounts.push(AccountConfig {
355                address: Some(address),
356                gas_amounts: vec![DEFAULT_GAS_AMOUNT; num_objects_per_account],
357            })
358        }
359
360        Self {
361            accounts,
362            ..Default::default()
363        }
364    }
365
366    /// Generate a genesis config allowing to easily bootstrap a network for
367    /// benchmarking purposes. This function is ultimately used to print the
368    /// genesis blob and all validators configs. All keys and parameters are
369    /// predictable to facilitate benchmarks orchestration. Only the main ip
370    /// addresses of the validators are specified (as those are often
371    /// dictated by the cloud provider hosing the testbed).
372    ///
373    /// `num_additional_gas_accounts` specifies how many additional gas accounts
374    /// to create. This can be used to support more dedicated client instances.
375    ///
376    /// `total_available_amount` specifies the total amount of tokens available
377    /// for all allocations. The function will divide the available amount
378    /// among all account gas objects.
379    pub fn new_for_benchmarks(
380        ips: &[String],
381        epoch_duration_ms: Option<u64>,
382        chain_start_timestamp_ms: Option<u64>,
383        num_additional_gas_accounts: Option<usize>,
384        total_available_amount: u64,
385    ) -> Self {
386        // this translates to an assert in iota::balance::increase_supply
387        assert!(
388            total_available_amount < u64::MAX,
389            "Total available amount must be less than 18446744073709551615u64"
390        );
391        // Set the validator's configs. They should be the same across multiple runs to
392        // ensure reproducibility.
393        let mut rng = StdRng::seed_from_u64(Self::BENCHMARKS_RNG_SEED);
394        let validator_config_info: Vec<_> = ips
395            .iter()
396            .enumerate()
397            .map(|(i, ip)| {
398                ValidatorGenesisConfigBuilder::new()
399                    .with_ip(ip.to_string())
400                    .with_deterministic_ports(Self::BENCHMARKS_PORT_OFFSET + 10 * i as u16)
401                    .with_p2p_listen_ip_address("0.0.0.0".parse().unwrap())
402                    .build(&mut rng)
403            })
404            .collect();
405
406        // Set the initial gas objects with a predictable owner address.
407        let num_validators = validator_config_info.len();
408        let num_accounts = num_additional_gas_accounts.unwrap_or(0) + num_validators;
409
410        // Divide the total available amount among all account gas objects.
411        let total_gas_objects = num_accounts * DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT;
412        let gas_amount_per_object = if total_gas_objects > 0 {
413            total_available_amount / total_gas_objects as u64
414        } else {
415            0
416        };
417
418        let account_configs = Self::benchmark_gas_keys(num_accounts)
419            .iter()
420            .map(|gas_key| {
421                let gas_address = Address::from(&gas_key.public());
422
423                AccountConfig {
424                    address: Some(gas_address),
425                    gas_amounts: vec![gas_amount_per_object; DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT],
426                }
427            })
428            .collect();
429
430        // Benchmarks require a deterministic genesis. Every validator locally generates
431        // it own genesis; it is thus important they have the same parameters.
432        let parameters = GenesisCeremonyParameters {
433            chain_start_timestamp_ms: chain_start_timestamp_ms.unwrap_or(0),
434            epoch_duration_ms: if let Some(duration_ms) = epoch_duration_ms {
435                duration_ms
436            } else {
437                Self::BENCHMARK_EPOCH_DURATION_MS
438            },
439            ..GenesisCeremonyParameters::new()
440        };
441
442        // Make a new genesis configuration.
443        GenesisConfig {
444            ssfn_config_info: None,
445            validator_config_info: Some(validator_config_info),
446            parameters,
447            accounts: account_configs,
448            migration_sources: Default::default(),
449            delegator: Default::default(),
450        }
451    }
452
453    /// Generate a predictable and fixed key that will own all gas objects used
454    /// for benchmarks. This function may be called by other parts of the
455    /// codebase (e.g. load generators) to get the same keypair used for
456    /// genesis (hence the importance of the seedable rng).
457    pub fn benchmark_gas_keys(n: usize) -> Vec<IotaKeyPair> {
458        let mut rng = StdRng::seed_from_u64(Self::BENCHMARKS_RNG_SEED);
459        (0..n)
460            .map(|_| IotaKeyPair::Ed25519(AccountKeyPair::generate(&mut rng)))
461            .collect()
462    }
463
464    pub fn add_faucet_account(mut self) -> Self {
465        self.accounts.push(AccountConfig {
466            address: None,
467            gas_amounts: vec![DEFAULT_GAS_AMOUNT; DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT],
468        });
469        self
470    }
471
472    pub fn add_delegator(mut self, address: Address) -> Self {
473        self.delegator = Some(address);
474        self
475    }
476}