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