clone_solana_genesis_config/
lib.rs1#![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
51pub 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 pub creation_time: UnixTimestamp,
67 pub accounts: BTreeMap<Pubkey, Account>,
69 pub native_instruction_processors: Vec<(String, Pubkey)>,
71 pub rewards_pools: BTreeMap<Pubkey, Account>,
73 pub ticks_per_slot: u64,
74 pub unused: u64,
75 pub poh_config: PohConfig,
77 pub __backwards_compat_with_v0_23: u64,
80 pub fee_rate_governor: FeeRateGovernor,
82 pub rent: Rent,
84 pub inflation: Inflation,
86 pub epoch_schedule: EpochSchedule,
88 pub cluster_type: ClusterType,
90}
91
92pub 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 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 let _ignored = std::fs::remove_dir_all(&path);
310 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}