sui_swarm_config/
genesis_config.rs

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