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