1#![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
40pub const UNUSED_DEFAULT: u64 = 1024;
42
43#[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 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 pub creation_time: UnixTimestamp,
96 pub accounts: BTreeMap<Pubkey, Account>,
98 pub native_instruction_processors: Vec<(String, Pubkey)>,
100 pub rewards_pools: BTreeMap<Pubkey, Account>,
102 pub ticks_per_slot: u64,
103 pub unused: u64,
104 pub poh_config: PohConfig,
106 pub __backwards_compat_with_v0_23: u64,
109 pub fee_rate_governor: FeeRateGovernor,
111 pub rent: Rent,
113 pub inflation: Inflation,
115 pub epoch_schedule: EpochSchedule,
117 pub cluster_type: ClusterType,
119}
120
121pub 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 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 let _ignored = std::fs::remove_dir_all(&path);
338 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}