clone_solana_genesis_config/
lib.rs

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