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