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::validator_info::{GenesisValidatorInfo, ValidatorInfo};
16use iota_multiaddr::Multiaddr;
17use iota_protocol_config::{Chain, ProtocolConfig};
18use iota_sdk_crypto::simple::SimpleKeypair;
19use iota_sdk_types::Address;
20use iota_types::{
21    committee::ProtocolVersion,
22    crypto::{
23        AccountPrivateKey, AuthorityKeyPair, AuthorityPublicKeyBytes, NetworkKeyPair,
24        NetworkPublicKey, PublicKey, generate_proof_of_possession, get_key_pair_from_rng,
25    },
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", with = "base64_formatted_keypair")]
46    pub account_key_pair: SimpleKeypair,
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::from(&self.account_key_pair);
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: Address::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            (&PublicKey::from(&self.account_key_pair)).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_private_key: Option<AccountPrivateKey>,
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    metrics_ip_address: Option<IpAddr>,
116}
117
118impl ValidatorGenesisConfigBuilder {
119    pub fn new() -> Self {
120        Self::default()
121    }
122
123    pub fn with_authority_key_pair(mut self, key_pair: AuthorityKeyPair) -> Self {
124        self.authority_key_pair = Some(key_pair);
125        self
126    }
127
128    pub fn with_account_private_key(mut self, private_key: AccountPrivateKey) -> Self {
129        self.account_private_key = Some(private_key);
130        self
131    }
132
133    pub fn with_ip(mut self, ip: String) -> Self {
134        self.ip = Some(ip);
135        self
136    }
137
138    pub fn with_gas_price(mut self, gas_price: u64) -> Self {
139        self.gas_price = Some(gas_price);
140        self
141    }
142
143    pub fn with_deterministic_ports(mut self, port_offset: u16) -> Self {
144        self.port_offset = Some(port_offset);
145        self
146    }
147
148    pub fn with_p2p_listen_ip_address(mut self, p2p_listen_ip_address: IpAddr) -> Self {
149        self.p2p_listen_ip_address = Some(p2p_listen_ip_address);
150        self
151    }
152
153    /// Bind the metrics endpoint to `metrics_ip_address`. Without this, it
154    /// binds all interfaces when the ports are deterministic, so that a
155    /// testbed can scrape it, and localhost otherwise.
156    pub fn with_metrics_ip_address(mut self, metrics_ip_address: IpAddr) -> Self {
157        self.metrics_ip_address = Some(metrics_ip_address);
158        self
159    }
160
161    pub fn build<R: rand::RngCore + rand::CryptoRng>(self, rng: &mut R) -> ValidatorGenesisConfig {
162        let ip = self.ip.unwrap_or_else(local_ip_utils::get_new_ip);
163        let localhost = local_ip_utils::localhost_for_testing();
164
165        let authority_key_pair = self
166            .authority_key_pair
167            .unwrap_or_else(|| get_key_pair_from_rng(rng).1);
168        let account_private_key = self
169            .account_private_key
170            .unwrap_or_else(|| get_key_pair_from_rng(rng).1);
171        let gas_price = self.gas_price.unwrap_or(DEFAULT_VALIDATOR_GAS_PRICE);
172
173        let (protocol_key_pair, network_key_pair): (NetworkKeyPair, NetworkKeyPair) =
174            (get_key_pair_from_rng(rng).1, get_key_pair_from_rng(rng).1);
175
176        let metrics_ip = self.metrics_ip_address.map(|ip| ip.to_string());
177
178        let (
179            network_address,
180            p2p_address,
181            metrics_address,
182            primary_address,
183            admin_interface_address,
184        ) = if let Some(offset) = self.port_offset {
185            (
186                local_ip_utils::new_deterministic_tcp_address_for_testing(&ip, offset),
187                local_ip_utils::new_deterministic_udp_address_for_testing(&ip, offset + 1),
188                match &metrics_ip {
189                    Some(metrics_ip) => local_ip_utils::new_deterministic_tcp_address_for_testing(
190                        metrics_ip,
191                        offset + 2,
192                    ),
193                    None => {
194                        local_ip_utils::new_deterministic_tcp_address_for_testing(&ip, offset + 2)
195                            .with_zero_ip()
196                    }
197                },
198                local_ip_utils::new_deterministic_udp_address_for_testing(&ip, offset + 3),
199                local_ip_utils::new_deterministic_tcp_address_for_testing(&ip, offset + 4),
200            )
201        } else {
202            (
203                local_ip_utils::new_tcp_address_for_testing(&ip),
204                local_ip_utils::new_udp_address_for_testing(&ip),
205                local_ip_utils::new_tcp_address_for_testing(
206                    metrics_ip.as_deref().unwrap_or(&localhost),
207                ),
208                local_ip_utils::new_udp_address_for_testing(&ip),
209                local_ip_utils::new_tcp_address_for_testing(&localhost),
210            )
211        };
212
213        let p2p_listen_address = self
214            .p2p_listen_ip_address
215            .map(|ip| SocketAddr::new(ip, p2p_address.port().unwrap()));
216
217        ValidatorGenesisConfig {
218            authority_key_pair,
219            protocol_key_pair,
220            account_key_pair: account_private_key.into(),
221            network_key_pair,
222            network_address,
223            p2p_address,
224            p2p_listen_address,
225            metrics_address: metrics_address.to_socket_addr().unwrap(),
226            admin_interface_address: admin_interface_address.to_socket_addr().unwrap(),
227            gas_price,
228            commission_rate: DEFAULT_COMMISSION_RATE,
229            primary_address,
230            // A test-wide protocol config override can replace even the MAX lookup
231            // with a pre-version-32 config where the threshold is absent, so fall
232            // back to the historical value.
233            stake: ProtocolConfig::get_for_version(ProtocolVersion::MAX, Chain::Unknown)
234                .validator_low_stake_threshold_as_option()
235                .unwrap_or(PRE_V32_VALIDATOR_LOW_STAKE_THRESHOLD),
236            name: None,
237        }
238    }
239}
240
241#[derive(Serialize, Deserialize, Default)]
242pub struct GenesisConfig {
243    pub ssfn_config_info: Option<Vec<SsfnGenesisConfig>>,
244    pub validator_config_info: Option<Vec<ValidatorGenesisConfig>>,
245    pub parameters: GenesisCeremonyParameters,
246    pub accounts: Vec<AccountConfig>,
247}
248
249impl Config for GenesisConfig {}
250
251impl GenesisConfig {
252    /// The protocol config for the version this genesis will be built at.
253    pub fn protocol_config(&self) -> ProtocolConfig {
254        ProtocolConfig::get_for_version(self.parameters.protocol_version, Chain::Unknown)
255    }
256
257    pub fn generate_accounts<R: rand::RngCore + rand::CryptoRng>(
258        &self,
259        mut rng: R,
260    ) -> Result<(Vec<AccountPrivateKey>, Vec<TokenAllocation>)> {
261        let mut addresses = Vec::new();
262        let mut allocations = Vec::new();
263
264        info!("Creating accounts and token allocations...");
265
266        let mut keys = Vec::new();
267        for account in &self.accounts {
268            let address = if let Some(address) = account.address {
269                address
270            } else {
271                let (address, key) = get_key_pair_from_rng(&mut rng);
272                keys.push(key);
273                address
274            };
275
276            addresses.push(address);
277
278            // Populate gas itemized objects
279            account.gas_amounts.iter().for_each(|a| {
280                allocations.push(TokenAllocation {
281                    recipient_address: address,
282                    amount_nanos: *a,
283                    staked_with_validator: None,
284                    staked_with_timelock_expiration: None,
285                });
286            });
287        }
288
289        Ok((keys, allocations))
290    }
291}
292
293fn default_socket_address() -> SocketAddr {
294    local_ip_utils::new_local_tcp_socket_for_testing()
295}
296
297fn default_stake() -> u64 {
298    // A test-wide protocol config override can replace even the MAX lookup with a
299    // pre-version-32 config where the threshold is absent, so fall back to the
300    // historical value.
301    ProtocolConfig::get_for_version(ProtocolVersion::MAX, Chain::Unknown)
302        .validator_low_stake_threshold_as_option()
303        .unwrap_or(PRE_V32_VALIDATOR_LOW_STAKE_THRESHOLD)
304}
305
306fn default_bls12381_key_pair() -> AuthorityKeyPair {
307    get_key_pair_from_rng(&mut rand::rngs::OsRng).1
308}
309
310fn default_ed25519_key_pair() -> NetworkKeyPair {
311    get_key_pair_from_rng(&mut rand::rngs::OsRng).1
312}
313
314fn default_iota_key_pair() -> SimpleKeypair {
315    SimpleKeypair::from(AccountPrivateKey::random())
316}
317
318// Serde adapter storing the keypair as base64 `flag || privkey`, the on-disk
319// format of this config field.
320mod base64_formatted_keypair {
321    use fastcrypto::encoding::{Base64, Encoding};
322    use iota_sdk_crypto::simple::SimpleKeypair;
323    use serde::{Deserialize, Deserializer, Serializer};
324
325    pub fn serialize<S: Serializer>(kp: &SimpleKeypair, serializer: S) -> Result<S::Ok, S::Error> {
326        serializer.serialize_str(&Base64::encode(kp.to_bytes()))
327    }
328
329    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<SimpleKeypair, D::Error> {
330        use serde::de::Error;
331
332        let s = String::deserialize(d)?;
333        let bytes = Base64::decode(&s).map_err(Error::custom)?;
334        SimpleKeypair::from_bytes(&bytes).map_err(Error::custom)
335    }
336}
337
338#[derive(Serialize, Deserialize, Debug, Clone)]
339pub struct AccountConfig {
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub address: Option<Address>,
342    pub gas_amounts: Vec<u64>,
343}
344
345pub const DEFAULT_GAS_AMOUNT: u64 = 30_000_000_000_000_000;
346pub const DEFAULT_NUMBER_OF_AUTHORITIES: usize = 4;
347const DEFAULT_NUMBER_OF_ACCOUNT: usize = 5;
348pub const DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT: usize = 5;
349
350impl GenesisConfig {
351    /// A predictable rng seed used to generate benchmark configs. This seed may
352    /// also be needed by other crates (e.g. the load generators).
353    pub const BENCHMARKS_RNG_SEED: u64 = 0;
354    /// Port offset for benchmarks' genesis configs.
355    pub const BENCHMARKS_PORT_OFFSET: u16 = 2000;
356    /// Trigger epoch change every hour.
357    const BENCHMARK_EPOCH_DURATION_MS: u64 = 60 * 60 * 1000;
358
359    pub fn for_local_testing() -> Self {
360        Self::custom_genesis(
361            DEFAULT_NUMBER_OF_ACCOUNT,
362            DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT,
363        )
364    }
365
366    pub fn for_local_testing_with_addresses(addresses: Vec<Address>) -> Self {
367        Self::custom_genesis_with_addresses(addresses, DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT)
368    }
369
370    pub fn custom_genesis(num_accounts: usize, num_objects_per_account: usize) -> Self {
371        let mut accounts = Vec::new();
372        for _ in 0..num_accounts {
373            accounts.push(AccountConfig {
374                address: None,
375                gas_amounts: vec![DEFAULT_GAS_AMOUNT; num_objects_per_account],
376            })
377        }
378
379        Self {
380            accounts,
381            ..Default::default()
382        }
383    }
384
385    pub fn custom_genesis_with_addresses(
386        addresses: Vec<Address>,
387        num_objects_per_account: usize,
388    ) -> Self {
389        let mut accounts = Vec::new();
390        for address in addresses {
391            accounts.push(AccountConfig {
392                address: Some(address),
393                gas_amounts: vec![DEFAULT_GAS_AMOUNT; num_objects_per_account],
394            })
395        }
396
397        Self {
398            accounts,
399            ..Default::default()
400        }
401    }
402
403    /// Generate a genesis config allowing to easily bootstrap a network for
404    /// benchmarking purposes. This function is ultimately used to print the
405    /// genesis blob and all validators configs. All keys and parameters are
406    /// predictable to facilitate benchmarks orchestration. Only the main ip
407    /// addresses of the validators are specified (as those are often
408    /// dictated by the cloud provider hosing the testbed).
409    ///
410    /// `num_additional_gas_accounts` specifies how many additional gas accounts
411    /// to create. This can be used to support more dedicated client instances.
412    ///
413    /// `total_available_amount` specifies the total amount of tokens available
414    /// for all allocations. The function will divide the available amount
415    /// among all account gas objects.
416    pub fn new_for_benchmarks(
417        ips: &[String],
418        epoch_duration_ms: Option<u64>,
419        chain_start_timestamp_ms: Option<u64>,
420        num_additional_gas_accounts: Option<usize>,
421        total_available_amount: u64,
422    ) -> Self {
423        // this translates to an assert in iota::balance::increase_supply
424        assert!(
425            total_available_amount < u64::MAX,
426            "Total available amount must be less than 18446744073709551615u64"
427        );
428        // Set the validator's configs. They should be the same across multiple runs to
429        // ensure reproducibility.
430        let mut rng = StdRng::seed_from_u64(Self::BENCHMARKS_RNG_SEED);
431        let validator_config_info: Vec<_> = ips
432            .iter()
433            .enumerate()
434            .map(|(i, ip)| {
435                ValidatorGenesisConfigBuilder::new()
436                    .with_ip(ip.to_string())
437                    .with_deterministic_ports(Self::BENCHMARKS_PORT_OFFSET + 10 * i as u16)
438                    .with_p2p_listen_ip_address("0.0.0.0".parse().unwrap())
439                    .build(&mut rng)
440            })
441            .collect();
442
443        // Set the initial gas objects with a predictable owner address.
444        let num_validators = validator_config_info.len();
445        let num_accounts = num_additional_gas_accounts.unwrap_or(0) + num_validators;
446
447        // Divide the total available amount among all account gas objects.
448        let total_gas_objects = num_accounts * DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT;
449        let gas_amount_per_object = if total_gas_objects > 0 {
450            total_available_amount / total_gas_objects as u64
451        } else {
452            0
453        };
454
455        let account_configs = Self::benchmark_gas_keys(num_accounts)
456            .iter()
457            .map(|gas_key| {
458                let gas_address = Address::from(&PublicKey::from(gas_key));
459
460                AccountConfig {
461                    address: Some(gas_address),
462                    gas_amounts: vec![gas_amount_per_object; DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT],
463                }
464            })
465            .collect();
466
467        // Benchmarks require a deterministic genesis. Every validator locally generates
468        // it own genesis; it is thus important they have the same parameters.
469        let parameters = GenesisCeremonyParameters {
470            chain_start_timestamp_ms: chain_start_timestamp_ms.unwrap_or(0),
471            epoch_duration_ms: if let Some(duration_ms) = epoch_duration_ms {
472                duration_ms
473            } else {
474                Self::BENCHMARK_EPOCH_DURATION_MS
475            },
476            ..GenesisCeremonyParameters::new()
477        };
478
479        // Make a new genesis configuration.
480        GenesisConfig {
481            ssfn_config_info: None,
482            validator_config_info: Some(validator_config_info),
483            parameters,
484            accounts: account_configs,
485        }
486    }
487
488    /// Generate a predictable and fixed key that will own all gas objects used
489    /// for benchmarks. This function may be called by other parts of the
490    /// codebase (e.g. load generators) to get the same keypair used for
491    /// genesis (hence the importance of the seedable rng).
492    pub fn benchmark_gas_keys(n: usize) -> Vec<SimpleKeypair> {
493        let mut rng = StdRng::seed_from_u64(Self::BENCHMARKS_RNG_SEED);
494        (0..n)
495            .map(|_| SimpleKeypair::from(AccountPrivateKey::random_with(&mut rng)))
496            .collect()
497    }
498
499    pub fn add_faucet_account(mut self) -> Self {
500        self.accounts.push(AccountConfig {
501            address: None,
502            gas_amounts: vec![DEFAULT_GAS_AMOUNT; DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT],
503        });
504        self
505    }
506}
507
508#[cfg(test)]
509mod tests {
510    use std::net::Ipv4Addr;
511
512    use rand::rngs::OsRng;
513
514    use super::ValidatorGenesisConfigBuilder;
515
516    #[test]
517    fn deterministic_ports_fill_the_five_slots_of_a_validator() {
518        let config = ValidatorGenesisConfigBuilder::new()
519            .with_ip("127.0.0.1".to_owned())
520            .with_deterministic_ports(9200)
521            .with_metrics_ip_address(Ipv4Addr::LOCALHOST.into())
522            .build(&mut OsRng);
523
524        assert_eq!(
525            config.network_address.to_string(),
526            "/ip4/127.0.0.1/tcp/9200/http"
527        );
528        assert_eq!(
529            config.p2p_address.to_string(),
530            "/ip4/127.0.0.1/udp/9201/http"
531        );
532        assert_eq!(config.metrics_address.to_string(), "127.0.0.1:9202");
533        assert_eq!(
534            config.primary_address.to_string(),
535            "/ip4/127.0.0.1/udp/9203/http"
536        );
537        assert_eq!(config.admin_interface_address.to_string(), "127.0.0.1:9204");
538    }
539
540    #[test]
541    fn the_metrics_endpoint_binds_all_interfaces_unless_an_ip_is_given() {
542        let config = ValidatorGenesisConfigBuilder::new()
543            .with_ip("127.0.0.1".to_owned())
544            .with_deterministic_ports(9200)
545            .build(&mut OsRng);
546
547        assert_eq!(config.metrics_address.to_string(), "0.0.0.0:9202");
548    }
549}