From 6234adc0d6801b92811383f686f59a1fad8985bc Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 30 Aug 2019 15:33:34 +1000 Subject: [PATCH] Add interop-spec genesis procedure --- beacon_node/beacon_chain/Cargo.toml | 2 + .../beacon_chain/src/beacon_chain_builder.rs | 174 +++++++++++++++++- beacon_node/client/src/lib.rs | 4 +- eth2/operation_pool/src/lib.rs | 6 +- eth2/types/src/slot_epoch_macros.rs | 2 +- .../builders/testing_beacon_state_builder.rs | 2 +- eth2/utils/bls/src/fake_public_key.rs | 8 + 7 files changed, 181 insertions(+), 17 deletions(-) diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 56cf7eed6..3378e6a34 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] eth2_config = { path = "../../eth2/utils/eth2_config" } +merkle_proof = { path = "../../eth2/utils/merkle_proof" } store = { path = "../store" } parking_lot = "0.7" lazy_static = "1.3.0" @@ -21,6 +22,7 @@ eth2-libp2p = { path = "../eth2-libp2p" } slog = { version = "^2.2.3" , features = ["max_level_trace"] } sloggers = { version = "^0.3" } slot_clock = { path = "../../eth2/utils/slot_clock" } +eth2_hashing = { path = "../../eth2/utils/eth2_hashing" } eth2_ssz = "0.1" eth2_ssz_derive = "0.1" state_processing = { path = "../../eth2/state_processing" } diff --git a/beacon_node/beacon_chain/src/beacon_chain_builder.rs b/beacon_node/beacon_chain/src/beacon_chain_builder.rs index 223d99d8d..8a5190048 100644 --- a/beacon_node/beacon_chain/src/beacon_chain_builder.rs +++ b/beacon_node/beacon_chain/src/beacon_chain_builder.rs @@ -1,11 +1,20 @@ use super::bootstrapper::Bootstrapper; use crate::{BeaconChain, BeaconChainTypes}; +use eth2_hashing::hash; +use merkle_proof::MerkleTree; +use rayon::prelude::*; use slog::Logger; +use ssz::Encode; +use state_processing::initialize_beacon_state_from_eth1; use std::fs::File; use std::path::PathBuf; use std::sync::Arc; use std::time::SystemTime; -use types::{test_utils::TestingBeaconStateBuilder, BeaconBlock, BeaconState, ChainSpec, EthSpec}; +use tree_hash::{SignedRoot, TreeHash}; +use types::{ + test_utils::generate_deterministic_keypairs, BeaconBlock, BeaconState, ChainSpec, Deposit, + DepositData, Domain, EthSpec, Fork, Hash256, PublicKey, Signature, +}; enum BuildStrategy { FromGenesis { @@ -27,7 +36,7 @@ impl BeaconChainBuilder { minutes: u64, spec: ChainSpec, log: Logger, - ) -> Self { + ) -> Result { Self::quick_start(recent_genesis_time(minutes), validator_count, spec, log) } @@ -36,14 +45,10 @@ impl BeaconChainBuilder { validator_count: usize, spec: ChainSpec, log: Logger, - ) -> Self { - let (mut genesis_state, _keypairs) = - TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec) - .build(); + ) -> Result { + let genesis_state = interop_genesis_state(validator_count, genesis_time, &spec)?; - genesis_state.genesis_time = genesis_time; - - Self::from_genesis_state(genesis_state, spec, log) + Ok(Self::from_genesis_state(genesis_state, spec, log)) } pub fn yaml_state(file: &PathBuf, spec: ChainSpec, log: Logger) -> Result { @@ -125,6 +130,95 @@ fn genesis_block(genesis_state: &BeaconState, spec: &ChainSpec) - genesis_block } +fn interop_genesis_state( + validator_count: usize, + genesis_time: u64, + spec: &ChainSpec, +) -> Result, String> { + let keypairs = generate_deterministic_keypairs(validator_count); + let eth1_block_hash = Hash256::from_slice(&[42; 32]); + let eth1_timestamp = 2_u64.pow(40); + let amount = spec.max_effective_balance; + dbg!(amount); + + 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.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; + + 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. @@ -134,6 +228,66 @@ fn recent_genesis_time(minutes: u64) -> u64 { .unwrap() .as_secs(); let secs_after_last_period = now.checked_rem(minutes * 60).unwrap_or(0); - // genesis is now the last 15 minute block. now - secs_after_last_period } + +#[cfg(test)] +mod test { + use super::*; + use types::{EthSpec, MinimalEthSpec}; + + type TestEthSpec = MinimalEthSpec; + + #[test] + fn interop_state() { + let validator_count = 16; + let genesis_time = 42; + let spec = &TestEthSpec::default_spec(); + + let state = interop_genesis_state::(validator_count, genesis_time, spec) + .expect("should build state"); + + assert_eq!( + state.eth1_data.block_hash, + Hash256::from_slice(&[42; 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" + ); + } +} diff --git a/beacon_node/client/src/lib.rs b/beacon_node/client/src/lib.rs index 9876e9672..c7558dd5e 100644 --- a/beacon_node/client/src/lib.rs +++ b/beacon_node/client/src/lib.rs @@ -96,7 +96,7 @@ where *minutes, spec.clone(), log.clone(), - ), + )?, BeaconChainStartMethod::Generated { validator_count, genesis_time, @@ -105,7 +105,7 @@ where *validator_count, spec.clone(), log.clone(), - ), + )?, BeaconChainStartMethod::Yaml { file } => { BeaconChainBuilder::yaml_state(file, spec.clone(), log.clone())? } diff --git a/eth2/operation_pool/src/lib.rs b/eth2/operation_pool/src/lib.rs index 0badf3807..bb64c3ca2 100644 --- a/eth2/operation_pool/src/lib.rs +++ b/eth2/operation_pool/src/lib.rs @@ -16,9 +16,9 @@ use state_processing::per_block_processing::errors::{ }; use state_processing::per_block_processing::{ get_slashable_indices_modular, verify_attestation_for_block_inclusion, - verify_attestation_for_state, verify_attester_slashing, verify_exit, - verify_exit_time_independent_only, verify_proposer_slashing, verify_transfer, - verify_transfer_time_independent_only, VerifySignatures, + verify_attester_slashing, verify_exit, verify_exit_time_independent_only, + verify_proposer_slashing, verify_transfer, verify_transfer_time_independent_only, + VerifySignatures, }; use std::collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet}; use std::marker::PhantomData; diff --git a/eth2/types/src/slot_epoch_macros.rs b/eth2/types/src/slot_epoch_macros.rs index 084ff98e7..62ca6b3af 100644 --- a/eth2/types/src/slot_epoch_macros.rs +++ b/eth2/types/src/slot_epoch_macros.rs @@ -182,7 +182,7 @@ macro_rules! impl_display { &self, record: &slog::Record, key: slog::Key, - serializer: &mut slog::Serializer, + serializer: &mut dyn slog::Serializer, ) -> slog::Result { slog::Value::serialize(&self.0, record, key, serializer) } diff --git a/eth2/types/src/test_utils/builders/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/builders/testing_beacon_state_builder.rs index 4f8a2d924..cf8c9ec8e 100644 --- a/eth2/types/src/test_utils/builders/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/builders/testing_beacon_state_builder.rs @@ -94,7 +94,7 @@ impl TestingBeaconStateBuilder { /// Creates the builder from an existing set of keypairs. pub fn from_keypairs(keypairs: Vec, spec: &ChainSpec) -> Self { let validator_count = keypairs.len(); - let starting_balance = 32_000_000_000; + let starting_balance = spec.max_effective_balance; debug!( "Building {} Validator objects from keypairs...", diff --git a/eth2/utils/bls/src/fake_public_key.rs b/eth2/utils/bls/src/fake_public_key.rs index e8dafaca6..82b1c707f 100644 --- a/eth2/utils/bls/src/fake_public_key.rs +++ b/eth2/utils/bls/src/fake_public_key.rs @@ -1,5 +1,6 @@ use super::{SecretKey, BLS_PUBLIC_KEY_BYTE_SIZE}; use milagro_bls::G1Point; +use milagro_bls::PublicKey as RawPublicKey; use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; use serde_hex::{encode as hex_encode, HexVisitor}; @@ -24,6 +25,13 @@ impl FakePublicKey { Self::zero() } + pub fn from_raw(raw: RawPublicKey) -> Self { + Self { + bytes: raw.clone().as_bytes(), + point: G1Point::new(), + } + } + /// Creates a new all-zero's public key pub fn zero() -> Self { Self {