From 191483f4ee3dabdaf3ec73a3f0c207f15f80e38b Mon Sep 17 00:00:00 2001 From: Pankaj Garg Date: Thu, 6 Jun 2019 19:24:09 -0700 Subject: [PATCH] Facility to add accounts with specific balance to genesis block (#4585) * Facility to add accounts with specific balance to genesis block * address review comments --- Cargo.lock | 4 ++ genesis/Cargo.toml | 4 ++ genesis/src/main.rs | 151 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 87514299b..ae32cd463 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2458,9 +2458,13 @@ dependencies = [ name = "solana-genesis" version = "0.16.0" dependencies = [ + "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "hashbrown 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_yaml 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", "solana 0.16.0", "solana-budget-api 0.16.0", "solana-budget-program 0.16.0", diff --git a/genesis/Cargo.toml b/genesis/Cargo.toml index 3f7520df2..16de3eeec 100644 --- a/genesis/Cargo.toml +++ b/genesis/Cargo.toml @@ -9,8 +9,12 @@ license = "Apache-2.0" homepage = "https://solana.com/" [dependencies] +bincode = "1.1.4" clap = "2.33.0" +serde = "1.0.92" +serde_derive = "1.0.92" serde_json = "1.0.39" +serde_yaml = "0.8.9" solana = { path = "../core", version = "0.16.0" } solana-budget-api = { path = "../programs/budget_api", version = "0.16.0" } solana-budget-program = { path = "../programs/budget_program", version = "0.16.0" } diff --git a/genesis/src/main.rs b/genesis/src/main.rs index 720a73e78..660af411d 100644 --- a/genesis/src/main.rs +++ b/genesis/src/main.rs @@ -13,12 +13,14 @@ extern crate solana_config_program; extern crate solana_exchange_program; use clap::{crate_description, crate_name, crate_version, value_t_or_exit, App, Arg}; +use serde_derive::{Deserialize, Serialize}; 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::hash::{hash, Hash}; use solana_sdk::poh_config::PohConfig; +use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::{read_keypair, KeypairUtil}; use solana_sdk::system_program; use solana_sdk::timing; @@ -26,10 +28,34 @@ use solana_stake_api::stake_state; use solana_storage_program::genesis_block_util::GenesisBlockUtil; use solana_vote_api::vote_state; use std::error; +use std::fs::File; +use std::io; use std::time::{Duration, Instant}; pub const BOOTSTRAP_LEADER_LAMPORTS: u64 = 42; +#[derive(Serialize, Deserialize, Default, Debug, PartialEq)] +pub struct PrimordialAccount { + pub pubkey: Pubkey, + pub lamports: u64, +} + +pub fn append_primordial_accounts(file: &str, genesis_block: &mut GenesisBlock) -> io::Result<()> { + let accounts_file = File::open(file.to_string())?; + + let primordial_accounts: Vec = serde_yaml::from_reader(accounts_file) + .map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?; + + primordial_accounts.iter().for_each(|primordial| { + genesis_block.accounts.push(( + primordial.pubkey, + Account::new(primordial.lamports, 0, &system_program::id()), + )) + }); + + Ok(()) +} + fn main() -> Result<(), Box> { let default_bootstrap_leader_lamports = &BOOTSTRAP_LEADER_LAMPORTS.to_string(); let default_lamports_per_signature = @@ -167,6 +193,13 @@ fn main() -> Result<(), Box> { .default_value(default_slots_per_epoch) .help("The number of slots in an epoch"), ) + .arg( + Arg::with_name("primordial_accounts_file") + .long("primordial-accounts-file") + .value_name("FILENAME") + .takes_value(true) + .help("The location of pubkey for primordial accounts and balance"), + ) .get_matches(); let bootstrap_leader_keypair_file = matches.value_of("bootstrap_leader_keypair_file").unwrap(); @@ -228,6 +261,11 @@ fn main() -> Result<(), Box> { solana_exchange_program!(), ], ); + + if let Some(file) = matches.value_of("primordial_accounts_file") { + append_primordial_accounts(file, &mut genesis_block)?; + } + genesis_block.add_storage_program( &bootstrap_leader_keypair.pubkey(), &bootstrap_storage_keypair.pubkey(), @@ -272,7 +310,13 @@ fn main() -> Result<(), Box> { #[cfg(test)] mod tests { + use super::*; use hashbrown::HashSet; + use solana_sdk::genesis_block::GenesisBlock; + use solana_sdk::pubkey::Pubkey; + use std::fs::remove_file; + use std::io::Write; + use std::path::Path; #[test] fn test_program_ids() { @@ -342,4 +386,111 @@ mod tests { ]; assert!(ids.into_iter().all(move |id| unique.insert(id))); } + + #[test] + fn test_append_primordial_accounts_to_genesis() { + let mut genesis_block = GenesisBlock::new(&Pubkey::new_rand(), &[], &[]); + + // Test invalid file returns error + assert!(append_primordial_accounts("unknownfile", &mut genesis_block).is_err()); + + let primordial_accounts = [ + PrimordialAccount { + pubkey: Pubkey::new_rand(), + lamports: 2, + }, + PrimordialAccount { + pubkey: Pubkey::new_rand(), + lamports: 1, + }, + PrimordialAccount { + pubkey: Pubkey::new_rand(), + lamports: 3, + }, + ]; + + let serialized = serde_yaml::to_string(&primordial_accounts).unwrap(); + let path = Path::new("test_append_primordial_accounts_to_genesis.yml"); + let mut file = File::create(path).unwrap(); + file.write_all(&serialized.into_bytes()).unwrap(); + + // Test valid file returns ok + assert!(append_primordial_accounts( + "test_append_primordial_accounts_to_genesis.yml", + &mut genesis_block + ) + .is_ok()); + + remove_file(path).unwrap(); + + // 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!(genesis_block.accounts[i].0, primordial_accounts[i].pubkey); + assert_eq!( + genesis_block.accounts[i].1.lamports, + primordial_accounts[i].lamports + ); + }); + + // Test more accounts can be appended + let primordial_accounts1 = [ + PrimordialAccount { + pubkey: Pubkey::new_rand(), + lamports: 6, + }, + PrimordialAccount { + pubkey: Pubkey::new_rand(), + lamports: 5, + }, + PrimordialAccount { + pubkey: Pubkey::new_rand(), + lamports: 10, + }, + ]; + + let serialized = serde_yaml::to_string(&primordial_accounts1).unwrap(); + let path = Path::new("test_append_primordial_accounts_to_genesis.yml"); + let mut file = File::create(path).unwrap(); + file.write_all(&serialized.into_bytes()).unwrap(); + + assert!(append_primordial_accounts( + "test_append_primordial_accounts_to_genesis.yml", + &mut genesis_block + ) + .is_ok()); + + remove_file(path).unwrap(); + + // Test total number of accounts is correct + assert_eq!( + genesis_block.accounts.len(), + primordial_accounts.len() + primordial_accounts1.len() + ); + + // Test old accounts are still there + (0..primordial_accounts.len()).for_each(|i| { + assert_eq!(genesis_block.accounts[i].0, primordial_accounts[i].pubkey); + assert_eq!( + genesis_block.accounts[i].1.lamports, + primordial_accounts[i].lamports + ); + }); + + // Test new account data matches + (0..primordial_accounts1.len()).for_each(|i| { + assert_eq!( + genesis_block.accounts[primordial_accounts.len() + i].0, + primordial_accounts1[i].pubkey + ); + assert_eq!( + genesis_block.accounts[primordial_accounts.len() + i] + .1 + .lamports, + primordial_accounts1[i].lamports + ); + }); + } }