solana_sdk/
genesis_config.rs

1//! The chain's genesis config.
2
3#![cfg(feature = "full")]
4
5use {
6    crate::{
7        clock::{UnixTimestamp, DEFAULT_TICKS_PER_SLOT},
8        epoch_schedule::EpochSchedule,
9        fee_calculator::FeeRateGovernor,
10        hash::{hash, Hash},
11        inflation::Inflation,
12        poh_config::PohConfig,
13        pubkey::Pubkey,
14        rent::Rent,
15        shred_version::compute_shred_version,
16        signature::{Keypair, Signer},
17        system_program,
18        timing::years_as_slots,
19    },
20    bincode::{deserialize, serialize},
21    chrono::{TimeZone, Utc},
22    memmap2::Mmap,
23    solana_account::{Account, AccountSharedData},
24    solana_native_token::lamports_to_sol,
25    std::{
26        collections::BTreeMap,
27        fmt,
28        fs::{File, OpenOptions},
29        io::Write,
30        path::{Path, PathBuf},
31        str::FromStr,
32        time::{SystemTime, UNIX_EPOCH},
33    },
34};
35
36pub const DEFAULT_GENESIS_FILE: &str = "genesis.bin";
37pub const DEFAULT_GENESIS_ARCHIVE: &str = "genesis.tar.bz2";
38pub const DEFAULT_GENESIS_DOWNLOAD_PATH: &str = "/genesis.tar.bz2";
39
40// deprecated default that is no longer used
41pub const UNUSED_DEFAULT: u64 = 1024;
42
43// The order can't align with release lifecycle only to remain ABI-compatible...
44#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))]
45#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
46pub enum ClusterType {
47    Testnet,
48    MainnetBeta,
49    Devnet,
50    Development,
51}
52
53impl ClusterType {
54    pub const STRINGS: [&'static str; 4] = ["development", "devnet", "testnet", "mainnet-beta"];
55
56    /// Get the known genesis hash for this ClusterType
57    pub fn get_genesis_hash(&self) -> Option<Hash> {
58        match self {
59            Self::MainnetBeta => {
60                Some(Hash::from_str("5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d").unwrap())
61            }
62            Self::Testnet => {
63                Some(Hash::from_str("4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY").unwrap())
64            }
65            Self::Devnet => {
66                Some(Hash::from_str("EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG").unwrap())
67            }
68            Self::Development => None,
69        }
70    }
71}
72
73impl FromStr for ClusterType {
74    type Err = String;
75
76    fn from_str(s: &str) -> Result<Self, Self::Err> {
77        match s {
78            "development" => Ok(ClusterType::Development),
79            "devnet" => Ok(ClusterType::Devnet),
80            "testnet" => Ok(ClusterType::Testnet),
81            "mainnet-beta" => Ok(ClusterType::MainnetBeta),
82            _ => Err(format!("{s} is unrecognized for cluster type")),
83        }
84    }
85}
86
87#[cfg_attr(
88    feature = "frozen-abi",
89    derive(AbiExample),
90    frozen_abi(digest = "41wPwgEZLhp9AS4tjTQebW7cvHnkbSSTN19vaKwVett6")
91)]
92#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
93pub struct GenesisConfig {
94    /// when the network (bootstrap validator) was started relative to the UNIX Epoch
95    pub creation_time: UnixTimestamp,
96    /// initial accounts
97    pub accounts: BTreeMap<Pubkey, Account>,
98    /// built-in programs
99    pub native_instruction_processors: Vec<(String, Pubkey)>,
100    /// accounts for network rewards, these do not count towards capitalization
101    pub rewards_pools: BTreeMap<Pubkey, Account>,
102    pub ticks_per_slot: u64,
103    pub unused: u64,
104    /// network speed configuration
105    pub poh_config: PohConfig,
106    /// this field exists only to ensure that the binary layout of GenesisConfig remains compatible
107    /// with the Solana v0.23 release line
108    pub __backwards_compat_with_v0_23: u64,
109    /// transaction fee config
110    pub fee_rate_governor: FeeRateGovernor,
111    /// rent config
112    pub rent: Rent,
113    /// inflation config
114    pub inflation: Inflation,
115    /// how slots map to epochs
116    pub epoch_schedule: EpochSchedule,
117    /// network runlevel
118    pub cluster_type: ClusterType,
119}
120
121// useful for basic tests
122pub fn create_genesis_config(lamports: u64) -> (GenesisConfig, Keypair) {
123    let faucet_keypair = Keypair::new();
124    (
125        GenesisConfig::new(
126            &[(
127                faucet_keypair.pubkey(),
128                AccountSharedData::new(lamports, 0, &system_program::id()),
129            )],
130            &[],
131        ),
132        faucet_keypair,
133    )
134}
135
136impl Default for GenesisConfig {
137    fn default() -> Self {
138        Self {
139            creation_time: SystemTime::now()
140                .duration_since(UNIX_EPOCH)
141                .unwrap()
142                .as_secs() as UnixTimestamp,
143            accounts: BTreeMap::default(),
144            native_instruction_processors: Vec::default(),
145            rewards_pools: BTreeMap::default(),
146            ticks_per_slot: DEFAULT_TICKS_PER_SLOT,
147            unused: UNUSED_DEFAULT,
148            poh_config: PohConfig::default(),
149            inflation: Inflation::default(),
150            __backwards_compat_with_v0_23: 0,
151            fee_rate_governor: FeeRateGovernor::default(),
152            rent: Rent::default(),
153            epoch_schedule: EpochSchedule::default(),
154            cluster_type: ClusterType::Development,
155        }
156    }
157}
158
159impl GenesisConfig {
160    pub fn new(
161        accounts: &[(Pubkey, AccountSharedData)],
162        native_instruction_processors: &[(String, Pubkey)],
163    ) -> Self {
164        Self {
165            accounts: accounts
166                .iter()
167                .cloned()
168                .map(|(key, account)| (key, Account::from(account)))
169                .collect::<BTreeMap<Pubkey, Account>>(),
170            native_instruction_processors: native_instruction_processors.to_vec(),
171            ..GenesisConfig::default()
172        }
173    }
174
175    pub fn hash(&self) -> Hash {
176        let serialized = serialize(&self).unwrap();
177        hash(&serialized)
178    }
179
180    fn genesis_filename(ledger_path: &Path) -> PathBuf {
181        Path::new(ledger_path).join(DEFAULT_GENESIS_FILE)
182    }
183
184    pub fn load(ledger_path: &Path) -> Result<Self, std::io::Error> {
185        let filename = Self::genesis_filename(ledger_path);
186        let file = OpenOptions::new()
187            .read(true)
188            .open(&filename)
189            .map_err(|err| {
190                std::io::Error::new(
191                    std::io::ErrorKind::Other,
192                    format!("Unable to open {filename:?}: {err:?}"),
193                )
194            })?;
195
196        //UNSAFE: Required to create a Mmap
197        let mem = unsafe { Mmap::map(&file) }.map_err(|err| {
198            std::io::Error::new(
199                std::io::ErrorKind::Other,
200                format!("Unable to map {filename:?}: {err:?}"),
201            )
202        })?;
203
204        let genesis_config = deserialize(&mem).map_err(|err| {
205            std::io::Error::new(
206                std::io::ErrorKind::Other,
207                format!("Unable to deserialize {filename:?}: {err:?}"),
208            )
209        })?;
210        Ok(genesis_config)
211    }
212
213    pub fn write(&self, ledger_path: &Path) -> Result<(), std::io::Error> {
214        let serialized = serialize(&self).map_err(|err| {
215            std::io::Error::new(
216                std::io::ErrorKind::Other,
217                format!("Unable to serialize: {err:?}"),
218            )
219        })?;
220
221        std::fs::create_dir_all(ledger_path)?;
222
223        let mut file = File::create(Self::genesis_filename(ledger_path))?;
224        file.write_all(&serialized)
225    }
226
227    pub fn add_account(&mut self, pubkey: Pubkey, account: AccountSharedData) {
228        self.accounts.insert(pubkey, Account::from(account));
229    }
230
231    pub fn add_native_instruction_processor(&mut self, name: String, program_id: Pubkey) {
232        self.native_instruction_processors.push((name, program_id));
233    }
234
235    pub fn hashes_per_tick(&self) -> Option<u64> {
236        self.poh_config.hashes_per_tick
237    }
238
239    pub fn ticks_per_slot(&self) -> u64 {
240        self.ticks_per_slot
241    }
242
243    pub fn ns_per_slot(&self) -> u128 {
244        self.poh_config
245            .target_tick_duration
246            .as_nanos()
247            .saturating_mul(self.ticks_per_slot() as u128)
248    }
249
250    pub fn slots_per_year(&self) -> f64 {
251        years_as_slots(
252            1.0,
253            &self.poh_config.target_tick_duration,
254            self.ticks_per_slot(),
255        )
256    }
257}
258
259impl fmt::Display for GenesisConfig {
260    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
261        write!(
262            f,
263            "\
264             Creation time: {}\n\
265             Cluster type: {:?}\n\
266             Genesis hash: {}\n\
267             Shred version: {}\n\
268             Ticks per slot: {:?}\n\
269             Hashes per tick: {:?}\n\
270             Target tick duration: {:?}\n\
271             Slots per epoch: {}\n\
272             Warmup epochs: {}abled\n\
273             Slots per year: {}\n\
274             {:?}\n\
275             {:?}\n\
276             {:?}\n\
277             Capitalization: {} SOL in {} accounts\n\
278             Native instruction processors: {:#?}\n\
279             Rewards pool: {:#?}\n\
280             ",
281            Utc.timestamp_opt(self.creation_time, 0)
282                .unwrap()
283                .to_rfc3339(),
284            self.cluster_type,
285            self.hash(),
286            compute_shred_version(&self.hash(), None),
287            self.ticks_per_slot,
288            self.poh_config.hashes_per_tick,
289            self.poh_config.target_tick_duration,
290            self.epoch_schedule.slots_per_epoch,
291            if self.epoch_schedule.warmup {
292                "en"
293            } else {
294                "dis"
295            },
296            self.slots_per_year(),
297            self.inflation,
298            self.rent,
299            self.fee_rate_governor,
300            lamports_to_sol(
301                self.accounts
302                    .iter()
303                    .map(|(pubkey, account)| {
304                        assert!(account.lamports > 0, "{:?}", (pubkey, account));
305                        account.lamports
306                    })
307                    .sum::<u64>()
308            ),
309            self.accounts.len(),
310            self.native_instruction_processors,
311            self.rewards_pools,
312        )
313    }
314}
315
316#[cfg(test)]
317mod tests {
318    use {
319        super::*,
320        crate::signature::{Keypair, Signer},
321        std::path::PathBuf,
322    };
323
324    fn make_tmp_path(name: &str) -> PathBuf {
325        let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
326        let keypair = Keypair::new();
327
328        let path = [
329            out_dir,
330            "tmp".to_string(),
331            format!("{}-{}", name, keypair.pubkey()),
332        ]
333        .iter()
334        .collect();
335
336        // whack any possible collision
337        let _ignored = std::fs::remove_dir_all(&path);
338        // whack any possible collision
339        let _ignored = std::fs::remove_file(&path);
340
341        path
342    }
343
344    #[test]
345    fn test_genesis_config() {
346        let faucet_keypair = Keypair::new();
347        let mut config = GenesisConfig::default();
348        config.add_account(
349            faucet_keypair.pubkey(),
350            AccountSharedData::new(10_000, 0, &Pubkey::default()),
351        );
352        config.add_account(
353            solana_sdk::pubkey::new_rand(),
354            AccountSharedData::new(1, 0, &Pubkey::default()),
355        );
356        config.add_native_instruction_processor("hi".to_string(), solana_sdk::pubkey::new_rand());
357
358        assert_eq!(config.accounts.len(), 2);
359        assert!(config
360            .accounts
361            .iter()
362            .any(|(pubkey, account)| *pubkey == faucet_keypair.pubkey()
363                && account.lamports == 10_000));
364
365        let path = &make_tmp_path("genesis_config");
366        config.write(path).expect("write");
367        let loaded_config = GenesisConfig::load(path).expect("load");
368        assert_eq!(config.hash(), loaded_config.hash());
369        let _ignored = std::fs::remove_file(path);
370    }
371}