diff --git a/Cargo.toml b/Cargo.toml index 4f453af4b5..a2c464366f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,8 @@ members = [ "eth2/attester", "eth2/block_producer", - "eth2/genesis", "eth2/fork_choice", + "eth2/state_processing", "eth2/types", "eth2/utils/bls", "eth2/utils/boolean-bitfield", @@ -12,7 +12,6 @@ members = [ "eth2/utils/slot_clock", "eth2/utils/ssz", "eth2/utils/vec_shuffle", - "eth2/validator_induction", "beacon_node", "beacon_node/db", "beacon_node/beacon_chain", diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..063ece3cdf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM rust:latest + +RUN apt-get update && apt-get install -y clang libclang-dev cmake build-essential git unzip autoconf libtool + +RUN git clone https://github.com/google/protobuf.git && \ + cd protobuf && \ + ./autogen.sh && \ + ./configure && \ + make && \ + make install && \ + ldconfig && \ + make clean && \ + cd .. && \ + rm -r protobuf + + +RUN mkdir /cargocache && chmod -R ugo+rwX /cargocache diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000000..42755d5f7f --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,20 @@ +pipeline { + agent { + dockerfile { + filename 'Dockerfile' + args '-v cargo-cache:/cargocache:rw -e "CARGO_HOME=/cargocache"' + } + } + stages { + stage('Build') { + steps { + sh 'cargo build' + } + } + stage('Test') { + steps { + sh 'cargo test --all' + } + } + } +} diff --git a/README.md b/README.md index 0d8a32040e..7759c11665 100644 --- a/README.md +++ b/README.md @@ -100,15 +100,8 @@ tests and benchmarks which may be of interest. A few basic steps are needed to get set up: - 1. Install [rustup](https://rustup.rs/). It's a toolchain manager for Rust (Linux | macos | Windows). For installation run the below command in your terminal - ``` - $ curl https://sh.rustup.rs -sSf | sh - ``` - 2. (Linux & MacOS) To configure your current shell run: - - ``` - $ source $HOME/.cargo/env - ``` + 1. Install [rustup](https://rustup.rs/). It's a toolchain manager for Rust (Linux | macos | Windows). For installation run the below command in your terminal `$ curl https://sh.rustup.rs -sSf | sh` + 2. (Linux & MacOS) To configure your current shell run: `$ source $HOME/.cargo/env` 3. Use the command `rustup show` to get information about the Rust installation. You should see that the active toolchain is the stable version. 4. Run `rustc --version` to check the installation and version of rust. diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index 4a930cfd29..a4804e07e6 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -15,7 +15,6 @@ db = { path = "db" } dirs = "1.0.3" futures = "0.1.23" fork_choice = { path = "../eth2/fork_choice" } -genesis = { path = "../eth2/genesis" } slog = "^2.2.3" slot_clock = { path = "../eth2/utils/slot_clock" } slog-term = "^2.4.0" diff --git a/beacon_node/beacon_chain/Cargo.toml b/beacon_node/beacon_chain/Cargo.toml index 779cb01559..36d7b37211 100644 --- a/beacon_node/beacon_chain/Cargo.toml +++ b/beacon_node/beacon_chain/Cargo.toml @@ -11,7 +11,6 @@ boolean-bitfield = { path = "../../eth2/utils/boolean-bitfield" } db = { path = "../db" } failure = "0.1" failure_derive = "0.1" -genesis = { path = "../../eth2/genesis" } hashing = { path = "../../eth2/utils/hashing" } fork_choice = { path = "../../eth2/fork_choice" } parking_lot = "0.7" @@ -22,4 +21,5 @@ serde_derive = "1.0" serde_json = "1.0" slot_clock = { path = "../../eth2/utils/slot_clock" } ssz = { path = "../../eth2/utils/ssz" } +state_processing = { path = "../../eth2/state_processing" } types = { path = "../../eth2/types" } diff --git a/beacon_node/beacon_chain/src/attestation_aggregator.rs b/beacon_node/beacon_chain/src/attestation_aggregator.rs index 0f6aa388e8..149f0d60d2 100644 --- a/beacon_node/beacon_chain/src/attestation_aggregator.rs +++ b/beacon_node/beacon_chain/src/attestation_aggregator.rs @@ -1,3 +1,4 @@ +use state_processing::validate_attestation_without_signature; use std::collections::{HashMap, HashSet}; use types::{ beacon_state::CommitteesError, AggregateSignature, Attestation, AttestationData, BeaconState, @@ -16,6 +17,7 @@ const PHASE_0_CUSTODY_BIT: bool = false; /// /// Note: `Attestations` are stored in memory and never deleted. This is not scalable and must be /// rectified in a future revision. +#[derive(Default)] pub struct AttestationAggregator { store: HashMap, Attestation>, } @@ -172,9 +174,7 @@ impl AttestationAggregator { self.store .values() .filter_map(|attestation| { - if state - .validate_attestation_without_signature(attestation, spec) - .is_ok() + if validate_attestation_without_signature(&state, attestation, spec).is_ok() && !known_attestation_data.contains(&attestation.data) { Some(attestation.clone()) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index f97b42b78f..41ceb4e297 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1,24 +1,25 @@ +use crate::attestation_aggregator::{AttestationAggregator, Outcome as AggregationOutcome}; +use crate::checkpoint::CheckPoint; use db::{ stores::{BeaconBlockStore, BeaconStateStore}, ClientDB, DBError, }; use fork_choice::{ForkChoice, ForkChoiceError}; -use genesis::{genesis_beacon_block, genesis_beacon_state}; use log::{debug, trace}; use parking_lot::{RwLock, RwLockReadGuard}; use slot_clock::SlotClock; use ssz::ssz_encode; +use state_processing::{ + BlockProcessable, BlockProcessingError, SlotProcessable, SlotProcessingError, +}; use std::sync::Arc; use types::{ - beacon_state::{BlockProcessingError, CommitteesError, SlotProcessingError}, + beacon_state::CommitteesError, readers::{BeaconBlockReader, BeaconStateReader}, - AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, ChainSpec, Eth1Data, - FreeAttestation, Hash256, PublicKey, Signature, + AttestationData, BeaconBlock, BeaconBlockBody, BeaconState, ChainSpec, Crosslink, Deposit, + Epoch, Eth1Data, FreeAttestation, Hash256, PublicKey, Signature, Slot, }; -use crate::attestation_aggregator::{AttestationAggregator, Outcome as AggregationOutcome}; -use crate::checkpoint::CheckPoint; - #[derive(Debug, PartialEq)] pub enum Error { InsufficientValidators, @@ -67,7 +68,6 @@ pub struct BeaconChain { pub attestation_aggregator: RwLock, canonical_head: RwLock, finalized_head: RwLock, - justified_head: RwLock, pub state: RwLock, pub spec: ChainSpec, pub fork_choice: RwLock, @@ -84,18 +84,26 @@ where state_store: Arc>, block_store: Arc>, slot_clock: U, + genesis_time: u64, + latest_eth1_data: Eth1Data, + initial_validator_deposits: Vec, spec: ChainSpec, fork_choice: F, ) -> Result { - if spec.initial_validators.is_empty() { + if initial_validator_deposits.is_empty() { return Err(Error::InsufficientValidators); } - let genesis_state = genesis_beacon_state(&spec); + let genesis_state = BeaconState::genesis( + genesis_time, + initial_validator_deposits, + latest_eth1_data, + &spec, + ); let state_root = genesis_state.canonical_root(); state_store.put(&state_root, &ssz_encode(&genesis_state)[..])?; - let genesis_block = genesis_beacon_block(state_root, &spec); + let genesis_block = BeaconBlock::genesis(state_root, &spec); let block_root = genesis_block.canonical_root(); block_store.put(&block_root, &ssz_encode(&genesis_block)[..])?; @@ -105,12 +113,6 @@ where genesis_state.clone(), state_root, )); - let justified_head = RwLock::new(CheckPoint::new( - genesis_block.clone(), - block_root, - genesis_state.clone(), - state_root, - )); let canonical_head = RwLock::new(CheckPoint::new( genesis_block.clone(), block_root, @@ -125,7 +127,6 @@ where slot_clock, attestation_aggregator, state: RwLock::new(genesis_state.clone()), - justified_head, finalized_head, canonical_head, spec, @@ -193,10 +194,10 @@ where /// It is important to note that this is _not_ the state corresponding to the canonical head /// block, instead it is that state which may or may not have had additional per slot/epoch /// processing applied to it. - pub fn advance_state(&self, slot: u64) -> Result<(), SlotProcessingError> { + pub fn advance_state(&self, slot: Slot) -> Result<(), SlotProcessingError> { let state_slot = self.state.read().slot; let head_block_root = self.head().beacon_block_root; - for _ in state_slot..slot { + for _ in state_slot.as_u64()..slot.as_u64() { self.state .write() .per_slot_processing(head_block_root, &self.spec)?; @@ -222,19 +223,6 @@ where None } - /// Returns the number of slots the validator has been required to propose. - /// - /// Returns `None` if the `validator_index` is invalid. - /// - /// Information is retrieved from the present `beacon_state.validator_registry`. - pub fn proposer_slots(&self, validator_index: usize) -> Option { - if let Some(validator) = self.state.read().validator_registry.get(validator_index) { - Some(validator.proposer_slots) - } else { - None - } - } - /// Reads the slot clock, returns `None` if the slot is unavailable. /// /// The slot might be unavailable due to an error with the system clock, or if the present time @@ -243,9 +231,10 @@ where /// This is distinct to `present_slot`, which simply reads the latest state. If a /// call to `read_slot_clock` results in a higher slot than a call to `present_slot`, /// `self.state` should undergo per slot processing. - pub fn read_slot_clock(&self) -> Option { + pub fn read_slot_clock(&self) -> Option { match self.slot_clock.present_slot() { - Ok(some_slot) => some_slot, + Ok(Some(some_slot)) => Some(some_slot), + Ok(None) => None, _ => None, } } @@ -255,7 +244,7 @@ where /// This is distinct to `read_slot_clock`, which reads from the actual system clock. If /// `self.state` has not been transitioned it is possible for the system clock to be on a /// different slot to what is returned from this call. - pub fn present_slot(&self) -> u64 { + pub fn present_slot(&self) -> Slot { self.state.read().slot } @@ -263,7 +252,7 @@ where /// /// Information is read from the present `beacon_state` shuffling, so only information from the /// present and prior epoch is available. - pub fn block_proposer(&self, slot: u64) -> Result { + pub fn block_proposer(&self, slot: Slot) -> Result { let index = self .state .read() @@ -273,8 +262,8 @@ where } /// Returns the justified slot for the present state. - pub fn justified_slot(&self) -> u64 { - self.state.read().justified_slot + pub fn justified_epoch(&self) -> Epoch { + self.state.read().justified_epoch } /// Returns the attestation slot and shard for a given validator index. @@ -284,7 +273,7 @@ where pub fn validator_attestion_slot_and_shard( &self, validator_index: usize, - ) -> Result, CommitteesError> { + ) -> Result, CommitteesError> { if let Some((slot, shard, _committee)) = self .state .read() @@ -298,11 +287,14 @@ where /// Produce an `AttestationData` that is valid for the present `slot` and given `shard`. pub fn produce_attestation_data(&self, shard: u64) -> Result { - let justified_slot = self.justified_slot(); + let justified_epoch = self.justified_epoch(); let justified_block_root = *self .state .read() - .get_block_root(justified_slot, &self.spec) + .get_block_root( + justified_epoch.start_slot(self.spec.epoch_length), + &self.spec, + ) .ok_or_else(|| Error::BadRecentBlockRoots)?; let epoch_boundary_root = *self @@ -320,8 +312,11 @@ where beacon_block_root: self.head().beacon_block_root, epoch_boundary_root, shard_block_root: Hash256::zero(), - latest_crosslink_root: Hash256::zero(), - justified_slot, + latest_crosslink: Crosslink { + epoch: self.state.read().slot.epoch(self.spec.epoch_length), + shard_block_root: Hash256::zero(), + }, + justified_epoch, justified_block_root, }) } @@ -450,7 +445,7 @@ where // Transition the parent state to the present slot. let mut state = parent_state; - for _ in state.slot..present_slot { + for _ in state.slot.as_u64()..present_slot.as_u64() { if let Err(e) = state.per_slot_processing(parent_block_root, &self.spec) { return Ok(BlockProcessingOutcome::InvalidBlock( InvalidBlock::SlotProcessingError(e), @@ -520,7 +515,7 @@ where attestations.len() ); - let parent_root = *state.get_block_root(state.slot.saturating_sub(1), &self.spec)?; + let parent_root = *state.get_block_root(state.slot.saturating_sub(1_u64), &self.spec)?; let mut block = BeaconBlock { slot: state.slot, @@ -535,11 +530,8 @@ where signature: self.spec.empty_signature.clone(), // To be completed by a validator. body: BeaconBlockBody { proposer_slashings: vec![], - casper_slashings: vec![], + attester_slashings: vec![], attestations, - custody_reseeds: vec![], - custody_challenges: vec![], - custody_responses: vec![], deposits: vec![], exits: vec![], }, diff --git a/beacon_node/beacon_chain/test_harness/Cargo.toml b/beacon_node/beacon_chain/test_harness/Cargo.toml index 86df42af89..bb335c1526 100644 --- a/beacon_node/beacon_chain/test_harness/Cargo.toml +++ b/beacon_node/beacon_chain/test_harness/Cargo.toml @@ -22,7 +22,6 @@ parking_lot = "0.7" failure = "0.1" failure_derive = "0.1" fork_choice = { path = "../../../eth2/fork_choice" } -genesis = { path = "../../../eth2/genesis" } hashing = { path = "../../../eth2/utils/hashing" } log = "0.4" env_logger = "0.6.0" diff --git a/beacon_node/beacon_chain/test_harness/src/harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs similarity index 78% rename from beacon_node/beacon_chain/test_harness/src/harness.rs rename to beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 533bfe4f64..15c863a7dd 100644 --- a/beacon_node/beacon_chain/test_harness/src/harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -1,6 +1,7 @@ -use super::TestValidator; +use super::ValidatorHarness; use beacon_chain::BeaconChain; pub use beacon_chain::{CheckPoint, Error as BeaconChainError}; +use bls::create_proof_of_possession; use db::{ stores::{BeaconBlockStore, BeaconStateStore}, MemoryDB, @@ -14,7 +15,10 @@ use std::fs::File; use std::io::prelude::*; use std::iter::FromIterator; use std::sync::Arc; -use types::{BeaconBlock, ChainSpec, FreeAttestation, Keypair, Validator}; +use types::{ + BeaconBlock, ChainSpec, Deposit, DepositData, DepositInput, Eth1Data, FreeAttestation, Hash256, + Keypair, Slot, +}; /// The beacon chain harness simulates a single beacon node with `validator_count` validators connected /// to it. Each validator is provided a borrow to the beacon chain, where it may read @@ -27,7 +31,7 @@ pub struct BeaconChainHarness { pub beacon_chain: Arc>>, pub block_store: Arc>, pub state_store: Arc>, - pub validators: Vec, + pub validators: Vec, pub spec: Arc, } @@ -36,16 +40,17 @@ impl BeaconChainHarness { /// /// - A keypair, `BlockProducer` and `Attester` for each validator. /// - A new BeaconChain struct where the given validators are in the genesis. - pub fn new(mut spec: ChainSpec, validator_count: usize) -> Self { + pub fn new(spec: ChainSpec, validator_count: usize) -> Self { let db = Arc::new(MemoryDB::open()); let block_store = Arc::new(BeaconBlockStore::new(db.clone())); let state_store = Arc::new(BeaconStateStore::new(db.clone())); - let slot_clock = TestingSlotClock::new(spec.genesis_slot); + let genesis_time = 1_549_935_547; // 12th Feb 2018 (arbitrary value in the past). + let slot_clock = TestingSlotClock::new(spec.genesis_slot.as_u64()); let fork_choice = OptimisedLMDGhost::new(block_store.clone(), state_store.clone()); - - // Remove the validators present in the spec (if any). - spec.initial_validators = Vec::with_capacity(validator_count); - spec.initial_balances = Vec::with_capacity(validator_count); + let latest_eth1_data = Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + }; debug!("Generating validator keypairs..."); @@ -55,25 +60,25 @@ impl BeaconChainHarness { .map(|_| Keypair::random()) .collect(); - debug!("Creating validator records..."); + debug!("Creating validator deposits..."); - spec.initial_validators = keypairs + let initial_validator_deposits = keypairs .par_iter() - .map(|keypair| Validator { - pubkey: keypair.pk.clone(), - activation_slot: 0, - ..std::default::Default::default() + .map(|keypair| Deposit { + branch: vec![], // branch verification is not specified. + index: 0, // index verification is not specified. + deposit_data: DepositData { + amount: 32_000_000_000, // 32 ETH (in Gwei) + timestamp: genesis_time - 1, + deposit_input: DepositInput { + pubkey: keypair.pk.clone(), + withdrawal_credentials: Hash256::zero(), // Withdrawal not possible. + proof_of_possession: create_proof_of_possession(&keypair), + }, + }, }) .collect(); - debug!("Setting validator balances..."); - - spec.initial_balances = spec - .initial_validators - .par_iter() - .map(|_| 32_000_000_000) // 32 ETH - .collect(); - debug!("Creating the BeaconChain..."); // Create the Beacon Chain @@ -82,6 +87,9 @@ impl BeaconChainHarness { state_store.clone(), block_store.clone(), slot_clock, + genesis_time, + latest_eth1_data, + initial_validator_deposits, spec.clone(), fork_choice, ) @@ -93,12 +101,14 @@ impl BeaconChainHarness { debug!("Creating validator producer and attester instances..."); // Spawn the test validator instances. - let validators: Vec = keypairs + let validators: Vec = keypairs .iter() - .map(|keypair| TestValidator::new(keypair.clone(), beacon_chain.clone(), spec.clone())) + .map(|keypair| { + ValidatorHarness::new(keypair.clone(), beacon_chain.clone(), spec.clone()) + }) .collect(); - debug!("Created {} TestValidators", validators.len()); + debug!("Created {} ValidatorHarnesss", validators.len()); Self { db, @@ -115,12 +125,12 @@ impl BeaconChainHarness { /// This is the equivalent of advancing a system clock forward one `SLOT_DURATION`. /// /// Returns the new slot. - pub fn increment_beacon_chain_slot(&mut self) -> u64 { + pub fn increment_beacon_chain_slot(&mut self) -> Slot { let slot = self.beacon_chain.present_slot() + 1; debug!("Incrementing BeaconChain slot to {}.", slot); - self.beacon_chain.slot_clock.set_slot(slot); + self.beacon_chain.slot_clock.set_slot(slot.as_u64()); self.beacon_chain.advance_state(slot).unwrap(); slot } @@ -136,7 +146,7 @@ impl BeaconChainHarness { .beacon_chain .state .read() - .get_crosslink_committees_at_slot(present_slot, &self.spec) + .get_crosslink_committees_at_slot(present_slot, false, &self.spec) .unwrap() .iter() .fold(vec![], |mut acc, (committee, _slot)| { @@ -226,7 +236,7 @@ impl BeaconChainHarness { } /// Write the output of `chain_dump` to a JSON file. - pub fn dump_to_file(&self, filename: String, chain_dump: &Vec) { + pub fn dump_to_file(&self, filename: String, chain_dump: &[CheckPoint]) { let json = serde_json::to_string(chain_dump).unwrap(); let mut file = File::create(filename).unwrap(); file.write_all(json.as_bytes()) diff --git a/beacon_node/beacon_chain/test_harness/src/lib.rs b/beacon_node/beacon_chain/test_harness/src/lib.rs index 71f9a88558..b04fc69963 100644 --- a/beacon_node/beacon_chain/test_harness/src/lib.rs +++ b/beacon_node/beacon_chain/test_harness/src/lib.rs @@ -1,5 +1,5 @@ -mod harness; -mod validator; +mod beacon_chain_harness; +mod validator_harness; -pub use self::harness::BeaconChainHarness; -pub use self::validator::TestValidator; +pub use self::beacon_chain_harness::BeaconChainHarness; +pub use self::validator_harness::ValidatorHarness; diff --git a/beacon_node/beacon_chain/test_harness/src/validator/mod.rs b/beacon_node/beacon_chain/test_harness/src/validator/mod.rs deleted file mode 100644 index 3a0d53395e..0000000000 --- a/beacon_node/beacon_chain/test_harness/src/validator/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod direct_beacon_node; -mod direct_duties; -mod signer; -mod validator; - -pub use self::validator::TestValidator; diff --git a/beacon_node/beacon_chain/test_harness/src/validator/direct_beacon_node.rs b/beacon_node/beacon_chain/test_harness/src/validator_harness/direct_beacon_node.rs similarity index 82% rename from beacon_node/beacon_chain/test_harness/src/validator/direct_beacon_node.rs rename to beacon_node/beacon_chain/test_harness/src/validator_harness/direct_beacon_node.rs index d33f782d66..be71b9abd8 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator/direct_beacon_node.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator_harness/direct_beacon_node.rs @@ -12,7 +12,7 @@ use fork_choice::ForkChoice; use parking_lot::RwLock; use slot_clock::SlotClock; use std::sync::Arc; -use types::{AttestationData, BeaconBlock, FreeAttestation, PublicKey, Signature}; +use types::{AttestationData, BeaconBlock, FreeAttestation, Signature, Slot}; // mod attester; // mod producer; @@ -52,7 +52,7 @@ impl DirectBeaconNode { impl AttesterBeaconNode for DirectBeaconNode { fn produce_attestation_data( &self, - _slot: u64, + _slot: Slot, shard: u64, ) -> Result, NodeError> { match self.beacon_chain.produce_attestation_data(shard) { @@ -71,31 +71,17 @@ impl AttesterBeaconNode for DirectBeac } impl BeaconBlockNode for DirectBeaconNode { - /// Requests the `proposer_nonce` from the `BeaconChain`. - fn proposer_nonce(&self, pubkey: &PublicKey) -> Result { - let validator_index = self - .beacon_chain - .validator_index(pubkey) - .ok_or_else(|| BeaconBlockNodeError::RemoteFailure("pubkey unknown.".to_string()))?; - - self.beacon_chain - .proposer_slots(validator_index) - .ok_or_else(|| { - BeaconBlockNodeError::RemoteFailure("validator_index unknown.".to_string()) - }) - } - /// Requests a new `BeaconBlock from the `BeaconChain`. fn produce_beacon_block( &self, - slot: u64, + slot: Slot, randao_reveal: &Signature, ) -> Result, BeaconBlockNodeError> { let (block, _state) = self .beacon_chain .produce_block(randao_reveal.clone()) .ok_or_else(|| { - BeaconBlockNodeError::RemoteFailure(format!("Did not produce block.")) + BeaconBlockNodeError::RemoteFailure("Did not produce block.".to_string()) })?; if block.slot == slot { diff --git a/beacon_node/beacon_chain/test_harness/src/validator/direct_duties.rs b/beacon_node/beacon_chain/test_harness/src/validator_harness/direct_duties.rs similarity index 87% rename from beacon_node/beacon_chain/test_harness/src/validator/direct_duties.rs rename to beacon_node/beacon_chain/test_harness/src/validator_harness/direct_duties.rs index c6d97ee379..66b9d650c2 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator/direct_duties.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator_harness/direct_duties.rs @@ -9,7 +9,7 @@ use db::ClientDB; use fork_choice::ForkChoice; use slot_clock::SlotClock; use std::sync::Arc; -use types::PublicKey; +use types::{PublicKey, Slot}; /// Connects directly to a borrowed `BeaconChain` and reads attester/proposer duties directly from /// it. @@ -28,7 +28,7 @@ impl DirectDuties { } impl ProducerDutiesReader for DirectDuties { - fn is_block_production_slot(&self, slot: u64) -> Result { + fn is_block_production_slot(&self, slot: Slot) -> Result { let validator_index = self .beacon_chain .validator_index(&self.pubkey) @@ -50,7 +50,7 @@ impl AttesterDutiesReader for DirectDu } } - fn attestation_shard(&self, slot: u64) -> Result, AttesterDutiesReaderError> { + fn attestation_shard(&self, slot: Slot) -> Result, AttesterDutiesReaderError> { if let Some(validator_index) = self.validator_index() { match self .beacon_chain @@ -61,7 +61,7 @@ impl AttesterDutiesReader for DirectDu } Ok(Some(_)) => Ok(None), Ok(None) => Err(AttesterDutiesReaderError::UnknownEpoch), - Err(_) => panic!("Error when getting validator attestation shard."), + Err(_) => unreachable!("Error when getting validator attestation shard."), } } else { Err(AttesterDutiesReaderError::UnknownValidator) diff --git a/beacon_node/beacon_chain/test_harness/src/validator/signer.rs b/beacon_node/beacon_chain/test_harness/src/validator_harness/local_signer.rs similarity index 90% rename from beacon_node/beacon_chain/test_harness/src/validator/signer.rs rename to beacon_node/beacon_chain/test_harness/src/validator_harness/local_signer.rs index 6e63c7f99e..8e901b057b 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator/signer.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator_harness/local_signer.rs @@ -4,12 +4,12 @@ use std::sync::RwLock; use types::{Keypair, Signature}; /// A test-only struct used to perform signing for a proposer or attester. -pub struct TestSigner { +pub struct LocalSigner { keypair: Keypair, should_sign: RwLock, } -impl TestSigner { +impl LocalSigner { /// Produce a new TestSigner with signing enabled by default. pub fn new(keypair: Keypair) -> Self { Self { @@ -30,7 +30,7 @@ impl TestSigner { } } -impl BlockProposerSigner for TestSigner { +impl BlockProposerSigner for LocalSigner { fn sign_block_proposal(&self, message: &[u8]) -> Option { self.bls_sign(message) } @@ -40,7 +40,7 @@ impl BlockProposerSigner for TestSigner { } } -impl AttesterSigner for TestSigner { +impl AttesterSigner for LocalSigner { fn sign_attestation_message(&self, message: &[u8]) -> Option { self.bls_sign(message) } diff --git a/beacon_node/beacon_chain/test_harness/src/validator/validator.rs b/beacon_node/beacon_chain/test_harness/src/validator_harness/mod.rs similarity index 87% rename from beacon_node/beacon_chain/test_harness/src/validator/validator.rs rename to beacon_node/beacon_chain/test_harness/src/validator_harness/mod.rs index 2e9b4e981e..e22ea1a2e2 100644 --- a/beacon_node/beacon_chain/test_harness/src/validator/validator.rs +++ b/beacon_node/beacon_chain/test_harness/src/validator_harness/mod.rs @@ -1,16 +1,20 @@ -use super::direct_beacon_node::DirectBeaconNode; -use super::direct_duties::DirectDuties; -use super::signer::TestSigner; +mod direct_beacon_node; +mod direct_duties; +mod local_signer; + use attester::PollOutcome as AttestationPollOutcome; use attester::{Attester, Error as AttestationPollError}; use beacon_chain::BeaconChain; use block_producer::PollOutcome as BlockPollOutcome; use block_producer::{BlockProducer, Error as BlockPollError}; use db::MemoryDB; +use direct_beacon_node::DirectBeaconNode; +use direct_duties::DirectDuties; use fork_choice::{optimised_lmd_ghost::OptimisedLMDGhost, slow_lmd_ghost::SlowLMDGhost}; +use local_signer::LocalSigner; use slot_clock::TestingSlotClock; use std::sync::Arc; -use types::{BeaconBlock, ChainSpec, FreeAttestation, Keypair}; +use types::{BeaconBlock, ChainSpec, FreeAttestation, Keypair, Slot}; #[derive(Debug, PartialEq)] pub enum BlockProduceError { @@ -29,29 +33,29 @@ pub enum AttestationProduceError { /// The test validator connects directly to a borrowed `BeaconChain` struct. It is useful for /// testing that the core proposer and attester logic is functioning. Also for supporting beacon /// chain tests. -pub struct TestValidator { +pub struct ValidatorHarness { pub block_producer: BlockProducer< TestingSlotClock, DirectBeaconNode>, DirectDuties>, - TestSigner, + LocalSigner, >, pub attester: Attester< TestingSlotClock, DirectBeaconNode>, DirectDuties>, - TestSigner, + LocalSigner, >, pub spec: Arc, pub epoch_map: Arc>>, pub keypair: Keypair, pub beacon_node: Arc>>, pub slot_clock: Arc, - pub signer: Arc, + pub signer: Arc, } -impl TestValidator { - /// Create a new TestValidator that signs with the given keypair, operates per the given spec and connects to the +impl ValidatorHarness { + /// Create a new ValidatorHarness that signs with the given keypair, operates per the given spec and connects to the /// supplied beacon node. /// /// A `BlockProducer` and `Attester` is created.. @@ -60,14 +64,13 @@ impl TestValidator { beacon_chain: Arc>>, spec: Arc, ) -> Self { - let slot_clock = Arc::new(TestingSlotClock::new(spec.genesis_slot)); - let signer = Arc::new(TestSigner::new(keypair.clone())); + let slot_clock = Arc::new(TestingSlotClock::new(spec.genesis_slot.as_u64())); + let signer = Arc::new(LocalSigner::new(keypair.clone())); let beacon_node = Arc::new(DirectBeaconNode::new(beacon_chain.clone())); let epoch_map = Arc::new(DirectDuties::new(keypair.pk.clone(), beacon_chain.clone())); let block_producer = BlockProducer::new( spec.clone(), - keypair.pk.clone(), epoch_map.clone(), slot_clock.clone(), beacon_node.clone(), @@ -128,7 +131,7 @@ impl TestValidator { /// Set the validators slot clock to the specified slot. /// /// The validators slot clock will always read this value until it is set to something else. - pub fn set_slot(&mut self, slot: u64) { - self.slot_clock.set_slot(slot) + pub fn set_slot(&mut self, slot: Slot) { + self.slot_clock.set_slot(slot.as_u64()) } } diff --git a/beacon_node/beacon_chain/test_harness/tests/chain.rs b/beacon_node/beacon_chain/test_harness/tests/chain.rs index c0b537695d..8be6f2a267 100644 --- a/beacon_node/beacon_chain/test_harness/tests/chain.rs +++ b/beacon_node/beacon_chain/test_harness/tests/chain.rs @@ -1,13 +1,13 @@ use env_logger::{Builder, Env}; use log::debug; use test_harness::BeaconChainHarness; -use types::ChainSpec; +use types::{ChainSpec, Slot}; #[test] #[ignore] fn it_can_build_on_genesis_block() { let mut spec = ChainSpec::foundation(); - spec.genesis_slot = spec.epoch_length * 8; + spec.genesis_slot = Slot::new(spec.epoch_length * 8); /* spec.shard_count = spec.shard_count / 8; diff --git a/beacon_node/db/src/stores/beacon_block_store.rs b/beacon_node/db/src/stores/beacon_block_store.rs index fcdd595f53..8a1fc2b0db 100644 --- a/beacon_node/db/src/stores/beacon_block_store.rs +++ b/beacon_node/db/src/stores/beacon_block_store.rs @@ -2,7 +2,7 @@ use super::BLOCKS_DB_COLUMN as DB_COLUMN; use super::{ClientDB, DBError}; use ssz::Decodable; use std::sync::Arc; -use types::{readers::BeaconBlockReader, BeaconBlock, Hash256}; +use types::{readers::BeaconBlockReader, BeaconBlock, Hash256, Slot}; #[derive(Clone, Debug, PartialEq)] pub enum BeaconBlockAtSlotError { @@ -71,7 +71,7 @@ impl BeaconBlockStore { pub fn block_at_slot( &self, head_hash: &Hash256, - slot: u64, + slot: Slot, ) -> Result, BeaconBlockAtSlotError> { let mut current_hash = *head_hash; @@ -119,12 +119,12 @@ mod tests { let mut rng = XorShiftRng::from_seed([42; 16]); let mut block = BeaconBlock::random_for_test(&mut rng); - block.slot = 10; + block.slot = Slot::from(10_u64); let block_root = block.canonical_root(); bs.put(&block_root, &ssz_encode(&block)).unwrap(); - let result = bs.block_at_slot(&block_root, 11).unwrap(); + let result = bs.block_at_slot(&block_root, Slot::from(11_u64)).unwrap(); assert_eq!(result, None); } @@ -138,7 +138,7 @@ mod tests { db.put(DB_COLUMN, hash, ssz).unwrap(); assert_eq!( - store.block_at_slot(hash, 42), + store.block_at_slot(hash, Slot::from(42_u64)), Err(BeaconBlockAtSlotError::DBError( "Bad BeaconBlock SSZ.".into() )) @@ -156,7 +156,7 @@ mod tests { db.put(DB_COLUMN, hash, ssz).unwrap(); assert_eq!( - store.block_at_slot(other_hash, 42), + store.block_at_slot(other_hash, Slot::from(42_u64)), Err(BeaconBlockAtSlotError::UnknownBeaconBlock(*other_hash)) ); } @@ -221,7 +221,7 @@ mod tests { Hash256::from(&[2; 32][..]), Hash256::from(&[3; 32][..]), ]; - let slots = [0, 1, 3, 4, 5]; + let slots: Vec = vec![0, 1, 3, 4, 5].iter().map(|x| Slot::new(*x)).collect(); // Generate a vec of random blocks and store them in the DB. let block_count = 5; @@ -249,14 +249,14 @@ mod tests { assert_eq!(reader.slot(), slots[slot_index]); } - let ssz = bs.block_at_slot(&hashes[4], 2).unwrap(); + let ssz = bs.block_at_slot(&hashes[4], Slot::new(2)).unwrap(); assert_eq!(ssz, None); - let ssz = bs.block_at_slot(&hashes[4], 6).unwrap(); + let ssz = bs.block_at_slot(&hashes[4], Slot::new(6)).unwrap(); assert_eq!(ssz, None); let bad_hash = &Hash256::from("unknown".as_bytes()); - let ssz = bs.block_at_slot(bad_hash, 2); + let ssz = bs.block_at_slot(bad_hash, Slot::new(2)); assert_eq!( ssz, Err(BeaconBlockAtSlotError::UnknownBeaconBlock(*bad_hash)) diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 09471860bf..2b6cdddcda 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -8,6 +8,7 @@ use std::path::PathBuf; use crate::config::LighthouseConfig; use crate::rpc::start_server; use beacon_chain::BeaconChain; +use bls::create_proof_of_possession; use clap::{App, Arg}; use db::{ stores::{BeaconBlockStore, BeaconStateStore}, @@ -17,7 +18,7 @@ use fork_choice::optimised_lmd_ghost::OptimisedLMDGhost; use slog::{error, info, o, Drain}; use slot_clock::SystemTimeSlotClock; use std::sync::Arc; -use types::ChainSpec; +use types::{ChainSpec, Deposit, DepositData, DepositInput, Eth1Data, Hash256, Keypair}; fn main() { let decorator = slog_term::TermDecorator::new().build(); @@ -76,17 +77,51 @@ fn main() { let state_store = Arc::new(BeaconStateStore::new(db.clone())); // Slot clock - let slot_clock = SystemTimeSlotClock::new(spec.genesis_time, spec.slot_duration) + let genesis_time = 1_549_935_547; // 12th Feb 2018 (arbitrary value in the past). + let slot_clock = SystemTimeSlotClock::new(genesis_time, spec.slot_duration) .expect("Unable to load SystemTimeSlotClock"); - // Choose the fork choice let fork_choice = OptimisedLMDGhost::new(block_store.clone(), state_store.clone()); + + /* + * Generate some random data to start a chain with. + * + * This is will need to be replace for production usage. + */ + let latest_eth1_data = Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + }; + let keypairs: Vec = (0..10) + .collect::>() + .iter() + .map(|_| Keypair::random()) + .collect(); + let initial_validator_deposits = keypairs + .iter() + .map(|keypair| Deposit { + branch: vec![], // branch verification is not specified. + index: 0, // index verification is not specified. + deposit_data: DepositData { + amount: 32_000_000_000, // 32 ETH (in Gwei) + timestamp: genesis_time - 1, + deposit_input: DepositInput { + pubkey: keypair.pk.clone(), + withdrawal_credentials: Hash256::zero(), // Withdrawal not possible. + proof_of_possession: create_proof_of_possession(&keypair), + }, + }, + }) + .collect(); + // Genesis chain - // TODO: persist chain to storage. let _chain_result = BeaconChain::genesis( state_store.clone(), block_store.clone(), slot_clock, + genesis_time, + latest_eth1_data, + initial_validator_deposits, spec, fork_choice, ); diff --git a/beacon_node/src/rpc/beacon_block.rs b/beacon_node/src/rpc/beacon_block.rs index a047365efa..96f64e0dd4 100644 --- a/beacon_node/src/rpc/beacon_block.rs +++ b/beacon_node/src/rpc/beacon_block.rs @@ -25,7 +25,7 @@ impl BeaconBlockService for BeaconBlockServiceInstance { // TODO: build a legit block. let mut block = BeaconBlockProto::new(); block.set_slot(req.get_slot()); - block.set_block_root("cats".as_bytes().to_vec()); + block.set_block_root(b"cats".to_vec()); let mut resp = ProduceBeaconBlockResponse::new(); resp.set_block(block); diff --git a/eth2/attester/src/lib.rs b/eth2/attester/src/lib.rs index 447bcb9eaa..7352dd2ea8 100644 --- a/eth2/attester/src/lib.rs +++ b/eth2/attester/src/lib.rs @@ -3,7 +3,7 @@ mod traits; use slot_clock::SlotClock; use std::sync::Arc; -use types::{AttestationData, FreeAttestation, Signature}; +use types::{AttestationData, FreeAttestation, Signature, Slot}; pub use self::traits::{ BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, PublishOutcome, Signer, @@ -13,14 +13,14 @@ const PHASE_0_CUSTODY_BIT: bool = false; #[derive(Debug, PartialEq)] pub enum PollOutcome { - AttestationProduced(u64), - AttestationNotRequired(u64), - SlashableAttestationNotProduced(u64), - BeaconNodeUnableToProduceAttestation(u64), - ProducerDutiesUnknown(u64), - SlotAlreadyProcessed(u64), - SignerRejection(u64), - ValidatorIsUnknown(u64), + AttestationProduced(Slot), + AttestationNotRequired(Slot), + SlashableAttestationNotProduced(Slot), + BeaconNodeUnableToProduceAttestation(Slot), + ProducerDutiesUnknown(Slot), + SlotAlreadyProcessed(Slot), + SignerRejection(Slot), + ValidatorIsUnknown(Slot), } #[derive(Debug, PartialEq)] @@ -40,7 +40,7 @@ pub enum Error { /// /// Relies upon an external service to keep the `EpochDutiesMap` updated. pub struct Attester { - pub last_processed_slot: Option, + pub last_processed_slot: Option, duties: Arc, slot_clock: Arc, beacon_node: Arc, @@ -91,7 +91,7 @@ impl Attester Result { + fn produce_attestation(&mut self, slot: Slot, shard: u64) -> Result { let attestation_data = match self.beacon_node.produce_attestation_data(slot, shard)? { Some(attestation_data) => attestation_data, None => return Ok(PollOutcome::BeaconNodeUnableToProduceAttestation(slot)), @@ -122,7 +122,7 @@ impl Attester bool { + fn is_processed_slot(&self, slot: Slot) -> bool { match self.last_processed_slot { Some(processed_slot) if slot <= processed_slot => true, _ => false, @@ -170,7 +170,7 @@ impl From for Error { #[cfg(test)] mod tests { - use super::test_utils::{TestBeaconNode, TestEpochMap, TestSigner}; + use super::test_utils::{EpochMap, LocalSigner, SimulatedBeaconNode}; use super::*; use slot_clock::TestingSlotClock; use types::{ @@ -189,11 +189,11 @@ mod tests { let spec = Arc::new(ChainSpec::foundation()); let slot_clock = Arc::new(TestingSlotClock::new(0)); - let beacon_node = Arc::new(TestBeaconNode::default()); - let signer = Arc::new(TestSigner::new(Keypair::random())); + let beacon_node = Arc::new(SimulatedBeaconNode::default()); + let signer = Arc::new(LocalSigner::new(Keypair::random())); - let mut duties = TestEpochMap::new(spec.epoch_length); - let attest_slot = 100; + let mut duties = EpochMap::new(spec.epoch_length); + let attest_slot = Slot::new(100); let attest_epoch = attest_slot / spec.epoch_length; let attest_shard = 12; duties.insert_attestation_shard(attest_slot, attest_shard); @@ -212,28 +212,28 @@ mod tests { beacon_node.set_next_publish_result(Ok(PublishOutcome::ValidAttestation)); // One slot before attestation slot... - slot_clock.set_slot(attest_slot - 1); + slot_clock.set_slot(attest_slot.as_u64() - 1); assert_eq!( attester.poll(), Ok(PollOutcome::AttestationNotRequired(attest_slot - 1)) ); // On the attest slot... - slot_clock.set_slot(attest_slot); + slot_clock.set_slot(attest_slot.as_u64()); assert_eq!( attester.poll(), Ok(PollOutcome::AttestationProduced(attest_slot)) ); // Trying the same attest slot again... - slot_clock.set_slot(attest_slot); + slot_clock.set_slot(attest_slot.as_u64()); assert_eq!( attester.poll(), Ok(PollOutcome::SlotAlreadyProcessed(attest_slot)) ); // One slot after the attest slot... - slot_clock.set_slot(attest_slot + 1); + slot_clock.set_slot(attest_slot.as_u64() + 1); assert_eq!( attester.poll(), Ok(PollOutcome::AttestationNotRequired(attest_slot + 1)) @@ -241,7 +241,7 @@ mod tests { // In an epoch without known duties... let slot = (attest_epoch + 1) * spec.epoch_length; - slot_clock.set_slot(slot); + slot_clock.set_slot(slot.into()); assert_eq!( attester.poll(), Ok(PollOutcome::ProducerDutiesUnknown(slot)) diff --git a/eth2/attester/src/test_utils/epoch_map.rs b/eth2/attester/src/test_utils/epoch_map.rs index 9f44ecf1a3..f0dc4312ef 100644 --- a/eth2/attester/src/test_utils/epoch_map.rs +++ b/eth2/attester/src/test_utils/epoch_map.rs @@ -1,13 +1,14 @@ use crate::{DutiesReader, DutiesReaderError}; use std::collections::HashMap; +use types::{Epoch, Slot}; -pub struct TestEpochMap { +pub struct EpochMap { epoch_length: u64, validator_index: Option, - map: HashMap, + map: HashMap, } -impl TestEpochMap { +impl EpochMap { pub fn new(epoch_length: u64) -> Self { Self { epoch_length, @@ -16,9 +17,8 @@ impl TestEpochMap { } } - pub fn insert_attestation_shard(&mut self, slot: u64, shard: u64) { - let epoch = slot / self.epoch_length; - + pub fn insert_attestation_shard(&mut self, slot: Slot, shard: u64) { + let epoch = slot.epoch(self.epoch_length); self.map.insert(epoch, (slot, shard)); } @@ -27,9 +27,9 @@ impl TestEpochMap { } } -impl DutiesReader for TestEpochMap { - fn attestation_shard(&self, slot: u64) -> Result, DutiesReaderError> { - let epoch = slot / self.epoch_length; +impl DutiesReader for EpochMap { + fn attestation_shard(&self, slot: Slot) -> Result, DutiesReaderError> { + let epoch = slot.epoch(self.epoch_length); match self.map.get(&epoch) { Some((attest_slot, attest_shard)) if *attest_slot == slot => Ok(Some(*attest_shard)), diff --git a/eth2/attester/src/test_utils/signer.rs b/eth2/attester/src/test_utils/local_signer.rs similarity index 83% rename from eth2/attester/src/test_utils/signer.rs rename to eth2/attester/src/test_utils/local_signer.rs index 185dec2787..c256d10506 100644 --- a/eth2/attester/src/test_utils/signer.rs +++ b/eth2/attester/src/test_utils/local_signer.rs @@ -3,13 +3,13 @@ use std::sync::RwLock; use types::{Keypair, Signature}; /// A test-only struct used to simulate a Beacon Node. -pub struct TestSigner { +pub struct LocalSigner { keypair: Keypair, should_sign: RwLock, } -impl TestSigner { - /// Produce a new TestSigner with signing enabled by default. +impl LocalSigner { + /// Produce a new LocalSigner with signing enabled by default. pub fn new(keypair: Keypair) -> Self { Self { keypair, @@ -24,7 +24,7 @@ impl TestSigner { } } -impl Signer for TestSigner { +impl Signer for LocalSigner { fn sign_attestation_message(&self, message: &[u8]) -> Option { Some(Signature::new(message, &self.keypair.sk)) } diff --git a/eth2/attester/src/test_utils/mod.rs b/eth2/attester/src/test_utils/mod.rs index 0dd384b125..481247dd00 100644 --- a/eth2/attester/src/test_utils/mod.rs +++ b/eth2/attester/src/test_utils/mod.rs @@ -1,7 +1,7 @@ -mod beacon_node; mod epoch_map; -mod signer; +mod local_signer; +mod simulated_beacon_node; -pub use self::beacon_node::TestBeaconNode; -pub use self::epoch_map::TestEpochMap; -pub use self::signer::TestSigner; +pub use self::epoch_map::EpochMap; +pub use self::local_signer::LocalSigner; +pub use self::simulated_beacon_node::SimulatedBeaconNode; diff --git a/eth2/attester/src/test_utils/beacon_node.rs b/eth2/attester/src/test_utils/simulated_beacon_node.rs similarity index 82% rename from eth2/attester/src/test_utils/beacon_node.rs rename to eth2/attester/src/test_utils/simulated_beacon_node.rs index 5b4061c609..bab48a9757 100644 --- a/eth2/attester/src/test_utils/beacon_node.rs +++ b/eth2/attester/src/test_utils/simulated_beacon_node.rs @@ -1,21 +1,21 @@ use crate::traits::{BeaconNode, BeaconNodeError, PublishOutcome}; use std::sync::RwLock; -use types::{AttestationData, FreeAttestation}; +use types::{AttestationData, FreeAttestation, Slot}; type ProduceResult = Result, BeaconNodeError>; type PublishResult = Result; /// A test-only struct used to simulate a Beacon Node. #[derive(Default)] -pub struct TestBeaconNode { - pub produce_input: RwLock>, +pub struct SimulatedBeaconNode { + pub produce_input: RwLock>, pub produce_result: RwLock>, pub publish_input: RwLock>, pub publish_result: RwLock>, } -impl TestBeaconNode { +impl SimulatedBeaconNode { pub fn set_next_produce_result(&self, result: ProduceResult) { *self.produce_result.write().unwrap() = Some(result); } @@ -25,8 +25,8 @@ impl TestBeaconNode { } } -impl BeaconNode for TestBeaconNode { - fn produce_attestation_data(&self, slot: u64, shard: u64) -> ProduceResult { +impl BeaconNode for SimulatedBeaconNode { + fn produce_attestation_data(&self, slot: Slot, shard: u64) -> ProduceResult { *self.produce_input.write().unwrap() = Some((slot, shard)); match *self.produce_result.read().unwrap() { Some(ref r) => r.clone(), diff --git a/eth2/attester/src/traits.rs b/eth2/attester/src/traits.rs index fd07fd1718..53bce3aaa7 100644 --- a/eth2/attester/src/traits.rs +++ b/eth2/attester/src/traits.rs @@ -1,4 +1,4 @@ -use types::{AttestationData, FreeAttestation, Signature}; +use types::{AttestationData, FreeAttestation, Signature, Slot}; #[derive(Debug, PartialEq, Clone)] pub enum BeaconNodeError { @@ -16,7 +16,7 @@ pub enum PublishOutcome { pub trait BeaconNode: Send + Sync { fn produce_attestation_data( &self, - slot: u64, + slot: Slot, shard: u64, ) -> Result, BeaconNodeError>; @@ -37,7 +37,7 @@ pub enum DutiesReaderError { /// Informs a validator of their duties (e.g., block production). pub trait DutiesReader: Send + Sync { /// Returns `Some(shard)` if this slot is an attestation slot. Otherwise, returns `None.` - fn attestation_shard(&self, slot: u64) -> Result, DutiesReaderError>; + fn attestation_shard(&self, slot: Slot) -> Result, DutiesReaderError>; /// Returns `Some(shard)` if this slot is an attestation slot. Otherwise, returns `None.` fn validator_index(&self) -> Option; diff --git a/eth2/block_producer/src/lib.rs b/eth2/block_producer/src/lib.rs index ed65622d4d..f6a0fd6dff 100644 --- a/eth2/block_producer/src/lib.rs +++ b/eth2/block_producer/src/lib.rs @@ -4,7 +4,7 @@ mod traits; use slot_clock::SlotClock; use ssz::ssz_encode; use std::sync::Arc; -use types::{BeaconBlock, ChainSpec, PublicKey}; +use types::{BeaconBlock, ChainSpec, Slot}; pub use self::traits::{ BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, PublishOutcome, Signer, @@ -13,21 +13,21 @@ pub use self::traits::{ #[derive(Debug, PartialEq)] pub enum PollOutcome { /// A new block was produced. - BlockProduced(u64), + BlockProduced(Slot), /// A block was not produced as it would have been slashable. - SlashableBlockNotProduced(u64), + SlashableBlockNotProduced(Slot), /// The validator duties did not require a block to be produced. - BlockProductionNotRequired(u64), + BlockProductionNotRequired(Slot), /// The duties for the present epoch were not found. - ProducerDutiesUnknown(u64), + ProducerDutiesUnknown(Slot), /// The slot has already been processed, execution was skipped. - SlotAlreadyProcessed(u64), + SlotAlreadyProcessed(Slot), /// The Beacon Node was unable to produce a block at that slot. - BeaconNodeUnableToProduceBlock(u64), + BeaconNodeUnableToProduceBlock(Slot), /// The signer failed to sign the message. - SignerRejection(u64), + SignerRejection(Slot), /// The public key for this validator is not an active validator. - ValidatorIsUnknown(u64), + ValidatorIsUnknown(Slot), } #[derive(Debug, PartialEq)] @@ -47,8 +47,7 @@ pub enum Error { /// /// Relies upon an external service to keep the `EpochDutiesMap` updated. pub struct BlockProducer { - pub last_processed_slot: Option, - pubkey: PublicKey, + pub last_processed_slot: Option, spec: Arc, epoch_map: Arc, slot_clock: Arc, @@ -60,7 +59,6 @@ impl BlockProducer, - pubkey: PublicKey, epoch_map: Arc, slot_clock: Arc, beacon_node: Arc, @@ -68,7 +66,6 @@ impl BlockProducer Self { Self { last_processed_slot: None, - pubkey, spec, epoch_map, slot_clock, @@ -115,7 +112,7 @@ impl BlockProducer bool { + fn is_processed_slot(&self, slot: Slot) -> bool { match self.last_processed_slot { Some(processed_slot) if processed_slot >= slot => true, _ => false, @@ -132,12 +129,10 @@ impl BlockProducer Result { + fn produce_block(&mut self, slot: Slot) -> Result { let randao_reveal = { - let producer_nonce = self.beacon_node.proposer_nonce(&self.pubkey)?; - - // TODO: add domain, etc to this message. - let message = ssz_encode(&producer_nonce); + // TODO: add domain, etc to this message. Also ensure result matches `into_to_bytes32`. + let message = ssz_encode(&slot.epoch(self.spec.epoch_length)); match self.signer.sign_randao_reveal(&message) { None => return Ok(PollOutcome::SignerRejection(slot)), @@ -213,7 +208,7 @@ impl From for Error { #[cfg(test)] mod tests { - use super::test_utils::{TestBeaconNode, TestEpochMap, TestSigner}; + use super::test_utils::{EpochMap, LocalSigner, SimulatedBeaconNode}; use super::*; use slot_clock::TestingSlotClock; use types::{ @@ -232,19 +227,17 @@ mod tests { let spec = Arc::new(ChainSpec::foundation()); let slot_clock = Arc::new(TestingSlotClock::new(0)); - let beacon_node = Arc::new(TestBeaconNode::default()); - let signer = Arc::new(TestSigner::new(Keypair::random())); + let beacon_node = Arc::new(SimulatedBeaconNode::default()); + let signer = Arc::new(LocalSigner::new(Keypair::random())); - let mut epoch_map = TestEpochMap::new(spec.epoch_length); - let produce_slot = 100; - let produce_epoch = produce_slot / spec.epoch_length; + let mut epoch_map = EpochMap::new(spec.epoch_length); + let produce_slot = Slot::new(100); + let produce_epoch = produce_slot.epoch(spec.epoch_length); epoch_map.map.insert(produce_epoch, produce_slot); let epoch_map = Arc::new(epoch_map); - let keypair = Keypair::random(); let mut block_producer = BlockProducer::new( spec.clone(), - keypair.pk.clone(), epoch_map.clone(), slot_clock.clone(), beacon_node.clone(), @@ -254,42 +247,41 @@ mod tests { // Configure responses from the BeaconNode. beacon_node.set_next_produce_result(Ok(Some(BeaconBlock::random_for_test(&mut rng)))); beacon_node.set_next_publish_result(Ok(PublishOutcome::ValidBlock)); - beacon_node.set_next_nonce_result(Ok(0)); // One slot before production slot... - slot_clock.set_slot(produce_slot - 1); + slot_clock.set_slot(produce_slot.as_u64() - 1); assert_eq!( block_producer.poll(), Ok(PollOutcome::BlockProductionNotRequired(produce_slot - 1)) ); // On the produce slot... - slot_clock.set_slot(produce_slot); + slot_clock.set_slot(produce_slot.as_u64()); assert_eq!( block_producer.poll(), - Ok(PollOutcome::BlockProduced(produce_slot)) + Ok(PollOutcome::BlockProduced(produce_slot.into())) ); // Trying the same produce slot again... - slot_clock.set_slot(produce_slot); + slot_clock.set_slot(produce_slot.as_u64()); assert_eq!( block_producer.poll(), Ok(PollOutcome::SlotAlreadyProcessed(produce_slot)) ); // One slot after the produce slot... - slot_clock.set_slot(produce_slot + 1); + slot_clock.set_slot(produce_slot.as_u64() + 1); assert_eq!( block_producer.poll(), Ok(PollOutcome::BlockProductionNotRequired(produce_slot + 1)) ); // In an epoch without known duties... - let slot = (produce_epoch + 1) * spec.epoch_length; + let slot = (produce_epoch.as_u64() + 1) * spec.epoch_length; slot_clock.set_slot(slot); assert_eq!( block_producer.poll(), - Ok(PollOutcome::ProducerDutiesUnknown(slot)) + Ok(PollOutcome::ProducerDutiesUnknown(Slot::new(slot))) ); } } diff --git a/eth2/block_producer/src/test_utils/epoch_map.rs b/eth2/block_producer/src/test_utils/epoch_map.rs index 848ae252e0..e9ed9b68a3 100644 --- a/eth2/block_producer/src/test_utils/epoch_map.rs +++ b/eth2/block_producer/src/test_utils/epoch_map.rs @@ -1,12 +1,13 @@ use crate::{DutiesReader, DutiesReaderError}; use std::collections::HashMap; +use types::{Epoch, Slot}; -pub struct TestEpochMap { +pub struct EpochMap { epoch_length: u64, - pub map: HashMap, + pub map: HashMap, } -impl TestEpochMap { +impl EpochMap { pub fn new(epoch_length: u64) -> Self { Self { epoch_length, @@ -15,9 +16,9 @@ impl TestEpochMap { } } -impl DutiesReader for TestEpochMap { - fn is_block_production_slot(&self, slot: u64) -> Result { - let epoch = slot / self.epoch_length; +impl DutiesReader for EpochMap { + fn is_block_production_slot(&self, slot: Slot) -> Result { + let epoch = slot.epoch(self.epoch_length); match self.map.get(&epoch) { Some(s) if *s == slot => Ok(true), Some(s) if *s != slot => Ok(false), diff --git a/eth2/block_producer/src/test_utils/signer.rs b/eth2/block_producer/src/test_utils/local_signer.rs similarity index 85% rename from eth2/block_producer/src/test_utils/signer.rs rename to eth2/block_producer/src/test_utils/local_signer.rs index 62163f5b21..0ebefa29d6 100644 --- a/eth2/block_producer/src/test_utils/signer.rs +++ b/eth2/block_producer/src/test_utils/local_signer.rs @@ -3,13 +3,13 @@ use std::sync::RwLock; use types::{Keypair, Signature}; /// A test-only struct used to simulate a Beacon Node. -pub struct TestSigner { +pub struct LocalSigner { keypair: Keypair, should_sign: RwLock, } -impl TestSigner { - /// Produce a new TestSigner with signing enabled by default. +impl LocalSigner { + /// Produce a new LocalSigner with signing enabled by default. pub fn new(keypair: Keypair) -> Self { Self { keypair, @@ -24,7 +24,7 @@ impl TestSigner { } } -impl Signer for TestSigner { +impl Signer for LocalSigner { fn sign_block_proposal(&self, message: &[u8]) -> Option { Some(Signature::new(message, &self.keypair.sk)) } diff --git a/eth2/block_producer/src/test_utils/mod.rs b/eth2/block_producer/src/test_utils/mod.rs index 0dd384b125..481247dd00 100644 --- a/eth2/block_producer/src/test_utils/mod.rs +++ b/eth2/block_producer/src/test_utils/mod.rs @@ -1,7 +1,7 @@ -mod beacon_node; mod epoch_map; -mod signer; +mod local_signer; +mod simulated_beacon_node; -pub use self::beacon_node::TestBeaconNode; -pub use self::epoch_map::TestEpochMap; -pub use self::signer::TestSigner; +pub use self::epoch_map::EpochMap; +pub use self::local_signer::LocalSigner; +pub use self::simulated_beacon_node::SimulatedBeaconNode; diff --git a/eth2/block_producer/src/test_utils/beacon_node.rs b/eth2/block_producer/src/test_utils/simulated_beacon_node.rs similarity index 57% rename from eth2/block_producer/src/test_utils/beacon_node.rs rename to eth2/block_producer/src/test_utils/simulated_beacon_node.rs index f132ce6ec2..c0a12c1acf 100644 --- a/eth2/block_producer/src/test_utils/beacon_node.rs +++ b/eth2/block_producer/src/test_utils/simulated_beacon_node.rs @@ -1,30 +1,21 @@ use crate::traits::{BeaconNode, BeaconNodeError, PublishOutcome}; use std::sync::RwLock; -use types::{BeaconBlock, PublicKey, Signature}; +use types::{BeaconBlock, Signature, Slot}; -type NonceResult = Result; type ProduceResult = Result, BeaconNodeError>; type PublishResult = Result; /// A test-only struct used to simulate a Beacon Node. #[derive(Default)] -pub struct TestBeaconNode { - pub nonce_input: RwLock>, - pub nonce_result: RwLock>, - - pub produce_input: RwLock>, +pub struct SimulatedBeaconNode { + pub produce_input: RwLock>, pub produce_result: RwLock>, pub publish_input: RwLock>, pub publish_result: RwLock>, } -impl TestBeaconNode { - /// Set the result to be returned when `produce_beacon_block` is called. - pub fn set_next_nonce_result(&self, result: NonceResult) { - *self.nonce_result.write().unwrap() = Some(result); - } - +impl SimulatedBeaconNode { /// Set the result to be returned when `produce_beacon_block` is called. pub fn set_next_produce_result(&self, result: ProduceResult) { *self.produce_result.write().unwrap() = Some(result); @@ -36,21 +27,13 @@ impl TestBeaconNode { } } -impl BeaconNode for TestBeaconNode { - fn proposer_nonce(&self, pubkey: &PublicKey) -> NonceResult { - *self.nonce_input.write().unwrap() = Some(pubkey.clone()); - match *self.nonce_result.read().unwrap() { - Some(ref r) => r.clone(), - None => panic!("TestBeaconNode: nonce_result == None"), - } - } - +impl BeaconNode for SimulatedBeaconNode { /// Returns the value specified by the `set_next_produce_result`. - fn produce_beacon_block(&self, slot: u64, randao_reveal: &Signature) -> ProduceResult { + fn produce_beacon_block(&self, slot: Slot, randao_reveal: &Signature) -> ProduceResult { *self.produce_input.write().unwrap() = Some((slot, randao_reveal.clone())); match *self.produce_result.read().unwrap() { Some(ref r) => r.clone(), - None => panic!("TestBeaconNode: produce_result == None"), + None => panic!("SimulatedBeaconNode: produce_result == None"), } } @@ -59,7 +42,7 @@ impl BeaconNode for TestBeaconNode { *self.publish_input.write().unwrap() = Some(block); match *self.publish_result.read().unwrap() { Some(ref r) => r.clone(), - None => panic!("TestBeaconNode: publish_result == None"), + None => panic!("SimulatedBeaconNode: publish_result == None"), } } } diff --git a/eth2/block_producer/src/traits.rs b/eth2/block_producer/src/traits.rs index b09b81e672..5eb27bce71 100644 --- a/eth2/block_producer/src/traits.rs +++ b/eth2/block_producer/src/traits.rs @@ -1,4 +1,4 @@ -use types::{BeaconBlock, PublicKey, Signature}; +use types::{BeaconBlock, Signature, Slot}; #[derive(Debug, PartialEq, Clone)] pub enum BeaconNodeError { @@ -14,15 +14,12 @@ pub enum PublishOutcome { /// Defines the methods required to produce and publish blocks on a Beacon Node. pub trait BeaconNode: Send + Sync { - /// Requests the proposer nonce (presently named `proposer_slots`). - fn proposer_nonce(&self, pubkey: &PublicKey) -> Result; - /// Request that the node produces a block. /// /// Returns Ok(None) if the Beacon Node is unable to produce at the given slot. fn produce_beacon_block( &self, - slot: u64, + slot: Slot, randao_reveal: &Signature, ) -> Result, BeaconNodeError>; @@ -42,7 +39,7 @@ pub enum DutiesReaderError { /// Informs a validator of their duties (e.g., block production). pub trait DutiesReader: Send + Sync { - fn is_block_production_slot(&self, slot: u64) -> Result; + fn is_block_production_slot(&self, slot: Slot) -> Result; } /// Signs message using an internally-maintained private key. diff --git a/eth2/fork_choice/src/lib.rs b/eth2/fork_choice/src/lib.rs index 19ddcb04c4..eaac8d80f4 100644 --- a/eth2/fork_choice/src/lib.rs +++ b/eth2/fork_choice/src/lib.rs @@ -1,3 +1,23 @@ +// Copyright 2019 Sigma Prime Pty Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + //! This crate stores the various implementations of fork-choice rules that can be used for the //! beacon blockchain. //! @@ -7,17 +27,21 @@ //! The current implementations are: //! - [`longest-chain`]: Simplistic longest-chain fork choice - primarily for testing, **not for //! production**. -//! - [`basic_lmd_ghost`]: This is a simple and very inefficient implementation given in the ethereum 2.0 +//! - [`slow_lmd_ghost`]: This is a simple and very inefficient implementation given in the ethereum 2.0 //! specifications (https://github.com/ethereum/eth2.0-specs/blob/v0.1/specs/core/0_beacon-chain.md#get_block_root). //! - [`optimised_lmd_ghost`]: This is an optimised version of the naive implementation as proposed //! by Vitalik. The reference implementation can be found at: https://github.com/ethereum/research/blob/master/ghost/ghost.py //! - [`protolambda_lmd_ghost`]: Another optimised version of LMD-GHOST designed by @protolambda. //! The go implementation can be found here: https://github.com/protolambda/lmd-ghost. //! -//! [`basic_lmd_ghost`]: struct.BasicLmdGhost.html +//! [`slow_lmd_ghost`]: struct.SlowLmdGhost.html //! [`optimised_lmd_ghost`]: struct.OptimisedLmdGhost.html //! [`protolambda_lmd_ghost`]: struct.ProtolambdaLmdGhost.html +extern crate db; +extern crate ssz; +extern crate types; + pub mod longest_chain; pub mod optimised_lmd_ghost; pub mod protolambda_lmd_ghost; diff --git a/eth2/fork_choice/src/longest_chain.rs b/eth2/fork_choice/src/longest_chain.rs index 478bc8ac1f..277d6b9509 100644 --- a/eth2/fork_choice/src/longest_chain.rs +++ b/eth2/fork_choice/src/longest_chain.rs @@ -1,12 +1,8 @@ -extern crate db; -extern crate ssz; -extern crate types; - use db::stores::BeaconBlockStore; use db::{ClientDB, DBError}; use ssz::{Decodable, DecodeError}; use std::sync::Arc; -use types::{BeaconBlock, Hash256}; +use types::{BeaconBlock, Hash256, Slot}; pub enum ForkChoiceError { BadSszInDatabase, @@ -37,7 +33,7 @@ where /* * Loop through all the head blocks and find the highest slot. */ - let highest_slot: Option = None; + let highest_slot: Option = None; for (_, block) in &head_blocks { let slot = block.slot; diff --git a/eth2/fork_choice/src/optimised_lmd_ghost.rs b/eth2/fork_choice/src/optimised_lmd_ghost.rs index 67faaca1c2..0d59befefd 100644 --- a/eth2/fork_choice/src/optimised_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimised_lmd_ghost.rs @@ -1,3 +1,23 @@ +// Copyright 2019 Sigma Prime Pty Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + extern crate byteorder; extern crate fast_math; use crate::{ForkChoice, ForkChoiceError}; @@ -11,6 +31,7 @@ use std::collections::HashMap; use std::sync::Arc; use types::{ readers::{BeaconBlockReader, BeaconStateReader}, + slot_epoch_height::{Height, Slot}, validator_registry::get_active_validator_indices, BeaconBlock, Hash256, }; @@ -22,6 +43,7 @@ use types::{ const GENESIS_SLOT: u64 = 0; const FORK_CHOICE_BALANCE_INCREMENT: u64 = 1e9 as u64; const MAX_DEPOSIT_AMOUNT: u64 = 32e9 as u64; +const EPOCH_LENGTH: u64 = 64; /// The optimised LMD-GHOST fork choice rule. /// NOTE: This uses u32 to represent difference between block heights. Thus this is only @@ -55,7 +77,7 @@ pub struct OptimisedLMDGhost { block_store: Arc>, /// State storage access. state_store: Arc>, - max_known_height: u64, + max_known_height: Height, } impl OptimisedLMDGhost @@ -71,7 +93,7 @@ where ancestors: vec![HashMap::new(); 16], latest_attestation_targets: HashMap::new(), children: HashMap::new(), - max_known_height: 0, + max_known_height: Height::new(0), block_store, state_store, } @@ -82,7 +104,7 @@ where pub fn get_latest_votes( &self, state_root: &Hash256, - block_slot: u64, + block_slot: Slot, ) -> Result, ForkChoiceError> { // get latest votes // Note: Votes are weighted by min(balance, MAX_DEPOSIT_AMOUNT) // @@ -97,8 +119,10 @@ where .into_beacon_state() .ok_or_else(|| ForkChoiceError::IncorrectBeaconState(*state_root))?; - let active_validator_indices = - get_active_validator_indices(¤t_state.validator_registry, block_slot); + let active_validator_indices = get_active_validator_indices( + ¤t_state.validator_registry, + block_slot.epoch(EPOCH_LENGTH), + ); for index in active_validator_indices { let balance = @@ -115,7 +139,7 @@ where } /// Gets the ancestor at a given height `at_height` of a block specified by `block_hash`. - fn get_ancestor(&mut self, block_hash: Hash256, at_height: u32) -> Option { + fn get_ancestor(&mut self, block_hash: Hash256, at_height: Height) -> Option { // return None if we can't get the block from the db. let block_height = { let block_slot = self @@ -126,7 +150,7 @@ where .into_beacon_block()? .slot; - (block_slot - GENESIS_SLOT) as u32 + block_slot.height(Slot::from(GENESIS_SLOT)) }; // verify we haven't exceeded the block height @@ -138,7 +162,7 @@ where } } // check if the result is stored in our cache - let cache_key = CacheKey::new(&block_hash, at_height); + let cache_key = CacheKey::new(&block_hash, at_height.as_u32()); if let Some(ancestor) = self.cache.get(&cache_key) { return Some(*ancestor); } @@ -147,7 +171,7 @@ where if let Some(ancestor) = { let ancestor_lookup = self.ancestors - [log2_int((block_height - at_height - 1) as u32) as usize] + [log2_int((block_height - at_height - 1u64).as_u32()) as usize] .get(&block_hash) //TODO: Panic if we can't lookup and fork choice fails .expect("All blocks should be added to the ancestor log lookup table"); @@ -165,7 +189,7 @@ where fn get_clear_winner( &mut self, latest_votes: &HashMap, - height: u64, + block_height: Height, ) -> Option { // map of vote counts for every hash at this height let mut current_votes: HashMap = HashMap::new(); @@ -174,7 +198,7 @@ where // loop through the latest votes and count all votes // these have already been weighted by balance for (hash, votes) in latest_votes.iter() { - if let Some(ancestor) = self.get_ancestor(*hash, height as u32) { + if let Some(ancestor) = self.get_ancestor(*hash, block_height) { let current_vote_value = current_votes.get(&ancestor).unwrap_or_else(|| &0); current_votes.insert(ancestor, current_vote_value + *votes); total_vote_count += votes; @@ -246,7 +270,7 @@ impl ForkChoice for OptimisedLMDGhost { .get_reader(&block.parent_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.parent_root))? .slot() - - GENESIS_SLOT; + .height(Slot::from(GENESIS_SLOT)); let parent_hash = &block.parent_root; @@ -316,7 +340,7 @@ impl ForkChoice for OptimisedLMDGhost { .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?; let block_slot = block.slot(); - let block_height = block_slot - GENESIS_SLOT; + let block_height = block_slot.height(Slot::from(GENESIS_SLOT)); let state_root = block.state_root(); let mut current_head = *justified_block_start; @@ -324,8 +348,7 @@ impl ForkChoice for OptimisedLMDGhost { let mut latest_votes = self.get_latest_votes(&state_root, block_slot)?; // remove any votes that don't relate to our current head. - latest_votes - .retain(|hash, _| self.get_ancestor(*hash, block_height as u32) == Some(current_head)); + latest_votes.retain(|hash, _| self.get_ancestor(*hash, block_height) == Some(current_head)); // begin searching for the head loop { @@ -337,7 +360,8 @@ impl ForkChoice for OptimisedLMDGhost { // logarithmic lookup blocks to see if there are obvious winners, if so, // progress to the next iteration. - let mut step = power_of_2_below(self.max_known_height as u32 - block_height as u32) / 2; + let mut step = + power_of_2_below(self.max_known_height.saturating_sub(block_height).as_u32()) / 2; while step > 0 { if let Some(clear_winner) = self.get_clear_winner( &latest_votes, @@ -359,7 +383,7 @@ impl ForkChoice for OptimisedLMDGhost { let mut child_votes = HashMap::new(); for (voted_hash, vote) in latest_votes.iter() { // if the latest votes correspond to a child - if let Some(child) = self.get_ancestor(*voted_hash, (block_height + 1) as u32) { + if let Some(child) = self.get_ancestor(*voted_hash, block_height + 1) { // add up the votes for each child *child_votes.entry(child).or_insert_with(|| 0) += vote; } @@ -378,13 +402,12 @@ impl ForkChoice for OptimisedLMDGhost { .get_reader(¤t_head)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))? .slot() - - GENESIS_SLOT; + .height(Slot::from(GENESIS_SLOT)); // prune the latest votes for votes that are not part of current chosen chain // more specifically, only keep votes that have head as an ancestor - latest_votes.retain(|hash, _| { - self.get_ancestor(*hash, block_height as u32) == Some(current_head) - }); + latest_votes + .retain(|hash, _| self.get_ancestor(*hash, block_height) == Some(current_head)); } } } diff --git a/eth2/fork_choice/src/slow_lmd_ghost.rs b/eth2/fork_choice/src/slow_lmd_ghost.rs index 3fe90f1825..aba87aa702 100644 --- a/eth2/fork_choice/src/slow_lmd_ghost.rs +++ b/eth2/fork_choice/src/slow_lmd_ghost.rs @@ -1,3 +1,23 @@ +// Copyright 2019 Sigma Prime Pty Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + extern crate db; use crate::{ForkChoice, ForkChoiceError}; @@ -9,6 +29,7 @@ use std::collections::HashMap; use std::sync::Arc; use types::{ readers::{BeaconBlockReader, BeaconStateReader}, + slot_epoch_height::Slot, validator_registry::get_active_validator_indices, BeaconBlock, Hash256, }; @@ -19,6 +40,7 @@ use types::{ const GENESIS_SLOT: u64 = 0; const FORK_CHOICE_BALANCE_INCREMENT: u64 = 1e9 as u64; const MAX_DEPOSIT_AMOUNT: u64 = 32e9 as u64; +const EPOCH_LENGTH: u64 = 64; pub struct SlowLMDGhost { /// The latest attestation targets as a map of validator index to block hash. @@ -50,7 +72,7 @@ where pub fn get_latest_votes( &self, state_root: &Hash256, - block_slot: u64, + block_slot: Slot, ) -> Result, ForkChoiceError> { // get latest votes // Note: Votes are weighted by min(balance, MAX_DEPOSIT_AMOUNT) // @@ -65,8 +87,10 @@ where .into_beacon_state() .ok_or_else(|| ForkChoiceError::IncorrectBeaconState(*state_root))?; - let active_validator_indices = - get_active_validator_indices(¤t_state.validator_registry, block_slot); + let active_validator_indices = get_active_validator_indices( + ¤t_state.validator_registry, + block_slot.epoch(EPOCH_LENGTH), + ); for index in active_validator_indices { let balance = @@ -148,7 +172,7 @@ impl ForkChoice for SlowLMDGhost { .get_reader(&target_block_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? .slot() - - GENESIS_SLOT; + .height(Slot::from(GENESIS_SLOT)); // get the height of the past target block let past_block_height = self @@ -156,7 +180,7 @@ impl ForkChoice for SlowLMDGhost { .get_reader(&attestation_target)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? .slot() - - GENESIS_SLOT; + .height(Slot::from(GENESIS_SLOT)); // update the attestation only if the new target is higher if past_block_height < block_height { *attestation_target = *target_block_root; diff --git a/eth2/genesis/Cargo.toml b/eth2/genesis/Cargo.toml deleted file mode 100644 index 09e5d1e88d..0000000000 --- a/eth2/genesis/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "genesis" -version = "0.1.0" -authors = ["Paul Hauner "] -edition = "2018" - -[dependencies] -bls = { path = "../utils/bls" } -ssz = { path = "../utils/ssz" } -types = { path = "../types" } -validator_induction = { path = "../validator_induction" } diff --git a/eth2/genesis/src/beacon_block.rs b/eth2/genesis/src/beacon_block.rs deleted file mode 100644 index 8b78f9e2d1..0000000000 --- a/eth2/genesis/src/beacon_block.rs +++ /dev/null @@ -1,93 +0,0 @@ -use types::{BeaconBlock, BeaconBlockBody, ChainSpec, Eth1Data, Hash256}; - -/// Generate a genesis BeaconBlock. -pub fn genesis_beacon_block(state_root: Hash256, spec: &ChainSpec) -> BeaconBlock { - BeaconBlock { - slot: spec.genesis_slot, - parent_root: spec.zero_hash, - state_root, - randao_reveal: spec.empty_signature.clone(), - eth1_data: Eth1Data { - deposit_root: spec.zero_hash, - block_hash: spec.zero_hash, - }, - signature: spec.empty_signature.clone(), - body: BeaconBlockBody { - proposer_slashings: vec![], - casper_slashings: vec![], - attestations: vec![], - custody_reseeds: vec![], - custody_challenges: vec![], - custody_responses: vec![], - deposits: vec![], - exits: vec![], - }, - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bls::Signature; - - #[test] - fn test_state_root() { - let spec = ChainSpec::foundation(); - let state_root = Hash256::from("cats".as_bytes()); - - let block = genesis_beacon_block(state_root, &spec); - - assert_eq!(block.state_root, state_root); - } - - #[test] - fn test_zero_items() { - let spec = ChainSpec::foundation(); - - let state_root = Hash256::zero(); - - let genesis_block = genesis_beacon_block(state_root, &spec); - - assert!(genesis_block.slot == 0); - assert!(genesis_block.parent_root.is_zero()); - assert_eq!(genesis_block.randao_reveal, Signature::empty_signature()); - assert!(genesis_block.eth1_data.deposit_root.is_zero()); - assert!(genesis_block.eth1_data.block_hash.is_zero()); - } - - #[test] - fn test_beacon_body() { - let spec = ChainSpec::foundation(); - - let state_root = Hash256::zero(); - - let genesis_block = genesis_beacon_block(state_root, &spec); - - // Custody items are not being implemented until phase 1 so tests to be added later - - assert!(genesis_block.body.proposer_slashings.is_empty()); - assert!(genesis_block.body.casper_slashings.is_empty()); - assert!(genesis_block.body.attestations.is_empty()); - assert!(genesis_block.body.deposits.is_empty()); - assert!(genesis_block.body.exits.is_empty()); - } - - #[test] - fn test_signature() { - let spec = ChainSpec::foundation(); - - let state_root = Hash256::zero(); - - let genesis_block = genesis_beacon_block(state_root, &spec); - - // Signature should consist of [bytes48(0), bytes48(0)] - // Note this is implemented using Apache Milagro BLS which requires one extra byte -> 97bytes - let raw_sig = genesis_block.signature.as_raw(); - let raw_sig_bytes = raw_sig.as_bytes(); - - for item in raw_sig_bytes.iter() { - assert!(*item == 0); - } - assert_eq!(genesis_block.signature, Signature::empty_signature()); - } -} diff --git a/eth2/genesis/src/beacon_state.rs b/eth2/genesis/src/beacon_state.rs deleted file mode 100644 index 4ccb32e54a..0000000000 --- a/eth2/genesis/src/beacon_state.rs +++ /dev/null @@ -1,194 +0,0 @@ -use types::{BeaconState, ChainSpec, Crosslink, Fork}; - -pub fn genesis_beacon_state(spec: &ChainSpec) -> BeaconState { - let initial_crosslink = Crosslink { - slot: spec.genesis_slot, - shard_block_root: spec.zero_hash, - }; - - BeaconState { - /* - * Misc - */ - slot: spec.genesis_slot, - genesis_time: spec.genesis_time, - fork_data: Fork { - pre_fork_version: spec.genesis_fork_version, - post_fork_version: spec.genesis_fork_version, - fork_slot: spec.genesis_slot, - }, - /* - * Validator registry - */ - validator_registry: spec.initial_validators.clone(), - validator_balances: spec.initial_balances.clone(), - validator_registry_update_slot: spec.genesis_slot, - validator_registry_exit_count: 0, - validator_registry_delta_chain_tip: spec.zero_hash, - /* - * Randomness and committees - */ - latest_randao_mixes: vec![spec.zero_hash; spec.latest_randao_mixes_length as usize], - latest_vdf_outputs: vec![ - spec.zero_hash; - (spec.latest_randao_mixes_length / spec.epoch_length) as usize - ], - previous_epoch_start_shard: spec.genesis_start_shard, - current_epoch_start_shard: spec.genesis_start_shard, - previous_epoch_calculation_slot: spec.genesis_slot, - current_epoch_calculation_slot: spec.genesis_slot, - previous_epoch_seed: spec.zero_hash, - current_epoch_seed: spec.zero_hash, - /* - * Custody challenges - */ - custody_challenges: vec![], - /* - * Finality - */ - previous_justified_slot: spec.genesis_slot, - justified_slot: spec.genesis_slot, - justification_bitfield: 0, - finalized_slot: spec.genesis_slot, - /* - * Recent state - */ - latest_crosslinks: vec![initial_crosslink; spec.shard_count as usize], - latest_block_roots: vec![spec.zero_hash; spec.latest_block_roots_length as usize], - latest_penalized_balances: vec![0; spec.latest_penalized_exit_length as usize], - latest_attestations: vec![], - batched_block_roots: vec![], - /* - * PoW receipt root - */ - latest_eth1_data: spec.intial_eth1_data.clone(), - eth1_data_votes: vec![], - } -} - -#[cfg(test)] -mod tests { - use super::*; - use types::Hash256; - - #[test] - fn test_genesis_state() { - let spec = ChainSpec::foundation(); - - let state = genesis_beacon_state(&spec); - - assert_eq!( - state.validator_registry.len(), - spec.initial_validators.len() - ); - } - - #[test] - fn test_genesis_state_misc() { - let spec = ChainSpec::foundation(); - - let state = genesis_beacon_state(&spec); - - assert_eq!(state.slot, 0); - assert_eq!(state.genesis_time, spec.genesis_time); - assert_eq!(state.fork_data.pre_fork_version, 0); - assert_eq!(state.fork_data.post_fork_version, 0); - assert_eq!(state.fork_data.fork_slot, 0); - } - - #[test] - fn test_genesis_state_validators() { - let spec = ChainSpec::foundation(); - - let state = genesis_beacon_state(&spec); - - assert_eq!(state.validator_registry, spec.initial_validators); - assert_eq!(state.validator_balances, spec.initial_balances); - assert!(state.validator_registry_update_slot == 0); - assert!(state.validator_registry_exit_count == 0); - assert_eq!(state.validator_registry_delta_chain_tip, Hash256::zero()); - } - - #[test] - fn test_genesis_state_randomness_committees() { - let spec = ChainSpec::foundation(); - - let state = genesis_beacon_state(&spec); - - // Array of size 8,192 each being zero_hash - assert_eq!(state.latest_randao_mixes.len(), 8_192); - for item in state.latest_randao_mixes.iter() { - assert_eq!(*item, Hash256::zero()); - } - - // Array of size 8,192 each being a zero hash - assert_eq!(state.latest_vdf_outputs.len(), (8_192 / 64)); - for item in state.latest_vdf_outputs.iter() { - assert_eq!(*item, Hash256::zero()); - } - - // TODO: Check shard and committee shuffling requires solving issue: - // https://github.com/sigp/lighthouse/issues/151 - - // initial_shuffling = get_shuffling(Hash256::zero(), &state.validator_registry, 0, 0) - // initial_shuffling = initial_shuffling.append(initial_shuffling.clone()); - } - - // Custody not implemented until Phase 1 - #[test] - fn test_genesis_state_custody() {} - - #[test] - fn test_genesis_state_finanilty() { - let spec = ChainSpec::foundation(); - - let state = genesis_beacon_state(&spec); - - assert_eq!(state.previous_justified_slot, 0); - assert_eq!(state.justified_slot, 0); - assert_eq!(state.justification_bitfield, 0); - assert_eq!(state.finalized_slot, 0); - } - - #[test] - fn test_genesis_state_recent_state() { - let spec = ChainSpec::foundation(); - - let state = genesis_beacon_state(&spec); - - // Test latest_crosslinks - assert_eq!(state.latest_crosslinks.len(), 1_024); - for link in state.latest_crosslinks.iter() { - assert_eq!(link.slot, 0); - assert_eq!(link.shard_block_root, Hash256::zero()); - } - - // Test latest_block_roots - assert_eq!(state.latest_block_roots.len(), 8_192); - for block in state.latest_block_roots.iter() { - assert_eq!(*block, Hash256::zero()); - } - - // Test latest_penalized_balances - assert_eq!(state.latest_penalized_balances.len(), 8_192); - for item in state.latest_penalized_balances.iter() { - assert!(*item == 0); - } - - // Test latest_attestations - assert!(state.latest_attestations.is_empty()); - - // batched_block_roots - assert!(state.batched_block_roots.is_empty()); - } - - #[test] - fn test_genesis_state_deposit_root() { - let spec = ChainSpec::foundation(); - - let state = genesis_beacon_state(&spec); - - assert_eq!(&state.latest_eth1_data, &spec.intial_eth1_data); - assert!(state.eth1_data_votes.is_empty()); - } -} diff --git a/eth2/genesis/src/lib.rs b/eth2/genesis/src/lib.rs deleted file mode 100644 index 295bdbd3c5..0000000000 --- a/eth2/genesis/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod beacon_block; -mod beacon_state; - -pub use crate::beacon_block::genesis_beacon_block; -pub use crate::beacon_state::genesis_beacon_state; diff --git a/eth2/validator_induction/Cargo.toml b/eth2/state_processing/Cargo.toml similarity index 63% rename from eth2/validator_induction/Cargo.toml rename to eth2/state_processing/Cargo.toml index 5907014df7..683475f47c 100644 --- a/eth2/validator_induction/Cargo.toml +++ b/eth2/state_processing/Cargo.toml @@ -1,10 +1,13 @@ [package] -name = "validator_induction" +name = "state_processing" version = "0.1.0" authors = ["Paul Hauner "] edition = "2018" [dependencies] -bls = { path = "../utils/bls" } hashing = { path = "../utils/hashing" } +integer-sqrt = "0.1" +log = "0.4" +ssz = { path = "../utils/ssz" } types = { path = "../types" } +rayon = "1.0" diff --git a/eth2/state_processing/src/block_processable.rs b/eth2/state_processing/src/block_processable.rs new file mode 100644 index 0000000000..f043a723d6 --- /dev/null +++ b/eth2/state_processing/src/block_processable.rs @@ -0,0 +1,403 @@ +use crate::SlotProcessingError; +use hashing::hash; +use log::debug; +use ssz::{ssz_encode, TreeHash}; +use types::{ + beacon_state::{AttestationValidationError, CommitteesError}, + AggregatePublicKey, Attestation, BeaconBlock, BeaconState, ChainSpec, Crosslink, Epoch, Exit, + Fork, Hash256, PendingAttestation, PublicKey, Signature, +}; + +// TODO: define elsehwere. +const DOMAIN_PROPOSAL: u64 = 2; +const DOMAIN_EXIT: u64 = 3; +const DOMAIN_RANDAO: u64 = 4; +const PHASE_0_CUSTODY_BIT: bool = false; +const DOMAIN_ATTESTATION: u64 = 1; + +#[derive(Debug, PartialEq)] +pub enum Error { + DBError(String), + StateAlreadyTransitioned, + PresentSlotIsNone, + UnableToDecodeBlock, + MissingParentState(Hash256), + InvalidParentState(Hash256), + MissingBeaconBlock(Hash256), + InvalidBeaconBlock(Hash256), + MissingParentBlock(Hash256), + NoBlockProducer, + StateSlotMismatch, + BadBlockSignature, + BadRandaoSignature, + MaxProposerSlashingsExceeded, + BadProposerSlashing, + MaxAttestationsExceeded, + InvalidAttestation(AttestationValidationError), + NoBlockRoot, + MaxDepositsExceeded, + MaxExitsExceeded, + BadExit, + BadCustodyReseeds, + BadCustodyChallenges, + BadCustodyResponses, + CommitteesError(CommitteesError), + SlotProcessingError(SlotProcessingError), +} + +macro_rules! ensure { + ($condition: expr, $result: expr) => { + if !$condition { + return Err($result); + } + }; +} + +pub trait BlockProcessable { + fn per_block_processing(&mut self, block: &BeaconBlock, spec: &ChainSpec) -> Result<(), Error>; + fn per_block_processing_without_verifying_block_signature( + &mut self, + block: &BeaconBlock, + spec: &ChainSpec, + ) -> Result<(), Error>; +} + +impl BlockProcessable for BeaconState { + fn per_block_processing(&mut self, block: &BeaconBlock, spec: &ChainSpec) -> Result<(), Error> { + per_block_processing_signature_optional(self, block, true, spec) + } + + fn per_block_processing_without_verifying_block_signature( + &mut self, + block: &BeaconBlock, + spec: &ChainSpec, + ) -> Result<(), Error> { + per_block_processing_signature_optional(self, block, false, spec) + } +} + +fn per_block_processing_signature_optional( + state: &mut BeaconState, + block: &BeaconBlock, + verify_block_signature: bool, + spec: &ChainSpec, +) -> Result<(), Error> { + ensure!(block.slot == state.slot, Error::StateSlotMismatch); + + /* + * Proposer Signature + */ + let block_proposer_index = state + .get_beacon_proposer_index(block.slot, spec) + .map_err(|_| Error::NoBlockProducer)?; + let block_proposer = &state.validator_registry[block_proposer_index]; + + if verify_block_signature { + ensure!( + bls_verify( + &block_proposer.pubkey, + &block.proposal_root(spec)[..], + &block.signature, + get_domain(&state.fork, state.current_epoch(spec), DOMAIN_PROPOSAL) + ), + Error::BadBlockSignature + ); + } + + /* + * RANDAO + */ + ensure!( + bls_verify( + &block_proposer.pubkey, + &ssz_encode(&state.current_epoch(spec)), + &block.randao_reveal, + get_domain(&state.fork, state.current_epoch(spec), DOMAIN_RANDAO) + ), + Error::BadRandaoSignature + ); + + // TODO: check this is correct. + let new_mix = { + let mut mix = state.latest_randao_mixes + [state.slot.as_usize() % spec.latest_randao_mixes_length] + .to_vec(); + mix.append(&mut ssz_encode(&block.randao_reveal)); + Hash256::from(&hash(&mix)[..]) + }; + + state.latest_randao_mixes[state.slot.as_usize() % spec.latest_randao_mixes_length] = new_mix; + + /* + * Eth1 data + */ + // TODO: Eth1 data processing. + + /* + * Proposer slashings + */ + ensure!( + block.body.proposer_slashings.len() as u64 <= spec.max_proposer_slashings, + Error::MaxProposerSlashingsExceeded + ); + for proposer_slashing in &block.body.proposer_slashings { + let proposer = state + .validator_registry + .get(proposer_slashing.proposer_index as usize) + .ok_or(Error::BadProposerSlashing)?; + ensure!( + proposer_slashing.proposal_data_1.slot == proposer_slashing.proposal_data_2.slot, + Error::BadProposerSlashing + ); + ensure!( + proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard, + Error::BadProposerSlashing + ); + ensure!( + proposer_slashing.proposal_data_1.block_root + != proposer_slashing.proposal_data_2.block_root, + Error::BadProposerSlashing + ); + ensure!( + proposer.penalized_epoch > state.current_epoch(spec), + Error::BadProposerSlashing + ); + ensure!( + bls_verify( + &proposer.pubkey, + &proposer_slashing.proposal_data_1.hash_tree_root(), + &proposer_slashing.proposal_signature_1, + get_domain( + &state.fork, + proposer_slashing + .proposal_data_1 + .slot + .epoch(spec.epoch_length), + DOMAIN_PROPOSAL + ) + ), + Error::BadProposerSlashing + ); + ensure!( + bls_verify( + &proposer.pubkey, + &proposer_slashing.proposal_data_2.hash_tree_root(), + &proposer_slashing.proposal_signature_2, + get_domain( + &state.fork, + proposer_slashing + .proposal_data_2 + .slot + .epoch(spec.epoch_length), + DOMAIN_PROPOSAL + ) + ), + Error::BadProposerSlashing + ); + state.penalize_validator(proposer_slashing.proposer_index as usize, spec)?; + } + + /* + * Attestations + */ + ensure!( + block.body.attestations.len() as u64 <= spec.max_attestations, + Error::MaxAttestationsExceeded + ); + + for attestation in &block.body.attestations { + validate_attestation(&state, attestation, spec)?; + + let pending_attestation = PendingAttestation { + data: attestation.data.clone(), + aggregation_bitfield: attestation.aggregation_bitfield.clone(), + custody_bitfield: attestation.custody_bitfield.clone(), + inclusion_slot: state.slot, + }; + state.latest_attestations.push(pending_attestation); + } + + debug!( + "{} attestations verified & processed.", + block.body.attestations.len() + ); + + /* + * Deposits + */ + ensure!( + block.body.deposits.len() as u64 <= spec.max_deposits, + Error::MaxDepositsExceeded + ); + + // TODO: process deposits. + + /* + * Exits + */ + ensure!( + block.body.exits.len() as u64 <= spec.max_exits, + Error::MaxExitsExceeded + ); + + for exit in &block.body.exits { + let validator = state + .validator_registry + .get(exit.validator_index as usize) + .ok_or(Error::BadExit)?; + ensure!( + validator.exit_epoch + > state.get_entry_exit_effect_epoch(state.current_epoch(spec), spec), + Error::BadExit + ); + ensure!(state.current_epoch(spec) >= exit.epoch, Error::BadExit); + let exit_message = { + let exit_struct = Exit { + epoch: exit.epoch, + validator_index: exit.validator_index, + signature: spec.empty_signature.clone(), + }; + exit_struct.hash_tree_root() + }; + ensure!( + bls_verify( + &validator.pubkey, + &exit_message, + &exit.signature, + get_domain(&state.fork, exit.epoch, DOMAIN_EXIT) + ), + Error::BadProposerSlashing + ); + state.initiate_validator_exit(exit.validator_index as usize); + } + + debug!("State transition complete."); + + Ok(()) +} + +pub fn validate_attestation( + state: &BeaconState, + attestation: &Attestation, + spec: &ChainSpec, +) -> Result<(), AttestationValidationError> { + validate_attestation_signature_optional(state, attestation, spec, true) +} + +pub fn validate_attestation_without_signature( + state: &BeaconState, + attestation: &Attestation, + spec: &ChainSpec, +) -> Result<(), AttestationValidationError> { + validate_attestation_signature_optional(state, attestation, spec, false) +} + +fn validate_attestation_signature_optional( + state: &BeaconState, + attestation: &Attestation, + spec: &ChainSpec, + verify_signature: bool, +) -> Result<(), AttestationValidationError> { + ensure!( + attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot, + AttestationValidationError::IncludedTooEarly + ); + ensure!( + attestation.data.slot + spec.epoch_length >= state.slot, + AttestationValidationError::IncludedTooLate + ); + if attestation.data.slot >= state.current_epoch_start_slot(spec) { + ensure!( + attestation.data.justified_epoch == state.justified_epoch, + AttestationValidationError::WrongJustifiedSlot + ); + } else { + ensure!( + attestation.data.justified_epoch == state.previous_justified_epoch, + AttestationValidationError::WrongJustifiedSlot + ); + } + ensure!( + attestation.data.justified_block_root + == *state + .get_block_root( + attestation + .data + .justified_epoch + .start_slot(spec.epoch_length), + &spec + ) + .ok_or(AttestationValidationError::NoBlockRoot)?, + AttestationValidationError::WrongJustifiedRoot + ); + let potential_crosslink = Crosslink { + shard_block_root: attestation.data.shard_block_root, + epoch: attestation.data.slot.epoch(spec.epoch_length), + }; + ensure!( + (attestation.data.latest_crosslink + == state.latest_crosslinks[attestation.data.shard as usize]) + | (attestation.data.latest_crosslink == potential_crosslink), + AttestationValidationError::BadLatestCrosslinkRoot + ); + if verify_signature { + let participants = state.get_attestation_participants( + &attestation.data, + &attestation.aggregation_bitfield, + spec, + )?; + let mut group_public_key = AggregatePublicKey::new(); + for participant in participants { + group_public_key.add( + state.validator_registry[participant as usize] + .pubkey + .as_raw(), + ) + } + ensure!( + attestation.verify_signature( + &group_public_key, + PHASE_0_CUSTODY_BIT, + get_domain( + &state.fork, + attestation.data.slot.epoch(spec.epoch_length), + DOMAIN_ATTESTATION, + ) + ), + AttestationValidationError::BadSignature + ); + } + ensure!( + attestation.data.shard_block_root == spec.zero_hash, + AttestationValidationError::ShardBlockRootNotZero + ); + Ok(()) +} + +fn get_domain(_fork: &Fork, _epoch: Epoch, _domain_type: u64) -> u64 { + // TODO: stubbed out. + 0 +} + +fn bls_verify(pubkey: &PublicKey, message: &[u8], signature: &Signature, _domain: u64) -> bool { + // TODO: add domain + signature.verify(message, pubkey) +} + +impl From for Error { + fn from(e: AttestationValidationError) -> Error { + Error::InvalidAttestation(e) + } +} + +impl From for Error { + fn from(e: CommitteesError) -> Error { + Error::CommitteesError(e) + } +} + +impl From for Error { + fn from(e: SlotProcessingError) -> Error { + Error::SlotProcessingError(e) + } +} diff --git a/eth2/state_processing/src/epoch_processable.rs b/eth2/state_processing/src/epoch_processable.rs new file mode 100644 index 0000000000..aece61184d --- /dev/null +++ b/eth2/state_processing/src/epoch_processable.rs @@ -0,0 +1,716 @@ +use integer_sqrt::IntegerSquareRoot; +use log::{debug, trace}; +use rayon::prelude::*; +use ssz::TreeHash; +use std::collections::{HashMap, HashSet}; +use std::iter::FromIterator; +use types::{ + beacon_state::{AttestationParticipantsError, CommitteesError, InclusionError}, + validator_registry::get_active_validator_indices, + BeaconState, ChainSpec, Crosslink, Epoch, Hash256, PendingAttestation, +}; + +macro_rules! safe_add_assign { + ($a: expr, $b: expr) => { + $a = $a.saturating_add($b); + }; +} +macro_rules! safe_sub_assign { + ($a: expr, $b: expr) => { + $a = $a.saturating_sub($b); + }; +} + +#[derive(Debug, PartialEq)] +pub enum Error { + UnableToDetermineProducer, + NoBlockRoots, + BaseRewardQuotientIsZero, + NoRandaoSeed, + CommitteesError(CommitteesError), + AttestationParticipantsError(AttestationParticipantsError), + InclusionError(InclusionError), + WinningRootError(WinningRootError), +} + +#[derive(Debug, PartialEq)] +pub enum WinningRootError { + NoWinningRoot, + AttestationParticipantsError(AttestationParticipantsError), +} + +#[derive(Clone)] +pub struct WinningRoot { + pub shard_block_root: Hash256, + pub attesting_validator_indices: Vec, + pub total_balance: u64, + pub total_attesting_balance: u64, +} + +pub trait EpochProcessable { + fn per_epoch_processing(&mut self, spec: &ChainSpec) -> Result<(), Error>; +} + +impl EpochProcessable for BeaconState { + // Cyclomatic complexity is ignored. It would be ideal to split this function apart, however it + // remains monolithic to allow for easier spec updates. Once the spec is more stable we can + // optimise. + #[allow(clippy::cyclomatic_complexity)] + fn per_epoch_processing(&mut self, spec: &ChainSpec) -> Result<(), Error> { + let current_epoch = self.current_epoch(spec); + let previous_epoch = self.previous_epoch(spec); + let next_epoch = self.next_epoch(spec); + + debug!( + "Starting per-epoch processing on epoch {}...", + self.current_epoch(spec) + ); + + /* + * Validators attesting during the current epoch. + */ + let active_validator_indices = get_active_validator_indices( + &self.validator_registry, + self.slot.epoch(spec.epoch_length), + ); + let current_total_balance = self.get_total_balance(&active_validator_indices[..], spec); + + trace!( + "{} validators with a total balance of {} wei.", + active_validator_indices.len(), + current_total_balance + ); + + let current_epoch_attestations: Vec<&PendingAttestation> = self + .latest_attestations + .par_iter() + .filter(|a| { + (a.data.slot / spec.epoch_length).epoch(spec.epoch_length) + == self.current_epoch(spec) + }) + .collect(); + + trace!( + "Current epoch attestations: {}", + current_epoch_attestations.len() + ); + + let current_epoch_boundary_attestations: Vec<&PendingAttestation> = + current_epoch_attestations + .par_iter() + .filter( + |a| match self.get_block_root(self.current_epoch_start_slot(spec), spec) { + Some(block_root) => { + (a.data.epoch_boundary_root == *block_root) + && (a.data.justified_epoch == self.justified_epoch) + } + None => unreachable!(), + }, + ) + .cloned() + .collect(); + + let current_epoch_boundary_attester_indices = self + .get_attestation_participants_union(¤t_epoch_boundary_attestations[..], spec)?; + let current_epoch_boundary_attesting_balance = + self.get_total_balance(¤t_epoch_boundary_attester_indices[..], spec); + + trace!( + "Current epoch boundary attesters: {}", + current_epoch_boundary_attester_indices.len() + ); + + /* + * Validators attesting during the previous epoch + */ + + /* + * Validators that made an attestation during the previous epoch + */ + let previous_epoch_attestations: Vec<&PendingAttestation> = self + .latest_attestations + .par_iter() + .filter(|a| { + //TODO: ensure these saturating subs are correct. + (a.data.slot / spec.epoch_length).epoch(spec.epoch_length) + == self.previous_epoch(spec) + }) + .collect(); + + debug!( + "previous epoch attestations: {}", + previous_epoch_attestations.len() + ); + + let previous_epoch_attester_indices = + self.get_attestation_participants_union(&previous_epoch_attestations[..], spec)?; + let previous_total_balance = + self.get_total_balance(&previous_epoch_attester_indices[..], spec); + + /* + * Validators targetting the previous justified slot + */ + let previous_epoch_justified_attestations: Vec<&PendingAttestation> = { + let mut a: Vec<&PendingAttestation> = current_epoch_attestations + .iter() + .filter(|a| a.data.justified_epoch == self.previous_justified_epoch) + .cloned() + .collect(); + let mut b: Vec<&PendingAttestation> = previous_epoch_attestations + .iter() + .filter(|a| a.data.justified_epoch == self.previous_justified_epoch) + .cloned() + .collect(); + a.append(&mut b); + a + }; + + let previous_epoch_justified_attester_indices = self + .get_attestation_participants_union(&previous_epoch_justified_attestations[..], spec)?; + let previous_epoch_justified_attesting_balance = + self.get_total_balance(&previous_epoch_justified_attester_indices[..], spec); + + /* + * Validators justifying the epoch boundary block at the start of the previous epoch + */ + let previous_epoch_boundary_attestations: Vec<&PendingAttestation> = + previous_epoch_justified_attestations + .iter() + .filter( + |a| match self.get_block_root(self.previous_epoch_start_slot(spec), spec) { + Some(block_root) => a.data.epoch_boundary_root == *block_root, + None => unreachable!(), + }, + ) + .cloned() + .collect(); + + let previous_epoch_boundary_attester_indices = self + .get_attestation_participants_union(&previous_epoch_boundary_attestations[..], spec)?; + let previous_epoch_boundary_attesting_balance = + self.get_total_balance(&previous_epoch_boundary_attester_indices[..], spec); + + /* + * Validators attesting to the expected beacon chain head during the previous epoch. + */ + let previous_epoch_head_attestations: Vec<&PendingAttestation> = + previous_epoch_attestations + .iter() + .filter(|a| match self.get_block_root(a.data.slot, spec) { + Some(block_root) => a.data.beacon_block_root == *block_root, + None => unreachable!(), + }) + .cloned() + .collect(); + + let previous_epoch_head_attester_indices = + self.get_attestation_participants_union(&previous_epoch_head_attestations[..], spec)?; + let previous_epoch_head_attesting_balance = + self.get_total_balance(&previous_epoch_head_attester_indices[..], spec); + + debug!( + "previous_epoch_head_attester_balance of {} wei.", + previous_epoch_head_attesting_balance + ); + + /* + * Eth1 Data + */ + if self.next_epoch(spec) % spec.eth1_data_voting_period == 0 { + for eth1_data_vote in &self.eth1_data_votes { + if eth1_data_vote.vote_count * 2 > spec.eth1_data_voting_period { + self.latest_eth1_data = eth1_data_vote.eth1_data.clone(); + } + } + self.eth1_data_votes = vec![]; + } + + /* + * Justification + */ + + let mut new_justified_epoch = self.justified_epoch; + self.justification_bitfield <<= 1; + + // If > 2/3 of the total balance attested to the previous epoch boundary + // + // - Set the 2nd bit of the bitfield. + // - Set the previous epoch to be justified. + if (3 * previous_epoch_boundary_attesting_balance) >= (2 * current_total_balance) { + self.justification_bitfield |= 2; + new_justified_epoch = previous_epoch; + trace!(">= 2/3 voted for previous epoch boundary"); + } + // If > 2/3 of the total balance attested to the previous epoch boundary + // + // - Set the 1st bit of the bitfield. + // - Set the current epoch to be justified. + if (3 * current_epoch_boundary_attesting_balance) >= (2 * current_total_balance) { + self.justification_bitfield |= 1; + new_justified_epoch = current_epoch; + trace!(">= 2/3 voted for current epoch boundary"); + } + + // If: + // + // - All three epochs prior to this epoch have been justified. + // - The previous justified justified epoch was three epochs ago. + // + // Then, set the finalized epoch to be three epochs ago. + if ((self.justification_bitfield >> 1) % 8 == 0b111) + & (self.previous_justified_epoch == previous_epoch - 2) + { + self.finalized_epoch = self.previous_justified_epoch; + trace!("epoch - 3 was finalized (1st condition)."); + } + // If: + // + // - Both two epochs prior to this epoch have been justified. + // - The previous justified epoch was two epochs ago. + // + // Then, set the finalized epoch to two epochs ago. + if ((self.justification_bitfield >> 1) % 4 == 0b11) + & (self.previous_justified_epoch == previous_epoch - 1) + { + self.finalized_epoch = self.previous_justified_epoch; + trace!("epoch - 2 was finalized (2nd condition)."); + } + // If: + // + // - This epoch and the two prior have been justified. + // - The presently justified epoch was two epochs ago. + // + // Then, set the finalized epoch to two epochs ago. + if (self.justification_bitfield % 8 == 0b111) & (self.justified_epoch == previous_epoch - 1) + { + self.finalized_epoch = self.justified_epoch; + trace!("epoch - 2 was finalized (3rd condition)."); + } + // If: + // + // - This epoch and the epoch prior to it have been justified. + // - Set the previous epoch to be justified. + // + // Then, set the finalized epoch to be the previous epoch. + if (self.justification_bitfield % 4 == 0b11) & (self.justified_epoch == previous_epoch) { + self.finalized_epoch = self.justified_epoch; + trace!("epoch - 1 was finalized (4th condition)."); + } + + self.previous_justified_epoch = self.justified_epoch; + self.justified_epoch = new_justified_epoch; + + debug!( + "Finalized epoch {}, justified epoch {}.", + self.finalized_epoch, self.justified_epoch + ); + + /* + * Crosslinks + */ + + // Cached for later lookups. + let mut winning_root_for_shards: HashMap> = + HashMap::new(); + + // for slot in self.slot.saturating_sub(2 * spec.epoch_length)..self.slot { + for slot in self.previous_epoch(spec).slot_iter(spec.epoch_length) { + let crosslink_committees_at_slot = + self.get_crosslink_committees_at_slot(slot, false, spec)?; + + for (crosslink_committee, shard) in crosslink_committees_at_slot { + let shard = shard as u64; + + let winning_root = winning_root( + self, + shard, + ¤t_epoch_attestations, + &previous_epoch_attestations, + spec, + ); + + if let Ok(winning_root) = &winning_root { + let total_committee_balance = + self.get_total_balance(&crosslink_committee[..], spec); + + if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) { + self.latest_crosslinks[shard as usize] = Crosslink { + epoch: current_epoch, + shard_block_root: winning_root.shard_block_root, + } + } + } + winning_root_for_shards.insert(shard, winning_root); + } + } + + trace!( + "Found {} winning shard roots.", + winning_root_for_shards.len() + ); + + /* + * Rewards and Penalities + */ + let base_reward_quotient = previous_total_balance.integer_sqrt(); + if base_reward_quotient == 0 { + return Err(Error::BaseRewardQuotientIsZero); + } + + /* + * Justification and finalization + */ + let epochs_since_finality = next_epoch - self.finalized_epoch; + + let previous_epoch_justified_attester_indices_hashset: HashSet = + HashSet::from_iter(previous_epoch_justified_attester_indices.iter().cloned()); + let previous_epoch_boundary_attester_indices_hashset: HashSet = + HashSet::from_iter(previous_epoch_boundary_attester_indices.iter().cloned()); + let previous_epoch_head_attester_indices_hashset: HashSet = + HashSet::from_iter(previous_epoch_head_attester_indices.iter().cloned()); + let previous_epoch_attester_indices_hashset: HashSet = + HashSet::from_iter(previous_epoch_attester_indices.iter().cloned()); + let active_validator_indices_hashset: HashSet = + HashSet::from_iter(active_validator_indices.iter().cloned()); + + debug!("previous epoch justified attesters: {}, previous epoch boundary attesters: {}, previous epoch head attesters: {}, previous epoch attesters: {}", previous_epoch_justified_attester_indices.len(), previous_epoch_boundary_attester_indices.len(), previous_epoch_head_attester_indices.len(), previous_epoch_attester_indices.len()); + + debug!("{} epochs since finality.", epochs_since_finality); + + if epochs_since_finality <= 4 { + for index in 0..self.validator_balances.len() { + let base_reward = self.base_reward(index, base_reward_quotient, spec); + + if previous_epoch_justified_attester_indices_hashset.contains(&index) { + safe_add_assign!( + self.validator_balances[index], + base_reward * previous_epoch_justified_attesting_balance + / previous_total_balance + ); + } else if active_validator_indices_hashset.contains(&index) { + safe_sub_assign!(self.validator_balances[index], base_reward); + } + + if previous_epoch_boundary_attester_indices_hashset.contains(&index) { + safe_add_assign!( + self.validator_balances[index], + base_reward * previous_epoch_boundary_attesting_balance + / previous_total_balance + ); + } else if active_validator_indices_hashset.contains(&index) { + safe_sub_assign!(self.validator_balances[index], base_reward); + } + + if previous_epoch_head_attester_indices_hashset.contains(&index) { + safe_add_assign!( + self.validator_balances[index], + base_reward * previous_epoch_head_attesting_balance + / previous_total_balance + ); + } else if active_validator_indices_hashset.contains(&index) { + safe_sub_assign!(self.validator_balances[index], base_reward); + } + } + + for index in previous_epoch_attester_indices { + let base_reward = self.base_reward(index, base_reward_quotient, spec); + let inclusion_distance = + self.inclusion_distance(&previous_epoch_attestations, index, spec)?; + + safe_add_assign!( + self.validator_balances[index], + base_reward * spec.min_attestation_inclusion_delay / inclusion_distance + ) + } + } else { + for index in 0..self.validator_balances.len() { + let inactivity_penalty = self.inactivity_penalty( + index, + epochs_since_finality, + base_reward_quotient, + spec, + ); + if active_validator_indices_hashset.contains(&index) { + if !previous_epoch_justified_attester_indices_hashset.contains(&index) { + safe_sub_assign!(self.validator_balances[index], inactivity_penalty); + } + if !previous_epoch_boundary_attester_indices_hashset.contains(&index) { + safe_sub_assign!(self.validator_balances[index], inactivity_penalty); + } + if !previous_epoch_head_attester_indices_hashset.contains(&index) { + safe_sub_assign!(self.validator_balances[index], inactivity_penalty); + } + + if self.validator_registry[index].penalized_epoch <= current_epoch { + let base_reward = self.base_reward(index, base_reward_quotient, spec); + safe_sub_assign!( + self.validator_balances[index], + 2 * inactivity_penalty + base_reward + ); + } + } + } + + for index in previous_epoch_attester_indices { + let base_reward = self.base_reward(index, base_reward_quotient, spec); + let inclusion_distance = + self.inclusion_distance(&previous_epoch_attestations, index, spec)?; + + safe_sub_assign!( + self.validator_balances[index], + base_reward + - base_reward * spec.min_attestation_inclusion_delay / inclusion_distance + ); + } + } + + trace!("Processed validator justification and finalization rewards/penalities."); + + /* + * Attestation inclusion + */ + for &index in &previous_epoch_attester_indices_hashset { + let inclusion_slot = + self.inclusion_slot(&previous_epoch_attestations[..], index, spec)?; + let proposer_index = self + .get_beacon_proposer_index(inclusion_slot, spec) + .map_err(|_| Error::UnableToDetermineProducer)?; + let base_reward = self.base_reward(proposer_index, base_reward_quotient, spec); + safe_add_assign!( + self.validator_balances[proposer_index], + base_reward / spec.includer_reward_quotient + ); + } + + trace!( + "Previous epoch attesters: {}.", + previous_epoch_attester_indices_hashset.len() + ); + + /* + * Crosslinks + */ + for slot in self.previous_epoch(spec).slot_iter(spec.epoch_length) { + let crosslink_committees_at_slot = + self.get_crosslink_committees_at_slot(slot, false, spec)?; + + for (_crosslink_committee, shard) in crosslink_committees_at_slot { + let shard = shard as u64; + + if let Some(Ok(winning_root)) = winning_root_for_shards.get(&shard) { + // TODO: remove the map. + let attesting_validator_indices: HashSet = HashSet::from_iter( + winning_root.attesting_validator_indices.iter().cloned(), + ); + + for index in 0..self.validator_balances.len() { + let base_reward = self.base_reward(index, base_reward_quotient, spec); + + if attesting_validator_indices.contains(&index) { + safe_add_assign!( + self.validator_balances[index], + base_reward * winning_root.total_attesting_balance + / winning_root.total_balance + ); + } else { + safe_sub_assign!(self.validator_balances[index], base_reward); + } + } + + for index in &winning_root.attesting_validator_indices { + let base_reward = self.base_reward(*index, base_reward_quotient, spec); + safe_add_assign!( + self.validator_balances[*index], + base_reward * winning_root.total_attesting_balance + / winning_root.total_balance + ); + } + } + } + } + + /* + * Ejections + */ + self.process_ejections(spec); + + /* + * Validator Registry + */ + self.previous_calculation_epoch = self.current_calculation_epoch; + self.previous_epoch_start_shard = self.current_epoch_start_shard; + self.previous_epoch_seed = self.current_epoch_seed; + + let should_update_validator_registy = if self.finalized_epoch + > self.validator_registry_update_epoch + { + (0..self.get_current_epoch_committee_count(spec)).all(|i| { + let shard = (self.current_epoch_start_shard + i as u64) % spec.shard_count; + self.latest_crosslinks[shard as usize].epoch > self.validator_registry_update_epoch + }) + } else { + false + }; + + if should_update_validator_registy { + self.update_validator_registry(spec); + + self.current_calculation_epoch = next_epoch; + self.current_epoch_start_shard = (self.current_epoch_start_shard + + self.get_current_epoch_committee_count(spec) as u64) + % spec.shard_count; + self.current_epoch_seed = self + .generate_seed(self.current_calculation_epoch, spec) + .ok_or_else(|| Error::NoRandaoSeed)?; + } else { + let epochs_since_last_registry_update = + current_epoch - self.validator_registry_update_epoch; + if (epochs_since_last_registry_update > 1) + & epochs_since_last_registry_update.is_power_of_two() + { + self.current_calculation_epoch = next_epoch; + self.current_epoch_seed = self + .generate_seed(self.current_calculation_epoch, spec) + .ok_or_else(|| Error::NoRandaoSeed)?; + } + } + + self.process_penalties_and_exits(spec); + + self.latest_index_roots[(next_epoch.as_usize() + spec.entry_exit_delay as usize) + % spec.latest_index_roots_length] = hash_tree_root(get_active_validator_indices( + &self.validator_registry, + next_epoch + Epoch::from(spec.entry_exit_delay), + )); + self.latest_penalized_balances[next_epoch.as_usize() % spec.latest_penalized_exit_length] = + self.latest_penalized_balances + [current_epoch.as_usize() % spec.latest_penalized_exit_length]; + self.latest_randao_mixes[next_epoch.as_usize() % spec.latest_randao_mixes_length] = self + .get_randao_mix(current_epoch, spec) + .and_then(|x| Some(*x)) + .ok_or_else(|| Error::NoRandaoSeed)?; + self.latest_attestations = self + .latest_attestations + .iter() + .filter(|a| a.data.slot.epoch(spec.epoch_length) >= current_epoch) + .cloned() + .collect(); + + debug!("Epoch transition complete."); + + Ok(()) + } +} + +fn hash_tree_root(input: Vec) -> Hash256 { + Hash256::from(&input.hash_tree_root()[..]) +} + +fn winning_root( + state: &BeaconState, + shard: u64, + current_epoch_attestations: &[&PendingAttestation], + previous_epoch_attestations: &[&PendingAttestation], + spec: &ChainSpec, +) -> Result { + let mut attestations = current_epoch_attestations.to_vec(); + attestations.append(&mut previous_epoch_attestations.to_vec()); + + let mut candidates: HashMap = HashMap::new(); + + let mut highest_seen_balance = 0; + + for a in &attestations { + if a.data.shard != shard { + continue; + } + + let shard_block_root = &a.data.shard_block_root; + + if candidates.contains_key(shard_block_root) { + continue; + } + + // TODO: `cargo fmt` makes this rather ugly; tidy up. + let attesting_validator_indices = attestations.iter().try_fold::<_, _, Result< + _, + AttestationParticipantsError, + >>(vec![], |mut acc, a| { + if (a.data.shard == shard) && (a.data.shard_block_root == *shard_block_root) { + acc.append(&mut state.get_attestation_participants( + &a.data, + &a.aggregation_bitfield, + spec, + )?); + } + Ok(acc) + })?; + + let total_balance: u64 = attesting_validator_indices + .iter() + .fold(0, |acc, i| acc + state.get_effective_balance(*i, spec)); + + let total_attesting_balance: u64 = attesting_validator_indices + .iter() + .fold(0, |acc, i| acc + state.get_effective_balance(*i, spec)); + + if total_attesting_balance > highest_seen_balance { + highest_seen_balance = total_attesting_balance; + } + + let candidate_root = WinningRoot { + shard_block_root: *shard_block_root, + attesting_validator_indices, + total_attesting_balance, + total_balance, + }; + + candidates.insert(*shard_block_root, candidate_root); + } + + Ok(candidates + .iter() + .filter_map(|(_hash, candidate)| { + if candidate.total_attesting_balance == highest_seen_balance { + Some(candidate) + } else { + None + } + }) + .min_by_key(|candidate| candidate.shard_block_root) + .ok_or_else(|| WinningRootError::NoWinningRoot)? + // TODO: avoid clone. + .clone()) +} + +impl From for Error { + fn from(e: InclusionError) -> Error { + Error::InclusionError(e) + } +} + +impl From for Error { + fn from(e: CommitteesError) -> Error { + Error::CommitteesError(e) + } +} + +impl From for Error { + fn from(e: AttestationParticipantsError) -> Error { + Error::AttestationParticipantsError(e) + } +} + +impl From for WinningRootError { + fn from(e: AttestationParticipantsError) -> WinningRootError { + WinningRootError::AttestationParticipantsError(e) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/eth2/state_processing/src/lib.rs b/eth2/state_processing/src/lib.rs new file mode 100644 index 0000000000..18d1f7554f --- /dev/null +++ b/eth2/state_processing/src/lib.rs @@ -0,0 +1,10 @@ +mod block_processable; +mod epoch_processable; +mod slot_processable; + +pub use block_processable::{ + validate_attestation, validate_attestation_without_signature, BlockProcessable, + Error as BlockProcessingError, +}; +pub use epoch_processable::{EpochProcessable, Error as EpochProcessingError}; +pub use slot_processable::{Error as SlotProcessingError, SlotProcessable}; diff --git a/eth2/state_processing/src/slot_processable.rs b/eth2/state_processing/src/slot_processable.rs new file mode 100644 index 0000000000..7726c50713 --- /dev/null +++ b/eth2/state_processing/src/slot_processable.rs @@ -0,0 +1,70 @@ +use crate::{EpochProcessable, EpochProcessingError}; +use types::{beacon_state::CommitteesError, BeaconState, ChainSpec, Hash256}; + +#[derive(Debug, PartialEq)] +pub enum Error { + CommitteesError(CommitteesError), + EpochProcessingError(EpochProcessingError), +} + +pub trait SlotProcessable { + fn per_slot_processing( + &mut self, + previous_block_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error>; +} + +impl SlotProcessable for BeaconState +where + BeaconState: EpochProcessable, +{ + fn per_slot_processing( + &mut self, + previous_block_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error> { + if (self.slot + 1) % spec.epoch_length == 0 { + self.per_epoch_processing(spec)?; + } + + self.slot += 1; + + self.latest_randao_mixes[self.slot.as_usize() % spec.latest_randao_mixes_length] = + self.latest_randao_mixes[(self.slot.as_usize() - 1) % spec.latest_randao_mixes_length]; + + // Block roots. + self.latest_block_roots[(self.slot.as_usize() - 1) % spec.latest_block_roots_length] = + previous_block_root; + + if self.slot.as_usize() % spec.latest_block_roots_length == 0 { + let root = merkle_root(&self.latest_block_roots[..]); + self.batched_block_roots.push(root); + } + Ok(()) + } +} + +fn merkle_root(_input: &[Hash256]) -> Hash256 { + Hash256::zero() +} + +impl From for Error { + fn from(e: CommitteesError) -> Error { + Error::CommitteesError(e) + } +} + +impl From for Error { + fn from(e: EpochProcessingError) -> Error { + Error::EpochProcessingError(e) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/eth2/types/Cargo.toml b/eth2/types/Cargo.toml index 77cfb60400..24aabf1482 100644 --- a/eth2/types/Cargo.toml +++ b/eth2/types/Cargo.toml @@ -10,12 +10,12 @@ boolean-bitfield = { path = "../utils/boolean-bitfield" } ethereum-types = "0.4.0" hashing = { path = "../utils/hashing" } honey-badger-split = { path = "../utils/honey-badger-split" } -integer-sqrt = "0.1" log = "0.4" rayon = "1.0" rand = "0.5.5" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" +slog = "^2.2.3" ssz = { path = "../utils/ssz" } vec_shuffle = { path = "../utils/vec_shuffle" } diff --git a/eth2/types/src/attestation.rs b/eth2/types/src/attestation.rs index 73ea5eec1c..eb375d4903 100644 --- a/eth2/types/src/attestation.rs +++ b/eth2/types/src/attestation.rs @@ -1,14 +1,13 @@ -use super::{AttestationData, Bitfield, Hash256}; +use super::{AggregatePublicKey, AggregateSignature, AttestationData, Bitfield, Hash256}; use crate::test_utils::TestRandom; -use bls::AggregateSignature; use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; #[derive(Debug, Clone, PartialEq, Serialize)] pub struct Attestation { - pub data: AttestationData, pub aggregation_bitfield: Bitfield, + pub data: AttestationData, pub custody_bitfield: Bitfield, pub aggregate_signature: AggregateSignature, } @@ -21,12 +20,23 @@ impl Attestation { pub fn signable_message(&self, custody_bit: bool) -> Vec { self.data.signable_message(custody_bit) } + + pub fn verify_signature( + &self, + group_public_key: &AggregatePublicKey, + custody_bit: bool, + // TODO: use domain. + _domain: u64, + ) -> bool { + self.aggregate_signature + .verify(&self.signable_message(custody_bit), group_public_key) + } } impl Encodable for Attestation { fn ssz_append(&self, s: &mut SszStream) { - s.append(&self.data); s.append(&self.aggregation_bitfield); + s.append(&self.data); s.append(&self.custody_bitfield); s.append(&self.aggregate_signature); } @@ -34,14 +44,14 @@ impl Encodable for Attestation { impl Decodable for Attestation { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { - let (data, i) = AttestationData::ssz_decode(bytes, i)?; let (aggregation_bitfield, i) = Bitfield::ssz_decode(bytes, i)?; + let (data, i) = AttestationData::ssz_decode(bytes, i)?; let (custody_bitfield, i) = Bitfield::ssz_decode(bytes, i)?; let (aggregate_signature, i) = AggregateSignature::ssz_decode(bytes, i)?; let attestation_record = Self { - data, aggregation_bitfield, + data, custody_bitfield, aggregate_signature, }; @@ -49,22 +59,11 @@ impl Decodable for Attestation { } } -impl Attestation { - pub fn zero() -> Self { - Self { - data: AttestationData::zero(), - aggregation_bitfield: Bitfield::new(), - custody_bitfield: Bitfield::new(), - aggregate_signature: AggregateSignature::new(), - } - } -} - impl TreeHash for Attestation { fn hash_tree_root(&self) -> Vec { let mut result: Vec = vec![]; - result.append(&mut self.data.hash_tree_root()); result.append(&mut self.aggregation_bitfield.hash_tree_root()); + result.append(&mut self.data.hash_tree_root()); result.append(&mut self.custody_bitfield.hash_tree_root()); result.append(&mut self.aggregate_signature.hash_tree_root()); hash(&result) diff --git a/eth2/types/src/attestation_data.rs b/eth2/types/src/attestation_data.rs index e2140527b6..702bba4166 100644 --- a/eth2/types/src/attestation_data.rs +++ b/eth2/types/src/attestation_data.rs @@ -1,5 +1,5 @@ -use super::{AttestationDataAndCustodyBit, Hash256}; use crate::test_utils::TestRandom; +use crate::{AttestationDataAndCustodyBit, Crosslink, Epoch, Hash256, Slot}; use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; @@ -11,38 +11,25 @@ pub const SSZ_ATTESTION_DATA_LENGTH: usize = { 32 + // epoch_boundary_root 32 + // shard_block_hash 32 + // latest_crosslink_hash - 8 + // justified_slot + 8 + // justified_epoch 32 // justified_block_root }; #[derive(Debug, Clone, PartialEq, Default, Serialize, Hash)] pub struct AttestationData { - pub slot: u64, + pub slot: Slot, pub shard: u64, pub beacon_block_root: Hash256, pub epoch_boundary_root: Hash256, pub shard_block_root: Hash256, - pub latest_crosslink_root: Hash256, - pub justified_slot: u64, + pub latest_crosslink: Crosslink, + pub justified_epoch: Epoch, pub justified_block_root: Hash256, } impl Eq for AttestationData {} impl AttestationData { - pub fn zero() -> Self { - Self { - slot: 0, - shard: 0, - beacon_block_root: Hash256::zero(), - epoch_boundary_root: Hash256::zero(), - shard_block_root: Hash256::zero(), - latest_crosslink_root: Hash256::zero(), - justified_slot: 0, - justified_block_root: Hash256::zero(), - } - } - pub fn canonical_root(&self) -> Hash256 { Hash256::from(&self.hash_tree_root()[..]) } @@ -63,22 +50,22 @@ impl Encodable for AttestationData { s.append(&self.beacon_block_root); s.append(&self.epoch_boundary_root); s.append(&self.shard_block_root); - s.append(&self.latest_crosslink_root); - s.append(&self.justified_slot); + s.append(&self.latest_crosslink); + s.append(&self.justified_epoch); s.append(&self.justified_block_root); } } impl Decodable for AttestationData { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { - let (slot, i) = u64::ssz_decode(bytes, i)?; - let (shard, i) = u64::ssz_decode(bytes, i)?; - let (beacon_block_root, i) = Hash256::ssz_decode(bytes, i)?; - let (epoch_boundary_root, i) = Hash256::ssz_decode(bytes, i)?; - let (shard_block_root, i) = Hash256::ssz_decode(bytes, i)?; - let (latest_crosslink_root, i) = Hash256::ssz_decode(bytes, i)?; - let (justified_slot, i) = u64::ssz_decode(bytes, i)?; - let (justified_block_root, i) = Hash256::ssz_decode(bytes, i)?; + let (slot, i) = <_>::ssz_decode(bytes, i)?; + let (shard, i) = <_>::ssz_decode(bytes, i)?; + let (beacon_block_root, i) = <_>::ssz_decode(bytes, i)?; + let (epoch_boundary_root, i) = <_>::ssz_decode(bytes, i)?; + let (shard_block_root, i) = <_>::ssz_decode(bytes, i)?; + let (latest_crosslink, i) = <_>::ssz_decode(bytes, i)?; + let (justified_epoch, i) = <_>::ssz_decode(bytes, i)?; + let (justified_block_root, i) = <_>::ssz_decode(bytes, i)?; let attestation_data = AttestationData { slot, @@ -86,8 +73,8 @@ impl Decodable for AttestationData { beacon_block_root, epoch_boundary_root, shard_block_root, - latest_crosslink_root, - justified_slot, + latest_crosslink, + justified_epoch, justified_block_root, }; Ok((attestation_data, i)) @@ -102,8 +89,8 @@ impl TreeHash for AttestationData { result.append(&mut self.beacon_block_root.hash_tree_root()); result.append(&mut self.epoch_boundary_root.hash_tree_root()); result.append(&mut self.shard_block_root.hash_tree_root()); - result.append(&mut self.latest_crosslink_root.hash_tree_root()); - result.append(&mut self.justified_slot.hash_tree_root()); + result.append(&mut self.latest_crosslink.hash_tree_root()); + result.append(&mut self.justified_epoch.hash_tree_root()); result.append(&mut self.justified_block_root.hash_tree_root()); hash(&result) } @@ -117,8 +104,8 @@ impl TestRandom for AttestationData { beacon_block_root: <_>::random_for_test(rng), epoch_boundary_root: <_>::random_for_test(rng), shard_block_root: <_>::random_for_test(rng), - latest_crosslink_root: <_>::random_for_test(rng), - justified_slot: <_>::random_for_test(rng), + latest_crosslink: <_>::random_for_test(rng), + justified_epoch: <_>::random_for_test(rng), justified_block_root: <_>::random_for_test(rng), } } diff --git a/eth2/types/src/attestation_data_and_custody_bit.rs b/eth2/types/src/attestation_data_and_custody_bit.rs index 8200abf307..4e93dd8934 100644 --- a/eth2/types/src/attestation_data_and_custody_bit.rs +++ b/eth2/types/src/attestation_data_and_custody_bit.rs @@ -2,7 +2,7 @@ use super::AttestationData; use crate::test_utils::TestRandom; use rand::RngCore; use serde_derive::Serialize; -use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; +use ssz::{Decodable, DecodeError, Encodable, SszStream, TreeHash}; #[derive(Debug, Clone, PartialEq, Default, Serialize)] pub struct AttestationDataAndCustodyBit { diff --git a/eth2/types/src/attester_slashing.rs b/eth2/types/src/attester_slashing.rs new file mode 100644 index 0000000000..0b27d20308 --- /dev/null +++ b/eth2/types/src/attester_slashing.rs @@ -0,0 +1,80 @@ +use crate::{test_utils::TestRandom, SlashableAttestation}; +use rand::RngCore; +use serde_derive::Serialize; +use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; + +#[derive(Debug, PartialEq, Clone, Serialize)] +pub struct AttesterSlashing { + pub slashable_attestation_1: SlashableAttestation, + pub slashable_attestation_2: SlashableAttestation, +} + +impl Encodable for AttesterSlashing { + fn ssz_append(&self, s: &mut SszStream) { + s.append(&self.slashable_attestation_1); + s.append(&self.slashable_attestation_2); + } +} + +impl Decodable for AttesterSlashing { + fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { + let (slashable_attestation_1, i) = <_>::ssz_decode(bytes, i)?; + let (slashable_attestation_2, i) = <_>::ssz_decode(bytes, i)?; + + Ok(( + AttesterSlashing { + slashable_attestation_1, + slashable_attestation_2, + }, + i, + )) + } +} + +impl TreeHash for AttesterSlashing { + fn hash_tree_root(&self) -> Vec { + let mut result: Vec = vec![]; + result.append(&mut self.slashable_attestation_1.hash_tree_root()); + result.append(&mut self.slashable_attestation_2.hash_tree_root()); + hash(&result) + } +} + +impl TestRandom for AttesterSlashing { + fn random_for_test(rng: &mut T) -> Self { + Self { + slashable_attestation_1: <_>::random_for_test(rng), + slashable_attestation_2: <_>::random_for_test(rng), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; + use ssz::ssz_encode; + + #[test] + pub fn test_ssz_round_trip() { + let mut rng = XorShiftRng::from_seed([42; 16]); + let original = AttesterSlashing::random_for_test(&mut rng); + + let bytes = ssz_encode(&original); + let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); + + assert_eq!(original, decoded); + } + + #[test] + pub fn test_hash_tree_root() { + let mut rng = XorShiftRng::from_seed([42; 16]); + let original = AttesterSlashing::random_for_test(&mut rng); + + let result = original.hash_tree_root(); + + assert_eq!(result.len(), 32); + // TODO: Add further tests + // https://github.com/sigp/lighthouse/issues/170 + } +} diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index 3e2c51ede7..f6977595a5 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -1,5 +1,5 @@ -use super::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, ProposalSignedData}; use crate::test_utils::TestRandom; +use crate::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, ProposalSignedData, Slot}; use bls::Signature; use rand::RngCore; use serde_derive::Serialize; @@ -7,7 +7,7 @@ use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; #[derive(Debug, PartialEq, Clone, Serialize)] pub struct BeaconBlock { - pub slot: u64, + pub slot: Slot, pub parent_root: Hash256, pub state_root: Hash256, pub randao_reveal: Signature, @@ -17,6 +17,28 @@ pub struct BeaconBlock { } impl BeaconBlock { + /// Produce the first block of the Beacon Chain. + pub fn genesis(state_root: Hash256, spec: &ChainSpec) -> BeaconBlock { + BeaconBlock { + slot: spec.genesis_slot, + parent_root: spec.zero_hash, + state_root, + randao_reveal: spec.empty_signature.clone(), + eth1_data: Eth1Data { + deposit_root: spec.zero_hash, + block_hash: spec.zero_hash, + }, + signature: spec.empty_signature.clone(), + body: BeaconBlockBody { + proposer_slashings: vec![], + attester_slashings: vec![], + attestations: vec![], + deposits: vec![], + exits: vec![], + }, + } + } + pub fn canonical_root(&self) -> Hash256 { Hash256::from(&self.hash_tree_root()[..]) } @@ -33,7 +55,7 @@ impl BeaconBlock { shard: spec.beacon_chain_shard_number, block_root: block_without_signature_root, }; - Hash256::from_slice(&proposal.hash_tree_root()[..]) + Hash256::from(&proposal.hash_tree_root()[..]) } } diff --git a/eth2/types/src/beacon_block_body.rs b/eth2/types/src/beacon_block_body.rs index ad9ec7ea6b..d3a61f7ba5 100644 --- a/eth2/types/src/beacon_block_body.rs +++ b/eth2/types/src/beacon_block_body.rs @@ -1,23 +1,14 @@ -use super::{Attestation, CasperSlashing, Deposit, Exit, ProposerSlashing}; +use super::{Attestation, AttesterSlashing, Deposit, Exit, ProposerSlashing}; use crate::test_utils::TestRandom; use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; -// The following types are just dummy classes as they will not be defined until -// Phase 1 (Sharding phase) -type CustodyReseed = usize; -type CustodyChallenge = usize; -type CustodyResponse = usize; - #[derive(Debug, PartialEq, Clone, Default, Serialize)] pub struct BeaconBlockBody { pub proposer_slashings: Vec, - pub casper_slashings: Vec, + pub attester_slashings: Vec, pub attestations: Vec, - pub custody_reseeds: Vec, - pub custody_challenges: Vec, - pub custody_responses: Vec, pub deposits: Vec, pub exits: Vec, } @@ -25,11 +16,8 @@ pub struct BeaconBlockBody { impl Encodable for BeaconBlockBody { fn ssz_append(&self, s: &mut SszStream) { s.append_vec(&self.proposer_slashings); - s.append_vec(&self.casper_slashings); + s.append_vec(&self.attester_slashings); s.append_vec(&self.attestations); - s.append_vec(&self.custody_reseeds); - s.append_vec(&self.custody_challenges); - s.append_vec(&self.custody_responses); s.append_vec(&self.deposits); s.append_vec(&self.exits); } @@ -38,22 +26,16 @@ impl Encodable for BeaconBlockBody { impl Decodable for BeaconBlockBody { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { let (proposer_slashings, i) = <_>::ssz_decode(bytes, i)?; - let (casper_slashings, i) = <_>::ssz_decode(bytes, i)?; + let (attester_slashings, i) = <_>::ssz_decode(bytes, i)?; let (attestations, i) = <_>::ssz_decode(bytes, i)?; - let (custody_reseeds, i) = <_>::ssz_decode(bytes, i)?; - let (custody_challenges, i) = <_>::ssz_decode(bytes, i)?; - let (custody_responses, i) = <_>::ssz_decode(bytes, i)?; let (deposits, i) = <_>::ssz_decode(bytes, i)?; let (exits, i) = <_>::ssz_decode(bytes, i)?; Ok(( Self { proposer_slashings, - casper_slashings, + attester_slashings, attestations, - custody_reseeds, - custody_challenges, - custody_responses, deposits, exits, }, @@ -66,11 +48,8 @@ impl TreeHash for BeaconBlockBody { fn hash_tree_root(&self) -> Vec { let mut result: Vec = vec![]; result.append(&mut self.proposer_slashings.hash_tree_root()); - result.append(&mut self.casper_slashings.hash_tree_root()); + result.append(&mut self.attester_slashings.hash_tree_root()); result.append(&mut self.attestations.hash_tree_root()); - result.append(&mut self.custody_reseeds.hash_tree_root()); - result.append(&mut self.custody_challenges.hash_tree_root()); - result.append(&mut self.custody_responses.hash_tree_root()); result.append(&mut self.deposits.hash_tree_root()); result.append(&mut self.exits.hash_tree_root()); hash(&result) @@ -81,11 +60,8 @@ impl TestRandom for BeaconBlockBody { fn random_for_test(rng: &mut T) -> Self { Self { proposer_slashings: <_>::random_for_test(rng), - casper_slashings: <_>::random_for_test(rng), + attester_slashings: <_>::random_for_test(rng), attestations: <_>::random_for_test(rng), - custody_reseeds: <_>::random_for_test(rng), - custody_challenges: <_>::random_for_test(rng), - custody_responses: <_>::random_for_test(rng), deposits: <_>::random_for_test(rng), exits: <_>::random_for_test(rng), } diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 39787ab32e..ed53bfea91 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1,88 +1,29 @@ use crate::test_utils::TestRandom; use crate::{ - validator::StatusFlags, validator_registry::get_active_validator_indices, AggregatePublicKey, - Attestation, AttestationData, BeaconBlock, Bitfield, ChainSpec, Crosslink, Eth1Data, - Eth1DataVote, Exit, Fork, Hash256, PendingAttestation, PublicKey, Signature, Validator, + validator::StatusFlags, validator_registry::get_active_validator_indices, AttestationData, + Bitfield, ChainSpec, Crosslink, Deposit, Epoch, Eth1Data, Eth1DataVote, Fork, Hash256, + PendingAttestation, PublicKey, Signature, Slot, Validator, }; -use bls::bls_verify_aggregate; +use bls::verify_proof_of_possession; use honey_badger_split::SplitExt; -use integer_sqrt::IntegerSquareRoot; -use log::debug; use rand::RngCore; -use rayon::prelude::*; use serde_derive::Serialize; -use ssz::ssz_encode; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; -use std::collections::{HashMap, HashSet}; -use std::iter::FromIterator; use std::ops::Range; use vec_shuffle::shuffle; -// TODO: define elsehwere. -const DOMAIN_PROPOSAL: u64 = 2; -const DOMAIN_EXIT: u64 = 3; -const DOMAIN_RANDAO: u64 = 4; -const PHASE_0_CUSTODY_BIT: bool = false; -const DOMAIN_ATTESTATION: u64 = 1; - pub enum Error { InsufficientValidators, BadBlockSignature, - InvalidEpoch(u64, Range), + InvalidEpoch(Slot, Range), CommitteesError(CommitteesError), } -#[derive(Debug, PartialEq)] -pub enum BlockProcessingError { - DBError(String), - StateAlreadyTransitioned, - PresentSlotIsNone, - UnableToDecodeBlock, - MissingParentState(Hash256), - InvalidParentState(Hash256), - MissingBeaconBlock(Hash256), - InvalidBeaconBlock(Hash256), - MissingParentBlock(Hash256), - NoBlockProducer, - StateSlotMismatch, - BadBlockSignature, - BadRandaoSignature, - MaxProposerSlashingsExceeded, - BadProposerSlashing, - MaxAttestationsExceeded, - InvalidAttestation(AttestationValidationError), - NoBlockRoot, - MaxDepositsExceeded, - MaxExitsExceeded, - BadExit, - BadCustodyReseeds, - BadCustodyChallenges, - BadCustodyResponses, - CommitteesError(CommitteesError), - SlotProcessingError(SlotProcessingError), -} - -#[derive(Debug, PartialEq)] -pub enum EpochError { - UnableToDetermineProducer, - NoBlockRoots, - BaseRewardQuotientIsZero, - CommitteesError(CommitteesError), - AttestationParticipantsError(AttestationParticipantsError), - InclusionError(InclusionError), - WinningRootError(WinningRootError), -} - -#[derive(Debug, PartialEq)] -pub enum WinningRootError { - NoWinningRoot, - AttestationParticipantsError(AttestationParticipantsError), -} - #[derive(Debug, PartialEq)] pub enum CommitteesError { - InvalidEpoch(u64, Range), + InvalidEpoch, InsufficientNumberOfValidators, + BadRandao, } #[derive(Debug, PartialEq)] @@ -99,12 +40,6 @@ pub enum AttestationParticipantsError { CommitteesError(CommitteesError), } -#[derive(Debug, PartialEq)] -pub enum SlotProcessingError { - CommitteesError(CommitteesError), - EpochProcessingError(EpochError), -} - #[derive(Debug, PartialEq)] pub enum AttestationValidationError { IncludedTooEarly, @@ -118,22 +53,6 @@ pub enum AttestationValidationError { AttestationParticipantsError(AttestationParticipantsError), } -#[derive(Clone)] -pub struct WinningRoot { - pub shard_block_root: Hash256, - pub attesting_validator_indices: Vec, - pub total_balance: u64, - pub total_attesting_balance: u64, -} - -macro_rules! ensure { - ($condition: expr, $result: expr) => { - if !$condition { - return Err($result); - } - }; -} - macro_rules! safe_add_assign { ($a: expr, $b: expr) => { $a = $a.saturating_add($b); @@ -145,45 +64,37 @@ macro_rules! safe_sub_assign { }; } -// Custody will not be added to the specs until Phase 1 (Sharding Phase) so dummy class used. -type CustodyChallenge = usize; - #[derive(Debug, PartialEq, Clone, Default, Serialize)] pub struct BeaconState { // Misc - pub slot: u64, + pub slot: Slot, pub genesis_time: u64, - pub fork_data: Fork, + pub fork: Fork, // Validator registry pub validator_registry: Vec, pub validator_balances: Vec, - pub validator_registry_update_slot: u64, - pub validator_registry_exit_count: u64, - pub validator_registry_delta_chain_tip: Hash256, + pub validator_registry_update_epoch: Epoch, // Randomness and committees pub latest_randao_mixes: Vec, - pub latest_vdf_outputs: Vec, pub previous_epoch_start_shard: u64, pub current_epoch_start_shard: u64, - pub previous_epoch_calculation_slot: u64, - pub current_epoch_calculation_slot: u64, + pub previous_calculation_epoch: Epoch, + pub current_calculation_epoch: Epoch, pub previous_epoch_seed: Hash256, pub current_epoch_seed: Hash256, - // Custody challenges - pub custody_challenges: Vec, - // Finality - pub previous_justified_slot: u64, - pub justified_slot: u64, + pub previous_justified_epoch: Epoch, + pub justified_epoch: Epoch, pub justification_bitfield: u64, - pub finalized_slot: u64, + pub finalized_epoch: Epoch, // Recent state pub latest_crosslinks: Vec, pub latest_block_roots: Vec, + pub latest_index_roots: Vec, pub latest_penalized_balances: Vec, pub latest_attestations: Vec, pub batched_block_roots: Vec, @@ -194,30 +105,151 @@ pub struct BeaconState { } impl BeaconState { + /// Produce the first state of the Beacon Chain. + pub fn genesis( + genesis_time: u64, + initial_validator_deposits: Vec, + latest_eth1_data: Eth1Data, + spec: &ChainSpec, + ) -> BeaconState { + let initial_crosslink = Crosslink { + epoch: spec.genesis_epoch, + shard_block_root: spec.zero_hash, + }; + + let mut genesis_state = BeaconState { + /* + * Misc + */ + slot: spec.genesis_slot, + genesis_time, + fork: Fork { + previous_version: spec.genesis_fork_version, + current_version: spec.genesis_fork_version, + epoch: spec.genesis_epoch, + }, + + /* + * Validator registry + */ + validator_registry: vec![], // Set later in the function. + validator_balances: vec![], // Set later in the function. + validator_registry_update_epoch: spec.genesis_epoch, + + /* + * Randomness and committees + */ + latest_randao_mixes: vec![spec.zero_hash; spec.latest_randao_mixes_length as usize], + previous_epoch_start_shard: spec.genesis_start_shard, + current_epoch_start_shard: spec.genesis_start_shard, + previous_calculation_epoch: spec.genesis_epoch, + current_calculation_epoch: spec.genesis_epoch, + previous_epoch_seed: spec.zero_hash, + current_epoch_seed: spec.zero_hash, + + /* + * Finality + */ + previous_justified_epoch: spec.genesis_epoch, + justified_epoch: spec.genesis_epoch, + justification_bitfield: 0, + finalized_epoch: spec.genesis_epoch, + + /* + * Recent state + */ + latest_crosslinks: vec![initial_crosslink; spec.shard_count as usize], + latest_block_roots: vec![spec.zero_hash; spec.latest_block_roots_length as usize], + latest_index_roots: vec![spec.zero_hash; spec.latest_index_roots_length as usize], + latest_penalized_balances: vec![0; spec.latest_penalized_exit_length as usize], + latest_attestations: vec![], + batched_block_roots: vec![], + + /* + * PoW receipt root + */ + latest_eth1_data, + eth1_data_votes: vec![], + }; + + for deposit in initial_validator_deposits { + let _index = genesis_state.process_deposit( + deposit.deposit_data.deposit_input.pubkey, + deposit.deposit_data.amount, + deposit.deposit_data.deposit_input.proof_of_possession, + deposit.deposit_data.deposit_input.withdrawal_credentials, + spec, + ); + } + + for validator_index in 0..genesis_state.validator_registry.len() { + if genesis_state.get_effective_balance(validator_index, spec) >= spec.max_deposit_amount + { + genesis_state.activate_validator(validator_index, true, spec); + } + } + + let genesis_active_index_root = hash_tree_root(get_active_validator_indices( + &genesis_state.validator_registry, + spec.genesis_epoch, + )); + genesis_state.latest_index_roots = + vec![genesis_active_index_root; spec.latest_index_roots_length]; + genesis_state.current_epoch_seed = genesis_state + .generate_seed(spec.genesis_epoch, spec) + .expect("Unable to generate seed."); + + genesis_state + } + + /// Return the tree hash root for this `BeaconState`. + /// + /// Spec v0.2.0 pub fn canonical_root(&self) -> Hash256 { Hash256::from(&self.hash_tree_root()[..]) } - pub fn current_epoch(&self, spec: &ChainSpec) -> u64 { - self.slot / spec.epoch_length - } - - pub fn previous_epoch(&self, spec: &ChainSpec) -> u64 { - self.current_epoch(spec).saturating_sub(1) - } - - pub fn current_epoch_start_slot(&self, spec: &ChainSpec) -> u64 { - self.current_epoch(spec) * spec.epoch_length - } - - pub fn previous_epoch_start_slot(&self, spec: &ChainSpec) -> u64 { - self.previous_epoch(spec) * spec.epoch_length - } - - /// Returns the number of committees per slot. + /// The epoch corresponding to `self.slot`. /// - /// Note: this is _not_ the committee size. - pub fn get_committee_count_per_slot( + /// Spec v0.2.0 + pub fn current_epoch(&self, spec: &ChainSpec) -> Epoch { + self.slot.epoch(spec.epoch_length) + } + + /// The epoch prior to `self.current_epoch()`. + /// + /// Spec v0.2.0 + pub fn previous_epoch(&self, spec: &ChainSpec) -> Epoch { + self.current_epoch(spec).saturating_sub(1_u64) + } + + /// The epoch following `self.current_epoch()`. + /// + /// Spec v0.2.0 + pub fn next_epoch(&self, spec: &ChainSpec) -> Epoch { + self.current_epoch(spec).saturating_add(1_u64) + } + + /// The first slot of the epoch corresponding to `self.slot`. + /// + /// Spec v0.2.0 + pub fn current_epoch_start_slot(&self, spec: &ChainSpec) -> Slot { + self.current_epoch(spec).start_slot(spec.epoch_length) + } + + /// The first slot of the epoch preceeding the one corresponding to `self.slot`. + /// + /// Spec v0.2.0 + pub fn previous_epoch_start_slot(&self, spec: &ChainSpec) -> Slot { + self.previous_epoch(spec).start_slot(spec.epoch_length) + } + + /// Return the number of committees in one epoch. + /// + /// TODO: this should probably be a method on `ChainSpec`. + /// + /// Spec v0.2.0 + pub fn get_epoch_committee_count( &self, active_validator_count: usize, spec: &ChainSpec, @@ -228,84 +260,160 @@ impl BeaconState { spec.shard_count / spec.epoch_length, active_validator_count as u64 / spec.epoch_length / spec.target_committee_size, ), - ) + ) * spec.epoch_length } - /// Returns the start slot and end slot of the current epoch containing `self.slot`. - pub fn get_current_epoch_boundaries(&self, epoch_length: u64) -> Range { - let slot_in_epoch = self.slot % epoch_length; - let start = self.slot - slot_in_epoch; - let end = self.slot + (epoch_length - slot_in_epoch); - start..end + /// Shuffle ``validators`` into crosslink committees seeded by ``seed`` and ``epoch``. + /// Return a list of ``committees_per_epoch`` committees where each + /// committee is itself a list of validator indices. + /// + /// Spec v0.1 + pub fn get_shuffling(&self, seed: Hash256, epoch: Epoch, spec: &ChainSpec) -> Vec> { + let active_validator_indices = + get_active_validator_indices(&self.validator_registry, epoch); + + let committees_per_epoch = + self.get_epoch_committee_count(active_validator_indices.len(), spec); + + // TODO: check that Hash256::from(u64) matches 'int_to_bytes32'. + let seed = seed ^ Hash256::from(epoch.as_u64()); + // TODO: fix `expect` assert. + let shuffled_active_validator_indices = + shuffle(&seed, active_validator_indices).expect("Max validator count exceed!"); + + shuffled_active_validator_indices + .honey_badger_split(committees_per_epoch as usize) + .map(|slice: &[usize]| slice.to_vec()) + .collect() } - /// Returns the start slot and end slot of the current epoch containing `self.slot`. - pub fn get_previous_epoch_boundaries(&self, spec: &ChainSpec) -> Range { - let current_epoch = self.slot / spec.epoch_length; - let previous_epoch = current_epoch.saturating_sub(1); - let start = previous_epoch * spec.epoch_length; - let end = start + spec.epoch_length; - start..end + /// Return the number of committees in the previous epoch. + /// + /// Spec v0.2.0 + fn get_previous_epoch_committee_count(&self, spec: &ChainSpec) -> u64 { + let previous_active_validators = + get_active_validator_indices(&self.validator_registry, self.previous_calculation_epoch); + self.get_epoch_committee_count(previous_active_validators.len(), spec) } - fn get_previous_epoch_committee_count_per_slot(&self, spec: &ChainSpec) -> u64 { - let previous_active_validators = get_active_validator_indices( - &self.validator_registry, - self.previous_epoch_calculation_slot, - ); - self.get_committee_count_per_slot(previous_active_validators.len(), spec) as u64 + /// Return the number of committees in the current epoch. + /// + /// Spec v0.2.0 + pub fn get_current_epoch_committee_count(&self, spec: &ChainSpec) -> u64 { + let current_active_validators = + get_active_validator_indices(&self.validator_registry, self.current_calculation_epoch); + self.get_epoch_committee_count(current_active_validators.len(), spec) } - pub fn get_current_epoch_committee_count_per_slot(&self, spec: &ChainSpec) -> u64 { - let current_active_validators = get_active_validator_indices( - &self.validator_registry, - self.current_epoch_calculation_slot, - ); - self.get_committee_count_per_slot(current_active_validators.len(), spec) + /// Return the number of committees in the next epoch. + /// + /// Spec v0.2.0 + pub fn get_next_epoch_committee_count(&self, spec: &ChainSpec) -> u64 { + let current_active_validators = + get_active_validator_indices(&self.validator_registry, self.next_epoch(spec)); + self.get_epoch_committee_count(current_active_validators.len(), spec) } + pub fn get_active_index_root(&self, epoch: Epoch, spec: &ChainSpec) -> Option { + let current_epoch = self.current_epoch(spec); + + let earliest_index_root = current_epoch - Epoch::from(spec.latest_index_roots_length) + + Epoch::from(spec.entry_exit_delay) + + 1; + let latest_index_root = current_epoch + spec.entry_exit_delay; + + if (epoch <= earliest_index_root) & (epoch >= latest_index_root) { + Some(self.latest_index_roots[epoch.as_usize() % spec.latest_index_roots_length]) + } else { + None + } + } + + /// Generate a seed for the given ``epoch``. + /// + /// Spec v0.2.0 + pub fn generate_seed(&self, epoch: Epoch, spec: &ChainSpec) -> Option { + let mut input = self.get_randao_mix(epoch, spec)?.to_vec(); + input.append(&mut self.get_active_index_root(epoch, spec)?.to_vec()); + // TODO: ensure `Hash256::from(u64)` == `int_to_bytes32`. + input.append(&mut Hash256::from(epoch.as_u64()).to_vec()); + Some(Hash256::from(&hash(&input[..])[..])) + } + + /// Return the list of ``(committee, shard)`` tuples for the ``slot``. + /// + /// Note: There are two possible shufflings for crosslink committees for a + /// `slot` in the next epoch: with and without a `registry_change` + /// + /// Spec v0.2.0 pub fn get_crosslink_committees_at_slot( &self, - slot: u64, + slot: Slot, + registry_change: bool, spec: &ChainSpec, ) -> Result, u64)>, CommitteesError> { - let epoch = slot / spec.epoch_length; - let current_epoch = self.slot / spec.epoch_length; - let previous_epoch = if current_epoch == spec.genesis_slot { + let epoch = slot.epoch(spec.epoch_length); + let current_epoch = self.current_epoch(spec); + let previous_epoch = if current_epoch == spec.genesis_epoch { current_epoch } else { - current_epoch.saturating_sub(1) + current_epoch.saturating_sub(1_u64) }; - let next_epoch = current_epoch + 1; + let next_epoch = self.next_epoch(spec); - ensure!( - (previous_epoch <= epoch) & (epoch < next_epoch), - CommitteesError::InvalidEpoch(slot, previous_epoch..current_epoch) - ); + let (committees_per_epoch, seed, shuffling_epoch, shuffling_start_shard) = + if epoch == previous_epoch { + ( + self.get_previous_epoch_committee_count(spec), + self.previous_epoch_seed, + self.previous_calculation_epoch, + self.previous_epoch_start_shard, + ) + } else if epoch == current_epoch { + ( + self.get_current_epoch_committee_count(spec), + self.current_epoch_seed, + self.current_calculation_epoch, + self.current_epoch_start_shard, + ) + } else if epoch == next_epoch { + let current_committees_per_epoch = self.get_current_epoch_committee_count(spec); + let epochs_since_last_registry_update = + current_epoch - self.validator_registry_update_epoch; + let (seed, shuffling_start_shard) = if registry_change { + let next_seed = self + .generate_seed(next_epoch, spec) + .ok_or_else(|| CommitteesError::BadRandao)?; + ( + next_seed, + (self.current_epoch_start_shard + current_committees_per_epoch) + % spec.shard_count, + ) + } else if (epochs_since_last_registry_update > 1) + & epochs_since_last_registry_update.is_power_of_two() + { + let next_seed = self + .generate_seed(next_epoch, spec) + .ok_or_else(|| CommitteesError::BadRandao)?; + (next_seed, self.current_epoch_start_shard) + } else { + (self.current_epoch_seed, self.current_epoch_start_shard) + }; + ( + self.get_next_epoch_committee_count(spec), + seed, + next_epoch, + shuffling_start_shard, + ) + } else { + panic!("Epoch out-of-bounds.") + }; - let offset = slot % spec.epoch_length; - - let (committees_per_slot, shuffling, slot_start_shard) = if epoch < current_epoch { - let committees_per_slot = self.get_previous_epoch_committee_count_per_slot(spec); - let shuffling = self.get_shuffling( - self.previous_epoch_seed, - self.previous_epoch_calculation_slot, - spec, - ); - let slot_start_shard = - (self.previous_epoch_start_shard + committees_per_slot * offset) % spec.shard_count; - (committees_per_slot, shuffling, slot_start_shard) - } else { - let committees_per_slot = self.get_current_epoch_committee_count_per_slot(spec); - let shuffling = self.get_shuffling( - self.current_epoch_seed, - self.current_epoch_calculation_slot, - spec, - ); - let slot_start_shard = - (self.current_epoch_start_shard + committees_per_slot * offset) % spec.shard_count; - (committees_per_slot, shuffling, slot_start_shard) - }; + let shuffling = self.get_shuffling(seed, shuffling_epoch, spec); + let offset = slot.as_u64() % spec.epoch_length; + let committees_per_slot = committees_per_epoch / spec.epoch_length; + let slot_start_shard = + (shuffling_start_shard + committees_per_slot * offset) % spec.shard_count; let mut crosslinks_at_slot = vec![]; for i in 0..committees_per_slot { @@ -318,42 +426,18 @@ impl BeaconState { Ok(crosslinks_at_slot) } - pub fn per_slot_processing( - &mut self, - previous_block_root: Hash256, - spec: &ChainSpec, - ) -> Result<(), SlotProcessingError> { - if (self.slot + 1) % spec.epoch_length == 0 { - self.per_epoch_processing(spec)?; - } - - self.slot += 1; - - let block_proposer = self.get_beacon_proposer_index(self.slot, spec)?; - - self.validator_registry[block_proposer].proposer_slots += 1; - self.latest_randao_mixes[(self.slot % spec.latest_randao_mixes_length) as usize] = - self.latest_randao_mixes[((self.slot - 1) % spec.latest_randao_mixes_length) as usize]; - - // Block roots. - self.latest_block_roots[((self.slot - 1) % spec.latest_block_roots_length) as usize] = - previous_block_root; - - if self.slot % spec.latest_block_roots_length == 0 { - let root = merkle_root(&self.latest_block_roots[..]); - self.batched_block_roots.push(root); - } - Ok(()) - } - + /// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an + /// attestation. + /// + /// Spec v0.2.0 pub fn attestation_slot_and_shard_for_validator( &self, validator_index: usize, spec: &ChainSpec, - ) -> Result, CommitteesError> { + ) -> Result, CommitteesError> { let mut result = None; - for slot in self.get_current_epoch_boundaries(spec.epoch_length) { - for (committee, shard) in self.get_crosslink_committees_at_slot(slot, spec)? { + for slot in self.current_epoch(spec).slot_iter(spec.epoch_length) { + for (committee, shard) in self.get_crosslink_committees_at_slot(slot, false, spec)? { if let Some(committee_index) = committee.iter().position(|&i| i == validator_index) { result = Some((slot, shard, committee_index as u64)); @@ -363,791 +447,58 @@ impl BeaconState { Ok(result) } - pub fn per_block_processing( - &mut self, - block: &BeaconBlock, - spec: &ChainSpec, - ) -> Result<(), BlockProcessingError> { - self.per_block_processing_signature_optional(block, true, spec) - } - - pub fn per_block_processing_without_verifying_block_signature( - &mut self, - block: &BeaconBlock, - spec: &ChainSpec, - ) -> Result<(), BlockProcessingError> { - self.per_block_processing_signature_optional(block, false, spec) - } - - fn per_block_processing_signature_optional( - &mut self, - block: &BeaconBlock, - verify_block_signature: bool, - spec: &ChainSpec, - ) -> Result<(), BlockProcessingError> { - ensure!( - block.slot == self.slot, - BlockProcessingError::StateSlotMismatch - ); - - /* - * Proposer Signature - */ - let block_proposer_index = self - .get_beacon_proposer_index(block.slot, spec) - .map_err(|_| BlockProcessingError::NoBlockProducer)?; - let block_proposer = &self.validator_registry[block_proposer_index]; - - if verify_block_signature { - ensure!( - bls_verify( - &block_proposer.pubkey, - &block.proposal_root(spec)[..], - &block.signature, - get_domain(&self.fork_data, self.slot, DOMAIN_PROPOSAL) - ), - BlockProcessingError::BadBlockSignature - ); - } - - /* - * RANDAO - */ - ensure!( - bls_verify( - &block_proposer.pubkey, - &ssz_encode(&block_proposer.proposer_slots), - &block.randao_reveal, - get_domain(&self.fork_data, self.slot, DOMAIN_RANDAO) - ), - BlockProcessingError::BadRandaoSignature - ); - - // TODO: check this is correct. - let new_mix = { - let mut mix = self.latest_randao_mixes - [(self.slot % spec.latest_randao_mixes_length) as usize] - .to_vec(); - mix.append(&mut ssz_encode(&block.randao_reveal)); - Hash256::from(&hash(&mix)[..]) - }; - - self.latest_randao_mixes[(self.slot % spec.latest_randao_mixes_length) as usize] = new_mix; - - /* - * Eth1 data - */ - - // TODO: Eth1 data processing. - - /* - * Proposer slashings - */ - ensure!( - block.body.proposer_slashings.len() as u64 <= spec.max_proposer_slashings, - BlockProcessingError::MaxProposerSlashingsExceeded - ); - for proposer_slashing in &block.body.proposer_slashings { - let proposer = self - .validator_registry - .get(proposer_slashing.proposer_index as usize) - .ok_or(BlockProcessingError::BadProposerSlashing)?; - ensure!( - proposer_slashing.proposal_data_1.slot == proposer_slashing.proposal_data_2.slot, - BlockProcessingError::BadProposerSlashing - ); - ensure!( - proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard, - BlockProcessingError::BadProposerSlashing - ); - ensure!( - proposer_slashing.proposal_data_1.block_root - != proposer_slashing.proposal_data_2.block_root, - BlockProcessingError::BadProposerSlashing - ); - ensure!( - proposer.penalized_slot > self.slot, - BlockProcessingError::BadProposerSlashing - ); - ensure!( - bls_verify( - &proposer.pubkey, - &proposer_slashing.proposal_data_1.hash_tree_root(), - &proposer_slashing.proposal_signature_1, - get_domain( - &self.fork_data, - proposer_slashing.proposal_data_1.slot, - DOMAIN_PROPOSAL - ) - ), - BlockProcessingError::BadProposerSlashing - ); - ensure!( - bls_verify( - &proposer.pubkey, - &proposer_slashing.proposal_data_2.hash_tree_root(), - &proposer_slashing.proposal_signature_2, - get_domain( - &self.fork_data, - proposer_slashing.proposal_data_2.slot, - DOMAIN_PROPOSAL - ) - ), - BlockProcessingError::BadProposerSlashing - ); - penalize_validator(&self, proposer_slashing.proposer_index as usize); - } - - /* - * Attestations - */ - ensure!( - block.body.attestations.len() as u64 <= spec.max_attestations, - BlockProcessingError::MaxAttestationsExceeded - ); - - for attestation in &block.body.attestations { - self.validate_attestation(attestation, spec)?; - - let pending_attestation = PendingAttestation { - data: attestation.data.clone(), - aggregation_bitfield: attestation.aggregation_bitfield.clone(), - custody_bitfield: attestation.custody_bitfield.clone(), - slot_included: self.slot, - }; - self.latest_attestations.push(pending_attestation); - } - - debug!( - "{} attestations verified & processed.", - block.body.attestations.len() - ); - - /* - * Deposits - */ - ensure!( - block.body.deposits.len() as u64 <= spec.max_deposits, - BlockProcessingError::MaxDepositsExceeded - ); - - // TODO: process deposits. - - /* - * Exits - */ - - ensure!( - block.body.exits.len() as u64 <= spec.max_exits, - BlockProcessingError::MaxExitsExceeded - ); - - for exit in &block.body.exits { - let validator = self - .validator_registry - .get(exit.validator_index as usize) - .ok_or(BlockProcessingError::BadExit)?; - ensure!( - validator.exit_slot > self.slot + spec.entry_exit_delay, - BlockProcessingError::BadExit - ); - ensure!(self.slot >= exit.slot, BlockProcessingError::BadExit); - let exit_message = { - let exit_struct = Exit { - slot: exit.slot, - validator_index: exit.validator_index, - signature: spec.empty_signature.clone(), - }; - exit_struct.hash_tree_root() - }; - ensure!( - bls_verify( - &validator.pubkey, - &exit_message, - &exit.signature, - get_domain(&self.fork_data, exit.slot, DOMAIN_EXIT) - ), - BlockProcessingError::BadProposerSlashing - ); - initiate_validator_exit(&self, exit.validator_index); - } - - /* - * Custody - */ - ensure!( - block.body.custody_reseeds.is_empty(), - BlockProcessingError::BadCustodyReseeds - ); - ensure!( - block.body.custody_challenges.is_empty(), - BlockProcessingError::BadCustodyChallenges - ); - ensure!( - block.body.custody_responses.is_empty(), - BlockProcessingError::BadCustodyResponses - ); - - debug!("State transition complete."); - - Ok(()) - } - - pub fn get_shuffling(&self, seed: Hash256, slot: u64, spec: &ChainSpec) -> Vec> { - let slot = slot - (slot % spec.epoch_length); - - let active_validator_indices = get_active_validator_indices(&self.validator_registry, slot); - - let committees_per_slot = - self.get_committee_count_per_slot(active_validator_indices.len(), spec); - - // TODO: check that Hash256 matches 'int_to_bytes32'. - let seed = seed ^ Hash256::from(slot); - let shuffled_active_validator_indices = - shuffle(&seed, active_validator_indices).expect("Max validator count exceed!"); - - shuffled_active_validator_indices - .honey_badger_split((committees_per_slot * spec.epoch_length) as usize) - .filter_map(|slice: &[usize]| Some(slice.to_vec())) - .collect() + /// An entry or exit triggered in the ``epoch`` given by the input takes effect at + /// the epoch given by the output. + /// + /// Spec v0.2.0 + pub fn get_entry_exit_effect_epoch(&self, epoch: Epoch, spec: &ChainSpec) -> Epoch { + epoch + 1 + spec.entry_exit_delay } /// Returns the beacon proposer index for the `slot`. + /// /// If the state does not contain an index for a beacon proposer at the requested `slot`, then `None` is returned. + /// + /// Spec v0.2.0 pub fn get_beacon_proposer_index( &self, - slot: u64, + slot: Slot, spec: &ChainSpec, ) -> Result { - let committees = self.get_crosslink_committees_at_slot(slot, spec)?; + let committees = self.get_crosslink_committees_at_slot(slot, false, spec)?; committees .first() .ok_or(CommitteesError::InsufficientNumberOfValidators) .and_then(|(first_committee, _)| { - let index = (slot as usize) + let index = (slot.as_usize()) .checked_rem(first_committee.len()) .ok_or(CommitteesError::InsufficientNumberOfValidators)?; - // NOTE: next index will not panic as we have already returned if this is the case + // NOTE: next index will not panic as we have already returned if this is the case. Ok(first_committee[index]) }) } - pub fn per_epoch_processing(&mut self, spec: &ChainSpec) -> Result<(), EpochError> { - debug!( - "Starting per-epoch processing on epoch {}...", - self.current_epoch(spec) - ); - /* - * All Validators - */ + /// Process the penalties and prepare the validators who are eligible to withdrawal. + /// + /// Spec v0.2.0 + pub fn process_penalties_and_exits(&mut self, spec: &ChainSpec) { + let current_epoch = self.current_epoch(spec); let active_validator_indices = - get_active_validator_indices(&self.validator_registry, self.slot); - let total_balance = self.get_effective_balances(&active_validator_indices[..], spec); - - debug!( - "{} validators with a total balance of {} wei.", - active_validator_indices.len(), - total_balance - ); - - let current_epoch_attestations: Vec<&PendingAttestation> = self - .latest_attestations - .par_iter() - .filter(|a| a.data.slot / spec.epoch_length == self.current_epoch(spec)) - .collect(); - - debug!( - "Current epoch attestations: {}", - current_epoch_attestations.len() - ); - - /* - * Validators attesting during the current epoch. - */ - if self.latest_block_roots.is_empty() { - return Err(EpochError::NoBlockRoots); - } - - let current_epoch_boundary_attestations: Vec<&PendingAttestation> = - current_epoch_attestations - .par_iter() - .filter(|a| { - match self.get_block_root(self.current_epoch_start_slot(spec), spec) { - Some(block_root) => { - (a.data.epoch_boundary_root == *block_root) - && (a.data.justified_slot == self.justified_slot) - } - // Protected by a check that latest_block_roots isn't empty. - // - // TODO: provide detailed reasoning. - None => unreachable!(), - } - }) - .cloned() - .collect(); - - let current_epoch_boundary_attester_indices = self - .get_attestation_participants_union(¤t_epoch_boundary_attestations[..], spec)?; - let current_epoch_boundary_attesting_balance = - self.get_effective_balances(¤t_epoch_boundary_attester_indices[..], spec); - - debug!( - "Current epoch boundary attesters: {}", - current_epoch_boundary_attester_indices.len() - ); - - /* - * Validators attesting during the previous epoch - */ - - /* - * Validators that made an attestation during the previous epoch - */ - let previous_epoch_attestations: Vec<&PendingAttestation> = self - .latest_attestations - .par_iter() - .filter(|a| { - //TODO: ensure these saturating subs are correct. - a.data.slot / spec.epoch_length == self.previous_epoch(spec) - }) - .collect(); - - debug!( - "previous epoch attestations: {}", - previous_epoch_attestations.len() - ); - - let previous_epoch_attester_indices = - self.get_attestation_participants_union(&previous_epoch_attestations[..], spec)?; - - /* - * Validators targetting the previous justified slot - */ - let previous_epoch_justified_attestations: Vec<&PendingAttestation> = { - let mut a: Vec<&PendingAttestation> = current_epoch_attestations - .iter() - .filter(|a| a.data.justified_slot == self.previous_justified_slot) - .cloned() - .collect(); - let mut b: Vec<&PendingAttestation> = previous_epoch_attestations - .iter() - .filter(|a| a.data.justified_slot == self.previous_justified_slot) - .cloned() - .collect(); - a.append(&mut b); - a - }; - - let previous_epoch_justified_attester_indices = self - .get_attestation_participants_union(&previous_epoch_justified_attestations[..], spec)?; - let previous_epoch_justified_attesting_balance = - self.get_effective_balances(&previous_epoch_justified_attester_indices[..], spec); - - /* - * Validators justifying the epoch boundary block at the start of the previous epoch - */ - let previous_epoch_boundary_attestations: Vec<&PendingAttestation> = - previous_epoch_justified_attestations - .iter() - .filter(|a| { - match self.get_block_root(self.previous_epoch_start_slot(spec), spec) { - Some(block_root) => a.data.epoch_boundary_root == *block_root, - // Protected by a check that latest_block_roots isn't empty. - // - // TODO: provide detailed reasoning. - None => unreachable!(), - } - }) - .cloned() - .collect(); - - let previous_epoch_boundary_attester_indices = self - .get_attestation_participants_union(&previous_epoch_boundary_attestations[..], spec)?; - let previous_epoch_boundary_attesting_balance = - self.get_effective_balances(&previous_epoch_boundary_attester_indices[..], spec); - - /* - * Validators attesting to the expected beacon chain head during the previous epoch. - */ - let previous_epoch_head_attestations: Vec<&PendingAttestation> = - previous_epoch_attestations - .iter() - .filter(|a| { - match self.get_block_root(a.data.slot, spec) { - Some(block_root) => a.data.beacon_block_root == *block_root, - // Protected by a check that latest_block_roots isn't empty. - // - // TODO: provide detailed reasoning. - None => unreachable!(), - } - }) - .cloned() - .collect(); - - let previous_epoch_head_attester_indices = - self.get_attestation_participants_union(&previous_epoch_head_attestations[..], spec)?; - let previous_epoch_head_attesting_balance = - self.get_effective_balances(&previous_epoch_head_attester_indices[..], spec); - - debug!( - "previous_epoch_head_attester_balance of {} wei.", - previous_epoch_head_attesting_balance - ); - - /* - * Eth1 Data - */ - if self.slot % spec.eth1_data_voting_period == 0 { - for eth1_data_vote in &self.eth1_data_votes { - if eth1_data_vote.vote_count * 2 > spec.eth1_data_voting_period { - self.latest_eth1_data = eth1_data_vote.eth1_data.clone(); - } - } - self.eth1_data_votes = vec![]; - } - - /* - * Justification - */ - self.previous_justified_slot = self.justified_slot; - let (new_bitfield, _) = self.justification_bitfield.overflowing_mul(2); - self.justification_bitfield = new_bitfield; - - // If >= 2/3 of validators voted for the previous epoch boundary - if (3 * previous_epoch_boundary_attesting_balance) >= (2 * total_balance) { - // TODO: check saturating_sub is correct. - self.justification_bitfield |= 2; - self.justified_slot = self.slot.saturating_sub(2 * spec.epoch_length); - debug!(">= 2/3 voted for previous epoch boundary"); - } - - // If >= 2/3 of validators voted for the current epoch boundary - if (3 * current_epoch_boundary_attesting_balance) >= (2 * total_balance) { - // TODO: check saturating_sub is correct. - self.justification_bitfield |= 1; - self.justified_slot = self.slot.saturating_sub(1 * spec.epoch_length); - debug!(">= 2/3 voted for current epoch boundary"); - } - - if (self.previous_justified_slot == self.slot.saturating_sub(2 * spec.epoch_length)) - && (self.justification_bitfield % 4 == 3) - { - self.finalized_slot = self.previous_justified_slot; - } - if (self.previous_justified_slot == self.slot.saturating_sub(3 * spec.epoch_length)) - && (self.justification_bitfield % 8 == 7) - { - self.finalized_slot = self.previous_justified_slot; - } - if (self.previous_justified_slot == self.slot.saturating_sub(4 * spec.epoch_length)) - && (self.justification_bitfield % 16 == 14) - { - self.finalized_slot = self.previous_justified_slot; - } - if (self.previous_justified_slot == self.slot.saturating_sub(4 * spec.epoch_length)) - && (self.justification_bitfield % 16 == 15) - { - self.finalized_slot = self.previous_justified_slot; - } - - debug!( - "Finalized slot {}, justified slot {}.", - self.finalized_slot, self.justified_slot - ); - - /* - * Crosslinks - */ - - // Cached for later lookups. - let mut winning_root_for_shards: HashMap> = - HashMap::new(); - - // for slot in self.slot.saturating_sub(2 * spec.epoch_length)..self.slot { - for slot in self.get_previous_epoch_boundaries(spec) { - let crosslink_committees_at_slot = self.get_crosslink_committees_at_slot(slot, spec)?; - - for (crosslink_committee, shard) in crosslink_committees_at_slot { - let shard = shard as u64; - - let winning_root = self.winning_root( - shard, - ¤t_epoch_attestations, - &previous_epoch_attestations, - spec, - ); - - if let Ok(winning_root) = &winning_root { - let total_committee_balance = - self.get_effective_balances(&crosslink_committee[..], spec); - - if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) { - self.latest_crosslinks[shard as usize] = Crosslink { - slot: self.slot, - shard_block_root: winning_root.shard_block_root, - } - } - } - winning_root_for_shards.insert(shard, winning_root); - } - } - - debug!( - "Found {} winning shard roots.", - winning_root_for_shards.len() - ); - - /* - * Rewards and Penalities - */ - let base_reward_quotient = total_balance.integer_sqrt(); - if base_reward_quotient == 0 { - return Err(EpochError::BaseRewardQuotientIsZero); - } - - /* - * Justification and finalization - */ - let epochs_since_finality = - self.slot.saturating_sub(self.finalized_slot) / spec.epoch_length; - - // TODO: fix this extra map - let previous_epoch_justified_attester_indices_hashset: HashSet = - HashSet::from_iter(previous_epoch_justified_attester_indices.iter().map(|i| *i)); - let previous_epoch_boundary_attester_indices_hashset: HashSet = - HashSet::from_iter(previous_epoch_boundary_attester_indices.iter().map(|i| *i)); - let previous_epoch_head_attester_indices_hashset: HashSet = - HashSet::from_iter(previous_epoch_head_attester_indices.iter().map(|i| *i)); - let previous_epoch_attester_indices_hashset: HashSet = - HashSet::from_iter(previous_epoch_attester_indices.iter().map(|i| *i)); - - debug!("previous epoch justified attesters: {}, previous epoch boundary attesters: {}, previous epoch head attesters: {}, previous epoch attesters: {}", previous_epoch_justified_attester_indices.len(), previous_epoch_boundary_attester_indices.len(), previous_epoch_head_attester_indices.len(), previous_epoch_attester_indices.len()); - - debug!("{} epochs since finality.", epochs_since_finality); - - if epochs_since_finality <= 4 { - for index in 0..self.validator_balances.len() { - let base_reward = self.base_reward(index, base_reward_quotient, spec); - - if previous_epoch_justified_attester_indices_hashset.contains(&index) { - safe_add_assign!( - self.validator_balances[index], - base_reward * previous_epoch_justified_attesting_balance / total_balance - ); - } else { - safe_sub_assign!(self.validator_balances[index], base_reward); - } - - if previous_epoch_boundary_attester_indices_hashset.contains(&index) { - safe_add_assign!( - self.validator_balances[index], - base_reward * previous_epoch_boundary_attesting_balance / total_balance - ); - } else { - safe_sub_assign!(self.validator_balances[index], base_reward); - } - - if previous_epoch_head_attester_indices_hashset.contains(&index) { - safe_add_assign!( - self.validator_balances[index], - base_reward * previous_epoch_head_attesting_balance / total_balance - ); - } else { - safe_sub_assign!(self.validator_balances[index], base_reward); - } - } - - for index in previous_epoch_attester_indices { - let base_reward = self.base_reward(index, base_reward_quotient, spec); - let inclusion_distance = - self.inclusion_distance(&previous_epoch_attestations, index, spec)?; - - safe_add_assign!( - self.validator_balances[index], - base_reward * spec.min_attestation_inclusion_delay / inclusion_distance - ) - } - } else { - for index in 0..self.validator_balances.len() { - let inactivity_penalty = self.inactivity_penalty( - index, - epochs_since_finality, - base_reward_quotient, - spec, - ); - - if !previous_epoch_justified_attester_indices_hashset.contains(&index) { - safe_sub_assign!(self.validator_balances[index], inactivity_penalty); - } - - if !previous_epoch_boundary_attester_indices_hashset.contains(&index) { - safe_sub_assign!(self.validator_balances[index], inactivity_penalty); - } - - if !previous_epoch_head_attester_indices_hashset.contains(&index) { - safe_sub_assign!(self.validator_balances[index], inactivity_penalty); - } - } - - for index in previous_epoch_attester_indices { - let base_reward = self.base_reward(index, base_reward_quotient, spec); - let inclusion_distance = - self.inclusion_distance(&previous_epoch_attestations, index, spec)?; - - safe_sub_assign!( - self.validator_balances[index], - base_reward - - base_reward * spec.min_attestation_inclusion_delay / inclusion_distance - ); - } - } - - debug!("Processed validator justification and finalization rewards/penalities."); - - /* - * Attestation inclusion - */ - for &index in &previous_epoch_attester_indices_hashset { - let inclusion_slot = - self.inclusion_slot(&previous_epoch_attestations[..], index, spec)?; - let proposer_index = self - .get_beacon_proposer_index(inclusion_slot, spec) - .map_err(|_| EpochError::UnableToDetermineProducer)?; - let base_reward = self.base_reward(proposer_index, base_reward_quotient, spec); - safe_add_assign!( - self.validator_balances[proposer_index], - base_reward / spec.includer_reward_quotient - ); - } - - debug!( - "Previous epoch attesters: {}.", - previous_epoch_attester_indices_hashset.len() - ); - - /* - * Crosslinks - */ - for slot in self.get_previous_epoch_boundaries(spec) { - let crosslink_committees_at_slot = self.get_crosslink_committees_at_slot(slot, spec)?; - - for (_crosslink_committee, shard) in crosslink_committees_at_slot { - let shard = shard as u64; - - if let Some(Ok(winning_root)) = winning_root_for_shards.get(&shard) { - // TODO: remove the map. - let attesting_validator_indices: HashSet = HashSet::from_iter( - winning_root.attesting_validator_indices.iter().map(|i| *i), - ); - - for index in 0..self.validator_balances.len() { - let base_reward = self.base_reward(index, base_reward_quotient, spec); - - if attesting_validator_indices.contains(&index) { - safe_add_assign!( - self.validator_balances[index], - base_reward * winning_root.total_attesting_balance - / winning_root.total_balance - ); - } else { - safe_sub_assign!(self.validator_balances[index], base_reward); - } - } - - for index in &winning_root.attesting_validator_indices { - let base_reward = self.base_reward(*index, base_reward_quotient, spec); - safe_add_assign!( - self.validator_balances[*index], - base_reward * winning_root.total_attesting_balance - / winning_root.total_balance - ); - } - } - } - } - - /* - * Ejections - */ - self.process_ejections(); - - /* - * Validator Registry - */ - self.previous_epoch_calculation_slot = self.current_epoch_calculation_slot; - self.previous_epoch_start_shard = self.current_epoch_start_shard; - self.previous_epoch_seed = self.current_epoch_seed; - - let should_update_validator_registy = if self.finalized_slot - > self.validator_registry_update_slot - { - (0..self.get_current_epoch_committee_count_per_slot(spec)).all(|i| { - let shard = (self.current_epoch_start_shard + i as u64) % spec.shard_count; - self.latest_crosslinks[shard as usize].slot > self.validator_registry_update_slot - }) - } else { - false - }; - - if should_update_validator_registy { - self.update_validator_registry(spec); - - self.current_epoch_calculation_slot = self.slot; - self.current_epoch_start_shard = (self.current_epoch_start_shard - + self.get_current_epoch_committee_count_per_slot(spec) as u64 * spec.epoch_length) - % spec.shard_count; - self.current_epoch_seed = self.get_randao_mix( - self.current_epoch_calculation_slot - .saturating_sub(spec.seed_lookahead), - spec, - ); - } else { - let epochs_since_last_registry_change = - (self.slot - self.validator_registry_update_slot) / spec.epoch_length; - if epochs_since_last_registry_change.is_power_of_two() { - self.current_epoch_calculation_slot = self.slot; - self.current_epoch_seed = self.get_randao_mix( - self.current_epoch_calculation_slot - .saturating_sub(spec.seed_lookahead), - spec, - ); - } - } - - self.process_penalties_and_exits(spec); - - let e = self.slot / spec.epoch_length; - self.latest_penalized_balances[((e + 1) % spec.latest_penalized_exit_length) as usize] = - self.latest_penalized_balances[(e % spec.latest_penalized_exit_length) as usize]; - - self.latest_attestations = self - .latest_attestations - .iter() - .filter(|a| a.data.slot / spec.epoch_length >= self.current_epoch(spec)) - .cloned() - .collect(); - - debug!("Epoch transition complete."); - - Ok(()) - } - - fn process_penalties_and_exits(&mut self, spec: &ChainSpec) { - let active_validator_indices = - get_active_validator_indices(&self.validator_registry, self.slot); - let total_balance = self.get_effective_balances(&active_validator_indices[..], spec); + get_active_validator_indices(&self.validator_registry, current_epoch); + let total_balance = self.get_total_balance(&active_validator_indices[..], spec); for index in 0..self.validator_balances.len() { let validator = &self.validator_registry[index]; - if (self.slot / spec.epoch_length) - == (validator.penalized_slot / spec.epoch_length) - + spec.latest_penalized_exit_length / 2 + if current_epoch + == validator.penalized_epoch + Epoch::from(spec.latest_penalized_exit_length / 2) { - let e = (self.slot / spec.epoch_length) % spec.latest_penalized_exit_length; + let epoch_index: usize = + current_epoch.as_usize() % spec.latest_penalized_exit_length; + let total_at_start = self.latest_penalized_balances - [((e + 1) % spec.latest_penalized_exit_length) as usize]; - let total_at_end = self.latest_penalized_balances[e as usize]; + [(epoch_index + 1) % spec.latest_penalized_exit_length]; + let total_at_end = self.latest_penalized_balances[epoch_index]; let total_penalities = total_at_end.saturating_sub(total_at_start); let penalty = self.get_effective_balance(index, spec) * std::cmp::min(total_penalities * 3, total_balance) @@ -1159,47 +510,47 @@ impl BeaconState { let eligible = |index: usize| { let validator = &self.validator_registry[index]; - if validator.penalized_slot <= self.slot { - let penalized_withdrawal_time = - spec.latest_penalized_exit_length * spec.epoch_length / 2; - self.slot >= validator.penalized_slot + penalized_withdrawal_time + if validator.penalized_epoch <= current_epoch { + let penalized_withdrawal_epochs = spec.latest_penalized_exit_length / 2; + current_epoch >= validator.penalized_epoch + penalized_withdrawal_epochs as u64 } else { - self.slot >= validator.exit_slot + spec.min_validator_withdrawal_time + current_epoch >= validator.exit_epoch + spec.min_validator_withdrawal_epochs } }; let mut eligable_indices: Vec = (0..self.validator_registry.len()) .filter(|i| eligible(*i)) .collect(); - eligable_indices.sort_by_key(|i| self.validator_registry[*i].exit_count); - let mut withdrawn_so_far = 0; - for index in eligable_indices { - self.prepare_validator_for_withdrawal(index); - withdrawn_so_far += 1; - if withdrawn_so_far >= spec.max_withdrawals_per_epoch { + eligable_indices.sort_by_key(|i| self.validator_registry[*i].exit_epoch); + for (withdrawn_so_far, index) in eligable_indices.iter().enumerate() { + self.prepare_validator_for_withdrawal(*index); + if withdrawn_so_far as u64 >= spec.max_withdrawals_per_epoch { break; } } } - fn prepare_validator_for_withdrawal(&mut self, index: usize) { - //TODO: we're not ANDing here, we're setting. Potentially wrong. - self.validator_registry[index].status_flags = Some(StatusFlags::Withdrawable); + /// Return the randao mix at a recent ``epoch``. + /// + /// Returns `None` if the epoch is out-of-bounds of `self.latest_randao_mixes`. + /// + /// Spec v0.2.0 + pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Option<&Hash256> { + self.latest_randao_mixes + .get(epoch.as_usize() % spec.latest_randao_mixes_length) } - fn get_randao_mix(&mut self, slot: u64, spec: &ChainSpec) -> Hash256 { - assert!(self.slot < slot + spec.latest_randao_mixes_length); - assert!(slot <= self.slot); - self.latest_randao_mixes[(slot & spec.latest_randao_mixes_length) as usize] - } - - fn update_validator_registry(&mut self, spec: &ChainSpec) { + /// Update validator registry, activating/exiting validators if possible. + /// + /// Spec v0.2.0 + pub fn update_validator_registry(&mut self, spec: &ChainSpec) { + let current_epoch = self.current_epoch(spec); let active_validator_indices = - get_active_validator_indices(&self.validator_registry, self.slot); - let total_balance = self.get_effective_balances(&active_validator_indices[..], spec); + get_active_validator_indices(&self.validator_registry, current_epoch); + let total_balance = self.get_total_balance(&active_validator_indices[..], spec); let max_balance_churn = std::cmp::max( - spec.max_deposit, + spec.max_deposit_amount, total_balance / (2 * spec.max_balance_churn_quotient), ); @@ -1207,14 +558,13 @@ impl BeaconState { for index in 0..self.validator_registry.len() { let validator = &self.validator_registry[index]; - if (validator.activation_slot > self.slot + spec.entry_exit_delay) - && self.validator_balances[index] >= spec.max_deposit + if (validator.activation_epoch > self.get_entry_exit_effect_epoch(current_epoch, spec)) + && self.validator_balances[index] >= spec.max_deposit_amount { balance_churn += self.get_effective_balance(index, spec); if balance_churn > max_balance_churn { break; } - self.activate_validator(index, false, spec); } } @@ -1223,7 +573,7 @@ impl BeaconState { for index in 0..self.validator_registry.len() { let validator = &self.validator_registry[index]; - if (validator.exit_slot > self.slot + spec.entry_exit_delay) + if (validator.exit_epoch > self.get_entry_exit_effect_epoch(current_epoch, spec)) && validator.status_flags == Some(StatusFlags::InitiatedExit) { balance_churn += self.get_effective_balance(index, spec); @@ -1235,52 +585,173 @@ impl BeaconState { } } - self.validator_registry_update_slot = self.slot; + self.validator_registry_update_epoch = current_epoch; + } + /// Process a validator deposit, returning the validator index if the deposit is valid. + /// + /// Spec v0.2.0 + pub fn process_deposit( + &mut self, + pubkey: PublicKey, + amount: u64, + proof_of_possession: Signature, + withdrawal_credentials: Hash256, + spec: &ChainSpec, + ) -> Result { + // TODO: ensure verify proof-of-possession represents the spec accurately. + if !verify_proof_of_possession(&proof_of_possession, &pubkey) { + return Err(()); + } + + if let Some(index) = self + .validator_registry + .iter() + .position(|v| v.pubkey == pubkey) + { + if self.validator_registry[index].withdrawal_credentials == withdrawal_credentials { + safe_add_assign!(self.validator_balances[index], amount); + Ok(index) + } else { + Err(()) + } + } else { + let validator = Validator { + pubkey, + withdrawal_credentials, + activation_epoch: spec.far_future_epoch, + exit_epoch: spec.far_future_epoch, + withdrawal_epoch: spec.far_future_epoch, + penalized_epoch: spec.far_future_epoch, + status_flags: None, + }; + self.validator_registry.push(validator); + self.validator_balances.push(amount); + Ok(self.validator_registry.len() - 1) + } } + /// Activate the validator of the given ``index``. + /// + /// Spec v0.2.0 + pub fn activate_validator( + &mut self, + validator_index: usize, + is_genesis: bool, + spec: &ChainSpec, + ) { + let current_epoch = self.current_epoch(spec); + + self.validator_registry[validator_index].activation_epoch = if is_genesis { + spec.genesis_epoch + } else { + self.get_entry_exit_effect_epoch(current_epoch, spec) + } + } + + /// Initiate an exit for the validator of the given `index`. + /// + /// Spec v0.2.0 + pub fn initiate_validator_exit(&mut self, validator_index: usize) { + // TODO: the spec does an `|=` here, ensure this isn't buggy. + self.validator_registry[validator_index].status_flags = Some(StatusFlags::InitiatedExit); + } + + /// Exit the validator of the given `index`. + /// + /// Spec v0.2.0 fn exit_validator(&mut self, validator_index: usize, spec: &ChainSpec) { - if self.validator_registry[validator_index].exit_slot - <= self.entry_exit_effect_slot(self.slot, spec) + let current_epoch = self.current_epoch(spec); + + if self.validator_registry[validator_index].exit_epoch + <= self.get_entry_exit_effect_epoch(current_epoch, spec) { return; } - self.validator_registry[validator_index].exit_slot = - self.entry_exit_effect_slot(self.slot, spec); - - self.validator_registry_exit_count += 1; - self.validator_registry[validator_index].exit_count = self.validator_registry_exit_count; + self.validator_registry[validator_index].exit_epoch = + self.get_entry_exit_effect_epoch(current_epoch, spec); } - fn activate_validator(&mut self, validator_index: usize, is_genesis: bool, spec: &ChainSpec) { - self.validator_registry[validator_index].activation_slot = if is_genesis { - spec.genesis_slot - } else { - self.entry_exit_effect_slot(self.slot, spec) + /// Penalize the validator of the given ``index``. + /// + /// Exits the validator and assigns its effective balance to the block producer for this + /// state. + /// + /// Spec v0.2.0 + pub fn penalize_validator( + &mut self, + validator_index: usize, + spec: &ChainSpec, + ) -> Result<(), CommitteesError> { + self.exit_validator(validator_index, spec); + let current_epoch = self.current_epoch(spec); + + self.latest_penalized_balances + [current_epoch.as_usize() % spec.latest_penalized_exit_length] += + self.get_effective_balance(validator_index, spec); + + let whistleblower_index = self.get_beacon_proposer_index(self.slot, spec)?; + let whistleblower_reward = self.get_effective_balance(validator_index, spec); + safe_add_assign!( + self.validator_balances[whistleblower_index as usize], + whistleblower_reward + ); + safe_sub_assign!( + self.validator_balances[validator_index], + whistleblower_reward + ); + self.validator_registry[validator_index].penalized_epoch = current_epoch; + Ok(()) + } + + /// Initiate an exit for the validator of the given `index`. + /// + /// Spec v0.2.0 + pub fn prepare_validator_for_withdrawal(&mut self, validator_index: usize) { + //TODO: we're not ANDing here, we're setting. Potentially wrong. + self.validator_registry[validator_index].status_flags = Some(StatusFlags::Withdrawable); + } + + /// Iterate through the validator registry and eject active validators with balance below + /// ``EJECTION_BALANCE``. + /// + /// Spec v0.2.0 + pub fn process_ejections(&mut self, spec: &ChainSpec) { + for validator_index in + get_active_validator_indices(&self.validator_registry, self.current_epoch(spec)) + { + if self.validator_balances[validator_index] < spec.ejection_balance { + self.exit_validator(validator_index, spec) + } } } - fn entry_exit_effect_slot(&self, slot: u64, spec: &ChainSpec) -> u64 { - (slot - slot % spec.epoch_length) + spec.epoch_length + spec.entry_exit_delay - } - - fn process_ejections(&self) { - //TODO: stubbed out. - } - - fn inactivity_penalty( + /// Returns the penality that should be applied to some validator for inactivity. + /// + /// Note: this is defined "inline" in the spec, not as a helper function. + /// + /// Spec v0.2.0 + pub fn inactivity_penalty( &self, validator_index: usize, - epochs_since_finality: u64, + epochs_since_finality: Epoch, base_reward_quotient: u64, spec: &ChainSpec, ) -> u64 { let effective_balance = self.get_effective_balance(validator_index, spec); self.base_reward(validator_index, base_reward_quotient, spec) - + effective_balance * epochs_since_finality / spec.inactivity_penalty_quotient / 2 + + effective_balance * epochs_since_finality.as_u64() + / spec.inactivity_penalty_quotient + / 2 } - fn inclusion_distance( + /// Returns the distance between the first included attestation for some validator and this + /// slot. + /// + /// Note: In the spec this is defined "inline", not as a helper function. + /// + /// Spec v0.2.0 + pub fn inclusion_distance( &self, attestations: &[&PendingAttestation], validator_index: usize, @@ -1288,22 +759,30 @@ impl BeaconState { ) -> Result { let attestation = self.earliest_included_attestation(attestations, validator_index, spec)?; - Ok(attestation - .slot_included - .saturating_sub(attestation.data.slot)) + Ok((attestation.inclusion_slot - attestation.data.slot).as_u64()) } - fn inclusion_slot( + /// Returns the slot of the earliest included attestation for some validator. + /// + /// Note: In the spec this is defined "inline", not as a helper function. + /// + /// Spec v0.2.0 + pub fn inclusion_slot( &self, attestations: &[&PendingAttestation], validator_index: usize, spec: &ChainSpec, - ) -> Result { + ) -> Result { let attestation = self.earliest_included_attestation(attestations, validator_index, spec)?; - Ok(attestation.slot_included) + Ok(attestation.inclusion_slot) } + /// Finds the earliest included attestation for some validator. + /// + /// Note: In the spec this is defined "inline", not as a helper function. + /// + /// Spec v0.2.0 fn earliest_included_attestation( &self, attestations: &[&PendingAttestation], @@ -1315,23 +794,24 @@ impl BeaconState { for (i, a) in attestations.iter().enumerate() { let participants = self.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?; - if participants - .iter() - .find(|i| **i == validator_index) - .is_some() - { + if participants.iter().any(|i| *i == validator_index) { included_attestations.push(i); } } let earliest_attestation_index = included_attestations .iter() - .min_by_key(|i| attestations[**i].slot_included) + .min_by_key(|i| attestations[**i].inclusion_slot) .ok_or_else(|| InclusionError::NoIncludedAttestations)?; Ok(attestations[*earliest_attestation_index].clone()) } - fn base_reward( + /// Returns the base reward for some validator. + /// + /// Note: In the spec this is defined "inline", not as a helper function. + /// + /// Spec v0.2.0 + pub fn base_reward( &self, validator_index: usize, base_reward_quotient: u64, @@ -1340,103 +820,31 @@ impl BeaconState { self.get_effective_balance(validator_index, spec) / base_reward_quotient / 5 } - pub fn get_effective_balances(&self, validator_indices: &[usize], spec: &ChainSpec) -> u64 { + /// Return the combined effective balance of an array of validators. + /// + /// Spec v0.2.0 + pub fn get_total_balance(&self, validator_indices: &[usize], spec: &ChainSpec) -> u64 { validator_indices .iter() .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)) } + /// Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. + /// + /// Spec v0.2.0 pub fn get_effective_balance(&self, validator_index: usize, spec: &ChainSpec) -> u64 { - std::cmp::min(self.validator_balances[validator_index], spec.max_deposit) + std::cmp::min( + self.validator_balances[validator_index], + spec.max_deposit_amount, + ) } - pub fn get_block_root(&self, slot: u64, spec: &ChainSpec) -> Option<&Hash256> { - if self.slot <= slot + spec.latest_block_roots_length && slot <= self.slot { - self.latest_block_roots - .get((slot % spec.latest_block_roots_length) as usize) - } else { - None - } - } - - pub(crate) fn winning_root( - &self, - shard: u64, - current_epoch_attestations: &[&PendingAttestation], - previous_epoch_attestations: &[&PendingAttestation], - spec: &ChainSpec, - ) -> Result { - let mut attestations = current_epoch_attestations.to_vec(); - attestations.append(&mut previous_epoch_attestations.to_vec()); - - let mut candidates: HashMap = HashMap::new(); - - let mut highest_seen_balance = 0; - - for a in &attestations { - if a.data.shard != shard { - continue; - } - - let shard_block_root = &a.data.shard_block_root; - - if candidates.contains_key(shard_block_root) { - continue; - } - - // TODO: `cargo fmt` makes this rather ugly; tidy up. - let attesting_validator_indices = attestations.iter().try_fold::<_, _, Result< - _, - AttestationParticipantsError, - >>( - vec![], - |mut acc, a| { - if (a.data.shard == shard) && (a.data.shard_block_root == *shard_block_root) { - acc.append(&mut self.get_attestation_participants( - &a.data, - &a.aggregation_bitfield, - spec, - )?); - } - Ok(acc) - }, - )?; - - let total_balance: u64 = attesting_validator_indices - .iter() - .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)); - - let total_attesting_balance: u64 = attesting_validator_indices - .iter() - .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)); - - if total_attesting_balance > highest_seen_balance { - highest_seen_balance = total_attesting_balance; - } - - let candidate_root = WinningRoot { - shard_block_root: shard_block_root.clone(), - attesting_validator_indices, - total_attesting_balance, - total_balance, - }; - - candidates.insert(*shard_block_root, candidate_root); - } - - Ok(candidates - .iter() - .filter_map(|(_hash, candidate)| { - if candidate.total_attesting_balance == highest_seen_balance { - Some(candidate) - } else { - None - } - }) - .min_by_key(|candidate| candidate.shard_block_root) - .ok_or_else(|| WinningRootError::NoWinningRoot)? - // TODO: avoid clone. - .clone()) + /// Return the block root at a recent `slot`. + /// + /// Spec v0.2.0 + pub fn get_block_root(&self, slot: Slot, spec: &ChainSpec) -> Option<&Hash256> { + self.latest_block_roots + .get(slot.as_usize() % spec.latest_block_roots_length) } pub fn get_attestation_participants_union( @@ -1460,15 +868,20 @@ impl BeaconState { Ok(all_participants) } - // TODO: analyse for efficiency improvments. This implementation is naive. + /// Return the participant indices at for the ``attestation_data`` and ``bitfield``. + /// + /// In effect, this converts the "committee indices" on the bitfield into "validator indices" + /// for self.validator_registy. + /// + /// Spec v0.2.0 pub fn get_attestation_participants( &self, attestation_data: &AttestationData, - aggregation_bitfield: &Bitfield, + bitfield: &Bitfield, spec: &ChainSpec, ) -> Result, AttestationParticipantsError> { let crosslink_committees = - self.get_crosslink_committees_at_slot(attestation_data.slot, spec)?; + self.get_crosslink_committees_at_slot(attestation_data.slot, false, spec)?; let committee_index: usize = crosslink_committees .iter() @@ -1477,126 +890,21 @@ impl BeaconState { let (crosslink_committee, _shard) = &crosslink_committees[committee_index]; /* - * TODO: that bitfield length is valid. - * + * TODO: verify bitfield length is valid. */ let mut participants = vec![]; for (i, validator_index) in crosslink_committee.iter().enumerate() { - if aggregation_bitfield.get(i).unwrap() { + if bitfield.get(i).unwrap() { participants.push(*validator_index); } } Ok(participants) } - - pub fn validate_attestation( - &self, - attestation: &Attestation, - spec: &ChainSpec, - ) -> Result<(), AttestationValidationError> { - self.validate_attestation_signature_optional(attestation, spec, true) - } - - pub fn validate_attestation_without_signature( - &self, - attestation: &Attestation, - spec: &ChainSpec, - ) -> Result<(), AttestationValidationError> { - self.validate_attestation_signature_optional(attestation, spec, false) - } - - fn validate_attestation_signature_optional( - &self, - attestation: &Attestation, - spec: &ChainSpec, - verify_signature: bool, - ) -> Result<(), AttestationValidationError> { - ensure!( - attestation.data.slot + spec.min_attestation_inclusion_delay <= self.slot, - AttestationValidationError::IncludedTooEarly - ); - ensure!( - attestation.data.slot + spec.epoch_length >= self.slot, - AttestationValidationError::IncludedTooLate - ); - if attestation.data.slot >= self.current_epoch_start_slot(spec) { - ensure!( - attestation.data.justified_slot == self.justified_slot, - AttestationValidationError::WrongJustifiedSlot - ); - } else { - ensure!( - attestation.data.justified_slot == self.previous_justified_slot, - AttestationValidationError::WrongJustifiedSlot - ); - } - ensure!( - attestation.data.justified_block_root - == *self - .get_block_root(attestation.data.justified_slot, &spec) - .ok_or(AttestationValidationError::NoBlockRoot)?, - AttestationValidationError::WrongJustifiedRoot - ); - ensure!( - (attestation.data.latest_crosslink_root - == self.latest_crosslinks[attestation.data.shard as usize].shard_block_root) - || (attestation.data.shard_block_root - == self.latest_crosslinks[attestation.data.shard as usize].shard_block_root), - AttestationValidationError::BadLatestCrosslinkRoot - ); - if verify_signature { - let participants = self.get_attestation_participants( - &attestation.data, - &attestation.aggregation_bitfield, - spec, - )?; - let mut group_public_key = AggregatePublicKey::new(); - for participant in participants { - group_public_key.add( - self.validator_registry[participant as usize] - .pubkey - .as_raw(), - ) - } - ensure!( - bls_verify_aggregate( - &group_public_key, - &attestation.signable_message(PHASE_0_CUSTODY_BIT), - &attestation.aggregate_signature, - get_domain(&self.fork_data, attestation.data.slot, DOMAIN_ATTESTATION) - ), - AttestationValidationError::BadSignature - ); - } - ensure!( - attestation.data.shard_block_root == spec.zero_hash, - AttestationValidationError::ShardBlockRootNotZero - ); - Ok(()) - } } -fn merkle_root(_input: &[Hash256]) -> Hash256 { - Hash256::zero() -} - -fn initiate_validator_exit(_state: &BeaconState, _index: u32) { - // TODO: stubbed out. -} - -fn penalize_validator(_state: &BeaconState, _proposer_index: usize) { - // TODO: stubbed out. -} - -fn get_domain(_fork: &Fork, _slot: u64, _domain_type: u64) -> u64 { - // TODO: stubbed out. - 0 -} - -fn bls_verify(pubkey: &PublicKey, message: &[u8], signature: &Signature, _domain: u64) -> bool { - // TODO: add domain - signature.verify(message, pubkey) +fn hash_tree_root(input: Vec) -> Hash256 { + Hash256::from(&input.hash_tree_root()[..]) } impl From for AttestationValidationError { @@ -1605,47 +913,15 @@ impl From for AttestationValidationError { } } -impl From for WinningRootError { - fn from(e: AttestationParticipantsError) -> WinningRootError { - WinningRootError::AttestationParticipantsError(e) - } -} - impl From for AttestationParticipantsError { fn from(e: CommitteesError) -> AttestationParticipantsError { AttestationParticipantsError::CommitteesError(e) } } -impl From for BlockProcessingError { - fn from(e: AttestationValidationError) -> BlockProcessingError { - BlockProcessingError::InvalidAttestation(e) - } -} +/* -impl From for BlockProcessingError { - fn from(e: CommitteesError) -> BlockProcessingError { - BlockProcessingError::CommitteesError(e) - } -} - -impl From for BlockProcessingError { - fn from(e: SlotProcessingError) -> BlockProcessingError { - BlockProcessingError::SlotProcessingError(e) - } -} - -impl From for SlotProcessingError { - fn from(e: CommitteesError) -> SlotProcessingError { - SlotProcessingError::CommitteesError(e) - } -} - -impl From for SlotProcessingError { - fn from(e: EpochError) -> SlotProcessingError { - SlotProcessingError::EpochProcessingError(e) - } -} +*/ impl From for InclusionError { fn from(e: AttestationParticipantsError) -> InclusionError { @@ -1653,24 +929,6 @@ impl From for InclusionError { } } -impl From for EpochError { - fn from(e: InclusionError) -> EpochError { - EpochError::InclusionError(e) - } -} - -impl From for EpochError { - fn from(e: CommitteesError) -> EpochError { - EpochError::CommitteesError(e) - } -} - -impl From for EpochError { - fn from(e: AttestationParticipantsError) -> EpochError { - EpochError::AttestationParticipantsError(e) - } -} - impl From for Error { fn from(e: CommitteesError) -> Error { Error::CommitteesError(e) @@ -1681,27 +939,24 @@ impl Encodable for BeaconState { fn ssz_append(&self, s: &mut SszStream) { s.append(&self.slot); s.append(&self.genesis_time); - s.append(&self.fork_data); + s.append(&self.fork); s.append(&self.validator_registry); s.append(&self.validator_balances); - s.append(&self.validator_registry_update_slot); - s.append(&self.validator_registry_exit_count); - s.append(&self.validator_registry_delta_chain_tip); + s.append(&self.validator_registry_update_epoch); s.append(&self.latest_randao_mixes); - s.append(&self.latest_vdf_outputs); s.append(&self.previous_epoch_start_shard); s.append(&self.current_epoch_start_shard); - s.append(&self.previous_epoch_calculation_slot); - s.append(&self.current_epoch_calculation_slot); + s.append(&self.previous_calculation_epoch); + s.append(&self.current_calculation_epoch); s.append(&self.previous_epoch_seed); s.append(&self.current_epoch_seed); - s.append(&self.custody_challenges); - s.append(&self.previous_justified_slot); - s.append(&self.justified_slot); + s.append(&self.previous_justified_epoch); + s.append(&self.justified_epoch); s.append(&self.justification_bitfield); - s.append(&self.finalized_slot); + s.append(&self.finalized_epoch); s.append(&self.latest_crosslinks); s.append(&self.latest_block_roots); + s.append(&self.latest_index_roots); s.append(&self.latest_penalized_balances); s.append(&self.latest_attestations); s.append(&self.batched_block_roots); @@ -1714,27 +969,24 @@ impl Decodable for BeaconState { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { let (slot, i) = <_>::ssz_decode(bytes, i)?; let (genesis_time, i) = <_>::ssz_decode(bytes, i)?; - let (fork_data, i) = <_>::ssz_decode(bytes, i)?; + let (fork, i) = <_>::ssz_decode(bytes, i)?; let (validator_registry, i) = <_>::ssz_decode(bytes, i)?; let (validator_balances, i) = <_>::ssz_decode(bytes, i)?; - let (validator_registry_update_slot, i) = <_>::ssz_decode(bytes, i)?; - let (validator_registry_exit_count, i) = <_>::ssz_decode(bytes, i)?; - let (validator_registry_delta_chain_tip, i) = <_>::ssz_decode(bytes, i)?; + let (validator_registry_update_epoch, i) = <_>::ssz_decode(bytes, i)?; let (latest_randao_mixes, i) = <_>::ssz_decode(bytes, i)?; - let (latest_vdf_outputs, i) = <_>::ssz_decode(bytes, i)?; let (previous_epoch_start_shard, i) = <_>::ssz_decode(bytes, i)?; let (current_epoch_start_shard, i) = <_>::ssz_decode(bytes, i)?; - let (previous_epoch_calculation_slot, i) = <_>::ssz_decode(bytes, i)?; - let (current_epoch_calculation_slot, i) = <_>::ssz_decode(bytes, i)?; + let (previous_calculation_epoch, i) = <_>::ssz_decode(bytes, i)?; + let (current_calculation_epoch, i) = <_>::ssz_decode(bytes, i)?; let (previous_epoch_seed, i) = <_>::ssz_decode(bytes, i)?; let (current_epoch_seed, i) = <_>::ssz_decode(bytes, i)?; - let (custody_challenges, i) = <_>::ssz_decode(bytes, i)?; - let (previous_justified_slot, i) = <_>::ssz_decode(bytes, i)?; - let (justified_slot, i) = <_>::ssz_decode(bytes, i)?; + let (previous_justified_epoch, i) = <_>::ssz_decode(bytes, i)?; + let (justified_epoch, i) = <_>::ssz_decode(bytes, i)?; let (justification_bitfield, i) = <_>::ssz_decode(bytes, i)?; - let (finalized_slot, i) = <_>::ssz_decode(bytes, i)?; + let (finalized_epoch, i) = <_>::ssz_decode(bytes, i)?; let (latest_crosslinks, i) = <_>::ssz_decode(bytes, i)?; let (latest_block_roots, i) = <_>::ssz_decode(bytes, i)?; + let (latest_index_roots, i) = <_>::ssz_decode(bytes, i)?; let (latest_penalized_balances, i) = <_>::ssz_decode(bytes, i)?; let (latest_attestations, i) = <_>::ssz_decode(bytes, i)?; let (batched_block_roots, i) = <_>::ssz_decode(bytes, i)?; @@ -1745,27 +997,24 @@ impl Decodable for BeaconState { Self { slot, genesis_time, - fork_data, + fork, validator_registry, validator_balances, - validator_registry_update_slot, - validator_registry_exit_count, - validator_registry_delta_chain_tip, + validator_registry_update_epoch, latest_randao_mixes, - latest_vdf_outputs, previous_epoch_start_shard, current_epoch_start_shard, - previous_epoch_calculation_slot, - current_epoch_calculation_slot, + previous_calculation_epoch, + current_calculation_epoch, previous_epoch_seed, current_epoch_seed, - custody_challenges, - previous_justified_slot, - justified_slot, + previous_justified_epoch, + justified_epoch, justification_bitfield, - finalized_slot, + finalized_epoch, latest_crosslinks, latest_block_roots, + latest_index_roots, latest_penalized_balances, latest_attestations, batched_block_roots, @@ -1782,27 +1031,24 @@ impl TreeHash for BeaconState { let mut result: Vec = vec![]; result.append(&mut self.slot.hash_tree_root()); result.append(&mut self.genesis_time.hash_tree_root()); - result.append(&mut self.fork_data.hash_tree_root()); + result.append(&mut self.fork.hash_tree_root()); result.append(&mut self.validator_registry.hash_tree_root()); result.append(&mut self.validator_balances.hash_tree_root()); - result.append(&mut self.validator_registry_update_slot.hash_tree_root()); - result.append(&mut self.validator_registry_exit_count.hash_tree_root()); - result.append(&mut self.validator_registry_delta_chain_tip.hash_tree_root()); + result.append(&mut self.validator_registry_update_epoch.hash_tree_root()); result.append(&mut self.latest_randao_mixes.hash_tree_root()); - result.append(&mut self.latest_vdf_outputs.hash_tree_root()); result.append(&mut self.previous_epoch_start_shard.hash_tree_root()); result.append(&mut self.current_epoch_start_shard.hash_tree_root()); - result.append(&mut self.previous_epoch_calculation_slot.hash_tree_root()); - result.append(&mut self.current_epoch_calculation_slot.hash_tree_root()); + result.append(&mut self.previous_calculation_epoch.hash_tree_root()); + result.append(&mut self.current_calculation_epoch.hash_tree_root()); result.append(&mut self.previous_epoch_seed.hash_tree_root()); result.append(&mut self.current_epoch_seed.hash_tree_root()); - result.append(&mut self.custody_challenges.hash_tree_root()); - result.append(&mut self.previous_justified_slot.hash_tree_root()); - result.append(&mut self.justified_slot.hash_tree_root()); + result.append(&mut self.previous_justified_epoch.hash_tree_root()); + result.append(&mut self.justified_epoch.hash_tree_root()); result.append(&mut self.justification_bitfield.hash_tree_root()); - result.append(&mut self.finalized_slot.hash_tree_root()); + result.append(&mut self.finalized_epoch.hash_tree_root()); result.append(&mut self.latest_crosslinks.hash_tree_root()); result.append(&mut self.latest_block_roots.hash_tree_root()); + result.append(&mut self.latest_index_roots.hash_tree_root()); result.append(&mut self.latest_penalized_balances.hash_tree_root()); result.append(&mut self.latest_attestations.hash_tree_root()); result.append(&mut self.batched_block_roots.hash_tree_root()); @@ -1817,27 +1063,24 @@ impl TestRandom for BeaconState { Self { slot: <_>::random_for_test(rng), genesis_time: <_>::random_for_test(rng), - fork_data: <_>::random_for_test(rng), + fork: <_>::random_for_test(rng), validator_registry: <_>::random_for_test(rng), validator_balances: <_>::random_for_test(rng), - validator_registry_update_slot: <_>::random_for_test(rng), - validator_registry_exit_count: <_>::random_for_test(rng), - validator_registry_delta_chain_tip: <_>::random_for_test(rng), + validator_registry_update_epoch: <_>::random_for_test(rng), latest_randao_mixes: <_>::random_for_test(rng), - latest_vdf_outputs: <_>::random_for_test(rng), previous_epoch_start_shard: <_>::random_for_test(rng), current_epoch_start_shard: <_>::random_for_test(rng), - previous_epoch_calculation_slot: <_>::random_for_test(rng), - current_epoch_calculation_slot: <_>::random_for_test(rng), + previous_calculation_epoch: <_>::random_for_test(rng), + current_calculation_epoch: <_>::random_for_test(rng), previous_epoch_seed: <_>::random_for_test(rng), current_epoch_seed: <_>::random_for_test(rng), - custody_challenges: <_>::random_for_test(rng), - previous_justified_slot: <_>::random_for_test(rng), - justified_slot: <_>::random_for_test(rng), + previous_justified_epoch: <_>::random_for_test(rng), + justified_epoch: <_>::random_for_test(rng), justification_bitfield: <_>::random_for_test(rng), - finalized_slot: <_>::random_for_test(rng), + finalized_epoch: <_>::random_for_test(rng), latest_crosslinks: <_>::random_for_test(rng), latest_block_roots: <_>::random_for_test(rng), + latest_index_roots: <_>::random_for_test(rng), latest_penalized_balances: <_>::random_for_test(rng), latest_attestations: <_>::random_for_test(rng), batched_block_roots: <_>::random_for_test(rng), diff --git a/eth2/types/src/crosslink.rs b/eth2/types/src/crosslink.rs index f727d7b438..3cb857ef4a 100644 --- a/eth2/types/src/crosslink.rs +++ b/eth2/types/src/crosslink.rs @@ -1,12 +1,12 @@ -use super::Hash256; use crate::test_utils::TestRandom; +use crate::{Epoch, Hash256}; use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; -#[derive(Clone, Debug, PartialEq, Serialize)] +#[derive(Debug, Clone, PartialEq, Default, Serialize, Hash)] pub struct Crosslink { - pub slot: u64, + pub epoch: Epoch, pub shard_block_root: Hash256, } @@ -14,7 +14,7 @@ impl Crosslink { /// Generates a new instance where `dynasty` and `hash` are both zero. pub fn zero() -> Self { Self { - slot: 0, + epoch: Epoch::new(0), shard_block_root: Hash256::zero(), } } @@ -22,19 +22,19 @@ impl Crosslink { impl Encodable for Crosslink { fn ssz_append(&self, s: &mut SszStream) { - s.append(&self.slot); + s.append(&self.epoch); s.append(&self.shard_block_root); } } impl Decodable for Crosslink { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { - let (slot, i) = <_>::ssz_decode(bytes, i)?; + let (epoch, i) = <_>::ssz_decode(bytes, i)?; let (shard_block_root, i) = <_>::ssz_decode(bytes, i)?; Ok(( Self { - slot, + epoch, shard_block_root, }, i, @@ -45,7 +45,7 @@ impl Decodable for Crosslink { impl TreeHash for Crosslink { fn hash_tree_root(&self) -> Vec { let mut result: Vec = vec![]; - result.append(&mut self.slot.hash_tree_root()); + result.append(&mut self.epoch.hash_tree_root()); result.append(&mut self.shard_block_root.hash_tree_root()); hash(&result) } @@ -54,7 +54,7 @@ impl TreeHash for Crosslink { impl TestRandom for Crosslink { fn random_for_test(rng: &mut T) -> Self { Self { - slot: <_>::random_for_test(rng), + epoch: <_>::random_for_test(rng), shard_block_root: <_>::random_for_test(rng), } } diff --git a/eth2/types/src/deposit.rs b/eth2/types/src/deposit.rs index 85b002101a..62349cbc19 100644 --- a/eth2/types/src/deposit.rs +++ b/eth2/types/src/deposit.rs @@ -6,29 +6,29 @@ use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; #[derive(Debug, PartialEq, Clone, Serialize)] pub struct Deposit { - pub merkle_branch: Vec, - pub merkle_tree_index: u64, + pub branch: Vec, + pub index: u64, pub deposit_data: DepositData, } impl Encodable for Deposit { fn ssz_append(&self, s: &mut SszStream) { - s.append_vec(&self.merkle_branch); - s.append(&self.merkle_tree_index); + s.append_vec(&self.branch); + s.append(&self.index); s.append(&self.deposit_data); } } impl Decodable for Deposit { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { - let (merkle_branch, i) = <_>::ssz_decode(bytes, i)?; - let (merkle_tree_index, i) = <_>::ssz_decode(bytes, i)?; + let (branch, i) = <_>::ssz_decode(bytes, i)?; + let (index, i) = <_>::ssz_decode(bytes, i)?; let (deposit_data, i) = <_>::ssz_decode(bytes, i)?; Ok(( Self { - merkle_branch, - merkle_tree_index, + branch, + index, deposit_data, }, i, @@ -39,8 +39,8 @@ impl Decodable for Deposit { impl TreeHash for Deposit { fn hash_tree_root(&self) -> Vec { let mut result: Vec = vec![]; - result.append(&mut self.merkle_branch.hash_tree_root()); - result.append(&mut self.merkle_tree_index.hash_tree_root()); + result.append(&mut self.branch.hash_tree_root()); + result.append(&mut self.index.hash_tree_root()); result.append(&mut self.deposit_data.hash_tree_root()); hash(&result) } @@ -49,8 +49,8 @@ impl TreeHash for Deposit { impl TestRandom for Deposit { fn random_for_test(rng: &mut T) -> Self { Self { - merkle_branch: <_>::random_for_test(rng), - merkle_tree_index: <_>::random_for_test(rng), + branch: <_>::random_for_test(rng), + index: <_>::random_for_test(rng), deposit_data: <_>::random_for_test(rng), } } diff --git a/eth2/types/src/exit.rs b/eth2/types/src/exit.rs index 97f1fd2868..cd77469198 100644 --- a/eth2/types/src/exit.rs +++ b/eth2/types/src/exit.rs @@ -1,4 +1,4 @@ -use crate::test_utils::TestRandom; +use crate::{test_utils::TestRandom, Epoch}; use bls::Signature; use rand::RngCore; use serde_derive::Serialize; @@ -6,14 +6,14 @@ use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; #[derive(Debug, PartialEq, Clone, Serialize)] pub struct Exit { - pub slot: u64, - pub validator_index: u32, + pub epoch: Epoch, + pub validator_index: u64, pub signature: Signature, } impl Encodable for Exit { fn ssz_append(&self, s: &mut SszStream) { - s.append(&self.slot); + s.append(&self.epoch); s.append(&self.validator_index); s.append(&self.signature); } @@ -21,13 +21,13 @@ impl Encodable for Exit { impl Decodable for Exit { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { - let (slot, i) = <_>::ssz_decode(bytes, i)?; + let (epoch, i) = <_>::ssz_decode(bytes, i)?; let (validator_index, i) = <_>::ssz_decode(bytes, i)?; let (signature, i) = <_>::ssz_decode(bytes, i)?; Ok(( Self { - slot, + epoch, validator_index, signature, }, @@ -39,7 +39,7 @@ impl Decodable for Exit { impl TreeHash for Exit { fn hash_tree_root(&self) -> Vec { let mut result: Vec = vec![]; - result.append(&mut self.slot.hash_tree_root()); + result.append(&mut self.epoch.hash_tree_root()); result.append(&mut self.validator_index.hash_tree_root()); result.append(&mut self.signature.hash_tree_root()); hash(&result) @@ -49,7 +49,7 @@ impl TreeHash for Exit { impl TestRandom for Exit { fn random_for_test(rng: &mut T) -> Self { Self { - slot: <_>::random_for_test(rng), + epoch: <_>::random_for_test(rng), validator_index: <_>::random_for_test(rng), signature: <_>::random_for_test(rng), } diff --git a/eth2/types/src/fork.rs b/eth2/types/src/fork.rs index c5a06caea9..1c96a34ac7 100644 --- a/eth2/types/src/fork.rs +++ b/eth2/types/src/fork.rs @@ -1,34 +1,34 @@ -use crate::test_utils::TestRandom; +use crate::{test_utils::TestRandom, Epoch}; use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; #[derive(Debug, Clone, PartialEq, Default, Serialize)] pub struct Fork { - pub pre_fork_version: u64, - pub post_fork_version: u64, - pub fork_slot: u64, + pub previous_version: u64, + pub current_version: u64, + pub epoch: Epoch, } impl Encodable for Fork { fn ssz_append(&self, s: &mut SszStream) { - s.append(&self.pre_fork_version); - s.append(&self.post_fork_version); - s.append(&self.fork_slot); + s.append(&self.previous_version); + s.append(&self.current_version); + s.append(&self.epoch); } } impl Decodable for Fork { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { - let (pre_fork_version, i) = <_>::ssz_decode(bytes, i)?; - let (post_fork_version, i) = <_>::ssz_decode(bytes, i)?; - let (fork_slot, i) = <_>::ssz_decode(bytes, i)?; + let (previous_version, i) = <_>::ssz_decode(bytes, i)?; + let (current_version, i) = <_>::ssz_decode(bytes, i)?; + let (epoch, i) = <_>::ssz_decode(bytes, i)?; Ok(( Self { - pre_fork_version, - post_fork_version, - fork_slot, + previous_version, + current_version, + epoch, }, i, )) @@ -38,9 +38,9 @@ impl Decodable for Fork { impl TreeHash for Fork { fn hash_tree_root(&self) -> Vec { let mut result: Vec = vec![]; - result.append(&mut self.pre_fork_version.hash_tree_root()); - result.append(&mut self.post_fork_version.hash_tree_root()); - result.append(&mut self.fork_slot.hash_tree_root()); + result.append(&mut self.previous_version.hash_tree_root()); + result.append(&mut self.current_version.hash_tree_root()); + result.append(&mut self.epoch.hash_tree_root()); hash(&result) } } @@ -48,9 +48,9 @@ impl TreeHash for Fork { impl TestRandom for Fork { fn random_for_test(rng: &mut T) -> Self { Self { - pre_fork_version: <_>::random_for_test(rng), - post_fork_version: <_>::random_for_test(rng), - fork_slot: <_>::random_for_test(rng), + previous_version: <_>::random_for_test(rng), + current_version: <_>::random_for_test(rng), + epoch: <_>::random_for_test(rng), } } } diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 79dd182552..ba88d43a10 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -3,6 +3,7 @@ pub mod test_utils; pub mod attestation; pub mod attestation_data; pub mod attestation_data_and_custody_bit; +pub mod attester_slashing; pub mod beacon_block; pub mod beacon_block_body; pub mod beacon_state; @@ -19,23 +20,23 @@ pub mod free_attestation; pub mod pending_attestation; pub mod proposal_signed_data; pub mod proposer_slashing; -pub mod shard_committee; +pub mod readers; pub mod shard_reassignment_record; +pub mod slashable_attestation; pub mod slashable_vote_data; +pub mod slot_epoch_height; pub mod spec; -pub mod special_record; pub mod validator; pub mod validator_registry; pub mod validator_registry_delta_block; -pub mod readers; - use ethereum_types::{H160, H256, U256}; use std::collections::HashMap; pub use crate::attestation::Attestation; pub use crate::attestation_data::AttestationData; pub use crate::attestation_data_and_custody_bit::AttestationDataAndCustodyBit; +pub use crate::attester_slashing::AttesterSlashing; pub use crate::beacon_block::BeaconBlock; pub use crate::beacon_block_body::BeaconBlockBody; pub use crate::beacon_state::BeaconState; @@ -52,10 +53,10 @@ pub use crate::free_attestation::FreeAttestation; pub use crate::pending_attestation::PendingAttestation; pub use crate::proposal_signed_data::ProposalSignedData; pub use crate::proposer_slashing::ProposerSlashing; -pub use crate::shard_committee::ShardCommittee; +pub use crate::slashable_attestation::SlashableAttestation; pub use crate::slashable_vote_data::SlashableVoteData; +pub use crate::slot_epoch_height::{Epoch, Slot}; pub use crate::spec::ChainSpec; -pub use crate::special_record::{SpecialRecord, SpecialRecordKind}; pub use crate::validator::{StatusFlags as ValidatorStatusFlags, Validator}; pub use crate::validator_registry_delta_block::ValidatorRegistryDeltaBlock; diff --git a/eth2/types/src/pending_attestation.rs b/eth2/types/src/pending_attestation.rs index d2af528266..25ec109d78 100644 --- a/eth2/types/src/pending_attestation.rs +++ b/eth2/types/src/pending_attestation.rs @@ -1,39 +1,39 @@ -use super::{AttestationData, Bitfield}; use crate::test_utils::TestRandom; +use crate::{AttestationData, Bitfield, Slot}; use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; #[derive(Debug, Clone, PartialEq, Serialize)] pub struct PendingAttestation { - pub data: AttestationData, pub aggregation_bitfield: Bitfield, + pub data: AttestationData, pub custody_bitfield: Bitfield, - pub slot_included: u64, + pub inclusion_slot: Slot, } impl Encodable for PendingAttestation { fn ssz_append(&self, s: &mut SszStream) { - s.append(&self.data); s.append(&self.aggregation_bitfield); + s.append(&self.data); s.append(&self.custody_bitfield); - s.append(&self.slot_included); + s.append(&self.inclusion_slot); } } impl Decodable for PendingAttestation { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { - let (data, i) = <_>::ssz_decode(bytes, i)?; let (aggregation_bitfield, i) = <_>::ssz_decode(bytes, i)?; + let (data, i) = <_>::ssz_decode(bytes, i)?; let (custody_bitfield, i) = <_>::ssz_decode(bytes, i)?; - let (slot_included, i) = <_>::ssz_decode(bytes, i)?; + let (inclusion_slot, i) = <_>::ssz_decode(bytes, i)?; Ok(( Self { data, aggregation_bitfield, custody_bitfield, - slot_included, + inclusion_slot, }, i, )) @@ -43,10 +43,10 @@ impl Decodable for PendingAttestation { impl TreeHash for PendingAttestation { fn hash_tree_root(&self) -> Vec { let mut result: Vec = vec![]; - result.append(&mut self.data.hash_tree_root()); result.append(&mut self.aggregation_bitfield.hash_tree_root()); + result.append(&mut self.data.hash_tree_root()); result.append(&mut self.custody_bitfield.hash_tree_root()); - result.append(&mut self.custody_bitfield.hash_tree_root()); + result.append(&mut self.inclusion_slot.hash_tree_root()); hash(&result) } } @@ -57,7 +57,7 @@ impl TestRandom for PendingAttestation { data: <_>::random_for_test(rng), aggregation_bitfield: <_>::random_for_test(rng), custody_bitfield: <_>::random_for_test(rng), - slot_included: <_>::random_for_test(rng), + inclusion_slot: <_>::random_for_test(rng), } } } diff --git a/eth2/types/src/proposal_signed_data.rs b/eth2/types/src/proposal_signed_data.rs index 829a16987d..c57eb1e2ad 100644 --- a/eth2/types/src/proposal_signed_data.rs +++ b/eth2/types/src/proposal_signed_data.rs @@ -1,12 +1,12 @@ -use super::Hash256; use crate::test_utils::TestRandom; +use crate::{Hash256, Slot}; use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; #[derive(Debug, PartialEq, Clone, Default, Serialize)] pub struct ProposalSignedData { - pub slot: u64, + pub slot: Slot, pub shard: u64, pub block_root: Hash256, } diff --git a/eth2/types/src/proposer_slashing.rs b/eth2/types/src/proposer_slashing.rs index a82a370748..417d23dbc7 100644 --- a/eth2/types/src/proposer_slashing.rs +++ b/eth2/types/src/proposer_slashing.rs @@ -7,7 +7,7 @@ use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; #[derive(Debug, PartialEq, Clone, Serialize)] pub struct ProposerSlashing { - pub proposer_index: u32, + pub proposer_index: u64, pub proposal_data_1: ProposalSignedData, pub proposal_signature_1: Signature, pub proposal_data_2: ProposalSignedData, diff --git a/eth2/types/src/readers/block_reader.rs b/eth2/types/src/readers/block_reader.rs index d87bc5caf7..bcb2d0e633 100644 --- a/eth2/types/src/readers/block_reader.rs +++ b/eth2/types/src/readers/block_reader.rs @@ -1,5 +1,4 @@ -use super::state_reader::BeaconStateReader; -use crate::{BeaconBlock, Hash256}; +use crate::{BeaconBlock, Hash256, Slot}; use std::fmt::Debug; /// The `BeaconBlockReader` provides interfaces for reading a subset of fields of a `BeaconBlock`. @@ -11,7 +10,7 @@ use std::fmt::Debug; /// Note: presently, direct SSZ reading has not been implemented so this trait is being used for /// "future proofing". pub trait BeaconBlockReader: Debug + PartialEq { - fn slot(&self) -> u64; + fn slot(&self) -> Slot; fn parent_root(&self) -> Hash256; fn state_root(&self) -> Hash256; fn canonical_root(&self) -> Hash256; @@ -19,7 +18,7 @@ pub trait BeaconBlockReader: Debug + PartialEq { } impl BeaconBlockReader for BeaconBlock { - fn slot(&self) -> u64 { + fn slot(&self) -> Slot { self.slot } diff --git a/eth2/types/src/readers/state_reader.rs b/eth2/types/src/readers/state_reader.rs index 47266bd364..92a870855a 100644 --- a/eth2/types/src/readers/state_reader.rs +++ b/eth2/types/src/readers/state_reader.rs @@ -1,4 +1,4 @@ -use crate::{BeaconState, Hash256}; +use crate::{BeaconState, Hash256, Slot}; use std::fmt::Debug; /// The `BeaconStateReader` provides interfaces for reading a subset of fields of a `BeaconState`. @@ -10,13 +10,13 @@ use std::fmt::Debug; /// Note: presently, direct SSZ reading has not been implemented so this trait is being used for /// "future proofing". pub trait BeaconStateReader: Debug + PartialEq { - fn slot(&self) -> u64; + fn slot(&self) -> Slot; fn canonical_root(&self) -> Hash256; fn into_beacon_state(self) -> Option; } impl BeaconStateReader for BeaconState { - fn slot(&self) -> u64 { + fn slot(&self) -> Slot { self.slot } diff --git a/eth2/types/src/shard_committee.rs b/eth2/types/src/shard_committee.rs deleted file mode 100644 index 3632cb0c18..0000000000 --- a/eth2/types/src/shard_committee.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::test_utils::TestRandom; -use rand::RngCore; -use serde_derive::Serialize; -use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; - -#[derive(Clone, Debug, PartialEq, Serialize)] -pub struct ShardCommittee { - pub shard: u64, - pub committee: Vec, -} - -impl Encodable for ShardCommittee { - fn ssz_append(&self, s: &mut SszStream) { - s.append(&self.shard); - s.append(&self.committee); - } -} - -impl Decodable for ShardCommittee { - fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { - let (shard, i) = <_>::ssz_decode(bytes, i)?; - let (committee, i) = <_>::ssz_decode(bytes, i)?; - - Ok((Self { shard, committee }, i)) - } -} - -impl TreeHash for ShardCommittee { - fn hash_tree_root(&self) -> Vec { - let mut result: Vec = vec![]; - result.append(&mut self.shard.hash_tree_root()); - result.append(&mut self.committee.hash_tree_root()); - hash(&result) - } -} - -impl TestRandom for ShardCommittee { - fn random_for_test(rng: &mut T) -> Self { - Self { - shard: <_>::random_for_test(rng), - committee: <_>::random_for_test(rng), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::ssz_encode; - - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = ShardCommittee::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = ShardCommittee::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } -} diff --git a/eth2/types/src/shard_reassignment_record.rs b/eth2/types/src/shard_reassignment_record.rs index a239233df9..61f68ac051 100644 --- a/eth2/types/src/shard_reassignment_record.rs +++ b/eth2/types/src/shard_reassignment_record.rs @@ -1,4 +1,4 @@ -use crate::test_utils::TestRandom; +use crate::{test_utils::TestRandom, Slot}; use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; @@ -7,7 +7,7 @@ use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; pub struct ShardReassignmentRecord { pub validator_index: u64, pub shard: u64, - pub slot: u64, + pub slot: Slot, } impl Encodable for ShardReassignmentRecord { diff --git a/eth2/types/src/slashable_attestation.rs b/eth2/types/src/slashable_attestation.rs new file mode 100644 index 0000000000..6d83ad147b --- /dev/null +++ b/eth2/types/src/slashable_attestation.rs @@ -0,0 +1,92 @@ +use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield}; +use rand::RngCore; +use serde_derive::Serialize; +use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; + +#[derive(Debug, PartialEq, Clone, Serialize)] +pub struct SlashableAttestation { + pub validator_indices: Vec, + pub data: AttestationData, + pub custody_bitfield: Bitfield, + pub aggregate_signature: AggregateSignature, +} + +impl Encodable for SlashableAttestation { + fn ssz_append(&self, s: &mut SszStream) { + s.append_vec(&self.validator_indices); + s.append(&self.data); + s.append(&self.custody_bitfield); + s.append(&self.aggregate_signature); + } +} + +impl Decodable for SlashableAttestation { + fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { + let (validator_indices, i) = <_>::ssz_decode(bytes, i)?; + let (data, i) = <_>::ssz_decode(bytes, i)?; + let (custody_bitfield, i) = <_>::ssz_decode(bytes, i)?; + let (aggregate_signature, i) = <_>::ssz_decode(bytes, i)?; + + Ok(( + SlashableAttestation { + validator_indices, + data, + custody_bitfield, + aggregate_signature, + }, + i, + )) + } +} + +impl TreeHash for SlashableAttestation { + fn hash_tree_root(&self) -> Vec { + let mut result: Vec = vec![]; + result.append(&mut self.validator_indices.hash_tree_root()); + result.append(&mut self.data.hash_tree_root()); + result.append(&mut self.custody_bitfield.hash_tree_root()); + result.append(&mut self.aggregate_signature.hash_tree_root()); + hash(&result) + } +} + +impl TestRandom for SlashableAttestation { + fn random_for_test(rng: &mut T) -> Self { + Self { + validator_indices: <_>::random_for_test(rng), + data: <_>::random_for_test(rng), + custody_bitfield: <_>::random_for_test(rng), + aggregate_signature: <_>::random_for_test(rng), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; + use ssz::ssz_encode; + + #[test] + pub fn test_ssz_round_trip() { + let mut rng = XorShiftRng::from_seed([42; 16]); + let original = SlashableAttestation::random_for_test(&mut rng); + + let bytes = ssz_encode(&original); + let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); + + assert_eq!(original, decoded); + } + + #[test] + pub fn test_hash_tree_root() { + let mut rng = XorShiftRng::from_seed([42; 16]); + let original = SlashableAttestation::random_for_test(&mut rng); + + let result = original.hash_tree_root(); + + assert_eq!(result.len(), 32); + // TODO: Add further tests + // https://github.com/sigp/lighthouse/issues/170 + } +} diff --git a/eth2/types/src/slot_epoch_height.rs b/eth2/types/src/slot_epoch_height.rs new file mode 100644 index 0000000000..4f6b50b3a7 --- /dev/null +++ b/eth2/types/src/slot_epoch_height.rs @@ -0,0 +1,763 @@ +/// The `Slot` `Epoch`, `Height` types are defined as newtypes over u64 to enforce type-safety between +/// the three types. +/// +/// `Slot`, `Epoch` and `Height` have implementations which permit conversion, comparison and math operations +/// between each and `u64`, however specifically not between each other. +/// +/// All math operations on `Slot` and `Epoch` are saturating, they never wrap. +/// +/// It would be easy to define `PartialOrd` and other traits generically across all types which +/// implement `Into`, however this would allow operations between `Slots`, `Epochs` and +/// `Heights` which may lead to programming errors which are not detected by the compiler. +use crate::test_utils::TestRandom; +use rand::RngCore; +use serde_derive::Serialize; +use slog; +use ssz::{hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash}; +use std::cmp::{Ord, Ordering}; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::iter::Iterator; +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign}; + +macro_rules! impl_from_into_u64 { + ($main: ident) => { + impl From for $main { + fn from(n: u64) -> $main { + $main(n) + } + } + + impl Into for $main { + fn into(self) -> u64 { + self.0 + } + } + + impl $main { + pub fn as_u64(&self) -> u64 { + self.0 + } + } + }; +} + +// need to truncate for some fork-choice algorithms +macro_rules! impl_into_u32 { + ($main: ident) => { + impl Into for $main { + fn into(self) -> u32 { + self.0 as u32 + } + } + + impl $main { + pub fn as_u32(&self) -> u32 { + self.0 as u32 + } + } + }; +} + +macro_rules! impl_from_into_usize { + ($main: ident) => { + impl From for $main { + fn from(n: usize) -> $main { + $main(n as u64) + } + } + + impl Into for $main { + fn into(self) -> usize { + self.0 as usize + } + } + + impl $main { + pub fn as_usize(&self) -> usize { + self.0 as usize + } + } + }; +} + +macro_rules! impl_math_between { + ($main: ident, $other: ident) => { + impl PartialOrd<$other> for $main { + /// Utilizes `partial_cmp` on the underlying `u64`. + fn partial_cmp(&self, other: &$other) -> Option { + Some(self.0.cmp(&(*other).into())) + } + } + + impl PartialEq<$other> for $main { + fn eq(&self, other: &$other) -> bool { + let other: u64 = (*other).into(); + self.0 == other + } + } + + impl Add<$other> for $main { + type Output = $main; + + fn add(self, other: $other) -> $main { + $main::from(self.0.saturating_add(other.into())) + } + } + + impl AddAssign<$other> for $main { + fn add_assign(&mut self, other: $other) { + self.0 = self.0.saturating_add(other.into()); + } + } + + impl Sub<$other> for $main { + type Output = $main; + + fn sub(self, other: $other) -> $main { + $main::from(self.0.saturating_sub(other.into())) + } + } + + impl SubAssign<$other> for $main { + fn sub_assign(&mut self, other: $other) { + self.0 = self.0.saturating_sub(other.into()); + } + } + + impl Mul<$other> for $main { + type Output = $main; + + fn mul(self, rhs: $other) -> $main { + let rhs: u64 = rhs.into(); + $main::from(self.0.saturating_mul(rhs)) + } + } + + impl MulAssign<$other> for $main { + fn mul_assign(&mut self, rhs: $other) { + let rhs: u64 = rhs.into(); + self.0 = self.0.saturating_mul(rhs) + } + } + + impl Div<$other> for $main { + type Output = $main; + + fn div(self, rhs: $other) -> $main { + let rhs: u64 = rhs.into(); + if rhs == 0 { + panic!("Cannot divide by zero-valued Slot/Epoch") + } + $main::from(self.0 / rhs) + } + } + + impl DivAssign<$other> for $main { + fn div_assign(&mut self, rhs: $other) { + let rhs: u64 = rhs.into(); + if rhs == 0 { + panic!("Cannot divide by zero-valued Slot/Epoch") + } + self.0 = self.0 / rhs + } + } + + impl Rem<$other> for $main { + type Output = $main; + + fn rem(self, modulus: $other) -> $main { + let modulus: u64 = modulus.into(); + $main::from(self.0 % modulus) + } + } + }; +} + +macro_rules! impl_math { + ($type: ident) => { + impl $type { + pub fn saturating_sub>(&self, other: T) -> $type { + *self - other.into() + } + + pub fn saturating_add>(&self, other: T) -> $type { + *self + other.into() + } + + pub fn checked_div>(&self, rhs: T) -> Option<$type> { + let rhs: $type = rhs.into(); + if rhs == 0 { + None + } else { + Some(*self / rhs) + } + } + + pub fn is_power_of_two(&self) -> bool { + self.0.is_power_of_two() + } + } + + impl Ord for $type { + fn cmp(&self, other: &$type) -> Ordering { + let other: u64 = (*other).into(); + self.0.cmp(&other) + } + } + }; +} + +macro_rules! impl_display { + ($type: ident) => { + impl fmt::Display for $type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } + } + + impl slog::Value for $type { + fn serialize( + &self, + record: &slog::Record, + key: slog::Key, + serializer: &mut slog::Serializer, + ) -> slog::Result { + self.0.serialize(record, key, serializer) + } + } + }; +} + +macro_rules! impl_ssz { + ($type: ident) => { + impl Encodable for $type { + fn ssz_append(&self, s: &mut SszStream) { + s.append(&self.0); + } + } + + impl Decodable for $type { + fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { + let (value, i) = <_>::ssz_decode(bytes, i)?; + + Ok(($type(value), i)) + } + } + + impl TreeHash for $type { + fn hash_tree_root(&self) -> Vec { + let mut result: Vec = vec![]; + result.append(&mut self.0.hash_tree_root()); + hash(&result) + } + } + + impl TestRandom for $type { + fn random_for_test(rng: &mut T) -> Self { + $type::from(u64::random_for_test(rng)) + } + } + }; +} + +macro_rules! impl_hash { + ($type: ident) => { + // Implemented to stop clippy lint: + // https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq + impl Hash for $type { + fn hash(&self, state: &mut H) { + ssz_encode(self).hash(state) + } + } + }; +} + +macro_rules! impl_common { + ($type: ident) => { + impl_from_into_u64!($type); + impl_from_into_usize!($type); + impl_math_between!($type, $type); + impl_math_between!($type, u64); + impl_math!($type); + impl_display!($type); + impl_ssz!($type); + impl_hash!($type); + }; +} + +/// Beacon block slot. +#[derive(Eq, Debug, Clone, Copy, Default, Serialize)] +pub struct Slot(u64); + +/// Beacon block height, effectively `Slot/GENESIS_START_BLOCK`. +#[derive(Eq, Debug, Clone, Copy, Default, Serialize)] +pub struct Height(u64); + +/// Beacon Epoch, effectively `Slot / EPOCH_LENGTH`. +#[derive(Eq, Debug, Clone, Copy, Default, Serialize)] +pub struct Epoch(u64); + +impl_common!(Slot); +impl_common!(Height); +impl_into_u32!(Height); // height can be converted to u32 +impl_common!(Epoch); + +impl Slot { + pub fn new(slot: u64) -> Slot { + Slot(slot) + } + + pub fn epoch(self, epoch_length: u64) -> Epoch { + Epoch::from(self.0 / epoch_length) + } + + pub fn height(self, genesis_slot: Slot) -> Height { + Height::from(self.0.saturating_sub(genesis_slot.as_u64())) + } + + pub fn max_value() -> Slot { + Slot(u64::max_value()) + } +} + +impl Height { + pub fn new(slot: u64) -> Height { + Height(slot) + } + + pub fn slot(self, genesis_slot: Slot) -> Slot { + Slot::from(self.0.saturating_add(genesis_slot.as_u64())) + } + + pub fn epoch(self, genesis_slot: u64, epoch_length: u64) -> Epoch { + Epoch::from(self.0.saturating_add(genesis_slot) / epoch_length) + } + + pub fn max_value() -> Height { + Height(u64::max_value()) + } +} + +impl Epoch { + pub fn new(slot: u64) -> Epoch { + Epoch(slot) + } + + pub fn max_value() -> Epoch { + Epoch(u64::max_value()) + } + + pub fn start_slot(self, epoch_length: u64) -> Slot { + Slot::from(self.0.saturating_mul(epoch_length)) + } + + pub fn end_slot(self, epoch_length: u64) -> Slot { + Slot::from( + self.0 + .saturating_add(1) + .saturating_mul(epoch_length) + .saturating_sub(1), + ) + } + + pub fn slot_iter(&self, epoch_length: u64) -> SlotIter { + SlotIter { + current: self.start_slot(epoch_length), + epoch: self, + epoch_length, + } + } +} + +pub struct SlotIter<'a> { + current: Slot, + epoch: &'a Epoch, + epoch_length: u64, +} + +impl<'a> Iterator for SlotIter<'a> { + type Item = Slot; + + fn next(&mut self) -> Option { + if self.current == self.epoch.end_slot(self.epoch_length) { + None + } else { + let previous = self.current; + self.current += 1; + Some(previous) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! new_tests { + ($type: ident) => { + #[test] + fn new() { + assert_eq!($type(0), $type::new(0)); + assert_eq!($type(3), $type::new(3)); + assert_eq!($type(u64::max_value()), $type::new(u64::max_value())); + } + }; + } + + macro_rules! from_into_tests { + ($type: ident, $other: ident) => { + #[test] + fn into() { + let x: $other = $type(0).into(); + assert_eq!(x, 0); + + let x: $other = $type(3).into(); + assert_eq!(x, 3); + + let x: $other = $type(u64::max_value()).into(); + // Note: this will fail on 32 bit systems. This is expected as we don't have a proper + // 32-bit system strategy in place. + assert_eq!(x, $other::max_value()); + } + + #[test] + fn from() { + assert_eq!($type(0), $type::from(0_u64)); + assert_eq!($type(3), $type::from(3_u64)); + assert_eq!($type(u64::max_value()), $type::from($other::max_value())); + } + }; + } + + macro_rules! math_between_tests { + ($type: ident, $other: ident) => { + #[test] + fn partial_ord() { + let assert_partial_ord = |a: u64, partial_ord: Ordering, b: u64| { + let other: $other = $type(b).into(); + assert_eq!($type(a).partial_cmp(&other), Some(partial_ord)); + }; + + assert_partial_ord(1, Ordering::Less, 2); + assert_partial_ord(2, Ordering::Greater, 1); + assert_partial_ord(0, Ordering::Less, u64::max_value()); + assert_partial_ord(u64::max_value(), Ordering::Greater, 0); + } + + #[test] + fn partial_eq() { + let assert_partial_eq = |a: u64, b: u64, is_equal: bool| { + let other: $other = $type(b).into(); + assert_eq!($type(a).eq(&other), is_equal); + }; + + assert_partial_eq(0, 0, true); + assert_partial_eq(0, 1, false); + assert_partial_eq(1, 0, false); + assert_partial_eq(1, 1, true); + + assert_partial_eq(u64::max_value(), u64::max_value(), true); + assert_partial_eq(0, u64::max_value(), false); + assert_partial_eq(u64::max_value(), 0, false); + } + + #[test] + fn add_and_add_assign() { + let assert_add = |a: u64, b: u64, result: u64| { + let other: $other = $type(b).into(); + assert_eq!($type(a) + other, $type(result)); + + let mut add_assigned = $type(a); + add_assigned += other; + + assert_eq!(add_assigned, $type(result)); + }; + + assert_add(0, 1, 1); + assert_add(1, 0, 1); + assert_add(1, 2, 3); + assert_add(2, 1, 3); + assert_add(7, 7, 14); + + // Addition should be saturating. + assert_add(u64::max_value(), 1, u64::max_value()); + assert_add(u64::max_value(), u64::max_value(), u64::max_value()); + } + + #[test] + fn sub_and_sub_assign() { + let assert_sub = |a: u64, b: u64, result: u64| { + let other: $other = $type(b).into(); + assert_eq!($type(a) - other, $type(result)); + + let mut sub_assigned = $type(a); + sub_assigned -= other; + + assert_eq!(sub_assigned, $type(result)); + }; + + assert_sub(1, 0, 1); + assert_sub(2, 1, 1); + assert_sub(14, 7, 7); + assert_sub(u64::max_value(), 1, u64::max_value() - 1); + assert_sub(u64::max_value(), u64::max_value(), 0); + + // Subtraction should be saturating + assert_sub(0, 1, 0); + assert_sub(1, 2, 0); + } + + #[test] + fn mul_and_mul_assign() { + let assert_mul = |a: u64, b: u64, result: u64| { + let other: $other = $type(b).into(); + assert_eq!($type(a) * other, $type(result)); + + let mut mul_assigned = $type(a); + mul_assigned *= other; + + assert_eq!(mul_assigned, $type(result)); + }; + + assert_mul(2, 2, 4); + assert_mul(1, 2, 2); + assert_mul(0, 2, 0); + + // Multiplication should be saturating. + assert_mul(u64::max_value(), 2, u64::max_value()); + } + + #[test] + fn div_and_div_assign() { + let assert_div = |a: u64, b: u64, result: u64| { + let other: $other = $type(b).into(); + assert_eq!($type(a) / other, $type(result)); + + let mut div_assigned = $type(a); + div_assigned /= other; + + assert_eq!(div_assigned, $type(result)); + }; + + assert_div(0, 2, 0); + assert_div(2, 2, 1); + assert_div(100, 50, 2); + assert_div(128, 2, 64); + assert_div(u64::max_value(), 2, 2_u64.pow(63) - 1); + } + + #[test] + #[should_panic] + fn div_panics_with_divide_by_zero() { + let other: $other = $type(0).into(); + let _ = $type(2) / other; + } + + #[test] + #[should_panic] + fn div_assign_panics_with_divide_by_zero() { + let other: $other = $type(0).into(); + let mut assigned = $type(2); + assigned /= other; + } + + #[test] + fn rem() { + let assert_rem = |a: u64, b: u64, result: u64| { + let other: $other = $type(b).into(); + assert_eq!($type(a) % other, $type(result)); + }; + + assert_rem(3, 2, 1); + assert_rem(40, 2, 0); + assert_rem(10, 100, 10); + assert_rem(302042, 3293, 2379); + } + }; + } + + macro_rules! math_tests { + ($type: ident) => { + #[test] + fn saturating_sub() { + let assert_saturating_sub = |a: u64, b: u64, result: u64| { + assert_eq!($type(a).saturating_sub($type(b)), $type(result)); + }; + + assert_saturating_sub(1, 0, 1); + assert_saturating_sub(2, 1, 1); + assert_saturating_sub(14, 7, 7); + assert_saturating_sub(u64::max_value(), 1, u64::max_value() - 1); + assert_saturating_sub(u64::max_value(), u64::max_value(), 0); + + // Subtraction should be saturating + assert_saturating_sub(0, 1, 0); + assert_saturating_sub(1, 2, 0); + } + + #[test] + fn saturating_add() { + let assert_saturating_add = |a: u64, b: u64, result: u64| { + assert_eq!($type(a).saturating_add($type(b)), $type(result)); + }; + + assert_saturating_add(0, 1, 1); + assert_saturating_add(1, 0, 1); + assert_saturating_add(1, 2, 3); + assert_saturating_add(2, 1, 3); + assert_saturating_add(7, 7, 14); + + // Addition should be saturating. + assert_saturating_add(u64::max_value(), 1, u64::max_value()); + assert_saturating_add(u64::max_value(), u64::max_value(), u64::max_value()); + } + + #[test] + fn checked_div() { + let assert_checked_div = |a: u64, b: u64, result: Option| { + let division_result_as_u64 = match $type(a).checked_div($type(b)) { + None => None, + Some(val) => Some(val.as_u64()), + }; + assert_eq!(division_result_as_u64, result); + }; + + assert_checked_div(0, 2, Some(0)); + assert_checked_div(2, 2, Some(1)); + assert_checked_div(100, 50, Some(2)); + assert_checked_div(128, 2, Some(64)); + assert_checked_div(u64::max_value(), 2, Some(2_u64.pow(63) - 1)); + + assert_checked_div(2, 0, None); + assert_checked_div(0, 0, None); + assert_checked_div(u64::max_value(), 0, None); + } + + #[test] + fn is_power_of_two() { + let assert_is_power_of_two = |a: u64, result: bool| { + assert_eq!( + $type(a).is_power_of_two(), + result, + "{}.is_power_of_two() != {}", + a, + result + ); + }; + + assert_is_power_of_two(0, false); + assert_is_power_of_two(1, true); + assert_is_power_of_two(2, true); + assert_is_power_of_two(3, false); + assert_is_power_of_two(4, true); + + assert_is_power_of_two(2_u64.pow(4), true); + assert_is_power_of_two(u64::max_value(), false); + } + + #[test] + fn ord() { + let assert_ord = |a: u64, ord: Ordering, b: u64| { + assert_eq!($type(a).cmp(&$type(b)), ord); + }; + + assert_ord(1, Ordering::Less, 2); + assert_ord(2, Ordering::Greater, 1); + assert_ord(0, Ordering::Less, u64::max_value()); + assert_ord(u64::max_value(), Ordering::Greater, 0); + } + }; + } + + macro_rules! ssz_tests { + ($type: ident) => { + #[test] + pub fn test_ssz_round_trip() { + let mut rng = XorShiftRng::from_seed([42; 16]); + let original = $type::random_for_test(&mut rng); + + let bytes = ssz_encode(&original); + let (decoded, _) = $type::ssz_decode(&bytes, 0).unwrap(); + + assert_eq!(original, decoded); + } + + #[test] + pub fn test_hash_tree_root() { + let mut rng = XorShiftRng::from_seed([42; 16]); + let original = $type::random_for_test(&mut rng); + + let result = original.hash_tree_root(); + + assert_eq!(result.len(), 32); + // TODO: Add further tests + // https://github.com/sigp/lighthouse/issues/170 + } + }; + } + + macro_rules! all_tests { + ($type: ident) => { + new_tests!($type); + math_between_tests!($type, $type); + math_tests!($type); + ssz_tests!($type); + + mod u64_tests { + use super::*; + + from_into_tests!($type, u64); + math_between_tests!($type, u64); + + #[test] + pub fn as_64() { + let x = $type(0).as_u64(); + assert_eq!(x, 0); + + let x = $type(3).as_u64(); + assert_eq!(x, 3); + + let x = $type(u64::max_value()).as_u64(); + assert_eq!(x, u64::max_value()); + } + } + + mod usize_tests { + use super::*; + + from_into_tests!($type, usize); + + #[test] + pub fn as_usize() { + let x = $type(0).as_usize(); + assert_eq!(x, 0); + + let x = $type(3).as_usize(); + assert_eq!(x, 3); + + let x = $type(u64::max_value()).as_usize(); + assert_eq!(x, usize::max_value()); + } + } + }; + } + + #[cfg(test)] + mod slot_tests { + use super::*; + use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; + use ssz::ssz_encode; + + all_tests!(Slot); + } + + #[cfg(test)] + mod epoch_tests { + use super::*; + use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; + use ssz::ssz_encode; + + all_tests!(Epoch); + } +} diff --git a/eth2/types/src/spec/foundation.rs b/eth2/types/src/spec/foundation.rs index f24a55b2ab..79abe40615 100644 --- a/eth2/types/src/spec/foundation.rs +++ b/eth2/types/src/spec/foundation.rs @@ -1,134 +1,105 @@ -use super::ChainSpec; -use bls::{Keypair, PublicKey, SecretKey, Signature}; +use crate::{Address, ChainSpec, Epoch, Hash256, Signature, Slot}; -use crate::{Address, Eth1Data, Hash256, Validator}; - -/// The size of a validators deposit in GWei. -pub const DEPOSIT_GWEI: u64 = 32_000_000_000; +const GWEI: u64 = 1_000_000_000; impl ChainSpec { /// Returns a `ChainSpec` compatible with the specification from Ethereum Foundation. /// /// Of course, the actual foundation specs are unknown at this point so these are just a rough /// estimate. + /// + /// Spec v0.2.0 pub fn foundation() -> Self { + let genesis_slot = Slot::new(2_u64.pow(19)); + let epoch_length = 64; + let genesis_epoch = genesis_slot.epoch(epoch_length); + Self { /* * Misc */ shard_count: 1_024, target_committee_size: 128, - ejection_balance: 16 * u64::pow(10, 9), max_balance_churn_quotient: 32, beacon_chain_shard_number: u64::max_value(), - max_casper_votes: 1_024, - latest_block_roots_length: 8_192, - latest_randao_mixes_length: 8_192, - latest_penalized_exit_length: 8_192, + max_indices_per_slashable_vote: 4_096, max_withdrawals_per_epoch: 4, + shuffle_round_count: 90, + /* * Deposit contract */ - deposit_contract_address: Address::from("TBD".as_bytes()), + deposit_contract_address: Address::zero(), deposit_contract_tree_depth: 32, - min_deposit: 1 * u64::pow(10, 9), - max_deposit: 32 * u64::pow(10, 9), + + /* + * Gwei values + */ + min_deposit_amount: u64::pow(2, 0) * GWEI, + max_deposit_amount: u64::pow(2, 5) * GWEI, + fork_choice_balance_increment: u64::pow(2, 0) * GWEI, + ejection_balance: u64::pow(2, 4) * GWEI, + /* * Initial Values */ genesis_fork_version: 0, - genesis_slot: 0, + genesis_slot: Slot::new(2_u64.pow(19)), + genesis_epoch, genesis_start_shard: 0, - far_future_slot: u64::max_value(), + far_future_epoch: Epoch::new(u64::max_value()), zero_hash: Hash256::zero(), empty_signature: Signature::empty_signature(), - bls_withdrawal_prefix_byte: 0x00, + bls_withdrawal_prefix_byte: 0, + /* * Time parameters */ slot_duration: 6, min_attestation_inclusion_delay: 4, - epoch_length: 64, - seed_lookahead: 64, - entry_exit_delay: 256, - eth1_data_voting_period: 1_024, - min_validator_withdrawal_time: u64::pow(2, 14), + epoch_length, + seed_lookahead: Epoch::new(1), + entry_exit_delay: 4, + eth1_data_voting_period: 16, + min_validator_withdrawal_epochs: Epoch::new(256), + + /* + * State list lengths + */ + latest_block_roots_length: 8_192, + latest_randao_mixes_length: 8_192, + latest_index_roots_length: 8_192, + latest_penalized_exit_length: 8_192, + /* * Reward and penalty quotients */ base_reward_quotient: 32, whistleblower_reward_quotient: 512, includer_reward_quotient: 8, - inactivity_penalty_quotient: u64::pow(2, 24), + inactivity_penalty_quotient: 16_777_216, + /* * Max operations per block */ max_proposer_slashings: 16, - max_casper_slashings: 16, + max_attester_slashings: 1, max_attestations: 128, max_deposits: 16, max_exits: 16, + /* - * Intialization parameters + * Signature domains */ - initial_validators: initial_validators_for_testing(), - initial_balances: initial_balances_for_testing(), - genesis_time: 1_544_672_897, - intial_eth1_data: Eth1Data { - deposit_root: Hash256::from("deposit_root".as_bytes()), - block_hash: Hash256::from("block_hash".as_bytes()), - }, + domain_deposit: 0, + domain_attestation: 1, + domain_proposal: 2, + domain_exit: 3, + domain_randao: 4, } } } -/// Generate a set of validator records to use with testing until the real chain starts. -fn initial_validators_for_testing() -> Vec { - // Some dummy private keys to start with. - let key_strings = vec![ - "jzjxxgjajfjrmgodszzsgqccmhnyvetcuxobhtynojtpdtbj", - "gpeehcjudxdijzhjgirfuhahmnjutlchjmoffxmimbdejakd", - "ntrrdwwebodokuwaclhoqreqyodngoyhurvesghjfxeswoaj", - "cibmzkqrzdgdlrvqaxinwpvyhcgjkeysrsjkqtkcxvznsvth", - "erqrfuahdwprsstkawggounxmihzhrvbhchcyiwtaypqcedr", - ]; - - let mut initial_validators = Vec::with_capacity(key_strings.len()); - for key_string in key_strings { - let keypair = { - let secret_key = match SecretKey::from_bytes(&key_string.as_bytes()) { - Ok(key) => key, - Err(_) => unreachable!(), // Keys are static and should not fail. - }; - let public_key = PublicKey::from_secret_key(&secret_key); - Keypair { - sk: secret_key, - pk: public_key, - } - }; - let validator = Validator { - pubkey: keypair.pk.clone(), - withdrawal_credentials: Hash256::zero(), - proposer_slots: 0, - activation_slot: u64::max_value(), - exit_slot: u64::max_value(), - withdrawal_slot: u64::max_value(), - penalized_slot: u64::max_value(), - exit_count: 0, - status_flags: None, - latest_custody_reseed_slot: 0, - penultimate_custody_reseed_slot: 0, - }; - initial_validators.push(validator); - } - - initial_validators -} - -fn initial_balances_for_testing() -> Vec { - vec![DEPOSIT_GWEI; 4] -} - #[cfg(test)] mod tests { use super::*; diff --git a/eth2/types/src/spec/mod.rs b/eth2/types/src/spec/mod.rs index 077b6bef52..53c78a2c2c 100644 --- a/eth2/types/src/spec/mod.rs +++ b/eth2/types/src/spec/mod.rs @@ -1,8 +1,11 @@ mod foundation; -use crate::{Address, Eth1Data, Hash256, Validator}; +use crate::{Address, Epoch, Hash256, Slot}; use bls::Signature; +/// Holds all the "constants" for a BeaconChain. +/// +/// Spec v0.2.0 #[derive(PartialEq, Debug, Clone)] pub struct ChainSpec { /* @@ -10,41 +13,57 @@ pub struct ChainSpec { */ pub shard_count: u64, pub target_committee_size: u64, - pub ejection_balance: u64, pub max_balance_churn_quotient: u64, pub beacon_chain_shard_number: u64, - pub max_casper_votes: u64, - pub latest_block_roots_length: u64, - pub latest_randao_mixes_length: u64, - pub latest_penalized_exit_length: u64, + pub max_indices_per_slashable_vote: u64, pub max_withdrawals_per_epoch: u64, + pub shuffle_round_count: u64, + /* * Deposit contract */ pub deposit_contract_address: Address, pub deposit_contract_tree_depth: u64, - pub min_deposit: u64, - pub max_deposit: u64, + + /* + * Gwei values + */ + pub min_deposit_amount: u64, + pub max_deposit_amount: u64, + pub fork_choice_balance_increment: u64, + pub ejection_balance: u64, + /* * Initial Values */ pub genesis_fork_version: u64, - pub genesis_slot: u64, + pub genesis_slot: Slot, + pub genesis_epoch: Epoch, pub genesis_start_shard: u64, - pub far_future_slot: u64, + pub far_future_epoch: Epoch, pub zero_hash: Hash256, pub empty_signature: Signature, pub bls_withdrawal_prefix_byte: u8, + /* * Time parameters */ pub slot_duration: u64, pub min_attestation_inclusion_delay: u64, pub epoch_length: u64, - pub seed_lookahead: u64, + pub seed_lookahead: Epoch, pub entry_exit_delay: u64, pub eth1_data_voting_period: u64, - pub min_validator_withdrawal_time: u64, + pub min_validator_withdrawal_epochs: Epoch, + + /* + * State list lengths + */ + pub latest_block_roots_length: usize, + pub latest_randao_mixes_length: usize, + pub latest_index_roots_length: usize, + pub latest_penalized_exit_length: usize, + /* * Reward and penalty quotients */ @@ -52,19 +71,22 @@ pub struct ChainSpec { pub whistleblower_reward_quotient: u64, pub includer_reward_quotient: u64, pub inactivity_penalty_quotient: u64, + /* * Max operations per block */ pub max_proposer_slashings: u64, - pub max_casper_slashings: u64, + pub max_attester_slashings: u64, pub max_attestations: u64, pub max_deposits: u64, pub max_exits: u64, + /* - * Intialization parameters + * Signature domains */ - pub initial_validators: Vec, - pub initial_balances: Vec, - pub genesis_time: u64, - pub intial_eth1_data: Eth1Data, + pub domain_deposit: u64, + pub domain_attestation: u64, + pub domain_proposal: u64, + pub domain_exit: u64, + pub domain_randao: u64, } diff --git a/eth2/types/src/special_record.rs b/eth2/types/src/special_record.rs deleted file mode 100644 index 2ab6f2b5b2..0000000000 --- a/eth2/types/src/special_record.rs +++ /dev/null @@ -1,142 +0,0 @@ -use serde_derive::Serialize; -use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; - -/// The value of the "type" field of SpecialRecord. -/// -/// Note: this value must serialize to a u8 and therefore must not be greater than 255. -#[derive(Debug, PartialEq, Clone, Copy, Serialize)] -pub enum SpecialRecordKind { - Logout = 0, - CasperSlashing = 1, - RandaoChange = 2, -} - -/// The structure used in the `BeaconBlock.specials` field. -#[derive(Debug, PartialEq, Clone)] -pub struct SpecialRecord { - pub kind: u8, - pub data: Vec, -} - -impl SpecialRecord { - pub fn logout(data: &[u8]) -> Self { - Self { - kind: SpecialRecordKind::Logout as u8, - data: data.to_vec(), - } - } - - pub fn casper_slashing(data: &[u8]) -> Self { - Self { - kind: SpecialRecordKind::CasperSlashing as u8, - data: data.to_vec(), - } - } - - pub fn randao_change(data: &[u8]) -> Self { - Self { - kind: SpecialRecordKind::RandaoChange as u8, - data: data.to_vec(), - } - } - - /// Match `self.kind` to a `SpecialRecordKind`. - /// - /// Returns `None` if `self.kind` is an unknown value. - pub fn resolve_kind(&self) -> Option { - match self.kind { - x if x == SpecialRecordKind::Logout as u8 => Some(SpecialRecordKind::Logout), - x if x == SpecialRecordKind::CasperSlashing as u8 => { - Some(SpecialRecordKind::CasperSlashing) - } - x if x == SpecialRecordKind::RandaoChange as u8 => { - Some(SpecialRecordKind::RandaoChange) - } - _ => None, - } - } -} - -impl Encodable for SpecialRecord { - fn ssz_append(&self, s: &mut SszStream) { - s.append(&self.kind); - s.append_vec(&self.data); - } -} - -impl Decodable for SpecialRecord { - fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { - let (kind, i) = u8::ssz_decode(bytes, i)?; - let (data, i) = Decodable::ssz_decode(bytes, i)?; - Ok((SpecialRecord { kind, data }, i)) - } -} - -impl TreeHash for SpecialRecord { - fn hash_tree_root(&self) -> Vec { - let mut result: Vec = vec![]; - result.append(&mut self.kind.hash_tree_root()); - result.append(&mut self.data.as_slice().hash_tree_root()); - hash(&result) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - pub fn test_special_record_ssz_encode() { - let s = SpecialRecord::logout(&vec![]); - let mut ssz_stream = SszStream::new(); - ssz_stream.append(&s); - let ssz = ssz_stream.drain(); - assert_eq!(ssz, vec![0, 0, 0, 0, 0]); - - let s = SpecialRecord::casper_slashing(&vec![]); - let mut ssz_stream = SszStream::new(); - ssz_stream.append(&s); - let ssz = ssz_stream.drain(); - assert_eq!(ssz, vec![1, 0, 0, 0, 0]); - - let s = SpecialRecord::randao_change(&vec![]); - let mut ssz_stream = SszStream::new(); - ssz_stream.append(&s); - let ssz = ssz_stream.drain(); - assert_eq!(ssz, vec![2, 0, 0, 0, 0]); - - let s = SpecialRecord::randao_change(&vec![42, 43, 44]); - let mut ssz_stream = SszStream::new(); - ssz_stream.append(&s); - let ssz = ssz_stream.drain(); - assert_eq!(ssz, vec![2, 0, 0, 0, 3, 42, 43, 44]); - } - - #[test] - pub fn test_special_record_ssz_encode_decode() { - let s = SpecialRecord::randao_change(&vec![13, 16, 14]); - let mut ssz_stream = SszStream::new(); - ssz_stream.append(&s); - let ssz = ssz_stream.drain(); - let (s_decoded, _) = SpecialRecord::ssz_decode(&ssz, 0).unwrap(); - assert_eq!(s, s_decoded); - } - - #[test] - pub fn test_special_record_resolve_kind() { - let s = SpecialRecord::logout(&vec![]); - assert_eq!(s.resolve_kind(), Some(SpecialRecordKind::Logout)); - - let s = SpecialRecord::casper_slashing(&vec![]); - assert_eq!(s.resolve_kind(), Some(SpecialRecordKind::CasperSlashing)); - - let s = SpecialRecord::randao_change(&vec![]); - assert_eq!(s.resolve_kind(), Some(SpecialRecordKind::RandaoChange)); - - let s = SpecialRecord { - kind: 88, - data: vec![], - }; - assert_eq!(s.resolve_kind(), None); - } -} diff --git a/eth2/types/src/validator.rs b/eth2/types/src/validator.rs index 5c7d0ad308..047817a864 100644 --- a/eth2/types/src/validator.rs +++ b/eth2/types/src/validator.rs @@ -1,5 +1,4 @@ -use super::Hash256; -use crate::{test_utils::TestRandom, PublicKey}; +use crate::{test_utils::TestRandom, Epoch, Hash256, PublicKey}; use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; @@ -47,21 +46,17 @@ fn status_flag_from_byte(flag: u8) -> Result, StatusFlagsDec pub struct Validator { pub pubkey: PublicKey, pub withdrawal_credentials: Hash256, - pub proposer_slots: u64, - pub activation_slot: u64, - pub exit_slot: u64, - pub withdrawal_slot: u64, - pub penalized_slot: u64, - pub exit_count: u64, + pub activation_epoch: Epoch, + pub exit_epoch: Epoch, + pub withdrawal_epoch: Epoch, + pub penalized_epoch: Epoch, pub status_flags: Option, - pub latest_custody_reseed_slot: u64, - pub penultimate_custody_reseed_slot: u64, } impl Validator { /// This predicate indicates if the validator represented by this record is considered "active" at `slot`. - pub fn is_active_at(&self, slot: u64) -> bool { - self.activation_slot <= slot && slot < self.exit_slot + pub fn is_active_at(&self, slot: Epoch) -> bool { + self.activation_epoch <= slot && slot < self.exit_epoch } } @@ -71,15 +66,11 @@ impl Default for Validator { Self { pubkey: PublicKey::default(), withdrawal_credentials: Hash256::default(), - proposer_slots: 0, - activation_slot: std::u64::MAX, - exit_slot: std::u64::MAX, - withdrawal_slot: std::u64::MAX, - penalized_slot: std::u64::MAX, - exit_count: 0, + activation_epoch: Epoch::from(std::u64::MAX), + exit_epoch: Epoch::from(std::u64::MAX), + withdrawal_epoch: Epoch::from(std::u64::MAX), + penalized_epoch: Epoch::from(std::u64::MAX), status_flags: None, - latest_custody_reseed_slot: 0, // NOTE: is `GENESIS_SLOT` - penultimate_custody_reseed_slot: 0, // NOTE: is `GENESIS_SLOT` } } } @@ -87,7 +78,7 @@ impl Default for Validator { impl TestRandom for StatusFlags { fn random_for_test(rng: &mut T) -> Self { let options = vec![StatusFlags::InitiatedExit, StatusFlags::Withdrawable]; - options[(rng.next_u32() as usize) % options.len()].clone() + options[(rng.next_u32() as usize) % options.len()] } } @@ -95,15 +86,11 @@ impl Encodable for Validator { fn ssz_append(&self, s: &mut SszStream) { s.append(&self.pubkey); s.append(&self.withdrawal_credentials); - s.append(&self.proposer_slots); - s.append(&self.activation_slot); - s.append(&self.exit_slot); - s.append(&self.withdrawal_slot); - s.append(&self.penalized_slot); - s.append(&self.exit_count); + s.append(&self.activation_epoch); + s.append(&self.exit_epoch); + s.append(&self.withdrawal_epoch); + s.append(&self.penalized_epoch); s.append(&status_flag_to_byte(self.status_flags)); - s.append(&self.latest_custody_reseed_slot); - s.append(&self.penultimate_custody_reseed_slot); } } @@ -111,15 +98,11 @@ impl Decodable for Validator { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { let (pubkey, i) = <_>::ssz_decode(bytes, i)?; let (withdrawal_credentials, i) = <_>::ssz_decode(bytes, i)?; - let (proposer_slots, i) = <_>::ssz_decode(bytes, i)?; - let (activation_slot, i) = <_>::ssz_decode(bytes, i)?; - let (exit_slot, i) = <_>::ssz_decode(bytes, i)?; - let (withdrawal_slot, i) = <_>::ssz_decode(bytes, i)?; - let (penalized_slot, i) = <_>::ssz_decode(bytes, i)?; - let (exit_count, i) = <_>::ssz_decode(bytes, i)?; + let (activation_epoch, i) = <_>::ssz_decode(bytes, i)?; + let (exit_epoch, i) = <_>::ssz_decode(bytes, i)?; + let (withdrawal_epoch, i) = <_>::ssz_decode(bytes, i)?; + let (penalized_epoch, i) = <_>::ssz_decode(bytes, i)?; let (status_flags_byte, i): (u8, usize) = <_>::ssz_decode(bytes, i)?; - let (latest_custody_reseed_slot, i) = <_>::ssz_decode(bytes, i)?; - let (penultimate_custody_reseed_slot, i) = <_>::ssz_decode(bytes, i)?; let status_flags = status_flag_from_byte(status_flags_byte)?; @@ -127,15 +110,11 @@ impl Decodable for Validator { Self { pubkey, withdrawal_credentials, - proposer_slots, - activation_slot, - exit_slot, - withdrawal_slot, - penalized_slot, - exit_count, + activation_epoch, + exit_epoch, + withdrawal_epoch, + penalized_epoch, status_flags, - latest_custody_reseed_slot, - penultimate_custody_reseed_slot, }, i, )) @@ -147,15 +126,11 @@ impl TreeHash for Validator { let mut result: Vec = vec![]; result.append(&mut self.pubkey.hash_tree_root()); result.append(&mut self.withdrawal_credentials.hash_tree_root()); - result.append(&mut self.proposer_slots.hash_tree_root()); - result.append(&mut self.activation_slot.hash_tree_root()); - result.append(&mut self.exit_slot.hash_tree_root()); - result.append(&mut self.withdrawal_slot.hash_tree_root()); - result.append(&mut self.penalized_slot.hash_tree_root()); - result.append(&mut self.exit_count.hash_tree_root()); - result.append(&mut (status_flag_to_byte(self.status_flags) as u64).hash_tree_root()); - result.append(&mut self.latest_custody_reseed_slot.hash_tree_root()); - result.append(&mut self.penultimate_custody_reseed_slot.hash_tree_root()); + result.append(&mut self.activation_epoch.hash_tree_root()); + result.append(&mut self.exit_epoch.hash_tree_root()); + result.append(&mut self.withdrawal_epoch.hash_tree_root()); + result.append(&mut self.penalized_epoch.hash_tree_root()); + result.append(&mut u64::from(status_flag_to_byte(self.status_flags)).hash_tree_root()); hash(&result) } } @@ -165,15 +140,11 @@ impl TestRandom for Validator { Self { pubkey: <_>::random_for_test(rng), withdrawal_credentials: <_>::random_for_test(rng), - proposer_slots: <_>::random_for_test(rng), - activation_slot: <_>::random_for_test(rng), - exit_slot: <_>::random_for_test(rng), - withdrawal_slot: <_>::random_for_test(rng), - penalized_slot: <_>::random_for_test(rng), - exit_count: <_>::random_for_test(rng), + activation_epoch: <_>::random_for_test(rng), + exit_epoch: <_>::random_for_test(rng), + withdrawal_epoch: <_>::random_for_test(rng), + penalized_epoch: <_>::random_for_test(rng), status_flags: Some(<_>::random_for_test(rng)), - latest_custody_reseed_slot: <_>::random_for_test(rng), - penultimate_custody_reseed_slot: <_>::random_for_test(rng), } } } @@ -200,16 +171,17 @@ mod tests { let mut rng = XorShiftRng::from_seed([42; 16]); let mut validator = Validator::random_for_test(&mut rng); - let activation_slot = u64::random_for_test(&mut rng); - let exit_slot = activation_slot + 234; + let activation_epoch = u64::random_for_test(&mut rng); + let exit_epoch = activation_epoch + 234; - validator.activation_slot = activation_slot; - validator.exit_slot = exit_slot; + validator.activation_epoch = Epoch::from(activation_epoch); + validator.exit_epoch = Epoch::from(exit_epoch); - for slot in (activation_slot - 100)..(exit_slot + 100) { - if slot < activation_slot { + for slot in (activation_epoch - 100)..(exit_epoch + 100) { + let slot = Epoch::from(slot); + if slot < activation_epoch { assert!(!validator.is_active_at(slot)); - } else if slot >= exit_slot { + } else if slot >= exit_epoch { assert!(!validator.is_active_at(slot)); } else { assert!(validator.is_active_at(slot)); diff --git a/eth2/types/src/validator_registry.rs b/eth2/types/src/validator_registry.rs index 509ab7c7b0..20863dd72d 100644 --- a/eth2/types/src/validator_registry.rs +++ b/eth2/types/src/validator_registry.rs @@ -1,14 +1,15 @@ /// Contains logic to manipulate a `&[Validator]`. /// For now, we avoid defining a newtype and just have flat functions here. use super::validator::*; +use crate::Epoch; -/// Given an indexed sequence of `validators`, return the indices corresponding to validators that are active at `slot`. -pub fn get_active_validator_indices(validators: &[Validator], slot: u64) -> Vec { +/// Given an indexed sequence of `validators`, return the indices corresponding to validators that are active at `epoch`. +pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec { validators .iter() .enumerate() .filter_map(|(index, validator)| { - if validator.is_active_at(slot) { + if validator.is_active_at(epoch) { Some(index) } else { None @@ -27,8 +28,8 @@ mod tests { let mut rng = XorShiftRng::from_seed([42; 16]); let validators = vec![]; - let some_slot = u64::random_for_test(&mut rng); - let indices = get_active_validator_indices(&validators, some_slot); + let some_epoch = Epoch::random_for_test(&mut rng); + let indices = get_active_validator_indices(&validators, some_epoch); assert_eq!(indices, vec![]); } @@ -41,8 +42,8 @@ mod tests { validators.push(Validator::default()) } - let some_slot = u64::random_for_test(&mut rng); - let indices = get_active_validator_indices(&validators, some_slot); + let some_epoch = Epoch::random_for_test(&mut rng); + let indices = get_active_validator_indices(&validators, some_epoch); assert_eq!(indices, vec![]); } @@ -50,7 +51,7 @@ mod tests { fn can_get_all_active_validator_indices() { let mut rng = XorShiftRng::from_seed([42; 16]); let count_validators = 10; - let some_slot = u64::random_for_test(&mut rng); + let some_epoch = Epoch::random_for_test(&mut rng); let mut validators = (0..count_validators) .into_iter() @@ -60,8 +61,8 @@ mod tests { let activation_offset = u64::random_for_test(&mut rng); let exit_offset = u64::random_for_test(&mut rng); - validator.activation_slot = some_slot.checked_sub(activation_offset).unwrap_or(0); - validator.exit_slot = some_slot.checked_add(exit_offset).unwrap_or(std::u64::MAX); + validator.activation_epoch = some_epoch - activation_offset; + validator.exit_epoch = some_epoch + exit_offset; validator }) @@ -69,10 +70,10 @@ mod tests { // test boundary condition by ensuring that at least one validator in the list just activated if let Some(validator) = validators.get_mut(0) { - validator.activation_slot = some_slot; + validator.activation_epoch = some_epoch; } - let indices = get_active_validator_indices(&validators, some_slot); + let indices = get_active_validator_indices(&validators, some_epoch); assert_eq!( indices, (0..count_validators).into_iter().collect::>() @@ -81,31 +82,35 @@ mod tests { fn set_validators_to_default_entry_exit(validators: &mut [Validator]) { for validator in validators.iter_mut() { - validator.activation_slot = std::u64::MAX; - validator.exit_slot = std::u64::MAX; + validator.activation_epoch = Epoch::max_value(); + validator.exit_epoch = Epoch::max_value(); } } - // sets all `validators` to be active as of some slot prior to `slot`. returns the activation slot. - fn set_validators_to_activated(validators: &mut [Validator], slot: u64) -> u64 { - let activation_slot = slot - 10; + // sets all `validators` to be active as of some epoch prior to `epoch`. returns the activation epoch. + fn set_validators_to_activated(validators: &mut [Validator], epoch: Epoch) -> Epoch { + let activation_epoch = epoch - 10; for validator in validators.iter_mut() { - validator.activation_slot = activation_slot; + validator.activation_epoch = activation_epoch; } - activation_slot + activation_epoch } - // sets all `validators` to be exited as of some slot before `slot`. - fn set_validators_to_exited(validators: &mut [Validator], slot: u64, activation_slot: u64) { - assert!(activation_slot < slot); - let mut exit_slot = activation_slot + 10; - while exit_slot >= slot { - exit_slot -= 1; + // sets all `validators` to be exited as of some epoch before `epoch`. + fn set_validators_to_exited( + validators: &mut [Validator], + epoch: Epoch, + activation_epoch: Epoch, + ) { + assert!(activation_epoch < epoch); + let mut exit_epoch = activation_epoch + 10; + while exit_epoch >= epoch { + exit_epoch -= 1; } - assert!(activation_slot < exit_slot && exit_slot < slot); + assert!(activation_epoch < exit_epoch && exit_epoch < epoch); for validator in validators.iter_mut() { - validator.exit_slot = exit_slot; + validator.exit_epoch = exit_epoch; } } @@ -114,18 +119,18 @@ mod tests { let mut rng = XorShiftRng::from_seed([42; 16]); const COUNT_PARTITIONS: usize = 3; const COUNT_VALIDATORS: usize = 3 * COUNT_PARTITIONS; - let some_slot: u64 = u64::random_for_test(&mut rng); + let some_epoch: Epoch = Epoch::random_for_test(&mut rng); let mut validators = (0..COUNT_VALIDATORS) .into_iter() .map(|_| { let mut validator = Validator::default(); - let activation_offset = u64::random_for_test(&mut rng); - let exit_offset = u64::random_for_test(&mut rng); + let activation_offset = Epoch::random_for_test(&mut rng); + let exit_offset = Epoch::random_for_test(&mut rng); - validator.activation_slot = some_slot.checked_sub(activation_offset).unwrap_or(0); - validator.exit_slot = some_slot.checked_add(exit_offset).unwrap_or(std::u64::MAX); + validator.activation_epoch = some_epoch - activation_offset; + validator.exit_epoch = some_epoch + exit_offset; validator }) @@ -140,19 +145,19 @@ mod tests { } 1 => { // 2. activated, but not exited - set_validators_to_activated(chunk, some_slot); + set_validators_to_activated(chunk, some_epoch); // test boundary condition by ensuring that at least one validator in the list just activated if let Some(validator) = chunk.get_mut(0) { - validator.activation_slot = some_slot; + validator.activation_epoch = some_epoch; } } 2 => { // 3. exited - let activation_slot = set_validators_to_activated(chunk, some_slot); - set_validators_to_exited(chunk, some_slot, activation_slot); + let activation_epoch = set_validators_to_activated(chunk, some_epoch); + set_validators_to_exited(chunk, some_epoch, activation_epoch); // test boundary condition by ensuring that at least one validator in the list just exited if let Some(validator) = chunk.get_mut(0) { - validator.exit_slot = some_slot; + validator.exit_epoch = some_epoch; } } _ => unreachable!( @@ -161,7 +166,7 @@ mod tests { } } - let indices = get_active_validator_indices(&validators, some_slot); + let indices = get_active_validator_indices(&validators, some_epoch); assert_eq!(indices, vec![3, 4, 5]); } } diff --git a/eth2/types/src/validator_registry_delta_block.rs b/eth2/types/src/validator_registry_delta_block.rs index 196da754d0..3142e02637 100644 --- a/eth2/types/src/validator_registry_delta_block.rs +++ b/eth2/types/src/validator_registry_delta_block.rs @@ -1,5 +1,4 @@ -use super::Hash256; -use crate::test_utils::TestRandom; +use crate::{test_utils::TestRandom, Hash256, Slot}; use bls::PublicKey; use rand::RngCore; use serde_derive::Serialize; @@ -11,7 +10,7 @@ pub struct ValidatorRegistryDeltaBlock { pub latest_registry_delta_root: Hash256, pub validator_index: u32, pub pubkey: PublicKey, - pub slot: u64, + pub slot: Slot, pub flag: u64, } @@ -22,7 +21,7 @@ impl Default for ValidatorRegistryDeltaBlock { latest_registry_delta_root: Hash256::zero(), validator_index: std::u32::MAX, pubkey: PublicKey::default(), - slot: std::u64::MAX, + slot: Slot::from(std::u64::MAX), flag: std::u64::MAX, } } diff --git a/eth2/utils/boolean-bitfield/src/lib.rs b/eth2/utils/boolean-bitfield/src/lib.rs index 16992c3fa0..a0fce1f0a4 100644 --- a/eth2/utils/boolean-bitfield/src/lib.rs +++ b/eth2/utils/boolean-bitfield/src/lib.rs @@ -81,6 +81,11 @@ impl BooleanBitfield { self.0.len() } + /// Returns true if `self.len() == 0` + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + /// Returns the number of bytes required to represent this bitfield. pub fn num_bytes(&self) -> usize { self.to_bytes().len() diff --git a/eth2/utils/slot_clock/Cargo.toml b/eth2/utils/slot_clock/Cargo.toml index 166f397fd1..31a4357251 100644 --- a/eth2/utils/slot_clock/Cargo.toml +++ b/eth2/utils/slot_clock/Cargo.toml @@ -5,3 +5,4 @@ authors = ["Paul Hauner "] edition = "2018" [dependencies] +types = { path = "../../types" } diff --git a/eth2/utils/slot_clock/src/lib.rs b/eth2/utils/slot_clock/src/lib.rs index 4863f76698..0379d50d9a 100644 --- a/eth2/utils/slot_clock/src/lib.rs +++ b/eth2/utils/slot_clock/src/lib.rs @@ -3,9 +3,10 @@ mod testing_slot_clock; pub use crate::system_time_slot_clock::{Error as SystemTimeSlotClockError, SystemTimeSlotClock}; pub use crate::testing_slot_clock::{Error as TestingSlotClockError, TestingSlotClock}; +pub use types::Slot; pub trait SlotClock: Send + Sync { type Error; - fn present_slot(&self) -> Result, Self::Error>; + fn present_slot(&self) -> Result, Self::Error>; } diff --git a/eth2/utils/slot_clock/src/system_time_slot_clock.rs b/eth2/utils/slot_clock/src/system_time_slot_clock.rs index ba62b3b932..99f051985f 100644 --- a/eth2/utils/slot_clock/src/system_time_slot_clock.rs +++ b/eth2/utils/slot_clock/src/system_time_slot_clock.rs @@ -1,5 +1,6 @@ use super::SlotClock; use std::time::{Duration, SystemTime}; +use types::Slot; pub use std::time::SystemTimeError; @@ -38,7 +39,7 @@ impl SystemTimeSlotClock { impl SlotClock for SystemTimeSlotClock { type Error = Error; - fn present_slot(&self) -> Result, Error> { + fn present_slot(&self) -> Result, Error> { let syslot_time = SystemTime::now(); let duration_since_epoch = syslot_time.duration_since(SystemTime::UNIX_EPOCH)?; let duration_since_genesis = @@ -56,8 +57,10 @@ impl From for Error { } } -fn slot_from_duration(slot_duration_seconds: u64, duration: Duration) -> Option { - duration.as_secs().checked_div(slot_duration_seconds) +fn slot_from_duration(slot_duration_seconds: u64, duration: Duration) -> Option { + Some(Slot::new( + duration.as_secs().checked_div(slot_duration_seconds)?, + )) } #[cfg(test)] @@ -81,19 +84,19 @@ mod tests { genesis_seconds: genesis, slot_duration_seconds: slot_time, }; - assert_eq!(clock.present_slot().unwrap(), Some(89)); + assert_eq!(clock.present_slot().unwrap(), Some(Slot::new(89))); let clock = SystemTimeSlotClock { genesis_seconds: since_epoch.as_secs(), slot_duration_seconds: slot_time, }; - assert_eq!(clock.present_slot().unwrap(), Some(0)); + assert_eq!(clock.present_slot().unwrap(), Some(Slot::new(0))); let clock = SystemTimeSlotClock { genesis_seconds: since_epoch.as_secs() - slot_time * 42 - 5, slot_duration_seconds: slot_time, }; - assert_eq!(clock.present_slot().unwrap(), Some(42)); + assert_eq!(clock.present_slot().unwrap(), Some(Slot::new(42))); } #[test] @@ -102,23 +105,23 @@ mod tests { assert_eq!( slot_from_duration(slot_time, Duration::from_secs(0)), - Some(0) + Some(Slot::new(0)) ); assert_eq!( slot_from_duration(slot_time, Duration::from_secs(10)), - Some(0) + Some(Slot::new(0)) ); assert_eq!( slot_from_duration(slot_time, Duration::from_secs(100)), - Some(1) + Some(Slot::new(1)) ); assert_eq!( slot_from_duration(slot_time, Duration::from_secs(101)), - Some(1) + Some(Slot::new(1)) ); assert_eq!( slot_from_duration(slot_time, Duration::from_secs(1000)), - Some(10) + Some(Slot::new(10)) ); } diff --git a/eth2/utils/slot_clock/src/testing_slot_clock.rs b/eth2/utils/slot_clock/src/testing_slot_clock.rs index 330d47f1ab..80ee405397 100644 --- a/eth2/utils/slot_clock/src/testing_slot_clock.rs +++ b/eth2/utils/slot_clock/src/testing_slot_clock.rs @@ -1,5 +1,6 @@ use super::SlotClock; use std::sync::RwLock; +use types::Slot; #[derive(Debug, PartialEq)] pub enum Error {} @@ -27,9 +28,9 @@ impl TestingSlotClock { impl SlotClock for TestingSlotClock { type Error = Error; - fn present_slot(&self) -> Result, Error> { + fn present_slot(&self) -> Result, Error> { let slot = *self.slot.read().expect("TestingSlotClock poisoned."); - Ok(Some(slot)) + Ok(Some(Slot::new(slot))) } } @@ -40,8 +41,8 @@ mod tests { #[test] fn test_slot_now() { let clock = TestingSlotClock::new(10); - assert_eq!(clock.present_slot(), Ok(Some(10))); + assert_eq!(clock.present_slot(), Ok(Some(Slot::new(10)))); clock.set_slot(123); - assert_eq!(clock.present_slot(), Ok(Some(123))); + assert_eq!(clock.present_slot(), Ok(Some(Slot::new(123)))); } } diff --git a/eth2/validator_induction/src/inductor.rs b/eth2/validator_induction/src/inductor.rs deleted file mode 100644 index 6b3cfdf16e..0000000000 --- a/eth2/validator_induction/src/inductor.rs +++ /dev/null @@ -1,180 +0,0 @@ -use bls::verify_proof_of_possession; -use types::{BeaconState, ChainSpec, Deposit, Validator}; - -#[derive(Debug, PartialEq, Clone)] -pub enum ValidatorInductionError { - InvalidShard, - InvaidProofOfPossession, - InvalidWithdrawalCredentials, -} - -pub fn process_deposit( - state: &mut BeaconState, - deposit: &Deposit, - spec: &ChainSpec, -) -> Result<(), ValidatorInductionError> { - let deposit_input = &deposit.deposit_data.deposit_input; - let deposit_data = &deposit.deposit_data; - - // TODO: Update the signature validation as defined in the spec once issues #91 and #70 are completed - if !verify_proof_of_possession(&deposit_input.proof_of_possession, &deposit_input.pubkey) { - return Err(ValidatorInductionError::InvaidProofOfPossession); - } - - let validator_index = state - .validator_registry - .iter() - .position(|validator| validator.pubkey == deposit_input.pubkey); - - match validator_index { - Some(i) => { - if state.validator_registry[i].withdrawal_credentials - == deposit_input.withdrawal_credentials - { - state.validator_balances[i] += deposit_data.amount; - return Ok(()); - } - - Err(ValidatorInductionError::InvalidWithdrawalCredentials) - } - None => { - let validator = Validator { - pubkey: deposit_input.pubkey.clone(), - withdrawal_credentials: deposit_input.withdrawal_credentials, - proposer_slots: 0, - activation_slot: spec.far_future_slot, - exit_slot: spec.far_future_slot, - withdrawal_slot: spec.far_future_slot, - penalized_slot: spec.far_future_slot, - exit_count: 0, - status_flags: None, - latest_custody_reseed_slot: 0, - penultimate_custody_reseed_slot: 0, - }; - - let _index = state.validator_registry.len(); - state.validator_registry.push(validator); - state.validator_balances.push(deposit_data.amount); - Ok(()) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use types::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - - use bls::{create_proof_of_possession, Keypair}; - - /// The size of a validators deposit in GWei. - pub const DEPOSIT_GWEI: u64 = 32_000_000_000; - - fn get_deposit() -> Deposit { - let mut rng = XorShiftRng::from_seed([42; 16]); - let mut deposit = Deposit::random_for_test(&mut rng); - - let kp = Keypair::random(); - deposit.deposit_data.deposit_input.pubkey = kp.pk.clone(); - deposit.deposit_data.deposit_input.proof_of_possession = create_proof_of_possession(&kp); - deposit - } - - fn get_validator() -> Validator { - let mut rng = XorShiftRng::from_seed([42; 16]); - Validator::random_for_test(&mut rng) - } - - fn deposit_equals_record(dep: &Deposit, val: &Validator) -> bool { - (dep.deposit_data.deposit_input.pubkey == val.pubkey) - & (dep.deposit_data.deposit_input.withdrawal_credentials == val.withdrawal_credentials) - & (verify_proof_of_possession( - &dep.deposit_data.deposit_input.proof_of_possession, - &val.pubkey, - )) - } - - #[test] - fn test_process_deposit_valid_empty_validators() { - let mut state = BeaconState::default(); - let mut deposit = get_deposit(); - let spec = ChainSpec::foundation(); - deposit.deposit_data.amount = DEPOSIT_GWEI; - - let result = process_deposit(&mut state, &deposit, &spec); - - assert_eq!(result.unwrap(), ()); - assert!(deposit_equals_record( - &deposit, - &state.validator_registry[0] - )); - assert_eq!(state.validator_registry.len(), 1); - assert_eq!(state.validator_balances.len(), 1); - } - - #[test] - fn test_process_deposits_empty_validators() { - let mut state = BeaconState::default(); - let spec = ChainSpec::foundation(); - - for i in 0..5 { - let mut deposit = get_deposit(); - let result = process_deposit(&mut state, &deposit, &spec); - deposit.deposit_data.amount = DEPOSIT_GWEI; - assert_eq!(result.unwrap(), ()); - assert!(deposit_equals_record( - &deposit, - &state.validator_registry[i] - )); - assert_eq!(state.validator_registry.len(), i + 1); - assert_eq!(state.validator_balances.len(), i + 1); - } - } - - #[test] - fn test_process_deposit_top_out() { - let mut state = BeaconState::default(); - let spec = ChainSpec::foundation(); - - let mut deposit = get_deposit(); - let mut validator = get_validator(); - - deposit.deposit_data.amount = DEPOSIT_GWEI; - validator.pubkey = deposit.deposit_data.deposit_input.pubkey.clone(); - validator.withdrawal_credentials = - deposit.deposit_data.deposit_input.withdrawal_credentials; - - state.validator_registry.push(validator); - state.validator_balances.push(DEPOSIT_GWEI); - - let result = process_deposit(&mut state, &deposit, &spec); - - assert_eq!(result.unwrap(), ()); - assert!(deposit_equals_record( - &deposit, - &state.validator_registry[0] - )); - assert_eq!(state.validator_balances[0], DEPOSIT_GWEI * 2); - assert_eq!(state.validator_registry.len(), 1); - assert_eq!(state.validator_balances.len(), 1); - } - - #[test] - fn test_process_deposit_invalid_proof_of_possession() { - let mut state = BeaconState::default(); - let mut deposit = get_deposit(); - let spec = ChainSpec::foundation(); - deposit.deposit_data.amount = DEPOSIT_GWEI; - deposit.deposit_data.deposit_input.proof_of_possession = - create_proof_of_possession(&Keypair::random()); - - let result = process_deposit(&mut state, &deposit, &spec); - - assert_eq!( - result, - Err(ValidatorInductionError::InvaidProofOfPossession) - ); - assert_eq!(state.validator_registry.len(), 0); - assert_eq!(state.validator_balances.len(), 0); - } -} diff --git a/eth2/validator_induction/src/lib.rs b/eth2/validator_induction/src/lib.rs deleted file mode 100644 index 3a4fa9a14a..0000000000 --- a/eth2/validator_induction/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod inductor; - -pub use crate::inductor::{process_deposit, ValidatorInductionError}; diff --git a/protos/src/lib.rs b/protos/src/lib.rs index a9684909d6..2759263e76 100644 --- a/protos/src/lib.rs +++ b/protos/src/lib.rs @@ -1,2 +1,5 @@ +// The protobuf code-generator is not up-to-date with clippy, therefore we silence some warnings. +#[allow(renamed_and_removed_lints)] pub mod services; +#[allow(renamed_and_removed_lints)] pub mod services_grpc; diff --git a/validator_client/src/block_producer_service/grpc.rs b/validator_client/src/block_producer_service/beacon_block_grpc_client.rs similarity index 83% rename from validator_client/src/block_producer_service/grpc.rs rename to validator_client/src/block_producer_service/beacon_block_grpc_client.rs index 9ac8e779c6..39ef7fcdc9 100644 --- a/validator_client/src/block_producer_service/grpc.rs +++ b/validator_client/src/block_producer_service/beacon_block_grpc_client.rs @@ -5,7 +5,7 @@ use protos::services::{ use protos::services_grpc::BeaconBlockServiceClient; use ssz::{ssz_encode, Decodable}; use std::sync::Arc; -use types::{BeaconBlock, BeaconBlockBody, Eth1Data, Hash256, PublicKey, Signature}; +use types::{BeaconBlock, BeaconBlockBody, Eth1Data, Hash256, Signature, Slot}; /// A newtype designed to wrap the gRPC-generated service so the `BeaconNode` trait may be /// implemented upon it. @@ -20,23 +20,18 @@ impl BeaconBlockGrpcClient { } impl BeaconNode for BeaconBlockGrpcClient { - fn proposer_nonce(&self, pubkey: &PublicKey) -> Result { - // TODO: this might not be required. - // - // See: https://github.com/ethereum/eth2.0-specs/pull/496 - panic!("Not implemented.") - } /// Request a Beacon Node (BN) to produce a new block at the supplied slot. /// /// Returns `None` if it is not possible to produce at the supplied slot. For example, if the /// BN is unable to find a parent block. fn produce_beacon_block( &self, - slot: u64, - randao_reveal: &Signature, + slot: Slot, + // TODO: use randao_reveal, when proto APIs have been updated. + _randao_reveal: &Signature, ) -> Result, BeaconNodeError> { let mut req = ProduceBeaconBlockRequest::new(); - req.set_slot(slot); + req.set_slot(slot.as_u64()); let reply = self .client @@ -54,7 +49,7 @@ impl BeaconNode for BeaconBlockGrpcClient { // TODO: this conversion is incomplete; fix it. Ok(Some(BeaconBlock { - slot: block.get_slot(), + slot: Slot::new(block.get_slot()), parent_root: Hash256::zero(), state_root: Hash256::zero(), randao_reveal, @@ -65,11 +60,8 @@ impl BeaconNode for BeaconBlockGrpcClient { signature, body: BeaconBlockBody { proposer_slashings: vec![], - casper_slashings: vec![], + attester_slashings: vec![], attestations: vec![], - custody_reseeds: vec![], - custody_challenges: vec![], - custody_responses: vec![], deposits: vec![], exits: vec![], }, @@ -88,7 +80,7 @@ impl BeaconNode for BeaconBlockGrpcClient { // TODO: this conversion is incomplete; fix it. let mut grpc_block = GrpcBeaconBlock::new(); - grpc_block.set_slot(block.slot); + grpc_block.set_slot(block.slot.as_u64()); grpc_block.set_block_root(vec![0]); grpc_block.set_randao_reveal(ssz_encode(&block.randao_reveal)); grpc_block.set_signature(ssz_encode(&block.signature)); diff --git a/validator_client/src/block_producer_service/mod.rs b/validator_client/src/block_producer_service/mod.rs index a4ddf760a4..82c3f2537f 100644 --- a/validator_client/src/block_producer_service/mod.rs +++ b/validator_client/src/block_producer_service/mod.rs @@ -1,5 +1,58 @@ -mod grpc; -mod service; +mod beacon_block_grpc_client; +// mod block_producer_service; -pub use self::grpc::BeaconBlockGrpcClient; -pub use self::service::BlockProducerService; +use block_producer::{ + BeaconNode, BlockProducer, DutiesReader, PollOutcome as BlockProducerPollOutcome, Signer, +}; +use slog::{error, info, warn, Logger}; +use slot_clock::SlotClock; +use std::time::Duration; + +pub use self::beacon_block_grpc_client::BeaconBlockGrpcClient; + +pub struct BlockProducerService { + pub block_producer: BlockProducer, + pub poll_interval_millis: u64, + pub log: Logger, +} + +impl BlockProducerService { + /// Run a loop which polls the block producer each `poll_interval_millis` millseconds. + /// + /// Logs the results of the polls. + pub fn run(&mut self) { + loop { + match self.block_producer.poll() { + Err(error) => { + error!(self.log, "Block producer poll error"; "error" => format!("{:?}", error)) + } + Ok(BlockProducerPollOutcome::BlockProduced(slot)) => { + info!(self.log, "Produced block"; "slot" => slot) + } + Ok(BlockProducerPollOutcome::SlashableBlockNotProduced(slot)) => { + warn!(self.log, "Slashable block was not signed"; "slot" => slot) + } + Ok(BlockProducerPollOutcome::BlockProductionNotRequired(slot)) => { + info!(self.log, "Block production not required"; "slot" => slot) + } + Ok(BlockProducerPollOutcome::ProducerDutiesUnknown(slot)) => { + error!(self.log, "Block production duties unknown"; "slot" => slot) + } + Ok(BlockProducerPollOutcome::SlotAlreadyProcessed(slot)) => { + warn!(self.log, "Attempted to re-process slot"; "slot" => slot) + } + Ok(BlockProducerPollOutcome::BeaconNodeUnableToProduceBlock(slot)) => { + error!(self.log, "Beacon node unable to produce block"; "slot" => slot) + } + Ok(BlockProducerPollOutcome::SignerRejection(slot)) => { + error!(self.log, "The cryptographic signer refused to sign the block"; "slot" => slot) + } + Ok(BlockProducerPollOutcome::ValidatorIsUnknown(slot)) => { + error!(self.log, "The Beacon Node does not recognise the validator"; "slot" => slot) + } + }; + + std::thread::sleep(Duration::from_millis(self.poll_interval_millis)); + } + } +} diff --git a/validator_client/src/block_producer_service/service.rs b/validator_client/src/block_producer_service/service.rs deleted file mode 100644 index 5e335e3835..0000000000 --- a/validator_client/src/block_producer_service/service.rs +++ /dev/null @@ -1,53 +0,0 @@ -use block_producer::{ - BeaconNode, BlockProducer, DutiesReader, PollOutcome as BlockProducerPollOutcome, Signer, -}; -use slog::{error, info, warn, Logger}; -use slot_clock::SlotClock; -use std::time::Duration; - -pub struct BlockProducerService { - pub block_producer: BlockProducer, - pub poll_interval_millis: u64, - pub log: Logger, -} - -impl BlockProducerService { - /// Run a loop which polls the block producer each `poll_interval_millis` millseconds. - /// - /// Logs the results of the polls. - pub fn run(&mut self) { - loop { - match self.block_producer.poll() { - Err(error) => { - error!(self.log, "Block producer poll error"; "error" => format!("{:?}", error)) - } - Ok(BlockProducerPollOutcome::BlockProduced(slot)) => { - info!(self.log, "Produced block"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::SlashableBlockNotProduced(slot)) => { - warn!(self.log, "Slashable block was not signed"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::BlockProductionNotRequired(slot)) => { - info!(self.log, "Block production not required"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::ProducerDutiesUnknown(slot)) => { - error!(self.log, "Block production duties unknown"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::SlotAlreadyProcessed(slot)) => { - warn!(self.log, "Attempted to re-process slot"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::BeaconNodeUnableToProduceBlock(slot)) => { - error!(self.log, "Beacon node unable to produce block"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::SignerRejection(slot)) => { - error!(self.log, "The cryptographic signer refused to sign the block"; "slot" => slot) - } - Ok(BlockProducerPollOutcome::ValidatorIsUnknown(slot)) => { - error!(self.log, "The Beacon Node does not recognise the validator"; "slot" => slot) - } - }; - - std::thread::sleep(Duration::from_millis(self.poll_interval_millis)); - } - } -} diff --git a/validator_client/src/duties/duties_map.rs b/validator_client/src/duties/epoch_duties.rs similarity index 78% rename from validator_client/src/duties/duties_map.rs rename to validator_client/src/duties/epoch_duties.rs index ea3c8ae4a2..b555eee28c 100644 --- a/validator_client/src/duties/duties_map.rs +++ b/validator_client/src/duties/epoch_duties.rs @@ -1,6 +1,7 @@ use block_producer::{DutiesReader, DutiesReaderError}; use std::collections::HashMap; use std::sync::RwLock; +use types::{Epoch, Slot}; /// The information required for a validator to propose and attest during some epoch. /// @@ -10,14 +11,14 @@ use std::sync::RwLock; #[derive(Debug, PartialEq, Clone, Copy, Default)] pub struct EpochDuties { pub validator_index: u64, - pub block_production_slot: Option, + pub block_production_slot: Option, // Future shard info } impl EpochDuties { /// Returns `true` if the supplied `slot` is a slot in which the validator should produce a /// block. - pub fn is_block_production_slot(&self, slot: u64) -> bool { + pub fn is_block_production_slot(&self, slot: Slot) -> bool { match self.block_production_slot { Some(s) if s == slot => true, _ => false, @@ -32,7 +33,7 @@ pub enum EpochDutiesMapError { /// Maps an `epoch` to some `EpochDuties` for a single validator. pub struct EpochDutiesMap { pub epoch_length: u64, - pub map: RwLock>, + pub map: RwLock>, } impl EpochDutiesMap { @@ -43,17 +44,17 @@ impl EpochDutiesMap { } } - pub fn get(&self, epoch: u64) -> Result, EpochDutiesMapError> { + pub fn get(&self, epoch: Epoch) -> Result, EpochDutiesMapError> { let map = self.map.read().map_err(|_| EpochDutiesMapError::Poisoned)?; match map.get(&epoch) { - Some(duties) => Ok(Some(duties.clone())), + Some(duties) => Ok(Some(*duties)), None => Ok(None), } } pub fn insert( &self, - epoch: u64, + epoch: Epoch, epoch_duties: EpochDuties, ) -> Result, EpochDutiesMapError> { let mut map = self @@ -65,10 +66,8 @@ impl EpochDutiesMap { } impl DutiesReader for EpochDutiesMap { - fn is_block_production_slot(&self, slot: u64) -> Result { - let epoch = slot - .checked_div(self.epoch_length) - .ok_or_else(|| DutiesReaderError::EpochLengthIsZero)?; + fn is_block_production_slot(&self, slot: Slot) -> Result { + let epoch = slot.epoch(self.epoch_length); let map = self.map.read().map_err(|_| DutiesReaderError::Poisoned)?; let duties = map diff --git a/validator_client/src/duties/grpc.rs b/validator_client/src/duties/grpc.rs index b4a2fac514..94f843b639 100644 --- a/validator_client/src/duties/grpc.rs +++ b/validator_client/src/duties/grpc.rs @@ -3,7 +3,7 @@ use super::EpochDuties; use protos::services::{ProposeBlockSlotRequest, PublicKey as IndexRequest}; use protos::services_grpc::ValidatorServiceClient; use ssz::ssz_encode; -use types::PublicKey; +use types::{Epoch, PublicKey, Slot}; impl BeaconNode for ValidatorServiceClient { /// Request the shuffling from the Beacon Node (BN). @@ -14,7 +14,7 @@ impl BeaconNode for ValidatorServiceClient { /// Note: presently only block production information is returned. fn request_shuffling( &self, - epoch: u64, + epoch: Epoch, public_key: &PublicKey, ) -> Result, BeaconNodeError> { // Lookup the validator index for the supplied public key. @@ -29,7 +29,7 @@ impl BeaconNode for ValidatorServiceClient { let mut req = ProposeBlockSlotRequest::new(); req.set_validator_index(validator_index); - req.set_epoch(epoch); + req.set_epoch(epoch.as_u64()); let reply = self .propose_block_slot(&req) @@ -41,6 +41,11 @@ impl BeaconNode for ValidatorServiceClient { None }; + let block_production_slot = match block_production_slot { + Some(slot) => Some(Slot::new(slot)), + None => None, + }; + Ok(Some(EpochDuties { validator_index, block_production_slot, diff --git a/validator_client/src/duties/mod.rs b/validator_client/src/duties/mod.rs index c9ebda249b..febab4755a 100644 --- a/validator_client/src/duties/mod.rs +++ b/validator_client/src/duties/mod.rs @@ -1,31 +1,31 @@ -mod duties_map; +mod epoch_duties; mod grpc; mod service; #[cfg(test)] mod test_node; mod traits; -pub use self::duties_map::EpochDutiesMap; -use self::duties_map::{EpochDuties, EpochDutiesMapError}; +pub use self::epoch_duties::EpochDutiesMap; +use self::epoch_duties::{EpochDuties, EpochDutiesMapError}; pub use self::service::DutiesManagerService; use self::traits::{BeaconNode, BeaconNodeError}; use bls::PublicKey; use slot_clock::SlotClock; use std::sync::Arc; -use types::ChainSpec; +use types::{ChainSpec, Epoch}; #[derive(Debug, PartialEq, Clone, Copy)] pub enum PollOutcome { /// The `EpochDuties` were not updated during this poll. - NoChange(u64), + NoChange(Epoch), /// The `EpochDuties` for the `epoch` were previously unknown, but obtained in the poll. - NewDuties(u64, EpochDuties), + NewDuties(Epoch, EpochDuties), /// New `EpochDuties` were obtained, different to those which were previously known. This is /// likely to be the result of chain re-organisation. - DutiesChanged(u64, EpochDuties), + DutiesChanged(Epoch, EpochDuties), /// The Beacon Node was unable to return the duties as the validator is unknown, or the /// shuffling for the epoch is unknown. - UnknownValidatorOrEpoch(u64), + UnknownValidatorOrEpoch(Epoch), } #[derive(Debug, PartialEq)] @@ -33,7 +33,6 @@ pub enum Error { SlotClockError, SlotUnknowable, EpochMapPoisoned, - EpochLengthIsZero, BeaconNodeError(BeaconNodeError), } @@ -62,9 +61,7 @@ impl DutiesManager { .map_err(|_| Error::SlotClockError)? .ok_or(Error::SlotUnknowable)?; - let epoch = slot - .checked_div(self.spec.epoch_length) - .ok_or(Error::EpochLengthIsZero)?; + let epoch = slot.epoch(self.spec.epoch_length); if let Some(duties) = self.beacon_node.request_shuffling(epoch, &self.pubkey)? { // If these duties were known, check to see if they're updates or identical. @@ -105,6 +102,7 @@ mod tests { use super::*; use bls::Keypair; use slot_clock::TestingSlotClock; + use types::Slot; // TODO: implement more thorough testing. // https://github.com/sigp/lighthouse/issues/160 @@ -130,25 +128,34 @@ mod tests { // Configure response from the BeaconNode. let duties = EpochDuties { validator_index: 0, - block_production_slot: Some(10), + block_production_slot: Some(Slot::new(10)), }; beacon_node.set_next_shuffling_result(Ok(Some(duties))); // Get the duties for the first time... - assert_eq!(manager.poll(), Ok(PollOutcome::NewDuties(0, duties))); + assert_eq!( + manager.poll(), + Ok(PollOutcome::NewDuties(Epoch::new(0), duties)) + ); // Get the same duties again... - assert_eq!(manager.poll(), Ok(PollOutcome::NoChange(0))); + assert_eq!(manager.poll(), Ok(PollOutcome::NoChange(Epoch::new(0)))); // Return new duties. let duties = EpochDuties { validator_index: 0, - block_production_slot: Some(11), + block_production_slot: Some(Slot::new(11)), }; beacon_node.set_next_shuffling_result(Ok(Some(duties))); - assert_eq!(manager.poll(), Ok(PollOutcome::DutiesChanged(0, duties))); + assert_eq!( + manager.poll(), + Ok(PollOutcome::DutiesChanged(Epoch::new(0), duties)) + ); // Return no duties. beacon_node.set_next_shuffling_result(Ok(None)); - assert_eq!(manager.poll(), Ok(PollOutcome::UnknownValidatorOrEpoch(0))); + assert_eq!( + manager.poll(), + Ok(PollOutcome::UnknownValidatorOrEpoch(Epoch::new(0))) + ); } } diff --git a/validator_client/src/duties/test_node.rs b/validator_client/src/duties/test_node.rs index 2b1d651728..331b78f3bc 100644 --- a/validator_client/src/duties/test_node.rs +++ b/validator_client/src/duties/test_node.rs @@ -2,13 +2,14 @@ use super::traits::{BeaconNode, BeaconNodeError}; use super::EpochDuties; use bls::PublicKey; use std::sync::RwLock; +use types::Epoch; type ShufflingResult = Result, BeaconNodeError>; /// A test-only struct used to simulate a Beacon Node. #[derive(Default)] pub struct TestBeaconNode { - pub request_shuffling_input: RwLock>, + pub request_shuffling_input: RwLock>, pub request_shuffling_result: RwLock>, } @@ -21,7 +22,7 @@ impl TestBeaconNode { impl BeaconNode for TestBeaconNode { /// Returns the value specified by the `set_next_shuffling_result`. - fn request_shuffling(&self, epoch: u64, public_key: &PublicKey) -> ShufflingResult { + fn request_shuffling(&self, epoch: Epoch, public_key: &PublicKey) -> ShufflingResult { *self.request_shuffling_input.write().unwrap() = Some((epoch, public_key.clone())); match *self.request_shuffling_result.read().unwrap() { Some(ref r) => r.clone(), diff --git a/validator_client/src/duties/traits.rs b/validator_client/src/duties/traits.rs index 38d61f967e..5bf7da1fdd 100644 --- a/validator_client/src/duties/traits.rs +++ b/validator_client/src/duties/traits.rs @@ -1,5 +1,6 @@ use super::EpochDuties; use bls::PublicKey; +use types::Epoch; #[derive(Debug, PartialEq, Clone)] pub enum BeaconNodeError { @@ -13,7 +14,7 @@ pub trait BeaconNode: Send + Sync { /// Returns Ok(None) if the public key is unknown, or the shuffling for that epoch is unknown. fn request_shuffling( &self, - epoch: u64, + epoch: Epoch, public_key: &PublicKey, ) -> Result, BeaconNodeError>; } diff --git a/validator_client/src/main.rs b/validator_client/src/main.rs index 3b516870a0..98be9159a8 100644 --- a/validator_client/src/main.rs +++ b/validator_client/src/main.rs @@ -1,7 +1,7 @@ use self::block_producer_service::{BeaconBlockGrpcClient, BlockProducerService}; use self::duties::{DutiesManager, DutiesManagerService, EpochDutiesMap}; use crate::config::ClientConfig; -use block_producer::{test_utils::TestSigner, BlockProducer}; +use block_producer::{test_utils::LocalSigner, BlockProducer}; use bls::Keypair; use clap::{App, Arg}; use grpcio::{ChannelBuilder, EnvBuilder}; @@ -88,9 +88,12 @@ fn main() { let spec = Arc::new(ChainSpec::foundation()); // Clock for determining the present slot. + // TODO: this shouldn't be a static time, instead it should be pulled from the beacon node. + // https://github.com/sigp/lighthouse/issues/160 + let genesis_time = 1_549_935_547; let slot_clock = { - info!(log, "Genesis time"; "unix_epoch_seconds" => spec.genesis_time); - let clock = SystemTimeSlotClock::new(spec.genesis_time, spec.slot_duration) + info!(log, "Genesis time"; "unix_epoch_seconds" => genesis_time); + let clock = SystemTimeSlotClock::new(genesis_time, spec.slot_duration) .expect("Unable to instantiate SystemTimeSlotClock."); Arc::new(clock) }; @@ -139,15 +142,14 @@ fn main() { // Spawn a new thread to perform block production for the validator. let producer_thread = { let spec = spec.clone(); - let pubkey = keypair.pk.clone(); - let signer = Arc::new(TestSigner::new(keypair.clone())); + let signer = Arc::new(LocalSigner::new(keypair.clone())); let duties_map = duties_map.clone(); let slot_clock = slot_clock.clone(); let log = log.clone(); let client = Arc::new(BeaconBlockGrpcClient::new(beacon_block_grpc_client.clone())); thread::spawn(move || { let block_producer = - BlockProducer::new(spec, pubkey, duties_map, slot_clock, client, signer); + BlockProducer::new(spec, duties_map, slot_clock, client, signer); let mut block_producer_service = BlockProducerService { block_producer, poll_interval_millis,