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