mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-17 11:52:42 +00:00
Altair consensus changes and refactors (#2279)
## Proposed Changes Implement the consensus changes necessary for the upcoming Altair hard fork. ## Additional Info This is quite a heavy refactor, with pivotal types like the `BeaconState` and `BeaconBlock` changing from structs to enums. This ripples through the whole codebase with field accesses changing to methods, e.g. `state.slot` => `state.slot()`. Co-authored-by: realbigsean <seananderson33@gmail.com>
This commit is contained in:
32
consensus/state_processing/src/common/altair.rs
Normal file
32
consensus/state_processing/src/common/altair.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use integer_sqrt::IntegerSquareRoot;
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use types::*;
|
||||
|
||||
/// Returns the base reward for some validator.
|
||||
///
|
||||
/// Spec v1.1.0
|
||||
pub fn get_base_reward<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
index: usize,
|
||||
// Should be == get_total_active_balance(state, spec)
|
||||
total_active_balance: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<u64, Error> {
|
||||
state
|
||||
.get_effective_balance(index)?
|
||||
.safe_div(spec.effective_balance_increment)?
|
||||
.safe_mul(get_base_reward_per_increment(total_active_balance, spec)?)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns the base reward for some validator.
|
||||
///
|
||||
/// Spec v1.1.0
|
||||
pub fn get_base_reward_per_increment(
|
||||
total_active_balance: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<u64, ArithError> {
|
||||
spec.effective_balance_increment
|
||||
.safe_mul(spec.base_reward_factor)?
|
||||
.safe_div(total_active_balance.integer_sqrt())
|
||||
}
|
||||
@@ -3,8 +3,6 @@ use safe_arith::SafeArith;
|
||||
use types::*;
|
||||
|
||||
/// Returns the base reward for some validator.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn get_base_reward<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
index: usize,
|
||||
@@ -12,13 +10,10 @@ pub fn get_base_reward<T: EthSpec>(
|
||||
total_active_balance: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<u64, BeaconStateError> {
|
||||
if total_active_balance == 0 {
|
||||
Ok(0)
|
||||
} else {
|
||||
Ok(state
|
||||
.get_effective_balance(index, spec)?
|
||||
.safe_mul(spec.base_reward_factor)?
|
||||
.safe_div(total_active_balance.integer_sqrt())?
|
||||
.safe_div(spec.base_rewards_per_epoch)?)
|
||||
}
|
||||
state
|
||||
.get_effective_balance(index)?
|
||||
.safe_mul(spec.base_reward_factor)?
|
||||
.safe_div(total_active_balance.integer_sqrt())?
|
||||
.safe_div(spec.base_rewards_per_epoch)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use integer_sqrt::IntegerSquareRoot;
|
||||
use smallvec::SmallVec;
|
||||
use types::{
|
||||
consts::altair::{
|
||||
NUM_FLAG_INDICES, TIMELY_HEAD_FLAG_INDEX, TIMELY_SOURCE_FLAG_INDEX,
|
||||
TIMELY_TARGET_FLAG_INDEX,
|
||||
},
|
||||
BeaconStateError as Error,
|
||||
};
|
||||
use types::{AttestationData, BeaconState, ChainSpec, EthSpec};
|
||||
|
||||
/// Get the participation flags for a valid attestation.
|
||||
///
|
||||
/// You should have called `verify_attestation_for_block_inclusion` or similar before
|
||||
/// calling this function, in order to ensure that the attestation's source is correct.
|
||||
///
|
||||
/// This function will return an error if the source of the attestation doesn't match the
|
||||
/// state's relevant justified checkpoint.
|
||||
pub fn get_attestation_participation_flag_indices<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
data: &AttestationData,
|
||||
inclusion_delay: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<SmallVec<[usize; NUM_FLAG_INDICES]>, Error> {
|
||||
let justified_checkpoint = if data.target.epoch == state.current_epoch() {
|
||||
state.current_justified_checkpoint()
|
||||
} else {
|
||||
state.previous_justified_checkpoint()
|
||||
};
|
||||
|
||||
// Matching roots.
|
||||
let is_matching_source = data.source == justified_checkpoint;
|
||||
let is_matching_target = is_matching_source
|
||||
&& data.target.root == *state.get_block_root_at_epoch(data.target.epoch)?;
|
||||
let is_matching_head =
|
||||
is_matching_target && data.beacon_block_root == *state.get_block_root(data.slot)?;
|
||||
|
||||
if !is_matching_source {
|
||||
return Err(Error::IncorrectAttestationSource);
|
||||
}
|
||||
|
||||
// Participation flag indices
|
||||
let mut participation_flag_indices = SmallVec::new();
|
||||
if is_matching_source && inclusion_delay <= T::slots_per_epoch().integer_sqrt() {
|
||||
participation_flag_indices.push(TIMELY_SOURCE_FLAG_INDEX);
|
||||
}
|
||||
if is_matching_target && inclusion_delay <= T::slots_per_epoch() {
|
||||
participation_flag_indices.push(TIMELY_TARGET_FLAG_INDEX);
|
||||
}
|
||||
if is_matching_head && inclusion_delay == spec.min_attestation_inclusion_delay {
|
||||
participation_flag_indices.push(TIMELY_HEAD_FLAG_INDEX);
|
||||
}
|
||||
Ok(participation_flag_indices)
|
||||
}
|
||||
@@ -3,40 +3,36 @@ use std::cmp::max;
|
||||
use types::{BeaconStateError as Error, *};
|
||||
|
||||
/// Initiate the exit of the validator of the given `index`.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn initiate_validator_exit<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
index: usize,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if index >= state.validators.len() {
|
||||
return Err(Error::UnknownValidator(index as u64));
|
||||
}
|
||||
|
||||
// Return if the validator already initiated exit
|
||||
if state.validators[index].exit_epoch != spec.far_future_epoch {
|
||||
if state.get_validator(index)?.exit_epoch != spec.far_future_epoch {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Ensure the exit cache is built.
|
||||
state.exit_cache.build(&state.validators, spec)?;
|
||||
state.build_exit_cache(spec)?;
|
||||
|
||||
// Compute exit queue epoch
|
||||
let delayed_epoch = state.compute_activation_exit_epoch(state.current_epoch(), spec)?;
|
||||
let mut exit_queue_epoch = state
|
||||
.exit_cache
|
||||
.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)?;
|
||||
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.safe_add_assign(1)?;
|
||||
}
|
||||
|
||||
state.exit_cache.record_validator_exit(exit_queue_epoch)?;
|
||||
state.validators[index].exit_epoch = exit_queue_epoch;
|
||||
state.validators[index].withdrawable_epoch =
|
||||
state
|
||||
.exit_cache_mut()
|
||||
.record_validator_exit(exit_queue_epoch)?;
|
||||
state.get_validator_mut(index)?.exit_epoch = exit_queue_epoch;
|
||||
state.get_validator_mut(index)?.withdrawable_epoch =
|
||||
exit_queue_epoch.safe_add(spec.min_validator_withdrawability_delay)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,34 +1,40 @@
|
||||
mod deposit_data_tree;
|
||||
mod get_attestation_participation;
|
||||
mod get_attesting_indices;
|
||||
mod get_base_reward;
|
||||
mod get_indexed_attestation;
|
||||
mod initiate_validator_exit;
|
||||
mod slash_validator;
|
||||
|
||||
pub mod altair;
|
||||
pub mod base;
|
||||
|
||||
pub use deposit_data_tree::DepositDataTree;
|
||||
pub use get_attestation_participation::get_attestation_participation_flag_indices;
|
||||
pub use get_attesting_indices::get_attesting_indices;
|
||||
pub use get_base_reward::get_base_reward;
|
||||
pub use get_indexed_attestation::get_indexed_attestation;
|
||||
pub use initiate_validator_exit::initiate_validator_exit;
|
||||
pub use slash_validator::slash_validator;
|
||||
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use types::{BeaconState, EthSpec};
|
||||
use safe_arith::SafeArith;
|
||||
use types::{BeaconState, BeaconStateError, EthSpec};
|
||||
|
||||
/// Increase the balance of a validator, erroring upon overflow, as per the spec.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn increase_balance<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
index: usize,
|
||||
delta: u64,
|
||||
) -> Result<(), ArithError> {
|
||||
state.balances[index].safe_add_assign(delta)
|
||||
) -> Result<(), BeaconStateError> {
|
||||
state.get_balance_mut(index)?.safe_add_assign(delta)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decrease the balance of a validator, saturating upon overflow, as per the spec.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn decrease_balance<E: EthSpec>(state: &mut BeaconState<E>, index: usize, delta: u64) {
|
||||
state.balances[index] = state.balances[index].saturating_sub(delta);
|
||||
pub fn decrease_balance<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
index: usize,
|
||||
delta: u64,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
let balance = state.get_balance_mut(index)?;
|
||||
*balance = balance.saturating_sub(delta);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,55 +1,61 @@
|
||||
use crate::common::{decrease_balance, increase_balance, initiate_validator_exit};
|
||||
use safe_arith::SafeArith;
|
||||
use std::cmp;
|
||||
use types::{BeaconStateError as Error, *};
|
||||
use types::{
|
||||
consts::altair::{PROPOSER_WEIGHT, WEIGHT_DENOMINATOR},
|
||||
BeaconStateError as Error, *,
|
||||
};
|
||||
|
||||
/// Slash the validator with index ``index``.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
/// Slash the validator with index `slashed_index`.
|
||||
pub fn slash_validator<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
slashed_index: usize,
|
||||
opt_whistleblower_index: Option<usize>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if slashed_index >= state.validators.len() || slashed_index >= state.balances.len() {
|
||||
return Err(BeaconStateError::UnknownValidator(slashed_index as u64));
|
||||
}
|
||||
|
||||
let epoch = state.current_epoch();
|
||||
|
||||
initiate_validator_exit(state, slashed_index, spec)?;
|
||||
|
||||
state.validators[slashed_index].slashed = true;
|
||||
state.validators[slashed_index].withdrawable_epoch = cmp::max(
|
||||
state.validators[slashed_index].withdrawable_epoch,
|
||||
let validator = state.get_validator_mut(slashed_index)?;
|
||||
validator.slashed = true;
|
||||
validator.withdrawable_epoch = cmp::max(
|
||||
validator.withdrawable_epoch,
|
||||
epoch.safe_add(T::EpochsPerSlashingsVector::to_u64())?,
|
||||
);
|
||||
let validator_effective_balance = state.get_effective_balance(slashed_index, spec)?;
|
||||
let validator_effective_balance = validator.effective_balance;
|
||||
state.set_slashings(
|
||||
epoch,
|
||||
state
|
||||
.get_slashings(epoch)?
|
||||
.safe_add(validator_effective_balance)?,
|
||||
)?;
|
||||
|
||||
let min_slashing_penalty_quotient = match state {
|
||||
BeaconState::Base(_) => spec.min_slashing_penalty_quotient,
|
||||
BeaconState::Altair(_) => spec.min_slashing_penalty_quotient_altair,
|
||||
};
|
||||
decrease_balance(
|
||||
state,
|
||||
slashed_index,
|
||||
validator_effective_balance.safe_div(spec.min_slashing_penalty_quotient)?,
|
||||
);
|
||||
validator_effective_balance.safe_div(min_slashing_penalty_quotient)?,
|
||||
)?;
|
||||
|
||||
// Apply proposer and whistleblower rewards
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot, spec)?;
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot(), spec)?;
|
||||
let whistleblower_index = opt_whistleblower_index.unwrap_or(proposer_index);
|
||||
let whistleblower_reward =
|
||||
validator_effective_balance.safe_div(spec.whistleblower_reward_quotient)?;
|
||||
let proposer_reward = whistleblower_reward.safe_div(spec.proposer_reward_quotient)?;
|
||||
let proposer_reward = match state {
|
||||
BeaconState::Base(_) => whistleblower_reward.safe_div(spec.proposer_reward_quotient)?,
|
||||
BeaconState::Altair(_) => whistleblower_reward
|
||||
.safe_mul(PROPOSER_WEIGHT)?
|
||||
.safe_div(WEIGHT_DENOMINATOR)?,
|
||||
};
|
||||
|
||||
// Ensure the whistleblower index is in the validator registry.
|
||||
if state.validators.get(whistleblower_index).is_none() {
|
||||
return Err(BeaconStateError::UnknownValidator(
|
||||
whistleblower_index as u64,
|
||||
));
|
||||
if state.validators().get(whistleblower_index).is_none() {
|
||||
return Err(BeaconStateError::UnknownValidator(whistleblower_index));
|
||||
}
|
||||
|
||||
increase_balance(state, proposer_index, proposer_reward)?;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use super::per_block_processing::{errors::BlockProcessingError, process_deposit};
|
||||
use super::per_block_processing::{
|
||||
errors::BlockProcessingError, process_operations::process_deposit,
|
||||
};
|
||||
use crate::common::DepositDataTree;
|
||||
use crate::upgrade::upgrade_to_altair;
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use tree_hash::TreeHash;
|
||||
use types::DEPOSIT_TREE_DEPTH;
|
||||
use types::*;
|
||||
|
||||
/// Initialize a `BeaconState` from genesis data.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
// TODO: this is quite inefficient and we probably want to rethink how we do this
|
||||
pub fn initialize_beacon_state_from_eth1<T: EthSpec>(
|
||||
eth1_block_hash: Hash256,
|
||||
eth1_timestamp: u64,
|
||||
@@ -33,42 +33,53 @@ pub fn initialize_beacon_state_from_eth1<T: EthSpec>(
|
||||
deposit_tree
|
||||
.push_leaf(deposit.data.tree_hash_root())
|
||||
.map_err(BlockProcessingError::MerkleTreeError)?;
|
||||
state.eth1_data.deposit_root = deposit_tree.root();
|
||||
state.eth1_data_mut().deposit_root = deposit_tree.root();
|
||||
process_deposit(&mut state, &deposit, spec, true)?;
|
||||
}
|
||||
|
||||
process_activations(&mut state, spec)?;
|
||||
|
||||
// To support testnets with Altair enabled from genesis, perform a possible state upgrade here.
|
||||
// This must happen *after* deposits and activations are processed or the calculation of sync
|
||||
// committees during the upgrade will fail. It's a bit cheeky to do this instead of having
|
||||
// separate Altair genesis initialization logic, but it turns out that our
|
||||
// use of `BeaconBlock::empty` in `BeaconState::new` is sufficient to correctly initialise
|
||||
// the `latest_block_header` as per:
|
||||
// https://github.com/ethereum/eth2.0-specs/pull/2323
|
||||
if spec.fork_name_at_epoch(state.current_epoch()) == ForkName::Altair {
|
||||
upgrade_to_altair(&mut state, spec)?;
|
||||
}
|
||||
|
||||
// Now that we have our validators, initialize the caches (including the committees)
|
||||
state.build_all_caches(spec)?;
|
||||
|
||||
// Set genesis validators root for domain separation and chain versioning
|
||||
state.genesis_validators_root = state.update_validators_tree_hash_cache()?;
|
||||
*state.genesis_validators_root_mut() = state.update_validators_tree_hash_cache()?;
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
/// Determine whether a candidate genesis state is suitable for starting the chain.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn is_valid_genesis_state<T: EthSpec>(state: &BeaconState<T>, spec: &ChainSpec) -> bool {
|
||||
state
|
||||
.get_active_validator_indices(T::genesis_epoch(), spec)
|
||||
.map_or(false, |active_validators| {
|
||||
state.genesis_time >= spec.min_genesis_time
|
||||
state.genesis_time() >= spec.min_genesis_time
|
||||
&& active_validators.len() as u64 >= spec.min_genesis_active_validator_count
|
||||
})
|
||||
}
|
||||
|
||||
/// Activate genesis validators, if their balance is acceptable.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn process_activations<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
for (index, validator) in state.validators.iter_mut().enumerate() {
|
||||
let balance = state.balances[index];
|
||||
let (validators, balances) = state.validators_and_balances_mut();
|
||||
for (index, validator) in validators.iter_mut().enumerate() {
|
||||
let balance = balances
|
||||
.get(index)
|
||||
.copied()
|
||||
.ok_or(Error::BalancesOutOfBounds(index))?;
|
||||
validator.effective_balance = std::cmp::min(
|
||||
balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?,
|
||||
spec.max_effective_balance,
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
#![deny(clippy::integer_arithmetic)]
|
||||
#![deny(clippy::disallowed_method)]
|
||||
// Clippy lint set-up (disabled in tests)
|
||||
#![cfg_attr(
|
||||
not(test),
|
||||
deny(
|
||||
clippy::integer_arithmetic,
|
||||
clippy::disallowed_method,
|
||||
clippy::indexing_slicing,
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
clippy::panic,
|
||||
clippy::let_underscore_must_use
|
||||
)
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
@@ -10,7 +21,7 @@ pub mod per_block_processing;
|
||||
pub mod per_epoch_processing;
|
||||
pub mod per_slot_processing;
|
||||
pub mod state_advance;
|
||||
pub mod test_utils;
|
||||
pub mod upgrade;
|
||||
pub mod verify_operation;
|
||||
|
||||
pub use genesis::{
|
||||
@@ -21,6 +32,8 @@ pub use per_block_processing::{
|
||||
block_signature_verifier, errors::BlockProcessingError, per_block_processing, signature_sets,
|
||||
BlockSignatureStrategy, BlockSignatureVerifier, VerifySignatures,
|
||||
};
|
||||
pub use per_epoch_processing::{errors::EpochProcessingError, per_epoch_processing};
|
||||
pub use per_epoch_processing::{
|
||||
errors::EpochProcessingError, process_epoch as per_epoch_processing,
|
||||
};
|
||||
pub use per_slot_processing::{per_slot_processing, Error as SlotProcessingError};
|
||||
pub use verify_operation::{SigVerifiedOp, VerifyOperation};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::common::{increase_balance, initiate_validator_exit, slash_validator};
|
||||
use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid, IntoWithIndex};
|
||||
use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid};
|
||||
use rayon::prelude::*;
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set};
|
||||
@@ -10,8 +9,10 @@ pub use self::verify_attester_slashing::{
|
||||
get_slashable_indices, get_slashable_indices_modular, verify_attester_slashing,
|
||||
};
|
||||
pub use self::verify_proposer_slashing::verify_proposer_slashing;
|
||||
pub use altair::sync_committee::process_sync_aggregate;
|
||||
pub use block_signature_verifier::BlockSignatureVerifier;
|
||||
pub use is_valid_indexed_attestation::is_valid_indexed_attestation;
|
||||
pub use process_operations::process_operations;
|
||||
pub use verify_attestation::{
|
||||
verify_attestation_for_block_inclusion, verify_attestation_for_state,
|
||||
};
|
||||
@@ -20,10 +21,11 @@ pub use verify_deposit::{
|
||||
};
|
||||
pub use verify_exit::{verify_exit, verify_exit_time_independent_only};
|
||||
|
||||
pub mod block_processing_builder;
|
||||
pub mod altair;
|
||||
pub mod block_signature_verifier;
|
||||
pub mod errors;
|
||||
mod is_valid_indexed_attestation;
|
||||
pub mod process_operations;
|
||||
pub mod signature_sets;
|
||||
pub mod tests;
|
||||
mod verify_attestation;
|
||||
@@ -74,16 +76,25 @@ impl VerifySignatures {
|
||||
/// re-calculating the root when it is already known. Note `block_root` should be equal to the
|
||||
/// tree hash root of the block, NOT the signing root of the block. This function takes
|
||||
/// care of mixing in the domain.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn per_block_processing<T: EthSpec>(
|
||||
mut state: &mut BeaconState<T>,
|
||||
state: &mut BeaconState<T>,
|
||||
signed_block: &SignedBeaconBlock<T>,
|
||||
block_root: Option<Hash256>,
|
||||
block_signature_strategy: BlockSignatureStrategy,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
let block = &signed_block.message;
|
||||
let block = signed_block.message();
|
||||
|
||||
// Verify that the `SignedBeaconBlock` instantiation matches the fork at `signed_block.slot()`.
|
||||
signed_block
|
||||
.fork_name(spec)
|
||||
.map_err(BlockProcessingError::InconsistentBlockFork)?;
|
||||
|
||||
// Verify that the `BeaconState` instantiation matches the fork at `state.slot()`.
|
||||
state
|
||||
.fork_name(spec)
|
||||
.map_err(BlockProcessingError::InconsistentStateFork)?;
|
||||
|
||||
let verify_signatures = match block_signature_strategy {
|
||||
BlockSignatureStrategy::VerifyBulk => {
|
||||
// Verify all signatures in the block at once.
|
||||
@@ -104,70 +115,51 @@ pub fn per_block_processing<T: EthSpec>(
|
||||
BlockSignatureStrategy::NoVerification => VerifySignatures::False,
|
||||
};
|
||||
|
||||
process_block_header(state, block, spec)?;
|
||||
let proposer_index = process_block_header(state, block, spec)?;
|
||||
|
||||
if verify_signatures.is_true() {
|
||||
verify_block_signature(&state, signed_block, block_root, &spec)?;
|
||||
verify_block_signature(state, signed_block, block_root, spec)?;
|
||||
}
|
||||
|
||||
// Ensure the current and previous epoch caches are built.
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||
|
||||
process_randao(&mut state, &block, verify_signatures, &spec)?;
|
||||
process_eth1_data(&mut state, &block.body.eth1_data)?;
|
||||
process_proposer_slashings(
|
||||
&mut state,
|
||||
&block.body.proposer_slashings,
|
||||
verify_signatures,
|
||||
spec,
|
||||
)?;
|
||||
process_attester_slashings(
|
||||
&mut state,
|
||||
&block.body.attester_slashings,
|
||||
verify_signatures,
|
||||
spec,
|
||||
)?;
|
||||
process_attestations(
|
||||
&mut state,
|
||||
&block.body.attestations,
|
||||
verify_signatures,
|
||||
spec,
|
||||
)?;
|
||||
process_deposits(&mut state, &block.body.deposits, spec)?;
|
||||
process_exits(
|
||||
&mut state,
|
||||
&block.body.voluntary_exits,
|
||||
verify_signatures,
|
||||
spec,
|
||||
)?;
|
||||
process_randao(state, block, verify_signatures, spec)?;
|
||||
process_eth1_data(state, block.body().eth1_data())?;
|
||||
process_operations(state, block.body(), verify_signatures, spec)?;
|
||||
|
||||
if let BeaconBlockRef::Altair(inner) = block {
|
||||
process_sync_aggregate(state, &inner.body.sync_aggregate, proposer_index, spec)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Processes the block header.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
/// Processes the block header, returning the proposer index.
|
||||
pub fn process_block_header<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
block: &BeaconBlock<T>,
|
||||
block: BeaconBlockRef<'_, T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockOperationError<HeaderInvalid>> {
|
||||
) -> Result<u64, BlockOperationError<HeaderInvalid>> {
|
||||
// Verify that the slots match
|
||||
verify!(block.slot == state.slot, HeaderInvalid::StateSlotMismatch);
|
||||
verify!(
|
||||
block.slot() == state.slot(),
|
||||
HeaderInvalid::StateSlotMismatch
|
||||
);
|
||||
|
||||
// Verify that the block is newer than the latest block header
|
||||
verify!(
|
||||
block.slot > state.latest_block_header.slot,
|
||||
block.slot() > state.latest_block_header().slot,
|
||||
HeaderInvalid::OlderThanLatestBlockHeader {
|
||||
block_slot: block.slot,
|
||||
latest_block_header_slot: state.latest_block_header.slot,
|
||||
block_slot: block.slot(),
|
||||
latest_block_header_slot: state.latest_block_header().slot,
|
||||
}
|
||||
);
|
||||
|
||||
// Verify that proposer index is the correct index
|
||||
let proposer_index = block.proposer_index as usize;
|
||||
let state_proposer_index = state.get_beacon_proposer_index(block.slot, spec)?;
|
||||
let proposer_index = block.proposer_index() as usize;
|
||||
let state_proposer_index = state.get_beacon_proposer_index(block.slot(), spec)?;
|
||||
verify!(
|
||||
proposer_index == state_proposer_index,
|
||||
HeaderInvalid::ProposerIndexMismatch {
|
||||
@@ -176,25 +168,24 @@ pub fn process_block_header<T: EthSpec>(
|
||||
}
|
||||
);
|
||||
|
||||
let expected_previous_block_root = state.latest_block_header.tree_hash_root();
|
||||
let expected_previous_block_root = state.latest_block_header().tree_hash_root();
|
||||
verify!(
|
||||
block.parent_root == expected_previous_block_root,
|
||||
block.parent_root() == expected_previous_block_root,
|
||||
HeaderInvalid::ParentBlockRootMismatch {
|
||||
state: expected_previous_block_root,
|
||||
block: block.parent_root,
|
||||
block: block.parent_root(),
|
||||
}
|
||||
);
|
||||
|
||||
state.latest_block_header = block.temporary_block_header();
|
||||
*state.latest_block_header_mut() = block.temporary_block_header();
|
||||
|
||||
// Verify proposer is not slashed
|
||||
let proposer = &state.validators[proposer_index];
|
||||
verify!(
|
||||
!proposer.slashed,
|
||||
!state.get_validator(proposer_index)?.slashed,
|
||||
HeaderInvalid::ProposerSlashed(proposer_index)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
Ok(block.proposer_index())
|
||||
}
|
||||
|
||||
/// Verifies the signature of a block.
|
||||
@@ -223,11 +214,9 @@ pub fn verify_block_signature<T: EthSpec>(
|
||||
|
||||
/// Verifies the `randao_reveal` against the block's proposer pubkey and updates
|
||||
/// `state.latest_randao_mixes`.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn process_randao<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
block: &BeaconBlock<T>,
|
||||
block: BeaconBlockRef<'_, T>,
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
@@ -240,37 +229,33 @@ pub fn process_randao<T: EthSpec>(
|
||||
}
|
||||
|
||||
// Update the current epoch RANDAO mix.
|
||||
state.update_randao_mix(state.current_epoch(), &block.body.randao_reveal)?;
|
||||
state.update_randao_mix(state.current_epoch(), block.body().randao_reveal())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update the `state.eth1_data_votes` based upon the `eth1_data` provided.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn process_eth1_data<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
eth1_data: &Eth1Data,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(new_eth1_data) = get_new_eth1_data(state, eth1_data)? {
|
||||
state.eth1_data = new_eth1_data;
|
||||
*state.eth1_data_mut() = new_eth1_data;
|
||||
}
|
||||
|
||||
state.eth1_data_votes.push(eth1_data.clone())?;
|
||||
state.eth1_data_votes_mut().push(eth1_data.clone())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `Ok(Some(eth1_data))` if adding the given `eth1_data` to `state.eth1_data_votes` would
|
||||
/// result in a change to `state.eth1_data`.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn get_new_eth1_data<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
eth1_data: &Eth1Data,
|
||||
) -> Result<Option<Eth1Data>, ArithError> {
|
||||
let num_votes = state
|
||||
.eth1_data_votes
|
||||
.eth1_data_votes()
|
||||
.iter()
|
||||
.filter(|vote| *vote == eth1_data)
|
||||
.count();
|
||||
@@ -282,226 +267,3 @@ pub fn get_new_eth1_data<T: EthSpec>(
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates each `ProposerSlashing` and updates the state, short-circuiting on an invalid object.
|
||||
///
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn process_proposer_slashings<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
proposer_slashings: &[ProposerSlashing],
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
// Verify and apply proposer slashings in series.
|
||||
// We have to verify in series because an invalid block may contain multiple slashings
|
||||
// for the same validator, and we need to correctly detect and reject that.
|
||||
proposer_slashings
|
||||
.iter()
|
||||
.enumerate()
|
||||
.try_for_each(|(i, proposer_slashing)| {
|
||||
verify_proposer_slashing(proposer_slashing, &state, verify_signatures, spec)
|
||||
.map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
slash_validator(
|
||||
state,
|
||||
proposer_slashing.signed_header_1.message.proposer_index as usize,
|
||||
None,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Validates each `AttesterSlashing` and updates the state, short-circuiting on an invalid object.
|
||||
///
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn process_attester_slashings<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
attester_slashings: &[AttesterSlashing<T>],
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
for (i, attester_slashing) in attester_slashings.iter().enumerate() {
|
||||
verify_attester_slashing(&state, &attester_slashing, verify_signatures, spec)
|
||||
.map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
let slashable_indices =
|
||||
get_slashable_indices(&state, &attester_slashing).map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
for i in slashable_indices {
|
||||
slash_validator(state, i as usize, None, spec)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates each `Attestation` and updates the state, short-circuiting on an invalid object.
|
||||
///
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn process_attestations<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
attestations: &[Attestation<T>],
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
// Ensure the previous epoch cache exists.
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot, spec)? as u64;
|
||||
|
||||
// Verify and apply each attestation.
|
||||
for (i, attestation) in attestations.iter().enumerate() {
|
||||
verify_attestation_for_block_inclusion(state, attestation, verify_signatures, spec)
|
||||
.map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
let pending_attestation = PendingAttestation {
|
||||
aggregation_bits: attestation.aggregation_bits.clone(),
|
||||
data: attestation.data.clone(),
|
||||
inclusion_delay: state.slot.safe_sub(attestation.data.slot)?.as_u64(),
|
||||
proposer_index,
|
||||
};
|
||||
|
||||
if attestation.data.target.epoch == state.current_epoch() {
|
||||
state.current_epoch_attestations.push(pending_attestation)?;
|
||||
} else {
|
||||
state
|
||||
.previous_epoch_attestations
|
||||
.push(pending_attestation)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates each `Deposit` and updates the state, short-circuiting on an invalid object.
|
||||
///
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn process_deposits<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
deposits: &[Deposit],
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
let expected_deposit_len = std::cmp::min(
|
||||
T::MaxDeposits::to_u64(),
|
||||
state.get_outstanding_deposit_len()?,
|
||||
);
|
||||
block_verify!(
|
||||
deposits.len() as u64 == expected_deposit_len,
|
||||
BlockProcessingError::DepositCountInvalid {
|
||||
expected: expected_deposit_len as usize,
|
||||
found: deposits.len(),
|
||||
}
|
||||
);
|
||||
|
||||
// Verify merkle proofs in parallel.
|
||||
deposits
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.try_for_each(|(i, deposit)| {
|
||||
verify_deposit_merkle_proof(
|
||||
state,
|
||||
deposit,
|
||||
state.eth1_deposit_index.safe_add(i as u64)?,
|
||||
spec,
|
||||
)
|
||||
.map_err(|e| e.into_with_index(i))
|
||||
})?;
|
||||
|
||||
// Update the state in series.
|
||||
for deposit in deposits {
|
||||
process_deposit(state, deposit, spec, false)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process a single deposit, optionally verifying its merkle proof.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn process_deposit<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
deposit: &Deposit,
|
||||
spec: &ChainSpec,
|
||||
verify_merkle_proof: bool,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
let deposit_index = state.eth1_deposit_index as usize;
|
||||
if verify_merkle_proof {
|
||||
verify_deposit_merkle_proof(state, deposit, state.eth1_deposit_index, spec)
|
||||
.map_err(|e| e.into_with_index(deposit_index))?;
|
||||
}
|
||||
|
||||
state.eth1_deposit_index.safe_add_assign(1)?;
|
||||
|
||||
// Get an `Option<u64>` where `u64` is the validator index if this deposit public key
|
||||
// already exists in the beacon_state.
|
||||
let validator_index = get_existing_validator_index(state, &deposit.data.pubkey)
|
||||
.map_err(|e| e.into_with_index(deposit_index))?;
|
||||
|
||||
let amount = deposit.data.amount;
|
||||
|
||||
if let Some(index) = validator_index {
|
||||
// Update the existing validator balance.
|
||||
increase_balance(state, index as usize, amount)?;
|
||||
} else {
|
||||
// The signature should be checked for new validators. Return early for a bad
|
||||
// signature.
|
||||
if verify_deposit_signature(&deposit.data, spec).is_err() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Create a new validator.
|
||||
let validator = Validator {
|
||||
pubkey: deposit.data.pubkey,
|
||||
withdrawal_credentials: deposit.data.withdrawal_credentials,
|
||||
activation_eligibility_epoch: spec.far_future_epoch,
|
||||
activation_epoch: spec.far_future_epoch,
|
||||
exit_epoch: spec.far_future_epoch,
|
||||
withdrawable_epoch: spec.far_future_epoch,
|
||||
effective_balance: std::cmp::min(
|
||||
amount.safe_sub(amount.safe_rem(spec.effective_balance_increment)?)?,
|
||||
spec.max_effective_balance,
|
||||
),
|
||||
slashed: false,
|
||||
};
|
||||
state.validators.push(validator)?;
|
||||
state.balances.push(deposit.data.amount)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates each `Exit` and updates the state, short-circuiting on an invalid object.
|
||||
///
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn process_exits<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
voluntary_exits: &[SignedVoluntaryExit],
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
// Verify and apply each exit in series. We iterate in series because higher-index exits may
|
||||
// become invalid due to the application of lower-index ones.
|
||||
for (i, exit) in voluntary_exits.iter().enumerate() {
|
||||
verify_exit(&state, exit, verify_signatures, spec).map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
initiate_validator_exit(state, exit.message.validator_index as usize, spec)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod sync_committee;
|
||||
@@ -0,0 +1,86 @@
|
||||
use crate::common::{altair::get_base_reward_per_increment, decrease_balance, increase_balance};
|
||||
use crate::per_block_processing::errors::{BlockProcessingError, SyncAggregateInvalid};
|
||||
use safe_arith::SafeArith;
|
||||
use tree_hash::TreeHash;
|
||||
use types::consts::altair::{PROPOSER_WEIGHT, SYNC_REWARD_WEIGHT, WEIGHT_DENOMINATOR};
|
||||
use types::{BeaconState, ChainSpec, Domain, EthSpec, SigningData, SyncAggregate, Unsigned};
|
||||
|
||||
pub fn process_sync_aggregate<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
aggregate: &SyncAggregate<T>,
|
||||
proposer_index: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
// Verify sync committee aggregate signature signing over the previous slot block root
|
||||
let previous_slot = state.slot().saturating_sub(1u64);
|
||||
|
||||
let current_sync_committee = state.current_sync_committee()?.clone();
|
||||
let committee_pubkeys = ¤t_sync_committee.pubkeys;
|
||||
|
||||
let participant_pubkeys = committee_pubkeys
|
||||
.iter()
|
||||
.zip(aggregate.sync_committee_bits.iter())
|
||||
.flat_map(|(pubkey, bit)| {
|
||||
if bit {
|
||||
// FIXME(altair): accelerate pubkey decompression with a cache
|
||||
Some(pubkey.decompress())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| SyncAggregateInvalid::PubkeyInvalid)?;
|
||||
|
||||
let domain = spec.get_domain(
|
||||
previous_slot.epoch(T::slots_per_epoch()),
|
||||
Domain::SyncCommittee,
|
||||
&state.fork(),
|
||||
state.genesis_validators_root(),
|
||||
);
|
||||
|
||||
let signing_root = SigningData {
|
||||
object_root: *state.get_block_root(previous_slot)?,
|
||||
domain,
|
||||
}
|
||||
.tree_hash_root();
|
||||
|
||||
let pubkey_refs = participant_pubkeys.iter().collect::<Vec<_>>();
|
||||
if !aggregate
|
||||
.sync_committee_signature
|
||||
.eth2_fast_aggregate_verify(signing_root, &pubkey_refs)
|
||||
{
|
||||
return Err(SyncAggregateInvalid::SignatureInvalid.into());
|
||||
}
|
||||
|
||||
// Compute participant and proposer rewards
|
||||
let total_active_balance = state.get_total_active_balance(spec)?;
|
||||
let total_active_increments =
|
||||
total_active_balance.safe_div(spec.effective_balance_increment)?;
|
||||
let total_base_rewards = get_base_reward_per_increment(total_active_balance, spec)?
|
||||
.safe_mul(total_active_increments)?;
|
||||
let max_participant_rewards = total_base_rewards
|
||||
.safe_mul(SYNC_REWARD_WEIGHT)?
|
||||
.safe_div(WEIGHT_DENOMINATOR)?
|
||||
.safe_div(T::slots_per_epoch())?;
|
||||
let participant_reward = max_participant_rewards.safe_div(T::SyncCommitteeSize::to_u64())?;
|
||||
let proposer_reward = participant_reward
|
||||
.safe_mul(PROPOSER_WEIGHT)?
|
||||
.safe_div(WEIGHT_DENOMINATOR.safe_sub(PROPOSER_WEIGHT)?)?;
|
||||
|
||||
// Apply participant and proposer rewards
|
||||
let committee_indices = state.get_sync_committee_indices(¤t_sync_committee)?;
|
||||
|
||||
for (participant_index, participation_bit) in committee_indices
|
||||
.into_iter()
|
||||
.zip(aggregate.sync_committee_bits.iter())
|
||||
{
|
||||
if participation_bit {
|
||||
increase_balance(state, participant_index as usize, participant_reward)?;
|
||||
increase_balance(state, proposer_index as usize, proposer_reward)?;
|
||||
} else {
|
||||
decrease_balance(state, participant_index as usize, participant_reward)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
use tree_hash::TreeHash;
|
||||
use types::test_utils::{
|
||||
AttestationTestTask, AttesterSlashingTestTask, DepositTestTask, ProposerSlashingTestTask,
|
||||
TestingAttestationDataBuilder, TestingBeaconBlockBuilder, TestingBeaconStateBuilder,
|
||||
};
|
||||
use types::*;
|
||||
|
||||
pub struct BlockProcessingBuilder<'a, T: EthSpec> {
|
||||
pub state: BeaconState<T>,
|
||||
pub keypairs: Vec<Keypair>,
|
||||
pub block_builder: TestingBeaconBlockBuilder<T>,
|
||||
pub spec: &'a ChainSpec,
|
||||
}
|
||||
|
||||
impl<'a, T: EthSpec> BlockProcessingBuilder<'a, T> {
|
||||
pub fn new(num_validators: usize, state_slot: Slot, spec: &'a ChainSpec) -> Self {
|
||||
let mut state_builder =
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(num_validators, &spec);
|
||||
state_builder.teleport_to_slot(state_slot);
|
||||
let (state, keypairs) = state_builder.build();
|
||||
let block_builder = TestingBeaconBlockBuilder::new(spec);
|
||||
|
||||
Self {
|
||||
state,
|
||||
keypairs,
|
||||
block_builder,
|
||||
spec,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_caches(mut self) -> Self {
|
||||
self.state
|
||||
.build_all_caches(self.spec)
|
||||
.expect("caches build OK");
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build_with_n_deposits(
|
||||
mut self,
|
||||
num_deposits: u64,
|
||||
test_task: DepositTestTask,
|
||||
randao_sk: Option<SecretKey>,
|
||||
previous_block_root: Option<Hash256>,
|
||||
spec: &ChainSpec,
|
||||
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
|
||||
let (mut state, keypairs) = (self.state, self.keypairs);
|
||||
|
||||
let builder = &mut self.block_builder;
|
||||
|
||||
builder.set_slot(state.slot);
|
||||
|
||||
match previous_block_root {
|
||||
Some(root) => builder.set_parent_root(root),
|
||||
None => builder.set_parent_root(state.latest_block_header.tree_hash_root()),
|
||||
}
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap();
|
||||
let keypair = &keypairs[proposer_index];
|
||||
|
||||
builder.set_proposer_index(proposer_index as u64);
|
||||
|
||||
match randao_sk {
|
||||
Some(sk) => {
|
||||
builder.set_randao_reveal(&sk, &state.fork, state.genesis_validators_root, spec)
|
||||
}
|
||||
None => builder.set_randao_reveal(
|
||||
&keypair.sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
),
|
||||
}
|
||||
|
||||
self.block_builder.insert_deposits(
|
||||
spec.max_effective_balance,
|
||||
test_task,
|
||||
1,
|
||||
num_deposits,
|
||||
&mut state,
|
||||
spec,
|
||||
);
|
||||
|
||||
let block = self.block_builder.build(
|
||||
&keypair.sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
|
||||
(block, state)
|
||||
}
|
||||
|
||||
/// Insert a signed `VoluntaryIndex` for the given validator at the given `exit_epoch`.
|
||||
pub fn insert_exit(mut self, validator_index: u64, exit_epoch: Epoch) -> Self {
|
||||
self.block_builder.insert_exit(
|
||||
validator_index,
|
||||
exit_epoch,
|
||||
&self.keypairs[validator_index as usize].sk,
|
||||
&self.state,
|
||||
self.spec,
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
/// Insert an attestation for the given slot and index.
|
||||
///
|
||||
/// It will be signed by all validators for which `should_sign` returns `true`
|
||||
/// when called with `(committee_position, validator_index)`.
|
||||
// TODO: consider using this pattern to replace the TestingAttestationBuilder
|
||||
pub fn insert_attestation(
|
||||
mut self,
|
||||
slot: Slot,
|
||||
index: u64,
|
||||
mut should_sign: impl FnMut(usize, usize) -> bool,
|
||||
) -> Self {
|
||||
let committee = self.state.get_beacon_committee(slot, index).unwrap();
|
||||
let data = TestingAttestationDataBuilder::new(
|
||||
AttestationTestTask::Valid,
|
||||
&self.state,
|
||||
index,
|
||||
slot,
|
||||
self.spec,
|
||||
)
|
||||
.build();
|
||||
|
||||
let mut attestation = Attestation {
|
||||
aggregation_bits: BitList::with_capacity(committee.committee.len()).unwrap(),
|
||||
data,
|
||||
signature: AggregateSignature::empty(),
|
||||
};
|
||||
|
||||
for (i, &validator_index) in committee.committee.iter().enumerate() {
|
||||
if should_sign(i, validator_index) {
|
||||
attestation
|
||||
.sign(
|
||||
&self.keypairs[validator_index].sk,
|
||||
i,
|
||||
&self.state.fork,
|
||||
self.state.genesis_validators_root,
|
||||
self.spec,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
self.block_builder
|
||||
.block
|
||||
.body
|
||||
.attestations
|
||||
.push(attestation)
|
||||
.unwrap();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Apply a mutation to the `BeaconBlock` before signing.
|
||||
pub fn modify(mut self, f: impl FnOnce(&mut BeaconBlock<T>)) -> Self {
|
||||
self.block_builder.modify(f);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build_with_n_attestations(
|
||||
mut self,
|
||||
test_task: AttestationTestTask,
|
||||
num_attestations: u64,
|
||||
randao_sk: Option<SecretKey>,
|
||||
previous_block_root: Option<Hash256>,
|
||||
spec: &ChainSpec,
|
||||
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
|
||||
let (state, keypairs) = (self.state, self.keypairs);
|
||||
let builder = &mut self.block_builder;
|
||||
|
||||
builder.set_slot(state.slot);
|
||||
|
||||
match previous_block_root {
|
||||
Some(root) => builder.set_parent_root(root),
|
||||
None => builder.set_parent_root(state.latest_block_header.tree_hash_root()),
|
||||
}
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap();
|
||||
let keypair = &keypairs[proposer_index];
|
||||
|
||||
builder.set_proposer_index(proposer_index as u64);
|
||||
|
||||
match randao_sk {
|
||||
Some(sk) => {
|
||||
builder.set_randao_reveal(&sk, &state.fork, state.genesis_validators_root, spec)
|
||||
}
|
||||
None => builder.set_randao_reveal(
|
||||
&keypair.sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
),
|
||||
}
|
||||
|
||||
let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect();
|
||||
self.block_builder
|
||||
.insert_attestations(
|
||||
test_task,
|
||||
&state,
|
||||
&all_secret_keys,
|
||||
num_attestations as usize,
|
||||
spec,
|
||||
)
|
||||
.unwrap();
|
||||
let block = self.block_builder.build(
|
||||
&keypair.sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
|
||||
(block, state)
|
||||
}
|
||||
|
||||
pub fn build_with_attester_slashing(
|
||||
mut self,
|
||||
test_task: AttesterSlashingTestTask,
|
||||
num_attester_slashings: u64,
|
||||
randao_sk: Option<SecretKey>,
|
||||
previous_block_root: Option<Hash256>,
|
||||
spec: &ChainSpec,
|
||||
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
|
||||
let (state, keypairs) = (self.state, self.keypairs);
|
||||
let builder = &mut self.block_builder;
|
||||
|
||||
builder.set_slot(state.slot);
|
||||
|
||||
match previous_block_root {
|
||||
Some(root) => builder.set_parent_root(root),
|
||||
None => builder.set_parent_root(state.latest_block_header.tree_hash_root()),
|
||||
}
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap();
|
||||
let keypair = &keypairs[proposer_index];
|
||||
|
||||
builder.set_proposer_index(proposer_index as u64);
|
||||
|
||||
match randao_sk {
|
||||
Some(sk) => {
|
||||
builder.set_randao_reveal(&sk, &state.fork, state.genesis_validators_root, spec)
|
||||
}
|
||||
None => builder.set_randao_reveal(
|
||||
&keypair.sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
),
|
||||
}
|
||||
|
||||
let mut validator_indices = vec![];
|
||||
let mut secret_keys = vec![];
|
||||
for i in 0..num_attester_slashings {
|
||||
validator_indices.push(i);
|
||||
secret_keys.push(&keypairs[i as usize].sk);
|
||||
}
|
||||
|
||||
for _ in 0..num_attester_slashings {
|
||||
self.block_builder.insert_attester_slashing(
|
||||
test_task,
|
||||
&validator_indices,
|
||||
&secret_keys,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
}
|
||||
let block = self.block_builder.build(
|
||||
&keypair.sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
|
||||
(block, state)
|
||||
}
|
||||
|
||||
pub fn build_with_proposer_slashing(
|
||||
mut self,
|
||||
test_task: ProposerSlashingTestTask,
|
||||
num_proposer_slashings: u64,
|
||||
randao_sk: Option<SecretKey>,
|
||||
previous_block_root: Option<Hash256>,
|
||||
spec: &ChainSpec,
|
||||
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
|
||||
let (state, keypairs) = (self.state, self.keypairs);
|
||||
let builder = &mut self.block_builder;
|
||||
|
||||
builder.set_slot(state.slot);
|
||||
|
||||
match previous_block_root {
|
||||
Some(root) => builder.set_parent_root(root),
|
||||
None => builder.set_parent_root(state.latest_block_header.tree_hash_root()),
|
||||
}
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap();
|
||||
let keypair = &keypairs[proposer_index];
|
||||
|
||||
builder.set_proposer_index(proposer_index as u64);
|
||||
|
||||
match randao_sk {
|
||||
Some(sk) => {
|
||||
builder.set_randao_reveal(&sk, &state.fork, state.genesis_validators_root, spec)
|
||||
}
|
||||
None => builder.set_randao_reveal(
|
||||
&keypair.sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
),
|
||||
}
|
||||
|
||||
for i in 0..num_proposer_slashings {
|
||||
let validator_indices = i;
|
||||
let secret_keys = &keypairs[i as usize].sk;
|
||||
self.block_builder.insert_proposer_slashing(
|
||||
test_task,
|
||||
validator_indices,
|
||||
&secret_keys,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
}
|
||||
let block = self.block_builder.build(
|
||||
&keypair.sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
|
||||
(block, state)
|
||||
}
|
||||
|
||||
// NOTE: could remove optional args
|
||||
// NOTE: could return keypairs as well
|
||||
pub fn build(
|
||||
mut self,
|
||||
randao_sk: Option<SecretKey>,
|
||||
previous_block_root: Option<Hash256>,
|
||||
) -> (SignedBeaconBlock<T>, BeaconState<T>) {
|
||||
let (state, keypairs) = (self.state, self.keypairs);
|
||||
let spec = self.spec;
|
||||
let builder = &mut self.block_builder;
|
||||
|
||||
builder.set_slot(state.slot);
|
||||
|
||||
match previous_block_root {
|
||||
Some(root) => builder.set_parent_root(root),
|
||||
None => builder.set_parent_root(state.latest_block_header.tree_hash_root()),
|
||||
}
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap();
|
||||
let keypair = &keypairs[proposer_index];
|
||||
|
||||
builder.set_proposer_index(proposer_index as u64);
|
||||
|
||||
match randao_sk {
|
||||
Some(sk) => {
|
||||
builder.set_randao_reveal(&sk, &state.fork, state.genesis_validators_root, spec)
|
||||
}
|
||||
None => builder.set_randao_reveal(
|
||||
&keypair.sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
),
|
||||
}
|
||||
|
||||
let block = self.block_builder.build(
|
||||
&keypair.sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
|
||||
(block, state)
|
||||
}
|
||||
}
|
||||
@@ -194,7 +194,7 @@ where
|
||||
let set = randao_signature_set(
|
||||
self.state,
|
||||
self.get_pubkey.clone(),
|
||||
&block.message,
|
||||
block.message(),
|
||||
self.spec,
|
||||
)?;
|
||||
self.sets.push(set);
|
||||
@@ -204,12 +204,12 @@ where
|
||||
/// Includes all signatures in `self.block.body.proposer_slashings` for verification.
|
||||
pub fn include_proposer_slashings(&mut self, block: &'a SignedBeaconBlock<T>) -> Result<()> {
|
||||
self.sets
|
||||
.reserve(block.message.body.proposer_slashings.len() * 2);
|
||||
.reserve(block.message().body().proposer_slashings().len() * 2);
|
||||
|
||||
block
|
||||
.message
|
||||
.body
|
||||
.proposer_slashings
|
||||
.message()
|
||||
.body()
|
||||
.proposer_slashings()
|
||||
.iter()
|
||||
.try_for_each(|proposer_slashing| {
|
||||
let (set_1, set_2) = proposer_slashing_signature_set(
|
||||
@@ -229,12 +229,12 @@ where
|
||||
/// Includes all signatures in `self.block.body.attester_slashings` for verification.
|
||||
pub fn include_attester_slashings(&mut self, block: &'a SignedBeaconBlock<T>) -> Result<()> {
|
||||
self.sets
|
||||
.reserve(block.message.body.attester_slashings.len() * 2);
|
||||
.reserve(block.message().body().attester_slashings().len() * 2);
|
||||
|
||||
block
|
||||
.message
|
||||
.body
|
||||
.attester_slashings
|
||||
.message()
|
||||
.body()
|
||||
.attester_slashings()
|
||||
.iter()
|
||||
.try_for_each(|attester_slashing| {
|
||||
let (set_1, set_2) = attester_slashing_signature_sets(
|
||||
@@ -256,15 +256,16 @@ where
|
||||
&mut self,
|
||||
block: &'a SignedBeaconBlock<T>,
|
||||
) -> Result<Vec<IndexedAttestation<T>>> {
|
||||
self.sets.reserve(block.message.body.attestations.len());
|
||||
self.sets
|
||||
.reserve(block.message().body().attestations().len());
|
||||
|
||||
block
|
||||
.message
|
||||
.body
|
||||
.attestations
|
||||
.message()
|
||||
.body()
|
||||
.attestations()
|
||||
.iter()
|
||||
.try_fold(
|
||||
Vec::with_capacity(block.message.body.attestations.len()),
|
||||
Vec::with_capacity(block.message().body().attestations().len()),
|
||||
|mut vec, attestation| {
|
||||
let committee = self
|
||||
.state
|
||||
@@ -290,12 +291,13 @@ where
|
||||
|
||||
/// Includes all signatures in `self.block.body.voluntary_exits` for verification.
|
||||
pub fn include_exits(&mut self, block: &'a SignedBeaconBlock<T>) -> Result<()> {
|
||||
self.sets.reserve(block.message.body.voluntary_exits.len());
|
||||
self.sets
|
||||
.reserve(block.message().body().voluntary_exits().len());
|
||||
|
||||
block
|
||||
.message
|
||||
.body
|
||||
.voluntary_exits
|
||||
.message()
|
||||
.body()
|
||||
.voluntary_exits()
|
||||
.iter()
|
||||
.try_for_each(|exit| {
|
||||
let exit =
|
||||
|
||||
@@ -11,6 +11,8 @@ use types::*;
|
||||
/// (e.g., when processing attestations instead of when processing deposits).
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum BlockProcessingError {
|
||||
/// Logic error indicating that the wrong state type was provided.
|
||||
IncorrectStateType,
|
||||
RandaoSignatureInvalid,
|
||||
BulkSignatureVerificationFailed,
|
||||
StateRootMismatch,
|
||||
@@ -45,11 +47,16 @@ pub enum BlockProcessingError {
|
||||
index: usize,
|
||||
reason: ExitInvalid,
|
||||
},
|
||||
SyncAggregateInvalid {
|
||||
reason: SyncAggregateInvalid,
|
||||
},
|
||||
BeaconStateError(BeaconStateError),
|
||||
SignatureSetError(SignatureSetError),
|
||||
SszTypesError(ssz_types::Error),
|
||||
MerkleTreeError(MerkleTreeError),
|
||||
ArithError(ArithError),
|
||||
InconsistentBlockFork(InconsistentFork),
|
||||
InconsistentStateFork(InconsistentFork),
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for BlockProcessingError {
|
||||
@@ -76,6 +83,12 @@ impl From<ArithError> for BlockProcessingError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SyncAggregateInvalid> for BlockProcessingError {
|
||||
fn from(reason: SyncAggregateInvalid) -> Self {
|
||||
BlockProcessingError::SyncAggregateInvalid { reason }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockOperationError<HeaderInvalid>> for BlockProcessingError {
|
||||
fn from(e: BlockOperationError<HeaderInvalid>) -> BlockProcessingError {
|
||||
match e {
|
||||
@@ -339,3 +352,11 @@ pub enum ExitInvalid {
|
||||
/// been invalid or an internal error occurred.
|
||||
SignatureSetError(SignatureSetError),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum SyncAggregateInvalid {
|
||||
/// One or more of the aggregate public keys is invalid.
|
||||
PubkeyInvalid,
|
||||
/// The signature is invalid.
|
||||
SignatureInvalid,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::errors::{BlockOperationError, IndexedAttestationInvalid as Invalid};
|
||||
use super::signature_sets::{get_pubkey_from_state, indexed_attestation_signature_set};
|
||||
use crate::VerifySignatures;
|
||||
use itertools::Itertools;
|
||||
use types::*;
|
||||
|
||||
type Result<T> = std::result::Result<T, BlockOperationError<Invalid>>;
|
||||
@@ -10,8 +11,6 @@ fn error(reason: Invalid) -> BlockOperationError<Invalid> {
|
||||
}
|
||||
|
||||
/// Verify an `IndexedAttestation`.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn is_valid_indexed_attestation<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
indexed_attestation: &IndexedAttestation<T>,
|
||||
@@ -25,13 +24,16 @@ pub fn is_valid_indexed_attestation<T: EthSpec>(
|
||||
|
||||
// Check that indices are sorted and unique
|
||||
let check_sorted = |list: &[u64]| -> Result<()> {
|
||||
list.windows(2).enumerate().try_for_each(|(i, pair)| {
|
||||
if pair[0] < pair[1] {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(error(Invalid::BadValidatorIndicesOrdering(i)))
|
||||
}
|
||||
})?;
|
||||
list.iter()
|
||||
.tuple_windows()
|
||||
.enumerate()
|
||||
.try_for_each(|(i, (x, y))| {
|
||||
if x < y {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(error(Invalid::BadValidatorIndicesOrdering(i)))
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
};
|
||||
check_sorted(indices)?;
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
use super::*;
|
||||
use crate::common::{
|
||||
altair::get_base_reward, get_attestation_participation_flag_indices, increase_balance,
|
||||
initiate_validator_exit, slash_validator,
|
||||
};
|
||||
use crate::per_block_processing::errors::{BlockProcessingError, IntoWithIndex};
|
||||
use crate::VerifySignatures;
|
||||
use safe_arith::SafeArith;
|
||||
use types::consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR};
|
||||
|
||||
pub fn process_operations<'a, T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
block_body: BeaconBlockBodyRef<'a, T>,
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
process_proposer_slashings(
|
||||
state,
|
||||
block_body.proposer_slashings(),
|
||||
verify_signatures,
|
||||
spec,
|
||||
)?;
|
||||
process_attester_slashings(
|
||||
state,
|
||||
block_body.attester_slashings(),
|
||||
verify_signatures,
|
||||
spec,
|
||||
)?;
|
||||
process_attestations(state, block_body, verify_signatures, spec)?;
|
||||
process_deposits(state, block_body.deposits(), spec)?;
|
||||
process_exits(state, block_body.voluntary_exits(), verify_signatures, spec)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub mod base {
|
||||
use super::*;
|
||||
|
||||
/// Validates each `Attestation` and updates the state, short-circuiting on an invalid object.
|
||||
///
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
pub fn process_attestations<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
attestations: &[Attestation<T>],
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
// Ensure the previous epoch cache exists.
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot(), spec)? as u64;
|
||||
|
||||
// Verify and apply each attestation.
|
||||
for (i, attestation) in attestations.iter().enumerate() {
|
||||
verify_attestation_for_block_inclusion(state, attestation, verify_signatures, spec)
|
||||
.map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
let pending_attestation = PendingAttestation {
|
||||
aggregation_bits: attestation.aggregation_bits.clone(),
|
||||
data: attestation.data.clone(),
|
||||
inclusion_delay: state.slot().safe_sub(attestation.data.slot)?.as_u64(),
|
||||
proposer_index,
|
||||
};
|
||||
|
||||
if attestation.data.target.epoch == state.current_epoch() {
|
||||
state
|
||||
.as_base_mut()?
|
||||
.current_epoch_attestations
|
||||
.push(pending_attestation)?;
|
||||
} else {
|
||||
state
|
||||
.as_base_mut()?
|
||||
.previous_epoch_attestations
|
||||
.push(pending_attestation)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod altair {
|
||||
use super::*;
|
||||
|
||||
pub fn process_attestations<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
attestations: &[Attestation<T>],
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
attestations
|
||||
.iter()
|
||||
.enumerate()
|
||||
.try_for_each(|(i, attestation)| {
|
||||
process_attestation(state, attestation, i, verify_signatures, spec)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process_attestation<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
attestation: &Attestation<T>,
|
||||
att_index: usize,
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||
|
||||
let indexed_attestation =
|
||||
verify_attestation_for_block_inclusion(state, attestation, verify_signatures, spec)
|
||||
.map_err(|e| e.into_with_index(att_index))?;
|
||||
|
||||
// Matching roots, participation flag indices
|
||||
let data = &attestation.data;
|
||||
let inclusion_delay = state.slot().safe_sub(data.slot)?.as_u64();
|
||||
let participation_flag_indices =
|
||||
get_attestation_participation_flag_indices(state, data, inclusion_delay, spec)?;
|
||||
|
||||
// Update epoch participation flags.
|
||||
let total_active_balance = state.get_total_active_balance(spec)?;
|
||||
let mut proposer_reward_numerator = 0;
|
||||
for index in &indexed_attestation.attesting_indices {
|
||||
let index = *index as usize;
|
||||
|
||||
for (flag_index, &weight) in PARTICIPATION_FLAG_WEIGHTS.iter().enumerate() {
|
||||
let epoch_participation = state.get_epoch_participation_mut(data.target.epoch)?;
|
||||
let validator_participation = epoch_participation
|
||||
.get_mut(index)
|
||||
.ok_or(BeaconStateError::ParticipationOutOfBounds(index))?;
|
||||
|
||||
if participation_flag_indices.contains(&flag_index)
|
||||
&& !validator_participation.has_flag(flag_index)?
|
||||
{
|
||||
validator_participation.add_flag(flag_index)?;
|
||||
proposer_reward_numerator.safe_add_assign(
|
||||
get_base_reward(state, index, total_active_balance, spec)?
|
||||
.safe_mul(weight)?,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let proposer_reward_denominator = WEIGHT_DENOMINATOR
|
||||
.safe_sub(PROPOSER_WEIGHT)?
|
||||
.safe_mul(WEIGHT_DENOMINATOR)?
|
||||
.safe_div(PROPOSER_WEIGHT)?;
|
||||
let proposer_reward = proposer_reward_numerator.safe_div(proposer_reward_denominator)?;
|
||||
// FIXME(altair): optimise by passing in proposer_index
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot(), spec)?;
|
||||
increase_balance(state, proposer_index, proposer_reward)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates each `ProposerSlashing` and updates the state, short-circuiting on an invalid object.
|
||||
///
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
pub fn process_proposer_slashings<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
proposer_slashings: &[ProposerSlashing],
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
// Verify and apply proposer slashings in series.
|
||||
// We have to verify in series because an invalid block may contain multiple slashings
|
||||
// for the same validator, and we need to correctly detect and reject that.
|
||||
proposer_slashings
|
||||
.iter()
|
||||
.enumerate()
|
||||
.try_for_each(|(i, proposer_slashing)| {
|
||||
verify_proposer_slashing(proposer_slashing, &state, verify_signatures, spec)
|
||||
.map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
slash_validator(
|
||||
state,
|
||||
proposer_slashing.signed_header_1.message.proposer_index as usize,
|
||||
None,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
/// Validates each `AttesterSlashing` and updates the state, short-circuiting on an invalid object.
|
||||
///
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
pub fn process_attester_slashings<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
attester_slashings: &[AttesterSlashing<T>],
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
for (i, attester_slashing) in attester_slashings.iter().enumerate() {
|
||||
verify_attester_slashing(&state, &attester_slashing, verify_signatures, spec)
|
||||
.map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
let slashable_indices =
|
||||
get_slashable_indices(&state, &attester_slashing).map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
for i in slashable_indices {
|
||||
slash_validator(state, i as usize, None, spec)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
/// Wrapper function to handle calling the correct version of `process_attestations` based on
|
||||
/// the fork.
|
||||
pub fn process_attestations<'a, T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
block_body: BeaconBlockBodyRef<'a, T>,
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
match block_body {
|
||||
BeaconBlockBodyRef::Base(_) => {
|
||||
base::process_attestations(state, block_body.attestations(), verify_signatures, spec)?;
|
||||
}
|
||||
BeaconBlockBodyRef::Altair(_) => {
|
||||
altair::process_attestations(
|
||||
state,
|
||||
block_body.attestations(),
|
||||
verify_signatures,
|
||||
spec,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates each `Exit` and updates the state, short-circuiting on an invalid object.
|
||||
///
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
pub fn process_exits<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
voluntary_exits: &[SignedVoluntaryExit],
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
// Verify and apply each exit in series. We iterate in series because higher-index exits may
|
||||
// become invalid due to the application of lower-index ones.
|
||||
for (i, exit) in voluntary_exits.iter().enumerate() {
|
||||
verify_exit(&state, exit, verify_signatures, spec).map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
initiate_validator_exit(state, exit.message.validator_index as usize, spec)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates each `Deposit` and updates the state, short-circuiting on an invalid object.
|
||||
///
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
pub fn process_deposits<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
deposits: &[Deposit],
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
let expected_deposit_len = std::cmp::min(
|
||||
T::MaxDeposits::to_u64(),
|
||||
state.get_outstanding_deposit_len()?,
|
||||
);
|
||||
block_verify!(
|
||||
deposits.len() as u64 == expected_deposit_len,
|
||||
BlockProcessingError::DepositCountInvalid {
|
||||
expected: expected_deposit_len as usize,
|
||||
found: deposits.len(),
|
||||
}
|
||||
);
|
||||
|
||||
// Verify merkle proofs in parallel.
|
||||
deposits
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.try_for_each(|(i, deposit)| {
|
||||
verify_deposit_merkle_proof(
|
||||
state,
|
||||
deposit,
|
||||
state.eth1_deposit_index().safe_add(i as u64)?,
|
||||
spec,
|
||||
)
|
||||
.map_err(|e| e.into_with_index(i))
|
||||
})?;
|
||||
|
||||
// Update the state in series.
|
||||
for deposit in deposits {
|
||||
process_deposit(state, deposit, spec, false)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process a single deposit, optionally verifying its merkle proof.
|
||||
pub fn process_deposit<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
deposit: &Deposit,
|
||||
spec: &ChainSpec,
|
||||
verify_merkle_proof: bool,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
let deposit_index = state.eth1_deposit_index() as usize;
|
||||
if verify_merkle_proof {
|
||||
verify_deposit_merkle_proof(state, deposit, state.eth1_deposit_index(), spec)
|
||||
.map_err(|e| e.into_with_index(deposit_index))?;
|
||||
}
|
||||
|
||||
state.eth1_deposit_index_mut().safe_add_assign(1)?;
|
||||
|
||||
// Get an `Option<u64>` where `u64` is the validator index if this deposit public key
|
||||
// already exists in the beacon_state.
|
||||
let validator_index = get_existing_validator_index(state, &deposit.data.pubkey)
|
||||
.map_err(|e| e.into_with_index(deposit_index))?;
|
||||
|
||||
let amount = deposit.data.amount;
|
||||
|
||||
if let Some(index) = validator_index {
|
||||
// Update the existing validator balance.
|
||||
increase_balance(state, index as usize, amount)?;
|
||||
} else {
|
||||
// The signature should be checked for new validators. Return early for a bad
|
||||
// signature.
|
||||
if verify_deposit_signature(&deposit.data, spec).is_err() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Create a new validator.
|
||||
let validator = Validator {
|
||||
pubkey: deposit.data.pubkey,
|
||||
withdrawal_credentials: deposit.data.withdrawal_credentials,
|
||||
activation_eligibility_epoch: spec.far_future_epoch,
|
||||
activation_epoch: spec.far_future_epoch,
|
||||
exit_epoch: spec.far_future_epoch,
|
||||
withdrawable_epoch: spec.far_future_epoch,
|
||||
effective_balance: std::cmp::min(
|
||||
amount.safe_sub(amount.safe_rem(spec.effective_balance_increment)?)?,
|
||||
spec.max_effective_balance,
|
||||
),
|
||||
slashed: false,
|
||||
};
|
||||
state.validators_mut().push(validator)?;
|
||||
state.balances_mut().push(deposit.data.amount)?;
|
||||
|
||||
// Altair-specific initializations.
|
||||
if let BeaconState::Altair(altair_state) = state {
|
||||
altair_state
|
||||
.previous_epoch_participation
|
||||
.push(ParticipationFlags::default())?;
|
||||
altair_state
|
||||
.current_epoch_participation
|
||||
.push(ParticipationFlags::default())?;
|
||||
altair_state.inactivity_scores.push(0)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -7,10 +7,10 @@ use ssz::DecodeError;
|
||||
use std::borrow::Cow;
|
||||
use tree_hash::TreeHash;
|
||||
use types::{
|
||||
AggregateSignature, AttesterSlashing, BeaconBlock, BeaconState, BeaconStateError, ChainSpec,
|
||||
DepositData, Domain, EthSpec, Fork, Hash256, IndexedAttestation, ProposerSlashing, PublicKey,
|
||||
Signature, SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockHeader, SignedRoot,
|
||||
SignedVoluntaryExit, SigningData,
|
||||
AggregateSignature, AttesterSlashing, BeaconBlockRef, BeaconState, BeaconStateError, ChainSpec,
|
||||
DepositData, Domain, EthSpec, Fork, Hash256, InconsistentFork, IndexedAttestation,
|
||||
ProposerSlashing, PublicKey, Signature, SignedAggregateAndProof, SignedBeaconBlock,
|
||||
SignedBeaconBlockHeader, SignedRoot, SignedVoluntaryExit, SigningData,
|
||||
};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -35,6 +35,8 @@ pub enum Error {
|
||||
/// The public key bytes stored in the `BeaconState` were not valid. This is a serious internal
|
||||
/// error.
|
||||
BadBlsBytes { validator_index: u64 },
|
||||
/// The block structure is not appropriate for the fork at `block.slot()`.
|
||||
InconsistentBlockFork(InconsistentFork),
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for Error {
|
||||
@@ -52,7 +54,7 @@ where
|
||||
T: EthSpec,
|
||||
{
|
||||
state
|
||||
.validators
|
||||
.validators()
|
||||
.get(validator_index)
|
||||
.and_then(|v| {
|
||||
let pk: Option<PublicKey> = v.pubkey.decompress().ok();
|
||||
@@ -73,21 +75,26 @@ where
|
||||
T: EthSpec,
|
||||
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
|
||||
{
|
||||
let block = &signed_block.message;
|
||||
let proposer_index = state.get_beacon_proposer_index(block.slot, spec)?;
|
||||
// Verify that the `SignedBeaconBlock` instantiation matches the fork at `signed_block.slot()`.
|
||||
signed_block
|
||||
.fork_name(spec)
|
||||
.map_err(Error::InconsistentBlockFork)?;
|
||||
|
||||
if proposer_index as u64 != block.proposer_index {
|
||||
let block = signed_block.message();
|
||||
let proposer_index = state.get_beacon_proposer_index(block.slot(), spec)?;
|
||||
|
||||
if proposer_index as u64 != block.proposer_index() {
|
||||
return Err(Error::IncorrectBlockProposer {
|
||||
block: block.proposer_index,
|
||||
block: block.proposer_index(),
|
||||
local_shuffling: proposer_index as u64,
|
||||
});
|
||||
}
|
||||
|
||||
let domain = spec.get_domain(
|
||||
block.slot.epoch(T::slots_per_epoch()),
|
||||
block.slot().epoch(T::slots_per_epoch()),
|
||||
Domain::BeaconProposer,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
&state.fork(),
|
||||
state.genesis_validators_root(),
|
||||
);
|
||||
|
||||
let message = if let Some(root) = block_root {
|
||||
@@ -101,7 +108,7 @@ where
|
||||
};
|
||||
|
||||
Ok(SignatureSet::single_pubkey(
|
||||
&signed_block.signature,
|
||||
signed_block.signature(),
|
||||
get_pubkey(proposer_index).ok_or_else(|| Error::ValidatorUnknown(proposer_index as u64))?,
|
||||
message,
|
||||
))
|
||||
@@ -111,26 +118,29 @@ where
|
||||
pub fn randao_signature_set<'a, T, F>(
|
||||
state: &'a BeaconState<T>,
|
||||
get_pubkey: F,
|
||||
block: &'a BeaconBlock<T>,
|
||||
block: BeaconBlockRef<'a, T>,
|
||||
spec: &'a ChainSpec,
|
||||
) -> Result<SignatureSet<'a>>
|
||||
where
|
||||
T: EthSpec,
|
||||
F: Fn(usize) -> Option<Cow<'a, PublicKey>>,
|
||||
{
|
||||
let proposer_index = state.get_beacon_proposer_index(block.slot, spec)?;
|
||||
let proposer_index = state.get_beacon_proposer_index(block.slot(), spec)?;
|
||||
|
||||
let domain = spec.get_domain(
|
||||
block.slot.epoch(T::slots_per_epoch()),
|
||||
block.slot().epoch(T::slots_per_epoch()),
|
||||
Domain::Randao,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
&state.fork(),
|
||||
state.genesis_validators_root(),
|
||||
);
|
||||
|
||||
let message = block.slot.epoch(T::slots_per_epoch()).signing_root(domain);
|
||||
let message = block
|
||||
.slot()
|
||||
.epoch(T::slots_per_epoch())
|
||||
.signing_root(domain);
|
||||
|
||||
Ok(SignatureSet::single_pubkey(
|
||||
&block.body.randao_reveal,
|
||||
block.body().randao_reveal(),
|
||||
get_pubkey(proposer_index).ok_or_else(|| Error::ValidatorUnknown(proposer_index as u64))?,
|
||||
message,
|
||||
))
|
||||
@@ -177,8 +187,8 @@ fn block_header_signature_set<'a, T: EthSpec>(
|
||||
let domain = spec.get_domain(
|
||||
signed_header.message.slot.epoch(T::slots_per_epoch()),
|
||||
Domain::BeaconProposer,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
&state.fork(),
|
||||
state.genesis_validators_root(),
|
||||
);
|
||||
|
||||
let message = signed_header.message.signing_root(domain);
|
||||
@@ -208,8 +218,8 @@ where
|
||||
let domain = spec.get_domain(
|
||||
indexed_attestation.data.target.epoch,
|
||||
Domain::BeaconAttester,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
&state.fork(),
|
||||
state.genesis_validators_root(),
|
||||
);
|
||||
|
||||
let message = indexed_attestation.data.signing_root(domain);
|
||||
@@ -309,8 +319,8 @@ where
|
||||
let domain = spec.get_domain(
|
||||
exit.epoch,
|
||||
Domain::VoluntaryExit,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
&state.fork(),
|
||||
state.genesis_validators_root(),
|
||||
);
|
||||
|
||||
let message = exit.signing_root(domain);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,28 +15,26 @@ fn error(reason: Invalid) -> BlockOperationError<Invalid> {
|
||||
/// to `state`. Otherwise, returns a descriptive `Err`.
|
||||
///
|
||||
/// Optionally verifies the aggregate signature, depending on `verify_signatures`.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn verify_attestation_for_block_inclusion<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
attestation: &Attestation<T>,
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<()> {
|
||||
) -> Result<IndexedAttestation<T>> {
|
||||
let data = &attestation.data;
|
||||
|
||||
verify!(
|
||||
data.slot.safe_add(spec.min_attestation_inclusion_delay)? <= state.slot,
|
||||
data.slot.safe_add(spec.min_attestation_inclusion_delay)? <= state.slot(),
|
||||
Invalid::IncludedTooEarly {
|
||||
state: state.slot,
|
||||
state: state.slot(),
|
||||
delay: spec.min_attestation_inclusion_delay,
|
||||
attestation: data.slot,
|
||||
}
|
||||
);
|
||||
verify!(
|
||||
state.slot <= data.slot.safe_add(T::slots_per_epoch())?,
|
||||
state.slot() <= data.slot.safe_add(T::slots_per_epoch())?,
|
||||
Invalid::IncludedTooLate {
|
||||
state: state.slot,
|
||||
state: state.slot(),
|
||||
attestation: data.slot,
|
||||
}
|
||||
);
|
||||
@@ -56,7 +54,7 @@ pub fn verify_attestation_for_state<T: EthSpec>(
|
||||
attestation: &Attestation<T>,
|
||||
verify_signatures: VerifySignatures,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<()> {
|
||||
) -> Result<IndexedAttestation<T>> {
|
||||
let data = &attestation.data;
|
||||
|
||||
verify!(
|
||||
@@ -72,7 +70,7 @@ pub fn verify_attestation_for_state<T: EthSpec>(
|
||||
let indexed_attestation = get_indexed_attestation(committee.committee, attestation)?;
|
||||
is_valid_indexed_attestation(state, &indexed_attestation, verify_signatures, spec)?;
|
||||
|
||||
Ok(())
|
||||
Ok(indexed_attestation)
|
||||
}
|
||||
|
||||
/// Check target epoch and source checkpoint.
|
||||
@@ -92,9 +90,9 @@ fn verify_casper_ffg_vote<T: EthSpec>(
|
||||
);
|
||||
if data.target.epoch == state.current_epoch() {
|
||||
verify!(
|
||||
data.source == state.current_justified_checkpoint,
|
||||
data.source == state.current_justified_checkpoint(),
|
||||
Invalid::WrongJustifiedCheckpoint {
|
||||
state: state.current_justified_checkpoint,
|
||||
state: state.current_justified_checkpoint(),
|
||||
attestation: data.source,
|
||||
is_current: true,
|
||||
}
|
||||
@@ -102,9 +100,9 @@ fn verify_casper_ffg_vote<T: EthSpec>(
|
||||
Ok(())
|
||||
} else if data.target.epoch == state.previous_epoch() {
|
||||
verify!(
|
||||
data.source == state.previous_justified_checkpoint,
|
||||
data.source == state.previous_justified_checkpoint(),
|
||||
Invalid::WrongJustifiedCheckpoint {
|
||||
state: state.previous_justified_checkpoint,
|
||||
state: state.previous_justified_checkpoint(),
|
||||
attestation: data.source,
|
||||
is_current: false,
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ where
|
||||
|
||||
for index in &attesting_indices_1 & &attesting_indices_2 {
|
||||
let validator = state
|
||||
.validators
|
||||
.validators()
|
||||
.get(index as usize)
|
||||
.ok_or_else(|| error(Invalid::UnknownValidator(index)))?;
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ pub fn verify_deposit_merkle_proof<T: EthSpec>(
|
||||
&deposit.proof[..],
|
||||
spec.deposit_contract_tree_depth.safe_add(1)? as usize,
|
||||
deposit_index as usize,
|
||||
state.eth1_data.deposit_root,
|
||||
state.eth1_data().deposit_root,
|
||||
),
|
||||
DepositInvalid::BadMerkleProof
|
||||
);
|
||||
|
||||
@@ -52,7 +52,7 @@ fn verify_exit_parametric<T: EthSpec>(
|
||||
let exit = &signed_exit.message;
|
||||
|
||||
let validator = state
|
||||
.validators
|
||||
.validators()
|
||||
.get(exit.validator_index as usize)
|
||||
.ok_or_else(|| error(ExitInvalid::ValidatorUnknown(exit.validator_index)))?;
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ pub fn verify_proposer_slashing<T: EthSpec>(
|
||||
|
||||
// Check proposer is slashable
|
||||
let proposer = state
|
||||
.validators
|
||||
.validators()
|
||||
.get(header_1.proposer_index as usize)
|
||||
.ok_or_else(|| error(Invalid::ProposerUnknown(header_1.proposer_index)))?;
|
||||
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
use errors::EpochProcessingError as Error;
|
||||
use safe_arith::SafeArith;
|
||||
use tree_hash::TreeHash;
|
||||
use types::*;
|
||||
#![deny(clippy::wildcard_imports)]
|
||||
|
||||
pub mod apply_rewards;
|
||||
// FIXME(altair): refactor to remove phase0/base structs, including `EpochProcessingSummary`
|
||||
pub use base::{TotalBalances, ValidatorStatus, ValidatorStatuses};
|
||||
use errors::EpochProcessingError as Error;
|
||||
pub use registry_updates::process_registry_updates;
|
||||
use safe_arith::SafeArith;
|
||||
pub use slashings::process_slashings;
|
||||
use types::{BeaconState, ChainSpec, EthSpec};
|
||||
pub use weigh_justification_and_finalization::weigh_justification_and_finalization;
|
||||
|
||||
pub mod altair;
|
||||
pub mod base;
|
||||
pub mod effective_balance_updates;
|
||||
pub mod errors;
|
||||
pub mod process_slashings;
|
||||
pub mod historical_roots_update;
|
||||
pub mod registry_updates;
|
||||
pub mod resets;
|
||||
pub mod slashings;
|
||||
pub mod tests;
|
||||
pub mod validator_statuses;
|
||||
|
||||
pub use apply_rewards::process_rewards_and_penalties;
|
||||
pub use process_slashings::process_slashings;
|
||||
pub use registry_updates::process_registry_updates;
|
||||
pub use validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses};
|
||||
pub mod weigh_justification_and_finalization;
|
||||
|
||||
/// Provides a summary of validator participation during the epoch.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct EpochProcessingSummary {
|
||||
pub total_balances: TotalBalances,
|
||||
pub statuses: Vec<ValidatorStatus>,
|
||||
@@ -25,195 +32,44 @@ pub struct EpochProcessingSummary {
|
||||
///
|
||||
/// Mutates the given `BeaconState`, returning early if an error is encountered. If an error is
|
||||
/// returned, a state might be "half-processed" and therefore in an invalid state.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn per_epoch_processing<T: EthSpec>(
|
||||
pub fn process_epoch<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<EpochProcessingSummary, Error> {
|
||||
// Ensure the committee caches are built.
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Next, spec)?;
|
||||
// Verify that the `BeaconState` instantiation matches the fork at `state.slot()`.
|
||||
state
|
||||
.fork_name(spec)
|
||||
.map_err(Error::InconsistentStateFork)?;
|
||||
|
||||
// Load the struct we use to assign validators into sets based on their participation.
|
||||
//
|
||||
// E.g., attestation in the previous epoch, attested to the head, etc.
|
||||
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
|
||||
validator_statuses.process_attestations(&state, spec)?;
|
||||
|
||||
// Justification and finalization.
|
||||
process_justification_and_finalization(state, &validator_statuses.total_balances)?;
|
||||
|
||||
// Rewards and Penalties.
|
||||
process_rewards_and_penalties(state, &mut validator_statuses, spec)?;
|
||||
|
||||
// Registry Updates.
|
||||
process_registry_updates(state, spec)?;
|
||||
|
||||
// Slashings.
|
||||
process_slashings(
|
||||
state,
|
||||
validator_statuses.total_balances.current_epoch(),
|
||||
spec,
|
||||
)?;
|
||||
|
||||
// Final updates.
|
||||
process_final_updates(state, spec)?;
|
||||
|
||||
// Rotate the epoch caches to suit the epoch transition.
|
||||
state.advance_caches();
|
||||
|
||||
Ok(EpochProcessingSummary {
|
||||
total_balances: validator_statuses.total_balances,
|
||||
statuses: validator_statuses.statuses,
|
||||
})
|
||||
match state {
|
||||
BeaconState::Base(_) => base::process_epoch(state, spec),
|
||||
BeaconState::Altair(_) => altair::process_epoch(state, spec),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the following fields on the `BeaconState`:
|
||||
///
|
||||
/// - `justification_bitfield`.
|
||||
/// - `previous_justified_epoch`
|
||||
/// - `previous_justified_root`
|
||||
/// - `current_justified_epoch`
|
||||
/// - `current_justified_root`
|
||||
/// - `finalized_epoch`
|
||||
/// - `finalized_root`
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
#[allow(clippy::if_same_then_else)] // For readability and consistency with spec.
|
||||
pub fn process_justification_and_finalization<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
total_balances: &TotalBalances,
|
||||
) -> Result<(), Error> {
|
||||
if state.current_epoch() <= T::genesis_epoch().safe_add(1)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let current_epoch = state.current_epoch();
|
||||
|
||||
let old_previous_justified_checkpoint = state.previous_justified_checkpoint;
|
||||
let old_current_justified_checkpoint = state.current_justified_checkpoint;
|
||||
|
||||
// Process justifications
|
||||
state.previous_justified_checkpoint = state.current_justified_checkpoint;
|
||||
state.justification_bits.shift_up(1)?;
|
||||
|
||||
if total_balances
|
||||
.previous_epoch_target_attesters()
|
||||
.safe_mul(3)?
|
||||
>= total_balances.current_epoch().safe_mul(2)?
|
||||
{
|
||||
state.current_justified_checkpoint = Checkpoint {
|
||||
epoch: previous_epoch,
|
||||
root: *state.get_block_root_at_epoch(previous_epoch)?,
|
||||
};
|
||||
state.justification_bits.set(1, true)?;
|
||||
}
|
||||
// If the current epoch gets justified, fill the last bit.
|
||||
if total_balances
|
||||
.current_epoch_target_attesters()
|
||||
.safe_mul(3)?
|
||||
>= total_balances.current_epoch().safe_mul(2)?
|
||||
{
|
||||
state.current_justified_checkpoint = Checkpoint {
|
||||
epoch: current_epoch,
|
||||
root: *state.get_block_root_at_epoch(current_epoch)?,
|
||||
};
|
||||
state.justification_bits.set(0, true)?;
|
||||
}
|
||||
|
||||
let bits = &state.justification_bits;
|
||||
|
||||
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
|
||||
if (1..4).all(|i| bits.get(i).unwrap_or(false))
|
||||
&& old_previous_justified_checkpoint.epoch.safe_add(3)? == current_epoch
|
||||
{
|
||||
state.finalized_checkpoint = old_previous_justified_checkpoint;
|
||||
}
|
||||
// The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
|
||||
else if (1..3).all(|i| bits.get(i).unwrap_or(false))
|
||||
&& old_previous_justified_checkpoint.epoch.safe_add(2)? == current_epoch
|
||||
{
|
||||
state.finalized_checkpoint = old_previous_justified_checkpoint;
|
||||
}
|
||||
// The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 3nd as source.
|
||||
if (0..3).all(|i| bits.get(i).unwrap_or(false))
|
||||
&& old_current_justified_checkpoint.epoch.safe_add(2)? == current_epoch
|
||||
{
|
||||
state.finalized_checkpoint = old_current_justified_checkpoint;
|
||||
}
|
||||
// The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
|
||||
else if (0..2).all(|i| bits.get(i).unwrap_or(false))
|
||||
&& old_current_justified_checkpoint.epoch.safe_add(1)? == current_epoch
|
||||
{
|
||||
state.finalized_checkpoint = old_current_justified_checkpoint;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
/// Used to track the changes to a validator's balance.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Delta {
|
||||
pub rewards: u64,
|
||||
pub penalties: u64,
|
||||
}
|
||||
|
||||
/// Finish up an epoch update.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn process_final_updates<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let current_epoch = state.current_epoch();
|
||||
let next_epoch = state.next_epoch()?;
|
||||
|
||||
// Reset eth1 data votes.
|
||||
if state
|
||||
.slot
|
||||
.safe_add(1)?
|
||||
.safe_rem(T::SlotsPerEth1VotingPeriod::to_u64())?
|
||||
== 0
|
||||
{
|
||||
state.eth1_data_votes = VariableList::empty();
|
||||
impl Delta {
|
||||
/// Reward the validator with the `reward`.
|
||||
pub fn reward(&mut self, reward: u64) -> Result<(), Error> {
|
||||
self.rewards = self.rewards.safe_add(reward)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Update effective balances with hysteresis (lag).
|
||||
let hysteresis_increment = spec
|
||||
.effective_balance_increment
|
||||
.safe_div(spec.hysteresis_quotient)?;
|
||||
let downward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_downward_multiplier)?;
|
||||
let upward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_upward_multiplier)?;
|
||||
for (index, validator) in state.validators.iter_mut().enumerate() {
|
||||
let balance = state.balances[index];
|
||||
|
||||
if balance.safe_add(downward_threshold)? < validator.effective_balance
|
||||
|| validator.effective_balance.safe_add(upward_threshold)? < balance
|
||||
{
|
||||
validator.effective_balance = std::cmp::min(
|
||||
balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?,
|
||||
spec.max_effective_balance,
|
||||
);
|
||||
}
|
||||
/// Penalize the validator with the `penalty`.
|
||||
pub fn penalize(&mut self, penalty: u64) -> Result<(), Error> {
|
||||
self.penalties = self.penalties.safe_add(penalty)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Reset slashings
|
||||
state.set_slashings(next_epoch, 0)?;
|
||||
|
||||
// Set randao mix
|
||||
state.set_randao_mix(next_epoch, *state.get_randao_mix(current_epoch)?)?;
|
||||
|
||||
// Set historical root accumulator
|
||||
if next_epoch
|
||||
.as_u64()
|
||||
.safe_rem(T::SlotsPerHistoricalRoot::to_u64().safe_div(T::slots_per_epoch())?)?
|
||||
== 0
|
||||
{
|
||||
let historical_batch = state.historical_batch();
|
||||
state
|
||||
.historical_roots
|
||||
.push(historical_batch.tree_hash_root())?;
|
||||
/// Combine two deltas.
|
||||
fn combine(&mut self, other: Delta) -> Result<(), Error> {
|
||||
self.reward(other.rewards)?;
|
||||
self.penalize(other.penalties)
|
||||
}
|
||||
|
||||
// Rotate current/previous epoch attestations
|
||||
state.previous_epoch_attestations =
|
||||
std::mem::replace(&mut state.current_epoch_attestations, VariableList::empty());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
use super::{process_registry_updates, process_slashings, EpochProcessingSummary, Error};
|
||||
use crate::per_epoch_processing::{
|
||||
effective_balance_updates::process_effective_balance_updates,
|
||||
historical_roots_update::process_historical_roots_update,
|
||||
resets::{process_eth1_data_reset, process_randao_mixes_reset, process_slashings_reset},
|
||||
validator_statuses::ValidatorStatuses,
|
||||
};
|
||||
pub use inactivity_updates::process_inactivity_updates;
|
||||
pub use justification_and_finalization::process_justification_and_finalization;
|
||||
pub use participation_flag_updates::process_participation_flag_updates;
|
||||
pub use rewards_and_penalties::process_rewards_and_penalties;
|
||||
pub use sync_committee_updates::process_sync_committee_updates;
|
||||
use types::{BeaconState, ChainSpec, EthSpec, RelativeEpoch};
|
||||
|
||||
pub mod inactivity_updates;
|
||||
pub mod justification_and_finalization;
|
||||
pub mod participation_flag_updates;
|
||||
pub mod rewards_and_penalties;
|
||||
pub mod sync_committee_updates;
|
||||
|
||||
pub fn process_epoch<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<EpochProcessingSummary, Error> {
|
||||
// Ensure the committee caches are built.
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Next, spec)?;
|
||||
|
||||
// Justification and finalization.
|
||||
process_justification_and_finalization(state, spec)?;
|
||||
|
||||
process_inactivity_updates(state, spec)?;
|
||||
|
||||
// Rewards and Penalties.
|
||||
process_rewards_and_penalties(state, spec)?;
|
||||
|
||||
// Registry Updates.
|
||||
process_registry_updates(state, spec)?;
|
||||
|
||||
// Slashings.
|
||||
process_slashings(
|
||||
state,
|
||||
state.get_total_active_balance(spec)?,
|
||||
spec.proportional_slashing_multiplier_altair,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
// Reset eth1 data votes.
|
||||
process_eth1_data_reset(state)?;
|
||||
|
||||
// Update effective balances with hysteresis (lag).
|
||||
process_effective_balance_updates(state, spec)?;
|
||||
|
||||
// Reset slashings
|
||||
process_slashings_reset(state)?;
|
||||
|
||||
// Set randao mix
|
||||
process_randao_mixes_reset(state)?;
|
||||
|
||||
// Set historical root accumulator
|
||||
process_historical_roots_update(state)?;
|
||||
|
||||
// Rotate current/previous epoch participation
|
||||
process_participation_flag_updates(state)?;
|
||||
|
||||
process_sync_committee_updates(state, spec)?;
|
||||
|
||||
// Rotate the epoch caches to suit the epoch transition.
|
||||
state.advance_caches()?;
|
||||
|
||||
// FIXME(altair): this is an incorrect dummy value, we should think harder
|
||||
// about how we want to unify validator statuses between phase0 & altair.
|
||||
// We should benchmark the new state transition and work out whether Altair could
|
||||
// be accelerated by some similar cache.
|
||||
let validator_statuses = ValidatorStatuses::new(state, spec)?;
|
||||
|
||||
Ok(EpochProcessingSummary {
|
||||
total_balances: validator_statuses.total_balances,
|
||||
statuses: validator_statuses.statuses,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use crate::EpochProcessingError;
|
||||
use core::result::Result;
|
||||
use core::result::Result::Ok;
|
||||
use safe_arith::SafeArith;
|
||||
use std::cmp::min;
|
||||
use types::beacon_state::BeaconState;
|
||||
use types::chain_spec::ChainSpec;
|
||||
use types::consts::altair::TIMELY_TARGET_FLAG_INDEX;
|
||||
use types::eth_spec::EthSpec;
|
||||
|
||||
pub fn process_inactivity_updates<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
// Score updates based on previous epoch participation, skip genesis epoch
|
||||
if state.current_epoch() == T::genesis_epoch() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let unslashed_indices = state.get_unslashed_participating_indices(
|
||||
TIMELY_TARGET_FLAG_INDEX,
|
||||
state.previous_epoch(),
|
||||
spec,
|
||||
)?;
|
||||
|
||||
for index in state.get_eligible_validator_indices()? {
|
||||
// Increase inactivity score of inactive validators
|
||||
if unslashed_indices.contains(&index) {
|
||||
let inactivity_score = state.get_inactivity_score_mut(index)?;
|
||||
inactivity_score.safe_sub_assign(min(1, *inactivity_score))?;
|
||||
} else {
|
||||
state
|
||||
.get_inactivity_score_mut(index)?
|
||||
.safe_add_assign(spec.inactivity_score_bias)?;
|
||||
}
|
||||
// Decrease the score of all validators for forgiveness when not during a leak
|
||||
if !state.is_in_inactivity_leak(spec) {
|
||||
let inactivity_score = state.get_inactivity_score_mut(index)?;
|
||||
inactivity_score
|
||||
.safe_sub_assign(min(spec.inactivity_score_recovery_rate, *inactivity_score))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use crate::per_epoch_processing::weigh_justification_and_finalization;
|
||||
use crate::per_epoch_processing::Error;
|
||||
use safe_arith::SafeArith;
|
||||
use types::consts::altair::TIMELY_TARGET_FLAG_INDEX;
|
||||
use types::{BeaconState, ChainSpec, EthSpec};
|
||||
|
||||
/// Update the justified and finalized checkpoints for matching target attestations.
|
||||
pub fn process_justification_and_finalization<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if state.current_epoch() <= T::genesis_epoch().safe_add(1)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let current_epoch = state.current_epoch();
|
||||
let previous_indices = state.get_unslashed_participating_indices(
|
||||
TIMELY_TARGET_FLAG_INDEX,
|
||||
previous_epoch,
|
||||
spec,
|
||||
)?;
|
||||
let current_indices =
|
||||
state.get_unslashed_participating_indices(TIMELY_TARGET_FLAG_INDEX, current_epoch, spec)?;
|
||||
let total_active_balance = state.get_total_balance(
|
||||
state
|
||||
.get_active_validator_indices(current_epoch, spec)?
|
||||
.as_slice(),
|
||||
spec,
|
||||
)?;
|
||||
let previous_target_balance = state.get_total_balance(&previous_indices, spec)?;
|
||||
let current_target_balance = state.get_total_balance(¤t_indices, spec)?;
|
||||
weigh_justification_and_finalization(
|
||||
state,
|
||||
total_active_balance,
|
||||
previous_target_balance,
|
||||
current_target_balance,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::EpochProcessingError;
|
||||
use core::result::Result;
|
||||
use core::result::Result::Ok;
|
||||
use types::beacon_state::BeaconState;
|
||||
use types::eth_spec::EthSpec;
|
||||
use types::participation_flags::ParticipationFlags;
|
||||
use types::VariableList;
|
||||
|
||||
pub fn process_participation_flag_updates<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
*state.previous_epoch_participation_mut()? =
|
||||
std::mem::take(state.current_epoch_participation_mut()?);
|
||||
*state.current_epoch_participation_mut()? = VariableList::new(vec![
|
||||
ParticipationFlags::default(
|
||||
);
|
||||
state.validators().len()
|
||||
])?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
use safe_arith::SafeArith;
|
||||
use types::consts::altair::{
|
||||
PARTICIPATION_FLAG_WEIGHTS, TIMELY_HEAD_FLAG_INDEX, TIMELY_TARGET_FLAG_INDEX,
|
||||
WEIGHT_DENOMINATOR,
|
||||
};
|
||||
use types::{BeaconState, ChainSpec, EthSpec};
|
||||
|
||||
use crate::common::{altair::get_base_reward, decrease_balance, increase_balance};
|
||||
use crate::per_epoch_processing::{Delta, Error};
|
||||
|
||||
/// Apply attester and proposer rewards.
|
||||
///
|
||||
/// Spec v1.1.0
|
||||
pub fn process_rewards_and_penalties<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if state.current_epoch() == T::genesis_epoch() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut deltas = vec![Delta::default(); state.validators().len()];
|
||||
|
||||
let total_active_balance = state.get_total_active_balance(spec)?;
|
||||
|
||||
for flag_index in 0..PARTICIPATION_FLAG_WEIGHTS.len() {
|
||||
get_flag_index_deltas(&mut deltas, state, flag_index, total_active_balance, spec)?;
|
||||
}
|
||||
|
||||
get_inactivity_penalty_deltas(&mut deltas, state, spec)?;
|
||||
|
||||
// Apply the deltas, erroring on overflow above but not on overflow below (saturating at 0
|
||||
// instead).
|
||||
for (i, delta) in deltas.into_iter().enumerate() {
|
||||
increase_balance(state, i, delta.rewards)?;
|
||||
decrease_balance(state, i, delta.penalties)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the deltas for a given flag index by scanning through the participation flags.
|
||||
///
|
||||
/// Spec v1.1.0
|
||||
pub fn get_flag_index_deltas<T: EthSpec>(
|
||||
deltas: &mut Vec<Delta>,
|
||||
state: &BeaconState<T>,
|
||||
flag_index: usize,
|
||||
total_active_balance: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let unslashed_participating_indices =
|
||||
state.get_unslashed_participating_indices(flag_index, previous_epoch, spec)?;
|
||||
let weight = get_flag_weight(flag_index)?;
|
||||
let unslashed_participating_balance =
|
||||
state.get_total_balance(&unslashed_participating_indices, spec)?;
|
||||
let unslashed_participating_increments =
|
||||
unslashed_participating_balance.safe_div(spec.effective_balance_increment)?;
|
||||
let active_increments = total_active_balance.safe_div(spec.effective_balance_increment)?;
|
||||
|
||||
for index in state.get_eligible_validator_indices()? {
|
||||
let base_reward = get_base_reward(state, index, total_active_balance, spec)?;
|
||||
let mut delta = Delta::default();
|
||||
|
||||
if unslashed_participating_indices.contains(&(index as usize)) {
|
||||
if !state.is_in_inactivity_leak(spec) {
|
||||
let reward_numerator = base_reward
|
||||
.safe_mul(weight)?
|
||||
.safe_mul(unslashed_participating_increments)?;
|
||||
delta.reward(
|
||||
reward_numerator.safe_div(active_increments.safe_mul(WEIGHT_DENOMINATOR)?)?,
|
||||
)?;
|
||||
}
|
||||
} else if flag_index != TIMELY_HEAD_FLAG_INDEX {
|
||||
delta.penalize(base_reward.safe_mul(weight)?.safe_div(WEIGHT_DENOMINATOR)?)?;
|
||||
}
|
||||
deltas
|
||||
.get_mut(index as usize)
|
||||
.ok_or(Error::DeltaOutOfBounds(index as usize))?
|
||||
.combine(delta)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the weight for a `flag_index` from the constant list of all weights.
|
||||
pub fn get_flag_weight(flag_index: usize) -> Result<u64, Error> {
|
||||
PARTICIPATION_FLAG_WEIGHTS
|
||||
.get(flag_index)
|
||||
.copied()
|
||||
.ok_or(Error::InvalidFlagIndex(flag_index))
|
||||
}
|
||||
|
||||
pub fn get_inactivity_penalty_deltas<T: EthSpec>(
|
||||
deltas: &mut Vec<Delta>,
|
||||
state: &BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let matching_target_indices = state.get_unslashed_participating_indices(
|
||||
TIMELY_TARGET_FLAG_INDEX,
|
||||
previous_epoch,
|
||||
spec,
|
||||
)?;
|
||||
for index in state.get_eligible_validator_indices()? {
|
||||
let mut delta = Delta::default();
|
||||
|
||||
if !matching_target_indices.contains(&index) {
|
||||
let penalty_numerator = state
|
||||
.get_validator(index)?
|
||||
.effective_balance
|
||||
.safe_mul(state.get_inactivity_score(index)?)?;
|
||||
let penalty_denominator = spec
|
||||
.inactivity_score_bias
|
||||
.safe_mul(spec.inactivity_penalty_quotient_altair)?;
|
||||
delta.penalize(penalty_numerator.safe_div(penalty_denominator)?)?;
|
||||
}
|
||||
deltas
|
||||
.get_mut(index)
|
||||
.ok_or(Error::DeltaOutOfBounds(index))?
|
||||
.combine(delta)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
use crate::EpochProcessingError;
|
||||
use safe_arith::SafeArith;
|
||||
use types::beacon_state::BeaconState;
|
||||
use types::chain_spec::ChainSpec;
|
||||
use types::eth_spec::EthSpec;
|
||||
|
||||
pub fn process_sync_committee_updates<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
let next_epoch = state.next_epoch()?;
|
||||
if next_epoch.safe_rem(spec.epochs_per_sync_committee_period)? == 0 {
|
||||
*state.current_sync_committee_mut()? = state.next_sync_committee()?.clone();
|
||||
|
||||
*state.next_sync_committee_mut()? = state.get_next_sync_committee(spec)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
76
consensus/state_processing/src/per_epoch_processing/base.rs
Normal file
76
consensus/state_processing/src/per_epoch_processing/base.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use super::{process_registry_updates, process_slashings, EpochProcessingSummary, Error};
|
||||
pub use crate::per_epoch_processing::validator_statuses::{
|
||||
TotalBalances, ValidatorStatus, ValidatorStatuses,
|
||||
};
|
||||
use crate::per_epoch_processing::{
|
||||
effective_balance_updates::process_effective_balance_updates,
|
||||
historical_roots_update::process_historical_roots_update,
|
||||
resets::{process_eth1_data_reset, process_randao_mixes_reset, process_slashings_reset},
|
||||
};
|
||||
pub use justification_and_finalization::process_justification_and_finalization;
|
||||
pub use participation_record_updates::process_participation_record_updates;
|
||||
pub use rewards_and_penalties::process_rewards_and_penalties;
|
||||
use types::{BeaconState, ChainSpec, EthSpec, RelativeEpoch};
|
||||
|
||||
pub mod justification_and_finalization;
|
||||
pub mod participation_record_updates;
|
||||
pub mod rewards_and_penalties;
|
||||
|
||||
pub fn process_epoch<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<EpochProcessingSummary, Error> {
|
||||
// Ensure the committee caches are built.
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||
state.build_committee_cache(RelativeEpoch::Next, spec)?;
|
||||
|
||||
// Load the struct we use to assign validators into sets based on their participation.
|
||||
//
|
||||
// E.g., attestation in the previous epoch, attested to the head, etc.
|
||||
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
|
||||
validator_statuses.process_attestations(&state)?;
|
||||
|
||||
// Justification and finalization.
|
||||
process_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
|
||||
|
||||
// Rewards and Penalties.
|
||||
process_rewards_and_penalties(state, &mut validator_statuses, spec)?;
|
||||
|
||||
// Registry Updates.
|
||||
process_registry_updates(state, spec)?;
|
||||
|
||||
// Slashings.
|
||||
process_slashings(
|
||||
state,
|
||||
validator_statuses.total_balances.current_epoch(),
|
||||
spec.proportional_slashing_multiplier,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
// Reset eth1 data votes.
|
||||
process_eth1_data_reset(state)?;
|
||||
|
||||
// Update effective balances with hysteresis (lag).
|
||||
process_effective_balance_updates(state, spec)?;
|
||||
|
||||
// Reset slashings
|
||||
process_slashings_reset(state)?;
|
||||
|
||||
// Set randao mix
|
||||
process_randao_mixes_reset(state)?;
|
||||
|
||||
// Set historical root accumulator
|
||||
process_historical_roots_update(state)?;
|
||||
|
||||
// Rotate current/previous epoch attestations
|
||||
process_participation_record_updates(state)?;
|
||||
|
||||
// Rotate the epoch caches to suit the epoch transition.
|
||||
state.advance_caches()?;
|
||||
|
||||
Ok(EpochProcessingSummary {
|
||||
total_balances: validator_statuses.total_balances,
|
||||
statuses: validator_statuses.statuses,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use crate::per_epoch_processing::base::TotalBalances;
|
||||
use crate::per_epoch_processing::weigh_justification_and_finalization;
|
||||
use crate::per_epoch_processing::Error;
|
||||
use safe_arith::SafeArith;
|
||||
use types::{BeaconState, ChainSpec, EthSpec};
|
||||
|
||||
/// Update the justified and finalized checkpoints for matching target attestations.
|
||||
pub fn process_justification_and_finalization<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
total_balances: &TotalBalances,
|
||||
_spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if state.current_epoch() <= T::genesis_epoch().safe_add(1)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
weigh_justification_and_finalization(
|
||||
state,
|
||||
total_balances.current_epoch(),
|
||||
total_balances.previous_epoch_target_attesters(),
|
||||
total_balances.current_epoch_target_attesters(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use crate::EpochProcessingError;
|
||||
use types::beacon_state::BeaconState;
|
||||
use types::eth_spec::EthSpec;
|
||||
|
||||
pub fn process_participation_record_updates<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
let base_state = state.as_base_mut()?;
|
||||
base_state.previous_epoch_attestations =
|
||||
std::mem::take(&mut base_state.current_epoch_attestations);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,40 +1,49 @@
|
||||
use super::super::common::get_base_reward;
|
||||
use super::validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses};
|
||||
use super::Error;
|
||||
use crate::common::{base::get_base_reward, decrease_balance, increase_balance};
|
||||
use crate::per_epoch_processing::validator_statuses::{
|
||||
TotalBalances, ValidatorStatus, ValidatorStatuses,
|
||||
};
|
||||
use crate::per_epoch_processing::{Delta, Error};
|
||||
use safe_arith::SafeArith;
|
||||
use std::array::IntoIter as ArrayIter;
|
||||
use types::{BeaconState, ChainSpec, EthSpec};
|
||||
|
||||
use types::*;
|
||||
|
||||
/// Use to track the changes to a validators balance.
|
||||
/// Combination of several deltas for different components of an attestation reward.
|
||||
///
|
||||
/// Exists only for compatibility with EF rewards tests.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Delta {
|
||||
rewards: u64,
|
||||
penalties: u64,
|
||||
pub struct AttestationDelta {
|
||||
pub source_delta: Delta,
|
||||
pub target_delta: Delta,
|
||||
pub head_delta: Delta,
|
||||
pub inclusion_delay_delta: Delta,
|
||||
pub inactivity_penalty_delta: Delta,
|
||||
}
|
||||
|
||||
impl Delta {
|
||||
/// Reward the validator with the `reward`.
|
||||
pub fn reward(&mut self, reward: u64) -> Result<(), Error> {
|
||||
self.rewards = self.rewards.safe_add(reward)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Penalize the validator with the `penalty`.
|
||||
pub fn penalize(&mut self, penalty: u64) -> Result<(), Error> {
|
||||
self.penalties = self.penalties.safe_add(penalty)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Combine two deltas.
|
||||
fn combine(&mut self, other: Delta) -> Result<(), Error> {
|
||||
self.reward(other.rewards)?;
|
||||
self.penalize(other.penalties)
|
||||
impl AttestationDelta {
|
||||
/// Flatten into a single delta.
|
||||
pub fn flatten(self) -> Result<Delta, Error> {
|
||||
let AttestationDelta {
|
||||
source_delta,
|
||||
target_delta,
|
||||
head_delta,
|
||||
inclusion_delay_delta,
|
||||
inactivity_penalty_delta,
|
||||
} = self;
|
||||
let mut result = Delta::default();
|
||||
for delta in ArrayIter::new([
|
||||
source_delta,
|
||||
target_delta,
|
||||
head_delta,
|
||||
inclusion_delay_delta,
|
||||
inactivity_penalty_delta,
|
||||
]) {
|
||||
result.combine(delta)?;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply attester and proposer rewards.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn process_rewards_and_penalties<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
validator_statuses: &mut ValidatorStatuses,
|
||||
@@ -45,8 +54,8 @@ pub fn process_rewards_and_penalties<T: EthSpec>(
|
||||
}
|
||||
|
||||
// Guard against an out-of-bounds during the validator balance update.
|
||||
if validator_statuses.statuses.len() != state.balances.len()
|
||||
|| validator_statuses.statuses.len() != state.validators.len()
|
||||
if validator_statuses.statuses.len() != state.balances().len()
|
||||
|| validator_statuses.statuses.len() != state.validators().len()
|
||||
{
|
||||
return Err(Error::ValidatorStatusesInconsistent);
|
||||
}
|
||||
@@ -55,28 +64,27 @@ pub fn process_rewards_and_penalties<T: EthSpec>(
|
||||
|
||||
// Apply the deltas, erroring on overflow above but not on overflow below (saturating at 0
|
||||
// instead).
|
||||
for (i, delta) in deltas.iter().enumerate() {
|
||||
state.balances[i] = state.balances[i].safe_add(delta.rewards)?;
|
||||
state.balances[i] = state.balances[i].saturating_sub(delta.penalties);
|
||||
for (i, delta) in deltas.into_iter().enumerate() {
|
||||
let combined_delta = delta.flatten()?;
|
||||
increase_balance(state, i, combined_delta.rewards)?;
|
||||
decrease_balance(state, i, combined_delta.penalties)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply rewards for participation in attestations during the previous epoch.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
fn get_attestation_deltas<T: EthSpec>(
|
||||
pub fn get_attestation_deltas<T: EthSpec>(
|
||||
state: &BeaconState<T>,
|
||||
validator_statuses: &ValidatorStatuses,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Vec<Delta>, Error> {
|
||||
) -> Result<Vec<AttestationDelta>, Error> {
|
||||
let finality_delay = state
|
||||
.previous_epoch()
|
||||
.safe_sub(state.finalized_checkpoint.epoch)?
|
||||
.safe_sub(state.finalized_checkpoint().epoch)?
|
||||
.as_u64();
|
||||
|
||||
let mut deltas = vec![Delta::default(); state.validators.len()];
|
||||
let mut deltas = vec![AttestationDelta::default(); state.validators().len()];
|
||||
|
||||
let total_balances = &validator_statuses.total_balances;
|
||||
|
||||
@@ -102,18 +110,23 @@ fn get_attestation_deltas<T: EthSpec>(
|
||||
let inactivity_penalty_delta =
|
||||
get_inactivity_penalty_delta(validator, base_reward, finality_delay, spec)?;
|
||||
|
||||
deltas[index].combine(source_delta)?;
|
||||
deltas[index].combine(target_delta)?;
|
||||
deltas[index].combine(head_delta)?;
|
||||
deltas[index].combine(inclusion_delay_delta)?;
|
||||
deltas[index].combine(inactivity_penalty_delta)?;
|
||||
let delta = deltas
|
||||
.get_mut(index)
|
||||
.ok_or(Error::DeltaOutOfBounds(index))?;
|
||||
delta.source_delta.combine(source_delta)?;
|
||||
delta.target_delta.combine(target_delta)?;
|
||||
delta.head_delta.combine(head_delta)?;
|
||||
delta.inclusion_delay_delta.combine(inclusion_delay_delta)?;
|
||||
delta
|
||||
.inactivity_penalty_delta
|
||||
.combine(inactivity_penalty_delta)?;
|
||||
|
||||
if let Some((proposer_index, proposer_delta)) = proposer_delta {
|
||||
if proposer_index >= deltas.len() {
|
||||
return Err(Error::ValidatorStatusesInconsistent);
|
||||
}
|
||||
|
||||
deltas[proposer_index].combine(proposer_delta)?;
|
||||
deltas
|
||||
.get_mut(proposer_index)
|
||||
.ok_or(Error::ValidatorStatusesInconsistent)?
|
||||
.inclusion_delay_delta
|
||||
.combine(proposer_delta)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +232,6 @@ fn get_inclusion_delay_delta(
|
||||
|
||||
let proposer_reward = get_proposer_reward(base_reward, spec)?;
|
||||
proposer_delta.reward(proposer_reward)?;
|
||||
|
||||
let max_attester_reward = base_reward.safe_sub(proposer_reward)?;
|
||||
delta.reward(max_attester_reward.safe_div(inclusion_info.delay)?)?;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
use super::errors::EpochProcessingError;
|
||||
use safe_arith::SafeArith;
|
||||
use types::beacon_state::BeaconState;
|
||||
use types::chain_spec::ChainSpec;
|
||||
use types::{BeaconStateError, EthSpec};
|
||||
|
||||
pub fn process_effective_balance_updates<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
let hysteresis_increment = spec
|
||||
.effective_balance_increment
|
||||
.safe_div(spec.hysteresis_quotient)?;
|
||||
let downward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_downward_multiplier)?;
|
||||
let upward_threshold = hysteresis_increment.safe_mul(spec.hysteresis_upward_multiplier)?;
|
||||
let (validators, balances) = state.validators_and_balances_mut();
|
||||
for (index, validator) in validators.iter_mut().enumerate() {
|
||||
let balance = balances
|
||||
.get(index)
|
||||
.copied()
|
||||
.ok_or(BeaconStateError::BalancesOutOfBounds(index))?;
|
||||
|
||||
if balance.safe_add(downward_threshold)? < validator.effective_balance
|
||||
|| validator.effective_balance.safe_add(upward_threshold)? < balance
|
||||
{
|
||||
validator.effective_balance = std::cmp::min(
|
||||
balance.safe_sub(balance.safe_rem(spec.effective_balance_increment)?)?,
|
||||
spec.max_effective_balance,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use types::*;
|
||||
use types::{BeaconStateError, InconsistentFork};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum EpochProcessingError {
|
||||
@@ -10,6 +10,7 @@ pub enum EpochProcessingError {
|
||||
InclusionDistanceZero,
|
||||
ValidatorStatusesInconsistent,
|
||||
DeltasInconsistent,
|
||||
DeltaOutOfBounds(usize),
|
||||
/// Unable to get the inclusion distance for a validator that should have an inclusion
|
||||
/// distance. This indicates an internal inconsistency.
|
||||
///
|
||||
@@ -19,6 +20,9 @@ pub enum EpochProcessingError {
|
||||
InclusionError(InclusionError),
|
||||
SszTypesError(ssz_types::Error),
|
||||
ArithError(safe_arith::ArithError),
|
||||
InconsistentStateFork(InconsistentFork),
|
||||
InvalidJustificationBit(ssz_types::Error),
|
||||
InvalidFlagIndex(usize),
|
||||
}
|
||||
|
||||
impl From<InclusionError> for EpochProcessingError {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
use super::errors::EpochProcessingError;
|
||||
use core::result::Result;
|
||||
use core::result::Result::Ok;
|
||||
use safe_arith::SafeArith;
|
||||
use tree_hash::TreeHash;
|
||||
use types::beacon_state::BeaconState;
|
||||
use types::eth_spec::EthSpec;
|
||||
use types::Unsigned;
|
||||
|
||||
pub fn process_historical_roots_update<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
let next_epoch = state.next_epoch()?;
|
||||
if next_epoch
|
||||
.as_u64()
|
||||
.safe_rem(T::SlotsPerHistoricalRoot::to_u64().safe_div(T::slots_per_epoch())?)?
|
||||
== 0
|
||||
{
|
||||
let historical_batch = state.historical_batch();
|
||||
state
|
||||
.historical_roots_mut()
|
||||
.push(historical_batch.tree_hash_root())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
use crate::{common::initiate_validator_exit, per_epoch_processing::Error};
|
||||
use itertools::Itertools;
|
||||
use safe_arith::SafeArith;
|
||||
use types::*;
|
||||
use types::{BeaconState, ChainSpec, EthSpec, Validator};
|
||||
|
||||
/// Performs a validator registry update, if required.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
/// NOTE: unchanged in Altair
|
||||
pub fn process_registry_updates<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
@@ -20,7 +20,7 @@ pub fn process_registry_updates<T: EthSpec>(
|
||||
&& validator.effective_balance <= spec.ejection_balance
|
||||
};
|
||||
let indices_to_update: Vec<_> = state
|
||||
.validators
|
||||
.validators()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, validator)| {
|
||||
@@ -30,17 +30,18 @@ pub fn process_registry_updates<T: EthSpec>(
|
||||
.collect();
|
||||
|
||||
for index in indices_to_update {
|
||||
if state.validators[index].is_eligible_for_activation_queue(spec) {
|
||||
state.validators[index].activation_eligibility_epoch = current_epoch.safe_add(1)?;
|
||||
let validator = state.get_validator_mut(index)?;
|
||||
if validator.is_eligible_for_activation_queue(spec) {
|
||||
validator.activation_eligibility_epoch = current_epoch.safe_add(1)?;
|
||||
}
|
||||
if is_ejectable(&state.validators[index]) {
|
||||
if is_ejectable(validator) {
|
||||
initiate_validator_exit(state, index, spec)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Queue validators eligible for activation and not dequeued for activation prior to finalized epoch
|
||||
let activation_queue = state
|
||||
.validators
|
||||
.validators()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, validator)| validator.is_eligible_for_activation(state, spec))
|
||||
@@ -52,8 +53,7 @@ pub fn process_registry_updates<T: EthSpec>(
|
||||
let churn_limit = state.get_churn_limit(spec)? as usize;
|
||||
let delayed_activation_epoch = state.compute_activation_exit_epoch(current_epoch, spec)?;
|
||||
for index in activation_queue.into_iter().take(churn_limit) {
|
||||
let validator = &mut state.validators[index];
|
||||
validator.activation_epoch = delayed_activation_epoch;
|
||||
state.get_validator_mut(index)?.activation_epoch = delayed_activation_epoch;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
use super::errors::EpochProcessingError;
|
||||
use core::result::Result;
|
||||
use core::result::Result::Ok;
|
||||
use safe_arith::SafeArith;
|
||||
use types::beacon_state::BeaconState;
|
||||
use types::eth_spec::EthSpec;
|
||||
use types::{Unsigned, VariableList};
|
||||
|
||||
pub fn process_eth1_data_reset<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
if state
|
||||
.slot()
|
||||
.safe_add(1)?
|
||||
.safe_rem(T::SlotsPerEth1VotingPeriod::to_u64())?
|
||||
== 0
|
||||
{
|
||||
*state.eth1_data_votes_mut() = VariableList::empty();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_slashings_reset<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
let next_epoch = state.next_epoch()?;
|
||||
state.set_slashings(next_epoch, 0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_randao_mixes_reset<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
) -> Result<(), EpochProcessingError> {
|
||||
let current_epoch = state.current_epoch();
|
||||
let next_epoch = state.next_epoch()?;
|
||||
state.set_randao_mix(next_epoch, *state.get_randao_mix(current_epoch)?)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
use crate::per_epoch_processing::Error;
|
||||
use safe_arith::{SafeArith, SafeArithIter};
|
||||
use types::{BeaconStateError as Error, *};
|
||||
use types::{BeaconState, BeaconStateError, ChainSpec, EthSpec, Unsigned};
|
||||
|
||||
/// Process slashings.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn process_slashings<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
total_balance: u64,
|
||||
slashing_multiplier: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let epoch = state.current_epoch();
|
||||
let sum_slashings = state.get_all_slashings().iter().copied().safe_sum()?;
|
||||
let adjusted_total_slashing_balance = std::cmp::min(
|
||||
sum_slashings.safe_mul(spec.proportional_slashing_multiplier)?,
|
||||
total_balance,
|
||||
);
|
||||
|
||||
for (index, validator) in state.validators.iter().enumerate() {
|
||||
let adjusted_total_slashing_balance =
|
||||
std::cmp::min(sum_slashings.safe_mul(slashing_multiplier)?, total_balance);
|
||||
|
||||
let (validators, balances) = state.validators_and_balances_mut();
|
||||
for (index, validator) in validators.iter().enumerate() {
|
||||
if validator.slashed
|
||||
&& epoch.safe_add(T::EpochsPerSlashingsVector::to_u64().safe_div(2)?)?
|
||||
== validator.withdrawable_epoch
|
||||
@@ -31,7 +31,10 @@ pub fn process_slashings<T: EthSpec>(
|
||||
.safe_mul(increment)?;
|
||||
|
||||
// Equivalent to `decrease_balance(state, index, penalty)`, but avoids borrowing `state`.
|
||||
state.balances[index] = state.balances[index].saturating_sub(penalty);
|
||||
let balance = balances
|
||||
.get_mut(index)
|
||||
.ok_or(BeaconStateError::BalancesOutOfBounds(index))?;
|
||||
*balance = balance.saturating_sub(penalty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,165 @@
|
||||
#![cfg(test)]
|
||||
use crate::per_epoch_processing::per_epoch_processing;
|
||||
use crate::per_epoch_processing::process_epoch;
|
||||
use beacon_chain::store::StoreConfig;
|
||||
use beacon_chain::test_utils::BeaconChainHarness;
|
||||
use beacon_chain::types::{EthSpec, MinimalEthSpec};
|
||||
use bls::Hash256;
|
||||
use env_logger::{Builder, Env};
|
||||
use types::test_utils::TestingBeaconStateBuilder;
|
||||
use types::*;
|
||||
use types::Slot;
|
||||
|
||||
#[test]
|
||||
fn runs_without_error() {
|
||||
Builder::from_env(Env::default().default_filter_or("error")).init();
|
||||
|
||||
let harness = BeaconChainHarness::new_with_store_config(
|
||||
MinimalEthSpec,
|
||||
None,
|
||||
types::test_utils::generate_deterministic_keypairs(8),
|
||||
StoreConfig::default(),
|
||||
);
|
||||
harness.advance_slot();
|
||||
|
||||
let spec = MinimalEthSpec::default_spec();
|
||||
|
||||
let mut builder: TestingBeaconStateBuilder<MinimalEthSpec> =
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(8, &spec);
|
||||
|
||||
let target_slot =
|
||||
(MinimalEthSpec::genesis_epoch() + 4).end_slot(MinimalEthSpec::slots_per_epoch());
|
||||
builder.teleport_to_slot(target_slot);
|
||||
|
||||
let (mut state, _keypairs) = builder.build();
|
||||
let state = harness.get_current_state();
|
||||
harness.add_attested_blocks_at_slots(
|
||||
state,
|
||||
Hash256::zero(),
|
||||
(1..target_slot.as_u64())
|
||||
.map(Slot::new)
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
(0..8).collect::<Vec<_>>().as_slice(),
|
||||
);
|
||||
let mut new_head_state = harness.get_current_state();
|
||||
|
||||
per_epoch_processing(&mut state, &spec).unwrap();
|
||||
process_epoch(&mut new_head_state, &spec).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
mod release_tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
per_slot_processing::per_slot_processing, EpochProcessingError, SlotProcessingError,
|
||||
};
|
||||
use beacon_chain::test_utils::{AttestationStrategy, BlockStrategy};
|
||||
use types::{Epoch, ForkName, InconsistentFork, MainnetEthSpec};
|
||||
|
||||
#[test]
|
||||
fn altair_state_on_base_fork() {
|
||||
let mut spec = MainnetEthSpec::default_spec();
|
||||
let slots_per_epoch = MainnetEthSpec::slots_per_epoch();
|
||||
// The Altair fork happens at epoch 1.
|
||||
spec.altair_fork_epoch = Some(Epoch::new(1));
|
||||
|
||||
let altair_state = {
|
||||
let harness = BeaconChainHarness::new(
|
||||
MainnetEthSpec,
|
||||
Some(spec.clone()),
|
||||
types::test_utils::generate_deterministic_keypairs(8),
|
||||
);
|
||||
|
||||
harness.advance_slot();
|
||||
|
||||
harness.extend_chain(
|
||||
// Build out enough blocks so we get an Altair block at the very end of an epoch.
|
||||
(slots_per_epoch * 2 - 1) as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
);
|
||||
|
||||
harness.get_current_state()
|
||||
};
|
||||
|
||||
// Pre-conditions for a valid test.
|
||||
assert_eq!(altair_state.fork_name(&spec).unwrap(), ForkName::Altair);
|
||||
assert_eq!(
|
||||
altair_state.slot(),
|
||||
altair_state.current_epoch().end_slot(slots_per_epoch)
|
||||
);
|
||||
|
||||
// Check the state is valid before starting this test.
|
||||
process_epoch(&mut altair_state.clone(), &spec)
|
||||
.expect("state passes intial epoch processing");
|
||||
per_slot_processing(&mut altair_state.clone(), None, &spec)
|
||||
.expect("state passes intial slot processing");
|
||||
|
||||
// Modify the spec so altair never happens.
|
||||
spec.altair_fork_epoch = None;
|
||||
|
||||
let expected_err = InconsistentFork {
|
||||
fork_at_slot: ForkName::Base,
|
||||
object_fork: ForkName::Altair,
|
||||
};
|
||||
|
||||
assert_eq!(altair_state.fork_name(&spec), Err(expected_err));
|
||||
assert_eq!(
|
||||
process_epoch(&mut altair_state.clone(), &spec),
|
||||
Err(EpochProcessingError::InconsistentStateFork(expected_err))
|
||||
);
|
||||
assert_eq!(
|
||||
per_slot_processing(&mut altair_state.clone(), None, &spec),
|
||||
Err(SlotProcessingError::InconsistentStateFork(expected_err))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base_state_on_altair_fork() {
|
||||
let mut spec = MainnetEthSpec::default_spec();
|
||||
let slots_per_epoch = MainnetEthSpec::slots_per_epoch();
|
||||
// The Altair fork never happens.
|
||||
spec.altair_fork_epoch = None;
|
||||
|
||||
let base_state = {
|
||||
let harness = BeaconChainHarness::new(
|
||||
MainnetEthSpec,
|
||||
Some(spec.clone()),
|
||||
types::test_utils::generate_deterministic_keypairs(8),
|
||||
);
|
||||
|
||||
harness.advance_slot();
|
||||
|
||||
harness.extend_chain(
|
||||
// Build out enough blocks so we get a block at the very end of an epoch.
|
||||
(slots_per_epoch * 2 - 1) as usize,
|
||||
BlockStrategy::OnCanonicalHead,
|
||||
AttestationStrategy::AllValidators,
|
||||
);
|
||||
|
||||
harness.get_current_state()
|
||||
};
|
||||
|
||||
// Pre-conditions for a valid test.
|
||||
assert_eq!(base_state.fork_name(&spec).unwrap(), ForkName::Base);
|
||||
assert_eq!(
|
||||
base_state.slot(),
|
||||
base_state.current_epoch().end_slot(slots_per_epoch)
|
||||
);
|
||||
|
||||
// Check the state is valid before starting this test.
|
||||
process_epoch(&mut base_state.clone(), &spec)
|
||||
.expect("state passes intial epoch processing");
|
||||
per_slot_processing(&mut base_state.clone(), None, &spec)
|
||||
.expect("state passes intial slot processing");
|
||||
|
||||
// Modify the spec so Altair happens at the first epoch.
|
||||
spec.altair_fork_epoch = Some(Epoch::new(1));
|
||||
|
||||
let expected_err = InconsistentFork {
|
||||
fork_at_slot: ForkName::Altair,
|
||||
object_fork: ForkName::Base,
|
||||
};
|
||||
|
||||
assert_eq!(base_state.fork_name(&spec), Err(expected_err));
|
||||
assert_eq!(
|
||||
process_epoch(&mut base_state.clone(), &spec),
|
||||
Err(EpochProcessingError::InconsistentStateFork(expected_err))
|
||||
);
|
||||
assert_eq!(
|
||||
per_slot_processing(&mut base_state.clone(), None, &spec),
|
||||
Err(SlotProcessingError::InconsistentStateFork(expected_err))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::common::get_attesting_indices;
|
||||
use safe_arith::SafeArith;
|
||||
use types::*;
|
||||
use types::{BeaconState, BeaconStateError, ChainSpec, Epoch, EthSpec, PendingAttestation};
|
||||
|
||||
#[cfg(feature = "arbitrary-fuzz")]
|
||||
use arbitrary::Arbitrary;
|
||||
@@ -17,7 +17,7 @@ macro_rules! set_self_if_other_is_true {
|
||||
|
||||
/// The information required to reward a block producer for including an attestation in a block.
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct InclusionInfo {
|
||||
/// The distance between the attestation slot and the slot that attestation was included in a
|
||||
/// block.
|
||||
@@ -49,7 +49,7 @@ impl InclusionInfo {
|
||||
|
||||
/// Information required to reward some validator during the current and previous epoch.
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct ValidatorStatus {
|
||||
/// True if the validator has been slashed, ever.
|
||||
pub is_slashed: bool,
|
||||
@@ -114,7 +114,7 @@ impl ValidatorStatus {
|
||||
/// The total effective balances for different sets of validators during the previous and current
|
||||
/// epochs.
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))]
|
||||
pub struct TotalBalances {
|
||||
/// The effective balance increment from the spec.
|
||||
@@ -192,11 +192,11 @@ impl ValidatorStatuses {
|
||||
state: &BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Self, BeaconStateError> {
|
||||
let mut statuses = Vec::with_capacity(state.validators.len());
|
||||
let mut statuses = Vec::with_capacity(state.validators().len());
|
||||
let mut total_balances = TotalBalances::new(spec);
|
||||
|
||||
for (i, validator) in state.validators.iter().enumerate() {
|
||||
let effective_balance = state.get_effective_balance(i, spec)?;
|
||||
for (i, validator) in state.validators().iter().enumerate() {
|
||||
let effective_balance = state.get_effective_balance(i)?;
|
||||
let mut status = ValidatorStatus {
|
||||
is_slashed: validator.slashed,
|
||||
is_withdrawable_in_current_epoch: validator
|
||||
@@ -235,12 +235,12 @@ impl ValidatorStatuses {
|
||||
pub fn process_attestations<T: EthSpec>(
|
||||
&mut self,
|
||||
state: &BeaconState<T>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
for a in state
|
||||
let base_state = state.as_base()?;
|
||||
for a in base_state
|
||||
.previous_epoch_attestations
|
||||
.iter()
|
||||
.chain(state.current_epoch_attestations.iter())
|
||||
.chain(base_state.current_epoch_attestations.iter())
|
||||
{
|
||||
let committee = state.get_beacon_committee(a.data.slot, a.data.index)?;
|
||||
let attesting_indices =
|
||||
@@ -277,7 +277,10 @@ impl ValidatorStatuses {
|
||||
|
||||
// Loop through the participating validator indices and update the status vec.
|
||||
for validator_index in attesting_indices {
|
||||
self.statuses[validator_index].update(&status);
|
||||
self.statuses
|
||||
.get_mut(validator_index)
|
||||
.ok_or(BeaconStateError::UnknownValidator(validator_index))?
|
||||
.update(&status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +288,7 @@ impl ValidatorStatuses {
|
||||
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)?;
|
||||
let validator_balance = state.get_effective_balance(index)?;
|
||||
|
||||
if v.is_current_epoch_attester {
|
||||
self.total_balances
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
use crate::per_epoch_processing::Error;
|
||||
use safe_arith::SafeArith;
|
||||
use std::ops::Range;
|
||||
use types::{BeaconState, Checkpoint, EthSpec};
|
||||
|
||||
/// Update the justified and finalized checkpoints for matching target attestations.
|
||||
#[allow(clippy::if_same_then_else)] // For readability and consistency with spec.
|
||||
pub fn weigh_justification_and_finalization<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
total_active_balance: u64,
|
||||
previous_target_balance: u64,
|
||||
current_target_balance: u64,
|
||||
) -> Result<(), Error> {
|
||||
let previous_epoch = state.previous_epoch();
|
||||
let current_epoch = state.current_epoch();
|
||||
|
||||
let old_previous_justified_checkpoint = state.previous_justified_checkpoint();
|
||||
let old_current_justified_checkpoint = state.current_justified_checkpoint();
|
||||
|
||||
// Process justifications
|
||||
*state.previous_justified_checkpoint_mut() = state.current_justified_checkpoint();
|
||||
state.justification_bits_mut().shift_up(1)?;
|
||||
|
||||
if previous_target_balance.safe_mul(3)? >= total_active_balance.safe_mul(2)? {
|
||||
*state.current_justified_checkpoint_mut() = Checkpoint {
|
||||
epoch: previous_epoch,
|
||||
root: *state.get_block_root_at_epoch(previous_epoch)?,
|
||||
};
|
||||
state.justification_bits_mut().set(1, true)?;
|
||||
}
|
||||
// If the current epoch gets justified, fill the last bit.
|
||||
if current_target_balance.safe_mul(3)? >= total_active_balance.safe_mul(2)? {
|
||||
*state.current_justified_checkpoint_mut() = Checkpoint {
|
||||
epoch: current_epoch,
|
||||
root: *state.get_block_root_at_epoch(current_epoch)?,
|
||||
};
|
||||
state.justification_bits_mut().set(0, true)?;
|
||||
}
|
||||
|
||||
let bits = state.justification_bits().clone();
|
||||
let all_bits_set = |range: Range<usize>| -> Result<bool, Error> {
|
||||
for i in range {
|
||||
if !bits.get(i).map_err(Error::InvalidJustificationBit)? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
};
|
||||
|
||||
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
|
||||
if all_bits_set(1..4)? && old_previous_justified_checkpoint.epoch.safe_add(3)? == current_epoch
|
||||
{
|
||||
*state.finalized_checkpoint_mut() = old_previous_justified_checkpoint;
|
||||
}
|
||||
// The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
|
||||
if all_bits_set(1..3)? && old_previous_justified_checkpoint.epoch.safe_add(2)? == current_epoch
|
||||
{
|
||||
*state.finalized_checkpoint_mut() = old_previous_justified_checkpoint;
|
||||
}
|
||||
// The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 3nd as source.
|
||||
if all_bits_set(0..3)? && old_current_justified_checkpoint.epoch.safe_add(2)? == current_epoch {
|
||||
*state.finalized_checkpoint_mut() = old_current_justified_checkpoint;
|
||||
}
|
||||
// The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
|
||||
if all_bits_set(0..2)? && old_current_justified_checkpoint.epoch.safe_add(1)? == current_epoch {
|
||||
*state.finalized_checkpoint_mut() = old_current_justified_checkpoint;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::upgrade::upgrade_to_altair;
|
||||
use crate::{per_epoch_processing::EpochProcessingSummary, *};
|
||||
use safe_arith::{ArithError, SafeArith};
|
||||
use types::*;
|
||||
@@ -7,6 +8,7 @@ pub enum Error {
|
||||
BeaconStateError(BeaconStateError),
|
||||
EpochProcessingError(EpochProcessingError),
|
||||
ArithError(ArithError),
|
||||
InconsistentStateFork(InconsistentFork),
|
||||
}
|
||||
|
||||
impl From<ArithError> for Error {
|
||||
@@ -20,24 +22,34 @@ impl From<ArithError> for Error {
|
||||
/// If the root of the supplied `state` is known, then it can be passed as `state_root`. If
|
||||
/// `state_root` is `None`, the root of `state` will be computed using a cached tree hash.
|
||||
/// Providing the `state_root` makes this function several orders of magniude faster.
|
||||
///
|
||||
/// Spec v0.12.1
|
||||
pub fn per_slot_processing<T: EthSpec>(
|
||||
state: &mut BeaconState<T>,
|
||||
state_root: Option<Hash256>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Option<EpochProcessingSummary>, Error> {
|
||||
// Verify that the `BeaconState` instantiation matches the fork at `state.slot()`.
|
||||
state
|
||||
.fork_name(spec)
|
||||
.map_err(Error::InconsistentStateFork)?;
|
||||
|
||||
cache_state(state, state_root)?;
|
||||
|
||||
let summary = if state.slot > spec.genesis_slot
|
||||
&& state.slot.safe_add(1)?.safe_rem(T::slots_per_epoch())? == 0
|
||||
let summary = if state.slot() > spec.genesis_slot
|
||||
&& state.slot().safe_add(1)?.safe_rem(T::slots_per_epoch())? == 0
|
||||
{
|
||||
Some(per_epoch_processing(state, spec)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
state.slot.safe_add_assign(1)?;
|
||||
state.slot_mut().safe_add_assign(1)?;
|
||||
|
||||
// If the Altair fork epoch is reached, perform an irregular state upgrade.
|
||||
if state.slot().safe_rem(T::slots_per_epoch())? == 0
|
||||
&& spec.altair_fork_epoch == Some(state.current_epoch())
|
||||
{
|
||||
upgrade_to_altair(state, spec)?;
|
||||
}
|
||||
|
||||
Ok(summary)
|
||||
}
|
||||
@@ -56,23 +68,23 @@ fn cache_state<T: EthSpec>(
|
||||
// getter/setter functions.
|
||||
//
|
||||
// This is a bit hacky, however it gets the job done safely without lots of code.
|
||||
let previous_slot = state.slot;
|
||||
state.slot.safe_add_assign(1)?;
|
||||
let previous_slot = state.slot();
|
||||
state.slot_mut().safe_add_assign(1)?;
|
||||
|
||||
// Store the previous slot's post state transition root.
|
||||
state.set_state_root(previous_slot, previous_state_root)?;
|
||||
|
||||
// Cache latest block header state root
|
||||
if state.latest_block_header.state_root == Hash256::zero() {
|
||||
state.latest_block_header.state_root = previous_state_root;
|
||||
if state.latest_block_header().state_root == Hash256::zero() {
|
||||
state.latest_block_header_mut().state_root = previous_state_root;
|
||||
}
|
||||
|
||||
// Cache block root
|
||||
let latest_block_root = state.latest_block_header.canonical_root();
|
||||
let latest_block_root = state.latest_block_header().canonical_root();
|
||||
state.set_block_root(previous_slot, latest_block_root)?;
|
||||
|
||||
// Set the state slot back to what it should be.
|
||||
state.slot.safe_sub_assign(1)?;
|
||||
state.slot_mut().safe_sub_assign(1)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ pub fn complete_state_advance<T: EthSpec>(
|
||||
target_slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
check_target_slot(state.slot, target_slot)?;
|
||||
check_target_slot(state.slot(), target_slot)?;
|
||||
|
||||
while state.slot < target_slot {
|
||||
while state.slot() < target_slot {
|
||||
// Use the initial state root on the first iteration of the loop, then use `None` for any
|
||||
// future iterations.
|
||||
let state_root_opt = state_root_opt.take();
|
||||
@@ -64,7 +64,7 @@ pub fn partial_state_advance<T: EthSpec>(
|
||||
target_slot: Slot,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
check_target_slot(state.slot, target_slot)?;
|
||||
check_target_slot(state.slot(), target_slot)?;
|
||||
|
||||
// The only time that a state root is mandatory is if a block has been applied to the state
|
||||
// without it yet being advanced another slot.
|
||||
@@ -72,13 +72,13 @@ pub fn partial_state_advance<T: EthSpec>(
|
||||
// Failing to provide a state root in this scenario would result in corrupting the
|
||||
// `state.block_roots` array, since the `state.latest_block_header` would contain an invalid
|
||||
// (all-zeros) state root.
|
||||
let mut initial_state_root = Some(if state.slot > state.latest_block_header.slot {
|
||||
let mut initial_state_root = Some(if state.slot() > state.latest_block_header().slot {
|
||||
state_root_opt.unwrap_or_else(Hash256::zero)
|
||||
} else {
|
||||
state_root_opt.ok_or(Error::StateRootNotProvided)?
|
||||
});
|
||||
|
||||
while state.slot < target_slot {
|
||||
while state.slot() < target_slot {
|
||||
// Use the initial state root on the first iteration of the loop, then use `[0; 32]` for any
|
||||
// later iterations.
|
||||
//
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
use log::info;
|
||||
use types::test_utils::{
|
||||
AttestationTestTask, AttesterSlashingTestTask, DepositTestTask, ProposerSlashingTestTask,
|
||||
TestingBeaconBlockBuilder, TestingBeaconStateBuilder,
|
||||
};
|
||||
use types::{EthSpec, *};
|
||||
|
||||
pub use crate::per_block_processing::block_processing_builder::BlockProcessingBuilder;
|
||||
|
||||
pub struct BlockBuilder<T: EthSpec> {
|
||||
pub state_builder: TestingBeaconStateBuilder<T>,
|
||||
pub block_builder: TestingBeaconBlockBuilder<T>,
|
||||
|
||||
pub num_validators: usize,
|
||||
pub num_proposer_slashings: usize,
|
||||
pub num_attester_slashings: usize,
|
||||
pub num_attestations: usize,
|
||||
pub num_deposits: usize,
|
||||
pub num_exits: usize,
|
||||
}
|
||||
|
||||
impl<T: EthSpec> BlockBuilder<T> {
|
||||
pub fn new(num_validators: usize, spec: &ChainSpec) -> Self {
|
||||
let state_builder =
|
||||
TestingBeaconStateBuilder::from_deterministic_keypairs(num_validators, &spec);
|
||||
let block_builder = TestingBeaconBlockBuilder::new(spec);
|
||||
|
||||
Self {
|
||||
state_builder,
|
||||
block_builder,
|
||||
num_validators: 0,
|
||||
num_proposer_slashings: 0,
|
||||
num_attester_slashings: 0,
|
||||
num_attestations: 0,
|
||||
num_deposits: 0,
|
||||
num_exits: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maximize_block_operations(&mut self) {
|
||||
self.num_proposer_slashings = T::MaxProposerSlashings::to_usize();
|
||||
self.num_attester_slashings = T::MaxAttesterSlashings::to_usize();
|
||||
self.num_attestations = T::MaxAttestations::to_usize();
|
||||
self.num_deposits = T::MaxDeposits::to_usize();
|
||||
self.num_exits = T::MaxVoluntaryExits::to_usize();
|
||||
}
|
||||
|
||||
pub fn set_slot(&mut self, slot: Slot) {
|
||||
self.state_builder.teleport_to_slot(slot);
|
||||
}
|
||||
|
||||
pub fn build_caches(&mut self, spec: &ChainSpec) {
|
||||
// Builds all caches; benches will not contain shuffling/committee building times.
|
||||
self.state_builder.build_caches(&spec).unwrap();
|
||||
}
|
||||
|
||||
pub fn build(mut self, spec: &ChainSpec) -> (SignedBeaconBlock<T>, BeaconState<T>) {
|
||||
let (mut state, keypairs) = self.state_builder.build();
|
||||
let builder = &mut self.block_builder;
|
||||
|
||||
builder.set_slot(state.slot);
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap();
|
||||
|
||||
let proposer_keypair = &keypairs[proposer_index];
|
||||
|
||||
builder.set_proposer_index(proposer_index as u64);
|
||||
|
||||
builder.set_randao_reveal(
|
||||
&proposer_keypair.sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
|
||||
let parent_root = state.latest_block_header.canonical_root();
|
||||
builder.set_parent_root(parent_root);
|
||||
|
||||
// Used as a stream of validator indices for use in slashings, exits, etc.
|
||||
let mut validators_iter = 0..keypairs.len() as u64;
|
||||
|
||||
// Insert `ProposerSlashing` objects.
|
||||
for _ in 0..self.num_proposer_slashings {
|
||||
let validator_index = validators_iter.next().expect("Insufficient validators.");
|
||||
|
||||
builder.insert_proposer_slashing(
|
||||
ProposerSlashingTestTask::Valid,
|
||||
validator_index,
|
||||
&keypairs[validator_index as usize].sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
}
|
||||
info!(
|
||||
"Inserted {} proposer slashings.",
|
||||
builder.block.body.proposer_slashings.len()
|
||||
);
|
||||
|
||||
// Insert `AttesterSlashing` objects
|
||||
for _ in 0..self.num_attester_slashings {
|
||||
let mut attesters: Vec<u64> = vec![];
|
||||
let mut secret_keys: Vec<&SecretKey> = vec![];
|
||||
|
||||
const NUM_SLASHED_INDICES: usize = 12;
|
||||
|
||||
for _ in 0..NUM_SLASHED_INDICES {
|
||||
let validator_index = validators_iter.next().expect("Insufficient validators.");
|
||||
|
||||
attesters.push(validator_index);
|
||||
secret_keys.push(&keypairs[validator_index as usize].sk);
|
||||
}
|
||||
|
||||
builder.insert_attester_slashing(
|
||||
AttesterSlashingTestTask::Valid,
|
||||
&attesters,
|
||||
&secret_keys,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
}
|
||||
info!(
|
||||
"Inserted {} attester slashings.",
|
||||
builder.block.body.attester_slashings.len()
|
||||
);
|
||||
|
||||
// Insert `Attestation` objects.
|
||||
let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect();
|
||||
builder
|
||||
.insert_attestations(
|
||||
AttestationTestTask::Valid,
|
||||
&state,
|
||||
&all_secret_keys,
|
||||
self.num_attestations as usize,
|
||||
spec,
|
||||
)
|
||||
.unwrap();
|
||||
info!(
|
||||
"Inserted {} attestations.",
|
||||
builder.block.body.attestations.len()
|
||||
);
|
||||
|
||||
// Insert `Deposit` objects.
|
||||
builder.insert_deposits(
|
||||
32_000_000_000,
|
||||
DepositTestTask::NoReset,
|
||||
state.eth1_data.deposit_count,
|
||||
self.num_deposits as u64,
|
||||
&mut state,
|
||||
spec,
|
||||
);
|
||||
info!("Inserted {} deposits.", builder.block.body.deposits.len());
|
||||
|
||||
// Insert the maximum possible number of `Exit` objects.
|
||||
for _ in 0..self.num_exits {
|
||||
let validator_index = validators_iter.next().expect("Insufficient validators.");
|
||||
|
||||
builder.insert_exit(
|
||||
validator_index,
|
||||
state.current_epoch(),
|
||||
&keypairs[validator_index as usize].sk,
|
||||
&state,
|
||||
spec,
|
||||
);
|
||||
}
|
||||
info!(
|
||||
"Inserted {} exits.",
|
||||
builder.block.body.voluntary_exits.len()
|
||||
);
|
||||
|
||||
// Set the eth1 data to be different from the state.
|
||||
self.block_builder.block.body.eth1_data.block_hash = Hash256::from_slice(&[42; 32]);
|
||||
|
||||
let block = self.block_builder.build(
|
||||
&proposer_keypair.sk,
|
||||
&state.fork,
|
||||
state.genesis_validators_root,
|
||||
spec,
|
||||
);
|
||||
|
||||
(block, state)
|
||||
}
|
||||
}
|
||||
3
consensus/state_processing/src/upgrade.rs
Normal file
3
consensus/state_processing/src/upgrade.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod altair;
|
||||
|
||||
pub use altair::upgrade_to_altair;
|
||||
119
consensus/state_processing/src/upgrade/altair.rs
Normal file
119
consensus/state_processing/src/upgrade/altair.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use crate::common::{get_attestation_participation_flag_indices, get_attesting_indices};
|
||||
use std::mem;
|
||||
use types::{
|
||||
BeaconState, BeaconStateAltair, BeaconStateError as Error, ChainSpec, EthSpec, Fork,
|
||||
ParticipationFlags, PendingAttestation, RelativeEpoch, SyncCommittee, VariableList,
|
||||
};
|
||||
|
||||
/// Translate the participation information from the epoch prior to the fork into Altair's format.
|
||||
pub fn translate_participation<E: EthSpec>(
|
||||
state: &mut BeaconState<E>,
|
||||
pending_attestations: &VariableList<PendingAttestation<E>, E::MaxPendingAttestations>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
// Previous epoch committee cache is required for `get_attesting_indices`.
|
||||
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||
|
||||
for attestation in pending_attestations {
|
||||
let data = &attestation.data;
|
||||
let inclusion_delay = attestation.inclusion_delay;
|
||||
|
||||
// Translate attestation inclusion info to flag indices.
|
||||
let participation_flag_indices =
|
||||
get_attestation_participation_flag_indices(state, data, inclusion_delay, spec)?;
|
||||
|
||||
// Apply flags to all attesting validators.
|
||||
let committee = state.get_beacon_committee(data.slot, data.index)?;
|
||||
let attesting_indices =
|
||||
get_attesting_indices::<E>(&committee.committee, &attestation.aggregation_bits)?;
|
||||
let epoch_participation = state.previous_epoch_participation_mut()?;
|
||||
|
||||
for index in attesting_indices {
|
||||
for flag_index in &participation_flag_indices {
|
||||
epoch_participation
|
||||
.get_mut(index)
|
||||
.ok_or(Error::UnknownValidator(index))?
|
||||
.add_flag(*flag_index)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transform a `Base` state into an `Altair` state.
|
||||
pub fn upgrade_to_altair<E: EthSpec>(
|
||||
pre_state: &mut BeaconState<E>,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let epoch = pre_state.current_epoch();
|
||||
let pre = pre_state.as_base_mut()?;
|
||||
|
||||
let default_epoch_participation =
|
||||
VariableList::new(vec![ParticipationFlags::default(); pre.validators.len()])?;
|
||||
let inactivity_scores = VariableList::new(vec![0; pre.validators.len()])?;
|
||||
|
||||
// Where possible, use something like `mem::take` to move fields from behind the &mut
|
||||
// reference. For other fields that don't have a good default value, use `clone`.
|
||||
//
|
||||
// Fixed size vectors get cloned because replacing them would require the same size
|
||||
// allocation as cloning.
|
||||
let mut post = BeaconState::Altair(BeaconStateAltair {
|
||||
// Versioning
|
||||
genesis_time: pre.genesis_time,
|
||||
genesis_validators_root: pre.genesis_validators_root,
|
||||
slot: pre.slot,
|
||||
fork: Fork {
|
||||
previous_version: pre.fork.current_version,
|
||||
current_version: spec.altair_fork_version,
|
||||
epoch,
|
||||
},
|
||||
// History
|
||||
latest_block_header: pre.latest_block_header.clone(),
|
||||
block_roots: pre.block_roots.clone(),
|
||||
state_roots: pre.state_roots.clone(),
|
||||
historical_roots: mem::take(&mut pre.historical_roots),
|
||||
// Eth1
|
||||
eth1_data: pre.eth1_data.clone(),
|
||||
eth1_data_votes: mem::take(&mut pre.eth1_data_votes),
|
||||
eth1_deposit_index: pre.eth1_deposit_index,
|
||||
// Registry
|
||||
validators: mem::take(&mut pre.validators),
|
||||
balances: mem::take(&mut pre.balances),
|
||||
// Randomness
|
||||
randao_mixes: pre.randao_mixes.clone(),
|
||||
// Slashings
|
||||
slashings: pre.slashings.clone(),
|
||||
// `Participation
|
||||
previous_epoch_participation: default_epoch_participation.clone(),
|
||||
current_epoch_participation: default_epoch_participation,
|
||||
// Finality
|
||||
justification_bits: pre.justification_bits.clone(),
|
||||
previous_justified_checkpoint: pre.previous_justified_checkpoint,
|
||||
current_justified_checkpoint: pre.current_justified_checkpoint,
|
||||
finalized_checkpoint: pre.finalized_checkpoint,
|
||||
// Inactivity
|
||||
inactivity_scores,
|
||||
// Sync committees
|
||||
current_sync_committee: SyncCommittee::temporary()?, // not read
|
||||
next_sync_committee: SyncCommittee::temporary()?, // not read
|
||||
// Caches
|
||||
committee_caches: mem::take(&mut pre.committee_caches),
|
||||
pubkey_cache: mem::take(&mut pre.pubkey_cache),
|
||||
exit_cache: mem::take(&mut pre.exit_cache),
|
||||
tree_hash_cache: mem::take(&mut pre.tree_hash_cache),
|
||||
});
|
||||
|
||||
// Fill in previous epoch participation from the pre state's pending attestations.
|
||||
translate_participation(&mut post, &pre.previous_epoch_attestations, spec)?;
|
||||
|
||||
// Fill in sync committees
|
||||
// Note: A duplicate committee is assigned for the current and next committee at the fork
|
||||
// boundary
|
||||
let sync_committee = post.get_next_sync_committee(spec)?;
|
||||
post.as_altair_mut()?.current_sync_committee = sync_committee.clone();
|
||||
post.as_altair_mut()?.next_sync_committee = sync_committee;
|
||||
|
||||
*pre_state = post;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user