Skip to main content

cbe_sdk/
genesis_config.rs

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