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    /// The gas amount for each genesis gas object.
303    const BENCHMARK_GAS_AMOUNT: u64 = 1_000_000_000_000_000_000;
304    /// Trigger epoch change every hour minutes.
305    const BENCHMARK_EPOCH_DURATION_MS: u64 = 60 * 60 * 1000;
306
307    pub fn for_local_testing() -> Self {
308        Self::custom_genesis(
309            DEFAULT_NUMBER_OF_ACCOUNT,
310            DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT,
311        )
312    }
313
314    pub fn for_local_testing_with_addresses(addresses: Vec<IotaAddress>) -> Self {
315        Self::custom_genesis_with_addresses(addresses, DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT)
316    }
317
318    pub fn custom_genesis(num_accounts: usize, num_objects_per_account: usize) -> Self {
319        let mut accounts = Vec::new();
320        for _ in 0..num_accounts {
321            accounts.push(AccountConfig {
322                address: None,
323                gas_amounts: vec![DEFAULT_GAS_AMOUNT; num_objects_per_account],
324            })
325        }
326
327        Self {
328            accounts,
329            ..Default::default()
330        }
331    }
332
333    pub fn custom_genesis_with_addresses(
334        addresses: Vec<IotaAddress>,
335        num_objects_per_account: usize,
336    ) -> Self {
337        let mut accounts = Vec::new();
338        for address in addresses {
339            accounts.push(AccountConfig {
340                address: Some(address),
341                gas_amounts: vec![DEFAULT_GAS_AMOUNT; num_objects_per_account],
342            })
343        }
344
345        Self {
346            accounts,
347            ..Default::default()
348        }
349    }
350
351    /// Generate a genesis config allowing to easily bootstrap a network for
352    /// benchmarking purposes. This function is ultimately used to print the
353    /// genesis blob and all validators configs. All keys and parameters are
354    /// predictable to facilitate benchmarks orchestration. Only the main ip
355    /// addresses of the validators are specified (as those are often
356    /// dictated by the cloud provider hosing the testbed).
357    ///
358    /// `num_additional_gas_accounts` specifies how many additional gas accounts
359    /// to create. This can be used to support more dedicated client instances.
360    pub fn new_for_benchmarks(
361        ips: &[String],
362        epoch_duration_ms: Option<u64>,
363        chain_start_timestamp_ms: Option<u64>,
364        num_additional_gas_accounts: Option<usize>,
365    ) -> Self {
366        // Set the validator's configs. They should be the same across multiple runs to
367        // ensure reproducibility.
368        let mut rng = StdRng::seed_from_u64(Self::BENCHMARKS_RNG_SEED);
369        let validator_config_info: Vec<_> = ips
370            .iter()
371            .enumerate()
372            .map(|(i, ip)| {
373                ValidatorGenesisConfigBuilder::new()
374                    .with_ip(ip.to_string())
375                    .with_deterministic_ports(Self::BENCHMARKS_PORT_OFFSET + 10 * i as u16)
376                    .with_p2p_listen_ip_address("0.0.0.0".parse().unwrap())
377                    .build(&mut rng)
378            })
379            .collect();
380
381        // Set the initial gas objects with a predictable owner address.
382        let num_accounts = num_additional_gas_accounts.unwrap_or(0) + validator_config_info.len();
383        let account_configs = Self::benchmark_gas_keys(num_accounts)
384            .iter()
385            .map(|gas_key| {
386                let gas_address = IotaAddress::from(&gas_key.public());
387
388                AccountConfig {
389                    address: Some(gas_address),
390                    // Generate one genesis gas object per validator (this seems a good rule of
391                    // thumb to produce enough gas objects for most types of
392                    // benchmarks), or more if specified via `num_additional_gas_accounts`.
393                    gas_amounts: vec![
394                        u64::min(
395                            Self::BENCHMARK_GAS_AMOUNT,
396                            u64::MAX / (6u64 * num_accounts as u64),
397                        );
398                        5
399                    ],
400                }
401            })
402            .collect();
403
404        // Benchmarks require a deterministic genesis. Every validator locally generates
405        // it own genesis; it is thus important they have the same parameters.
406        let parameters = GenesisCeremonyParameters {
407            chain_start_timestamp_ms: chain_start_timestamp_ms.unwrap_or(0),
408            epoch_duration_ms: if let Some(duration_ms) = epoch_duration_ms {
409                duration_ms
410            } else {
411                Self::BENCHMARK_EPOCH_DURATION_MS
412            },
413            ..GenesisCeremonyParameters::new()
414        };
415
416        // Make a new genesis configuration.
417        GenesisConfig {
418            ssfn_config_info: None,
419            validator_config_info: Some(validator_config_info),
420            parameters,
421            accounts: account_configs,
422            migration_sources: Default::default(),
423            delegator: Default::default(),
424        }
425    }
426
427    /// Generate a predictable and fixed key that will own all gas objects used
428    /// for benchmarks. This function may be called by other parts of the
429    /// codebase (e.g. load generators) to get the same keypair used for
430    /// genesis (hence the importance of the seedable rng).
431    pub fn benchmark_gas_keys(n: usize) -> Vec<IotaKeyPair> {
432        let mut rng = StdRng::seed_from_u64(Self::BENCHMARKS_RNG_SEED);
433        (0..n)
434            .map(|_| IotaKeyPair::Ed25519(NetworkKeyPair::generate(&mut rng)))
435            .collect()
436    }
437
438    pub fn add_faucet_account(mut self) -> Self {
439        self.accounts.push(AccountConfig {
440            address: None,
441            gas_amounts: vec![DEFAULT_GAS_AMOUNT; DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT],
442        });
443        self
444    }
445
446    pub fn add_delegator(mut self, address: IotaAddress) -> Self {
447        self.delegator = Some(address);
448        self
449    }
450}