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