use crate::{BeaconChain, BeaconChainTypes}; use eth2_hashing::hash; use lighthouse_bootstrap::Bootstrapper; use merkle_proof::MerkleTree; use rayon::prelude::*; use slog::Logger; use ssz::{Decode, Encode}; use state_processing::initialize_beacon_state_from_eth1; use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; use std::sync::Arc; use std::time::SystemTime; use tree_hash::{SignedRoot, TreeHash}; use types::{ BeaconBlock, BeaconState, ChainSpec, Deposit, DepositData, Domain, EthSpec, Fork, Hash256, Keypair, PublicKey, Signature, }; enum BuildStrategy { FromGenesis { genesis_state: Box>, genesis_block: Box>, }, LoadFromStore, } pub struct BeaconChainBuilder { build_strategy: BuildStrategy, spec: ChainSpec, log: Logger, } impl BeaconChainBuilder { pub fn recent_genesis( keypairs: &[Keypair], minutes: u64, spec: ChainSpec, log: Logger, ) -> Result { Self::quick_start(recent_genesis_time(minutes), keypairs, spec, log) } pub fn quick_start( genesis_time: u64, keypairs: &[Keypair], spec: ChainSpec, log: Logger, ) -> Result { let genesis_state = interop_genesis_state(keypairs, genesis_time, &spec)?; Ok(Self::from_genesis_state(genesis_state, spec, log)) } pub fn yaml_state(file: &PathBuf, spec: ChainSpec, log: Logger) -> Result { let file = File::open(file.clone()) .map_err(|e| format!("Unable to open YAML genesis state file {:?}: {:?}", file, e))?; let genesis_state = serde_yaml::from_reader(file) .map_err(|e| format!("Unable to parse YAML genesis state file: {:?}", e))?; Ok(Self::from_genesis_state(genesis_state, spec, log)) } pub fn ssz_state(file: &PathBuf, spec: ChainSpec, log: Logger) -> Result { let mut file = File::open(file.clone()) .map_err(|e| format!("Unable to open SSZ genesis state file {:?}: {:?}", file, e))?; let mut bytes = vec![]; file.read_to_end(&mut bytes) .map_err(|e| format!("Failed to read SSZ file: {:?}", e))?; let genesis_state = BeaconState::from_ssz_bytes(&bytes) .map_err(|e| format!("Unable to parse SSZ genesis state file: {:?}", e))?; Ok(Self::from_genesis_state(genesis_state, spec, log)) } pub fn json_state(file: &PathBuf, spec: ChainSpec, log: Logger) -> Result { let file = File::open(file.clone()) .map_err(|e| format!("Unable to open JSON genesis state file {:?}: {:?}", file, e))?; let genesis_state = serde_json::from_reader(file) .map_err(|e| format!("Unable to parse JSON genesis state file: {:?}", e))?; Ok(Self::from_genesis_state(genesis_state, spec, log)) } pub fn http_bootstrap(server: &str, spec: ChainSpec, log: Logger) -> Result { let bootstrapper = Bootstrapper::connect(server.to_string(), &log) .map_err(|e| format!("Failed to initialize bootstrap client: {}", e))?; let (genesis_state, genesis_block) = bootstrapper .genesis() .map_err(|e| format!("Failed to bootstrap genesis state: {}", e))?; Ok(Self { build_strategy: BuildStrategy::FromGenesis { genesis_block: Box::new(genesis_block), genesis_state: Box::new(genesis_state), }, spec, log, }) } fn from_genesis_state( genesis_state: BeaconState, spec: ChainSpec, log: Logger, ) -> Self { Self { build_strategy: BuildStrategy::FromGenesis { genesis_block: Box::new(genesis_block(&genesis_state, &spec)), genesis_state: Box::new(genesis_state), }, spec, log, } } pub fn from_store(spec: ChainSpec, log: Logger) -> Self { Self { build_strategy: BuildStrategy::LoadFromStore, spec, log, } } pub fn build( self, store: Arc, eth1_backend: T::Eth1Chain, ) -> Result, String> { Ok(match self.build_strategy { BuildStrategy::LoadFromStore => { BeaconChain::from_store(store, eth1_backend, self.spec, self.log) .map_err(|e| format!("Error loading BeaconChain from database: {:?}", e))? .ok_or_else(|| format!("Unable to find exising BeaconChain in database."))? } BuildStrategy::FromGenesis { genesis_block, genesis_state, } => BeaconChain::from_genesis( store, eth1_backend, genesis_state.as_ref().clone(), genesis_block.as_ref().clone(), self.spec, self.log, ) .map_err(|e| format!("Failed to initialize new beacon chain: {:?}", e))?, }) } } fn genesis_block(genesis_state: &BeaconState, spec: &ChainSpec) -> BeaconBlock { let mut genesis_block = BeaconBlock::empty(&spec); genesis_block.state_root = genesis_state.canonical_root(); genesis_block } /// Builds a genesis state as defined by the Eth2 interop procedure (see below). /// /// Reference: /// https://github.com/ethereum/eth2.0-pm/tree/6e41fcf383ebeb5125938850d8e9b4e9888389b4/interop/mocked_start fn interop_genesis_state( keypairs: &[Keypair], genesis_time: u64, spec: &ChainSpec, ) -> Result, String> { let eth1_block_hash = Hash256::from_slice(&[0x42; 32]); let eth1_timestamp = 2_u64.pow(40); let amount = spec.max_effective_balance; let withdrawal_credentials = |pubkey: &PublicKey| { let mut credentials = hash(&pubkey.as_ssz_bytes()); credentials[0] = spec.bls_withdrawal_prefix_byte; Hash256::from_slice(&credentials) }; let datas = keypairs .into_par_iter() .map(|keypair| { let mut data = DepositData { withdrawal_credentials: withdrawal_credentials(&keypair.pk), pubkey: keypair.pk.clone().into(), amount, signature: Signature::empty_signature().into(), }; let domain = spec.get_domain( spec.genesis_slot.epoch(T::slots_per_epoch()), Domain::Deposit, &Fork::default(), ); data.signature = Signature::new(&data.signed_root()[..], domain, &keypair.sk).into(); data }) .collect::>(); let deposit_root_leaves = datas .par_iter() .map(|data| Hash256::from_slice(&data.tree_hash_root())) .collect::>(); let mut proofs = vec![]; for i in 1..=deposit_root_leaves.len() { // Note: this implementation is not so efficient. // // If `MerkleTree` had a push method, we could just build one tree and sample it instead of // rebuilding the tree for each deposit. let tree = MerkleTree::create( &deposit_root_leaves[0..i], spec.deposit_contract_tree_depth as usize, ); let (_, mut proof) = tree.generate_proof(i - 1, spec.deposit_contract_tree_depth as usize); proof.push(Hash256::from_slice(&int_to_bytes32(i))); assert_eq!( proof.len(), spec.deposit_contract_tree_depth as usize + 1, "Deposit proof should be correct len" ); proofs.push(proof); } let deposits = datas .into_par_iter() .zip(proofs.into_par_iter()) .map(|(data, proof)| (data, proof.into())) .map(|(data, proof)| Deposit { proof, data }) .collect::>(); let mut state = initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits, spec) .map_err(|e| format!("Unable to initialize genesis state: {:?}", e))?; state.genesis_time = genesis_time; // Invalid all the caches after all the manual state surgery. state.drop_all_caches(); Ok(state) } /// Returns `int` as little-endian bytes with a length of 32. fn int_to_bytes32(int: usize) -> Vec { let mut vec = int.to_le_bytes().to_vec(); vec.resize(32, 0); vec } /// Returns the system time, mod 30 minutes. /// /// Used for easily creating testnets. fn recent_genesis_time(minutes: u64) -> u64 { let now = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(); let secs_after_last_period = now.checked_rem(minutes * 60).unwrap_or(0); now - secs_after_last_period } #[cfg(test)] mod test { use super::*; use types::{test_utils::generate_deterministic_keypairs, EthSpec, MinimalEthSpec}; type TestEthSpec = MinimalEthSpec; #[test] fn interop_state() { let validator_count = 16; let genesis_time = 42; let spec = &TestEthSpec::default_spec(); let keypairs = generate_deterministic_keypairs(validator_count); let state = interop_genesis_state::(&keypairs, genesis_time, spec) .expect("should build state"); assert_eq!( state.eth1_data.block_hash, Hash256::from_slice(&[0x42; 32]), "eth1 block hash should be co-ordinated junk" ); assert_eq!( state.genesis_time, genesis_time, "genesis time should be as specified" ); for b in &state.balances { assert_eq!( *b, spec.max_effective_balance, "validator balances should be max effective balance" ); } for v in &state.validators { let creds = v.withdrawal_credentials.as_bytes(); assert_eq!( creds[0], spec.bls_withdrawal_prefix_byte, "first byte of withdrawal creds should be bls prefix" ); assert_eq!( &creds[1..], &hash(&v.pubkey.as_ssz_bytes())[1..], "rest of withdrawal creds should be pubkey hash" ) } assert_eq!( state.balances.len(), validator_count, "validator balances len should be correct" ); assert_eq!( state.validators.len(), validator_count, "validator count should be correct" ); } }