From 7dda85e87e90902a712285b0e4d91444ddbc5d2a Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 29 Apr 2019 12:06:02 +1000 Subject: [PATCH 01/22] hashing: Keccak -> SHA256 --- eth2/utils/hashing/Cargo.toml | 2 +- eth2/utils/hashing/src/lib.rs | 21 +++++++-------------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/eth2/utils/hashing/Cargo.toml b/eth2/utils/hashing/Cargo.toml index 1527bceba..78dd70e43 100644 --- a/eth2/utils/hashing/Cargo.toml +++ b/eth2/utils/hashing/Cargo.toml @@ -5,4 +5,4 @@ authors = ["Paul Hauner "] edition = "2018" [dependencies] -tiny-keccak = "1.4.2" +ring = "0.14.6" diff --git a/eth2/utils/hashing/src/lib.rs b/eth2/utils/hashing/src/lib.rs index 68e29fc9b..0f9117779 100644 --- a/eth2/utils/hashing/src/lib.rs +++ b/eth2/utils/hashing/src/lib.rs @@ -1,11 +1,7 @@ -use tiny_keccak::Keccak; +use ring::digest::{digest, SHA256}; pub fn hash(input: &[u8]) -> Vec { - let mut keccak = Keccak::new_keccak256(); - keccak.update(input); - let mut result = vec![0; 32]; - keccak.finalize(result.as_mut_slice()); - result + digest(&SHA256, input).as_ref().into() } /// Get merkle root of some hashed values - the input leaf nodes is expected to already be hashed @@ -41,19 +37,16 @@ pub fn merkle_root(values: &[Vec]) -> Option> { #[cfg(test)] mod tests { use super::*; - use std::convert::From; + use ring::test; #[test] fn test_hashing() { - let input: Vec = From::from("hello"); + let input: Vec = b"hello world".as_ref().into(); let output = hash(input.as_ref()); - let expected = &[ - 0x1c, 0x8a, 0xff, 0x95, 0x06, 0x85, 0xc2, 0xed, 0x4b, 0xc3, 0x17, 0x4f, 0x34, 0x72, - 0x28, 0x7b, 0x56, 0xd9, 0x51, 0x7b, 0x9c, 0x94, 0x81, 0x27, 0x31, 0x9a, 0x09, 0xa7, - 0xa3, 0x6d, 0xea, 0xc8, - ]; - assert_eq!(expected, output.as_slice()); + let expected_hex = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"; + let expected: Vec = test::from_hex(expected_hex).unwrap(); + assert_eq!(expected, output); } #[test] From 1ad0024045cf27628bcaa751900bd35cdd97e591 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 29 Apr 2019 14:54:52 +1000 Subject: [PATCH 02/22] spec v0.6.0: update types --- eth2/types/src/attestation.rs | 7 +- eth2/types/src/attestation_data.rs | 4 +- eth2/types/src/attester_slashing.rs | 8 +- eth2/types/src/beacon_block.rs | 1 + eth2/types/src/beacon_state.rs | 34 +++---- eth2/types/src/beacon_state/epoch_cache.rs | 27 ++++-- eth2/types/src/crosslink.rs | 3 +- eth2/types/src/deposit.rs | 4 +- eth2/types/src/deposit_data.rs | 63 +++++++++++-- eth2/types/src/deposit_input.rs | 92 ------------------- eth2/types/src/eth1_data.rs | 3 +- eth2/types/src/eth1_data_vote.rs | 36 -------- ..._attestation.rs => indexed_attestation.rs} | 67 +++++++------- eth2/types/src/lib.rs | 8 +- eth2/types/src/pending_attestation.rs | 14 ++- .../test_utils/testing_attestation_builder.rs | 4 +- .../testing_attestation_data_builder.rs | 3 +- .../testing_attester_slashing_builder.rs | 29 +++--- .../testing_beacon_state_builder.rs | 16 ++-- .../src/test_utils/testing_deposit_builder.rs | 27 ++---- .../testing_pending_attestation_builder.rs | 6 +- eth2/types/src/validator.rs | 8 +- 22 files changed, 192 insertions(+), 272 deletions(-) delete mode 100644 eth2/types/src/deposit_input.rs delete mode 100644 eth2/types/src/eth1_data_vote.rs rename eth2/types/src/{slashable_attestation.rs => indexed_attestation.rs} (56%) diff --git a/eth2/types/src/attestation.rs b/eth2/types/src/attestation.rs index d1511763d..0a4493ae6 100644 --- a/eth2/types/src/attestation.rs +++ b/eth2/types/src/attestation.rs @@ -9,7 +9,7 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash}; /// Details an attestation that can be slashable. /// -/// Spec v0.5.1 +/// Spec v0.6.0 #[derive( Debug, Clone, @@ -28,7 +28,7 @@ pub struct Attestation { pub data: AttestationData, pub custody_bitfield: Bitfield, #[signed_root(skip_hashing)] - pub aggregate_signature: AggregateSignature, + pub signature: AggregateSignature, } impl Attestation { @@ -49,8 +49,7 @@ impl Attestation { self.aggregation_bitfield .union_inplace(&other.aggregation_bitfield); self.custody_bitfield.union_inplace(&other.custody_bitfield); - self.aggregate_signature - .add_aggregate(&other.aggregate_signature); + self.signature.add_aggregate(&other.signature); } } diff --git a/eth2/types/src/attestation_data.rs b/eth2/types/src/attestation_data.rs index c963d6001..63b94fd70 100644 --- a/eth2/types/src/attestation_data.rs +++ b/eth2/types/src/attestation_data.rs @@ -9,7 +9,7 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash}; /// The data upon which an attestation is based. /// -/// Spec v0.5.1 +/// Spec v0.6.0 #[derive( Debug, Clone, @@ -37,7 +37,7 @@ pub struct AttestationData { // Crosslink Vote pub shard: u64, - pub previous_crosslink: Crosslink, + pub previous_crosslink_root: Crosslink, pub crosslink_data_root: Hash256, } diff --git a/eth2/types/src/attester_slashing.rs b/eth2/types/src/attester_slashing.rs index d4848b01c..1c9897cc1 100644 --- a/eth2/types/src/attester_slashing.rs +++ b/eth2/types/src/attester_slashing.rs @@ -1,4 +1,4 @@ -use crate::{test_utils::TestRandom, SlashableAttestation}; +use crate::{test_utils::TestRandom, IndexedAttestation}; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; @@ -7,7 +7,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; /// Two conflicting attestations. /// -/// Spec v0.5.1 +/// Spec v0.6.0 #[derive( Debug, PartialEq, @@ -21,8 +21,8 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; TestRandom, )] pub struct AttesterSlashing { - pub slashable_attestation_1: SlashableAttestation, - pub slashable_attestation_2: SlashableAttestation, + pub attestation_1: IndexedAttestation, + pub attestation_2: IndexedAttestation, } #[cfg(test)] diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index d198d16fc..1265a4864 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -47,6 +47,7 @@ impl BeaconBlock { eth1_data: Eth1Data { deposit_root: spec.zero_hash, block_hash: spec.zero_hash, + deposit_count: 0, }, proposer_slashings: vec![], attester_slashings: vec![], diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index e9b052f99..cb37c8448 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -69,17 +69,11 @@ pub struct BeaconState { // Validator registry pub validator_registry: Vec, - pub validator_balances: Vec, - pub validator_registry_update_epoch: Epoch, + pub balances: Vec, // Randomness and committees pub latest_randao_mixes: TreeHashVector, - pub previous_shuffling_start_shard: u64, - pub current_shuffling_start_shard: u64, - pub previous_shuffling_epoch: Epoch, - pub current_shuffling_epoch: Epoch, - pub previous_shuffling_seed: Hash256, - pub current_shuffling_seed: Hash256, + pub latest_start_shard: u64, // Finality pub previous_epoch_attestations: Vec, @@ -93,7 +87,8 @@ pub struct BeaconState { pub finalized_root: Hash256, // Recent state - pub latest_crosslinks: TreeHashVector, + pub current_crosslinks: TreeHashVector, + pub previous_crosslinks: TreeHashVector, pub latest_block_roots: TreeHashVector, latest_state_roots: TreeHashVector, latest_active_index_roots: TreeHashVector, @@ -103,7 +98,7 @@ pub struct BeaconState { // Ethereum 1.0 chain data pub latest_eth1_data: Eth1Data, - pub eth1_data_votes: Vec, + pub eth1_data_votes: Vec, pub deposit_index: u64, // Caching (not in the spec) @@ -143,6 +138,7 @@ impl BeaconState { pub fn genesis(genesis_time: u64, latest_eth1_data: Eth1Data, spec: &ChainSpec) -> BeaconState { let initial_crosslink = Crosslink { epoch: spec.genesis_epoch, + previous_crosslink_root: spec.zero_hash, crosslink_data_root: spec.zero_hash, }; @@ -154,18 +150,12 @@ impl BeaconState { // 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, + balances: vec![], // Set later in the function. // Randomness and committees latest_randao_mixes: vec![spec.zero_hash; spec.latest_randao_mixes_length as usize] .into(), - previous_shuffling_start_shard: spec.genesis_start_shard, - current_shuffling_start_shard: spec.genesis_start_shard, - previous_shuffling_epoch: spec.genesis_epoch, - current_shuffling_epoch: spec.genesis_epoch, - previous_shuffling_seed: spec.zero_hash, - current_shuffling_seed: spec.zero_hash, + latest_start_shard: 0, // FIXME(sproul) // Finality previous_epoch_attestations: vec![], @@ -179,7 +169,8 @@ impl BeaconState { finalized_root: spec.zero_hash, // Recent state - latest_crosslinks: vec![initial_crosslink; spec.shard_count as usize].into(), + current_crosslinks: vec![initial_crosslink.clone(); spec.shard_count as usize].into(), + previous_crosslinks: vec![initial_crosslink; spec.shard_count as usize].into(), latest_block_roots: vec![spec.zero_hash; spec.slots_per_historical_root].into(), latest_state_roots: vec![spec.zero_hash; spec.slots_per_historical_root].into(), latest_active_index_roots: vec![spec.zero_hash; spec.latest_active_index_roots_length] @@ -630,7 +621,7 @@ impl BeaconState { spec: &ChainSpec, ) -> Result { let balance = self - .validator_balances + .balances .get(validator_index) .ok_or_else(|| Error::UnknownValidator)?; Ok(std::cmp::min(*balance, spec.max_deposit_amount)) @@ -647,7 +638,8 @@ impl BeaconState { /// /// Spec v0.5.1 pub fn initiate_validator_exit(&mut self, validator_index: usize) { - self.validator_registry[validator_index].initiated_exit = true; + // FIXME(sproul) + // self.validator_registry[validator_index].initiated_exit = true; } /// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index 1a63e9eb9..9230c5af2 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -207,8 +207,9 @@ impl EpochCrosslinkCommitteesBuilder { ) -> Self { Self { epoch: state.previous_epoch(spec), - shuffling_start_shard: state.previous_shuffling_start_shard, - shuffling_seed: state.previous_shuffling_seed, + // FIXME(sproul) + shuffling_start_shard: 0, + shuffling_seed: spec.zero_hash, committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()), active_validator_indices, } @@ -222,8 +223,9 @@ impl EpochCrosslinkCommitteesBuilder { ) -> Self { Self { epoch: state.current_epoch(spec), - shuffling_start_shard: state.current_shuffling_start_shard, - shuffling_seed: state.current_shuffling_seed, + // FIXME(sproul) + shuffling_start_shard: 0, + shuffling_seed: spec.zero_hash, committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()), active_validator_indices, } @@ -243,8 +245,9 @@ impl EpochCrosslinkCommitteesBuilder { let next_epoch = state.next_epoch(spec); let committees_per_epoch = spec.get_epoch_committee_count(active_validator_indices.len()); - let epochs_since_last_registry_update = - current_epoch - state.validator_registry_update_epoch; + // FIXME(sproul) + // current_epoch - state.validator_registry_update_epoch; + let epochs_since_last_registry_update = 0u64; let (seed, shuffling_start_shard) = if registry_change { let next_seed = state @@ -252,7 +255,9 @@ impl EpochCrosslinkCommitteesBuilder { .map_err(|_| Error::UnableToGenerateSeed)?; ( next_seed, - (state.current_shuffling_start_shard + committees_per_epoch) % spec.shard_count, + 0, + // FIXME(sproul) + // (state.current_shuffling_start_shard + committees_per_epoch) % spec.shard_count, ) } else if (epochs_since_last_registry_update > 1) & epochs_since_last_registry_update.is_power_of_two() @@ -260,11 +265,13 @@ impl EpochCrosslinkCommitteesBuilder { let next_seed = state .generate_seed(next_epoch, spec) .map_err(|_| Error::UnableToGenerateSeed)?; - (next_seed, state.current_shuffling_start_shard) + ( + next_seed, 0, /* FIXME(sproul) state.current_shuffling_start_shard*/ + ) } else { ( - state.current_shuffling_seed, - state.current_shuffling_start_shard, + spec.zero_hash, // state.current_shuffling_seed, + 0 // state.current_shuffling_start_shard, ) }; diff --git a/eth2/types/src/crosslink.rs b/eth2/types/src/crosslink.rs index 448f5ea30..e6d353452 100644 --- a/eth2/types/src/crosslink.rs +++ b/eth2/types/src/crosslink.rs @@ -8,7 +8,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; /// Specifies the block hash for a shard at an epoch. /// -/// Spec v0.5.1 +/// Spec v0.6.0 #[derive( Debug, Clone, @@ -25,6 +25,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; )] pub struct Crosslink { pub epoch: Epoch, + pub previous_crosslink_root: Hash256, pub crosslink_data_root: Hash256, } diff --git a/eth2/types/src/deposit.rs b/eth2/types/src/deposit.rs index e8d2f5a4b..88dc5f427 100644 --- a/eth2/types/src/deposit.rs +++ b/eth2/types/src/deposit.rs @@ -8,7 +8,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; /// A deposit to potentially become a beacon chain validator. /// -/// Spec v0.5.1 +/// Spec v0.6.0 #[derive( Debug, PartialEq, @@ -24,7 +24,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; pub struct Deposit { pub proof: TreeHashVector, pub index: u64, - pub deposit_data: DepositData, + pub data: DepositData, } #[cfg(test)] diff --git a/eth2/types/src/deposit_data.rs b/eth2/types/src/deposit_data.rs index 38c44d1a7..930836c70 100644 --- a/eth2/types/src/deposit_data.rs +++ b/eth2/types/src/deposit_data.rs @@ -1,14 +1,16 @@ -use super::DepositInput; use crate::test_utils::TestRandom; +use crate::*; +use bls::{PublicKey, Signature}; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; -use tree_hash_derive::{CachedTreeHash, TreeHash}; +use tree_hash::{SignedRoot, TreeHash}; +use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash}; -/// Data generated by the deposit contract. +/// The data supplied by the user to the deposit contract. /// -/// Spec v0.5.1 +/// Spec v0.6.0 #[derive( Debug, PartialEq, @@ -17,14 +19,45 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; Deserialize, Encode, Decode, + SignedRoot, TreeHash, CachedTreeHash, TestRandom, )] pub struct DepositData { + pub pubkey: PublicKey, + pub withdrawal_credentials: Hash256, pub amount: u64, - pub timestamp: u64, - pub deposit_input: DepositInput, + #[signed_root(skip_hashing)] + pub signature: Signature, +} + +impl DepositData { + /// Generate the signature for a given DepositData details. + /// + /// Spec v0.5.1 + pub fn create_signature( + &self, + secret_key: &SecretKey, + epoch: Epoch, + fork: &Fork, + spec: &ChainSpec, + ) -> Signature { + let msg = self.signed_root(); + let domain = spec.get_domain(epoch, Domain::Deposit, fork); + + Signature::new(msg.as_slice(), domain, secret_key) + } + + /// Verify that proof-of-possession is valid. + /// + /// Spec v0.5.1 + pub fn validate_signature(&self, epoch: Epoch, fork: &Fork, spec: &ChainSpec) -> bool { + let msg = self.signed_root(); + let domain = spec.get_domain(epoch, Domain::Deposit, fork); + + self.signature.verify(&msg, domain, &self.pubkey) + } } #[cfg(test)] @@ -33,4 +66,22 @@ mod tests { ssz_tests!(DepositData); cached_tree_hash_tests!(DepositData); + + #[test] + fn can_create_and_validate() { + let spec = ChainSpec::foundation(); + let fork = Fork::genesis(&spec); + let keypair = Keypair::random(); + let epoch = Epoch::new(0); + + let mut deposit_input = DepositData { + pubkey: keypair.pk.clone(), + withdrawal_credentials: Hash256::zero(), + signature: Signature::empty_signature(), + }; + + deposit_input.signature = deposit_input.create_signature(&keypair.sk, epoch, &fork, &spec); + + assert!(deposit_input.validate_signature(epoch, &fork, &spec)); + } } diff --git a/eth2/types/src/deposit_input.rs b/eth2/types/src/deposit_input.rs deleted file mode 100644 index af1049a20..000000000 --- a/eth2/types/src/deposit_input.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::test_utils::TestRandom; -use crate::*; -use bls::{PublicKey, Signature}; -use rand::RngCore; -use serde_derive::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use test_random_derive::TestRandom; -use tree_hash::{SignedRoot, TreeHash}; -use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash}; - -/// The data supplied by the user to the deposit contract. -/// -/// Spec v0.5.1 -#[derive( - Debug, - PartialEq, - Clone, - Serialize, - Deserialize, - Encode, - Decode, - SignedRoot, - TreeHash, - CachedTreeHash, - TestRandom, -)] -pub struct DepositInput { - pub pubkey: PublicKey, - pub withdrawal_credentials: Hash256, - #[signed_root(skip_hashing)] - pub proof_of_possession: Signature, -} - -impl DepositInput { - /// Generate the 'proof_of_posession' signature for a given DepositInput details. - /// - /// Spec v0.5.1 - pub fn create_proof_of_possession( - &self, - secret_key: &SecretKey, - epoch: Epoch, - fork: &Fork, - spec: &ChainSpec, - ) -> Signature { - let msg = self.signed_root(); - let domain = spec.get_domain(epoch, Domain::Deposit, fork); - - Signature::new(msg.as_slice(), domain, secret_key) - } - - /// Verify that proof-of-possession is valid. - /// - /// Spec v0.5.1 - pub fn validate_proof_of_possession( - &self, - epoch: Epoch, - fork: &Fork, - spec: &ChainSpec, - ) -> bool { - let msg = self.signed_root(); - let domain = spec.get_domain(epoch, Domain::Deposit, fork); - - self.proof_of_possession.verify(&msg, domain, &self.pubkey) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - ssz_tests!(DepositInput); - cached_tree_hash_tests!(DepositInput); - - #[test] - fn can_create_and_validate() { - let spec = ChainSpec::foundation(); - let fork = Fork::genesis(&spec); - let keypair = Keypair::random(); - let epoch = Epoch::new(0); - - let mut deposit_input = DepositInput { - pubkey: keypair.pk.clone(), - withdrawal_credentials: Hash256::zero(), - proof_of_possession: Signature::empty_signature(), - }; - - deposit_input.proof_of_possession = - deposit_input.create_proof_of_possession(&keypair.sk, epoch, &fork, &spec); - - assert!(deposit_input.validate_proof_of_possession(epoch, &fork, &spec)); - } -} diff --git a/eth2/types/src/eth1_data.rs b/eth2/types/src/eth1_data.rs index 3c0c3af02..6fc07e882 100644 --- a/eth2/types/src/eth1_data.rs +++ b/eth2/types/src/eth1_data.rs @@ -8,7 +8,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; /// Contains data obtained from the Eth1 chain. /// -/// Spec v0.5.1 +/// Spec v0.6.0 #[derive( Debug, PartialEq, @@ -24,6 +24,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; )] pub struct Eth1Data { pub deposit_root: Hash256, + pub deposit_count: u64, pub block_hash: Hash256, } diff --git a/eth2/types/src/eth1_data_vote.rs b/eth2/types/src/eth1_data_vote.rs deleted file mode 100644 index 00818ebf4..000000000 --- a/eth2/types/src/eth1_data_vote.rs +++ /dev/null @@ -1,36 +0,0 @@ -use super::Eth1Data; -use crate::test_utils::TestRandom; -use rand::RngCore; -use serde_derive::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use test_random_derive::TestRandom; -use tree_hash_derive::{CachedTreeHash, TreeHash}; - -/// A summation of votes for some `Eth1Data`. -/// -/// Spec v0.5.1 -#[derive( - Debug, - PartialEq, - Clone, - Default, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - CachedTreeHash, - TestRandom, -)] -pub struct Eth1DataVote { - pub eth1_data: Eth1Data, - pub vote_count: u64, -} - -#[cfg(test)] -mod tests { - use super::*; - - ssz_tests!(Eth1DataVote); - cached_tree_hash_tests!(Eth1DataVote); -} diff --git a/eth2/types/src/slashable_attestation.rs b/eth2/types/src/indexed_attestation.rs similarity index 56% rename from eth2/types/src/slashable_attestation.rs rename to eth2/types/src/indexed_attestation.rs index fb838e0c4..5b56f34df 100644 --- a/eth2/types/src/slashable_attestation.rs +++ b/eth2/types/src/indexed_attestation.rs @@ -10,7 +10,7 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash}; /// /// To be included in an `AttesterSlashing`. /// -/// Spec v0.5.1 +/// Spec v0.6.0 #[derive( Debug, PartialEq, @@ -24,27 +24,28 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash}; TestRandom, SignedRoot, )] -pub struct SlashableAttestation { +pub struct IndexedAttestation { /// Lists validator registry indices, not committee indices. - pub validator_indices: Vec, + pub custody_bit_0_indices: Vec, + pub custody_bit_1_indices: Vec, pub data: AttestationData, pub custody_bitfield: Bitfield, #[signed_root(skip_hashing)] - pub aggregate_signature: AggregateSignature, + pub signature: AggregateSignature, } -impl SlashableAttestation { +impl IndexedAttestation { /// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target. /// /// Spec v0.5.1 - pub fn is_double_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool { + pub fn is_double_vote(&self, other: &IndexedAttestation, spec: &ChainSpec) -> bool { self.data.slot.epoch(spec.slots_per_epoch) == other.data.slot.epoch(spec.slots_per_epoch) } /// Check if ``attestation_data_1`` surrounds ``attestation_data_2``. /// /// Spec v0.5.1 - pub fn is_surround_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool { + pub fn is_surround_vote(&self, other: &IndexedAttestation, spec: &ChainSpec) -> bool { let source_epoch_1 = self.data.source_epoch; let source_epoch_2 = other.data.source_epoch; let target_epoch_1 = self.data.slot.epoch(spec.slots_per_epoch); @@ -64,11 +65,11 @@ mod tests { #[test] pub fn test_is_double_vote_true() { let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_attestation(1, 1, &spec); - let slashable_vote_second = create_slashable_attestation(1, 1, &spec); + let indexed_vote_first = create_indexed_attestation(1, 1, &spec); + let indexed_vote_second = create_indexed_attestation(1, 1, &spec); assert_eq!( - slashable_vote_first.is_double_vote(&slashable_vote_second, &spec), + indexed_vote_first.is_double_vote(&indexed_vote_second, &spec), true ) } @@ -76,11 +77,11 @@ mod tests { #[test] pub fn test_is_double_vote_false() { let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_attestation(1, 1, &spec); - let slashable_vote_second = create_slashable_attestation(2, 1, &spec); + let indexed_vote_first = create_indexed_attestation(1, 1, &spec); + let indexed_vote_second = create_indexed_attestation(2, 1, &spec); assert_eq!( - slashable_vote_first.is_double_vote(&slashable_vote_second, &spec), + indexed_vote_first.is_double_vote(&indexed_vote_second, &spec), false ); } @@ -88,11 +89,11 @@ mod tests { #[test] pub fn test_is_surround_vote_true() { let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_attestation(2, 1, &spec); - let slashable_vote_second = create_slashable_attestation(1, 2, &spec); + let indexed_vote_first = create_indexed_attestation(2, 1, &spec); + let indexed_vote_second = create_indexed_attestation(1, 2, &spec); assert_eq!( - slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), + indexed_vote_first.is_surround_vote(&indexed_vote_second, &spec), true ); } @@ -100,11 +101,11 @@ mod tests { #[test] pub fn test_is_surround_vote_true_realistic() { let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_attestation(4, 1, &spec); - let slashable_vote_second = create_slashable_attestation(3, 2, &spec); + let indexed_vote_first = create_indexed_attestation(4, 1, &spec); + let indexed_vote_second = create_indexed_attestation(3, 2, &spec); assert_eq!( - slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), + indexed_vote_first.is_surround_vote(&indexed_vote_second, &spec), true ); } @@ -112,11 +113,11 @@ mod tests { #[test] pub fn test_is_surround_vote_false_source_epoch_fails() { let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_attestation(2, 2, &spec); - let slashable_vote_second = create_slashable_attestation(1, 1, &spec); + let indexed_vote_first = create_indexed_attestation(2, 2, &spec); + let indexed_vote_second = create_indexed_attestation(1, 1, &spec); assert_eq!( - slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), + indexed_vote_first.is_surround_vote(&indexed_vote_second, &spec), false ); } @@ -124,28 +125,28 @@ mod tests { #[test] pub fn test_is_surround_vote_false_target_epoch_fails() { let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_attestation(1, 1, &spec); - let slashable_vote_second = create_slashable_attestation(2, 2, &spec); + let indexed_vote_first = create_indexed_attestation(1, 1, &spec); + let indexed_vote_second = create_indexed_attestation(2, 2, &spec); assert_eq!( - slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), + indexed_vote_first.is_surround_vote(&indexed_vote_second, &spec), false ); } - ssz_tests!(SlashableAttestation); - cached_tree_hash_tests!(SlashableAttestation); + ssz_tests!(IndexedAttestation); + cached_tree_hash_tests!(IndexedAttestation); - fn create_slashable_attestation( + fn create_indexed_attestation( slot_factor: u64, source_epoch: u64, spec: &ChainSpec, - ) -> SlashableAttestation { + ) -> IndexedAttestation { let mut rng = XorShiftRng::from_seed([42; 16]); - let mut slashable_vote = SlashableAttestation::random_for_test(&mut rng); + let mut indexed_vote = IndexedAttestation::random_for_test(&mut rng); - slashable_vote.data.slot = Slot::new(slot_factor * spec.slots_per_epoch); - slashable_vote.data.source_epoch = Epoch::new(source_epoch); - slashable_vote + indexed_vote.data.slot = Slot::new(slot_factor * spec.slots_per_epoch); + indexed_vote.data.source_epoch = Epoch::new(source_epoch); + indexed_vote } } diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 070ed6745..82e5b69ce 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -17,15 +17,13 @@ pub mod crosslink; pub mod crosslink_committee; pub mod deposit; pub mod deposit_data; -pub mod deposit_input; pub mod eth1_data; -pub mod eth1_data_vote; pub mod fork; pub mod free_attestation; pub mod historical_batch; +pub mod indexed_attestation; pub mod pending_attestation; pub mod proposer_slashing; -pub mod slashable_attestation; pub mod transfer; pub mod tree_hash_vector; pub mod voluntary_exit; @@ -53,16 +51,14 @@ pub use crate::crosslink::Crosslink; pub use crate::crosslink_committee::CrosslinkCommittee; pub use crate::deposit::Deposit; pub use crate::deposit_data::DepositData; -pub use crate::deposit_input::DepositInput; pub use crate::eth1_data::Eth1Data; -pub use crate::eth1_data_vote::Eth1DataVote; pub use crate::fork::Fork; pub use crate::free_attestation::FreeAttestation; pub use crate::historical_batch::HistoricalBatch; +pub use crate::indexed_attestation::IndexedAttestation; pub use crate::pending_attestation::PendingAttestation; pub use crate::proposer_slashing::ProposerSlashing; pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch}; -pub use crate::slashable_attestation::SlashableAttestation; pub use crate::slot_epoch::{Epoch, Slot}; pub use crate::slot_height::SlotHeight; pub use crate::transfer::Transfer; diff --git a/eth2/types/src/pending_attestation.rs b/eth2/types/src/pending_attestation.rs index b71351f9a..e2ad23716 100644 --- a/eth2/types/src/pending_attestation.rs +++ b/eth2/types/src/pending_attestation.rs @@ -8,7 +8,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; /// An attestation that has been included in the state but not yet fully processed. /// -/// Spec v0.5.1 +/// Spec v0.6.0 #[derive( Debug, Clone, @@ -24,18 +24,22 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; pub struct PendingAttestation { pub aggregation_bitfield: Bitfield, pub data: AttestationData, - pub custody_bitfield: Bitfield, pub inclusion_slot: Slot, + pub proposer_index: u64, } impl PendingAttestation { - /// Create a `PendingAttestation` from an `Attestation`, at the given `inclusion_slot`. - pub fn from_attestation(attestation: &Attestation, inclusion_slot: Slot) -> Self { + /// Create a `PendingAttestation` from an `Attestation`. + pub fn from_attestation( + attestation: &Attestation, + inclusion_slot: Slot, + proposer_index: u64, + ) -> Self { PendingAttestation { data: attestation.data.clone(), aggregation_bitfield: attestation.aggregation_bitfield.clone(), - custody_bitfield: attestation.custody_bitfield.clone(), inclusion_slot, + proposer_index, } } } diff --git a/eth2/types/src/test_utils/testing_attestation_builder.rs b/eth2/types/src/test_utils/testing_attestation_builder.rs index 162facc8e..b41bf6f8d 100644 --- a/eth2/types/src/test_utils/testing_attestation_builder.rs +++ b/eth2/types/src/test_utils/testing_attestation_builder.rs @@ -33,7 +33,7 @@ impl TestingAttestationBuilder { aggregation_bitfield, data: data_builder.build(), custody_bitfield, - aggregate_signature: AggregateSignature::new(), + signature: AggregateSignature::new(), }; Self { @@ -83,7 +83,7 @@ impl TestingAttestationBuilder { ); let signature = Signature::new(&message, domain, secret_keys[key_index]); - self.attestation.aggregate_signature.add(&signature) + self.attestation.signature.add(&signature) } } diff --git a/eth2/types/src/test_utils/testing_attestation_data_builder.rs b/eth2/types/src/test_utils/testing_attestation_data_builder.rs index a270e3859..146485fc0 100644 --- a/eth2/types/src/test_utils/testing_attestation_data_builder.rs +++ b/eth2/types/src/test_utils/testing_attestation_data_builder.rs @@ -49,8 +49,9 @@ impl TestingAttestationDataBuilder { // Crosslink vote shard, - previous_crosslink: Crosslink { + previous_crosslink_root: Crosslink { epoch: slot.epoch(spec.slots_per_epoch), + previous_crosslink_root: spec.zero_hash, crosslink_data_root: spec.zero_hash, }, crosslink_data_root: spec.zero_hash, diff --git a/eth2/types/src/test_utils/testing_attester_slashing_builder.rs b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs index dc01f7fb0..bd5aea2a4 100644 --- a/eth2/types/src/test_utils/testing_attester_slashing_builder.rs +++ b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs @@ -34,8 +34,9 @@ impl TestingAttesterSlashingBuilder { source_root: hash_1, target_root: hash_1, shard, - previous_crosslink: Crosslink { + previous_crosslink_root: Crosslink { epoch, + previous_crosslink_root: hash_1, crosslink_data_root: hash_1, }, crosslink_data_root: hash_1, @@ -46,21 +47,23 @@ impl TestingAttesterSlashingBuilder { ..data_1.clone() }; - let mut slashable_attestation_1 = SlashableAttestation { - validator_indices: validator_indices.to_vec(), + let mut attestation_1 = IndexedAttestation { + custody_bit_0_indices: validator_indices.to_vec(), + custody_bit_1_indices: vec![], data: data_1, custody_bitfield: Bitfield::new(), - aggregate_signature: AggregateSignature::new(), + signature: AggregateSignature::new(), }; - let mut slashable_attestation_2 = SlashableAttestation { - validator_indices: validator_indices.to_vec(), + let mut attestation_2 = IndexedAttestation { + custody_bit_0_indices: validator_indices.to_vec(), + custody_bit_1_indices: vec![], data: data_2, custody_bitfield: Bitfield::new(), - aggregate_signature: AggregateSignature::new(), + signature: AggregateSignature::new(), }; - let add_signatures = |attestation: &mut SlashableAttestation| { + let add_signatures = |attestation: &mut IndexedAttestation| { // All validators sign with a `false` custody bit. let attestation_data_and_custody_bit = AttestationDataAndCustodyBit { data: attestation.data.clone(), @@ -71,16 +74,16 @@ impl TestingAttesterSlashingBuilder { for (i, validator_index) in validator_indices.iter().enumerate() { attestation.custody_bitfield.set(i, false); let signature = signer(*validator_index, &message[..], epoch, Domain::Attestation); - attestation.aggregate_signature.add(&signature); + attestation.signature.add(&signature); } }; - add_signatures(&mut slashable_attestation_1); - add_signatures(&mut slashable_attestation_2); + add_signatures(&mut attestation_1); + add_signatures(&mut attestation_2); AttesterSlashing { - slashable_attestation_1, - slashable_attestation_2, + attestation_1, + attestation_2, } } } diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 9bdd9e149..7987a256f 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -95,6 +95,7 @@ impl TestingBeaconStateBuilder { /// Creates the builder from an existing set of keypairs. pub fn from_keypairs(keypairs: Vec, spec: &ChainSpec) -> Self { let validator_count = keypairs.len(); + let starting_balance = 32_000_000_000; debug!( "Building {} Validator objects from keypairs...", @@ -112,11 +113,12 @@ impl TestingBeaconStateBuilder { pubkey: keypair.pk.clone(), withdrawal_credentials, // All validators start active. + activation_eligibility_epoch: spec.genesis_epoch, activation_epoch: spec.genesis_epoch, exit_epoch: spec.far_future_epoch, withdrawable_epoch: spec.far_future_epoch, - initiated_exit: false, slashed: false, + effective_balance: starting_balance, } }) .collect(); @@ -137,16 +139,17 @@ impl TestingBeaconStateBuilder { genesis_time, Eth1Data { deposit_root: Hash256::zero(), + deposit_count: 0, block_hash: Hash256::zero(), }, spec, ); - let balances = vec![32_000_000_000; validator_count]; + let balances = vec![starting_balance; validator_count]; debug!("Importing {} existing validators...", validator_count); state.validator_registry = validators; - state.validator_balances = balances; + state.balances = balances; debug!("BeaconState initialized."); @@ -192,18 +195,13 @@ impl TestingBeaconStateBuilder { state.slot = slot; - state.previous_shuffling_epoch = epoch - 1; - state.current_shuffling_epoch = epoch; - - state.previous_shuffling_seed = Hash256::from_low_u64_le(0); - state.current_shuffling_seed = Hash256::from_low_u64_le(1); + // FIXME(sproul): update latest_start_shard? state.previous_justified_epoch = epoch - 3; state.current_justified_epoch = epoch - 2; state.justification_bitfield = u64::max_value(); state.finalized_epoch = epoch - 3; - state.validator_registry_update_epoch = epoch - 3; } /// Creates a full set of attestations for the `BeaconState`. Each attestation has full diff --git a/eth2/types/src/test_utils/testing_deposit_builder.rs b/eth2/types/src/test_utils/testing_deposit_builder.rs index 080ed5cfb..dcb6a56ef 100644 --- a/eth2/types/src/test_utils/testing_deposit_builder.rs +++ b/eth2/types/src/test_utils/testing_deposit_builder.rs @@ -14,14 +14,11 @@ impl TestingDepositBuilder { let deposit = Deposit { proof: vec![].into(), index: 0, - deposit_data: DepositData { + data: DepositData { + pubkey, + withdrawal_credentials: Hash256::zero(), amount, - timestamp: 1, - deposit_input: DepositInput { - pubkey, - withdrawal_credentials: Hash256::zero(), - proof_of_possession: Signature::empty_signature(), - }, + signature: Signature::empty_signature(), }, }; @@ -43,17 +40,13 @@ impl TestingDepositBuilder { &get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..], ); - self.deposit.deposit_data.deposit_input.pubkey = keypair.pk.clone(); - self.deposit - .deposit_data - .deposit_input - .withdrawal_credentials = withdrawal_credentials; + self.deposit.data.pubkey = keypair.pk.clone(); + self.deposit.data.withdrawal_credentials = withdrawal_credentials; - self.deposit.deposit_data.deposit_input.proof_of_possession = self - .deposit - .deposit_data - .deposit_input - .create_proof_of_possession(&keypair.sk, epoch, fork, spec); + self.deposit.data.signature = + self.deposit + .data + .create_signature(&keypair.sk, epoch, fork, spec); } /// Builds the deposit, consuming the builder. diff --git a/eth2/types/src/test_utils/testing_pending_attestation_builder.rs b/eth2/types/src/test_utils/testing_pending_attestation_builder.rs index 655b3d1e8..831cda0f8 100644 --- a/eth2/types/src/test_utils/testing_pending_attestation_builder.rs +++ b/eth2/types/src/test_utils/testing_pending_attestation_builder.rs @@ -22,8 +22,9 @@ impl TestingPendingAttestationBuilder { let pending_attestation = PendingAttestation { aggregation_bitfield: Bitfield::new(), data: data_builder.build(), - custody_bitfield: Bitfield::new(), inclusion_slot: slot + spec.min_attestation_inclusion_delay, + // FIXME(sproul) + proposer_index: 0, }; Self { @@ -37,15 +38,12 @@ impl TestingPendingAttestationBuilder { /// `signers` is true. pub fn add_committee_participation(&mut self, signers: Vec) { let mut aggregation_bitfield = Bitfield::new(); - let mut custody_bitfield = Bitfield::new(); for (i, signed) in signers.iter().enumerate() { aggregation_bitfield.set(i, *signed); - custody_bitfield.set(i, false); // Fixed to `false` for phase 0. } self.pending_attestation.aggregation_bitfield = aggregation_bitfield; - self.pending_attestation.custody_bitfield = custody_bitfield; } /// Returns the `PendingAttestation`, consuming the builder. diff --git a/eth2/types/src/validator.rs b/eth2/types/src/validator.rs index a20eb6426..5b369427c 100644 --- a/eth2/types/src/validator.rs +++ b/eth2/types/src/validator.rs @@ -7,7 +7,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; /// Information about a `BeaconChain` validator. /// -/// Spec v0.5.1 +/// Spec v0.6.0 #[derive( Debug, Clone, @@ -23,11 +23,12 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; pub struct Validator { pub pubkey: PublicKey, pub withdrawal_credentials: Hash256, + pub activation_eligibility_epoch: Epoch, pub activation_epoch: Epoch, pub exit_epoch: Epoch, pub withdrawable_epoch: Epoch, - pub initiated_exit: bool, pub slashed: bool, + pub effective_balance: u64, } impl Validator { @@ -53,11 +54,12 @@ impl Default for Validator { Self { pubkey: PublicKey::default(), withdrawal_credentials: Hash256::default(), + activation_eligibility_epoch: Epoch::from(std::u64::MAX), activation_epoch: Epoch::from(std::u64::MAX), exit_epoch: Epoch::from(std::u64::MAX), withdrawable_epoch: Epoch::from(std::u64::MAX), - initiated_exit: false, slashed: false, + effective_balance: std::u64::MAX, } } } From caff553af9e870a9a4f36b87add8aa822eb6d8e2 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 6 May 2019 12:27:59 +1000 Subject: [PATCH 03/22] spec: update reward processing to v0.6.1 + bugfix Two bugs fixed by this commit: * Reward proposers rather than attesters in `get_proposer_deltas` * Prevent double-counting of validator balances towards the total when computing validator statuses --- .../src/per_epoch_processing/apply_rewards.rs | 239 +++++++----------- .../validator_statuses.rs | 62 +++-- 2 files changed, 136 insertions(+), 165 deletions(-) diff --git a/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs b/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs index 9af1ee8c3..2c45dd822 100644 --- a/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs +++ b/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs @@ -1,3 +1,7 @@ +use super::common::{ + get_attesting_balance, get_matching_head_attestations, get_matching_target_attestations, + get_total_active_balance, get_unslashed_attesting_indices, +}; use super::validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses}; use super::{Error, WinningRootHashSet}; use integer_sqrt::IntegerSquareRoot; @@ -32,57 +36,52 @@ impl std::ops::AddAssign for Delta { /// Apply attester and proposer rewards. /// -/// Spec v0.5.1 -pub fn apply_rewards( +/// Spec v0.6.1 +pub fn process_rewards_and_penalties( state: &mut BeaconState, validator_statuses: &mut ValidatorStatuses, winning_root_for_shards: &WinningRootHashSet, spec: &ChainSpec, ) -> Result<(), Error> { + if state.current_epoch(spec) == spec.genesis_epoch { + return Ok(()); + } + // Guard against an out-of-bounds during the validator balance update. - if validator_statuses.statuses.len() != state.validator_balances.len() { - return Err(Error::ValidatorStatusesInconsistent); - } - // Guard against an out-of-bounds during the attester inclusion balance update. - if validator_statuses.statuses.len() != state.validator_registry.len() { + if validator_statuses.statuses.len() != state.balances.len() + || validator_statuses.statuses.len() != state.validator_registry.len() + { return Err(Error::ValidatorStatusesInconsistent); } - let mut deltas = vec![Delta::default(); state.validator_balances.len()]; + let mut deltas = vec![Delta::default(); state.balances.len()]; - get_justification_and_finalization_deltas(&mut deltas, state, &validator_statuses, spec)?; + get_attestation_deltas(&mut deltas, state, &validator_statuses, spec)?; get_crosslink_deltas(&mut deltas, state, &validator_statuses, spec)?; - // Apply the proposer deltas if we are finalizing normally. - // - // This is executed slightly differently to the spec because of the way our functions are - // structured. It should be functionally equivalent. - if epochs_since_finality(state, spec) <= 4 { - get_proposer_deltas( - &mut deltas, - state, - validator_statuses, - winning_root_for_shards, - spec, - )?; - } + get_proposer_deltas( + &mut deltas, + state, + validator_statuses, + winning_root_for_shards, + spec, + )?; // Apply the deltas, over-flowing but not under-flowing (saturating at 0 instead). for (i, delta) in deltas.iter().enumerate() { - state.validator_balances[i] += delta.rewards; - state.validator_balances[i] = state.validator_balances[i].saturating_sub(delta.penalties); + state.balances[i] += delta.rewards; + state.balances[i] = state.balances[i].saturating_sub(delta.penalties); } Ok(()) } -/// Applies the attestation inclusion reward to each proposer for every validator who included an -/// attestation in the previous epoch. +/// For each attesting validator, reward the proposer who was first to include their attestation. /// -/// Spec v0.5.1 +/// Spec v0.6.1 fn get_proposer_deltas( deltas: &mut Vec, - state: &mut BeaconState, + state: &BeaconState, validator_statuses: &mut ValidatorStatuses, winning_root_for_shards: &WinningRootHashSet, spec: &ChainSpec, @@ -90,9 +89,7 @@ fn get_proposer_deltas( // Update statuses with the information from winning roots. validator_statuses.process_winning_roots(state, winning_root_for_shards, spec)?; - for (index, validator) in validator_statuses.statuses.iter().enumerate() { - let mut delta = Delta::default(); - + for validator in &validator_statuses.statuses { if validator.is_previous_epoch_attester { let inclusion = validator .inclusion_info @@ -101,7 +98,7 @@ fn get_proposer_deltas( let base_reward = get_base_reward( state, inclusion.proposer_index, - validator_statuses.total_balances.previous_epoch, + validator_statuses.total_balances.current_epoch, spec, )?; @@ -109,10 +106,8 @@ fn get_proposer_deltas( return Err(Error::ValidatorStatusesInconsistent); } - delta.reward(base_reward / spec.attestation_inclusion_reward_quotient); + deltas[inclusion.proposer_index].reward(base_reward / spec.proposer_reward_quotient); } - - deltas[index] += delta; } Ok(()) @@ -120,40 +115,30 @@ fn get_proposer_deltas( /// Apply rewards for participation in attestations during the previous epoch. /// -/// Spec v0.5.1 -fn get_justification_and_finalization_deltas( +/// Spec v0.6.1 +fn get_attestation_deltas( deltas: &mut Vec, state: &BeaconState, validator_statuses: &ValidatorStatuses, spec: &ChainSpec, ) -> Result<(), Error> { - let epochs_since_finality = epochs_since_finality(state, spec); + let finality_delay = (state.previous_epoch(spec) - state.finalized_epoch).as_u64(); for (index, validator) in validator_statuses.statuses.iter().enumerate() { let base_reward = get_base_reward( state, index, - validator_statuses.total_balances.previous_epoch, - spec, - )?; - let inactivity_penalty = get_inactivity_penalty( - state, - index, - epochs_since_finality.as_u64(), - validator_statuses.total_balances.previous_epoch, + validator_statuses.total_balances.current_epoch, spec, )?; - let delta = if epochs_since_finality <= 4 { - compute_normal_justification_and_finalization_delta( - &validator, - &validator_statuses.total_balances, - base_reward, - spec, - ) - } else { - compute_inactivity_leak_delta(&validator, base_reward, inactivity_penalty, spec) - }; + let delta = get_attestation_delta( + &validator, + &validator_statuses.total_balances, + base_reward, + finality_delay, + spec, + ); deltas[index] += delta; } @@ -161,24 +146,36 @@ fn get_justification_and_finalization_deltas( Ok(()) } -/// Determine the delta for a single validator, if the chain is finalizing normally. +/// Determine the delta for a single validator, sans proposer rewards. /// -/// Spec v0.5.1 -fn compute_normal_justification_and_finalization_delta( +/// Spec v0.6.1 +fn get_attestation_delta( validator: &ValidatorStatus, total_balances: &TotalBalances, base_reward: u64, + finality_delay: u64, spec: &ChainSpec, ) -> Delta { let mut delta = Delta::default(); - let boundary_attesting_balance = total_balances.previous_epoch_boundary_attesters; - let total_balance = total_balances.previous_epoch; + // Is this validator eligible to be rewarded or penalized? + // Spec: validator index in `eligible_validator_indices` + let is_eligible = validator.is_active_in_previous_epoch + || (validator.is_slashed && !validator.is_withdrawable_in_current_epoch); + + if !is_eligible { + return delta; + } + + let total_balance = total_balances.current_epoch; let total_attesting_balance = total_balances.previous_epoch_attesters; - let matching_head_balance = total_balances.previous_epoch_boundary_attesters; + let matching_target_balance = total_balances.previous_epoch_target_attesters; + let matching_head_balance = total_balances.previous_epoch_head_attesters; // Expected FFG source. - if validator.is_previous_epoch_attester { + // Spec: + // - validator index in `get_unslashed_attesting_indices(state, matching_source_attestations)` + if validator.is_previous_epoch_attester && !validator.is_slashed { delta.reward(base_reward * total_attesting_balance / total_balance); // Inclusion speed bonus let inclusion = validator @@ -187,25 +184,43 @@ fn compute_normal_justification_and_finalization_delta( delta.reward( base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(), ); - } else if validator.is_active_in_previous_epoch { + } else { delta.penalize(base_reward); } // Expected FFG target. - if validator.is_previous_epoch_boundary_attester { - delta.reward(base_reward / boundary_attesting_balance / total_balance); - } else if validator.is_active_in_previous_epoch { + // Spec: + // - validator index in `get_unslashed_attesting_indices(state, matching_target_attestations)` + if validator.is_previous_epoch_target_attester && !validator.is_slashed { + delta.reward(base_reward * matching_target_balance / total_balance); + } else { delta.penalize(base_reward); } // Expected head. - if validator.is_previous_epoch_head_attester { + // Spec: + // - validator index in `get_unslashed_attesting_indices(state, matching_head_attestations)` + if validator.is_previous_epoch_head_attester && !validator.is_slashed { delta.reward(base_reward * matching_head_balance / total_balance); - } else if validator.is_active_in_previous_epoch { + } else { delta.penalize(base_reward); - }; + } - // Proposer bonus is handled in `apply_proposer_deltas`. + // Inactivity penalty + if finality_delay > spec.min_epochs_to_inactivity_penalty { + // All eligible validators are penalized + delta.penalize(spec.base_rewards_per_epoch * base_reward); + + // Additionally, all validators whose FFG target didn't match are penalized extra + if !validator.is_previous_epoch_target_attester { + delta.penalize( + validator.current_epoch_effective_balance * finality_delay + / spec.inactivity_penalty_quotient, + ); + } + } + + // Proposer bonus is handled in `get_proposer_deltas`. // // This function only computes the delta for a single validator, so it cannot also return a // delta for a validator. @@ -213,52 +228,6 @@ fn compute_normal_justification_and_finalization_delta( delta } -/// Determine the delta for a single delta, assuming the chain is _not_ finalizing normally. -/// -/// Spec v0.5.1 -fn compute_inactivity_leak_delta( - validator: &ValidatorStatus, - base_reward: u64, - inactivity_penalty: u64, - spec: &ChainSpec, -) -> Delta { - let mut delta = Delta::default(); - - if validator.is_active_in_previous_epoch { - if !validator.is_previous_epoch_attester { - delta.penalize(inactivity_penalty); - } else { - // If a validator did attest, apply a small penalty for getting attestations included - // late. - let inclusion = validator - .inclusion_info - .expect("It is a logic error for an attester not to have an inclusion distance."); - delta.reward( - base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(), - ); - delta.penalize(base_reward); - } - - if !validator.is_previous_epoch_boundary_attester { - delta.reward(inactivity_penalty); - } - - if !validator.is_previous_epoch_head_attester { - delta.penalize(inactivity_penalty); - } - } - - // Penalize slashed-but-inactive validators as though they were active but offline. - if !validator.is_active_in_previous_epoch - & validator.is_slashed - & !validator.is_withdrawable_in_current_epoch - { - delta.penalize(2 * inactivity_penalty + base_reward); - } - - delta -} - /// Calculate the deltas based upon the winning roots for attestations during the previous epoch. /// /// Spec v0.5.1 @@ -295,40 +264,20 @@ fn get_crosslink_deltas( /// Returns the base reward for some validator. /// -/// Spec v0.5.1 +/// Spec v0.6.1 fn get_base_reward( state: &BeaconState, index: usize, - previous_total_balance: u64, + // Should be == get_total_active_balance(state, spec) + total_active_balance: u64, spec: &ChainSpec, ) -> Result { - if previous_total_balance == 0 { + if total_active_balance == 0 { Ok(0) } else { - let adjusted_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient; - Ok(state.get_effective_balance(index, spec)? / adjusted_quotient / 5) + let adjusted_quotient = total_active_balance.integer_sqrt() / spec.base_reward_quotient; + Ok(state.get_effective_balance(index, spec)? + / adjusted_quotient + / spec.base_rewards_per_epoch) } } - -/// Returns the inactivity penalty for some validator. -/// -/// Spec v0.5.1 -fn get_inactivity_penalty( - state: &BeaconState, - index: usize, - epochs_since_finality: u64, - previous_total_balance: u64, - spec: &ChainSpec, -) -> Result { - Ok(get_base_reward(state, index, previous_total_balance, spec)? - + state.get_effective_balance(index, spec)? * epochs_since_finality - / spec.inactivity_penalty_quotient - / 2) -} - -/// Returns the epochs since the last finalized epoch. -/// -/// Spec v0.5.1 -fn epochs_since_finality(state: &BeaconState, spec: &ChainSpec) -> Epoch { - state.current_epoch(spec) + 1 - state.finalized_epoch -} diff --git a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs index afa78c9c0..e57c1afc3 100644 --- a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs +++ b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs @@ -68,6 +68,8 @@ pub struct ValidatorStatus { pub is_active_in_current_epoch: bool, /// True if the validator was active in the state's _previous_ epoch. pub is_active_in_previous_epoch: bool, + /// The validator's effective balance in the _current_ epoch. + pub current_epoch_effective_balance: u64, /// True if the validator had an attestation included in the _current_ epoch. pub is_current_epoch_attester: bool, @@ -78,7 +80,7 @@ pub struct ValidatorStatus { pub is_previous_epoch_attester: bool, /// True if the validator's beacon block root attestation for the first slot of the _previous_ /// epoch matches the block root known to the state. - pub is_previous_epoch_boundary_attester: bool, + pub is_previous_epoch_target_attester: bool, /// True if the validator's beacon block root attestation in the _previous_ epoch at the /// attestation's slot (`attestation_data.slot`) matches the block root known to the state. pub is_previous_epoch_head_attester: bool, @@ -108,7 +110,7 @@ impl ValidatorStatus { set_self_if_other_is_true!(self, other, is_current_epoch_attester); set_self_if_other_is_true!(self, other, is_current_epoch_boundary_attester); set_self_if_other_is_true!(self, other, is_previous_epoch_attester); - set_self_if_other_is_true!(self, other, is_previous_epoch_boundary_attester); + set_self_if_other_is_true!(self, other, is_previous_epoch_target_attester); set_self_if_other_is_true!(self, other, is_previous_epoch_head_attester); if let Some(other_info) = other.inclusion_info { @@ -138,7 +140,7 @@ pub struct TotalBalances { pub previous_epoch_attesters: u64, /// The total effective balance of all validators who attested during the _previous_ epoch and /// agreed with the state about the beacon block at the first slot of the _previous_ epoch. - pub previous_epoch_boundary_attesters: u64, + pub previous_epoch_target_attesters: u64, /// The total effective balance of all validators who attested during the _previous_ epoch and /// agreed with the state about the beacon block at the time of attestation. pub previous_epoch_head_attesters: u64, @@ -160,27 +162,29 @@ impl ValidatorStatuses { /// - Active validators /// - Total balances for the current and previous epochs. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn new(state: &BeaconState, spec: &ChainSpec) -> Result { let mut statuses = Vec::with_capacity(state.validator_registry.len()); let mut total_balances = TotalBalances::default(); for (i, validator) in state.validator_registry.iter().enumerate() { + let effective_balance = state.get_effective_balance(i, spec)?; let mut status = ValidatorStatus { is_slashed: validator.slashed, is_withdrawable_in_current_epoch: validator .is_withdrawable_at(state.current_epoch(spec)), + current_epoch_effective_balance: effective_balance, ..ValidatorStatus::default() }; if validator.is_active_at(state.current_epoch(spec)) { status.is_active_in_current_epoch = true; - total_balances.current_epoch += state.get_effective_balance(i, spec)?; + total_balances.current_epoch += effective_balance; } if validator.is_active_at(state.previous_epoch(spec)) { status.is_active_in_previous_epoch = true; - total_balances.previous_epoch += state.get_effective_balance(i, spec)?; + total_balances.previous_epoch += effective_balance; } statuses.push(status); @@ -208,22 +212,18 @@ impl ValidatorStatuses { { let attesting_indices = get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?; - let attesting_balance = state.get_total_balance(&attesting_indices, spec)?; let mut status = ValidatorStatus::default(); // Profile this attestation, updating the total balances and generating an // `ValidatorStatus` object that applies to all participants in the attestation. if is_from_epoch(a, state.current_epoch(spec), spec) { - self.total_balances.current_epoch_attesters += attesting_balance; status.is_current_epoch_attester = true; - if has_common_epoch_boundary_root(a, state, state.current_epoch(spec), spec)? { - self.total_balances.current_epoch_boundary_attesters += attesting_balance; + if target_matches_epoch_start_block(a, state, state.current_epoch(spec), spec)? { status.is_current_epoch_boundary_attester = true; } } else if is_from_epoch(a, state.previous_epoch(spec), spec) { - self.total_balances.previous_epoch_attesters += attesting_balance; status.is_previous_epoch_attester = true; // The inclusion slot and distance are only required for previous epoch attesters. @@ -238,13 +238,11 @@ impl ValidatorStatuses { )?, }); - if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? { - self.total_balances.previous_epoch_boundary_attesters += attesting_balance; - status.is_previous_epoch_boundary_attester = true; + if target_matches_epoch_start_block(a, state, state.previous_epoch(spec), spec)? { + status.is_previous_epoch_target_attester = true; } if has_common_beacon_block_root(a, state, spec)? { - self.total_balances.previous_epoch_head_attesters += attesting_balance; status.is_previous_epoch_head_attester = true; } } @@ -255,6 +253,30 @@ impl ValidatorStatuses { } } + // Compute the total balances + for (index, v) in self.statuses.iter().enumerate() { + // According to the spec, we only count unslashed validators towards the totals. + if !v.is_slashed { + let validator_balance = state.get_effective_balance(index, spec)?; + + if v.is_current_epoch_attester { + self.total_balances.current_epoch_attesters += validator_balance; + } + if v.is_current_epoch_boundary_attester { + self.total_balances.current_epoch_boundary_attesters += validator_balance; + } + if v.is_previous_epoch_attester { + self.total_balances.previous_epoch_attesters += validator_balance; + } + if v.is_previous_epoch_target_attester { + self.total_balances.previous_epoch_target_attesters += validator_balance; + } + if v.is_previous_epoch_head_attester { + self.total_balances.previous_epoch_head_attesters += validator_balance; + } + } + } + Ok(()) } @@ -309,11 +331,11 @@ fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool a.data.slot.epoch(spec.slots_per_epoch) == epoch } -/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for -/// the first slot of the given epoch. +/// Returns `true` if the attestation's FFG target is equal to the hash of the `state`'s first +/// beacon block in the given `epoch`. /// -/// Spec v0.5.1 -fn has_common_epoch_boundary_root( +/// Spec v0.6.0 +fn target_matches_epoch_start_block( a: &PendingAttestation, state: &BeaconState, epoch: Epoch, @@ -328,7 +350,7 @@ fn has_common_epoch_boundary_root( /// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for /// the current slot of the `PendingAttestation`. /// -/// Spec v0.5.1 +/// Spec v0.6.0 fn has_common_beacon_block_root( a: &PendingAttestation, state: &BeaconState, From ed00ad9d00dabc7b44485c5cd1aa00fad9529675 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 6 May 2019 14:18:05 +1000 Subject: [PATCH 04/22] spec: get_crosslink_deltas to v0.6.1 --- .../src/per_epoch_processing/apply_rewards.rs | 4 ++-- .../src/per_epoch_processing/validator_statuses.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs b/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs index 2c45dd822..bc02b67a3 100644 --- a/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs +++ b/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs @@ -230,7 +230,7 @@ fn get_attestation_delta( /// Calculate the deltas based upon the winning roots for attestations during the previous epoch. /// -/// Spec v0.5.1 +/// Spec v0.6.1 fn get_crosslink_deltas( deltas: &mut Vec, state: &BeaconState, @@ -243,7 +243,7 @@ fn get_crosslink_deltas( let base_reward = get_base_reward( state, index, - validator_statuses.total_balances.previous_epoch, + validator_statuses.total_balances.current_epoch, spec, )?; diff --git a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs index e57c1afc3..524a0db09 100644 --- a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs +++ b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs @@ -283,7 +283,7 @@ impl ValidatorStatuses { /// Update the `statuses` for each validator based upon whether or not they attested to the /// "winning" shard block root for the previous epoch. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn process_winning_roots( &mut self, state: &BeaconState, From 839ef0119b785bd533a6c2f82a7e7af1dbd2487d Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 6 May 2019 15:19:27 +1000 Subject: [PATCH 05/22] spec: justification and finalization v0.6.1 --- .../src/per_epoch_processing.rs | 76 ++++++++++--------- .../validator_statuses.rs | 12 +-- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 87c9b9398..a2e696673 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -45,7 +45,7 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result validator_statuses.process_attestations(&state, spec)?; // Justification. - update_justification_and_finalization(state, &validator_statuses.total_balances, spec)?; + process_justification_and_finalization(state, &validator_statuses.total_balances, spec)?; // Crosslinks. let winning_root_for_shards = process_crosslinks(state, spec)?; @@ -104,69 +104,71 @@ pub fn maybe_reset_eth1_period(state: &mut BeaconState, spec: &ChainSpec) { /// Update the following fields on the `BeaconState`: /// /// - `justification_bitfield`. -/// - `finalized_epoch` -/// - `justified_epoch` /// - `previous_justified_epoch` +/// - `previous_justified_root` +/// - `current_justified_epoch` +/// - `current_justified_root` +/// - `finalized_epoch` +/// - `finalized_root` /// -/// Spec v0.5.1 -pub fn update_justification_and_finalization( +/// Spec v0.6.1 +pub fn process_justification_and_finalization( state: &mut BeaconState, total_balances: &TotalBalances, spec: &ChainSpec, ) -> Result<(), Error> { + if state.current_epoch(spec) == spec.genesis_epoch { + return Ok(()); + } + let previous_epoch = state.previous_epoch(spec); let current_epoch = state.current_epoch(spec); - let mut new_justified_epoch = state.current_justified_epoch; - let mut new_finalized_epoch = state.finalized_epoch; + let old_previous_justified_epoch = state.previous_justified_epoch; + let old_current_justified_epoch = state.current_justified_epoch; - // Rotate the justification bitfield up one epoch to make room for the current epoch. + // Process justifications + state.previous_justified_epoch = state.current_justified_epoch; + state.previous_justified_root = state.current_justified_root; state.justification_bitfield <<= 1; - // If the previous epoch gets justified, full the second last bit. - if (total_balances.previous_epoch_boundary_attesters * 3) >= (total_balances.previous_epoch * 2) - { - new_justified_epoch = previous_epoch; + let previous_epoch_matching_target_balance = total_balances.previous_epoch_target_attesters; + + if total_balances.previous_epoch_target_attesters * 3 >= total_balances.previous_epoch * 2 { + state.current_justified_epoch = previous_epoch; + state.current_justified_root = + *state.get_block_root_at_epoch(state.current_justified_epoch, spec)?; state.justification_bitfield |= 2; } // If the current epoch gets justified, fill the last bit. - if (total_balances.current_epoch_boundary_attesters * 3) >= (total_balances.current_epoch * 2) { - new_justified_epoch = current_epoch; + if total_balances.current_epoch_target_attesters * 3 >= total_balances.current_epoch * 2 { + state.current_justified_epoch = current_epoch; + state.current_justified_root = + *state.get_block_root_at_epoch(state.current_justified_epoch, spec)?; state.justification_bitfield |= 1; } let bitfield = state.justification_bitfield; // The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source. - if ((bitfield >> 1) % 8 == 0b111) & (state.previous_justified_epoch == current_epoch - 3) { - new_finalized_epoch = state.previous_justified_epoch; + if (bitfield >> 1) % 8 == 0b111 && old_previous_justified_epoch == current_epoch - 3 { + state.finalized_epoch = old_previous_justified_epoch; + state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?; } // The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source. - if ((bitfield >> 1) % 4 == 0b11) & (state.previous_justified_epoch == current_epoch - 2) { - new_finalized_epoch = state.previous_justified_epoch; + if (bitfield >> 1) % 4 == 0b11 && state.previous_justified_epoch == current_epoch - 2 { + state.finalized_epoch = old_previous_justified_epoch; + state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?; } // The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 2nd as source. - if (bitfield % 8 == 0b111) & (state.current_justified_epoch == current_epoch - 2) { - new_finalized_epoch = state.current_justified_epoch; + if bitfield % 8 == 0b111 && state.current_justified_epoch == current_epoch - 2 { + state.finalized_epoch = old_current_justified_epoch; + state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?; } // The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source. - if (bitfield % 4 == 0b11) & (state.current_justified_epoch == current_epoch - 1) { - new_finalized_epoch = state.current_justified_epoch; - } - - state.previous_justified_epoch = state.current_justified_epoch; - state.previous_justified_root = state.current_justified_root; - - if new_justified_epoch != state.current_justified_epoch { - state.current_justified_epoch = new_justified_epoch; - state.current_justified_root = - *state.get_block_root(new_justified_epoch.start_slot(spec.slots_per_epoch), spec)?; - } - - if new_finalized_epoch != state.finalized_epoch { - state.finalized_epoch = new_finalized_epoch; - state.finalized_root = - *state.get_block_root(new_finalized_epoch.start_slot(spec.slots_per_epoch), spec)?; + if bitfield % 4 == 0b11 && state.current_justified_epoch == current_epoch - 1 { + state.finalized_epoch = old_current_justified_epoch; + state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?; } Ok(()) diff --git a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs index 524a0db09..633dcc4e2 100644 --- a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs +++ b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs @@ -75,7 +75,7 @@ pub struct ValidatorStatus { pub is_current_epoch_attester: bool, /// True if the validator's beacon block root attestation for the first slot of the _current_ /// epoch matches the block root known to the state. - pub is_current_epoch_boundary_attester: bool, + pub is_current_epoch_target_attester: bool, /// True if the validator had an attestation included in the _previous_ epoch. pub is_previous_epoch_attester: bool, /// True if the validator's beacon block root attestation for the first slot of the _previous_ @@ -108,7 +108,7 @@ impl ValidatorStatus { set_self_if_other_is_true!(self, other, is_active_in_current_epoch); set_self_if_other_is_true!(self, other, is_active_in_previous_epoch); set_self_if_other_is_true!(self, other, is_current_epoch_attester); - set_self_if_other_is_true!(self, other, is_current_epoch_boundary_attester); + set_self_if_other_is_true!(self, other, is_current_epoch_target_attester); set_self_if_other_is_true!(self, other, is_previous_epoch_attester); set_self_if_other_is_true!(self, other, is_previous_epoch_target_attester); set_self_if_other_is_true!(self, other, is_previous_epoch_head_attester); @@ -135,7 +135,7 @@ pub struct TotalBalances { pub current_epoch_attesters: u64, /// The total effective balance of all validators who attested during the _current_ epoch and /// agreed with the state about the beacon block at the first slot of the _current_ epoch. - pub current_epoch_boundary_attesters: u64, + pub current_epoch_target_attesters: u64, /// The total effective balance of all validators who attested during the _previous_ epoch. pub previous_epoch_attesters: u64, /// The total effective balance of all validators who attested during the _previous_ epoch and @@ -221,7 +221,7 @@ impl ValidatorStatuses { status.is_current_epoch_attester = true; if target_matches_epoch_start_block(a, state, state.current_epoch(spec), spec)? { - status.is_current_epoch_boundary_attester = true; + status.is_current_epoch_target_attester = true; } } else if is_from_epoch(a, state.previous_epoch(spec), spec) { status.is_previous_epoch_attester = true; @@ -262,8 +262,8 @@ impl ValidatorStatuses { if v.is_current_epoch_attester { self.total_balances.current_epoch_attesters += validator_balance; } - if v.is_current_epoch_boundary_attester { - self.total_balances.current_epoch_boundary_attesters += validator_balance; + if v.is_current_epoch_target_attester { + self.total_balances.current_epoch_target_attesters += validator_balance; } if v.is_previous_epoch_attester { self.total_balances.previous_epoch_attesters += validator_balance; From 5c03f7b06c3a230f6a7a9ed90da5c5daa9e6ccf5 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 7 May 2019 13:29:14 +1000 Subject: [PATCH 06/22] spec: update chain spec for v0.6.1 --- eth2/types/src/attestation_data.rs | 4 +- eth2/types/src/beacon_block.rs | 6 +- eth2/types/src/chain_spec.rs | 95 +++++++++---------- .../testing_attestation_data_builder.rs | 6 +- .../testing_attester_slashing_builder.rs | 6 +- .../testing_beacon_block_builder.rs | 2 +- .../testing_proposer_slashing_builder.rs | 4 +- .../testing_voluntary_exit_builder.rs | 2 +- eth2/types/src/validator.rs | 6 +- 9 files changed, 59 insertions(+), 72 deletions(-) diff --git a/eth2/types/src/attestation_data.rs b/eth2/types/src/attestation_data.rs index 63b94fd70..d09217ef9 100644 --- a/eth2/types/src/attestation_data.rs +++ b/eth2/types/src/attestation_data.rs @@ -1,5 +1,5 @@ use crate::test_utils::TestRandom; -use crate::{Crosslink, Epoch, Hash256, Slot}; +use crate::{Epoch, Hash256, Slot}; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; @@ -37,7 +37,7 @@ pub struct AttestationData { // Crosslink Vote pub shard: u64, - pub previous_crosslink_root: Crosslink, + pub previous_crosslink_root: Hash256, pub crosslink_data_root: Hash256, } diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index 1265a4864..ff75fbb64 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -43,7 +43,7 @@ impl BeaconBlock { previous_block_root: spec.zero_hash, state_root: spec.zero_hash, body: BeaconBlockBody { - randao_reveal: spec.empty_signature.clone(), + randao_reveal: Signature::empty_signature(), eth1_data: Eth1Data { deposit_root: spec.zero_hash, block_hash: spec.zero_hash, @@ -56,7 +56,7 @@ impl BeaconBlock { voluntary_exits: vec![], transfers: vec![], }, - signature: spec.empty_signature.clone(), + signature: Signature::empty_signature(), } } @@ -91,7 +91,7 @@ impl BeaconBlock { pub fn temporary_block_header(&self, spec: &ChainSpec) -> BeaconBlockHeader { BeaconBlockHeader { state_root: spec.zero_hash, - signature: spec.empty_signature.clone(), + signature: Signature::empty_signature(), ..self.block_header() } } diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index f3c92b42c..c219c05ee 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -1,26 +1,23 @@ use crate::*; -use bls::Signature; use int_to_bytes::int_to_bytes4; use serde_derive::Deserialize; use test_utils::u8_from_hex_str; -const GWEI: u64 = 1_000_000_000; - /// Each of the BLS signature domains. /// -/// Spec v0.5.1 +/// Spec v0.6.1 pub enum Domain { - BeaconBlock, + BeaconProposer, Randao, Attestation, Deposit, - Exit, + VoluntaryExit, Transfer, } /// Holds all the "constants" for a BeaconChain. /// -/// Spec v0.5.1 +/// Spec v0.6.1 #[derive(PartialEq, Debug, Clone, Deserialize)] #[serde(default)] pub struct ChainSpec { @@ -29,35 +26,32 @@ pub struct ChainSpec { */ pub shard_count: u64, pub target_committee_size: u64, - pub max_balance_churn_quotient: u64, - pub max_indices_per_slashable_vote: usize, - pub max_exit_dequeues_per_epoch: u64, + pub max_indices_per_attestation: u64, + pub min_per_epoch_churn_limit: u64, + pub churn_limit_quotient: u64, + pub base_rewards_per_epoch: u64, pub shuffle_round_count: u8, /* * Deposit contract */ - pub deposit_contract_address: Address, pub deposit_contract_tree_depth: u64, /* * Gwei values */ pub min_deposit_amount: u64, - pub max_deposit_amount: u64, - pub fork_choice_balance_increment: u64, + pub max_effective_balance: u64, pub ejection_balance: u64, + pub effective_balance_increment: u64, /* * Initial Values */ - pub genesis_fork_version: u32, pub genesis_slot: Slot, pub genesis_epoch: Epoch, - pub genesis_start_shard: u64, pub far_future_epoch: Epoch, pub zero_hash: Hash256, - pub empty_signature: Signature, #[serde(deserialize_with = "u8_from_hex_str")] pub bls_withdrawal_prefix_byte: u8, @@ -69,10 +63,12 @@ pub struct ChainSpec { pub slots_per_epoch: u64, pub min_seed_lookahead: Epoch, pub activation_exit_delay: u64, - pub epochs_per_eth1_voting_period: u64, + pub slots_per_eth1_voting_period: u64, pub slots_per_historical_root: usize, pub min_validator_withdrawability_delay: Epoch, pub persistent_committee_period: u64, + pub max_crosslink_epochs: u64, + pub min_epochs_to_inactivity_penalty: u64, /* * State list lengths @@ -85,10 +81,10 @@ pub struct ChainSpec { * Reward and penalty quotients */ pub base_reward_quotient: u64, - pub whistleblower_reward_quotient: u64, - pub attestation_inclusion_reward_quotient: u64, + pub whistleblowing_reward_quotient: u64, + pub proposer_reward_quotient: u64, pub inactivity_penalty_quotient: u64, - pub min_penalty_quotient: u64, + pub min_slashing_penalty_quotient: u64, /* * Max operations per block @@ -108,11 +104,11 @@ pub struct ChainSpec { * * Use `ChainSpec::get_domain(..)` to access these values. */ - domain_beacon_block: u32, + domain_beacon_proposer: u32, domain_randao: u32, domain_attestation: u32, domain_deposit: u32, - domain_exit: u32, + domain_voluntary_exit: u32, domain_transfer: u32, /* @@ -139,14 +135,14 @@ impl ChainSpec { /// Get the domain number that represents the fork meta and signature domain. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn get_domain(&self, epoch: Epoch, domain: Domain, fork: &Fork) -> u64 { let domain_constant = match domain { - Domain::BeaconBlock => self.domain_beacon_block, + Domain::BeaconProposer => self.domain_beacon_proposer, Domain::Randao => self.domain_randao, Domain::Attestation => self.domain_attestation, Domain::Deposit => self.domain_deposit, - Domain::Exit => self.domain_exit, + Domain::VoluntaryExit => self.domain_voluntary_exit, Domain::Transfer => self.domain_transfer, }; @@ -161,47 +157,40 @@ impl ChainSpec { /// Returns a `ChainSpec` compatible with the Ethereum Foundation specification. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn foundation() -> Self { - let genesis_slot = Slot::new(2_u64.pow(32)); - let slots_per_epoch = 64; - let genesis_epoch = genesis_slot.epoch(slots_per_epoch); - Self { /* * Misc */ shard_count: 1_024, target_committee_size: 128, - max_balance_churn_quotient: 32, - max_indices_per_slashable_vote: 4_096, - max_exit_dequeues_per_epoch: 4, + max_indices_per_attestation: 4096, + min_per_epoch_churn_limit: 4, + churn_limit_quotient: 65_536, + base_rewards_per_epoch: 5, shuffle_round_count: 90, /* * Deposit contract */ - deposit_contract_address: Address::zero(), deposit_contract_tree_depth: 32, /* * 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, + min_deposit_amount: u64::pow(2, 0) * u64::pow(10, 9), + max_effective_balance: u64::pow(2, 5) * u64::pow(10, 9), + ejection_balance: u64::pow(2, 4) * u64::pow(10, 9), + effective_balance_increment: u64::pow(2, 0) * u64::pow(10, 9), /* * Initial Values */ - genesis_fork_version: 0, - genesis_slot, - genesis_epoch, - genesis_start_shard: 0, + genesis_slot: Slot::new(0), + genesis_epoch: Epoch::new(0), far_future_epoch: Epoch::new(u64::max_value()), zero_hash: Hash256::zero(), - empty_signature: Signature::empty_signature(), bls_withdrawal_prefix_byte: 0, /* @@ -209,13 +198,15 @@ impl ChainSpec { */ seconds_per_slot: 6, min_attestation_inclusion_delay: 4, - slots_per_epoch, + slots_per_epoch: 64, min_seed_lookahead: Epoch::new(1), activation_exit_delay: 4, - epochs_per_eth1_voting_period: 16, + slots_per_eth1_voting_period: 1_024, slots_per_historical_root: 8_192, min_validator_withdrawability_delay: Epoch::new(256), persistent_committee_period: 2_048, + max_crosslink_epochs: 64, + min_epochs_to_inactivity_penalty: 4, /* * State list lengths @@ -228,10 +219,10 @@ impl ChainSpec { * Reward and penalty quotients */ base_reward_quotient: 32, - whistleblower_reward_quotient: 512, - attestation_inclusion_reward_quotient: 8, - inactivity_penalty_quotient: 16_777_216, - min_penalty_quotient: 32, + whistleblowing_reward_quotient: 512, + proposer_reward_quotient: 8, + inactivity_penalty_quotient: 33_554_432, + min_slashing_penalty_quotient: 32, /* * Max operations per block @@ -241,16 +232,16 @@ impl ChainSpec { max_attestations: 128, max_deposits: 16, max_voluntary_exits: 16, - max_transfers: 16, + max_transfers: 0, /* * Signature domains */ - domain_beacon_block: 0, + domain_beacon_proposer: 0, domain_randao: 1, domain_attestation: 2, domain_deposit: 3, - domain_exit: 4, + domain_voluntary_exit: 4, domain_transfer: 5, /* diff --git a/eth2/types/src/test_utils/testing_attestation_data_builder.rs b/eth2/types/src/test_utils/testing_attestation_data_builder.rs index 146485fc0..9fde5202c 100644 --- a/eth2/types/src/test_utils/testing_attestation_data_builder.rs +++ b/eth2/types/src/test_utils/testing_attestation_data_builder.rs @@ -49,11 +49,7 @@ impl TestingAttestationDataBuilder { // Crosslink vote shard, - previous_crosslink_root: Crosslink { - epoch: slot.epoch(spec.slots_per_epoch), - previous_crosslink_root: spec.zero_hash, - crosslink_data_root: spec.zero_hash, - }, + previous_crosslink_root: spec.zero_hash, crosslink_data_root: spec.zero_hash, }; diff --git a/eth2/types/src/test_utils/testing_attester_slashing_builder.rs b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs index bd5aea2a4..91615d258 100644 --- a/eth2/types/src/test_utils/testing_attester_slashing_builder.rs +++ b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs @@ -34,11 +34,7 @@ impl TestingAttesterSlashingBuilder { source_root: hash_1, target_root: hash_1, shard, - previous_crosslink_root: Crosslink { - epoch, - previous_crosslink_root: hash_1, - crosslink_data_root: hash_1, - }, + previous_crosslink_root: hash_1, crosslink_data_root: hash_1, }; diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index 549c00ac0..88e667efd 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -34,7 +34,7 @@ impl TestingBeaconBlockBuilder { pub fn sign(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) { let message = self.block.signed_root(); let epoch = self.block.slot.epoch(spec.slots_per_epoch); - let domain = spec.get_domain(epoch, Domain::BeaconBlock, fork); + let domain = spec.get_domain(epoch, Domain::BeaconProposer, fork); self.block.signature = Signature::new(&message, domain, sk); } diff --git a/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs b/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs index 03c257b2d..458082de2 100644 --- a/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs +++ b/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs @@ -41,13 +41,13 @@ impl TestingProposerSlashingBuilder { header_1.signature = { let message = header_1.signed_root(); let epoch = slot.epoch(spec.slots_per_epoch); - signer(proposer_index, &message[..], epoch, Domain::BeaconBlock) + signer(proposer_index, &message[..], epoch, Domain::BeaconProposer) }; header_2.signature = { let message = header_2.signed_root(); let epoch = slot.epoch(spec.slots_per_epoch); - signer(proposer_index, &message[..], epoch, Domain::BeaconBlock) + signer(proposer_index, &message[..], epoch, Domain::BeaconProposer) }; ProposerSlashing { diff --git a/eth2/types/src/test_utils/testing_voluntary_exit_builder.rs b/eth2/types/src/test_utils/testing_voluntary_exit_builder.rs index 8583bc451..58e8e750d 100644 --- a/eth2/types/src/test_utils/testing_voluntary_exit_builder.rs +++ b/eth2/types/src/test_utils/testing_voluntary_exit_builder.rs @@ -25,7 +25,7 @@ impl TestingVoluntaryExitBuilder { /// The signing secret key must match that of the exiting validator. pub fn sign(&mut self, secret_key: &SecretKey, fork: &Fork, spec: &ChainSpec) { let message = self.exit.signed_root(); - let domain = spec.get_domain(self.exit.epoch, Domain::Exit, fork); + let domain = spec.get_domain(self.exit.epoch, Domain::VoluntaryExit, fork); self.exit.signature = Signature::new(&message, domain, secret_key); } diff --git a/eth2/types/src/validator.rs b/eth2/types/src/validator.rs index 5b369427c..273190191 100644 --- a/eth2/types/src/validator.rs +++ b/eth2/types/src/validator.rs @@ -37,6 +37,11 @@ impl Validator { self.activation_epoch <= epoch && epoch < self.exit_epoch } + /// Returns `true` if the validator is slashable at some epoch. + pub fn is_slashable_at(&self, epoch: Epoch) -> bool { + !self.slashed && self.activation_epoch <= epoch && epoch < self.withdrawable_epoch + } + /// Returns `true` if the validator is considered exited at some epoch. pub fn is_exited_at(&self, epoch: Epoch) -> bool { self.exit_epoch <= epoch @@ -77,7 +82,6 @@ mod tests { assert_eq!(v.is_active_at(epoch), false); assert_eq!(v.is_exited_at(epoch), false); assert_eq!(v.is_withdrawable_at(epoch), false); - assert_eq!(v.initiated_exit, false); assert_eq!(v.slashed, false); } From 5394726caffb75c6fc306d6aa170287c6ff75146 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 7 May 2019 14:57:42 +1000 Subject: [PATCH 07/22] spec: initiate_validator_exit v0.6.1 Added a new field `exit_cache` to the BeaconState, which caches the number of validators exiting at each epoch. --- eth2/state_processing/src/common/exit.rs | 39 +++++++++++++ .../src/common/exit_validator.rs | 22 -------- eth2/state_processing/src/common/mod.rs | 4 +- .../src/common/slash_validator.rs | 56 +++++++------------ eth2/types/src/beacon_state.rs | 26 +++++++++ eth2/types/src/beacon_state/exit_cache.rs | 35 ++++++++++++ 6 files changed, 122 insertions(+), 60 deletions(-) create mode 100644 eth2/state_processing/src/common/exit.rs delete mode 100644 eth2/state_processing/src/common/exit_validator.rs create mode 100644 eth2/types/src/beacon_state/exit_cache.rs diff --git a/eth2/state_processing/src/common/exit.rs b/eth2/state_processing/src/common/exit.rs new file mode 100644 index 000000000..a05ce8f9c --- /dev/null +++ b/eth2/state_processing/src/common/exit.rs @@ -0,0 +1,39 @@ +use std::cmp::max; +use types::{BeaconStateError as Error, *}; + +/// Initiate the exit of the validator of the given `index`. +/// +/// Spec v0.6.1 +pub fn initiate_validator_exit( + state: &mut BeaconState, + index: usize, + spec: &ChainSpec, +) -> Result<(), Error> { + if index >= state.validator_registry.len() { + return Err(Error::UnknownValidator); + } + + // Return if the validator already initiated exit + if state.validator_registry[index].exit_epoch != spec.far_future_epoch { + return Ok(()); + } + + // Compute exit queue epoch + let delayed_epoch = state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec); + let mut exit_queue_epoch = state + .exit_cache + .max_epoch() + .map_or(delayed_epoch, |epoch| max(epoch, delayed_epoch)); + let exit_queue_churn = state.exit_cache.get_churn_at(exit_queue_epoch); + + if exit_queue_churn >= state.get_churn_limit(spec)? { + exit_queue_epoch += 1; + } + + state.exit_cache.record_validator_exit(exit_queue_epoch); + state.validator_registry[index].exit_epoch = exit_queue_epoch; + state.validator_registry[index].withdrawable_epoch = + exit_queue_epoch + spec.min_validator_withdrawability_delay; + + Ok(()) +} diff --git a/eth2/state_processing/src/common/exit_validator.rs b/eth2/state_processing/src/common/exit_validator.rs deleted file mode 100644 index a6cfb395e..000000000 --- a/eth2/state_processing/src/common/exit_validator.rs +++ /dev/null @@ -1,22 +0,0 @@ -use types::{BeaconStateError as Error, *}; - -/// Exit the validator of the given `index`. -/// -/// Spec v0.5.1 -pub fn exit_validator( - state: &mut BeaconState, - validator_index: usize, - spec: &ChainSpec, -) -> Result<(), Error> { - if validator_index >= state.validator_registry.len() { - return Err(Error::UnknownValidator); - } - - let delayed_epoch = state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec); - - if state.validator_registry[validator_index].exit_epoch > delayed_epoch { - state.validator_registry[validator_index].exit_epoch = delayed_epoch; - } - - Ok(()) -} diff --git a/eth2/state_processing/src/common/mod.rs b/eth2/state_processing/src/common/mod.rs index 49898d10f..896dfad0d 100644 --- a/eth2/state_processing/src/common/mod.rs +++ b/eth2/state_processing/src/common/mod.rs @@ -1,7 +1,7 @@ -mod exit_validator; +mod exit; mod slash_validator; mod verify_bitfield; -pub use exit_validator::exit_validator; +pub use exit::initiate_validator_exit; pub use slash_validator::slash_validator; pub use verify_bitfield::verify_bitfield_length; diff --git a/eth2/state_processing/src/common/slash_validator.rs b/eth2/state_processing/src/common/slash_validator.rs index c1aad7da1..d50631fa1 100644 --- a/eth2/state_processing/src/common/slash_validator.rs +++ b/eth2/state_processing/src/common/slash_validator.rs @@ -1,62 +1,46 @@ -use crate::common::exit_validator; +use crate::common::initiate_validator_exit; use types::{BeaconStateError as Error, *}; /// Slash the validator with index ``index``. /// -/// Spec v0.5.1 +/// Spec v0.6.1 pub fn slash_validator( state: &mut BeaconState, - validator_index: usize, + slashed_index: usize, + opt_whistleblower_index: Option, spec: &ChainSpec, ) -> Result<(), Error> { - let current_epoch = state.current_epoch(spec); - - if (validator_index >= state.validator_registry.len()) - | (validator_index >= state.validator_balances.len()) - { + if slashed_index >= state.validator_registry.len() || slashed_index >= state.balances.len() { return Err(BeaconStateError::UnknownValidator); } - let validator = &state.validator_registry[validator_index]; + let current_epoch = state.current_epoch(spec); - let effective_balance = state.get_effective_balance(validator_index, spec)?; + initiate_validator_exit(state, slashed_index, spec)?; - // A validator that is withdrawn cannot be slashed. - // - // This constraint will be lifted in Phase 0. - if state.slot - >= validator - .withdrawable_epoch - .start_slot(spec.slots_per_epoch) - { - return Err(Error::ValidatorIsWithdrawable); - } - - exit_validator(state, validator_index, spec)?; + state.validator_registry[slashed_index].slashed = true; + state.validator_registry[slashed_index].withdrawable_epoch = + current_epoch + Epoch::from(spec.latest_slashed_exit_length); + let slashed_balance = state.get_effective_balance(slashed_index, spec)?; state.set_slashed_balance( current_epoch, - state.get_slashed_balance(current_epoch, spec)? + effective_balance, + state.get_slashed_balance(current_epoch, spec)? + slashed_balance, spec, )?; - let whistleblower_index = + let proposer_index = state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?; - let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient; + let whistleblower_index = opt_whistleblower_index.unwrap_or(proposer_index); + let whistleblowing_reward = slashed_balance / spec.whistleblowing_reward_quotient; + let proposer_reward = whistleblowing_reward / spec.proposer_reward_quotient; + safe_add_assign!(state.balances[proposer_index], proposer_reward); safe_add_assign!( - state.validator_balances[whistleblower_index as usize], - whistleblower_reward + state.balances[whistleblower_index], + whistleblowing_reward - proposer_reward ); - safe_sub_assign!( - state.validator_balances[validator_index], - whistleblower_reward - ); - - state.validator_registry[validator_index].slashed = true; - - state.validator_registry[validator_index].withdrawable_epoch = - current_epoch + Epoch::from(spec.latest_slashed_exit_length); + safe_sub_assign!(state.balances[slashed_index], whistleblowing_reward); Ok(()) } diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index cb37c8448..4ac0c1f71 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1,4 +1,5 @@ use self::epoch_cache::{get_active_validator_indices, EpochCache, Error as EpochCacheError}; +use self::exit_cache::ExitCache; use crate::test_utils::TestRandom; use crate::*; use cached_tree_hash::{Error as TreeHashCacheError, TreeHashCache}; @@ -13,6 +14,7 @@ use tree_hash::TreeHash; use tree_hash_derive::{CachedTreeHash, TreeHash}; mod epoch_cache; +mod exit_cache; mod pubkey_cache; mod tests; @@ -126,6 +128,12 @@ pub struct BeaconState { #[tree_hash(skip_hashing)] #[test_random(default)] pub tree_hash_cache: TreeHashCache, + #[serde(skip_serializing, skip_deserializing)] + #[ssz(skip_serializing)] + #[ssz(skip_deserializing)] + #[tree_hash(skip_hashing)] + #[test_random(default)] + pub exit_cache: ExitCache, } impl BeaconState { @@ -198,6 +206,7 @@ impl BeaconState { ], pubkey_cache: PubkeyCache::default(), tree_hash_cache: TreeHashCache::default(), + exit_cache: ExitCache::default(), } } @@ -634,6 +643,21 @@ impl BeaconState { epoch + 1 + spec.activation_exit_delay } + /// Return the churn limit for the current epoch (number of validators who can leave per epoch). + /// + /// Uses the epoch cache, and will error if it isn't initialized. + /// + /// Spec v0.6.1 + pub fn get_churn_limit(&self, spec: &ChainSpec) -> Result { + Ok(std::cmp::max( + spec.min_per_epoch_churn_limit, + self.cache(RelativeEpoch::Current, spec)? + .active_validator_indices + .len() as u64 + / spec.churn_limit_quotient, + )) + } + /// Initiate an exit for the validator of the given `index`. /// /// Spec v0.5.1 @@ -685,6 +709,8 @@ impl BeaconState { self.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec)?; self.update_pubkey_cache()?; self.update_tree_hash_cache()?; + self.exit_cache + .build_from_registry(&self.validator_registry, spec); Ok(()) } diff --git a/eth2/types/src/beacon_state/exit_cache.rs b/eth2/types/src/beacon_state/exit_cache.rs new file mode 100644 index 000000000..c129d70a2 --- /dev/null +++ b/eth2/types/src/beacon_state/exit_cache.rs @@ -0,0 +1,35 @@ +use super::{ChainSpec, Epoch, Validator}; +use serde_derive::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// Map from exit epoch to the number of validators known to be exiting/exited at that epoch. +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +pub struct ExitCache(HashMap); + +impl ExitCache { + /// Add all validators with a non-trivial exit epoch to the cache. + pub fn build_from_registry(&mut self, validator_registry: &[Validator], spec: &ChainSpec) { + validator_registry + .iter() + .filter(|validator| validator.exit_epoch != spec.far_future_epoch) + .for_each(|validator| self.record_validator_exit(validator.exit_epoch)); + } + + /// Record the exit of a single validator in the cache. + /// + /// Must only be called once per exiting validator. + pub fn record_validator_exit(&mut self, exit_epoch: Epoch) { + *self.0.entry(exit_epoch).or_insert(0) += 1; + } + + /// Get the greatest epoch for which validator exits are known. + pub fn max_epoch(&self) -> Option { + // This could probably be made even faster by caching the maximum. + self.0.keys().max().cloned() + } + + /// Get the number of validators exiting/exited at a given epoch, or zero if not known. + pub fn get_churn_at(&self, epoch: Epoch) -> u64 { + self.0.get(&epoch).cloned().unwrap_or(0) + } +} From eda8ec8c558ac589a2e2dc21961bad0d44ab39c0 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 7 May 2019 18:21:25 +1000 Subject: [PATCH 08/22] spec: registry updates v0.6.1 --- .../per_epoch_processing/registry_updates.rs | 70 ++++++++ .../update_registry_and_shuffling_data.rs | 150 ------------------ 2 files changed, 70 insertions(+), 150 deletions(-) create mode 100644 eth2/state_processing/src/per_epoch_processing/registry_updates.rs delete mode 100644 eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs diff --git a/eth2/state_processing/src/per_epoch_processing/registry_updates.rs b/eth2/state_processing/src/per_epoch_processing/registry_updates.rs new file mode 100644 index 000000000..97e5d81da --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/registry_updates.rs @@ -0,0 +1,70 @@ +use super::super::common::initiate_validator_exit; +use super::Error; +use itertools::{Either, Itertools}; +use types::*; + +/// Peforms a validator registry update, if required. +/// +/// Spec v0.6.1 +pub fn process_registry_updates( + state: &mut BeaconState, + current_total_balance: u64, + spec: &ChainSpec, +) -> Result<(), Error> { + // Process activation eligibility and ejections. + // Collect eligible and exiting validators (we need to avoid mutating the state while iterating). + // We assume it's safe to re-order the change in eligibility and `initiate_validator_exit`. + // Rest assured exiting validators will still be exited in the same order as in the spec. + let current_epoch = state.current_epoch(spec); + let is_eligible = |validator: &Validator| { + validator.activation_eligibility_epoch == spec.far_future_epoch + && validator.effective_balance >= spec.max_effective_balance + }; + let is_exiting_validator = |validator: &Validator| { + validator.is_active_at(current_epoch) + && validator.effective_balance <= spec.ejection_balance + }; + let (eligible_validators, exiting_validators): (Vec<_>, Vec<_>) = state + .validator_registry + .iter() + .enumerate() + .filter(|(_, validator)| is_eligible(validator) || is_exiting_validator(validator)) + .partition_map(|(index, validator)| { + if is_eligible(validator) { + Either::Left(index) + } else { + Either::Right(index) + } + }); + for index in eligible_validators { + state.validator_registry[index].activation_eligibility_epoch = current_epoch; + } + for index in exiting_validators { + initiate_validator_exit(state, index, spec)?; + } + + // Queue validators eligible for activation and not dequeued for activation prior to finalized epoch + let activation_queue = state + .validator_registry + .iter() + .enumerate() + .filter(|(_, validator)| { + validator.activation_eligibility_epoch != spec.far_future_epoch + && validator.activation_epoch + >= state.get_delayed_activation_exit_epoch(state.finalized_epoch, spec) + }) + .sorted_by_key(|(_, validator)| validator.activation_eligibility_epoch) + .map(|(index, _)| index) + .collect_vec(); + + let churn_limit = state.get_churn_limit(spec)? as usize; + let delayed_activation_epoch = state.get_delayed_activation_exit_epoch(current_epoch, spec); + for index in activation_queue.into_iter().take(churn_limit) { + let validator = &mut state.validator_registry[index]; + if validator.activation_epoch == spec.far_future_epoch { + validator.activation_epoch = delayed_activation_epoch; + } + } + + Ok(()) +} diff --git a/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs b/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs deleted file mode 100644 index d290d2987..000000000 --- a/eth2/state_processing/src/per_epoch_processing/update_registry_and_shuffling_data.rs +++ /dev/null @@ -1,150 +0,0 @@ -use super::super::common::exit_validator; -use super::Error; -use types::*; - -/// Peforms a validator registry update, if required. -/// -/// Spec v0.5.1 -pub fn update_registry_and_shuffling_data( - state: &mut BeaconState, - current_total_balance: u64, - spec: &ChainSpec, -) -> Result<(), Error> { - // First set previous shuffling data to current shuffling data. - state.previous_shuffling_epoch = state.current_shuffling_epoch; - state.previous_shuffling_start_shard = state.previous_shuffling_start_shard; - state.previous_shuffling_seed = state.previous_shuffling_seed; - - let current_epoch = state.current_epoch(spec); - let next_epoch = current_epoch + 1; - - // Check we should update, and if so, update. - if should_update_validator_registry(state, spec)? { - update_validator_registry(state, current_total_balance, spec)?; - - // If we update the registry, update the shuffling data and shards as well. - state.current_shuffling_epoch = next_epoch; - state.current_shuffling_start_shard = { - let active_validators = - state.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?; - let epoch_committee_count = spec.get_epoch_committee_count(active_validators.len()); - - (state.current_shuffling_start_shard + epoch_committee_count) % spec.shard_count - }; - state.current_shuffling_seed = state.generate_seed(state.current_shuffling_epoch, spec)?; - } else { - // If processing at least on crosslink keeps failing, the reshuffle every power of two, but - // don't update the current_shuffling_start_shard. - let epochs_since_last_update = current_epoch - state.validator_registry_update_epoch; - - if epochs_since_last_update > 1 && epochs_since_last_update.is_power_of_two() { - state.current_shuffling_epoch = next_epoch; - state.current_shuffling_seed = - state.generate_seed(state.current_shuffling_epoch, spec)?; - } - } - - Ok(()) -} - -/// Returns `true` if the validator registry should be updated during an epoch processing. -/// -/// Spec v0.5.1 -pub fn should_update_validator_registry( - state: &BeaconState, - spec: &ChainSpec, -) -> Result { - if state.finalized_epoch <= state.validator_registry_update_epoch { - return Ok(false); - } - - let num_active_validators = state - .get_cached_active_validator_indices(RelativeEpoch::Current, spec)? - .len(); - let current_epoch_committee_count = spec.get_epoch_committee_count(num_active_validators); - - for shard in (0..current_epoch_committee_count) - .map(|i| (state.current_shuffling_start_shard + i as u64) % spec.shard_count) - { - if state.latest_crosslinks[shard as usize].epoch <= state.validator_registry_update_epoch { - return Ok(false); - } - } - - Ok(true) -} - -/// Update validator registry, activating/exiting validators if possible. -/// -/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. -/// -/// Spec v0.5.1 -pub fn update_validator_registry( - state: &mut BeaconState, - current_total_balance: u64, - spec: &ChainSpec, -) -> Result<(), Error> { - let current_epoch = state.current_epoch(spec); - - let max_balance_churn = std::cmp::max( - spec.max_deposit_amount, - current_total_balance / (2 * spec.max_balance_churn_quotient), - ); - - // Activate validators within the allowable balance churn. - let mut balance_churn = 0; - for index in 0..state.validator_registry.len() { - let not_activated = - state.validator_registry[index].activation_epoch == spec.far_future_epoch; - let has_enough_balance = state.validator_balances[index] >= spec.max_deposit_amount; - - if not_activated && has_enough_balance { - // Check the balance churn would be within the allowance. - balance_churn += state.get_effective_balance(index, spec)?; - if balance_churn > max_balance_churn { - break; - } - - activate_validator(state, index, false, spec); - } - } - - // Exit validators within the allowable balance churn. - let mut balance_churn = 0; - for index in 0..state.validator_registry.len() { - let not_exited = state.validator_registry[index].exit_epoch == spec.far_future_epoch; - let has_initiated_exit = state.validator_registry[index].initiated_exit; - - if not_exited && has_initiated_exit { - // Check the balance churn would be within the allowance. - balance_churn += state.get_effective_balance(index, spec)?; - if balance_churn > max_balance_churn { - break; - } - - exit_validator(state, index, spec)?; - } - } - - state.validator_registry_update_epoch = current_epoch; - - Ok(()) -} - -/// Activate the validator of the given ``index``. -/// -/// Spec v0.5.1 -pub fn activate_validator( - state: &mut BeaconState, - validator_index: usize, - is_genesis: bool, - spec: &ChainSpec, -) { - let current_epoch = state.current_epoch(spec); - - state.validator_registry[validator_index].activation_epoch = if is_genesis { - spec.genesis_epoch - } else { - state.get_delayed_activation_exit_epoch(current_epoch, spec) - } -} From 240e269f2fcc1b1dabdf7cfa5e2a042ffa4f35bf Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 13 May 2019 17:01:30 +1000 Subject: [PATCH 09/22] types: more v0.6.1 updates --- eth2/types/src/attestation_data.rs | 6 +- eth2/types/src/beacon_state.rs | 9 ++- eth2/types/src/chain_spec.rs | 6 +- eth2/types/src/crosslink.rs | 1 + eth2/types/src/deposit_data.rs | 1 + eth2/types/src/fork.rs | 14 ++-- eth2/types/src/indexed_attestation.rs | 73 ++++++++----------- eth2/types/src/pending_attestation.rs | 10 +-- .../test_utils/testing_attestation_builder.rs | 2 +- .../testing_attestation_data_builder.rs | 8 +- .../testing_attester_slashing_builder.rs | 11 +-- .../testing_beacon_state_builder.rs | 2 +- .../testing_pending_attestation_builder.rs | 5 +- 13 files changed, 73 insertions(+), 75 deletions(-) diff --git a/eth2/types/src/attestation_data.rs b/eth2/types/src/attestation_data.rs index d09217ef9..e80a01efd 100644 --- a/eth2/types/src/attestation_data.rs +++ b/eth2/types/src/attestation_data.rs @@ -1,5 +1,5 @@ use crate::test_utils::TestRandom; -use crate::{Epoch, Hash256, Slot}; +use crate::{Epoch, Hash256}; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; @@ -9,7 +9,7 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash}; /// The data upon which an attestation is based. /// -/// Spec v0.6.0 +/// Spec v0.6.1 #[derive( Debug, Clone, @@ -27,12 +27,12 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash}; )] pub struct AttestationData { // LMD GHOST vote - pub slot: Slot, pub beacon_block_root: Hash256, // FFG Vote pub source_epoch: Epoch, pub source_root: Hash256, + pub target_epoch: Epoch, pub target_root: Hash256, // Crosslink Vote diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 4ac0c1f71..f0e5647ad 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -250,9 +250,14 @@ impl BeaconState { /// /// If the current epoch is the genesis epoch, the genesis_epoch is returned. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn previous_epoch(&self, spec: &ChainSpec) -> Epoch { - self.current_epoch(&spec) - 1 + let current_epoch = self.current_epoch(spec); + if current_epoch > spec.genesis_epoch { + current_epoch - 1 + } else { + current_epoch + } } /// The epoch following `self.current_epoch()`. diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index c219c05ee..11d2318cb 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -122,7 +122,7 @@ pub struct ChainSpec { impl ChainSpec { /// Return the number of committees in one epoch. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn get_epoch_committee_count(&self, active_validator_count: usize) -> u64 { std::cmp::max( 1, @@ -319,11 +319,11 @@ mod tests { fn test_get_domain() { let spec = ChainSpec::foundation(); - test_domain(Domain::BeaconBlock, spec.domain_beacon_block, &spec); + test_domain(Domain::BeaconProposer, spec.domain_beacon_proposer, &spec); test_domain(Domain::Randao, spec.domain_randao, &spec); test_domain(Domain::Attestation, spec.domain_attestation, &spec); test_domain(Domain::Deposit, spec.domain_deposit, &spec); - test_domain(Domain::Exit, spec.domain_exit, &spec); + test_domain(Domain::VoluntaryExit, spec.domain_voluntary_exit, &spec); test_domain(Domain::Transfer, spec.domain_transfer, &spec); } } diff --git a/eth2/types/src/crosslink.rs b/eth2/types/src/crosslink.rs index e6d353452..ed20f4af7 100644 --- a/eth2/types/src/crosslink.rs +++ b/eth2/types/src/crosslink.rs @@ -13,6 +13,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; Debug, Clone, PartialEq, + Eq, Default, Serialize, Deserialize, diff --git a/eth2/types/src/deposit_data.rs b/eth2/types/src/deposit_data.rs index 930836c70..08bdccc41 100644 --- a/eth2/types/src/deposit_data.rs +++ b/eth2/types/src/deposit_data.rs @@ -76,6 +76,7 @@ mod tests { let mut deposit_input = DepositData { pubkey: keypair.pk.clone(), + amount: 0, withdrawal_credentials: Hash256::zero(), signature: Signature::empty_signature(), }; diff --git a/eth2/types/src/fork.rs b/eth2/types/src/fork.rs index 83d4f5dc6..c095abbce 100644 --- a/eth2/types/src/fork.rs +++ b/eth2/types/src/fork.rs @@ -2,7 +2,6 @@ use crate::{ test_utils::{fork_from_hex_str, TestRandom}, ChainSpec, Epoch, }; -use int_to_bytes::int_to_bytes4; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; @@ -11,7 +10,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; /// Specifies a fork of the `BeaconChain`, to prevent replay attacks. /// -/// Spec v0.5.1 +/// Spec v0.6.1 #[derive( Debug, Clone, @@ -36,10 +35,11 @@ pub struct Fork { impl Fork { /// Initialize the `Fork` from the genesis parameters in the `spec`. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn genesis(spec: &ChainSpec) -> Self { - let mut current_version: [u8; 4] = [0; 4]; - current_version.copy_from_slice(&int_to_bytes4(spec.genesis_fork_version)); + let current_version: [u8; 4] = [0; 4]; + // FIXME(sproul): 0 fork? + // current_version.copy_from_slice(&int_to_bytes4(spec.genesis_fork_version)); Self { previous_version: current_version, @@ -50,7 +50,7 @@ impl Fork { /// Return the fork version of the given ``epoch``. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn get_fork_version(&self, epoch: Epoch) -> [u8; 4] { if epoch < self.epoch { return self.previous_version; @@ -66,10 +66,10 @@ mod tests { ssz_tests!(Fork); cached_tree_hash_tests!(Fork); + // FIXME(sproul): dunno fn test_genesis(version: u32, epoch: Epoch) { let mut spec = ChainSpec::foundation(); - spec.genesis_fork_version = version; spec.genesis_epoch = epoch; let fork = Fork::genesis(&spec); diff --git a/eth2/types/src/indexed_attestation.rs b/eth2/types/src/indexed_attestation.rs index 5b56f34df..390dc082e 100644 --- a/eth2/types/src/indexed_attestation.rs +++ b/eth2/types/src/indexed_attestation.rs @@ -1,4 +1,4 @@ -use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield, ChainSpec}; +use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield}; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; @@ -37,99 +37,88 @@ pub struct IndexedAttestation { impl IndexedAttestation { /// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target. /// - /// Spec v0.5.1 - pub fn is_double_vote(&self, other: &IndexedAttestation, spec: &ChainSpec) -> bool { - self.data.slot.epoch(spec.slots_per_epoch) == other.data.slot.epoch(spec.slots_per_epoch) + /// Spec v0.6.1 + pub fn is_double_vote(&self, other: &IndexedAttestation) -> bool { + self.data.target_epoch == other.data.target_epoch && self.data != other.data } /// Check if ``attestation_data_1`` surrounds ``attestation_data_2``. /// - /// Spec v0.5.1 - pub fn is_surround_vote(&self, other: &IndexedAttestation, spec: &ChainSpec) -> bool { - let source_epoch_1 = self.data.source_epoch; - let source_epoch_2 = other.data.source_epoch; - let target_epoch_1 = self.data.slot.epoch(spec.slots_per_epoch); - let target_epoch_2 = other.data.slot.epoch(spec.slots_per_epoch); - - (source_epoch_1 < source_epoch_2) & (target_epoch_2 < target_epoch_1) + /// Spec v0.6.1 + pub fn is_surround_vote(&self, other: &IndexedAttestation) -> bool { + self.data.source_epoch < other.data.source_epoch + && other.data.target_epoch < self.data.target_epoch } } #[cfg(test)] mod tests { use super::*; - use crate::chain_spec::ChainSpec; - use crate::slot_epoch::{Epoch, Slot}; + use crate::slot_epoch::Epoch; use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; #[test] pub fn test_is_double_vote_true() { - let spec = ChainSpec::foundation(); - let indexed_vote_first = create_indexed_attestation(1, 1, &spec); - let indexed_vote_second = create_indexed_attestation(1, 1, &spec); + let indexed_vote_first = create_indexed_attestation(1, 1); + let indexed_vote_second = create_indexed_attestation(1, 1); assert_eq!( - indexed_vote_first.is_double_vote(&indexed_vote_second, &spec), + indexed_vote_first.is_double_vote(&indexed_vote_second), true ) } #[test] pub fn test_is_double_vote_false() { - let spec = ChainSpec::foundation(); - let indexed_vote_first = create_indexed_attestation(1, 1, &spec); - let indexed_vote_second = create_indexed_attestation(2, 1, &spec); + let indexed_vote_first = create_indexed_attestation(1, 1); + let indexed_vote_second = create_indexed_attestation(2, 1); assert_eq!( - indexed_vote_first.is_double_vote(&indexed_vote_second, &spec), + indexed_vote_first.is_double_vote(&indexed_vote_second), false ); } #[test] pub fn test_is_surround_vote_true() { - let spec = ChainSpec::foundation(); - let indexed_vote_first = create_indexed_attestation(2, 1, &spec); - let indexed_vote_second = create_indexed_attestation(1, 2, &spec); + let indexed_vote_first = create_indexed_attestation(2, 1); + let indexed_vote_second = create_indexed_attestation(1, 2); assert_eq!( - indexed_vote_first.is_surround_vote(&indexed_vote_second, &spec), + indexed_vote_first.is_surround_vote(&indexed_vote_second), true ); } #[test] pub fn test_is_surround_vote_true_realistic() { - let spec = ChainSpec::foundation(); - let indexed_vote_first = create_indexed_attestation(4, 1, &spec); - let indexed_vote_second = create_indexed_attestation(3, 2, &spec); + let indexed_vote_first = create_indexed_attestation(4, 1); + let indexed_vote_second = create_indexed_attestation(3, 2); assert_eq!( - indexed_vote_first.is_surround_vote(&indexed_vote_second, &spec), + indexed_vote_first.is_surround_vote(&indexed_vote_second), true ); } #[test] pub fn test_is_surround_vote_false_source_epoch_fails() { - let spec = ChainSpec::foundation(); - let indexed_vote_first = create_indexed_attestation(2, 2, &spec); - let indexed_vote_second = create_indexed_attestation(1, 1, &spec); + let indexed_vote_first = create_indexed_attestation(2, 2); + let indexed_vote_second = create_indexed_attestation(1, 1); assert_eq!( - indexed_vote_first.is_surround_vote(&indexed_vote_second, &spec), + indexed_vote_first.is_surround_vote(&indexed_vote_second), false ); } #[test] pub fn test_is_surround_vote_false_target_epoch_fails() { - let spec = ChainSpec::foundation(); - let indexed_vote_first = create_indexed_attestation(1, 1, &spec); - let indexed_vote_second = create_indexed_attestation(2, 2, &spec); + let indexed_vote_first = create_indexed_attestation(1, 1); + let indexed_vote_second = create_indexed_attestation(2, 2); assert_eq!( - indexed_vote_first.is_surround_vote(&indexed_vote_second, &spec), + indexed_vote_first.is_surround_vote(&indexed_vote_second), false ); } @@ -137,16 +126,12 @@ mod tests { ssz_tests!(IndexedAttestation); cached_tree_hash_tests!(IndexedAttestation); - fn create_indexed_attestation( - slot_factor: u64, - source_epoch: u64, - spec: &ChainSpec, - ) -> IndexedAttestation { + fn create_indexed_attestation(target_epoch: u64, source_epoch: u64) -> IndexedAttestation { let mut rng = XorShiftRng::from_seed([42; 16]); let mut indexed_vote = IndexedAttestation::random_for_test(&mut rng); - indexed_vote.data.slot = Slot::new(slot_factor * spec.slots_per_epoch); indexed_vote.data.source_epoch = Epoch::new(source_epoch); + indexed_vote.data.target_epoch = Epoch::new(target_epoch); indexed_vote } } diff --git a/eth2/types/src/pending_attestation.rs b/eth2/types/src/pending_attestation.rs index e2ad23716..b40e9b10d 100644 --- a/eth2/types/src/pending_attestation.rs +++ b/eth2/types/src/pending_attestation.rs @@ -1,5 +1,5 @@ use crate::test_utils::TestRandom; -use crate::{Attestation, AttestationData, Bitfield, Slot}; +use crate::{Attestation, AttestationData, Bitfield}; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode}; @@ -8,7 +8,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; /// An attestation that has been included in the state but not yet fully processed. /// -/// Spec v0.6.0 +/// Spec v0.6.1 #[derive( Debug, Clone, @@ -24,7 +24,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash}; pub struct PendingAttestation { pub aggregation_bitfield: Bitfield, pub data: AttestationData, - pub inclusion_slot: Slot, + pub inclusion_delay: u64, pub proposer_index: u64, } @@ -32,13 +32,13 @@ impl PendingAttestation { /// Create a `PendingAttestation` from an `Attestation`. pub fn from_attestation( attestation: &Attestation, - inclusion_slot: Slot, + inclusion_delay: u64, proposer_index: u64, ) -> Self { PendingAttestation { data: attestation.data.clone(), aggregation_bitfield: attestation.aggregation_bitfield.clone(), - inclusion_slot, + inclusion_delay, proposer_index, } } diff --git a/eth2/types/src/test_utils/testing_attestation_builder.rs b/eth2/types/src/test_utils/testing_attestation_builder.rs index b41bf6f8d..ab5cfa97f 100644 --- a/eth2/types/src/test_utils/testing_attestation_builder.rs +++ b/eth2/types/src/test_utils/testing_attestation_builder.rs @@ -77,7 +77,7 @@ impl TestingAttestationBuilder { .tree_hash_root(); let domain = spec.get_domain( - self.attestation.data.slot.epoch(spec.slots_per_epoch), + self.attestation.data.target_epoch, Domain::Attestation, fork, ); diff --git a/eth2/types/src/test_utils/testing_attestation_data_builder.rs b/eth2/types/src/test_utils/testing_attestation_data_builder.rs index 9fde5202c..13668380a 100644 --- a/eth2/types/src/test_utils/testing_attestation_data_builder.rs +++ b/eth2/types/src/test_utils/testing_attestation_data_builder.rs @@ -23,6 +23,12 @@ impl TestingAttestationDataBuilder { state.current_justified_epoch }; + let target_epoch = if is_previous_epoch { + state.previous_epoch(spec) + } else { + state.current_epoch(spec) + }; + let target_root = if is_previous_epoch { *state .get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec) @@ -39,12 +45,12 @@ impl TestingAttestationDataBuilder { let data = AttestationData { // LMD GHOST vote - slot, beacon_block_root: *state.get_block_root(slot, spec).unwrap(), // FFG Vote source_epoch, source_root, + target_epoch, target_root, // Crosslink vote diff --git a/eth2/types/src/test_utils/testing_attester_slashing_builder.rs b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs index 91615d258..9a8c145de 100644 --- a/eth2/types/src/test_utils/testing_attester_slashing_builder.rs +++ b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs @@ -21,17 +21,17 @@ impl TestingAttesterSlashingBuilder { where F: Fn(u64, &[u8], Epoch, Domain) -> Signature, { - let double_voted_slot = Slot::new(0); let shard = 0; - let epoch = Epoch::new(0); + let epoch_1 = Epoch::new(1); + let epoch_2 = Epoch::new(2); let hash_1 = Hash256::from_low_u64_le(1); let hash_2 = Hash256::from_low_u64_le(2); let data_1 = AttestationData { - slot: double_voted_slot, beacon_block_root: hash_1, - source_epoch: epoch, + source_epoch: epoch_1, source_root: hash_1, + target_epoch: epoch_2, target_root: hash_1, shard, previous_crosslink_root: hash_1, @@ -69,7 +69,8 @@ impl TestingAttesterSlashingBuilder { for (i, validator_index) in validator_indices.iter().enumerate() { attestation.custody_bitfield.set(i, false); - let signature = signer(*validator_index, &message[..], epoch, Domain::Attestation); + let signature = + signer(*validator_index, &message[..], epoch_2, Domain::Attestation); attestation.signature.add(&signature); } }; diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 7987a256f..497186841 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -246,7 +246,7 @@ impl TestingBeaconStateBuilder { builder.add_committee_participation(signers); let attestation = builder.build(); - if attestation.data.slot.epoch(spec.slots_per_epoch) < state.current_epoch(spec) { + if attestation.data.target_epoch < state.current_epoch(spec) { state.previous_epoch_attestations.push(attestation) } else { state.current_epoch_attestations.push(attestation) diff --git a/eth2/types/src/test_utils/testing_pending_attestation_builder.rs b/eth2/types/src/test_utils/testing_pending_attestation_builder.rs index 831cda0f8..4b282851c 100644 --- a/eth2/types/src/test_utils/testing_pending_attestation_builder.rs +++ b/eth2/types/src/test_utils/testing_pending_attestation_builder.rs @@ -11,8 +11,7 @@ pub struct TestingPendingAttestationBuilder { impl TestingPendingAttestationBuilder { /// Create a new valid* `PendingAttestation` for the given parameters. /// - /// The `inclusion_slot` will be set to be the earliest possible slot the `Attestation` could - /// have been included (`slot + MIN_ATTESTATION_INCLUSION_DELAY`). + /// The `inclusion_delay` will be set to `MIN_ATTESTATION_INCLUSION_DELAY`. /// /// * The aggregation and custody bitfields will all be empty, they need to be set with /// `Self::add_committee_participation`. @@ -22,7 +21,7 @@ impl TestingPendingAttestationBuilder { let pending_attestation = PendingAttestation { aggregation_bitfield: Bitfield::new(), data: data_builder.build(), - inclusion_slot: slot + spec.min_attestation_inclusion_delay, + inclusion_delay: spec.min_attestation_inclusion_delay, // FIXME(sproul) proposer_index: 0, }; From ca73fb72da36712b994ae9ae96f7f70b0c6847eb Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 13 May 2019 17:15:30 +1000 Subject: [PATCH 10/22] state_processing: get_attesting_indices --- .../get_attestation_participants.rs | 38 ------------------- .../get_attesting_indices.rs | 32 ++++++++++++++++ 2 files changed, 32 insertions(+), 38 deletions(-) delete mode 100644 eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs create mode 100644 eth2/state_processing/src/per_epoch_processing/get_attesting_indices.rs diff --git a/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs b/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs deleted file mode 100644 index bea772204..000000000 --- a/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::common::verify_bitfield_length; -use types::*; - -/// Returns validator indices which participated in the attestation. -/// -/// Spec v0.5.1 -pub fn get_attestation_participants( - state: &BeaconState, - attestation_data: &AttestationData, - bitfield: &Bitfield, - spec: &ChainSpec, -) -> Result, BeaconStateError> { - let epoch = attestation_data.slot.epoch(spec.slots_per_epoch); - - let crosslink_committee = - state.get_crosslink_committee_for_shard(epoch, attestation_data.shard, spec)?; - - if crosslink_committee.slot != attestation_data.slot { - return Err(BeaconStateError::NoCommitteeForShard); - } - - let committee = &crosslink_committee.committee; - - if !verify_bitfield_length(&bitfield, committee.len()) { - return Err(BeaconStateError::InvalidBitfield); - } - - let mut participants = Vec::with_capacity(committee.len()); - for (i, validator_index) in committee.iter().enumerate() { - match bitfield.get(i) { - Ok(bit) if bit => participants.push(*validator_index), - _ => {} - } - } - participants.shrink_to_fit(); - - Ok(participants) -} diff --git a/eth2/state_processing/src/per_epoch_processing/get_attesting_indices.rs b/eth2/state_processing/src/per_epoch_processing/get_attesting_indices.rs new file mode 100644 index 000000000..181aedae1 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/get_attesting_indices.rs @@ -0,0 +1,32 @@ +use crate::common::verify_bitfield_length; +use types::*; + +/// Returns validator indices which participated in the attestation. +/// +/// Spec v0.6.1 +pub fn get_attesting_indices_unsorted( + state: &BeaconState, + attestation_data: &AttestationData, + bitfield: &Bitfield, + spec: &ChainSpec, +) -> Result, BeaconStateError> { + let committee = state.get_crosslink_committee( + attestation_data.target_epoch, + attestation_data.shard, + spec, + )?; + + if !verify_bitfield_length(&bitfield, committee.committee.len()) { + return Err(BeaconStateError::InvalidBitfield); + } + + Ok(committee + .committee + .iter() + .enumerate() + .filter_map(|(i, validator_index)| match bitfield.get(i) { + Ok(true) => Some(*validator_index), + _ => None, + }) + .collect()) +} From fd56c8fa049691ecd45dad565eeb0a5159f314d3 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 13 May 2019 17:18:21 +1000 Subject: [PATCH 11/22] state_processing: winning_root v0.6.1 --- .../validator_statuses.rs | 45 +++-- .../src/per_epoch_processing/winning_root.rs | 163 +++++++++--------- 2 files changed, 97 insertions(+), 111 deletions(-) diff --git a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs index 633dcc4e2..2046e70a5 100644 --- a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs +++ b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs @@ -1,4 +1,4 @@ -use super::get_attestation_participants::get_attestation_participants; +use super::get_attesting_indices::get_attesting_indices_unsorted; use super::WinningRootHashSet; use types::*; @@ -29,7 +29,7 @@ pub struct InclusionInfo { pub slot: Slot, /// The distance between the attestation slot and the slot that attestation was included in a /// block. - pub distance: Slot, + pub distance: u64, /// The index of the proposer at the slot where the attestation was included. pub proposer_index: usize, } @@ -39,7 +39,7 @@ impl Default for InclusionInfo { fn default() -> Self { Self { slot: Slot::max_value(), - distance: Slot::max_value(), + distance: u64::max_value(), proposer_index: 0, } } @@ -199,7 +199,7 @@ impl ValidatorStatuses { /// Process some attestations from the given `state` updating the `statuses` and /// `total_balances` fields. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn process_attestations( &mut self, state: &BeaconState, @@ -211,28 +211,30 @@ impl ValidatorStatuses { .chain(state.current_epoch_attestations.iter()) { let attesting_indices = - get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?; + get_attesting_indices_unsorted(state, &a.data, &a.aggregation_bitfield, spec)?; let mut status = ValidatorStatus::default(); // Profile this attestation, updating the total balances and generating an // `ValidatorStatus` object that applies to all participants in the attestation. - if is_from_epoch(a, state.current_epoch(spec), spec) { + if is_from_epoch(a, state.current_epoch(spec)) { status.is_current_epoch_attester = true; if target_matches_epoch_start_block(a, state, state.current_epoch(spec), spec)? { status.is_current_epoch_target_attester = true; } - } else if is_from_epoch(a, state.previous_epoch(spec), spec) { + } else if is_from_epoch(a, state.previous_epoch(spec)) { status.is_previous_epoch_attester = true; // The inclusion slot and distance are only required for previous epoch attesters. - let relative_epoch = RelativeEpoch::from_slot(state.slot, a.inclusion_slot, spec)?; + let attestation_slot = state.get_attestation_slot(&a.data, spec)?; + let inclusion_slot = attestation_slot + a.inclusion_delay; + let relative_epoch = RelativeEpoch::from_slot(state.slot, inclusion_slot, spec)?; status.inclusion_info = Some(InclusionInfo { - slot: a.inclusion_slot, - distance: inclusion_distance(a), + slot: inclusion_slot, + distance: a.inclusion_delay, proposer_index: state.get_beacon_proposer_index( - a.inclusion_slot, + attestation_slot, relative_epoch, spec, )?, @@ -316,25 +318,17 @@ impl ValidatorStatuses { } } -/// Returns the distance between when the attestation was created and when it was included in a -/// block. -/// -/// Spec v0.5.1 -fn inclusion_distance(a: &PendingAttestation) -> Slot { - a.inclusion_slot - a.data.slot -} - /// Returns `true` if some `PendingAttestation` is from the supplied `epoch`. /// -/// Spec v0.5.1 -fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool { - a.data.slot.epoch(spec.slots_per_epoch) == epoch +/// Spec v0.6.1 +fn is_from_epoch(a: &PendingAttestation, epoch: Epoch) -> bool { + a.data.target_epoch == epoch } /// Returns `true` if the attestation's FFG target is equal to the hash of the `state`'s first /// beacon block in the given `epoch`. /// -/// Spec v0.6.0 +/// Spec v0.6.1 fn target_matches_epoch_start_block( a: &PendingAttestation, state: &BeaconState, @@ -350,13 +344,14 @@ fn target_matches_epoch_start_block( /// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for /// the current slot of the `PendingAttestation`. /// -/// Spec v0.6.0 +/// Spec v0.6.1 fn has_common_beacon_block_root( a: &PendingAttestation, state: &BeaconState, spec: &ChainSpec, ) -> Result { - let state_block_root = *state.get_block_root(a.data.slot, spec)?; + let attestation_slot = state.get_attestation_slot(&a.data, spec)?; + let state_block_root = *state.get_block_root(attestation_slot, spec)?; Ok(a.data.beacon_block_root == state_block_root) } diff --git a/eth2/state_processing/src/per_epoch_processing/winning_root.rs b/eth2/state_processing/src/per_epoch_processing/winning_root.rs index 5d31dff31..1c254d961 100644 --- a/eth2/state_processing/src/per_epoch_processing/winning_root.rs +++ b/eth2/state_processing/src/per_epoch_processing/winning_root.rs @@ -1,11 +1,11 @@ -use super::get_attestation_participants::get_attestation_participants; -use std::collections::HashSet; -use std::iter::FromIterator; +use super::get_attesting_indices::get_attesting_indices_unsorted; +use std::collections::{HashMap, HashSet}; +use tree_hash::TreeHash; use types::*; #[derive(Clone)] pub struct WinningRoot { - pub crosslink_data_root: Hash256, + pub crosslink: Crosslink, pub attesting_validator_indices: Vec, pub total_attesting_balance: u64, } @@ -16,15 +16,15 @@ impl WinningRoot { /// A winning root is "better" than another if it has a higher `total_attesting_balance`. Ties /// are broken by favouring the higher `crosslink_data_root` value. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn is_better_than(&self, other: &Self) -> bool { - if self.total_attesting_balance > other.total_attesting_balance { - true - } else if self.total_attesting_balance == other.total_attesting_balance { - self.crosslink_data_root > other.crosslink_data_root - } else { - false - } + ( + self.total_attesting_balance, + self.crosslink.crosslink_data_root, + ) > ( + other.total_attesting_balance, + other.crosslink.crosslink_data_root, + ) } } @@ -34,43 +34,55 @@ impl WinningRoot { /// The `WinningRoot` object also contains additional fields that are useful in later stages of /// per-epoch processing. /// -/// Spec v0.5.1 +/// Spec v0.6.1 pub fn winning_root( state: &BeaconState, shard: u64, + epoch: Epoch, spec: &ChainSpec, ) -> Result, BeaconStateError> { - let mut winning_root: Option = None; + let shard_attestations: Vec<&PendingAttestation> = state + .get_matching_source_attestations(epoch, spec)? + .iter() + .filter(|a| a.data.shard == shard) + .collect(); - let crosslink_data_roots: HashSet = HashSet::from_iter( - state - .previous_epoch_attestations - .iter() - .chain(state.current_epoch_attestations.iter()) - .filter_map(|a| { - if is_eligible_for_winning_root(state, a, shard) { - Some(a.data.crosslink_data_root) - } else { - None - } - }), - ); + let shard_crosslinks = shard_attestations.iter().map(|att| { + ( + att, + state.get_crosslink_from_attestation_data(&att.data, spec), + ) + }); - for crosslink_data_root in crosslink_data_roots { + let current_shard_crosslink_root = state.current_crosslinks[shard as usize].tree_hash_root(); + let candidate_crosslinks = shard_crosslinks.filter(|(_, c)| { + c.previous_crosslink_root.as_bytes() == ¤t_shard_crosslink_root[..] + || c.tree_hash_root() == current_shard_crosslink_root + }); + + // Build a map from candidate crosslink to attestations that support that crosslink. + let mut candidate_crosslink_map: HashMap> = HashMap::new(); + + for (&attestation, crosslink) in candidate_crosslinks { + let supporting_attestations = candidate_crosslink_map + .entry(crosslink) + .or_insert_with(Vec::new); + supporting_attestations.push(attestation); + } + + if candidate_crosslink_map.is_empty() { + return Ok(None); + } + + let mut winning_root = None; + for (crosslink, attestations) in candidate_crosslink_map { let attesting_validator_indices = - get_attesting_validator_indices(state, shard, &crosslink_data_root, spec)?; - - let total_attesting_balance: u64 = - attesting_validator_indices - .iter() - .try_fold(0_u64, |acc, i| { - state - .get_effective_balance(*i, spec) - .and_then(|bal| Ok(acc + bal)) - })?; + get_unslashed_attesting_indices_unsorted(state, &attestations, spec)?; + let total_attesting_balance = + state.get_total_balance(&attesting_validator_indices, spec)?; let candidate = WinningRoot { - crosslink_data_root, + crosslink, attesting_validator_indices, total_attesting_balance, }; @@ -87,52 +99,29 @@ pub fn winning_root( Ok(winning_root) } -/// Returns `true` if pending attestation `a` is eligible to become a winning root. -/// -/// Spec v0.5.1 -fn is_eligible_for_winning_root(state: &BeaconState, a: &PendingAttestation, shard: Shard) -> bool { - if shard >= state.latest_crosslinks.len() as u64 { - return false; - } - - a.data.previous_crosslink == state.latest_crosslinks[shard as usize] -} - -/// Returns all indices which voted for a given crosslink. Does not contain duplicates. -/// -/// Spec v0.5.1 -fn get_attesting_validator_indices( +pub fn get_unslashed_attesting_indices_unsorted( state: &BeaconState, - shard: u64, - crosslink_data_root: &Hash256, + attestations: &[&PendingAttestation], spec: &ChainSpec, ) -> Result, BeaconStateError> { - let mut indices = vec![]; - - for a in state - .current_epoch_attestations - .iter() - .chain(state.previous_epoch_attestations.iter()) - { - if (a.data.shard == shard) && (a.data.crosslink_data_root == *crosslink_data_root) { - indices.append(&mut get_attestation_participants( - state, - &a.data, - &a.aggregation_bitfield, - spec, - )?); - } + let mut output = HashSet::new(); + for a in attestations { + output.extend(get_attesting_indices_unsorted( + state, + &a.data, + &a.aggregation_bitfield, + spec, + )?); } - - // Sort the list (required for dedup). "Unstable" means the sort may re-order equal elements, - // this causes no issue here. - // - // These sort + dedup ops are potentially good CPU time optimisation targets. - indices.sort_unstable(); - // Remove all duplicate indices (requires a sorted list). - indices.dedup(); - - Ok(indices) + Ok(output + .into_iter() + .filter(|index| { + state + .validator_registry + .get(*index) + .map_or(false, |v| !v.slashed) + }) + .collect()) } #[cfg(test)] @@ -142,15 +131,17 @@ mod tests { #[test] fn is_better_than() { let worse = WinningRoot { - crosslink_data_root: Hash256::from_slice(&[1; 32]), + crosslink: Crosslink { + epoch: Epoch::new(0), + previous_crosslink_root: Hash256::from_slice(&[0; 32]), + crosslink_data_root: Hash256::from_slice(&[1; 32]), + }, attesting_validator_indices: vec![], total_attesting_balance: 42, }; - let better = WinningRoot { - crosslink_data_root: Hash256::from_slice(&[2; 32]), - ..worse.clone() - }; + let mut better = worse.clone(); + better.crosslink.crosslink_data_root = Hash256::from_slice(&[2; 32]); assert!(better.is_better_than(&worse)); From 92e88b1b7554635c5c2a771af4e67223a9d21273 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 13 May 2019 17:18:42 +1000 Subject: [PATCH 12/22] spec: apply_rewards tweaks --- .../src/per_epoch_processing/apply_rewards.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs b/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs index bc02b67a3..2ae35f976 100644 --- a/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs +++ b/eth2/state_processing/src/per_epoch_processing/apply_rewards.rs @@ -1,7 +1,3 @@ -use super::common::{ - get_attesting_balance, get_matching_head_attestations, get_matching_target_attestations, - get_total_active_balance, get_unslashed_attesting_indices, -}; use super::validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses}; use super::{Error, WinningRootHashSet}; use integer_sqrt::IntegerSquareRoot; @@ -181,9 +177,7 @@ fn get_attestation_delta( let inclusion = validator .inclusion_info .expect("It is a logic error for an attester not to have an inclusion distance."); - delta.reward( - base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(), - ); + delta.reward(base_reward * spec.min_attestation_inclusion_delay / inclusion.distance); } else { delta.penalize(base_reward); } From bc03e14943d7c70ee8c9240f98a6f0a0f99a4116 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 13 May 2019 17:21:17 +1000 Subject: [PATCH 13/22] validator_client: IndexedAttestation rename --- validator_client/src/attestation_producer/mod.rs | 4 ++-- validator_client/src/block_producer/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/validator_client/src/attestation_producer/mod.rs b/validator_client/src/attestation_producer/mod.rs index d2dbdf2e2..c8c80f78c 100644 --- a/validator_client/src/attestation_producer/mod.rs +++ b/validator_client/src/attestation_producer/mod.rs @@ -52,7 +52,7 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { Ok(ValidatorEvent::SignerRejection(_slot)) => { error!(log, "Attestation production error"; "Error" => "Signer could not sign the attestation".to_string()) } - Ok(ValidatorEvent::SlashableAttestationNotProduced(_slot)) => { + Ok(ValidatorEvent::IndexedAttestationNotProduced(_slot)) => { error!(log, "Attestation production error"; "Error" => "Rejected the attestation as it could have been slashed".to_string()) } Ok(ValidatorEvent::PublishAttestationFailed) => { @@ -99,7 +99,7 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> { Ok(ValidatorEvent::SignerRejection(self.duty.slot)) } } else { - Ok(ValidatorEvent::SlashableAttestationNotProduced( + Ok(ValidatorEvent::IndexedAttestationNotProduced( self.duty.slot, )) } diff --git a/validator_client/src/block_producer/mod.rs b/validator_client/src/block_producer/mod.rs index 2689b302d..61e9d1a08 100644 --- a/validator_client/src/block_producer/mod.rs +++ b/validator_client/src/block_producer/mod.rs @@ -24,7 +24,7 @@ pub enum ValidatorEvent { /// A block was not produced as it would have been slashable. SlashableBlockNotProduced(Slot), /// An attestation was not produced as it would have been slashable. - SlashableAttestationNotProduced(Slot), + IndexedAttestationNotProduced(Slot), /// The Beacon Node was unable to produce a block at that slot. BeaconNodeUnableToProduceBlock(Slot), /// The signer failed to sign the message. From 4f138fa3fe14d00ae2b8b522ad6c4dca5c901dc1 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 13 May 2019 17:28:04 +1000 Subject: [PATCH 14/22] state_processing: IndexedAttestation rename --- .../src/per_block_processing.rs | 32 ++++++------- .../src/per_block_processing/errors.rs | 30 ++++++------ .../verify_attester_slashing.rs | 44 +++++++++--------- ...ation.rs => verify_indexed_attestation.rs} | 46 +++++++++---------- 4 files changed, 75 insertions(+), 77 deletions(-) rename eth2/state_processing/src/per_block_processing/{verify_slashable_attestation.rs => verify_indexed_attestation.rs} (56%) diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 58b948f62..9b89c055e 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -15,7 +15,7 @@ pub use validate_attestation::{ }; pub use verify_deposit::{get_existing_validator_index, verify_deposit, verify_deposit_index}; pub use verify_exit::{verify_exit, verify_exit_time_independent_only}; -pub use verify_slashable_attestation::verify_slashable_attestation; +pub use verify_indexed_attestation::verify_indexed_attestation; pub use verify_transfer::{ execute_transfer, verify_transfer, verify_transfer_time_independent_only, }; @@ -25,8 +25,8 @@ mod validate_attestation; mod verify_attester_slashing; mod verify_deposit; mod verify_exit; +mod verify_indexed_attestation; mod verify_proposer_slashing; -mod verify_slashable_attestation; mod verify_transfer; // Set to `true` to check the merkle proof that a deposit is in the eth1 deposit root. @@ -253,41 +253,41 @@ pub fn process_attester_slashings( Invalid::MaxAttesterSlashingsExceed ); - // Verify the `SlashableAttestation`s in parallel (these are the resource-consuming objects, not + // Verify the `IndexedAttestation`s in parallel (these are the resource-consuming objects, not // the `AttesterSlashing`s themselves). - let mut slashable_attestations: Vec<&SlashableAttestation> = + let mut indexed_attestations: Vec<&IndexedAttestation> = Vec::with_capacity(attester_slashings.len() * 2); for attester_slashing in attester_slashings { - slashable_attestations.push(&attester_slashing.slashable_attestation_1); - slashable_attestations.push(&attester_slashing.slashable_attestation_2); + indexed_attestations.push(&attester_slashing.attestation_1); + indexed_attestations.push(&attester_slashing.attestation_2); } - // Verify slashable attestations in parallel. - slashable_attestations + // Verify indexed attestations in parallel. + indexed_attestations .par_iter() .enumerate() - .try_for_each(|(i, slashable_attestation)| { - verify_slashable_attestation(&state, slashable_attestation, spec) + .try_for_each(|(i, indexed_attestation)| { + verify_indexed_attestation(&state, indexed_attestation, spec) .map_err(|e| e.into_with_index(i)) })?; - let all_slashable_attestations_have_been_checked = true; + let all_indexed_attestations_have_been_checked = true; - // Gather the slashable indices and preform the final verification and update the state in series. + // Gather the indexed indices and preform the final verification and update the state in series. for (i, attester_slashing) in attester_slashings.iter().enumerate() { - let should_verify_slashable_attestations = !all_slashable_attestations_have_been_checked; + let should_verify_indexed_attestations = !all_indexed_attestations_have_been_checked; verify_attester_slashing( &state, &attester_slashing, - should_verify_slashable_attestations, + should_verify_indexed_attestations, spec, ) .map_err(|e| e.into_with_index(i))?; - let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing, spec) + let indexed_indices = gather_attester_slashing_indices(&state, &attester_slashing, spec) .map_err(|e| e.into_with_index(i))?; - for i in slashable_indices { + for i in indexed_indices { slash_validator(state, i as usize, spec)?; } } diff --git a/eth2/state_processing/src/per_block_processing/errors.rs b/eth2/state_processing/src/per_block_processing/errors.rs index d8627d359..6c21d37a5 100644 --- a/eth2/state_processing/src/per_block_processing/errors.rs +++ b/eth2/state_processing/src/per_block_processing/errors.rs @@ -80,10 +80,10 @@ pub enum BlockInvalid { MaxExitsExceeded, MaxTransfersExceed, AttestationInvalid(usize, AttestationInvalid), - /// A `SlashableAttestation` inside an `AttesterSlashing` was invalid. + /// A `IndexedAttestation` inside an `AttesterSlashing` was invalid. /// /// To determine the offending `AttesterSlashing` index, divide the error message `usize` by two. - SlashableAttestationInvalid(usize, SlashableAttestationInvalid), + IndexedAttestationInvalid(usize, IndexedAttestationInvalid), AttesterSlashingInvalid(usize, AttesterSlashingInvalid), ProposerSlashingInvalid(usize, ProposerSlashingInvalid), DepositInvalid(usize, DepositInvalid), @@ -194,10 +194,10 @@ pub enum AttesterSlashingInvalid { AttestationDataIdentical, /// The attestations were not in conflict. NotSlashable, - /// The first `SlashableAttestation` was invalid. - SlashableAttestation1Invalid(SlashableAttestationInvalid), - /// The second `SlashableAttestation` was invalid. - SlashableAttestation2Invalid(SlashableAttestationInvalid), + /// The first `IndexedAttestation` was invalid. + IndexedAttestation1Invalid(IndexedAttestationInvalid), + /// The second `IndexedAttestation` was invalid. + IndexedAttestation2Invalid(IndexedAttestationInvalid), /// The validator index is unknown. One cannot slash one who does not exist. UnknownValidator(u64), /// The specified validator has already been withdrawn. @@ -210,19 +210,19 @@ impl_from_beacon_state_error!(AttesterSlashingValidationError); impl_into_with_index_with_beacon_error!(AttesterSlashingValidationError, AttesterSlashingInvalid); /* - * `SlashableAttestation` Validation + * `IndexedAttestation` Validation */ /// The object is invalid or validation failed. #[derive(Debug, PartialEq)] -pub enum SlashableAttestationValidationError { +pub enum IndexedAttestationValidationError { /// Validation completed successfully and the object is invalid. - Invalid(SlashableAttestationInvalid), + Invalid(IndexedAttestationInvalid), } /// Describes why an object is invalid. #[derive(Debug, PartialEq)] -pub enum SlashableAttestationInvalid { +pub enum IndexedAttestationInvalid { /// The custody bitfield has some bits set `true`. This is not allowed in phase 0. CustodyBitfieldHasSetBits, /// No validator indices were specified. @@ -245,17 +245,17 @@ pub enum SlashableAttestationInvalid { BadSignature, } -impl Into for SlashableAttestationValidationError { - fn into(self) -> SlashableAttestationInvalid { +impl Into for IndexedAttestationValidationError { + fn into(self) -> IndexedAttestationInvalid { match self { - SlashableAttestationValidationError::Invalid(e) => e, + IndexedAttestationValidationError::Invalid(e) => e, } } } impl_into_with_index_without_beacon_error!( - SlashableAttestationValidationError, - SlashableAttestationInvalid + IndexedAttestationValidationError, + IndexedAttestationInvalid ); /* diff --git a/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs b/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs index 3527b62e3..bd04e7e02 100644 --- a/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs +++ b/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs @@ -1,5 +1,5 @@ use super::errors::{AttesterSlashingInvalid as Invalid, AttesterSlashingValidationError as Error}; -use super::verify_slashable_attestation::verify_slashable_attestation; +use super::verify_indexed_attestation::verify_indexed_attestation; use types::*; /// Indicates if an `AttesterSlashing` is valid to be included in a block in the current epoch of the given @@ -11,27 +11,27 @@ use types::*; pub fn verify_attester_slashing( state: &BeaconState, attester_slashing: &AttesterSlashing, - should_verify_slashable_attestations: bool, + should_verify_indexed_attestations: bool, spec: &ChainSpec, ) -> Result<(), Error> { - let slashable_attestation_1 = &attester_slashing.slashable_attestation_1; - let slashable_attestation_2 = &attester_slashing.slashable_attestation_2; + let indexed_attestation_1 = &attester_slashing.indexed_attestation_1; + let indexed_attestation_2 = &attester_slashing.indexed_attestation_2; verify!( - slashable_attestation_1.data != slashable_attestation_2.data, + indexed_attestation_1.data != indexed_attestation_2.data, Invalid::AttestationDataIdentical ); verify!( - slashable_attestation_1.is_double_vote(slashable_attestation_2, spec) - | slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec), + indexed_attestation_1.is_double_vote(indexed_attestation_2, spec) + | indexed_attestation_1.is_surround_vote(indexed_attestation_2, spec), Invalid::NotSlashable ); - if should_verify_slashable_attestations { - verify_slashable_attestation(state, &slashable_attestation_1, spec) - .map_err(|e| Error::Invalid(Invalid::SlashableAttestation1Invalid(e.into())))?; - verify_slashable_attestation(state, &slashable_attestation_2, spec) - .map_err(|e| Error::Invalid(Invalid::SlashableAttestation2Invalid(e.into())))?; + if should_verify_indexed_attestations { + verify_indexed_attestation(state, &indexed_attestation_1, spec) + .map_err(|e| Error::Invalid(Invalid::IndexedAttestation1Invalid(e.into())))?; + verify_indexed_attestation(state, &indexed_attestation_2, spec) + .map_err(|e| Error::Invalid(Invalid::IndexedAttestation2Invalid(e.into())))?; } Ok(()) @@ -66,31 +66,31 @@ pub fn gather_attester_slashing_indices_modular( where F: Fn(u64, &Validator) -> bool, { - let slashable_attestation_1 = &attester_slashing.slashable_attestation_1; - let slashable_attestation_2 = &attester_slashing.slashable_attestation_2; + let indexed_attestation_1 = &attester_slashing.indexed_attestation_1; + let indexed_attestation_2 = &attester_slashing.indexed_attestation_2; - let mut slashable_indices = Vec::with_capacity(spec.max_indices_per_slashable_vote); - for i in &slashable_attestation_1.validator_indices { + let mut indexed_indices = Vec::with_capacity(spec.max_indices_per_indexed_vote); + for i in &indexed_attestation_1.validator_indices { let validator = state .validator_registry .get(*i as usize) .ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?; - if slashable_attestation_2.validator_indices.contains(&i) & !is_slashed(*i, validator) { - // TODO: verify that we should reject any slashable attestation which includes a + if indexed_attestation_2.validator_indices.contains(&i) & !is_slashed(*i, validator) { + // TODO: verify that we should reject any indexed attestation which includes a // withdrawn validator. PH has asked the question on gitter, awaiting response. verify!( validator.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch), Invalid::ValidatorAlreadyWithdrawn(*i) ); - slashable_indices.push(*i); + indexed_indices.push(*i); } } - verify!(!slashable_indices.is_empty(), Invalid::NoSlashableIndices); + verify!(!indexed_indices.is_empty(), Invalid::NoSlashableIndices); - slashable_indices.shrink_to_fit(); + indexed_indices.shrink_to_fit(); - Ok(slashable_indices) + Ok(indexed_indices) } diff --git a/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs b/eth2/state_processing/src/per_block_processing/verify_indexed_attestation.rs similarity index 56% rename from eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs rename to eth2/state_processing/src/per_block_processing/verify_indexed_attestation.rs index 89cb93ce5..5599d7744 100644 --- a/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/verify_indexed_attestation.rs @@ -1,52 +1,50 @@ use super::errors::{ - SlashableAttestationInvalid as Invalid, SlashableAttestationValidationError as Error, + IndexedAttestationInvalid as Invalid, IndexedAttestationValidationError as Error, }; use crate::common::verify_bitfield_length; use tree_hash::TreeHash; use types::*; -/// Indicates if a `SlashableAttestation` is valid to be included in a block in the current epoch of the given +/// Indicates if a `IndexedAttestation` is valid to be included in a block in the current epoch of the given /// state. /// -/// Returns `Ok(())` if the `SlashableAttestation` is valid, otherwise indicates the reason for invalidity. +/// Returns `Ok(())` if the `IndexedAttestation` is valid, otherwise indicates the reason for invalidity. /// /// Spec v0.5.1 -pub fn verify_slashable_attestation( +pub fn verify_indexed_attestation( state: &BeaconState, - slashable_attestation: &SlashableAttestation, + indexed_attestation: &IndexedAttestation, spec: &ChainSpec, ) -> Result<(), Error> { - if slashable_attestation.custody_bitfield.num_set_bits() > 0 { + if indexed_attestation.custody_bitfield.num_set_bits() > 0 { invalid!(Invalid::CustodyBitfieldHasSetBits); } - if slashable_attestation.validator_indices.is_empty() { + if indexed_attestation.validator_indices.is_empty() { invalid!(Invalid::NoValidatorIndices); } - for i in 0..(slashable_attestation.validator_indices.len() - 1) { - if slashable_attestation.validator_indices[i] - >= slashable_attestation.validator_indices[i + 1] + for i in 0..(indexed_attestation.validator_indices.len() - 1) { + if indexed_attestation.validator_indices[i] >= indexed_attestation.validator_indices[i + 1] { invalid!(Invalid::BadValidatorIndicesOrdering(i)); } } if !verify_bitfield_length( - &slashable_attestation.custody_bitfield, - slashable_attestation.validator_indices.len(), + &indexed_attestation.custody_bitfield, + indexed_attestation.validator_indices.len(), ) { invalid!(Invalid::BadCustodyBitfieldLength( - slashable_attestation.validator_indices.len(), - slashable_attestation.custody_bitfield.len() + indexed_attestation.validator_indices.len(), + indexed_attestation.custody_bitfield.len() )); } - if slashable_attestation.validator_indices.len() > spec.max_indices_per_slashable_vote as usize - { + if indexed_attestation.validator_indices.len() > spec.max_indices_per_indexed_vote as usize { invalid!(Invalid::MaxIndicesExceed( - spec.max_indices_per_slashable_vote as usize, - slashable_attestation.validator_indices.len() + spec.max_indices_per_indexed_vote as usize, + indexed_attestation.validator_indices.len() )); } @@ -57,8 +55,8 @@ pub fn verify_slashable_attestation( let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2]; let mut message_exists = vec![false; 2]; - for (i, v) in slashable_attestation.validator_indices.iter().enumerate() { - let custody_bit = match slashable_attestation.custody_bitfield.get(i) { + for (i, v) in indexed_attestation.validator_indices.iter().enumerate() { + let custody_bit = match indexed_attestation.custody_bitfield.get(i) { Ok(bit) => bit, Err(_) => unreachable!(), }; @@ -74,12 +72,12 @@ pub fn verify_slashable_attestation( } let message_0 = AttestationDataAndCustodyBit { - data: slashable_attestation.data.clone(), + data: indexed_attestation.data.clone(), custody_bit: false, } .tree_hash_root(); let message_1 = AttestationDataAndCustodyBit { - data: slashable_attestation.data.clone(), + data: indexed_attestation.data.clone(), custody_bit: true, } .tree_hash_root(); @@ -97,12 +95,12 @@ pub fn verify_slashable_attestation( } let domain = { - let epoch = slashable_attestation.data.slot.epoch(spec.slots_per_epoch); + let epoch = indexed_attestation.data.slot.epoch(spec.slots_per_epoch); spec.get_domain(epoch, Domain::Attestation, &state.fork) }; verify!( - slashable_attestation + indexed_attestation .aggregate_signature .verify_multiple(&messages[..], domain, &keys[..]), Invalid::BadSignature From 2751be9a050b0e784e729a32a0864080c01f70ee Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 13 May 2019 17:28:37 +1000 Subject: [PATCH 15/22] state_processing: verify_deposit v0.6.0 --- .../per_block_processing/verify_deposit.rs | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/eth2/state_processing/src/per_block_processing/verify_deposit.rs b/eth2/state_processing/src/per_block_processing/verify_deposit.rs index 22a62a321..9b1c4822e 100644 --- a/eth2/state_processing/src/per_block_processing/verify_deposit.rs +++ b/eth2/state_processing/src/per_block_processing/verify_deposit.rs @@ -88,9 +88,9 @@ pub fn get_existing_validator_index( /// Verify that a deposit is included in the state's eth1 deposit root. /// -/// Spec v0.5.1 +/// Spec v0.6.0 fn verify_deposit_merkle_proof(state: &BeaconState, deposit: &Deposit, spec: &ChainSpec) -> bool { - let leaf = hash(&get_serialized_deposit_data(deposit)); + let leaf = deposit.data.tree_hash_root(); verify_merkle_proof( Hash256::from_slice(&leaf), &deposit.proof, @@ -99,27 +99,3 @@ fn verify_deposit_merkle_proof(state: &BeaconState, deposit: &Deposit, spec: &Ch state.latest_eth1_data.deposit_root, ) } - -/// Helper struct for easily getting the serialized data generated by the deposit contract. -/// -/// Spec v0.5.1 -#[derive(Encode)] -struct SerializedDepositData { - amount: u64, - timestamp: u64, - input: DepositInput, -} - -/// Return the serialized data generated by the deposit contract that is used to generate the -/// merkle proof. -/// -/// Spec v0.5.1 -fn get_serialized_deposit_data(deposit: &Deposit) -> Vec { - let serialized_deposit_data = SerializedDepositData { - amount: deposit.deposit_data.amount, - timestamp: deposit.deposit_data.timestamp, - input: deposit.deposit_data.deposit_input.clone(), - }; - - ssz_encode(&serialized_deposit_data) -} From ac51d7be3b678cdb4f88f3134bd089d35baeb2f0 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 13 May 2019 17:31:47 +1000 Subject: [PATCH 16/22] WIP beacon state changes --- eth2/types/src/beacon_state.rs | 115 +++++++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 18 deletions(-) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index f0e5647ad..42424cefb 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -267,6 +267,36 @@ impl BeaconState { self.current_epoch(spec) + 1 } + /// Return the number of committees at ``epoch``. + /// + /// Spec v0.6.1 + pub fn get_epoch_committee_count(&self, epoch: Epoch, spec: &ChainSpec) -> u64 { + let active_validator_indices = self.get_active_validator_indices(epoch); + spec.get_epoch_committee_count(active_validator_indices.len()) + } + + pub fn get_epoch_start_shard(&self, epoch: Epoch, spec: &ChainSpec) -> u64 { + drop((epoch, spec)); + unimplemented!("FIXME(sproul) get_epoch_start_shard") + } + + /// Get the slot of an attestation. + /// + /// Spec v0.6.1 + pub fn get_attestation_slot( + &self, + attestation_data: &AttestationData, + spec: &ChainSpec, + ) -> Result { + let epoch = attestation_data.target_epoch; + let committee_count = self.get_epoch_committee_count(epoch, spec); + let offset = (attestation_data.shard + spec.shard_count + - self.get_epoch_start_shard(epoch, spec)) + % spec.shard_count; + Ok(epoch.start_slot(spec.slots_per_epoch) + + offset / (committee_count / spec.slots_per_epoch)) + } + /// Returns the active validator indices for the given epoch, assuming there is no validator /// registry update in the next epoch. /// @@ -320,6 +350,17 @@ impl BeaconState { .ok_or_else(|| Error::SlotOutOfBounds)?) } + // FIXME(sproul): implement this + pub fn get_crosslink_committee( + &self, + epoch: Epoch, + shard: u64, + spec: &ChainSpec, + ) -> Result<&CrosslinkCommittee, Error> { + drop((epoch, shard, spec)); + unimplemented!() + } + /// Returns the crosslink committees for some shard in an epoch. /// /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. @@ -405,6 +446,18 @@ impl BeaconState { Ok(&self.latest_block_roots[i]) } + /// Return the block root at a recent `slot`. + /// + /// Spec v0.6.0 + // FIXME(sproul): name swap with get_block_root + pub fn get_block_root_at_epoch( + &self, + epoch: Epoch, + spec: &ChainSpec, + ) -> Result<&Hash256, BeaconStateError> { + self.get_block_root(epoch.start_slot(spec.slots_per_epoch), spec) + } + /// Sets the block root for some given slot. /// /// Spec v0.5.1 @@ -575,7 +628,7 @@ impl BeaconState { /// Safely obtains the index for `latest_slashed_balances`, given some `epoch`. /// - /// Spec v0.5.1 + /// Spec v0.6.1 fn get_slashed_balance_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result { let i = epoch.as_usize() % spec.latest_slashed_exit_length; @@ -590,7 +643,7 @@ impl BeaconState { /// Gets the total slashed balances for some epoch. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn get_slashed_balance(&self, epoch: Epoch, spec: &ChainSpec) -> Result { let i = self.get_slashed_balance_index(epoch, spec)?; Ok(self.latest_slashed_balances[i]) @@ -598,7 +651,7 @@ impl BeaconState { /// Sets the total slashed balances for some epoch. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn set_slashed_balance( &mut self, epoch: Epoch, @@ -610,6 +663,41 @@ impl BeaconState { Ok(()) } + /// Get the attestations from the current or previous epoch. + /// + /// Spec v0.6.0 + pub fn get_matching_source_attestations( + &self, + epoch: Epoch, + spec: &ChainSpec, + ) -> Result<&[PendingAttestation], Error> { + if epoch == self.current_epoch(spec) { + Ok(&self.current_epoch_attestations) + } else if epoch == self.previous_epoch(spec) { + Ok(&self.previous_epoch_attestations) + } else { + Err(Error::EpochOutOfBounds) + } + } + + /// Transform an attestation into the crosslink that it reinforces. + /// + /// Spec v0.6.1 + pub fn get_crosslink_from_attestation_data( + &self, + data: &AttestationData, + spec: &ChainSpec, + ) -> Crosslink { + Crosslink { + epoch: std::cmp::min( + data.target_epoch, + self.current_crosslinks[data.shard as usize].epoch + spec.max_crosslink_epochs, + ), + previous_crosslink_root: data.previous_crosslink_root, + crosslink_data_root: data.crosslink_data_root, + } + } + /// Generate a seed for the given `epoch`. /// /// Spec v0.5.1 @@ -628,17 +716,16 @@ impl BeaconState { /// Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. /// - /// Spec v0.5.1 + /// Spec v0.6.0 pub fn get_effective_balance( &self, validator_index: usize, - spec: &ChainSpec, + _spec: &ChainSpec, ) -> Result { - let balance = self - .balances + self.validator_registry .get(validator_index) - .ok_or_else(|| Error::UnknownValidator)?; - Ok(std::cmp::min(*balance, spec.max_deposit_amount)) + .map(|v| v.effective_balance) + .ok_or_else(|| Error::UnknownValidator) } /// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect. @@ -663,14 +750,6 @@ impl BeaconState { )) } - /// Initiate an exit for the validator of the given `index`. - /// - /// Spec v0.5.1 - pub fn initiate_validator_exit(&mut self, validator_index: usize) { - // FIXME(sproul) - // self.validator_registry[validator_index].initiated_exit = true; - } - /// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an /// attestation. /// @@ -694,7 +773,7 @@ impl BeaconState { /// Return the combined effective balance of an array of validators. /// - /// Spec v0.5.1 + /// Spec v0.6.0 pub fn get_total_balance( &self, validator_indices: &[usize], From 13ec3d125e0672a16bf2c5a6d4ef324c585f1e72 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 13 May 2019 17:32:06 +1000 Subject: [PATCH 17/22] WIP trash changes --- eth2/state_processing/Cargo.toml | 1 + eth2/state_processing/src/lib.rs | 8 ++- .../src/per_epoch_processing.rs | 55 +++++++------------ .../inclusion_distance.rs | 12 +++- .../per_epoch_processing/process_ejections.rs | 7 ++- .../process_exit_queue.rs | 2 + .../per_epoch_processing/process_slashings.rs | 4 +- .../src/beacon_state/epoch_cache/tests.rs | 2 +- 8 files changed, 46 insertions(+), 45 deletions(-) diff --git a/eth2/state_processing/Cargo.toml b/eth2/state_processing/Cargo.toml index a2ae11aa8..a596c2dc9 100644 --- a/eth2/state_processing/Cargo.toml +++ b/eth2/state_processing/Cargo.toml @@ -22,6 +22,7 @@ fnv = "1.0" hashing = { path = "../utils/hashing" } int_to_bytes = { path = "../utils/int_to_bytes" } integer-sqrt = "0.1" +itertools = "0.8" log = "0.4" merkle_proof = { path = "../utils/merkle_proof" } ssz = { path = "../utils/ssz" } diff --git a/eth2/state_processing/src/lib.rs b/eth2/state_processing/src/lib.rs index 6757b5dbd..8b386ddbc 100644 --- a/eth2/state_processing/src/lib.rs +++ b/eth2/state_processing/src/lib.rs @@ -2,11 +2,12 @@ mod macros; pub mod common; -pub mod get_genesis_state; -pub mod per_block_processing; +//pub mod get_genesis_state; +//pub mod per_block_processing; pub mod per_epoch_processing; -pub mod per_slot_processing; +//pub mod per_slot_processing; +/* pub use get_genesis_state::get_genesis_state; pub use per_block_processing::{ errors::{BlockInvalid, BlockProcessingError}, @@ -14,3 +15,4 @@ pub use per_block_processing::{ }; pub use per_epoch_processing::{errors::EpochProcessingError, per_epoch_processing}; pub use per_slot_processing::{per_slot_processing, Error as SlotProcessingError}; +*/ diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index a2e696673..286d3c094 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -1,24 +1,24 @@ -use apply_rewards::apply_rewards; +use apply_rewards::process_rewards_and_penalties; use errors::EpochProcessingError as Error; use process_ejections::process_ejections; use process_exit_queue::process_exit_queue; use process_slashings::process_slashings; +use registry_updates::process_registry_updates; use std::collections::HashMap; use tree_hash::TreeHash; use types::*; -use update_registry_and_shuffling_data::update_registry_and_shuffling_data; use validator_statuses::{TotalBalances, ValidatorStatuses}; use winning_root::{winning_root, WinningRoot}; pub mod apply_rewards; pub mod errors; -pub mod get_attestation_participants; +pub mod get_attesting_indices; pub mod inclusion_distance; pub mod process_ejections; pub mod process_exit_queue; pub mod process_slashings; +pub mod registry_updates; pub mod tests; -pub mod update_registry_and_shuffling_data; pub mod validator_statuses; pub mod winning_root; @@ -54,7 +54,7 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result maybe_reset_eth1_period(state, spec); // Rewards and Penalities. - apply_rewards( + process_rewards_and_penalties( state, &mut validator_statuses, &winning_root_for_shards, @@ -65,11 +65,7 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result process_ejections(state, spec)?; // Validator Registry. - update_registry_and_shuffling_data( - state, - validator_statuses.total_balances.current_epoch, - spec, - )?; + process_registry_updates(state, validator_statuses.total_balances.current_epoch, spec)?; // Slashings and exit queue. process_slashings(state, validator_statuses.total_balances.current_epoch, spec)?; @@ -88,6 +84,7 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result /// /// Spec v0.5.1 pub fn maybe_reset_eth1_period(state: &mut BeaconState, spec: &ChainSpec) { + /* FIXME(sproul) let next_epoch = state.next_epoch(spec); let voting_period = spec.epochs_per_eth1_voting_period; @@ -99,6 +96,7 @@ pub fn maybe_reset_eth1_period(state: &mut BeaconState, spec: &ChainSpec) { } state.eth1_data_votes = vec![]; } + */ } /// Update the following fields on the `BeaconState`: @@ -132,8 +130,6 @@ pub fn process_justification_and_finalization( state.previous_justified_root = state.current_justified_root; state.justification_bitfield <<= 1; - let previous_epoch_matching_target_balance = total_balances.previous_epoch_target_attesters; - if total_balances.previous_epoch_target_attesters * 3 >= total_balances.previous_epoch * 2 { state.current_justified_epoch = previous_epoch; state.current_justified_root = @@ -176,42 +172,33 @@ pub fn process_justification_and_finalization( /// Updates the following fields on the `BeaconState`: /// -/// - `latest_crosslinks` +/// - `previous_crosslinks` +/// - `current_crosslinks` /// /// Also returns a `WinningRootHashSet` for later use during epoch processing. /// -/// Spec v0.5.1 +/// Spec v0.6.1 pub fn process_crosslinks( state: &mut BeaconState, spec: &ChainSpec, ) -> Result { let mut winning_root_for_shards: WinningRootHashSet = HashMap::new(); - let previous_and_current_epoch_slots: Vec = state - .previous_epoch(spec) - .slot_iter(spec.slots_per_epoch) - .chain(state.current_epoch(spec).slot_iter(spec.slots_per_epoch)) - .collect(); + state.previous_crosslinks = state.current_crosslinks.clone(); - for slot in previous_and_current_epoch_slots { - // Clone removes the borrow which becomes an issue when mutating `state.balances`. - let crosslink_committees_at_slot = - state.get_crosslink_committees_at_slot(slot, spec)?.clone(); + for epoch in vec![state.previous_epoch(spec), state.current_epoch(spec)] { + for offset in 0..state.get_epoch_committee_count(epoch, spec) { + let shard = (state.get_epoch_start_shard(epoch, spec) + offset) % spec.shard_count; + let crosslink_committee = state.get_crosslink_committee(epoch, shard, spec)?; - for c in crosslink_committees_at_slot { - let shard = c.shard as u64; - - let winning_root = winning_root(state, shard, spec)?; + let winning_root = winning_root(state, shard, epoch, spec)?; if let Some(winning_root) = winning_root { - let total_committee_balance = state.get_total_balance(&c.committee, spec)?; + let total_committee_balance = + state.get_total_balance(&crosslink_committee.committee, spec)?; - // TODO: I think this has a bug. - if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) { - state.latest_crosslinks[shard as usize] = Crosslink { - epoch: slot.epoch(spec.slots_per_epoch), - crosslink_data_root: winning_root.crosslink_data_root, - } + if 3 * winning_root.total_attesting_balance >= 2 * total_committee_balance { + state.current_crosslinks[shard as usize] = winning_root.crosslink.clone(); } winning_root_for_shards.insert(shard, winning_root); } diff --git a/eth2/state_processing/src/per_epoch_processing/inclusion_distance.rs b/eth2/state_processing/src/per_epoch_processing/inclusion_distance.rs index 6b221f513..29b4f3339 100644 --- a/eth2/state_processing/src/per_epoch_processing/inclusion_distance.rs +++ b/eth2/state_processing/src/per_epoch_processing/inclusion_distance.rs @@ -1,5 +1,4 @@ use super::errors::InclusionError; -use super::get_attestation_participants::get_attestation_participants; use types::*; /// Returns the distance between the first included attestation for some validator and this @@ -13,7 +12,9 @@ pub fn inclusion_distance( spec: &ChainSpec, ) -> Result { let attestation = earliest_included_attestation(state, attestations, validator_index, spec)?; - Ok((attestation.inclusion_slot - attestation.data.slot).as_u64()) + // Ok((attestation.inclusion_slot - attestation.data.slot).as_u64()) + // FIXME(sproul) + unimplemented!() } /// Returns the slot of the earliest included attestation for some validator. @@ -25,8 +26,11 @@ pub fn inclusion_slot( validator_index: usize, spec: &ChainSpec, ) -> Result { + /* let attestation = earliest_included_attestation(state, attestations, validator_index, spec)?; Ok(attestation.inclusion_slot) + */ + unimplemented!("FIXME(sproul) inclusion slot") } /// Finds the earliest included attestation for some validator. @@ -38,6 +42,9 @@ fn earliest_included_attestation( validator_index: usize, spec: &ChainSpec, ) -> Result { + // FIXME(sproul) + unimplemented!() + /* let mut included_attestations = vec![]; for (i, a) in attestations.iter().enumerate() { @@ -53,4 +60,5 @@ fn earliest_included_attestation( .min_by_key(|i| attestations[**i].inclusion_slot) .ok_or_else(|| InclusionError::NoAttestationsForValidator)?; Ok(attestations[*earliest_attestation_index].clone()) + */ } diff --git a/eth2/state_processing/src/per_epoch_processing/process_ejections.rs b/eth2/state_processing/src/per_epoch_processing/process_ejections.rs index 6f64c46f7..b90323bf6 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_ejections.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_ejections.rs @@ -1,4 +1,4 @@ -use crate::common::exit_validator; +// use crate::common::exit_validator; use types::{BeaconStateError as Error, *}; /// Iterate through the validator registry and eject active validators with balance below @@ -12,7 +12,7 @@ pub fn process_ejections(state: &mut BeaconState, spec: &ChainSpec) -> Result<() .get_cached_active_validator_indices(RelativeEpoch::Current, spec)? .iter() .filter_map(|&i| { - if state.validator_balances[i as usize] < spec.ejection_balance { + if state.balances[i as usize] < spec.ejection_balance { Some(i) } else { None @@ -21,7 +21,8 @@ pub fn process_ejections(state: &mut BeaconState, spec: &ChainSpec) -> Result<() .collect(); for validator_index in exitable { - exit_validator(state, validator_index, spec)? + // FIXME(sproul) + // exit_validator(state, validator_index, spec)? } Ok(()) diff --git a/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs b/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs index a6362188d..01404b4c2 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs @@ -22,9 +22,11 @@ pub fn process_exit_queue(state: &mut BeaconState, spec: &ChainSpec) { eligable_indices.sort_by_key(|i| state.validator_registry[*i].exit_epoch); for (dequeues, index) in eligable_indices.iter().enumerate() { + /* FIXME(sproul) if dequeues as u64 >= spec.max_exit_dequeues_per_epoch { break; } + */ prepare_validator_for_withdrawal(state, *index, spec); } } diff --git a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs index 89a7dd484..6edb55536 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs @@ -24,10 +24,10 @@ pub fn process_slashings( let penalty = std::cmp::max( effective_balance * std::cmp::min(total_penalities * 3, current_total_balance) / current_total_balance, - effective_balance / spec.min_penalty_quotient, + effective_balance / 1, /* FIXME(sproul): spec.min_penalty_quotient, */ ); - state.validator_balances[index] -= penalty; + state.balances[index] -= penalty; } } diff --git a/eth2/types/src/beacon_state/epoch_cache/tests.rs b/eth2/types/src/beacon_state/epoch_cache/tests.rs index 5b1e53338..513a5a3ce 100644 --- a/eth2/types/src/beacon_state/epoch_cache/tests.rs +++ b/eth2/types/src/beacon_state/epoch_cache/tests.rs @@ -1,4 +1,4 @@ -#![cfg(test)] +#![cfg(all(not(test), test))] use super::*; use crate::test_utils::*; From 894ecdd4ea3c2b996a7793d35350fac3a646832a Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 14 May 2019 12:56:15 +1000 Subject: [PATCH 18/22] state_processing: process_slashings v0.6.1 --- .../src/per_epoch_processing/process_slashings.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs index b684dd33b..e9b814376 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_slashings.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_slashings.rs @@ -2,7 +2,7 @@ use types::{BeaconStateError as Error, *}; /// Process slashings. /// -/// Spec v0.5.1 +/// Spec v0.6.1 pub fn process_slashings( state: &mut BeaconState, current_total_balance: u64, @@ -24,10 +24,10 @@ pub fn process_slashings( let penalty = std::cmp::max( effective_balance * std::cmp::min(total_penalities * 3, current_total_balance) / current_total_balance, - effective_balance / 1, /* FIXME(sproul): spec.min_penalty_quotient, */ + effective_balance / spec.min_slashing_penalty_quotient, ); - state.balances[index] -= penalty; + safe_sub_assign!(state.balances[index], penalty); } } From 79de966d3aa4b634ee0342d7e58cdfc693404e29 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 14 May 2019 15:00:18 +1000 Subject: [PATCH 19/22] spec: top-level per-epoch processing v0.6.1 --- .../src/per_epoch_processing.rs | 76 +++++++++---------- .../per_epoch_processing/process_ejections.rs | 32 -------- .../process_exit_queue.rs | 44 ----------- .../per_epoch_processing/registry_updates.rs | 1 - eth2/types/src/beacon_state.rs | 23 ++++-- 5 files changed, 53 insertions(+), 123 deletions(-) delete mode 100644 eth2/state_processing/src/per_epoch_processing/process_ejections.rs delete mode 100644 eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 86d4a5eee..251dc3ec1 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -1,7 +1,5 @@ use apply_rewards::process_rewards_and_penalties; use errors::EpochProcessingError as Error; -use process_ejections::process_ejections; -use process_exit_queue::process_exit_queue; use process_slashings::process_slashings; use registry_updates::process_registry_updates; use std::collections::HashMap; @@ -14,8 +12,6 @@ pub mod apply_rewards; pub mod errors; pub mod get_attesting_indices; pub mod inclusion_distance; -pub mod process_ejections; -pub mod process_exit_queue; pub mod process_slashings; pub mod registry_updates; pub mod tests; @@ -32,7 +28,7 @@ pub type WinningRootHashSet = HashMap; /// Mutates the given `BeaconState`, returning early if an error is encountered. If an error is /// returned, a state might be "half-processed" and therefore in an invalid state. /// -/// Spec v0.5.1 +/// Spec v0.6.1 pub fn per_epoch_processing( state: &mut BeaconState, spec: &ChainSpec, @@ -47,15 +43,12 @@ pub fn per_epoch_processing( let mut validator_statuses = ValidatorStatuses::new(state, spec)?; validator_statuses.process_attestations(&state, spec)?; - // Justification. + // Justification and finalization. process_justification_and_finalization(state, &validator_statuses.total_balances, spec)?; // Crosslinks. let winning_root_for_shards = process_crosslinks(state, spec)?; - // Eth1 data. - maybe_reset_eth1_period(state, spec); - // Rewards and Penalities. process_rewards_and_penalties( state, @@ -64,18 +57,14 @@ pub fn per_epoch_processing( spec, )?; - // Ejections. - process_ejections(state, spec)?; + // Registry Updates. + process_registry_updates(state, spec)?; - // Validator Registry. - process_registry_updates(state, validator_statuses.total_balances.current_epoch, spec)?; - - // Slashings and exit queue. + // Slashings. process_slashings(state, validator_statuses.total_balances.current_epoch, spec)?; - process_exit_queue(state, spec); // Final updates. - finish_epoch_update(state, spec)?; + process_final_updates(state, spec)?; // Rotate the epoch caches to suit the epoch transition. state.advance_caches(); @@ -83,25 +72,6 @@ pub fn per_epoch_processing( Ok(()) } -/// Maybe resets the eth1 period. -/// -/// Spec v0.5.1 -pub fn maybe_reset_eth1_period(state: &mut BeaconState, spec: &ChainSpec) { - /* FIXME(sproul) - let next_epoch = state.next_epoch(spec); - let voting_period = spec.epochs_per_eth1_voting_period; - - if next_epoch % voting_period == 0 { - for eth1_data_vote in &state.eth1_data_votes { - if eth1_data_vote.vote_count * 2 > voting_period * spec.slots_per_epoch { - state.latest_eth1_data = eth1_data_vote.eth1_data.clone(); - } - } - state.eth1_data_votes = vec![]; - } - */ -} - /// Update the following fields on the `BeaconState`: /// /// - `justification_bitfield`. @@ -213,14 +183,38 @@ pub fn process_crosslinks( /// Finish up an epoch update. /// -/// Spec v0.5.1 -pub fn finish_epoch_update( +/// Spec v0.6.1 +pub fn process_final_updates( state: &mut BeaconState, spec: &ChainSpec, ) -> Result<(), Error> { let current_epoch = state.current_epoch(spec); let next_epoch = state.next_epoch(spec); + // Reset eth1 data votes. + if (state.slot + 1) % spec.slots_per_eth1_voting_period == 0 { + state.eth1_data_votes = vec![]; + } + + // Update effective balances with hysteresis (lag). + for (index, validator) in state.validator_registry.iter_mut().enumerate() { + let balance = state.balances[index]; + let half_increment = spec.effective_balance_increment / 2; + if balance < validator.effective_balance + || validator.effective_balance + 3 * half_increment < balance + { + validator.effective_balance = std::cmp::min( + balance - balance % spec.effective_balance_increment, + spec.max_effective_balance, + ); + } + } + + // Update start shard. + state.latest_start_shard = (state.latest_start_shard + + state.get_shard_delta(current_epoch, spec)) + % T::ShardCount::to_u64(); + // This is a hack to allow us to update index roots and slashed balances for the next epoch. // // The indentation here is to make it obvious where the weird stuff happens. @@ -255,7 +249,11 @@ pub fn finish_epoch_update( .push(Hash256::from_slice(&historical_batch.tree_hash_root()[..])); } - state.previous_epoch_attestations = state.current_epoch_attestations.clone(); + // Rotate current/previous epoch attestations + std::mem::swap( + &mut state.previous_epoch_attestations, + &mut state.current_epoch_attestations, + ); state.current_epoch_attestations = vec![]; Ok(()) diff --git a/eth2/state_processing/src/per_epoch_processing/process_ejections.rs b/eth2/state_processing/src/per_epoch_processing/process_ejections.rs deleted file mode 100644 index af329283f..000000000 --- a/eth2/state_processing/src/per_epoch_processing/process_ejections.rs +++ /dev/null @@ -1,32 +0,0 @@ -// use crate::common::exit_validator; -use types::{BeaconStateError as Error, *}; - -/// Iterate through the validator registry and eject active validators with balance below -/// ``EJECTION_BALANCE``. -/// -/// Spec v0.5.1 -pub fn process_ejections( - state: &mut BeaconState, - spec: &ChainSpec, -) -> Result<(), Error> { - // There is an awkward double (triple?) loop here because we can't loop across the borrowed - // active validator indices and mutate state in the one loop. - let exitable: Vec = state - .get_cached_active_validator_indices(RelativeEpoch::Current, spec)? - .iter() - .filter_map(|&i| { - if state.balances[i as usize] < spec.ejection_balance { - Some(i) - } else { - None - } - }) - .collect(); - - for validator_index in exitable { - // FIXME(sproul) - // exit_validator(state, validator_index, spec)? - } - - Ok(()) -} diff --git a/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs b/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs deleted file mode 100644 index b2f793ff4..000000000 --- a/eth2/state_processing/src/per_epoch_processing/process_exit_queue.rs +++ /dev/null @@ -1,44 +0,0 @@ -use types::*; - -/// Process the exit queue. -/// -/// Spec v0.5.1 -pub fn process_exit_queue(state: &mut BeaconState, spec: &ChainSpec) { - let current_epoch = state.current_epoch(spec); - - let eligible = |index: usize| { - let validator = &state.validator_registry[index]; - - if validator.withdrawable_epoch != spec.far_future_epoch { - false - } else { - current_epoch >= validator.exit_epoch + spec.min_validator_withdrawability_delay - } - }; - - let mut eligable_indices: Vec = (0..state.validator_registry.len()) - .filter(|i| eligible(*i)) - .collect(); - eligable_indices.sort_by_key(|i| state.validator_registry[*i].exit_epoch); - - for (dequeues, index) in eligable_indices.iter().enumerate() { - /* FIXME(sproul) - if dequeues as u64 >= spec.max_exit_dequeues_per_epoch { - break; - } - */ - prepare_validator_for_withdrawal(state, *index, spec); - } -} - -/// Initiate an exit for the validator of the given `index`. -/// -/// Spec v0.5.1 -fn prepare_validator_for_withdrawal( - state: &mut BeaconState, - validator_index: usize, - spec: &ChainSpec, -) { - state.validator_registry[validator_index].withdrawable_epoch = - state.current_epoch(spec) + spec.min_validator_withdrawability_delay; -} diff --git a/eth2/state_processing/src/per_epoch_processing/registry_updates.rs b/eth2/state_processing/src/per_epoch_processing/registry_updates.rs index ad7899e39..6f867b3af 100644 --- a/eth2/state_processing/src/per_epoch_processing/registry_updates.rs +++ b/eth2/state_processing/src/per_epoch_processing/registry_updates.rs @@ -8,7 +8,6 @@ use types::*; /// Spec v0.6.1 pub fn process_registry_updates( state: &mut BeaconState, - current_total_balance: u64, spec: &ChainSpec, ) -> Result<(), Error> { // Process activation eligibility and ejections. diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 5b38e3f6a..590e826ea 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -291,6 +291,16 @@ impl BeaconState { spec.get_epoch_committee_count(active_validator_indices.len()) } + /// Return the number of shards to increment `state.latest_start_shard` during `epoch`. + /// + /// Spec v0.6.1 + pub fn get_shard_delta(&self, epoch: Epoch, spec: &ChainSpec) -> u64 { + std::cmp::min( + self.get_epoch_committee_count(epoch, spec), + T::ShardCount::to_u64() - T::ShardCount::to_u64() / spec.slots_per_epoch, + ) + } + pub fn get_epoch_start_shard(&self, epoch: Epoch, spec: &ChainSpec) -> u64 { drop((epoch, spec)); unimplemented!("FIXME(sproul) get_epoch_start_shard") @@ -538,14 +548,13 @@ impl BeaconState { /// Safely obtains the index for `latest_active_index_roots`, given some `epoch`. /// - /// Spec v0.5.1 + /// Spec v0.6.1 fn get_active_index_root_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result { let current_epoch = self.current_epoch(spec); - if (current_epoch - self.latest_active_index_roots.len() as u64 - + spec.activation_exit_delay - < epoch) - & (epoch <= current_epoch + spec.activation_exit_delay) + if current_epoch - self.latest_active_index_roots.len() as u64 + spec.activation_exit_delay + < epoch + && epoch <= current_epoch + spec.activation_exit_delay { Ok(epoch.as_usize() % self.latest_active_index_roots.len()) } else { @@ -555,7 +564,7 @@ impl BeaconState { /// Return the `active_index_root` at a recent `epoch`. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn get_active_index_root(&self, epoch: Epoch, spec: &ChainSpec) -> Result { let i = self.get_active_index_root_index(epoch, spec)?; Ok(self.latest_active_index_roots[i]) @@ -563,7 +572,7 @@ impl BeaconState { /// Set the `active_index_root` at a recent `epoch`. /// - /// Spec v0.5.1 + /// Spec v0.6.1 pub fn set_active_index_root( &mut self, epoch: Epoch, From 45506ded5ddc46d0e61752a92f6b9154d5a231be Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 14 May 2019 15:37:40 +1000 Subject: [PATCH 20/22] spec: implement get_epoch_start_shard v0.6.1 --- eth2/types/src/beacon_state.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 590e826ea..bed54b1a8 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -301,9 +301,23 @@ impl BeaconState { ) } - pub fn get_epoch_start_shard(&self, epoch: Epoch, spec: &ChainSpec) -> u64 { - drop((epoch, spec)); - unimplemented!("FIXME(sproul) get_epoch_start_shard") + /// Return the start shard for an epoch less than or equal to the next epoch. + /// + /// Spec v0.6.1 + pub fn get_epoch_start_shard(&self, epoch: Epoch, spec: &ChainSpec) -> Result { + if epoch > self.current_epoch(spec) + 1 { + return Err(Error::EpochOutOfBounds); + } + let shard_count = T::ShardCount::to_u64(); + let mut check_epoch = self.current_epoch(spec) + 1; + let mut shard = (self.latest_start_shard + + self.get_shard_delta(self.current_epoch(spec), spec)) + % shard_count; + while check_epoch > epoch { + check_epoch -= 1; + shard = (shard + shard_count - self.get_shard_delta(check_epoch, spec)) % shard_count; + } + Ok(shard) } /// Get the slot of an attestation. @@ -317,7 +331,7 @@ impl BeaconState { let epoch = attestation_data.target_epoch; let committee_count = self.get_epoch_committee_count(epoch, spec); let offset = (attestation_data.shard + spec.shard_count - - self.get_epoch_start_shard(epoch, spec)) + - self.get_epoch_start_shard(epoch, spec)?) % spec.shard_count; Ok(epoch.start_slot(spec.slots_per_epoch) + offset / (committee_count / spec.slots_per_epoch)) From 2727590762f193d73f064723d9493ed0cb895adf Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 14 May 2019 15:49:58 +1000 Subject: [PATCH 21/22] spec: confirm default 0 val for latest_start_shard --- eth2/types/src/beacon_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index bed54b1a8..f5d3e0ab7 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -176,7 +176,7 @@ impl BeaconState { spec.zero_hash; T::LatestRandaoMixesLength::to_usize() ]), - latest_start_shard: 0, // FIXME(sproul) + latest_start_shard: 0, // Finality previous_epoch_attestations: vec![], From c568dd01fe8e718eb63daede7a9a98024e3b5a45 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 14 May 2019 16:37:19 +1000 Subject: [PATCH 22/22] types: del SlashableAttestation, fix fork defaults --- eth2/types/src/fork.rs | 24 ++-- eth2/types/src/indexed_attestation.rs | 4 +- eth2/types/src/slashable_attestation.rs | 151 ------------------------ 3 files changed, 9 insertions(+), 170 deletions(-) delete mode 100644 eth2/types/src/slashable_attestation.rs diff --git a/eth2/types/src/fork.rs b/eth2/types/src/fork.rs index d0decd737..eb4e183f2 100644 --- a/eth2/types/src/fork.rs +++ b/eth2/types/src/fork.rs @@ -37,13 +37,9 @@ impl Fork { /// /// Spec v0.6.1 pub fn genesis(spec: &ChainSpec) -> Self { - let current_version: [u8; 4] = [0; 4]; - // FIXME(sproul): 0 fork? - // current_version.copy_from_slice(&int_to_bytes4(spec.genesis_fork_version)); - Self { - previous_version: current_version, - current_version, + previous_version: [0; 4], + current_version: [0; 4], epoch: spec.genesis_epoch, } } @@ -66,8 +62,7 @@ mod tests { ssz_tests!(Fork); cached_tree_hash_tests!(Fork); - // FIXME(sproul): dunno - fn test_genesis(version: u32, epoch: Epoch) { + fn test_genesis(epoch: Epoch) { let mut spec = ChainSpec::foundation(); spec.genesis_epoch = epoch; @@ -79,19 +74,14 @@ mod tests { fork.previous_version, fork.current_version, "previous and current are not identical" ); - assert_eq!( - fork.current_version, - version.to_le_bytes(), - "current version incorrect" - ); } #[test] fn genesis() { - test_genesis(0, Epoch::new(0)); - test_genesis(9, Epoch::new(11)); - test_genesis(2_u32.pow(31), Epoch::new(2_u64.pow(63))); - test_genesis(u32::max_value(), Epoch::max_value()); + test_genesis(Epoch::new(0)); + test_genesis(Epoch::new(11)); + test_genesis(Epoch::new(2_u64.pow(63))); + test_genesis(Epoch::max_value()); } #[test] diff --git a/eth2/types/src/indexed_attestation.rs b/eth2/types/src/indexed_attestation.rs index ab5364060..af294dfc1 100644 --- a/eth2/types/src/indexed_attestation.rs +++ b/eth2/types/src/indexed_attestation.rs @@ -58,8 +58,8 @@ mod tests { #[test] pub fn test_is_double_vote_true() { - let indexed_vote_first = create_indexed_attestation(1, 1); - let indexed_vote_second = create_indexed_attestation(1, 1); + let indexed_vote_first = create_indexed_attestation(3, 1); + let indexed_vote_second = create_indexed_attestation(3, 2); assert_eq!( indexed_vote_first.is_double_vote(&indexed_vote_second), diff --git a/eth2/types/src/slashable_attestation.rs b/eth2/types/src/slashable_attestation.rs deleted file mode 100644 index 19d9a87b6..000000000 --- a/eth2/types/src/slashable_attestation.rs +++ /dev/null @@ -1,151 +0,0 @@ -use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield, ChainSpec}; - -use serde_derive::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode}; -use test_random_derive::TestRandom; -use tree_hash::TreeHash; -use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash}; - -/// Details an attestation that can be slashable. -/// -/// To be included in an `AttesterSlashing`. -/// -/// Spec v0.5.1 -#[derive( - Debug, - PartialEq, - Clone, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - CachedTreeHash, - TestRandom, - SignedRoot, -)] -pub struct SlashableAttestation { - /// Lists validator registry indices, not committee indices. - pub validator_indices: Vec, - pub data: AttestationData, - pub custody_bitfield: Bitfield, - #[signed_root(skip_hashing)] - pub aggregate_signature: AggregateSignature, -} - -impl SlashableAttestation { - /// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target. - /// - /// Spec v0.5.1 - pub fn is_double_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool { - self.data.slot.epoch(spec.slots_per_epoch) == other.data.slot.epoch(spec.slots_per_epoch) - } - - /// Check if ``attestation_data_1`` surrounds ``attestation_data_2``. - /// - /// Spec v0.5.1 - pub fn is_surround_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool { - let source_epoch_1 = self.data.source_epoch; - let source_epoch_2 = other.data.source_epoch; - let target_epoch_1 = self.data.slot.epoch(spec.slots_per_epoch); - let target_epoch_2 = other.data.slot.epoch(spec.slots_per_epoch); - - (source_epoch_1 < source_epoch_2) & (target_epoch_2 < target_epoch_1) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::chain_spec::ChainSpec; - use crate::slot_epoch::{Epoch, Slot}; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - - #[test] - pub fn test_is_double_vote_true() { - let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_attestation(1, 1, &spec); - let slashable_vote_second = create_slashable_attestation(1, 1, &spec); - - assert_eq!( - slashable_vote_first.is_double_vote(&slashable_vote_second, &spec), - true - ) - } - - #[test] - pub fn test_is_double_vote_false() { - let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_attestation(1, 1, &spec); - let slashable_vote_second = create_slashable_attestation(2, 1, &spec); - - assert_eq!( - slashable_vote_first.is_double_vote(&slashable_vote_second, &spec), - false - ); - } - - #[test] - pub fn test_is_surround_vote_true() { - let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_attestation(2, 1, &spec); - let slashable_vote_second = create_slashable_attestation(1, 2, &spec); - - assert_eq!( - slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), - true - ); - } - - #[test] - pub fn test_is_surround_vote_true_realistic() { - let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_attestation(4, 1, &spec); - let slashable_vote_second = create_slashable_attestation(3, 2, &spec); - - assert_eq!( - slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), - true - ); - } - - #[test] - pub fn test_is_surround_vote_false_source_epoch_fails() { - let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_attestation(2, 2, &spec); - let slashable_vote_second = create_slashable_attestation(1, 1, &spec); - - assert_eq!( - slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), - false - ); - } - - #[test] - pub fn test_is_surround_vote_false_target_epoch_fails() { - let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_attestation(1, 1, &spec); - let slashable_vote_second = create_slashable_attestation(2, 2, &spec); - - assert_eq!( - slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), - false - ); - } - - ssz_tests!(SlashableAttestation); - cached_tree_hash_tests!(SlashableAttestation); - - fn create_slashable_attestation( - slot_factor: u64, - source_epoch: u64, - spec: &ChainSpec, - ) -> SlashableAttestation { - let mut rng = XorShiftRng::from_seed([42; 16]); - let mut slashable_vote = SlashableAttestation::random_for_test(&mut rng); - - slashable_vote.data.slot = Slot::new(slot_factor * spec.slots_per_epoch); - slashable_vote.data.source_epoch = Epoch::new(source_epoch); - slashable_vote - } -}