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