#![cfg(feature = "full")]
use {
crate::{
clock::{UnixTimestamp, DEFAULT_TICKS_PER_SLOT},
epoch_schedule::EpochSchedule,
fee_calculator::FeeRateGovernor,
hash::{hash, Hash},
inflation::Inflation,
poh_config::PohConfig,
pubkey::Pubkey,
rent::Rent,
shred_version::compute_shred_version,
signature::{Keypair, Signer},
system_program,
timing::years_as_slots,
},
bincode::{deserialize, serialize},
chrono::{TimeZone, Utc},
memmap2::Mmap,
solana_account::{Account, AccountSharedData},
solana_native_token::lamports_to_sol,
std::{
collections::BTreeMap,
fmt,
fs::{File, OpenOptions},
io::Write,
path::{Path, PathBuf},
str::FromStr,
time::{SystemTime, UNIX_EPOCH},
},
};
pub const DEFAULT_GENESIS_FILE: &str = "genesis.bin";
pub const DEFAULT_GENESIS_ARCHIVE: &str = "genesis.tar.bz2";
pub const DEFAULT_GENESIS_DOWNLOAD_PATH: &str = "/genesis.tar.bz2";
pub const UNUSED_DEFAULT: u64 = 1024;
#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClusterType {
Testnet,
MainnetBeta,
Devnet,
Development,
}
impl ClusterType {
pub const STRINGS: [&'static str; 4] = ["development", "devnet", "testnet", "mainnet-beta"];
pub fn get_genesis_hash(&self) -> Option<Hash> {
match self {
Self::MainnetBeta => {
Some(Hash::from_str("5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d").unwrap())
}
Self::Testnet => {
Some(Hash::from_str("4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY").unwrap())
}
Self::Devnet => {
Some(Hash::from_str("EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG").unwrap())
}
Self::Development => None,
}
}
}
impl FromStr for ClusterType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"development" => Ok(ClusterType::Development),
"devnet" => Ok(ClusterType::Devnet),
"testnet" => Ok(ClusterType::Testnet),
"mainnet-beta" => Ok(ClusterType::MainnetBeta),
_ => Err(format!("{s} is unrecognized for cluster type")),
}
}
}
#[cfg_attr(
feature = "frozen-abi",
derive(AbiExample),
frozen_abi(digest = "41wPwgEZLhp9AS4tjTQebW7cvHnkbSSTN19vaKwVett6")
)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct GenesisConfig {
pub creation_time: UnixTimestamp,
pub accounts: BTreeMap<Pubkey, Account>,
pub native_instruction_processors: Vec<(String, Pubkey)>,
pub rewards_pools: BTreeMap<Pubkey, Account>,
pub ticks_per_slot: u64,
pub unused: u64,
pub poh_config: PohConfig,
pub __backwards_compat_with_v0_23: u64,
pub fee_rate_governor: FeeRateGovernor,
pub rent: Rent,
pub inflation: Inflation,
pub epoch_schedule: EpochSchedule,
pub cluster_type: ClusterType,
}
pub fn create_genesis_config(lamports: u64) -> (GenesisConfig, Keypair) {
let faucet_keypair = Keypair::new();
(
GenesisConfig::new(
&[(
faucet_keypair.pubkey(),
AccountSharedData::new(lamports, 0, &system_program::id()),
)],
&[],
),
faucet_keypair,
)
}
impl Default for GenesisConfig {
fn default() -> Self {
Self {
creation_time: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as UnixTimestamp,
accounts: BTreeMap::default(),
native_instruction_processors: Vec::default(),
rewards_pools: BTreeMap::default(),
ticks_per_slot: DEFAULT_TICKS_PER_SLOT,
unused: UNUSED_DEFAULT,
poh_config: PohConfig::default(),
inflation: Inflation::default(),
__backwards_compat_with_v0_23: 0,
fee_rate_governor: FeeRateGovernor::default(),
rent: Rent::default(),
epoch_schedule: EpochSchedule::default(),
cluster_type: ClusterType::Development,
}
}
}
impl GenesisConfig {
pub fn new(
accounts: &[(Pubkey, AccountSharedData)],
native_instruction_processors: &[(String, Pubkey)],
) -> Self {
Self {
accounts: accounts
.iter()
.cloned()
.map(|(key, account)| (key, Account::from(account)))
.collect::<BTreeMap<Pubkey, Account>>(),
native_instruction_processors: native_instruction_processors.to_vec(),
..GenesisConfig::default()
}
}
pub fn hash(&self) -> Hash {
let serialized = serialize(&self).unwrap();
hash(&serialized)
}
fn genesis_filename(ledger_path: &Path) -> PathBuf {
Path::new(ledger_path).join(DEFAULT_GENESIS_FILE)
}
pub fn load(ledger_path: &Path) -> Result<Self, std::io::Error> {
let filename = Self::genesis_filename(ledger_path);
let file = OpenOptions::new()
.read(true)
.open(&filename)
.map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("Unable to open {filename:?}: {err:?}"),
)
})?;
let mem = unsafe { Mmap::map(&file) }.map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("Unable to map {filename:?}: {err:?}"),
)
})?;
let genesis_config = deserialize(&mem).map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("Unable to deserialize {filename:?}: {err:?}"),
)
})?;
Ok(genesis_config)
}
pub fn write(&self, ledger_path: &Path) -> Result<(), std::io::Error> {
let serialized = serialize(&self).map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("Unable to serialize: {err:?}"),
)
})?;
std::fs::create_dir_all(ledger_path)?;
let mut file = File::create(Self::genesis_filename(ledger_path))?;
file.write_all(&serialized)
}
pub fn add_account(&mut self, pubkey: Pubkey, account: AccountSharedData) {
self.accounts.insert(pubkey, Account::from(account));
}
pub fn add_native_instruction_processor(&mut self, name: String, program_id: Pubkey) {
self.native_instruction_processors.push((name, program_id));
}
pub fn hashes_per_tick(&self) -> Option<u64> {
self.poh_config.hashes_per_tick
}
pub fn ticks_per_slot(&self) -> u64 {
self.ticks_per_slot
}
pub fn ns_per_slot(&self) -> u128 {
self.poh_config
.target_tick_duration
.as_nanos()
.saturating_mul(self.ticks_per_slot() as u128)
}
pub fn slots_per_year(&self) -> f64 {
years_as_slots(
1.0,
&self.poh_config.target_tick_duration,
self.ticks_per_slot(),
)
}
}
impl fmt::Display for GenesisConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"\
Creation time: {}\n\
Cluster type: {:?}\n\
Genesis hash: {}\n\
Shred version: {}\n\
Ticks per slot: {:?}\n\
Hashes per tick: {:?}\n\
Target tick duration: {:?}\n\
Slots per epoch: {}\n\
Warmup epochs: {}abled\n\
Slots per year: {}\n\
{:?}\n\
{:?}\n\
{:?}\n\
Capitalization: {} SOL in {} accounts\n\
Native instruction processors: {:#?}\n\
Rewards pool: {:#?}\n\
",
Utc.timestamp_opt(self.creation_time, 0)
.unwrap()
.to_rfc3339(),
self.cluster_type,
self.hash(),
compute_shred_version(&self.hash(), None),
self.ticks_per_slot,
self.poh_config.hashes_per_tick,
self.poh_config.target_tick_duration,
self.epoch_schedule.slots_per_epoch,
if self.epoch_schedule.warmup {
"en"
} else {
"dis"
},
self.slots_per_year(),
self.inflation,
self.rent,
self.fee_rate_governor,
lamports_to_sol(
self.accounts
.iter()
.map(|(pubkey, account)| {
assert!(account.lamports > 0, "{:?}", (pubkey, account));
account.lamports
})
.sum::<u64>()
),
self.accounts.len(),
self.native_instruction_processors,
self.rewards_pools,
)
}
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::signature::{Keypair, Signer},
std::path::PathBuf,
};
fn make_tmp_path(name: &str) -> PathBuf {
let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
let keypair = Keypair::new();
let path = [
out_dir,
"tmp".to_string(),
format!("{}-{}", name, keypair.pubkey()),
]
.iter()
.collect();
let _ignored = std::fs::remove_dir_all(&path);
let _ignored = std::fs::remove_file(&path);
path
}
#[test]
fn test_genesis_config() {
let faucet_keypair = Keypair::new();
let mut config = GenesisConfig::default();
config.add_account(
faucet_keypair.pubkey(),
AccountSharedData::new(10_000, 0, &Pubkey::default()),
);
config.add_account(
solana_sdk::pubkey::new_rand(),
AccountSharedData::new(1, 0, &Pubkey::default()),
);
config.add_native_instruction_processor("hi".to_string(), solana_sdk::pubkey::new_rand());
assert_eq!(config.accounts.len(), 2);
assert!(config
.accounts
.iter()
.any(|(pubkey, account)| *pubkey == faucet_keypair.pubkey()
&& account.lamports == 10_000));
let path = &make_tmp_path("genesis_config");
config.write(path).expect("write");
let loaded_config = GenesisConfig::load(path).expect("load");
assert_eq!(config.hash(), loaded_config.hash());
let _ignored = std::fs::remove_file(path);
}
}