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:
Michael Sproul
2021-07-09 06:15:32 +00:00
parent 89361573d4
commit b4689e20c6
271 changed files with 9652 additions and 8444 deletions

View 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())
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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(())

View File

@@ -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(())
}

View File

@@ -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)?;

View File

@@ -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,

View File

@@ -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};

View File

@@ -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(())
}

View File

@@ -0,0 +1 @@
pub mod sync_committee;

View File

@@ -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 = &current_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(&current_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(())
}

View File

@@ -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)
}
}

View File

@@ -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 =

View File

@@ -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,
}

View File

@@ -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)?;

View File

@@ -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(())
}

View File

@@ -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

View File

@@ -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,
}

View File

@@ -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)))?;

View File

@@ -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
);

View File

@@ -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)))?;

View File

@@ -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)))?;

View File

@@ -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(())
}

View File

@@ -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,
})
}

View File

@@ -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(())
}

View File

@@ -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(&current_indices, spec)?;
weigh_justification_and_finalization(
state,
total_active_balance,
previous_target_balance,
current_target_balance,
)
}

View File

@@ -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(())
}

View File

@@ -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(())
}

View File

@@ -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(())
}

View 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,
})
}

View File

@@ -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(),
)
}

View File

@@ -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(())
}

View File

@@ -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)?)?;

View File

@@ -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(())
}

View File

@@ -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 {

View File

@@ -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(())
}

View File

@@ -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(())

View File

@@ -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(())
}

View File

@@ -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);
}
}

View File

@@ -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))
);
}
}

View File

@@ -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

View File

@@ -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(())
}

View File

@@ -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(())
}

View File

@@ -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.
//

View File

@@ -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)
}
}

View File

@@ -0,0 +1,3 @@
pub mod altair;
pub use altair::upgrade_to_altair;

View 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(())
}