diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 9c5442d769..369af8887c 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -20,7 +20,7 @@ use clap::{crate_description, crate_name, crate_version, value_t_or_exit, App, A use solana::blocktree::create_new_ledger; use solana_sdk::account::Account; use solana_sdk::fee_calculator::FeeCalculator; -use solana_sdk::genesis_block::GenesisBlock; +use solana_sdk::genesis_block::Builder; use solana_sdk::hash::{hash, Hash}; use solana_sdk::poh_config::PohConfig; use solana_sdk::pubkey::Pubkey; @@ -47,30 +47,26 @@ pub enum AccountFileFormat { pub fn append_primordial_accounts( file: &str, file_format: AccountFileFormat, - genesis_block: &mut GenesisBlock, -) -> io::Result<()> { + mut builder: Builder, +) -> io::Result<(Builder)> { let accounts_file = File::open(file.to_string())?; let primordial_accounts: HashMap = serde_yaml::from_reader(accounts_file) .map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?; - primordial_accounts - .into_iter() - .for_each(|(account, balance)| { - let pubkey = match file_format { - AccountFileFormat::Pubkey => Pubkey::from_str(account.as_str()).unwrap(), - AccountFileFormat::Keypair => { - let bytes: Vec = serde_json::from_str(account.as_str()).unwrap(); - Keypair::from_bytes(&bytes).unwrap().pubkey() - } - }; + for (account, balance) in primordial_accounts { + let pubkey = match file_format { + AccountFileFormat::Pubkey => Pubkey::from_str(account.as_str()).unwrap(), + AccountFileFormat::Keypair => { + let bytes: Vec = serde_json::from_str(account.as_str()).unwrap(); + Keypair::from_bytes(&bytes).unwrap().pubkey() + } + }; - genesis_block - .accounts - .push((pubkey, Account::new(balance, 0, &system_program::id()))) - }); + builder = builder.account(pubkey, Account::new(balance, 0, &system_program::id())); + } - Ok(()) + Ok(builder) } fn main() -> Result<(), Box> { @@ -292,8 +288,8 @@ fn main() -> Result<(), Box> { 1, ); - let mut genesis_block = GenesisBlock::new( - &[ + let mut builder = Builder::new() + .accounts(&[ // the mint ( mint_keypair.pubkey(), @@ -326,8 +322,8 @@ fn main() -> Result<(), Box> { storage_mining_keypair.pubkey(), storage_contract::create_mining_pool_account(storage_pool_lamports), ), - ], - &[ + ]) + .native_instruction_processors(&[ solana_bpf_loader_program!(), solana_vote_program!(), solana_stake_program!(), @@ -336,27 +332,19 @@ fn main() -> Result<(), Box> { solana_config_program!(), solana_exchange_program!(), solana_storage_program!(), - ], - ); + ]) + .ticks_per_slot(value_t_or_exit!(matches, "ticks_per_slot", u64)) + .slots_per_epoch(value_t_or_exit!(matches, "slots_per_epoch", u64)); - if let Some(file) = matches.value_of("primordial_accounts_file") { - append_primordial_accounts(file, AccountFileFormat::Pubkey, &mut genesis_block)?; - } - - if let Some(file) = matches.value_of("primordial_keypairs_file") { - append_primordial_accounts(file, AccountFileFormat::Keypair, &mut genesis_block)?; - } - - genesis_block.fee_calculator.target_lamports_per_signature = + let mut fee_calculator = FeeCalculator::default(); + fee_calculator.target_lamports_per_signature = value_t_or_exit!(matches, "target_lamports_per_signature", u64); - genesis_block.fee_calculator.target_signatures_per_slot = + fee_calculator.target_signatures_per_slot = value_t_or_exit!(matches, "target_signatures_per_slot", usize); - genesis_block.fee_calculator = FeeCalculator::new_derived(&genesis_block.fee_calculator, 0); + builder = builder.fee_calculator(&FeeCalculator::new_derived(&fee_calculator, 0)); - genesis_block.ticks_per_slot = value_t_or_exit!(matches, "ticks_per_slot", u64); - genesis_block.slots_per_epoch = value_t_or_exit!(matches, "slots_per_epoch", u64); - genesis_block.stakers_slot_offset = genesis_block.slots_per_epoch; - genesis_block.poh_config.target_tick_duration = + let mut poh_config = PohConfig::default(); + poh_config.target_tick_duration = Duration::from_millis(value_t_or_exit!(matches, "target_tick_duration", u64)); match matches.value_of("hashes_per_tick").unwrap() { @@ -370,22 +358,29 @@ fn main() -> Result<(), Box> { let end = Instant::now(); let elapsed = end.duration_since(start).as_millis(); - let hashes_per_tick = (genesis_block.poh_config.target_tick_duration.as_millis() - * 1_000_000 - / elapsed) as u64; + let hashes_per_tick = + (poh_config.target_tick_duration.as_millis() * 1_000_000 / elapsed) as u64; println!("Hashes per tick: {}", hashes_per_tick); - genesis_block.poh_config.hashes_per_tick = Some(hashes_per_tick); + poh_config.hashes_per_tick = Some(hashes_per_tick); } "sleep" => { - genesis_block.poh_config.hashes_per_tick = None; + poh_config.hashes_per_tick = None; } _ => { - genesis_block.poh_config.hashes_per_tick = - Some(value_t_or_exit!(matches, "hashes_per_tick", u64)); + poh_config.hashes_per_tick = Some(value_t_or_exit!(matches, "hashes_per_tick", u64)); } } + builder = builder.poh_config(&poh_config); - create_new_ledger(ledger_path, &genesis_block)?; + if let Some(file) = matches.value_of("primordial_accounts_file") { + builder = append_primordial_accounts(file, AccountFileFormat::Pubkey, builder)?; + } + + if let Some(file) = matches.value_of("primordial_keypairs_file") { + builder = append_primordial_accounts(file, AccountFileFormat::Keypair, builder)?; + } + + create_new_ledger(ledger_path, &builder.build())?; Ok(()) } @@ -393,7 +388,7 @@ fn main() -> Result<(), Box> { mod tests { use super::*; use hashbrown::HashSet; - use solana_sdk::genesis_block::GenesisBlock; + use solana_sdk::genesis_block::Builder; use solana_sdk::pubkey::Pubkey; use std::collections::HashMap; use std::fs::remove_file; @@ -420,16 +415,16 @@ mod tests { #[test] fn test_append_primordial_accounts_to_genesis() { - let mut genesis_block = GenesisBlock::new(&[], &[]); - // Test invalid file returns error assert!(append_primordial_accounts( "unknownfile", AccountFileFormat::Pubkey, - &mut genesis_block + Builder::new() ) .is_err()); + let mut builder = Builder::new(); + let mut primordial_accounts = HashMap::new(); primordial_accounts.insert(Pubkey::new_rand().to_string(), 2 as u64); primordial_accounts.insert(Pubkey::new_rand().to_string(), 1 as u64); @@ -440,26 +435,29 @@ mod tests { let mut file = File::create(path).unwrap(); file.write_all(&serialized.into_bytes()).unwrap(); - // Test valid file returns ok - assert!(append_primordial_accounts( + builder = append_primordial_accounts( "test_append_primordial_accounts_to_genesis.yml", AccountFileFormat::Pubkey, - &mut genesis_block + builder, ) - .is_ok()); + .expect("test_append_primordial_accounts_to_genesis.yml"); + // Test valid file returns ok remove_file(path).unwrap(); - // Test all accounts were added - assert_eq!(genesis_block.accounts.len(), primordial_accounts.len()); + { + let genesis_block = builder.clone().build(); + // Test all accounts were added + assert_eq!(genesis_block.accounts.len(), primordial_accounts.len()); - // Test account data matches - (0..primordial_accounts.len()).for_each(|i| { - assert_eq!( - primordial_accounts[&genesis_block.accounts[i].0.to_string()], - genesis_block.accounts[i].1.lamports, - ); - }); + // Test account data matches + (0..primordial_accounts.len()).for_each(|i| { + assert_eq!( + primordial_accounts[&genesis_block.accounts[i].0.to_string()], + genesis_block.accounts[i].1.lamports, + ); + }); + } // Test more accounts can be appended let mut primordial_accounts1 = HashMap::new(); @@ -472,15 +470,16 @@ mod tests { let mut file = File::create(path).unwrap(); file.write_all(&serialized.into_bytes()).unwrap(); - assert!(append_primordial_accounts( + builder = append_primordial_accounts( "test_append_primordial_accounts_to_genesis.yml", AccountFileFormat::Pubkey, - &mut genesis_block + builder, ) - .is_ok()); + .expect("test_append_primordial_accounts_to_genesis.yml"); remove_file(path).unwrap(); + let genesis_block = builder.clone().build(); // Test total number of accounts is correct assert_eq!( genesis_block.accounts.len(), @@ -528,15 +527,16 @@ mod tests { let mut file = File::create(path).unwrap(); file.write_all(&serialized.into_bytes()).unwrap(); - assert!(append_primordial_accounts( + builder = append_primordial_accounts( "test_append_primordial_accounts_to_genesis.yml", AccountFileFormat::Keypair, - &mut genesis_block + builder, ) - .is_ok()); + .expect("builder"); remove_file(path).unwrap(); + let genesis_block = builder.clone().build(); // Test total number of accounts is correct assert_eq!( genesis_block.accounts.len(), diff --git a/sdk/src/genesis_block.rs b/sdk/src/genesis_block.rs index b3efb5ba50..115c719ec8 100644 --- a/sdk/src/genesis_block.rs +++ b/sdk/src/genesis_block.rs @@ -15,16 +15,17 @@ use std::fs::{File, OpenOptions}; use std::io::Write; use std::path::Path; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct GenesisBlock { pub accounts: Vec<(Pubkey, Account)>, - pub fee_calculator: FeeCalculator, pub native_instruction_processors: Vec<(String, Pubkey)>, - pub ticks_per_slot: u64, + pub rewards_pools: Vec<(Pubkey, Account)>, pub slots_per_epoch: u64, pub stakers_slot_offset: u64, pub epoch_warmup: bool, + pub ticks_per_slot: u64, pub poh_config: PohConfig, + pub fee_calculator: FeeCalculator, pub inflation: Inflation, } @@ -47,18 +48,102 @@ impl Default for GenesisBlock { fn default() -> Self { Self { accounts: Vec::new(), - epoch_warmup: true, - fee_calculator: FeeCalculator::default(), native_instruction_processors: Vec::new(), + rewards_pools: Vec::new(), + epoch_warmup: true, slots_per_epoch: DEFAULT_SLOTS_PER_EPOCH, stakers_slot_offset: DEFAULT_SLOTS_PER_EPOCH, ticks_per_slot: DEFAULT_TICKS_PER_SLOT, poh_config: PohConfig::default(), inflation: Inflation::default(), + fee_calculator: FeeCalculator::default(), } } } +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct Builder { + genesis_block: GenesisBlock, + already_have_stakers_slot_offset: bool, +} + +impl Builder { + pub fn new() -> Self { + Builder::default() + } + // consuming builder because I don't want to clone all the accounts + pub fn build(self) -> GenesisBlock { + self.genesis_block + } + + fn append(items: &[T], mut dest: Vec) -> Vec { + items.iter().cloned().for_each(|item| dest.push(item)); + dest + } + + pub fn account(self, pubkey: Pubkey, account: Account) -> Self { + self.accounts(&[(pubkey, account)]) + } + pub fn accounts(mut self, accounts: &[(Pubkey, Account)]) -> Self { + self.genesis_block.accounts = Self::append(accounts, self.genesis_block.accounts); + self + } + pub fn native_instruction_processor(self, name: &str, pubkey: Pubkey) -> Self { + self.native_instruction_processors(&[(name.to_string(), pubkey)]) + } + pub fn native_instruction_processors( + mut self, + native_instruction_processors: &[(String, Pubkey)], + ) -> Self { + self.genesis_block.native_instruction_processors = Self::append( + native_instruction_processors, + self.genesis_block.native_instruction_processors, + ); + self + } + pub fn rewards_pool(self, pubkey: Pubkey, account: Account) -> Self { + self.rewards_pools(&[(pubkey, account)]) + } + pub fn rewards_pools(mut self, rewards_pools: &[(Pubkey, Account)]) -> Self { + self.genesis_block.rewards_pools = + Self::append(rewards_pools, self.genesis_block.rewards_pools); + self + } + // also sets stakers_slot_offset, unless already set explicitly + pub fn slots_per_epoch(mut self, slots_per_epoch: u64) -> Self { + self.genesis_block.slots_per_epoch = slots_per_epoch; + if !self.already_have_stakers_slot_offset { + self.genesis_block.stakers_slot_offset = slots_per_epoch; + } + self + } + pub fn stakers_slot_offset(mut self, stakers_slot_offset: u64) -> Self { + self.genesis_block.stakers_slot_offset = stakers_slot_offset; + self.already_have_stakers_slot_offset = true; + self + } + pub fn epoch_warmup(mut self, epoch_warmup: bool) -> Self { + self.genesis_block.epoch_warmup = epoch_warmup; + self + } + pub fn ticks_per_slot(mut self, ticks_per_slot: u64) -> Self { + self.genesis_block.ticks_per_slot = ticks_per_slot; + self + } + pub fn poh_config(mut self, poh_config: &PohConfig) -> Self { + self.genesis_block.poh_config = poh_config.clone(); + self + } + pub fn fee_calculator(mut self, fee_calculator: &FeeCalculator) -> Self { + self.genesis_block.fee_calculator = fee_calculator.clone(); + self + } + pub fn inflation(mut self, inflation: &Inflation) -> Self { + self.genesis_block.inflation = inflation.clone(); + self + } +} + impl GenesisBlock { pub fn new( accounts: &[(Pubkey, Account)], @@ -123,16 +208,15 @@ mod tests { #[test] fn test_genesis_block() { let mint_keypair = Keypair::new(); - let block = GenesisBlock::new( - &[ - ( - mint_keypair.pubkey(), - Account::new(10_000, 0, &Pubkey::default()), - ), - (Pubkey::new_rand(), Account::new(1, 0, &Pubkey::default())), - ], - &[("hi".to_string(), Pubkey::new_rand())], - ); + let block = Builder::new() + .account( + mint_keypair.pubkey(), + Account::new(10_000, 0, &Pubkey::default()), + ) + .accounts(&[(Pubkey::new_rand(), Account::new(1, 0, &Pubkey::default()))]) + .native_instruction_processor("hi", Pubkey::new_rand()) + .build(); + assert_eq!(block.accounts.len(), 2); assert!(block.accounts.iter().any( |(pubkey, account)| *pubkey == mint_keypair.pubkey() && account.lamports == 10_000