Merge branch '368' into ef-tests
This commit is contained in:
commit
f4ec8b3e84
@ -21,6 +21,7 @@ fnv = "1.0"
|
|||||||
hashing = { path = "../utils/hashing" }
|
hashing = { path = "../utils/hashing" }
|
||||||
int_to_bytes = { path = "../utils/int_to_bytes" }
|
int_to_bytes = { path = "../utils/int_to_bytes" }
|
||||||
integer-sqrt = "0.1"
|
integer-sqrt = "0.1"
|
||||||
|
itertools = "0.8"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
merkle_proof = { path = "../utils/merkle_proof" }
|
merkle_proof = { path = "../utils/merkle_proof" }
|
||||||
ssz = { path = "../utils/ssz" }
|
ssz = { path = "../utils/ssz" }
|
||||||
|
39
eth2/state_processing/src/common/exit.rs
Normal file
39
eth2/state_processing/src/common/exit.rs
Normal file
@ -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<T: EthSpec>(
|
||||||
|
state: &mut BeaconState<T>,
|
||||||
|
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(())
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
mod exit_validator;
|
mod exit;
|
||||||
mod slash_validator;
|
mod slash_validator;
|
||||||
mod verify_bitfield;
|
mod verify_bitfield;
|
||||||
|
|
||||||
pub use exit_validator::exit_validator;
|
pub use exit::initiate_validator_exit;
|
||||||
pub use slash_validator::slash_validator;
|
pub use slash_validator::slash_validator;
|
||||||
pub use verify_bitfield::verify_bitfield_length;
|
pub use verify_bitfield::verify_bitfield_length;
|
||||||
|
@ -1,61 +1,45 @@
|
|||||||
use crate::common::exit_validator;
|
use crate::common::initiate_validator_exit;
|
||||||
use types::{BeaconStateError as Error, *};
|
use types::{BeaconStateError as Error, *};
|
||||||
|
|
||||||
/// Slash the validator with index ``index``.
|
/// Slash the validator with index ``index``.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn slash_validator<T: EthSpec>(
|
pub fn slash_validator<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
validator_index: usize,
|
slashed_index: usize,
|
||||||
|
opt_whistleblower_index: Option<usize>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let current_epoch = state.current_epoch(spec);
|
if slashed_index >= state.validator_registry.len() || slashed_index >= state.balances.len() {
|
||||||
|
|
||||||
if (validator_index >= state.validator_registry.len())
|
|
||||||
| (validator_index >= state.validator_balances.len())
|
|
||||||
{
|
|
||||||
return Err(BeaconStateError::UnknownValidator);
|
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.
|
state.validator_registry[slashed_index].slashed = true;
|
||||||
//
|
state.validator_registry[slashed_index].withdrawable_epoch =
|
||||||
// This constraint will be lifted in Phase 0.
|
current_epoch + Epoch::from(T::latest_slashed_exit_length());
|
||||||
if state.slot
|
let slashed_balance = state.get_effective_balance(slashed_index, spec)?;
|
||||||
>= validator
|
|
||||||
.withdrawable_epoch
|
|
||||||
.start_slot(spec.slots_per_epoch)
|
|
||||||
{
|
|
||||||
return Err(Error::ValidatorIsWithdrawable);
|
|
||||||
}
|
|
||||||
|
|
||||||
exit_validator(state, validator_index, spec)?;
|
|
||||||
|
|
||||||
state.set_slashed_balance(
|
state.set_slashed_balance(
|
||||||
current_epoch,
|
current_epoch,
|
||||||
state.get_slashed_balance(current_epoch)? + effective_balance,
|
state.get_slashed_balance(current_epoch)? + slashed_balance,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let whistleblower_index =
|
let proposer_index =
|
||||||
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
|
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!(
|
safe_add_assign!(
|
||||||
state.validator_balances[whistleblower_index as usize],
|
state.balances[whistleblower_index],
|
||||||
whistleblower_reward
|
whistleblowing_reward - proposer_reward
|
||||||
);
|
);
|
||||||
safe_sub_assign!(
|
safe_sub_assign!(state.balances[slashed_index], whistleblowing_reward);
|
||||||
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(T::LatestSlashedExitLength::to_usize());
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,12 @@
|
|||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod get_genesis_state;
|
//pub mod get_genesis_state;
|
||||||
pub mod per_block_processing;
|
//pub mod per_block_processing;
|
||||||
pub mod per_epoch_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 get_genesis_state::get_genesis_state;
|
||||||
pub use per_block_processing::{
|
pub use per_block_processing::{
|
||||||
errors::{BlockInvalid, BlockProcessingError},
|
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_epoch_processing::{errors::EpochProcessingError, per_epoch_processing};
|
||||||
pub use per_slot_processing::{per_slot_processing, Error as SlotProcessingError};
|
pub use per_slot_processing::{per_slot_processing, Error as SlotProcessingError};
|
||||||
|
*/
|
||||||
|
@ -15,7 +15,7 @@ pub use validate_attestation::{
|
|||||||
};
|
};
|
||||||
pub use verify_deposit::{get_existing_validator_index, verify_deposit, verify_deposit_index};
|
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_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::{
|
pub use verify_transfer::{
|
||||||
execute_transfer, verify_transfer, verify_transfer_time_independent_only,
|
execute_transfer, verify_transfer, verify_transfer_time_independent_only,
|
||||||
};
|
};
|
||||||
@ -25,8 +25,8 @@ mod validate_attestation;
|
|||||||
mod verify_attester_slashing;
|
mod verify_attester_slashing;
|
||||||
mod verify_deposit;
|
mod verify_deposit;
|
||||||
mod verify_exit;
|
mod verify_exit;
|
||||||
|
mod verify_indexed_attestation;
|
||||||
mod verify_proposer_slashing;
|
mod verify_proposer_slashing;
|
||||||
mod verify_slashable_attestation;
|
|
||||||
mod verify_transfer;
|
mod verify_transfer;
|
||||||
|
|
||||||
// Set to `true` to check the merkle proof that a deposit is in the eth1 deposit root.
|
// Set to `true` to check the merkle proof that a deposit is in the eth1 deposit root.
|
||||||
@ -256,41 +256,41 @@ pub fn process_attester_slashings<T: EthSpec>(
|
|||||||
Invalid::MaxAttesterSlashingsExceed
|
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).
|
// the `AttesterSlashing`s themselves).
|
||||||
let mut slashable_attestations: Vec<&SlashableAttestation> =
|
let mut indexed_attestations: Vec<&IndexedAttestation> =
|
||||||
Vec::with_capacity(attester_slashings.len() * 2);
|
Vec::with_capacity(attester_slashings.len() * 2);
|
||||||
for attester_slashing in attester_slashings {
|
for attester_slashing in attester_slashings {
|
||||||
slashable_attestations.push(&attester_slashing.slashable_attestation_1);
|
indexed_attestations.push(&attester_slashing.attestation_1);
|
||||||
slashable_attestations.push(&attester_slashing.slashable_attestation_2);
|
indexed_attestations.push(&attester_slashing.attestation_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify slashable attestations in parallel.
|
// Verify indexed attestations in parallel.
|
||||||
slashable_attestations
|
indexed_attestations
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.try_for_each(|(i, slashable_attestation)| {
|
.try_for_each(|(i, indexed_attestation)| {
|
||||||
verify_slashable_attestation(&state, slashable_attestation, spec)
|
verify_indexed_attestation(&state, indexed_attestation, spec)
|
||||||
.map_err(|e| e.into_with_index(i))
|
.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() {
|
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(
|
verify_attester_slashing(
|
||||||
&state,
|
&state,
|
||||||
&attester_slashing,
|
&attester_slashing,
|
||||||
should_verify_slashable_attestations,
|
should_verify_indexed_attestations,
|
||||||
spec,
|
spec,
|
||||||
)
|
)
|
||||||
.map_err(|e| e.into_with_index(i))?;
|
.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))?;
|
.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)?;
|
slash_validator(state, i as usize, spec)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,10 +80,10 @@ pub enum BlockInvalid {
|
|||||||
MaxExitsExceeded,
|
MaxExitsExceeded,
|
||||||
MaxTransfersExceed,
|
MaxTransfersExceed,
|
||||||
AttestationInvalid(usize, AttestationInvalid),
|
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.
|
/// To determine the offending `AttesterSlashing` index, divide the error message `usize` by two.
|
||||||
SlashableAttestationInvalid(usize, SlashableAttestationInvalid),
|
IndexedAttestationInvalid(usize, IndexedAttestationInvalid),
|
||||||
AttesterSlashingInvalid(usize, AttesterSlashingInvalid),
|
AttesterSlashingInvalid(usize, AttesterSlashingInvalid),
|
||||||
ProposerSlashingInvalid(usize, ProposerSlashingInvalid),
|
ProposerSlashingInvalid(usize, ProposerSlashingInvalid),
|
||||||
DepositInvalid(usize, DepositInvalid),
|
DepositInvalid(usize, DepositInvalid),
|
||||||
@ -194,10 +194,10 @@ pub enum AttesterSlashingInvalid {
|
|||||||
AttestationDataIdentical,
|
AttestationDataIdentical,
|
||||||
/// The attestations were not in conflict.
|
/// The attestations were not in conflict.
|
||||||
NotSlashable,
|
NotSlashable,
|
||||||
/// The first `SlashableAttestation` was invalid.
|
/// The first `IndexedAttestation` was invalid.
|
||||||
SlashableAttestation1Invalid(SlashableAttestationInvalid),
|
IndexedAttestation1Invalid(IndexedAttestationInvalid),
|
||||||
/// The second `SlashableAttestation` was invalid.
|
/// The second `IndexedAttestation` was invalid.
|
||||||
SlashableAttestation2Invalid(SlashableAttestationInvalid),
|
IndexedAttestation2Invalid(IndexedAttestationInvalid),
|
||||||
/// The validator index is unknown. One cannot slash one who does not exist.
|
/// The validator index is unknown. One cannot slash one who does not exist.
|
||||||
UnknownValidator(u64),
|
UnknownValidator(u64),
|
||||||
/// The specified validator has already been withdrawn.
|
/// 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);
|
impl_into_with_index_with_beacon_error!(AttesterSlashingValidationError, AttesterSlashingInvalid);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* `SlashableAttestation` Validation
|
* `IndexedAttestation` Validation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// The object is invalid or validation failed.
|
/// The object is invalid or validation failed.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum SlashableAttestationValidationError {
|
pub enum IndexedAttestationValidationError {
|
||||||
/// Validation completed successfully and the object is invalid.
|
/// Validation completed successfully and the object is invalid.
|
||||||
Invalid(SlashableAttestationInvalid),
|
Invalid(IndexedAttestationInvalid),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes why an object is invalid.
|
/// Describes why an object is invalid.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum SlashableAttestationInvalid {
|
pub enum IndexedAttestationInvalid {
|
||||||
/// The custody bitfield has some bits set `true`. This is not allowed in phase 0.
|
/// The custody bitfield has some bits set `true`. This is not allowed in phase 0.
|
||||||
CustodyBitfieldHasSetBits,
|
CustodyBitfieldHasSetBits,
|
||||||
/// No validator indices were specified.
|
/// No validator indices were specified.
|
||||||
@ -245,17 +245,17 @@ pub enum SlashableAttestationInvalid {
|
|||||||
BadSignature,
|
BadSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<SlashableAttestationInvalid> for SlashableAttestationValidationError {
|
impl Into<IndexedAttestationInvalid> for IndexedAttestationValidationError {
|
||||||
fn into(self) -> SlashableAttestationInvalid {
|
fn into(self) -> IndexedAttestationInvalid {
|
||||||
match self {
|
match self {
|
||||||
SlashableAttestationValidationError::Invalid(e) => e,
|
IndexedAttestationValidationError::Invalid(e) => e,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_into_with_index_without_beacon_error!(
|
impl_into_with_index_without_beacon_error!(
|
||||||
SlashableAttestationValidationError,
|
IndexedAttestationValidationError,
|
||||||
SlashableAttestationInvalid
|
IndexedAttestationInvalid
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use super::errors::{AttesterSlashingInvalid as Invalid, AttesterSlashingValidationError as Error};
|
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::*;
|
use types::*;
|
||||||
|
|
||||||
/// Indicates if an `AttesterSlashing` is valid to be included in a block in the current epoch of the given
|
/// 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<T: EthSpec>(
|
pub fn verify_attester_slashing<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
attester_slashing: &AttesterSlashing,
|
attester_slashing: &AttesterSlashing,
|
||||||
should_verify_slashable_attestations: bool,
|
should_verify_indexed_attestations: bool,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
|
let indexed_attestation_1 = &attester_slashing.indexed_attestation_1;
|
||||||
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
|
let indexed_attestation_2 = &attester_slashing.indexed_attestation_2;
|
||||||
|
|
||||||
verify!(
|
verify!(
|
||||||
slashable_attestation_1.data != slashable_attestation_2.data,
|
indexed_attestation_1.data != indexed_attestation_2.data,
|
||||||
Invalid::AttestationDataIdentical
|
Invalid::AttestationDataIdentical
|
||||||
);
|
);
|
||||||
verify!(
|
verify!(
|
||||||
slashable_attestation_1.is_double_vote(slashable_attestation_2, spec)
|
indexed_attestation_1.is_double_vote(indexed_attestation_2, spec)
|
||||||
| slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec),
|
| indexed_attestation_1.is_surround_vote(indexed_attestation_2, spec),
|
||||||
Invalid::NotSlashable
|
Invalid::NotSlashable
|
||||||
);
|
);
|
||||||
|
|
||||||
if should_verify_slashable_attestations {
|
if should_verify_indexed_attestations {
|
||||||
verify_slashable_attestation(state, &slashable_attestation_1, spec)
|
verify_indexed_attestation(state, &indexed_attestation_1, spec)
|
||||||
.map_err(|e| Error::Invalid(Invalid::SlashableAttestation1Invalid(e.into())))?;
|
.map_err(|e| Error::Invalid(Invalid::IndexedAttestation1Invalid(e.into())))?;
|
||||||
verify_slashable_attestation(state, &slashable_attestation_2, spec)
|
verify_indexed_attestation(state, &indexed_attestation_2, spec)
|
||||||
.map_err(|e| Error::Invalid(Invalid::SlashableAttestation2Invalid(e.into())))?;
|
.map_err(|e| Error::Invalid(Invalid::IndexedAttestation2Invalid(e.into())))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -66,31 +66,31 @@ pub fn gather_attester_slashing_indices_modular<F, T: EthSpec>(
|
|||||||
where
|
where
|
||||||
F: Fn(u64, &Validator) -> bool,
|
F: Fn(u64, &Validator) -> bool,
|
||||||
{
|
{
|
||||||
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
|
let indexed_attestation_1 = &attester_slashing.indexed_attestation_1;
|
||||||
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
|
let indexed_attestation_2 = &attester_slashing.indexed_attestation_2;
|
||||||
|
|
||||||
let mut slashable_indices = Vec::with_capacity(spec.max_indices_per_slashable_vote);
|
let mut indexed_indices = Vec::with_capacity(spec.max_indices_per_indexed_vote);
|
||||||
for i in &slashable_attestation_1.validator_indices {
|
for i in &indexed_attestation_1.validator_indices {
|
||||||
let validator = state
|
let validator = state
|
||||||
.validator_registry
|
.validator_registry
|
||||||
.get(*i as usize)
|
.get(*i as usize)
|
||||||
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?;
|
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?;
|
||||||
|
|
||||||
if slashable_attestation_2.validator_indices.contains(&i) & !is_slashed(*i, validator) {
|
if indexed_attestation_2.validator_indices.contains(&i) & !is_slashed(*i, validator) {
|
||||||
// TODO: verify that we should reject any slashable attestation which includes a
|
// TODO: verify that we should reject any indexed attestation which includes a
|
||||||
// withdrawn validator. PH has asked the question on gitter, awaiting response.
|
// withdrawn validator. PH has asked the question on gitter, awaiting response.
|
||||||
verify!(
|
verify!(
|
||||||
validator.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch),
|
validator.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch),
|
||||||
Invalid::ValidatorAlreadyWithdrawn(*i)
|
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)
|
||||||
}
|
}
|
||||||
|
@ -91,13 +91,13 @@ pub fn get_existing_validator_index<T: EthSpec>(
|
|||||||
|
|
||||||
/// Verify that a deposit is included in the state's eth1 deposit root.
|
/// 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<T: EthSpec>(
|
fn verify_deposit_merkle_proof<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
deposit: &Deposit,
|
deposit: &Deposit,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let leaf = hash(&get_serialized_deposit_data(deposit));
|
let leaf = deposit.data.tree_hash_root();
|
||||||
verify_merkle_proof(
|
verify_merkle_proof(
|
||||||
Hash256::from_slice(&leaf),
|
Hash256::from_slice(&leaf),
|
||||||
&deposit.proof[..],
|
&deposit.proof[..],
|
||||||
@ -106,27 +106,3 @@ fn verify_deposit_merkle_proof<T: EthSpec>(
|
|||||||
state.latest_eth1_data.deposit_root,
|
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<u8> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
@ -1,52 +1,50 @@
|
|||||||
use super::errors::{
|
use super::errors::{
|
||||||
SlashableAttestationInvalid as Invalid, SlashableAttestationValidationError as Error,
|
IndexedAttestationInvalid as Invalid, IndexedAttestationValidationError as Error,
|
||||||
};
|
};
|
||||||
use crate::common::verify_bitfield_length;
|
use crate::common::verify_bitfield_length;
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
use types::*;
|
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.
|
/// 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
|
/// Spec v0.5.1
|
||||||
pub fn verify_slashable_attestation<T: EthSpec>(
|
pub fn verify_indexed_attestation<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
slashable_attestation: &SlashableAttestation,
|
indexed_attestation: &IndexedAttestation,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if slashable_attestation.custody_bitfield.num_set_bits() > 0 {
|
if indexed_attestation.custody_bitfield.num_set_bits() > 0 {
|
||||||
invalid!(Invalid::CustodyBitfieldHasSetBits);
|
invalid!(Invalid::CustodyBitfieldHasSetBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
if slashable_attestation.validator_indices.is_empty() {
|
if indexed_attestation.validator_indices.is_empty() {
|
||||||
invalid!(Invalid::NoValidatorIndices);
|
invalid!(Invalid::NoValidatorIndices);
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..(slashable_attestation.validator_indices.len() - 1) {
|
for i in 0..(indexed_attestation.validator_indices.len() - 1) {
|
||||||
if slashable_attestation.validator_indices[i]
|
if indexed_attestation.validator_indices[i] >= indexed_attestation.validator_indices[i + 1]
|
||||||
>= slashable_attestation.validator_indices[i + 1]
|
|
||||||
{
|
{
|
||||||
invalid!(Invalid::BadValidatorIndicesOrdering(i));
|
invalid!(Invalid::BadValidatorIndicesOrdering(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !verify_bitfield_length(
|
if !verify_bitfield_length(
|
||||||
&slashable_attestation.custody_bitfield,
|
&indexed_attestation.custody_bitfield,
|
||||||
slashable_attestation.validator_indices.len(),
|
indexed_attestation.validator_indices.len(),
|
||||||
) {
|
) {
|
||||||
invalid!(Invalid::BadCustodyBitfieldLength(
|
invalid!(Invalid::BadCustodyBitfieldLength(
|
||||||
slashable_attestation.validator_indices.len(),
|
indexed_attestation.validator_indices.len(),
|
||||||
slashable_attestation.custody_bitfield.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(
|
invalid!(Invalid::MaxIndicesExceed(
|
||||||
spec.max_indices_per_slashable_vote as usize,
|
spec.max_indices_per_indexed_vote as usize,
|
||||||
slashable_attestation.validator_indices.len()
|
indexed_attestation.validator_indices.len()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,8 +55,8 @@ pub fn verify_slashable_attestation<T: EthSpec>(
|
|||||||
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
||||||
let mut message_exists = vec![false; 2];
|
let mut message_exists = vec![false; 2];
|
||||||
|
|
||||||
for (i, v) in slashable_attestation.validator_indices.iter().enumerate() {
|
for (i, v) in indexed_attestation.validator_indices.iter().enumerate() {
|
||||||
let custody_bit = match slashable_attestation.custody_bitfield.get(i) {
|
let custody_bit = match indexed_attestation.custody_bitfield.get(i) {
|
||||||
Ok(bit) => bit,
|
Ok(bit) => bit,
|
||||||
Err(_) => unreachable!(),
|
Err(_) => unreachable!(),
|
||||||
};
|
};
|
||||||
@ -74,12 +72,12 @@ pub fn verify_slashable_attestation<T: EthSpec>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let message_0 = AttestationDataAndCustodyBit {
|
let message_0 = AttestationDataAndCustodyBit {
|
||||||
data: slashable_attestation.data.clone(),
|
data: indexed_attestation.data.clone(),
|
||||||
custody_bit: false,
|
custody_bit: false,
|
||||||
}
|
}
|
||||||
.tree_hash_root();
|
.tree_hash_root();
|
||||||
let message_1 = AttestationDataAndCustodyBit {
|
let message_1 = AttestationDataAndCustodyBit {
|
||||||
data: slashable_attestation.data.clone(),
|
data: indexed_attestation.data.clone(),
|
||||||
custody_bit: true,
|
custody_bit: true,
|
||||||
}
|
}
|
||||||
.tree_hash_root();
|
.tree_hash_root();
|
||||||
@ -97,12 +95,12 @@ pub fn verify_slashable_attestation<T: EthSpec>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let domain = {
|
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)
|
spec.get_domain(epoch, Domain::Attestation, &state.fork)
|
||||||
};
|
};
|
||||||
|
|
||||||
verify!(
|
verify!(
|
||||||
slashable_attestation
|
indexed_attestation
|
||||||
.aggregate_signature
|
.aggregate_signature
|
||||||
.verify_multiple(&messages[..], domain, &keys[..]),
|
.verify_multiple(&messages[..], domain, &keys[..]),
|
||||||
Invalid::BadSignature
|
Invalid::BadSignature
|
@ -1,24 +1,20 @@
|
|||||||
use apply_rewards::apply_rewards;
|
use apply_rewards::process_rewards_and_penalties;
|
||||||
use errors::EpochProcessingError as Error;
|
use errors::EpochProcessingError as Error;
|
||||||
use process_ejections::process_ejections;
|
|
||||||
use process_exit_queue::process_exit_queue;
|
|
||||||
use process_slashings::process_slashings;
|
use process_slashings::process_slashings;
|
||||||
|
use registry_updates::process_registry_updates;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
use types::*;
|
use types::*;
|
||||||
use update_registry_and_shuffling_data::update_registry_and_shuffling_data;
|
|
||||||
use validator_statuses::{TotalBalances, ValidatorStatuses};
|
use validator_statuses::{TotalBalances, ValidatorStatuses};
|
||||||
use winning_root::{winning_root, WinningRoot};
|
use winning_root::{winning_root, WinningRoot};
|
||||||
|
|
||||||
pub mod apply_rewards;
|
pub mod apply_rewards;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod get_attestation_participants;
|
pub mod get_attesting_indices;
|
||||||
pub mod inclusion_distance;
|
pub mod inclusion_distance;
|
||||||
pub mod process_ejections;
|
|
||||||
pub mod process_exit_queue;
|
|
||||||
pub mod process_slashings;
|
pub mod process_slashings;
|
||||||
|
pub mod registry_updates;
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
pub mod update_registry_and_shuffling_data;
|
|
||||||
pub mod validator_statuses;
|
pub mod validator_statuses;
|
||||||
pub mod winning_root;
|
pub mod winning_root;
|
||||||
|
|
||||||
@ -32,7 +28,7 @@ pub type WinningRootHashSet = HashMap<u64, WinningRoot>;
|
|||||||
/// Mutates the given `BeaconState`, returning early if an error is encountered. If an error is
|
/// 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.
|
/// 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<T: EthSpec>(
|
pub fn per_epoch_processing<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
@ -47,39 +43,28 @@ pub fn per_epoch_processing<T: EthSpec>(
|
|||||||
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
|
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
|
||||||
validator_statuses.process_attestations(&state, spec)?;
|
validator_statuses.process_attestations(&state, spec)?;
|
||||||
|
|
||||||
// Justification.
|
// Justification and finalization.
|
||||||
update_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
|
process_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
|
||||||
|
|
||||||
// Crosslinks.
|
// Crosslinks.
|
||||||
let winning_root_for_shards = process_crosslinks(state, spec)?;
|
let winning_root_for_shards = process_crosslinks(state, spec)?;
|
||||||
|
|
||||||
// Eth1 data.
|
|
||||||
maybe_reset_eth1_period(state, spec);
|
|
||||||
|
|
||||||
// Rewards and Penalities.
|
// Rewards and Penalities.
|
||||||
apply_rewards(
|
process_rewards_and_penalties(
|
||||||
state,
|
state,
|
||||||
&mut validator_statuses,
|
&mut validator_statuses,
|
||||||
&winning_root_for_shards,
|
&winning_root_for_shards,
|
||||||
spec,
|
spec,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Ejections.
|
// Registry Updates.
|
||||||
process_ejections(state, spec)?;
|
process_registry_updates(state, spec)?;
|
||||||
|
|
||||||
// Validator Registry.
|
// Slashings.
|
||||||
update_registry_and_shuffling_data(
|
|
||||||
state,
|
|
||||||
validator_statuses.total_balances.current_epoch,
|
|
||||||
spec,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Slashings and exit queue.
|
|
||||||
process_slashings(state, validator_statuses.total_balances.current_epoch, spec)?;
|
process_slashings(state, validator_statuses.total_balances.current_epoch, spec)?;
|
||||||
process_exit_queue(state, spec);
|
|
||||||
|
|
||||||
// Final updates.
|
// Final updates.
|
||||||
finish_epoch_update(state, spec)?;
|
process_final_updates(state, spec)?;
|
||||||
|
|
||||||
// Rotate the epoch caches to suit the epoch transition.
|
// Rotate the epoch caches to suit the epoch transition.
|
||||||
state.advance_caches();
|
state.advance_caches();
|
||||||
@ -87,89 +72,72 @@ pub fn per_epoch_processing<T: EthSpec>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maybe resets the eth1 period.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
pub fn maybe_reset_eth1_period<T: EthSpec>(state: &mut BeaconState<T>, spec: &ChainSpec) {
|
|
||||||
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`:
|
/// Update the following fields on the `BeaconState`:
|
||||||
///
|
///
|
||||||
/// - `justification_bitfield`.
|
/// - `justification_bitfield`.
|
||||||
/// - `finalized_epoch`
|
|
||||||
/// - `justified_epoch`
|
|
||||||
/// - `previous_justified_epoch`
|
/// - `previous_justified_epoch`
|
||||||
|
/// - `previous_justified_root`
|
||||||
|
/// - `current_justified_epoch`
|
||||||
|
/// - `current_justified_root`
|
||||||
|
/// - `finalized_epoch`
|
||||||
|
/// - `finalized_root`
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn update_justification_and_finalization<T: EthSpec>(
|
pub fn process_justification_and_finalization<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
total_balances: &TotalBalances,
|
total_balances: &TotalBalances,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
if state.current_epoch(spec) == spec.genesis_epoch {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let previous_epoch = state.previous_epoch(spec);
|
let previous_epoch = state.previous_epoch(spec);
|
||||||
let current_epoch = state.current_epoch(spec);
|
let current_epoch = state.current_epoch(spec);
|
||||||
|
|
||||||
let mut new_justified_epoch = state.current_justified_epoch;
|
let old_previous_justified_epoch = state.previous_justified_epoch;
|
||||||
let mut new_finalized_epoch = state.finalized_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;
|
state.justification_bitfield <<= 1;
|
||||||
|
|
||||||
// If the previous epoch gets justified, full the second last bit.
|
if total_balances.previous_epoch_target_attesters * 3 >= total_balances.previous_epoch * 2 {
|
||||||
if (total_balances.previous_epoch_boundary_attesters * 3) >= (total_balances.previous_epoch * 2)
|
state.current_justified_epoch = previous_epoch;
|
||||||
{
|
state.current_justified_root =
|
||||||
new_justified_epoch = previous_epoch;
|
*state.get_block_root_at_epoch(state.current_justified_epoch, spec)?;
|
||||||
state.justification_bitfield |= 2;
|
state.justification_bitfield |= 2;
|
||||||
}
|
}
|
||||||
// If the current epoch gets justified, fill the last bit.
|
// If the current epoch gets justified, fill the last bit.
|
||||||
if (total_balances.current_epoch_boundary_attesters * 3) >= (total_balances.current_epoch * 2) {
|
if total_balances.current_epoch_target_attesters * 3 >= total_balances.current_epoch * 2 {
|
||||||
new_justified_epoch = current_epoch;
|
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;
|
state.justification_bitfield |= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bitfield = state.justification_bitfield;
|
let bitfield = state.justification_bitfield;
|
||||||
|
|
||||||
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
|
// 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) {
|
if (bitfield >> 1) % 8 == 0b111 && old_previous_justified_epoch == current_epoch - 3 {
|
||||||
new_finalized_epoch = state.previous_justified_epoch;
|
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.
|
// 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) {
|
if (bitfield >> 1) % 4 == 0b11 && state.previous_justified_epoch == current_epoch - 2 {
|
||||||
new_finalized_epoch = state.previous_justified_epoch;
|
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.
|
// 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) {
|
if bitfield % 8 == 0b111 && state.current_justified_epoch == current_epoch - 2 {
|
||||||
new_finalized_epoch = state.current_justified_epoch;
|
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.
|
// 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) {
|
if bitfield % 4 == 0b11 && state.current_justified_epoch == current_epoch - 1 {
|
||||||
new_finalized_epoch = state.current_justified_epoch;
|
state.finalized_epoch = old_current_justified_epoch;
|
||||||
}
|
state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch, spec)?;
|
||||||
|
|
||||||
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))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -177,42 +145,33 @@ pub fn update_justification_and_finalization<T: EthSpec>(
|
|||||||
|
|
||||||
/// Updates the following fields on the `BeaconState`:
|
/// Updates the following fields on the `BeaconState`:
|
||||||
///
|
///
|
||||||
/// - `latest_crosslinks`
|
/// - `previous_crosslinks`
|
||||||
|
/// - `current_crosslinks`
|
||||||
///
|
///
|
||||||
/// Also returns a `WinningRootHashSet` for later use during epoch processing.
|
/// Also returns a `WinningRootHashSet` for later use during epoch processing.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn process_crosslinks<T: EthSpec>(
|
pub fn process_crosslinks<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<WinningRootHashSet, Error> {
|
) -> Result<WinningRootHashSet, Error> {
|
||||||
let mut winning_root_for_shards: WinningRootHashSet = HashMap::new();
|
let mut winning_root_for_shards: WinningRootHashSet = HashMap::new();
|
||||||
|
|
||||||
let previous_and_current_epoch_slots: Vec<Slot> = state
|
state.previous_crosslinks = state.current_crosslinks.clone();
|
||||||
.previous_epoch(spec)
|
|
||||||
.slot_iter(spec.slots_per_epoch)
|
|
||||||
.chain(state.current_epoch(spec).slot_iter(spec.slots_per_epoch))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for slot in previous_and_current_epoch_slots {
|
for epoch in vec![state.previous_epoch(spec), state.current_epoch(spec)] {
|
||||||
// Clone removes the borrow which becomes an issue when mutating `state.balances`.
|
for offset in 0..state.get_epoch_committee_count(epoch, spec) {
|
||||||
let crosslink_committees_at_slot =
|
let shard = (state.get_epoch_start_shard(epoch, spec) + offset) % spec.shard_count;
|
||||||
state.get_crosslink_committees_at_slot(slot, spec)?.clone();
|
let crosslink_committee = state.get_crosslink_committee(epoch, shard, spec)?;
|
||||||
|
|
||||||
for c in crosslink_committees_at_slot {
|
let winning_root = winning_root(state, shard, epoch, spec)?;
|
||||||
let shard = c.shard as u64;
|
|
||||||
|
|
||||||
let winning_root = winning_root(state, shard, spec)?;
|
|
||||||
|
|
||||||
if let Some(winning_root) = winning_root {
|
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 {
|
||||||
if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) {
|
state.current_crosslinks[shard as usize] = winning_root.crosslink.clone();
|
||||||
state.latest_crosslinks[shard as usize] = Crosslink {
|
|
||||||
epoch: slot.epoch(spec.slots_per_epoch),
|
|
||||||
crosslink_data_root: winning_root.crosslink_data_root,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
winning_root_for_shards.insert(shard, winning_root);
|
winning_root_for_shards.insert(shard, winning_root);
|
||||||
}
|
}
|
||||||
@ -224,14 +183,38 @@ pub fn process_crosslinks<T: EthSpec>(
|
|||||||
|
|
||||||
/// Finish up an epoch update.
|
/// Finish up an epoch update.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn finish_epoch_update<T: EthSpec>(
|
pub fn process_final_updates<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let current_epoch = state.current_epoch(spec);
|
let current_epoch = state.current_epoch(spec);
|
||||||
let next_epoch = state.next_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.
|
// 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.
|
// The indentation here is to make it obvious where the weird stuff happens.
|
||||||
@ -266,7 +249,11 @@ pub fn finish_epoch_update<T: EthSpec>(
|
|||||||
.push(Hash256::from_slice(&historical_batch.tree_hash_root()[..]));
|
.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![];
|
state.current_epoch_attestations = vec![];
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -32,57 +32,52 @@ impl std::ops::AddAssign for Delta {
|
|||||||
|
|
||||||
/// Apply attester and proposer rewards.
|
/// Apply attester and proposer rewards.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn apply_rewards<T: EthSpec>(
|
pub fn process_rewards_and_penalties<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
validator_statuses: &mut ValidatorStatuses,
|
validator_statuses: &mut ValidatorStatuses,
|
||||||
winning_root_for_shards: &WinningRootHashSet,
|
winning_root_for_shards: &WinningRootHashSet,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
if state.current_epoch(spec) == spec.genesis_epoch {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// Guard against an out-of-bounds during the validator balance update.
|
// Guard against an out-of-bounds during the validator balance update.
|
||||||
if validator_statuses.statuses.len() != state.validator_balances.len() {
|
if validator_statuses.statuses.len() != state.balances.len()
|
||||||
return Err(Error::ValidatorStatusesInconsistent);
|
|| validator_statuses.statuses.len() != state.validator_registry.len()
|
||||||
}
|
{
|
||||||
// Guard against an out-of-bounds during the attester inclusion balance update.
|
|
||||||
if validator_statuses.statuses.len() != state.validator_registry.len() {
|
|
||||||
return Err(Error::ValidatorStatusesInconsistent);
|
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)?;
|
get_crosslink_deltas(&mut deltas, state, &validator_statuses, spec)?;
|
||||||
|
|
||||||
// Apply the proposer deltas if we are finalizing normally.
|
get_proposer_deltas(
|
||||||
//
|
&mut deltas,
|
||||||
// This is executed slightly differently to the spec because of the way our functions are
|
state,
|
||||||
// structured. It should be functionally equivalent.
|
validator_statuses,
|
||||||
if epochs_since_finality(state, spec) <= 4 {
|
winning_root_for_shards,
|
||||||
get_proposer_deltas(
|
spec,
|
||||||
&mut deltas,
|
)?;
|
||||||
state,
|
|
||||||
validator_statuses,
|
|
||||||
winning_root_for_shards,
|
|
||||||
spec,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the deltas, over-flowing but not under-flowing (saturating at 0 instead).
|
// Apply the deltas, over-flowing but not under-flowing (saturating at 0 instead).
|
||||||
for (i, delta) in deltas.iter().enumerate() {
|
for (i, delta) in deltas.iter().enumerate() {
|
||||||
state.validator_balances[i] += delta.rewards;
|
state.balances[i] += delta.rewards;
|
||||||
state.validator_balances[i] = state.validator_balances[i].saturating_sub(delta.penalties);
|
state.balances[i] = state.balances[i].saturating_sub(delta.penalties);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies the attestation inclusion reward to each proposer for every validator who included an
|
/// For each attesting validator, reward the proposer who was first to include their attestation.
|
||||||
/// attestation in the previous epoch.
|
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
fn get_proposer_deltas<T: EthSpec>(
|
fn get_proposer_deltas<T: EthSpec>(
|
||||||
deltas: &mut Vec<Delta>,
|
deltas: &mut Vec<Delta>,
|
||||||
state: &mut BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
validator_statuses: &mut ValidatorStatuses,
|
validator_statuses: &mut ValidatorStatuses,
|
||||||
winning_root_for_shards: &WinningRootHashSet,
|
winning_root_for_shards: &WinningRootHashSet,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
@ -90,9 +85,7 @@ fn get_proposer_deltas<T: EthSpec>(
|
|||||||
// Update statuses with the information from winning roots.
|
// Update statuses with the information from winning roots.
|
||||||
validator_statuses.process_winning_roots(state, winning_root_for_shards, spec)?;
|
validator_statuses.process_winning_roots(state, winning_root_for_shards, spec)?;
|
||||||
|
|
||||||
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
|
for validator in &validator_statuses.statuses {
|
||||||
let mut delta = Delta::default();
|
|
||||||
|
|
||||||
if validator.is_previous_epoch_attester {
|
if validator.is_previous_epoch_attester {
|
||||||
let inclusion = validator
|
let inclusion = validator
|
||||||
.inclusion_info
|
.inclusion_info
|
||||||
@ -101,7 +94,7 @@ fn get_proposer_deltas<T: EthSpec>(
|
|||||||
let base_reward = get_base_reward(
|
let base_reward = get_base_reward(
|
||||||
state,
|
state,
|
||||||
inclusion.proposer_index,
|
inclusion.proposer_index,
|
||||||
validator_statuses.total_balances.previous_epoch,
|
validator_statuses.total_balances.current_epoch,
|
||||||
spec,
|
spec,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -109,10 +102,8 @@ fn get_proposer_deltas<T: EthSpec>(
|
|||||||
return Err(Error::ValidatorStatusesInconsistent);
|
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(())
|
Ok(())
|
||||||
@ -120,40 +111,30 @@ fn get_proposer_deltas<T: EthSpec>(
|
|||||||
|
|
||||||
/// Apply rewards for participation in attestations during the previous epoch.
|
/// Apply rewards for participation in attestations during the previous epoch.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
fn get_justification_and_finalization_deltas<T: EthSpec>(
|
fn get_attestation_deltas<T: EthSpec>(
|
||||||
deltas: &mut Vec<Delta>,
|
deltas: &mut Vec<Delta>,
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
validator_statuses: &ValidatorStatuses,
|
validator_statuses: &ValidatorStatuses,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> 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() {
|
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
|
||||||
let base_reward = get_base_reward(
|
let base_reward = get_base_reward(
|
||||||
state,
|
state,
|
||||||
index,
|
index,
|
||||||
validator_statuses.total_balances.previous_epoch,
|
validator_statuses.total_balances.current_epoch,
|
||||||
spec,
|
|
||||||
)?;
|
|
||||||
let inactivity_penalty = get_inactivity_penalty(
|
|
||||||
state,
|
|
||||||
index,
|
|
||||||
epochs_since_finality.as_u64(),
|
|
||||||
validator_statuses.total_balances.previous_epoch,
|
|
||||||
spec,
|
spec,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let delta = if epochs_since_finality <= 4 {
|
let delta = get_attestation_delta(
|
||||||
compute_normal_justification_and_finalization_delta(
|
&validator,
|
||||||
&validator,
|
&validator_statuses.total_balances,
|
||||||
&validator_statuses.total_balances,
|
base_reward,
|
||||||
base_reward,
|
finality_delay,
|
||||||
spec,
|
spec,
|
||||||
)
|
);
|
||||||
} else {
|
|
||||||
compute_inactivity_leak_delta(&validator, base_reward, inactivity_penalty, spec)
|
|
||||||
};
|
|
||||||
|
|
||||||
deltas[index] += delta;
|
deltas[index] += delta;
|
||||||
}
|
}
|
||||||
@ -161,51 +142,79 @@ fn get_justification_and_finalization_deltas<T: EthSpec>(
|
|||||||
Ok(())
|
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
|
/// Spec v0.6.1
|
||||||
fn compute_normal_justification_and_finalization_delta(
|
fn get_attestation_delta(
|
||||||
validator: &ValidatorStatus,
|
validator: &ValidatorStatus,
|
||||||
total_balances: &TotalBalances,
|
total_balances: &TotalBalances,
|
||||||
base_reward: u64,
|
base_reward: u64,
|
||||||
|
finality_delay: u64,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Delta {
|
) -> Delta {
|
||||||
let mut delta = Delta::default();
|
let mut delta = Delta::default();
|
||||||
|
|
||||||
let boundary_attesting_balance = total_balances.previous_epoch_boundary_attesters;
|
// Is this validator eligible to be rewarded or penalized?
|
||||||
let total_balance = total_balances.previous_epoch;
|
// 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 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.
|
// 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);
|
delta.reward(base_reward * total_attesting_balance / total_balance);
|
||||||
// Inclusion speed bonus
|
// Inclusion speed bonus
|
||||||
let inclusion = validator
|
let inclusion = validator
|
||||||
.inclusion_info
|
.inclusion_info
|
||||||
.expect("It is a logic error for an attester not to have an inclusion distance.");
|
.expect("It is a logic error for an attester not to have an inclusion distance.");
|
||||||
delta.reward(
|
delta.reward(base_reward * spec.min_attestation_inclusion_delay / inclusion.distance);
|
||||||
base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(),
|
} else {
|
||||||
);
|
|
||||||
} else if validator.is_active_in_previous_epoch {
|
|
||||||
delta.penalize(base_reward);
|
delta.penalize(base_reward);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expected FFG target.
|
// Expected FFG target.
|
||||||
if validator.is_previous_epoch_boundary_attester {
|
// Spec:
|
||||||
delta.reward(base_reward / boundary_attesting_balance / total_balance);
|
// - validator index in `get_unslashed_attesting_indices(state, matching_target_attestations)`
|
||||||
} else if validator.is_active_in_previous_epoch {
|
if validator.is_previous_epoch_target_attester && !validator.is_slashed {
|
||||||
|
delta.reward(base_reward * matching_target_balance / total_balance);
|
||||||
|
} else {
|
||||||
delta.penalize(base_reward);
|
delta.penalize(base_reward);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expected head.
|
// 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);
|
delta.reward(base_reward * matching_head_balance / total_balance);
|
||||||
} else if validator.is_active_in_previous_epoch {
|
} else {
|
||||||
delta.penalize(base_reward);
|
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
|
// This function only computes the delta for a single validator, so it cannot also return a
|
||||||
// delta for a validator.
|
// delta for a validator.
|
||||||
@ -213,55 +222,9 @@ fn compute_normal_justification_and_finalization_delta(
|
|||||||
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.
|
/// 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<T: EthSpec>(
|
fn get_crosslink_deltas<T: EthSpec>(
|
||||||
deltas: &mut Vec<Delta>,
|
deltas: &mut Vec<Delta>,
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
@ -274,7 +237,7 @@ fn get_crosslink_deltas<T: EthSpec>(
|
|||||||
let base_reward = get_base_reward(
|
let base_reward = get_base_reward(
|
||||||
state,
|
state,
|
||||||
index,
|
index,
|
||||||
validator_statuses.total_balances.previous_epoch,
|
validator_statuses.total_balances.current_epoch,
|
||||||
spec,
|
spec,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -295,40 +258,20 @@ fn get_crosslink_deltas<T: EthSpec>(
|
|||||||
|
|
||||||
/// Returns the base reward for some validator.
|
/// Returns the base reward for some validator.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
fn get_base_reward<T: EthSpec>(
|
fn get_base_reward<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
index: usize,
|
index: usize,
|
||||||
previous_total_balance: u64,
|
// Should be == get_total_active_balance(state, spec)
|
||||||
|
total_active_balance: u64,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<u64, BeaconStateError> {
|
) -> Result<u64, BeaconStateError> {
|
||||||
if previous_total_balance == 0 {
|
if total_active_balance == 0 {
|
||||||
Ok(0)
|
Ok(0)
|
||||||
} else {
|
} else {
|
||||||
let adjusted_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient;
|
let adjusted_quotient = total_active_balance.integer_sqrt() / spec.base_reward_quotient;
|
||||||
Ok(state.get_effective_balance(index, spec)? / adjusted_quotient / 5)
|
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<T: EthSpec>(
|
|
||||||
state: &BeaconState<T>,
|
|
||||||
index: usize,
|
|
||||||
epochs_since_finality: u64,
|
|
||||||
previous_total_balance: u64,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<u64, BeaconStateError> {
|
|
||||||
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<T: EthSpec>(state: &BeaconState<T>, spec: &ChainSpec) -> Epoch {
|
|
||||||
state.current_epoch(spec) + 1 - state.finalized_epoch
|
|
||||||
}
|
|
||||||
|
@ -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<T: EthSpec>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
attestation_data: &AttestationData,
|
||||||
|
bitfield: &Bitfield,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<Vec<usize>, 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())
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
use super::errors::InclusionError;
|
use super::errors::InclusionError;
|
||||||
use super::get_attestation_participants::get_attestation_participants;
|
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
/// Returns the distance between the first included attestation for some validator and this
|
/// Returns the distance between the first included attestation for some validator and this
|
||||||
@ -13,7 +12,9 @@ pub fn inclusion_distance<T: EthSpec>(
|
|||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<u64, InclusionError> {
|
) -> Result<u64, InclusionError> {
|
||||||
let attestation = earliest_included_attestation(state, attestations, validator_index, spec)?;
|
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.
|
/// Returns the slot of the earliest included attestation for some validator.
|
||||||
@ -25,8 +26,11 @@ pub fn inclusion_slot<T: EthSpec>(
|
|||||||
validator_index: usize,
|
validator_index: usize,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<Slot, InclusionError> {
|
) -> Result<Slot, InclusionError> {
|
||||||
|
/*
|
||||||
let attestation = earliest_included_attestation(state, attestations, validator_index, spec)?;
|
let attestation = earliest_included_attestation(state, attestations, validator_index, spec)?;
|
||||||
Ok(attestation.inclusion_slot)
|
Ok(attestation.inclusion_slot)
|
||||||
|
*/
|
||||||
|
unimplemented!("FIXME(sproul) inclusion slot")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the earliest included attestation for some validator.
|
/// Finds the earliest included attestation for some validator.
|
||||||
@ -38,6 +42,9 @@ fn earliest_included_attestation<T: EthSpec>(
|
|||||||
validator_index: usize,
|
validator_index: usize,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<PendingAttestation, InclusionError> {
|
) -> Result<PendingAttestation, InclusionError> {
|
||||||
|
// FIXME(sproul)
|
||||||
|
unimplemented!()
|
||||||
|
/*
|
||||||
let mut included_attestations = vec![];
|
let mut included_attestations = vec![];
|
||||||
|
|
||||||
for (i, a) in attestations.iter().enumerate() {
|
for (i, a) in attestations.iter().enumerate() {
|
||||||
@ -53,4 +60,5 @@ fn earliest_included_attestation<T: EthSpec>(
|
|||||||
.min_by_key(|i| attestations[**i].inclusion_slot)
|
.min_by_key(|i| attestations[**i].inclusion_slot)
|
||||||
.ok_or_else(|| InclusionError::NoAttestationsForValidator)?;
|
.ok_or_else(|| InclusionError::NoAttestationsForValidator)?;
|
||||||
Ok(attestations[*earliest_attestation_index].clone())
|
Ok(attestations[*earliest_attestation_index].clone())
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
@ -1,31 +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<T: EthSpec>(
|
|
||||||
state: &mut BeaconState<T>,
|
|
||||||
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<usize> = state
|
|
||||||
.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
|
|
||||||
.iter()
|
|
||||||
.filter_map(|&i| {
|
|
||||||
if state.validator_balances[i as usize] < spec.ejection_balance {
|
|
||||||
Some(i)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for validator_index in exitable {
|
|
||||||
exit_validator(state, validator_index, spec)?
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
use types::*;
|
|
||||||
|
|
||||||
/// Process the exit queue.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
pub fn process_exit_queue<T: EthSpec>(state: &mut BeaconState<T>, 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<usize> = (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() {
|
|
||||||
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<T: EthSpec>(
|
|
||||||
state: &mut BeaconState<T>,
|
|
||||||
validator_index: usize,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) {
|
|
||||||
state.validator_registry[validator_index].withdrawable_epoch =
|
|
||||||
state.current_epoch(spec) + spec.min_validator_withdrawability_delay;
|
|
||||||
}
|
|
@ -2,7 +2,7 @@ use types::{BeaconStateError as Error, *};
|
|||||||
|
|
||||||
/// Process slashings.
|
/// Process slashings.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn process_slashings<T: EthSpec>(
|
pub fn process_slashings<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
current_total_balance: u64,
|
current_total_balance: u64,
|
||||||
@ -24,10 +24,10 @@ pub fn process_slashings<T: EthSpec>(
|
|||||||
let penalty = std::cmp::max(
|
let penalty = std::cmp::max(
|
||||||
effective_balance * std::cmp::min(total_penalities * 3, current_total_balance)
|
effective_balance * std::cmp::min(total_penalities * 3, current_total_balance)
|
||||||
/ current_total_balance,
|
/ current_total_balance,
|
||||||
effective_balance / spec.min_penalty_quotient,
|
effective_balance / spec.min_slashing_penalty_quotient,
|
||||||
);
|
);
|
||||||
|
|
||||||
state.validator_balances[index] -= penalty;
|
safe_sub_assign!(state.balances[index], penalty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
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<T: EthSpec>(
|
||||||
|
state: &mut BeaconState<T>,
|
||||||
|
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(())
|
||||||
|
}
|
@ -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 super::WinningRootHashSet;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ pub struct InclusionInfo {
|
|||||||
pub slot: Slot,
|
pub slot: Slot,
|
||||||
/// The distance between the attestation slot and the slot that attestation was included in a
|
/// The distance between the attestation slot and the slot that attestation was included in a
|
||||||
/// block.
|
/// block.
|
||||||
pub distance: Slot,
|
pub distance: u64,
|
||||||
/// The index of the proposer at the slot where the attestation was included.
|
/// The index of the proposer at the slot where the attestation was included.
|
||||||
pub proposer_index: usize,
|
pub proposer_index: usize,
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ impl Default for InclusionInfo {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
slot: Slot::max_value(),
|
slot: Slot::max_value(),
|
||||||
distance: Slot::max_value(),
|
distance: u64::max_value(),
|
||||||
proposer_index: 0,
|
proposer_index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,17 +68,19 @@ pub struct ValidatorStatus {
|
|||||||
pub is_active_in_current_epoch: bool,
|
pub is_active_in_current_epoch: bool,
|
||||||
/// True if the validator was active in the state's _previous_ epoch.
|
/// True if the validator was active in the state's _previous_ epoch.
|
||||||
pub is_active_in_previous_epoch: bool,
|
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.
|
/// True if the validator had an attestation included in the _current_ epoch.
|
||||||
pub is_current_epoch_attester: bool,
|
pub is_current_epoch_attester: bool,
|
||||||
/// True if the validator's beacon block root attestation for the first slot of the _current_
|
/// 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.
|
/// 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.
|
/// True if the validator had an attestation included in the _previous_ epoch.
|
||||||
pub is_previous_epoch_attester: bool,
|
pub is_previous_epoch_attester: bool,
|
||||||
/// True if the validator's beacon block root attestation for the first slot of the _previous_
|
/// 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.
|
/// 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
|
/// 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.
|
/// attestation's slot (`attestation_data.slot`) matches the block root known to the state.
|
||||||
pub is_previous_epoch_head_attester: bool,
|
pub is_previous_epoch_head_attester: bool,
|
||||||
@ -106,9 +108,9 @@ 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_current_epoch);
|
||||||
set_self_if_other_is_true!(self, other, is_active_in_previous_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_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_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);
|
set_self_if_other_is_true!(self, other, is_previous_epoch_head_attester);
|
||||||
|
|
||||||
if let Some(other_info) = other.inclusion_info {
|
if let Some(other_info) = other.inclusion_info {
|
||||||
@ -133,12 +135,12 @@ pub struct TotalBalances {
|
|||||||
pub current_epoch_attesters: u64,
|
pub current_epoch_attesters: u64,
|
||||||
/// The total effective balance of all validators who attested during the _current_ epoch and
|
/// 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.
|
/// 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.
|
/// The total effective balance of all validators who attested during the _previous_ epoch.
|
||||||
pub previous_epoch_attesters: u64,
|
pub previous_epoch_attesters: u64,
|
||||||
/// The total effective balance of all validators who attested during the _previous_ epoch and
|
/// 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.
|
/// 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
|
/// 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.
|
/// agreed with the state about the beacon block at the time of attestation.
|
||||||
pub previous_epoch_head_attesters: u64,
|
pub previous_epoch_head_attesters: u64,
|
||||||
@ -160,7 +162,7 @@ impl ValidatorStatuses {
|
|||||||
/// - Active validators
|
/// - Active validators
|
||||||
/// - Total balances for the current and previous epochs.
|
/// - Total balances for the current and previous epochs.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn new<T: EthSpec>(
|
pub fn new<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
@ -169,21 +171,23 @@ impl ValidatorStatuses {
|
|||||||
let mut total_balances = TotalBalances::default();
|
let mut total_balances = TotalBalances::default();
|
||||||
|
|
||||||
for (i, validator) in state.validator_registry.iter().enumerate() {
|
for (i, validator) in state.validator_registry.iter().enumerate() {
|
||||||
|
let effective_balance = state.get_effective_balance(i, spec)?;
|
||||||
let mut status = ValidatorStatus {
|
let mut status = ValidatorStatus {
|
||||||
is_slashed: validator.slashed,
|
is_slashed: validator.slashed,
|
||||||
is_withdrawable_in_current_epoch: validator
|
is_withdrawable_in_current_epoch: validator
|
||||||
.is_withdrawable_at(state.current_epoch(spec)),
|
.is_withdrawable_at(state.current_epoch(spec)),
|
||||||
|
current_epoch_effective_balance: effective_balance,
|
||||||
..ValidatorStatus::default()
|
..ValidatorStatus::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
if validator.is_active_at(state.current_epoch(spec)) {
|
if validator.is_active_at(state.current_epoch(spec)) {
|
||||||
status.is_active_in_current_epoch = true;
|
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)) {
|
if validator.is_active_at(state.previous_epoch(spec)) {
|
||||||
status.is_active_in_previous_epoch = true;
|
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);
|
statuses.push(status);
|
||||||
@ -198,7 +202,7 @@ impl ValidatorStatuses {
|
|||||||
/// Process some attestations from the given `state` updating the `statuses` and
|
/// Process some attestations from the given `state` updating the `statuses` and
|
||||||
/// `total_balances` fields.
|
/// `total_balances` fields.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn process_attestations<T: EthSpec>(
|
pub fn process_attestations<T: EthSpec>(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
@ -210,44 +214,40 @@ impl ValidatorStatuses {
|
|||||||
.chain(state.current_epoch_attestations.iter())
|
.chain(state.current_epoch_attestations.iter())
|
||||||
{
|
{
|
||||||
let attesting_indices =
|
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 attesting_balance = state.get_total_balance(&attesting_indices, spec)?;
|
|
||||||
|
|
||||||
let mut status = ValidatorStatus::default();
|
let mut status = ValidatorStatus::default();
|
||||||
|
|
||||||
// Profile this attestation, updating the total balances and generating an
|
// Profile this attestation, updating the total balances and generating an
|
||||||
// `ValidatorStatus` object that applies to all participants in the attestation.
|
// `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)) {
|
||||||
self.total_balances.current_epoch_attesters += attesting_balance;
|
|
||||||
status.is_current_epoch_attester = true;
|
status.is_current_epoch_attester = true;
|
||||||
|
|
||||||
if has_common_epoch_boundary_root(a, state, state.current_epoch(spec), spec)? {
|
if target_matches_epoch_start_block(a, state, state.current_epoch(spec), spec)? {
|
||||||
self.total_balances.current_epoch_boundary_attesters += attesting_balance;
|
status.is_current_epoch_target_attester = true;
|
||||||
status.is_current_epoch_boundary_attester = true;
|
|
||||||
}
|
}
|
||||||
} else if is_from_epoch(a, state.previous_epoch(spec), spec) {
|
} else if is_from_epoch(a, state.previous_epoch(spec)) {
|
||||||
self.total_balances.previous_epoch_attesters += attesting_balance;
|
|
||||||
status.is_previous_epoch_attester = true;
|
status.is_previous_epoch_attester = true;
|
||||||
|
|
||||||
// The inclusion slot and distance are only required for previous epoch attesters.
|
// 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 {
|
status.inclusion_info = Some(InclusionInfo {
|
||||||
slot: a.inclusion_slot,
|
slot: inclusion_slot,
|
||||||
distance: inclusion_distance(a),
|
distance: a.inclusion_delay,
|
||||||
proposer_index: state.get_beacon_proposer_index(
|
proposer_index: state.get_beacon_proposer_index(
|
||||||
a.inclusion_slot,
|
attestation_slot,
|
||||||
relative_epoch,
|
relative_epoch,
|
||||||
spec,
|
spec,
|
||||||
)?,
|
)?,
|
||||||
});
|
});
|
||||||
|
|
||||||
if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? {
|
if target_matches_epoch_start_block(a, state, state.previous_epoch(spec), spec)? {
|
||||||
self.total_balances.previous_epoch_boundary_attesters += attesting_balance;
|
status.is_previous_epoch_target_attester = true;
|
||||||
status.is_previous_epoch_boundary_attester = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_common_beacon_block_root(a, state)? {
|
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;
|
status.is_previous_epoch_head_attester = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -258,13 +258,37 @@ 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_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;
|
||||||
|
}
|
||||||
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the `statuses` for each validator based upon whether or not they attested to the
|
/// Update the `statuses` for each validator based upon whether or not they attested to the
|
||||||
/// "winning" shard block root for the previous epoch.
|
/// "winning" shard block root for the previous epoch.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn process_winning_roots<T: EthSpec>(
|
pub fn process_winning_roots<T: EthSpec>(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
@ -297,26 +321,18 @@ 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`.
|
/// Returns `true` if some `PendingAttestation` is from the supplied `epoch`.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool {
|
fn is_from_epoch(a: &PendingAttestation, epoch: Epoch) -> bool {
|
||||||
a.data.slot.epoch(spec.slots_per_epoch) == epoch
|
a.data.target_epoch == epoch
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
|
/// Returns `true` if the attestation's FFG target is equal to the hash of the `state`'s first
|
||||||
/// the first slot of the given epoch.
|
/// beacon block in the given `epoch`.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
fn has_common_epoch_boundary_root<T: EthSpec>(
|
fn target_matches_epoch_start_block<T: EthSpec>(
|
||||||
a: &PendingAttestation,
|
a: &PendingAttestation,
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
epoch: Epoch,
|
epoch: Epoch,
|
||||||
@ -331,12 +347,14 @@ fn has_common_epoch_boundary_root<T: EthSpec>(
|
|||||||
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
|
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
|
||||||
/// the current slot of the `PendingAttestation`.
|
/// the current slot of the `PendingAttestation`.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
fn has_common_beacon_block_root<T: EthSpec>(
|
fn has_common_beacon_block_root<T: EthSpec>(
|
||||||
a: &PendingAttestation,
|
a: &PendingAttestation,
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
|
spec: &ChainSpec,
|
||||||
) -> Result<bool, BeaconStateError> {
|
) -> Result<bool, BeaconStateError> {
|
||||||
let state_block_root = *state.get_block_root(a.data.slot)?;
|
let attestation_slot = state.get_attestation_slot(&a.data, spec)?;
|
||||||
|
let state_block_root = *state.get_block_root(attestation_slot)?;
|
||||||
|
|
||||||
Ok(a.data.beacon_block_root == state_block_root)
|
Ok(a.data.beacon_block_root == state_block_root)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use super::get_attestation_participants::get_attestation_participants;
|
use super::get_attesting_indices::get_attesting_indices_unsorted;
|
||||||
use std::collections::HashSet;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::iter::FromIterator;
|
use tree_hash::TreeHash;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WinningRoot {
|
pub struct WinningRoot {
|
||||||
pub crosslink_data_root: Hash256,
|
pub crosslink: Crosslink,
|
||||||
pub attesting_validator_indices: Vec<usize>,
|
pub attesting_validator_indices: Vec<usize>,
|
||||||
pub total_attesting_balance: u64,
|
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
|
/// 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.
|
/// 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 {
|
pub fn is_better_than(&self, other: &Self) -> bool {
|
||||||
if self.total_attesting_balance > other.total_attesting_balance {
|
(
|
||||||
true
|
self.total_attesting_balance,
|
||||||
} else if self.total_attesting_balance == other.total_attesting_balance {
|
self.crosslink.crosslink_data_root,
|
||||||
self.crosslink_data_root > other.crosslink_data_root
|
) > (
|
||||||
} else {
|
other.total_attesting_balance,
|
||||||
false
|
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
|
/// The `WinningRoot` object also contains additional fields that are useful in later stages of
|
||||||
/// per-epoch processing.
|
/// per-epoch processing.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn winning_root<T: EthSpec>(
|
pub fn winning_root<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
shard: u64,
|
shard: u64,
|
||||||
|
epoch: Epoch,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<Option<WinningRoot>, BeaconStateError> {
|
) -> Result<Option<WinningRoot>, BeaconStateError> {
|
||||||
let mut winning_root: Option<WinningRoot> = 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<Hash256> = HashSet::from_iter(
|
let shard_crosslinks = shard_attestations.iter().map(|att| {
|
||||||
state
|
(
|
||||||
.previous_epoch_attestations
|
att,
|
||||||
.iter()
|
state.get_crosslink_from_attestation_data(&att.data, spec),
|
||||||
.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
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
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<Crosslink, Vec<&PendingAttestation>> = 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 =
|
let attesting_validator_indices =
|
||||||
get_attesting_validator_indices(state, shard, &crosslink_data_root, spec)?;
|
get_unslashed_attesting_indices_unsorted(state, &attestations, spec)?;
|
||||||
|
let total_attesting_balance =
|
||||||
let total_attesting_balance: u64 =
|
state.get_total_balance(&attesting_validator_indices, spec)?;
|
||||||
attesting_validator_indices
|
|
||||||
.iter()
|
|
||||||
.try_fold(0_u64, |acc, i| {
|
|
||||||
state
|
|
||||||
.get_effective_balance(*i, spec)
|
|
||||||
.and_then(|bal| Ok(acc + bal))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let candidate = WinningRoot {
|
let candidate = WinningRoot {
|
||||||
crosslink_data_root,
|
crosslink,
|
||||||
attesting_validator_indices,
|
attesting_validator_indices,
|
||||||
total_attesting_balance,
|
total_attesting_balance,
|
||||||
};
|
};
|
||||||
@ -87,56 +99,29 @@ pub fn winning_root<T: EthSpec>(
|
|||||||
Ok(winning_root)
|
Ok(winning_root)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if pending attestation `a` is eligible to become a winning root.
|
pub fn get_unslashed_attesting_indices_unsorted<T: EthSpec>(
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
fn is_eligible_for_winning_root<T: EthSpec>(
|
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
a: &PendingAttestation,
|
attestations: &[&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<T: EthSpec>(
|
|
||||||
state: &BeaconState<T>,
|
|
||||||
shard: u64,
|
|
||||||
crosslink_data_root: &Hash256,
|
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<Vec<usize>, BeaconStateError> {
|
) -> Result<Vec<usize>, BeaconStateError> {
|
||||||
let mut indices = vec![];
|
let mut output = HashSet::new();
|
||||||
|
for a in attestations {
|
||||||
for a in state
|
output.extend(get_attesting_indices_unsorted(
|
||||||
.current_epoch_attestations
|
state,
|
||||||
.iter()
|
&a.data,
|
||||||
.chain(state.previous_epoch_attestations.iter())
|
&a.aggregation_bitfield,
|
||||||
{
|
spec,
|
||||||
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,
|
|
||||||
)?);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Ok(output
|
||||||
// Sort the list (required for dedup). "Unstable" means the sort may re-order equal elements,
|
.into_iter()
|
||||||
// this causes no issue here.
|
.filter(|index| {
|
||||||
//
|
state
|
||||||
// These sort + dedup ops are potentially good CPU time optimisation targets.
|
.validator_registry
|
||||||
indices.sort_unstable();
|
.get(*index)
|
||||||
// Remove all duplicate indices (requires a sorted list).
|
.map_or(false, |v| !v.slashed)
|
||||||
indices.dedup();
|
})
|
||||||
|
.collect())
|
||||||
Ok(indices)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -146,15 +131,17 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn is_better_than() {
|
fn is_better_than() {
|
||||||
let worse = WinningRoot {
|
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![],
|
attesting_validator_indices: vec![],
|
||||||
total_attesting_balance: 42,
|
total_attesting_balance: 42,
|
||||||
};
|
};
|
||||||
|
|
||||||
let better = WinningRoot {
|
let mut better = worse.clone();
|
||||||
crosslink_data_root: Hash256::from_slice(&[2; 32]),
|
better.crosslink.crosslink_data_root = Hash256::from_slice(&[2; 32]);
|
||||||
..worse.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(better.is_better_than(&worse));
|
assert!(better.is_better_than(&worse));
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
|
|||||||
|
|
||||||
/// Details an attestation that can be slashable.
|
/// Details an attestation that can be slashable.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.0
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
Clone,
|
Clone,
|
||||||
@ -28,7 +28,7 @@ pub struct Attestation {
|
|||||||
pub data: AttestationData,
|
pub data: AttestationData,
|
||||||
pub custody_bitfield: Bitfield,
|
pub custody_bitfield: Bitfield,
|
||||||
#[signed_root(skip_hashing)]
|
#[signed_root(skip_hashing)]
|
||||||
pub aggregate_signature: AggregateSignature,
|
pub signature: AggregateSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Attestation {
|
impl Attestation {
|
||||||
@ -49,8 +49,7 @@ impl Attestation {
|
|||||||
self.aggregation_bitfield
|
self.aggregation_bitfield
|
||||||
.union_inplace(&other.aggregation_bitfield);
|
.union_inplace(&other.aggregation_bitfield);
|
||||||
self.custody_bitfield.union_inplace(&other.custody_bitfield);
|
self.custody_bitfield.union_inplace(&other.custody_bitfield);
|
||||||
self.aggregate_signature
|
self.signature.add_aggregate(&other.signature);
|
||||||
.add_aggregate(&other.aggregate_signature);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::test_utils::TestRandom;
|
use crate::test_utils::TestRandom;
|
||||||
use crate::{Crosslink, Epoch, Hash256, Slot};
|
use crate::{Epoch, Hash256};
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use ssz_derive::{Decode, Encode};
|
use ssz_derive::{Decode, Encode};
|
||||||
@ -9,7 +9,7 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
|
|||||||
|
|
||||||
/// The data upon which an attestation is based.
|
/// The data upon which an attestation is based.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
Clone,
|
Clone,
|
||||||
@ -27,17 +27,17 @@ use tree_hash_derive::{CachedTreeHash, SignedRoot, TreeHash};
|
|||||||
)]
|
)]
|
||||||
pub struct AttestationData {
|
pub struct AttestationData {
|
||||||
// LMD GHOST vote
|
// LMD GHOST vote
|
||||||
pub slot: Slot,
|
|
||||||
pub beacon_block_root: Hash256,
|
pub beacon_block_root: Hash256,
|
||||||
|
|
||||||
// FFG Vote
|
// FFG Vote
|
||||||
pub source_epoch: Epoch,
|
pub source_epoch: Epoch,
|
||||||
pub source_root: Hash256,
|
pub source_root: Hash256,
|
||||||
|
pub target_epoch: Epoch,
|
||||||
pub target_root: Hash256,
|
pub target_root: Hash256,
|
||||||
|
|
||||||
// Crosslink Vote
|
// Crosslink Vote
|
||||||
pub shard: u64,
|
pub shard: u64,
|
||||||
pub previous_crosslink: Crosslink,
|
pub previous_crosslink_root: Hash256,
|
||||||
pub crosslink_data_root: Hash256,
|
pub crosslink_data_root: Hash256,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{test_utils::TestRandom, SlashableAttestation};
|
use crate::{test_utils::TestRandom, IndexedAttestation};
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use ssz_derive::{Decode, Encode};
|
use ssz_derive::{Decode, Encode};
|
||||||
@ -7,7 +7,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
|
|
||||||
/// Two conflicting attestations.
|
/// Two conflicting attestations.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.0
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
PartialEq,
|
PartialEq,
|
||||||
@ -21,8 +21,8 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
TestRandom,
|
TestRandom,
|
||||||
)]
|
)]
|
||||||
pub struct AttesterSlashing {
|
pub struct AttesterSlashing {
|
||||||
pub slashable_attestation_1: SlashableAttestation,
|
pub attestation_1: IndexedAttestation,
|
||||||
pub slashable_attestation_2: SlashableAttestation,
|
pub attestation_2: IndexedAttestation,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -43,10 +43,11 @@ impl BeaconBlock {
|
|||||||
previous_block_root: spec.zero_hash,
|
previous_block_root: spec.zero_hash,
|
||||||
state_root: spec.zero_hash,
|
state_root: spec.zero_hash,
|
||||||
body: BeaconBlockBody {
|
body: BeaconBlockBody {
|
||||||
randao_reveal: spec.empty_signature.clone(),
|
randao_reveal: Signature::empty_signature(),
|
||||||
eth1_data: Eth1Data {
|
eth1_data: Eth1Data {
|
||||||
deposit_root: spec.zero_hash,
|
deposit_root: spec.zero_hash,
|
||||||
block_hash: spec.zero_hash,
|
block_hash: spec.zero_hash,
|
||||||
|
deposit_count: 0,
|
||||||
},
|
},
|
||||||
proposer_slashings: vec![],
|
proposer_slashings: vec![],
|
||||||
attester_slashings: vec![],
|
attester_slashings: vec![],
|
||||||
@ -55,7 +56,7 @@ impl BeaconBlock {
|
|||||||
voluntary_exits: vec![],
|
voluntary_exits: vec![],
|
||||||
transfers: vec![],
|
transfers: vec![],
|
||||||
},
|
},
|
||||||
signature: spec.empty_signature.clone(),
|
signature: Signature::empty_signature(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +91,7 @@ impl BeaconBlock {
|
|||||||
pub fn temporary_block_header(&self, spec: &ChainSpec) -> BeaconBlockHeader {
|
pub fn temporary_block_header(&self, spec: &ChainSpec) -> BeaconBlockHeader {
|
||||||
BeaconBlockHeader {
|
BeaconBlockHeader {
|
||||||
state_root: spec.zero_hash,
|
state_root: spec.zero_hash,
|
||||||
signature: spec.empty_signature.clone(),
|
signature: Signature::empty_signature(),
|
||||||
..self.block_header()
|
..self.block_header()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use self::epoch_cache::{get_active_validator_indices, EpochCache, Error as EpochCacheError};
|
use self::epoch_cache::{get_active_validator_indices, EpochCache, Error as EpochCacheError};
|
||||||
|
use self::exit_cache::ExitCache;
|
||||||
use crate::test_utils::TestRandom;
|
use crate::test_utils::TestRandom;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use cached_tree_hash::{Error as TreeHashCacheError, TreeHashCache};
|
use cached_tree_hash::{Error as TreeHashCacheError, TreeHashCache};
|
||||||
@ -18,6 +19,7 @@ pub use beacon_state_types::*;
|
|||||||
|
|
||||||
mod beacon_state_types;
|
mod beacon_state_types;
|
||||||
mod epoch_cache;
|
mod epoch_cache;
|
||||||
|
mod exit_cache;
|
||||||
mod pubkey_cache;
|
mod pubkey_cache;
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
@ -77,17 +79,11 @@ where
|
|||||||
|
|
||||||
// Validator registry
|
// Validator registry
|
||||||
pub validator_registry: Vec<Validator>,
|
pub validator_registry: Vec<Validator>,
|
||||||
pub validator_balances: Vec<u64>,
|
pub balances: Vec<u64>,
|
||||||
pub validator_registry_update_epoch: Epoch,
|
|
||||||
|
|
||||||
// Randomness and committees
|
// Randomness and committees
|
||||||
pub latest_randao_mixes: FixedLenVec<Hash256, T::LatestRandaoMixesLength>,
|
pub latest_randao_mixes: FixedLenVec<Hash256, T::LatestRandaoMixesLength>,
|
||||||
pub previous_shuffling_start_shard: u64,
|
pub latest_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,
|
|
||||||
|
|
||||||
// Finality
|
// Finality
|
||||||
pub previous_epoch_attestations: Vec<PendingAttestation>,
|
pub previous_epoch_attestations: Vec<PendingAttestation>,
|
||||||
@ -101,7 +97,8 @@ where
|
|||||||
pub finalized_root: Hash256,
|
pub finalized_root: Hash256,
|
||||||
|
|
||||||
// Recent state
|
// Recent state
|
||||||
pub latest_crosslinks: FixedLenVec<Crosslink, T::ShardCount>,
|
pub current_crosslinks: FixedLenVec<Crosslink, T::ShardCount>,
|
||||||
|
pub previous_crosslinks: FixedLenVec<Crosslink, T::ShardCount>,
|
||||||
pub latest_block_roots: FixedLenVec<Hash256, T::SlotsPerHistoricalRoot>,
|
pub latest_block_roots: FixedLenVec<Hash256, T::SlotsPerHistoricalRoot>,
|
||||||
latest_state_roots: FixedLenVec<Hash256, T::SlotsPerHistoricalRoot>,
|
latest_state_roots: FixedLenVec<Hash256, T::SlotsPerHistoricalRoot>,
|
||||||
latest_active_index_roots: FixedLenVec<Hash256, T::LatestActiveIndexRootsLength>,
|
latest_active_index_roots: FixedLenVec<Hash256, T::LatestActiveIndexRootsLength>,
|
||||||
@ -111,7 +108,7 @@ where
|
|||||||
|
|
||||||
// Ethereum 1.0 chain data
|
// Ethereum 1.0 chain data
|
||||||
pub latest_eth1_data: Eth1Data,
|
pub latest_eth1_data: Eth1Data,
|
||||||
pub eth1_data_votes: Vec<Eth1DataVote>,
|
pub eth1_data_votes: Vec<Eth1Data>,
|
||||||
pub deposit_index: u64,
|
pub deposit_index: u64,
|
||||||
|
|
||||||
// Caching (not in the spec)
|
// Caching (not in the spec)
|
||||||
@ -139,6 +136,12 @@ where
|
|||||||
#[tree_hash(skip_hashing)]
|
#[tree_hash(skip_hashing)]
|
||||||
#[test_random(default)]
|
#[test_random(default)]
|
||||||
pub tree_hash_cache: TreeHashCache,
|
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<T: EthSpec> BeaconState<T> {
|
impl<T: EthSpec> BeaconState<T> {
|
||||||
@ -155,6 +158,7 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
) -> BeaconState<T> {
|
) -> BeaconState<T> {
|
||||||
let initial_crosslink = Crosslink {
|
let initial_crosslink = Crosslink {
|
||||||
epoch: spec.genesis_epoch,
|
epoch: spec.genesis_epoch,
|
||||||
|
previous_crosslink_root: spec.zero_hash,
|
||||||
crosslink_data_root: spec.zero_hash,
|
crosslink_data_root: spec.zero_hash,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -166,20 +170,14 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
|
|
||||||
// Validator registry
|
// Validator registry
|
||||||
validator_registry: vec![], // Set later in the function.
|
validator_registry: vec![], // Set later in the function.
|
||||||
validator_balances: vec![], // Set later in the function.
|
balances: vec![], // Set later in the function.
|
||||||
validator_registry_update_epoch: spec.genesis_epoch,
|
|
||||||
|
|
||||||
// Randomness and committees
|
// Randomness and committees
|
||||||
latest_randao_mixes: FixedLenVec::from(vec![
|
latest_randao_mixes: FixedLenVec::from(vec![
|
||||||
spec.zero_hash;
|
spec.zero_hash;
|
||||||
T::LatestRandaoMixesLength::to_usize()
|
T::LatestRandaoMixesLength::to_usize()
|
||||||
]),
|
]),
|
||||||
previous_shuffling_start_shard: spec.genesis_start_shard,
|
latest_start_shard: 0,
|
||||||
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,
|
|
||||||
|
|
||||||
// Finality
|
// Finality
|
||||||
previous_epoch_attestations: vec![],
|
previous_epoch_attestations: vec![],
|
||||||
@ -193,22 +191,16 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
finalized_root: spec.zero_hash,
|
finalized_root: spec.zero_hash,
|
||||||
|
|
||||||
// Recent state
|
// Recent state
|
||||||
latest_crosslinks: vec![initial_crosslink; spec.shard_count as usize].into(),
|
current_crosslinks: vec![initial_crosslink.clone(); T::ShardCount::to_usize()].into(),
|
||||||
latest_block_roots: FixedLenVec::from(vec![
|
previous_crosslinks: vec![initial_crosslink; T::ShardCount::to_usize()].into(),
|
||||||
|
latest_block_roots: vec![spec.zero_hash; T::SlotsPerHistoricalRoot::to_usize()].into(),
|
||||||
|
latest_state_roots: vec![spec.zero_hash; T::SlotsPerHistoricalRoot::to_usize()].into(),
|
||||||
|
latest_active_index_roots: vec![
|
||||||
spec.zero_hash;
|
spec.zero_hash;
|
||||||
T::SlotsPerHistoricalRoot::to_usize()
|
T::LatestActiveIndexRootsLength::to_usize()
|
||||||
]),
|
]
|
||||||
latest_state_roots: FixedLenVec::from(vec![
|
.into(),
|
||||||
spec.zero_hash;
|
latest_slashed_balances: vec![0; T::LatestSlashedExitLength::to_usize()].into(),
|
||||||
T::SlotsPerHistoricalRoot::to_usize()
|
|
||||||
]),
|
|
||||||
latest_active_index_roots: FixedLenVec::from(
|
|
||||||
vec![spec.zero_hash; T::LatestActiveIndexRootsLength::to_usize()],
|
|
||||||
),
|
|
||||||
latest_slashed_balances: FixedLenVec::from(vec![
|
|
||||||
0;
|
|
||||||
T::LatestSlashedExitLength::to_usize()
|
|
||||||
]),
|
|
||||||
latest_block_header: BeaconBlock::empty(spec).temporary_block_header(spec),
|
latest_block_header: BeaconBlock::empty(spec).temporary_block_header(spec),
|
||||||
historical_roots: vec![],
|
historical_roots: vec![],
|
||||||
|
|
||||||
@ -231,6 +223,7 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
],
|
],
|
||||||
pubkey_cache: PubkeyCache::default(),
|
pubkey_cache: PubkeyCache::default(),
|
||||||
tree_hash_cache: TreeHashCache::default(),
|
tree_hash_cache: TreeHashCache::default(),
|
||||||
|
exit_cache: ExitCache::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,9 +267,14 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
///
|
///
|
||||||
/// If the current epoch is the genesis epoch, the genesis_epoch is returned.
|
/// 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 {
|
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()`.
|
/// The epoch following `self.current_epoch()`.
|
||||||
@ -286,6 +284,60 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
self.current_epoch(spec) + 1
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<u64, Error> {
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.1
|
||||||
|
pub fn get_attestation_slot(
|
||||||
|
&self,
|
||||||
|
attestation_data: &AttestationData,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<Slot, Error> {
|
||||||
|
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
|
/// Returns the active validator indices for the given epoch, assuming there is no validator
|
||||||
/// registry update in the next epoch.
|
/// registry update in the next epoch.
|
||||||
///
|
///
|
||||||
@ -339,6 +391,17 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
.ok_or_else(|| Error::SlotOutOfBounds)?)
|
.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.
|
/// Returns the crosslink committees for some shard in an epoch.
|
||||||
///
|
///
|
||||||
/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized.
|
/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized.
|
||||||
@ -415,6 +478,18 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
Ok(&self.latest_block_roots[i])
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the block root for some given slot.
|
/// Sets the block root for some given slot.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.5.1
|
||||||
@ -488,14 +563,13 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
|
|
||||||
/// Safely obtains the index for `latest_active_index_roots`, given some `epoch`.
|
/// 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<usize, Error> {
|
fn get_active_index_root_index(&self, epoch: Epoch, spec: &ChainSpec) -> Result<usize, Error> {
|
||||||
let current_epoch = self.current_epoch(spec);
|
let current_epoch = self.current_epoch(spec);
|
||||||
|
|
||||||
if (current_epoch - self.latest_active_index_roots.len() as u64
|
if current_epoch - self.latest_active_index_roots.len() as u64 + spec.activation_exit_delay
|
||||||
+ spec.activation_exit_delay
|
< epoch
|
||||||
< epoch)
|
&& epoch <= current_epoch + spec.activation_exit_delay
|
||||||
& (epoch <= current_epoch + spec.activation_exit_delay)
|
|
||||||
{
|
{
|
||||||
Ok(epoch.as_usize() % self.latest_active_index_roots.len())
|
Ok(epoch.as_usize() % self.latest_active_index_roots.len())
|
||||||
} else {
|
} else {
|
||||||
@ -505,7 +579,7 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
|
|
||||||
/// Return the `active_index_root` at a recent `epoch`.
|
/// 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<Hash256, Error> {
|
pub fn get_active_index_root(&self, epoch: Epoch, spec: &ChainSpec) -> Result<Hash256, Error> {
|
||||||
let i = self.get_active_index_root_index(epoch, spec)?;
|
let i = self.get_active_index_root_index(epoch, spec)?;
|
||||||
Ok(self.latest_active_index_roots[i])
|
Ok(self.latest_active_index_roots[i])
|
||||||
@ -513,7 +587,7 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
|
|
||||||
/// Set the `active_index_root` at a recent `epoch`.
|
/// Set the `active_index_root` at a recent `epoch`.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn set_active_index_root(
|
pub fn set_active_index_root(
|
||||||
&mut self,
|
&mut self,
|
||||||
epoch: Epoch,
|
epoch: Epoch,
|
||||||
@ -563,7 +637,7 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
|
|
||||||
/// Safely obtains the index for `latest_slashed_balances`, given some `epoch`.
|
/// 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) -> Result<usize, Error> {
|
fn get_slashed_balance_index(&self, epoch: Epoch) -> Result<usize, Error> {
|
||||||
let i = epoch.as_usize() % self.latest_slashed_balances.len();
|
let i = epoch.as_usize() % self.latest_slashed_balances.len();
|
||||||
|
|
||||||
@ -578,7 +652,7 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
|
|
||||||
/// Gets the total slashed balances for some epoch.
|
/// Gets the total slashed balances for some epoch.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn get_slashed_balance(&self, epoch: Epoch) -> Result<u64, Error> {
|
pub fn get_slashed_balance(&self, epoch: Epoch) -> Result<u64, Error> {
|
||||||
let i = self.get_slashed_balance_index(epoch)?;
|
let i = self.get_slashed_balance_index(epoch)?;
|
||||||
Ok(self.latest_slashed_balances[i])
|
Ok(self.latest_slashed_balances[i])
|
||||||
@ -586,13 +660,48 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
|
|
||||||
/// Sets the total slashed balances for some epoch.
|
/// 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, balance: u64) -> Result<(), Error> {
|
pub fn set_slashed_balance(&mut self, epoch: Epoch, balance: u64) -> Result<(), Error> {
|
||||||
let i = self.get_slashed_balance_index(epoch)?;
|
let i = self.get_slashed_balance_index(epoch)?;
|
||||||
self.latest_slashed_balances[i] = balance;
|
self.latest_slashed_balances[i] = balance;
|
||||||
Ok(())
|
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`.
|
/// Generate a seed for the given `epoch`.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.5.1
|
||||||
@ -611,17 +720,16 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
|
|
||||||
/// Return the effective balance (also known as "balance at stake") for a validator with the given ``index``.
|
/// 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(
|
pub fn get_effective_balance(
|
||||||
&self,
|
&self,
|
||||||
validator_index: usize,
|
validator_index: usize,
|
||||||
spec: &ChainSpec,
|
_spec: &ChainSpec,
|
||||||
) -> Result<u64, Error> {
|
) -> Result<u64, Error> {
|
||||||
let balance = self
|
self.validator_registry
|
||||||
.validator_balances
|
|
||||||
.get(validator_index)
|
.get(validator_index)
|
||||||
.ok_or_else(|| Error::UnknownValidator)?;
|
.map(|v| v.effective_balance)
|
||||||
Ok(std::cmp::min(*balance, spec.max_deposit_amount))
|
.ok_or_else(|| Error::UnknownValidator)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect.
|
/// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect.
|
||||||
@ -631,11 +739,19 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
epoch + 1 + spec.activation_exit_delay
|
epoch + 1 + spec.activation_exit_delay
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initiate an exit for the validator of the given `index`.
|
/// Return the churn limit for the current epoch (number of validators who can leave per epoch).
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Uses the epoch cache, and will error if it isn't initialized.
|
||||||
pub fn initiate_validator_exit(&mut self, validator_index: usize) {
|
///
|
||||||
self.validator_registry[validator_index].initiated_exit = true;
|
/// Spec v0.6.1
|
||||||
|
pub fn get_churn_limit(&self, spec: &ChainSpec) -> Result<u64, Error> {
|
||||||
|
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,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an
|
/// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an
|
||||||
@ -661,7 +777,7 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
|
|
||||||
/// Return the combined effective balance of an array of validators.
|
/// Return the combined effective balance of an array of validators.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.0
|
||||||
pub fn get_total_balance(
|
pub fn get_total_balance(
|
||||||
&self,
|
&self,
|
||||||
validator_indices: &[usize],
|
validator_indices: &[usize],
|
||||||
@ -681,6 +797,8 @@ impl<T: EthSpec> BeaconState<T> {
|
|||||||
self.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec)?;
|
self.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec)?;
|
||||||
self.update_pubkey_cache()?;
|
self.update_pubkey_cache()?;
|
||||||
self.update_tree_hash_cache()?;
|
self.update_tree_hash_cache()?;
|
||||||
|
self.exit_cache
|
||||||
|
.build_from_registry(&self.validator_registry, spec);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -207,8 +207,9 @@ impl EpochCrosslinkCommitteesBuilder {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
epoch: state.previous_epoch(spec),
|
epoch: state.previous_epoch(spec),
|
||||||
shuffling_start_shard: state.previous_shuffling_start_shard,
|
// FIXME(sproul)
|
||||||
shuffling_seed: state.previous_shuffling_seed,
|
shuffling_start_shard: 0,
|
||||||
|
shuffling_seed: spec.zero_hash,
|
||||||
committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()),
|
committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()),
|
||||||
active_validator_indices,
|
active_validator_indices,
|
||||||
}
|
}
|
||||||
@ -222,8 +223,9 @@ impl EpochCrosslinkCommitteesBuilder {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
epoch: state.current_epoch(spec),
|
epoch: state.current_epoch(spec),
|
||||||
shuffling_start_shard: state.current_shuffling_start_shard,
|
// FIXME(sproul)
|
||||||
shuffling_seed: state.current_shuffling_seed,
|
shuffling_start_shard: 0,
|
||||||
|
shuffling_seed: spec.zero_hash,
|
||||||
committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()),
|
committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()),
|
||||||
active_validator_indices,
|
active_validator_indices,
|
||||||
}
|
}
|
||||||
@ -243,8 +245,9 @@ impl EpochCrosslinkCommitteesBuilder {
|
|||||||
let next_epoch = state.next_epoch(spec);
|
let next_epoch = state.next_epoch(spec);
|
||||||
let committees_per_epoch = spec.get_epoch_committee_count(active_validator_indices.len());
|
let committees_per_epoch = spec.get_epoch_committee_count(active_validator_indices.len());
|
||||||
|
|
||||||
let epochs_since_last_registry_update =
|
// FIXME(sproul)
|
||||||
current_epoch - state.validator_registry_update_epoch;
|
// current_epoch - state.validator_registry_update_epoch;
|
||||||
|
let epochs_since_last_registry_update = 0u64;
|
||||||
|
|
||||||
let (seed, shuffling_start_shard) = if registry_change {
|
let (seed, shuffling_start_shard) = if registry_change {
|
||||||
let next_seed = state
|
let next_seed = state
|
||||||
@ -252,7 +255,9 @@ impl EpochCrosslinkCommitteesBuilder {
|
|||||||
.map_err(|_| Error::UnableToGenerateSeed)?;
|
.map_err(|_| Error::UnableToGenerateSeed)?;
|
||||||
(
|
(
|
||||||
next_seed,
|
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)
|
} else if (epochs_since_last_registry_update > 1)
|
||||||
& epochs_since_last_registry_update.is_power_of_two()
|
& epochs_since_last_registry_update.is_power_of_two()
|
||||||
@ -260,11 +265,13 @@ impl EpochCrosslinkCommitteesBuilder {
|
|||||||
let next_seed = state
|
let next_seed = state
|
||||||
.generate_seed(next_epoch, spec)
|
.generate_seed(next_epoch, spec)
|
||||||
.map_err(|_| Error::UnableToGenerateSeed)?;
|
.map_err(|_| Error::UnableToGenerateSeed)?;
|
||||||
(next_seed, state.current_shuffling_start_shard)
|
(
|
||||||
|
next_seed, 0, /* FIXME(sproul) state.current_shuffling_start_shard*/
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
state.current_shuffling_seed,
|
spec.zero_hash, // state.current_shuffling_seed,
|
||||||
state.current_shuffling_start_shard,
|
0 // state.current_shuffling_start_shard,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#![cfg(test)]
|
#![cfg(all(not(test), test))]
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::beacon_state::FewValidatorsEthSpec;
|
use crate::beacon_state::FewValidatorsEthSpec;
|
||||||
|
35
eth2/types/src/beacon_state/exit_cache.rs
Normal file
35
eth2/types/src/beacon_state/exit_cache.rs
Normal file
@ -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<Epoch, u64>);
|
||||||
|
|
||||||
|
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<Epoch> {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +1,23 @@
|
|||||||
use crate::*;
|
use crate::*;
|
||||||
use bls::Signature;
|
|
||||||
use int_to_bytes::int_to_bytes4;
|
use int_to_bytes::int_to_bytes4;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
use test_utils::u8_from_hex_str;
|
use test_utils::u8_from_hex_str;
|
||||||
|
|
||||||
const GWEI: u64 = 1_000_000_000;
|
|
||||||
|
|
||||||
/// Each of the BLS signature domains.
|
/// Each of the BLS signature domains.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub enum Domain {
|
pub enum Domain {
|
||||||
BeaconBlock,
|
BeaconProposer,
|
||||||
Randao,
|
Randao,
|
||||||
Attestation,
|
Attestation,
|
||||||
Deposit,
|
Deposit,
|
||||||
Exit,
|
VoluntaryExit,
|
||||||
Transfer,
|
Transfer,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Holds all the "constants" for a BeaconChain.
|
/// Holds all the "constants" for a BeaconChain.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
#[derive(PartialEq, Debug, Clone, Deserialize)]
|
#[derive(PartialEq, Debug, Clone, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct ChainSpec {
|
pub struct ChainSpec {
|
||||||
@ -29,35 +26,32 @@ pub struct ChainSpec {
|
|||||||
*/
|
*/
|
||||||
pub shard_count: u64,
|
pub shard_count: u64,
|
||||||
pub target_committee_size: u64,
|
pub target_committee_size: u64,
|
||||||
pub max_balance_churn_quotient: u64,
|
pub max_indices_per_attestation: u64,
|
||||||
pub max_indices_per_slashable_vote: usize,
|
pub min_per_epoch_churn_limit: u64,
|
||||||
pub max_exit_dequeues_per_epoch: u64,
|
pub churn_limit_quotient: u64,
|
||||||
|
pub base_rewards_per_epoch: u64,
|
||||||
pub shuffle_round_count: u8,
|
pub shuffle_round_count: u8,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deposit contract
|
* Deposit contract
|
||||||
*/
|
*/
|
||||||
pub deposit_contract_address: Address,
|
|
||||||
pub deposit_contract_tree_depth: u64,
|
pub deposit_contract_tree_depth: u64,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Gwei values
|
* Gwei values
|
||||||
*/
|
*/
|
||||||
pub min_deposit_amount: u64,
|
pub min_deposit_amount: u64,
|
||||||
pub max_deposit_amount: u64,
|
pub max_effective_balance: u64,
|
||||||
pub fork_choice_balance_increment: u64,
|
|
||||||
pub ejection_balance: u64,
|
pub ejection_balance: u64,
|
||||||
|
pub effective_balance_increment: u64,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initial Values
|
* Initial Values
|
||||||
*/
|
*/
|
||||||
pub genesis_fork_version: u32,
|
|
||||||
pub genesis_slot: Slot,
|
pub genesis_slot: Slot,
|
||||||
pub genesis_epoch: Epoch,
|
pub genesis_epoch: Epoch,
|
||||||
pub genesis_start_shard: u64,
|
|
||||||
pub far_future_epoch: Epoch,
|
pub far_future_epoch: Epoch,
|
||||||
pub zero_hash: Hash256,
|
pub zero_hash: Hash256,
|
||||||
pub empty_signature: Signature,
|
|
||||||
#[serde(deserialize_with = "u8_from_hex_str")]
|
#[serde(deserialize_with = "u8_from_hex_str")]
|
||||||
pub bls_withdrawal_prefix_byte: u8,
|
pub bls_withdrawal_prefix_byte: u8,
|
||||||
|
|
||||||
@ -69,18 +63,21 @@ pub struct ChainSpec {
|
|||||||
pub slots_per_epoch: u64,
|
pub slots_per_epoch: u64,
|
||||||
pub min_seed_lookahead: Epoch,
|
pub min_seed_lookahead: Epoch,
|
||||||
pub activation_exit_delay: u64,
|
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 min_validator_withdrawability_delay: Epoch,
|
||||||
pub persistent_committee_period: u64,
|
pub persistent_committee_period: u64,
|
||||||
|
pub max_crosslink_epochs: u64,
|
||||||
|
pub min_epochs_to_inactivity_penalty: u64,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reward and penalty quotients
|
* Reward and penalty quotients
|
||||||
*/
|
*/
|
||||||
pub base_reward_quotient: u64,
|
pub base_reward_quotient: u64,
|
||||||
pub whistleblower_reward_quotient: u64,
|
pub whistleblowing_reward_quotient: u64,
|
||||||
pub attestation_inclusion_reward_quotient: u64,
|
pub proposer_reward_quotient: u64,
|
||||||
pub inactivity_penalty_quotient: u64,
|
pub inactivity_penalty_quotient: u64,
|
||||||
pub min_penalty_quotient: u64,
|
pub min_slashing_penalty_quotient: u64,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Max operations per block
|
* Max operations per block
|
||||||
@ -100,11 +97,11 @@ pub struct ChainSpec {
|
|||||||
*
|
*
|
||||||
* Use `ChainSpec::get_domain(..)` to access these values.
|
* Use `ChainSpec::get_domain(..)` to access these values.
|
||||||
*/
|
*/
|
||||||
domain_beacon_block: u32,
|
domain_beacon_proposer: u32,
|
||||||
domain_randao: u32,
|
domain_randao: u32,
|
||||||
domain_attestation: u32,
|
domain_attestation: u32,
|
||||||
domain_deposit: u32,
|
domain_deposit: u32,
|
||||||
domain_exit: u32,
|
domain_voluntary_exit: u32,
|
||||||
domain_transfer: u32,
|
domain_transfer: u32,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -118,7 +115,7 @@ pub struct ChainSpec {
|
|||||||
impl ChainSpec {
|
impl ChainSpec {
|
||||||
/// Return the number of committees in one epoch.
|
/// 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 {
|
pub fn get_epoch_committee_count(&self, active_validator_count: usize) -> u64 {
|
||||||
std::cmp::max(
|
std::cmp::max(
|
||||||
1,
|
1,
|
||||||
@ -131,14 +128,14 @@ impl ChainSpec {
|
|||||||
|
|
||||||
/// Get the domain number that represents the fork meta and signature domain.
|
/// 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 {
|
pub fn get_domain(&self, epoch: Epoch, domain: Domain, fork: &Fork) -> u64 {
|
||||||
let domain_constant = match domain {
|
let domain_constant = match domain {
|
||||||
Domain::BeaconBlock => self.domain_beacon_block,
|
Domain::BeaconProposer => self.domain_beacon_proposer,
|
||||||
Domain::Randao => self.domain_randao,
|
Domain::Randao => self.domain_randao,
|
||||||
Domain::Attestation => self.domain_attestation,
|
Domain::Attestation => self.domain_attestation,
|
||||||
Domain::Deposit => self.domain_deposit,
|
Domain::Deposit => self.domain_deposit,
|
||||||
Domain::Exit => self.domain_exit,
|
Domain::VoluntaryExit => self.domain_voluntary_exit,
|
||||||
Domain::Transfer => self.domain_transfer,
|
Domain::Transfer => self.domain_transfer,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -153,47 +150,40 @@ impl ChainSpec {
|
|||||||
|
|
||||||
/// Returns a `ChainSpec` compatible with the Ethereum Foundation specification.
|
/// Returns a `ChainSpec` compatible with the Ethereum Foundation specification.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub(crate) fn foundation() -> Self {
|
pub(crate) 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 {
|
Self {
|
||||||
/*
|
/*
|
||||||
* Misc
|
* Misc
|
||||||
*/
|
*/
|
||||||
shard_count: 1_024,
|
shard_count: 1_024,
|
||||||
target_committee_size: 128,
|
target_committee_size: 128,
|
||||||
max_balance_churn_quotient: 32,
|
max_indices_per_attestation: 4096,
|
||||||
max_indices_per_slashable_vote: 4_096,
|
min_per_epoch_churn_limit: 4,
|
||||||
max_exit_dequeues_per_epoch: 4,
|
churn_limit_quotient: 65_536,
|
||||||
|
base_rewards_per_epoch: 5,
|
||||||
shuffle_round_count: 90,
|
shuffle_round_count: 90,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deposit contract
|
* Deposit contract
|
||||||
*/
|
*/
|
||||||
deposit_contract_address: Address::zero(),
|
|
||||||
deposit_contract_tree_depth: 32,
|
deposit_contract_tree_depth: 32,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Gwei values
|
* Gwei values
|
||||||
*/
|
*/
|
||||||
min_deposit_amount: u64::pow(2, 0) * GWEI,
|
min_deposit_amount: u64::pow(2, 0) * u64::pow(10, 9),
|
||||||
max_deposit_amount: u64::pow(2, 5) * GWEI,
|
max_effective_balance: u64::pow(2, 5) * u64::pow(10, 9),
|
||||||
fork_choice_balance_increment: u64::pow(2, 0) * GWEI,
|
ejection_balance: u64::pow(2, 4) * u64::pow(10, 9),
|
||||||
ejection_balance: u64::pow(2, 4) * GWEI,
|
effective_balance_increment: u64::pow(2, 0) * u64::pow(10, 9),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initial Values
|
* Initial Values
|
||||||
*/
|
*/
|
||||||
genesis_fork_version: 0,
|
genesis_slot: Slot::new(0),
|
||||||
genesis_slot,
|
genesis_epoch: Epoch::new(0),
|
||||||
genesis_epoch,
|
|
||||||
genesis_start_shard: 0,
|
|
||||||
far_future_epoch: Epoch::new(u64::max_value()),
|
far_future_epoch: Epoch::new(u64::max_value()),
|
||||||
zero_hash: Hash256::zero(),
|
zero_hash: Hash256::zero(),
|
||||||
empty_signature: Signature::empty_signature(),
|
|
||||||
bls_withdrawal_prefix_byte: 0,
|
bls_withdrawal_prefix_byte: 0,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -201,21 +191,24 @@ impl ChainSpec {
|
|||||||
*/
|
*/
|
||||||
seconds_per_slot: 6,
|
seconds_per_slot: 6,
|
||||||
min_attestation_inclusion_delay: 4,
|
min_attestation_inclusion_delay: 4,
|
||||||
slots_per_epoch,
|
slots_per_epoch: 64,
|
||||||
min_seed_lookahead: Epoch::new(1),
|
min_seed_lookahead: Epoch::new(1),
|
||||||
activation_exit_delay: 4,
|
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),
|
min_validator_withdrawability_delay: Epoch::new(256),
|
||||||
persistent_committee_period: 2_048,
|
persistent_committee_period: 2_048,
|
||||||
|
max_crosslink_epochs: 64,
|
||||||
|
min_epochs_to_inactivity_penalty: 4,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reward and penalty quotients
|
* Reward and penalty quotients
|
||||||
*/
|
*/
|
||||||
base_reward_quotient: 32,
|
base_reward_quotient: 32,
|
||||||
whistleblower_reward_quotient: 512,
|
whistleblowing_reward_quotient: 512,
|
||||||
attestation_inclusion_reward_quotient: 8,
|
proposer_reward_quotient: 8,
|
||||||
inactivity_penalty_quotient: 16_777_216,
|
inactivity_penalty_quotient: 33_554_432,
|
||||||
min_penalty_quotient: 32,
|
min_slashing_penalty_quotient: 32,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Max operations per block
|
* Max operations per block
|
||||||
@ -225,16 +218,16 @@ impl ChainSpec {
|
|||||||
max_attestations: 128,
|
max_attestations: 128,
|
||||||
max_deposits: 16,
|
max_deposits: 16,
|
||||||
max_voluntary_exits: 16,
|
max_voluntary_exits: 16,
|
||||||
max_transfers: 16,
|
max_transfers: 0,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Signature domains
|
* Signature domains
|
||||||
*/
|
*/
|
||||||
domain_beacon_block: 0,
|
domain_beacon_proposer: 0,
|
||||||
domain_randao: 1,
|
domain_randao: 1,
|
||||||
domain_attestation: 2,
|
domain_attestation: 2,
|
||||||
domain_deposit: 3,
|
domain_deposit: 3,
|
||||||
domain_exit: 4,
|
domain_voluntary_exit: 4,
|
||||||
domain_transfer: 5,
|
domain_transfer: 5,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -312,11 +305,11 @@ mod tests {
|
|||||||
fn test_get_domain() {
|
fn test_get_domain() {
|
||||||
let spec = ChainSpec::foundation();
|
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::Randao, spec.domain_randao, &spec);
|
||||||
test_domain(Domain::Attestation, spec.domain_attestation, &spec);
|
test_domain(Domain::Attestation, spec.domain_attestation, &spec);
|
||||||
test_domain(Domain::Deposit, spec.domain_deposit, &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);
|
test_domain(Domain::Transfer, spec.domain_transfer, &spec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,12 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
|
|
||||||
/// Specifies the block hash for a shard at an epoch.
|
/// Specifies the block hash for a shard at an epoch.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.0
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
Clone,
|
Clone,
|
||||||
PartialEq,
|
PartialEq,
|
||||||
|
Eq,
|
||||||
Default,
|
Default,
|
||||||
Serialize,
|
Serialize,
|
||||||
Deserialize,
|
Deserialize,
|
||||||
@ -25,6 +26,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
)]
|
)]
|
||||||
pub struct Crosslink {
|
pub struct Crosslink {
|
||||||
pub epoch: Epoch,
|
pub epoch: Epoch,
|
||||||
|
pub previous_crosslink_root: Hash256,
|
||||||
pub crosslink_data_root: Hash256,
|
pub crosslink_data_root: Hash256,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
|
|
||||||
/// A deposit to potentially become a beacon chain validator.
|
/// A deposit to potentially become a beacon chain validator.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.0
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
PartialEq,
|
PartialEq,
|
||||||
@ -25,7 +25,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
pub struct Deposit {
|
pub struct Deposit {
|
||||||
pub proof: FixedLenVec<Hash256, U32>,
|
pub proof: FixedLenVec<Hash256, U32>,
|
||||||
pub index: u64,
|
pub index: u64,
|
||||||
pub deposit_data: DepositData,
|
pub data: DepositData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
use super::DepositInput;
|
|
||||||
use crate::test_utils::TestRandom;
|
use crate::test_utils::TestRandom;
|
||||||
|
use crate::*;
|
||||||
|
use bls::{PublicKey, Signature};
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use ssz_derive::{Decode, Encode};
|
use ssz_derive::{Decode, Encode};
|
||||||
use test_random_derive::TestRandom;
|
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(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
PartialEq,
|
PartialEq,
|
||||||
@ -17,14 +19,45 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
Deserialize,
|
Deserialize,
|
||||||
Encode,
|
Encode,
|
||||||
Decode,
|
Decode,
|
||||||
|
SignedRoot,
|
||||||
TreeHash,
|
TreeHash,
|
||||||
CachedTreeHash,
|
CachedTreeHash,
|
||||||
TestRandom,
|
TestRandom,
|
||||||
)]
|
)]
|
||||||
pub struct DepositData {
|
pub struct DepositData {
|
||||||
|
pub pubkey: PublicKey,
|
||||||
|
pub withdrawal_credentials: Hash256,
|
||||||
pub amount: u64,
|
pub amount: u64,
|
||||||
pub timestamp: u64,
|
#[signed_root(skip_hashing)]
|
||||||
pub deposit_input: DepositInput,
|
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)]
|
#[cfg(test)]
|
||||||
@ -33,4 +66,23 @@ mod tests {
|
|||||||
|
|
||||||
ssz_tests!(DepositData);
|
ssz_tests!(DepositData);
|
||||||
cached_tree_hash_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(),
|
||||||
|
amount: 0,
|
||||||
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
|
|
||||||
/// Contains data obtained from the Eth1 chain.
|
/// Contains data obtained from the Eth1 chain.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.0
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
PartialEq,
|
PartialEq,
|
||||||
@ -24,6 +24,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
)]
|
)]
|
||||||
pub struct Eth1Data {
|
pub struct Eth1Data {
|
||||||
pub deposit_root: Hash256,
|
pub deposit_root: Hash256,
|
||||||
|
pub deposit_count: u64,
|
||||||
pub block_hash: Hash256,
|
pub block_hash: Hash256,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ use crate::{
|
|||||||
test_utils::{fork_from_hex_str, TestRandom},
|
test_utils::{fork_from_hex_str, TestRandom},
|
||||||
ChainSpec, Epoch,
|
ChainSpec, Epoch,
|
||||||
};
|
};
|
||||||
use int_to_bytes::int_to_bytes4;
|
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use ssz_derive::{Decode, Encode};
|
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.
|
/// Specifies a fork of the `BeaconChain`, to prevent replay attacks.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
Clone,
|
Clone,
|
||||||
@ -36,21 +35,18 @@ pub struct Fork {
|
|||||||
impl Fork {
|
impl Fork {
|
||||||
/// Initialize the `Fork` from the genesis parameters in the `spec`.
|
/// Initialize the `Fork` from the genesis parameters in the `spec`.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
pub fn genesis(spec: &ChainSpec) -> Self {
|
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));
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
previous_version: current_version,
|
previous_version: [0; 4],
|
||||||
current_version,
|
current_version: [0; 4],
|
||||||
epoch: spec.genesis_epoch,
|
epoch: spec.genesis_epoch,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the fork version of the given ``epoch``.
|
/// 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] {
|
pub fn get_fork_version(&self, epoch: Epoch) -> [u8; 4] {
|
||||||
if epoch < self.epoch {
|
if epoch < self.epoch {
|
||||||
return self.previous_version;
|
return self.previous_version;
|
||||||
@ -66,10 +62,9 @@ mod tests {
|
|||||||
ssz_tests!(Fork);
|
ssz_tests!(Fork);
|
||||||
cached_tree_hash_tests!(Fork);
|
cached_tree_hash_tests!(Fork);
|
||||||
|
|
||||||
fn test_genesis(version: u32, epoch: Epoch) {
|
fn test_genesis(epoch: Epoch) {
|
||||||
let mut spec = ChainSpec::foundation();
|
let mut spec = ChainSpec::foundation();
|
||||||
|
|
||||||
spec.genesis_fork_version = version;
|
|
||||||
spec.genesis_epoch = epoch;
|
spec.genesis_epoch = epoch;
|
||||||
|
|
||||||
let fork = Fork::genesis(&spec);
|
let fork = Fork::genesis(&spec);
|
||||||
@ -79,19 +74,14 @@ mod tests {
|
|||||||
fork.previous_version, fork.current_version,
|
fork.previous_version, fork.current_version,
|
||||||
"previous and current are not identical"
|
"previous and current are not identical"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
fork.current_version,
|
|
||||||
version.to_le_bytes(),
|
|
||||||
"current version incorrect"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn genesis() {
|
fn genesis() {
|
||||||
test_genesis(0, Epoch::new(0));
|
test_genesis(Epoch::new(0));
|
||||||
test_genesis(9, Epoch::new(11));
|
test_genesis(Epoch::new(11));
|
||||||
test_genesis(2_u32.pow(31), Epoch::new(2_u64.pow(63)));
|
test_genesis(Epoch::new(2_u64.pow(63)));
|
||||||
test_genesis(u32::max_value(), Epoch::max_value());
|
test_genesis(Epoch::max_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
136
eth2/types/src/indexed_attestation.rs
Normal file
136
eth2/types/src/indexed_attestation.rs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield};
|
||||||
|
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.6.0
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
PartialEq,
|
||||||
|
Clone,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
Encode,
|
||||||
|
Decode,
|
||||||
|
TreeHash,
|
||||||
|
CachedTreeHash,
|
||||||
|
TestRandom,
|
||||||
|
SignedRoot,
|
||||||
|
)]
|
||||||
|
pub struct IndexedAttestation {
|
||||||
|
/// Lists validator registry indices, not committee indices.
|
||||||
|
pub custody_bit_0_indices: Vec<u64>,
|
||||||
|
pub custody_bit_1_indices: Vec<u64>,
|
||||||
|
pub data: AttestationData,
|
||||||
|
pub custody_bitfield: Bitfield,
|
||||||
|
#[signed_root(skip_hashing)]
|
||||||
|
pub signature: AggregateSignature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexedAttestation {
|
||||||
|
/// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target.
|
||||||
|
///
|
||||||
|
/// 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.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::slot_epoch::Epoch;
|
||||||
|
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_is_double_vote_true() {
|
||||||
|
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),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_is_double_vote_false() {
|
||||||
|
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),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_is_surround_vote_true() {
|
||||||
|
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),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_is_surround_vote_true_realistic() {
|
||||||
|
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),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_is_surround_vote_false_source_epoch_fails() {
|
||||||
|
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),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_is_surround_vote_false_target_epoch_fails() {
|
||||||
|
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),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssz_tests!(IndexedAttestation);
|
||||||
|
cached_tree_hash_tests!(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.source_epoch = Epoch::new(source_epoch);
|
||||||
|
indexed_vote.data.target_epoch = Epoch::new(target_epoch);
|
||||||
|
indexed_vote
|
||||||
|
}
|
||||||
|
}
|
@ -17,15 +17,13 @@ pub mod crosslink;
|
|||||||
pub mod crosslink_committee;
|
pub mod crosslink_committee;
|
||||||
pub mod deposit;
|
pub mod deposit;
|
||||||
pub mod deposit_data;
|
pub mod deposit_data;
|
||||||
pub mod deposit_input;
|
|
||||||
pub mod eth1_data;
|
pub mod eth1_data;
|
||||||
pub mod eth1_data_vote;
|
|
||||||
pub mod fork;
|
pub mod fork;
|
||||||
pub mod free_attestation;
|
pub mod free_attestation;
|
||||||
pub mod historical_batch;
|
pub mod historical_batch;
|
||||||
|
pub mod indexed_attestation;
|
||||||
pub mod pending_attestation;
|
pub mod pending_attestation;
|
||||||
pub mod proposer_slashing;
|
pub mod proposer_slashing;
|
||||||
pub mod slashable_attestation;
|
|
||||||
pub mod transfer;
|
pub mod transfer;
|
||||||
pub mod voluntary_exit;
|
pub mod voluntary_exit;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -52,16 +50,14 @@ pub use crate::crosslink::Crosslink;
|
|||||||
pub use crate::crosslink_committee::CrosslinkCommittee;
|
pub use crate::crosslink_committee::CrosslinkCommittee;
|
||||||
pub use crate::deposit::Deposit;
|
pub use crate::deposit::Deposit;
|
||||||
pub use crate::deposit_data::DepositData;
|
pub use crate::deposit_data::DepositData;
|
||||||
pub use crate::deposit_input::DepositInput;
|
|
||||||
pub use crate::eth1_data::Eth1Data;
|
pub use crate::eth1_data::Eth1Data;
|
||||||
pub use crate::eth1_data_vote::Eth1DataVote;
|
|
||||||
pub use crate::fork::Fork;
|
pub use crate::fork::Fork;
|
||||||
pub use crate::free_attestation::FreeAttestation;
|
pub use crate::free_attestation::FreeAttestation;
|
||||||
pub use crate::historical_batch::HistoricalBatch;
|
pub use crate::historical_batch::HistoricalBatch;
|
||||||
|
pub use crate::indexed_attestation::IndexedAttestation;
|
||||||
pub use crate::pending_attestation::PendingAttestation;
|
pub use crate::pending_attestation::PendingAttestation;
|
||||||
pub use crate::proposer_slashing::ProposerSlashing;
|
pub use crate::proposer_slashing::ProposerSlashing;
|
||||||
pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch};
|
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_epoch::{Epoch, Slot};
|
||||||
pub use crate::slot_height::SlotHeight;
|
pub use crate::slot_height::SlotHeight;
|
||||||
pub use crate::transfer::Transfer;
|
pub use crate::transfer::Transfer;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::test_utils::TestRandom;
|
use crate::test_utils::TestRandom;
|
||||||
use crate::{Attestation, AttestationData, Bitfield, Slot};
|
use crate::{Attestation, AttestationData, Bitfield};
|
||||||
|
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use ssz_derive::{Decode, Encode};
|
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.
|
/// An attestation that has been included in the state but not yet fully processed.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.1
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
Clone,
|
Clone,
|
||||||
@ -24,18 +24,22 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
pub struct PendingAttestation {
|
pub struct PendingAttestation {
|
||||||
pub aggregation_bitfield: Bitfield,
|
pub aggregation_bitfield: Bitfield,
|
||||||
pub data: AttestationData,
|
pub data: AttestationData,
|
||||||
pub custody_bitfield: Bitfield,
|
pub inclusion_delay: u64,
|
||||||
pub inclusion_slot: Slot,
|
pub proposer_index: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PendingAttestation {
|
impl PendingAttestation {
|
||||||
/// Create a `PendingAttestation` from an `Attestation`, at the given `inclusion_slot`.
|
/// Create a `PendingAttestation` from an `Attestation`.
|
||||||
pub fn from_attestation(attestation: &Attestation, inclusion_slot: Slot) -> Self {
|
pub fn from_attestation(
|
||||||
|
attestation: &Attestation,
|
||||||
|
inclusion_delay: u64,
|
||||||
|
proposer_index: u64,
|
||||||
|
) -> Self {
|
||||||
PendingAttestation {
|
PendingAttestation {
|
||||||
data: attestation.data.clone(),
|
data: attestation.data.clone(),
|
||||||
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
|
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
|
||||||
custody_bitfield: attestation.custody_bitfield.clone(),
|
inclusion_delay,
|
||||||
inclusion_slot,
|
proposer_index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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<u64>,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -33,7 +33,7 @@ impl TestingAttestationBuilder {
|
|||||||
aggregation_bitfield,
|
aggregation_bitfield,
|
||||||
data: data_builder.build(),
|
data: data_builder.build(),
|
||||||
custody_bitfield,
|
custody_bitfield,
|
||||||
aggregate_signature: AggregateSignature::new(),
|
signature: AggregateSignature::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -77,13 +77,13 @@ impl TestingAttestationBuilder {
|
|||||||
.tree_hash_root();
|
.tree_hash_root();
|
||||||
|
|
||||||
let domain = spec.get_domain(
|
let domain = spec.get_domain(
|
||||||
self.attestation.data.slot.epoch(spec.slots_per_epoch),
|
self.attestation.data.target_epoch,
|
||||||
Domain::Attestation,
|
Domain::Attestation,
|
||||||
fork,
|
fork,
|
||||||
);
|
);
|
||||||
|
|
||||||
let signature = Signature::new(&message, domain, secret_keys[key_index]);
|
let signature = Signature::new(&message, domain, secret_keys[key_index]);
|
||||||
self.attestation.aggregate_signature.add(&signature)
|
self.attestation.signature.add(&signature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,12 @@ impl TestingAttestationDataBuilder {
|
|||||||
state.current_justified_epoch
|
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 {
|
let target_root = if is_previous_epoch {
|
||||||
*state
|
*state
|
||||||
.get_block_root(previous_epoch.start_slot(spec.slots_per_epoch))
|
.get_block_root(previous_epoch.start_slot(spec.slots_per_epoch))
|
||||||
@ -44,20 +50,17 @@ impl TestingAttestationDataBuilder {
|
|||||||
|
|
||||||
let data = AttestationData {
|
let data = AttestationData {
|
||||||
// LMD GHOST vote
|
// LMD GHOST vote
|
||||||
slot,
|
|
||||||
beacon_block_root: *state.get_block_root(slot).unwrap(),
|
beacon_block_root: *state.get_block_root(slot).unwrap(),
|
||||||
|
|
||||||
// FFG Vote
|
// FFG Vote
|
||||||
source_epoch,
|
source_epoch,
|
||||||
source_root,
|
source_root,
|
||||||
|
target_epoch,
|
||||||
target_root,
|
target_root,
|
||||||
|
|
||||||
// Crosslink vote
|
// Crosslink vote
|
||||||
shard,
|
shard,
|
||||||
previous_crosslink: Crosslink {
|
previous_crosslink_root: spec.zero_hash,
|
||||||
epoch: slot.epoch(spec.slots_per_epoch),
|
|
||||||
crosslink_data_root: spec.zero_hash,
|
|
||||||
},
|
|
||||||
crosslink_data_root: spec.zero_hash,
|
crosslink_data_root: spec.zero_hash,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,23 +21,20 @@ impl TestingAttesterSlashingBuilder {
|
|||||||
where
|
where
|
||||||
F: Fn(u64, &[u8], Epoch, Domain) -> Signature,
|
F: Fn(u64, &[u8], Epoch, Domain) -> Signature,
|
||||||
{
|
{
|
||||||
let double_voted_slot = Slot::new(0);
|
|
||||||
let shard = 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_1 = Hash256::from_low_u64_le(1);
|
||||||
let hash_2 = Hash256::from_low_u64_le(2);
|
let hash_2 = Hash256::from_low_u64_le(2);
|
||||||
|
|
||||||
let data_1 = AttestationData {
|
let data_1 = AttestationData {
|
||||||
slot: double_voted_slot,
|
|
||||||
beacon_block_root: hash_1,
|
beacon_block_root: hash_1,
|
||||||
source_epoch: epoch,
|
source_epoch: epoch_1,
|
||||||
source_root: hash_1,
|
source_root: hash_1,
|
||||||
|
target_epoch: epoch_2,
|
||||||
target_root: hash_1,
|
target_root: hash_1,
|
||||||
shard,
|
shard,
|
||||||
previous_crosslink: Crosslink {
|
previous_crosslink_root: hash_1,
|
||||||
epoch,
|
|
||||||
crosslink_data_root: hash_1,
|
|
||||||
},
|
|
||||||
crosslink_data_root: hash_1,
|
crosslink_data_root: hash_1,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,21 +43,23 @@ impl TestingAttesterSlashingBuilder {
|
|||||||
..data_1.clone()
|
..data_1.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut slashable_attestation_1 = SlashableAttestation {
|
let mut attestation_1 = IndexedAttestation {
|
||||||
validator_indices: validator_indices.to_vec(),
|
custody_bit_0_indices: validator_indices.to_vec(),
|
||||||
|
custody_bit_1_indices: vec![],
|
||||||
data: data_1,
|
data: data_1,
|
||||||
custody_bitfield: Bitfield::new(),
|
custody_bitfield: Bitfield::new(),
|
||||||
aggregate_signature: AggregateSignature::new(),
|
signature: AggregateSignature::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut slashable_attestation_2 = SlashableAttestation {
|
let mut attestation_2 = IndexedAttestation {
|
||||||
validator_indices: validator_indices.to_vec(),
|
custody_bit_0_indices: validator_indices.to_vec(),
|
||||||
|
custody_bit_1_indices: vec![],
|
||||||
data: data_2,
|
data: data_2,
|
||||||
custody_bitfield: Bitfield::new(),
|
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.
|
// All validators sign with a `false` custody bit.
|
||||||
let attestation_data_and_custody_bit = AttestationDataAndCustodyBit {
|
let attestation_data_and_custody_bit = AttestationDataAndCustodyBit {
|
||||||
data: attestation.data.clone(),
|
data: attestation.data.clone(),
|
||||||
@ -70,17 +69,18 @@ impl TestingAttesterSlashingBuilder {
|
|||||||
|
|
||||||
for (i, validator_index) in validator_indices.iter().enumerate() {
|
for (i, validator_index) in validator_indices.iter().enumerate() {
|
||||||
attestation.custody_bitfield.set(i, false);
|
attestation.custody_bitfield.set(i, false);
|
||||||
let signature = signer(*validator_index, &message[..], epoch, Domain::Attestation);
|
let signature =
|
||||||
attestation.aggregate_signature.add(&signature);
|
signer(*validator_index, &message[..], epoch_2, Domain::Attestation);
|
||||||
|
attestation.signature.add(&signature);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
add_signatures(&mut slashable_attestation_1);
|
add_signatures(&mut attestation_1);
|
||||||
add_signatures(&mut slashable_attestation_2);
|
add_signatures(&mut attestation_2);
|
||||||
|
|
||||||
AttesterSlashing {
|
AttesterSlashing {
|
||||||
slashable_attestation_1,
|
attestation_1,
|
||||||
slashable_attestation_2,
|
attestation_2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ impl TestingBeaconBlockBuilder {
|
|||||||
pub fn sign(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) {
|
pub fn sign(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) {
|
||||||
let message = self.block.signed_root();
|
let message = self.block.signed_root();
|
||||||
let epoch = self.block.slot.epoch(spec.slots_per_epoch);
|
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);
|
self.block.signature = Signature::new(&message, domain, sk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
|
|||||||
/// Creates the builder from an existing set of keypairs.
|
/// Creates the builder from an existing set of keypairs.
|
||||||
pub fn from_keypairs(keypairs: Vec<Keypair>, spec: &ChainSpec) -> Self {
|
pub fn from_keypairs(keypairs: Vec<Keypair>, spec: &ChainSpec) -> Self {
|
||||||
let validator_count = keypairs.len();
|
let validator_count = keypairs.len();
|
||||||
|
let starting_balance = 32_000_000_000;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"Building {} Validator objects from keypairs...",
|
"Building {} Validator objects from keypairs...",
|
||||||
@ -112,11 +113,12 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
|
|||||||
pubkey: keypair.pk.clone(),
|
pubkey: keypair.pk.clone(),
|
||||||
withdrawal_credentials,
|
withdrawal_credentials,
|
||||||
// All validators start active.
|
// All validators start active.
|
||||||
|
activation_eligibility_epoch: spec.genesis_epoch,
|
||||||
activation_epoch: spec.genesis_epoch,
|
activation_epoch: spec.genesis_epoch,
|
||||||
exit_epoch: spec.far_future_epoch,
|
exit_epoch: spec.far_future_epoch,
|
||||||
withdrawable_epoch: spec.far_future_epoch,
|
withdrawable_epoch: spec.far_future_epoch,
|
||||||
initiated_exit: false,
|
|
||||||
slashed: false,
|
slashed: false,
|
||||||
|
effective_balance: starting_balance,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -137,16 +139,17 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
|
|||||||
genesis_time,
|
genesis_time,
|
||||||
Eth1Data {
|
Eth1Data {
|
||||||
deposit_root: Hash256::zero(),
|
deposit_root: Hash256::zero(),
|
||||||
|
deposit_count: 0,
|
||||||
block_hash: Hash256::zero(),
|
block_hash: Hash256::zero(),
|
||||||
},
|
},
|
||||||
spec,
|
spec,
|
||||||
);
|
);
|
||||||
|
|
||||||
let balances = vec![32_000_000_000; validator_count];
|
let balances = vec![starting_balance; validator_count];
|
||||||
|
|
||||||
debug!("Importing {} existing validators...", validator_count);
|
debug!("Importing {} existing validators...", validator_count);
|
||||||
state.validator_registry = validators;
|
state.validator_registry = validators;
|
||||||
state.validator_balances = balances;
|
state.balances = balances;
|
||||||
|
|
||||||
debug!("BeaconState initialized.");
|
debug!("BeaconState initialized.");
|
||||||
|
|
||||||
@ -192,18 +195,13 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
|
|||||||
|
|
||||||
state.slot = slot;
|
state.slot = slot;
|
||||||
|
|
||||||
state.previous_shuffling_epoch = epoch - 1;
|
// FIXME(sproul): update latest_start_shard?
|
||||||
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);
|
|
||||||
|
|
||||||
state.previous_justified_epoch = epoch - 3;
|
state.previous_justified_epoch = epoch - 3;
|
||||||
state.current_justified_epoch = epoch - 2;
|
state.current_justified_epoch = epoch - 2;
|
||||||
state.justification_bitfield = u64::max_value();
|
state.justification_bitfield = u64::max_value();
|
||||||
|
|
||||||
state.finalized_epoch = epoch - 3;
|
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
|
/// Creates a full set of attestations for the `BeaconState`. Each attestation has full
|
||||||
@ -248,7 +246,7 @@ impl<T: EthSpec> TestingBeaconStateBuilder<T> {
|
|||||||
builder.add_committee_participation(signers);
|
builder.add_committee_participation(signers);
|
||||||
let attestation = builder.build();
|
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)
|
state.previous_epoch_attestations.push(attestation)
|
||||||
} else {
|
} else {
|
||||||
state.current_epoch_attestations.push(attestation)
|
state.current_epoch_attestations.push(attestation)
|
||||||
|
@ -14,14 +14,11 @@ impl TestingDepositBuilder {
|
|||||||
let deposit = Deposit {
|
let deposit = Deposit {
|
||||||
proof: vec![].into(),
|
proof: vec![].into(),
|
||||||
index: 0,
|
index: 0,
|
||||||
deposit_data: DepositData {
|
data: DepositData {
|
||||||
|
pubkey,
|
||||||
|
withdrawal_credentials: Hash256::zero(),
|
||||||
amount,
|
amount,
|
||||||
timestamp: 1,
|
signature: Signature::empty_signature(),
|
||||||
deposit_input: DepositInput {
|
|
||||||
pubkey,
|
|
||||||
withdrawal_credentials: Hash256::zero(),
|
|
||||||
proof_of_possession: Signature::empty_signature(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,17 +40,13 @@ impl TestingDepositBuilder {
|
|||||||
&get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..],
|
&get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..],
|
||||||
);
|
);
|
||||||
|
|
||||||
self.deposit.deposit_data.deposit_input.pubkey = keypair.pk.clone();
|
self.deposit.data.pubkey = keypair.pk.clone();
|
||||||
self.deposit
|
self.deposit.data.withdrawal_credentials = withdrawal_credentials;
|
||||||
.deposit_data
|
|
||||||
.deposit_input
|
|
||||||
.withdrawal_credentials = withdrawal_credentials;
|
|
||||||
|
|
||||||
self.deposit.deposit_data.deposit_input.proof_of_possession = self
|
self.deposit.data.signature =
|
||||||
.deposit
|
self.deposit
|
||||||
.deposit_data
|
.data
|
||||||
.deposit_input
|
.create_signature(&keypair.sk, epoch, fork, spec);
|
||||||
.create_proof_of_possession(&keypair.sk, epoch, fork, spec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds the deposit, consuming the builder.
|
/// Builds the deposit, consuming the builder.
|
||||||
|
@ -11,8 +11,7 @@ pub struct TestingPendingAttestationBuilder {
|
|||||||
impl TestingPendingAttestationBuilder {
|
impl TestingPendingAttestationBuilder {
|
||||||
/// Create a new valid* `PendingAttestation` for the given parameters.
|
/// Create a new valid* `PendingAttestation` for the given parameters.
|
||||||
///
|
///
|
||||||
/// The `inclusion_slot` will be set to be the earliest possible slot the `Attestation` could
|
/// The `inclusion_delay` will be set to `MIN_ATTESTATION_INCLUSION_DELAY`.
|
||||||
/// have been included (`slot + MIN_ATTESTATION_INCLUSION_DELAY`).
|
|
||||||
///
|
///
|
||||||
/// * The aggregation and custody bitfields will all be empty, they need to be set with
|
/// * The aggregation and custody bitfields will all be empty, they need to be set with
|
||||||
/// `Self::add_committee_participation`.
|
/// `Self::add_committee_participation`.
|
||||||
@ -27,8 +26,9 @@ impl TestingPendingAttestationBuilder {
|
|||||||
let pending_attestation = PendingAttestation {
|
let pending_attestation = PendingAttestation {
|
||||||
aggregation_bitfield: Bitfield::new(),
|
aggregation_bitfield: Bitfield::new(),
|
||||||
data: data_builder.build(),
|
data: data_builder.build(),
|
||||||
custody_bitfield: Bitfield::new(),
|
inclusion_delay: spec.min_attestation_inclusion_delay,
|
||||||
inclusion_slot: slot + spec.min_attestation_inclusion_delay,
|
// FIXME(sproul)
|
||||||
|
proposer_index: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -42,15 +42,12 @@ impl TestingPendingAttestationBuilder {
|
|||||||
/// `signers` is true.
|
/// `signers` is true.
|
||||||
pub fn add_committee_participation(&mut self, signers: Vec<bool>) {
|
pub fn add_committee_participation(&mut self, signers: Vec<bool>) {
|
||||||
let mut aggregation_bitfield = Bitfield::new();
|
let mut aggregation_bitfield = Bitfield::new();
|
||||||
let mut custody_bitfield = Bitfield::new();
|
|
||||||
|
|
||||||
for (i, signed) in signers.iter().enumerate() {
|
for (i, signed) in signers.iter().enumerate() {
|
||||||
aggregation_bitfield.set(i, *signed);
|
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.aggregation_bitfield = aggregation_bitfield;
|
||||||
self.pending_attestation.custody_bitfield = custody_bitfield;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `PendingAttestation`, consuming the builder.
|
/// Returns the `PendingAttestation`, consuming the builder.
|
||||||
|
@ -41,13 +41,13 @@ impl TestingProposerSlashingBuilder {
|
|||||||
header_1.signature = {
|
header_1.signature = {
|
||||||
let message = header_1.signed_root();
|
let message = header_1.signed_root();
|
||||||
let epoch = slot.epoch(spec.slots_per_epoch);
|
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 = {
|
header_2.signature = {
|
||||||
let message = header_2.signed_root();
|
let message = header_2.signed_root();
|
||||||
let epoch = slot.epoch(spec.slots_per_epoch);
|
let epoch = slot.epoch(spec.slots_per_epoch);
|
||||||
signer(proposer_index, &message[..], epoch, Domain::BeaconBlock)
|
signer(proposer_index, &message[..], epoch, Domain::BeaconProposer)
|
||||||
};
|
};
|
||||||
|
|
||||||
ProposerSlashing {
|
ProposerSlashing {
|
||||||
|
@ -25,7 +25,7 @@ impl TestingVoluntaryExitBuilder {
|
|||||||
/// The signing secret key must match that of the exiting validator.
|
/// The signing secret key must match that of the exiting validator.
|
||||||
pub fn sign(&mut self, secret_key: &SecretKey, fork: &Fork, spec: &ChainSpec) {
|
pub fn sign(&mut self, secret_key: &SecretKey, fork: &Fork, spec: &ChainSpec) {
|
||||||
let message = self.exit.signed_root();
|
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);
|
self.exit.signature = Signature::new(&message, domain, secret_key);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
|
|
||||||
/// Information about a `BeaconChain` validator.
|
/// Information about a `BeaconChain` validator.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.0
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
Clone,
|
Clone,
|
||||||
@ -23,11 +23,12 @@ use tree_hash_derive::{CachedTreeHash, TreeHash};
|
|||||||
pub struct Validator {
|
pub struct Validator {
|
||||||
pub pubkey: PublicKey,
|
pub pubkey: PublicKey,
|
||||||
pub withdrawal_credentials: Hash256,
|
pub withdrawal_credentials: Hash256,
|
||||||
|
pub activation_eligibility_epoch: Epoch,
|
||||||
pub activation_epoch: Epoch,
|
pub activation_epoch: Epoch,
|
||||||
pub exit_epoch: Epoch,
|
pub exit_epoch: Epoch,
|
||||||
pub withdrawable_epoch: Epoch,
|
pub withdrawable_epoch: Epoch,
|
||||||
pub initiated_exit: bool,
|
|
||||||
pub slashed: bool,
|
pub slashed: bool,
|
||||||
|
pub effective_balance: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Validator {
|
impl Validator {
|
||||||
@ -36,6 +37,11 @@ impl Validator {
|
|||||||
self.activation_epoch <= epoch && epoch < self.exit_epoch
|
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.
|
/// Returns `true` if the validator is considered exited at some epoch.
|
||||||
pub fn is_exited_at(&self, epoch: Epoch) -> bool {
|
pub fn is_exited_at(&self, epoch: Epoch) -> bool {
|
||||||
self.exit_epoch <= epoch
|
self.exit_epoch <= epoch
|
||||||
@ -53,11 +59,12 @@ impl Default for Validator {
|
|||||||
Self {
|
Self {
|
||||||
pubkey: PublicKey::default(),
|
pubkey: PublicKey::default(),
|
||||||
withdrawal_credentials: Hash256::default(),
|
withdrawal_credentials: Hash256::default(),
|
||||||
|
activation_eligibility_epoch: Epoch::from(std::u64::MAX),
|
||||||
activation_epoch: Epoch::from(std::u64::MAX),
|
activation_epoch: Epoch::from(std::u64::MAX),
|
||||||
exit_epoch: Epoch::from(std::u64::MAX),
|
exit_epoch: Epoch::from(std::u64::MAX),
|
||||||
withdrawable_epoch: Epoch::from(std::u64::MAX),
|
withdrawable_epoch: Epoch::from(std::u64::MAX),
|
||||||
initiated_exit: false,
|
|
||||||
slashed: false,
|
slashed: false,
|
||||||
|
effective_balance: std::u64::MAX,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,7 +82,6 @@ mod tests {
|
|||||||
assert_eq!(v.is_active_at(epoch), false);
|
assert_eq!(v.is_active_at(epoch), false);
|
||||||
assert_eq!(v.is_exited_at(epoch), false);
|
assert_eq!(v.is_exited_at(epoch), false);
|
||||||
assert_eq!(v.is_withdrawable_at(epoch), false);
|
assert_eq!(v.is_withdrawable_at(epoch), false);
|
||||||
assert_eq!(v.initiated_exit, false);
|
|
||||||
assert_eq!(v.slashed, false);
|
assert_eq!(v.slashed, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,4 +5,4 @@ authors = ["Paul Hauner <paul@paulhauner.com>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tiny-keccak = "1.4.2"
|
ring = "0.14.6"
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
use tiny_keccak::Keccak;
|
use ring::digest::{digest, SHA256};
|
||||||
|
|
||||||
pub fn hash(input: &[u8]) -> Vec<u8> {
|
pub fn hash(input: &[u8]) -> Vec<u8> {
|
||||||
let mut keccak = Keccak::new_keccak256();
|
digest(&SHA256, input).as_ref().into()
|
||||||
keccak.update(input);
|
|
||||||
let mut result = vec![0; 32];
|
|
||||||
keccak.finalize(result.as_mut_slice());
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get merkle root of some hashed values - the input leaf nodes is expected to already be hashed
|
/// 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<u8>]) -> Option<Vec<u8>> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::convert::From;
|
use ring::test;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_hashing() {
|
fn test_hashing() {
|
||||||
let input: Vec<u8> = From::from("hello");
|
let input: Vec<u8> = b"hello world".as_ref().into();
|
||||||
|
|
||||||
let output = hash(input.as_ref());
|
let output = hash(input.as_ref());
|
||||||
let expected = &[
|
let expected_hex = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9";
|
||||||
0x1c, 0x8a, 0xff, 0x95, 0x06, 0x85, 0xc2, 0xed, 0x4b, 0xc3, 0x17, 0x4f, 0x34, 0x72,
|
let expected: Vec<u8> = test::from_hex(expected_hex).unwrap();
|
||||||
0x28, 0x7b, 0x56, 0xd9, 0x51, 0x7b, 0x9c, 0x94, 0x81, 0x27, 0x31, 0x9a, 0x09, 0xa7,
|
assert_eq!(expected, output);
|
||||||
0xa3, 0x6d, 0xea, 0xc8,
|
|
||||||
];
|
|
||||||
assert_eq!(expected, output.as_slice());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -52,7 +52,7 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> {
|
|||||||
Ok(ValidatorEvent::SignerRejection(_slot)) => {
|
Ok(ValidatorEvent::SignerRejection(_slot)) => {
|
||||||
error!(log, "Attestation production error"; "Error" => "Signer could not sign the attestation".to_string())
|
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())
|
error!(log, "Attestation production error"; "Error" => "Rejected the attestation as it could have been slashed".to_string())
|
||||||
}
|
}
|
||||||
Ok(ValidatorEvent::PublishAttestationFailed) => {
|
Ok(ValidatorEvent::PublishAttestationFailed) => {
|
||||||
@ -99,7 +99,7 @@ impl<'a, B: BeaconNodeAttestation, S: Signer> AttestationProducer<'a, B, S> {
|
|||||||
Ok(ValidatorEvent::SignerRejection(self.duty.slot))
|
Ok(ValidatorEvent::SignerRejection(self.duty.slot))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(ValidatorEvent::SlashableAttestationNotProduced(
|
Ok(ValidatorEvent::IndexedAttestationNotProduced(
|
||||||
self.duty.slot,
|
self.duty.slot,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ pub enum ValidatorEvent {
|
|||||||
/// A block was not produced as it would have been slashable.
|
/// A block was not produced as it would have been slashable.
|
||||||
SlashableBlockNotProduced(Slot),
|
SlashableBlockNotProduced(Slot),
|
||||||
/// An attestation was not produced as it would have been slashable.
|
/// 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.
|
/// The Beacon Node was unable to produce a block at that slot.
|
||||||
BeaconNodeUnableToProduceBlock(Slot),
|
BeaconNodeUnableToProduceBlock(Slot),
|
||||||
/// The signer failed to sign the message.
|
/// The signer failed to sign the message.
|
||||||
|
Loading…
Reference in New Issue
Block a user