Update block processing to v0.5.0

This commit is contained in:
Paul Hauner 2019-03-17 12:25:37 +11:00
parent 7f4af20212
commit 6bd2055a0a
No known key found for this signature in database
GPG Key ID: D362883A9218FCC6
13 changed files with 413 additions and 336 deletions

View File

@ -1,17 +1,14 @@
use self::verify_proposer_slashing::verify_proposer_slashing; use self::verify_proposer_slashing::verify_proposer_slashing;
use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex}; use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex};
use hashing::hash;
use rayon::prelude::*; use rayon::prelude::*;
use ssz::{ssz_encode, SignedRoot, TreeHash}; use ssz::{SignedRoot, TreeHash};
use types::*; use types::*;
pub use self::verify_attester_slashing::{ pub use self::verify_attester_slashing::{
gather_attester_slashing_indices, verify_attester_slashing, gather_attester_slashing_indices, verify_attester_slashing,
}; };
pub use validate_attestation::{validate_attestation, validate_attestation_without_signature}; pub use validate_attestation::{validate_attestation, validate_attestation_without_signature};
pub use verify_deposit::{ pub use verify_deposit::{get_existing_validator_index, verify_deposit, verify_deposit_index};
build_public_key_hashmap, get_existing_validator_index, verify_deposit, verify_deposit_index,
};
pub use verify_exit::verify_exit; pub use verify_exit::verify_exit;
pub use verify_slashable_attestation::verify_slashable_attestation; pub use verify_slashable_attestation::verify_slashable_attestation;
pub use verify_transfer::{execute_transfer, verify_transfer}; pub use verify_transfer::{execute_transfer, verify_transfer};
@ -72,8 +69,7 @@ fn per_block_processing_signature_optional(
should_verify_block_signature: bool, should_verify_block_signature: bool,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Verify that `block.slot == state.slot`. process_block_header(state, block, spec)?;
verify!(block.slot == state.slot, Invalid::StateSlotMismatch);
// Ensure the current and previous epoch cache is built. // Ensure the current and previous epoch cache is built.
state.build_epoch_cache(RelativeEpoch::Current, spec)?; state.build_epoch_cache(RelativeEpoch::Current, spec)?;
@ -83,7 +79,7 @@ fn per_block_processing_signature_optional(
verify_block_signature(&state, &block, &spec)?; verify_block_signature(&state, &block, &spec)?;
} }
process_randao(&mut state, &block, &spec)?; process_randao(&mut state, &block, &spec)?;
process_eth1_data(&mut state, &block.eth1_data)?; process_eth1_data(&mut state, &block.body.eth1_data)?;
process_proposer_slashings(&mut state, &block.body.proposer_slashings, spec)?; process_proposer_slashings(&mut state, &block.body.proposer_slashings, spec)?;
process_attester_slashings(&mut state, &block.body.attester_slashings, spec)?; process_attester_slashings(&mut state, &block.body.attester_slashings, spec)?;
process_attestations(&mut state, &block.body.attestations, spec)?; process_attestations(&mut state, &block.body.attestations, spec)?;
@ -94,33 +90,47 @@ fn per_block_processing_signature_optional(
Ok(()) Ok(())
} }
/// Processes the block header.
///
/// Spec v0.5.0
pub fn process_block_header(
state: &BeaconState,
block: &BeaconBlock,
spec: &ChainSpec,
) -> Result<(), Error> {
verify!(block.slot == state.slot, Invalid::StateSlotMismatch);
verify!(
block.previous_block_root.as_bytes() == &state.latest_block_header.hash_tree_root()[..],
Invalid::ParentBlockRootMismatch
);
state.latest_block_header = block.into_temporary_header(spec);
Ok(())
}
/// Verifies the signature of a block. /// Verifies the signature of a block.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn verify_block_signature( pub fn verify_block_signature(
state: &BeaconState, state: &BeaconState,
block: &BeaconBlock, block: &BeaconBlock,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<(), Error> { ) -> Result<(), Error> {
let block_proposer = let block_proposer = &state.validator_registry
&state.validator_registry[state.get_beacon_proposer_index(block.slot, spec)?]; [state.get_beacon_proposer_index(block.slot, RelativeEpoch::Current, spec)?];
let proposal = Proposal {
slot: block.slot,
shard: spec.beacon_chain_shard_number,
block_root: Hash256::from_slice(&block.signed_root()[..]),
signature: block.signature.clone(),
};
let domain = spec.get_domain( let domain = spec.get_domain(
block.slot.epoch(spec.slots_per_epoch), block.slot.epoch(spec.slots_per_epoch),
Domain::Proposal, Domain::BeaconBlock,
&state.fork, &state.fork,
); );
verify!( verify!(
proposal block
.signature .signature
.verify(&proposal.signed_root()[..], domain, &block_proposer.pubkey), .verify(&block.signed_root()[..], domain, &block_proposer.pubkey),
Invalid::BadSignature Invalid::BadSignature
); );
@ -130,21 +140,18 @@ pub fn verify_block_signature(
/// Verifies the `randao_reveal` against the block's proposer pubkey and updates /// Verifies the `randao_reveal` against the block's proposer pubkey and updates
/// `state.latest_randao_mixes`. /// `state.latest_randao_mixes`.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn process_randao( pub fn process_randao(
state: &mut BeaconState, state: &mut BeaconState,
block: &BeaconBlock, block: &BeaconBlock,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Let `proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]`. let block_proposer = &state.validator_registry
let block_proposer = [state.get_beacon_proposer_index(block.slot, RelativeEpoch::Current, spec)?];
&state.validator_registry[state.get_beacon_proposer_index(block.slot, spec)?];
// Verify that `bls_verify(pubkey=proposer.pubkey, // Verify the RANDAO is a valid signature of the proposer.
// message_hash=hash_tree_root(get_current_epoch(state)), signature=block.randao_reveal,
// domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO))`.
verify!( verify!(
block.randao_reveal.verify( block.body.randao_reveal.verify(
&state.current_epoch(spec).hash_tree_root()[..], &state.current_epoch(spec).hash_tree_root()[..],
spec.get_domain( spec.get_domain(
block.slot.epoch(spec.slots_per_epoch), block.slot.epoch(spec.slots_per_epoch),
@ -156,21 +163,23 @@ pub fn process_randao(
Invalid::BadRandaoSignature Invalid::BadRandaoSignature
); );
// Update the state's RANDAO mix with the one revealed in the block. // Update the current epoch RANDAO mix.
update_randao(state, &block.randao_reveal, spec)?; state.update_randao_mix(state.current_epoch(spec), &block.body.randao_reveal, spec)?;
Ok(()) Ok(())
} }
/// Update the `state.eth1_data_votes` based upon the `eth1_data` provided. /// Update the `state.eth1_data_votes` based upon the `eth1_data` provided.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn process_eth1_data(state: &mut BeaconState, eth1_data: &Eth1Data) -> Result<(), Error> { pub fn process_eth1_data(state: &mut BeaconState, eth1_data: &Eth1Data) -> Result<(), Error> {
// Either increment the eth1_data vote count, or add a new eth1_data. // Attempt to find a `Eth1DataVote` with matching `Eth1Data`.
let matching_eth1_vote_index = state let matching_eth1_vote_index = state
.eth1_data_votes .eth1_data_votes
.iter() .iter()
.position(|vote| vote.eth1_data == *eth1_data); .position(|vote| vote.eth1_data == *eth1_data);
// If a vote exists, increment it's `vote_count`. Otherwise, create a new `Eth1DataVote`.
if let Some(index) = matching_eth1_vote_index { if let Some(index) = matching_eth1_vote_index {
state.eth1_data_votes[index].vote_count += 1; state.eth1_data_votes[index].vote_count += 1;
} else { } else {
@ -183,46 +192,12 @@ pub fn process_eth1_data(state: &mut BeaconState, eth1_data: &Eth1Data) -> Resul
Ok(()) Ok(())
} }
/// Updates the present randao mix.
///
/// Set `state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] =
/// xor(get_randao_mix(state, get_current_epoch(state)), hash(block.randao_reveal))`.
///
/// Spec v0.4.0
pub fn update_randao(
state: &mut BeaconState,
reveal: &Signature,
spec: &ChainSpec,
) -> Result<(), BeaconStateError> {
let hashed_reveal = {
let encoded_signature = ssz_encode(reveal);
Hash256::from_slice(&hash(&encoded_signature[..])[..])
};
let current_epoch = state.slot.epoch(spec.slots_per_epoch);
let current_mix = state
.get_randao_mix(current_epoch, spec)
.ok_or_else(|| BeaconStateError::InsufficientRandaoMixes)?;
let new_mix = *current_mix ^ hashed_reveal;
let index = current_epoch.as_usize() % spec.latest_randao_mixes_length;
if index < state.latest_randao_mixes.len() {
state.latest_randao_mixes[index] = new_mix;
Ok(())
} else {
Err(BeaconStateError::InsufficientRandaoMixes)
}
}
/// Validates each `ProposerSlashing` and updates the state, short-circuiting on an invalid object. /// 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 /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure. /// an `Err` describing the invalid object or cause of failure.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn process_proposer_slashings( pub fn process_proposer_slashings(
state: &mut BeaconState, state: &mut BeaconState,
proposer_slashings: &[ProposerSlashing], proposer_slashings: &[ProposerSlashing],
@ -242,6 +217,7 @@ pub fn process_proposer_slashings(
.map_err(|e| e.into_with_index(i)) .map_err(|e| e.into_with_index(i))
})?; })?;
// Update the state.
for proposer_slashing in proposer_slashings { for proposer_slashing in proposer_slashings {
state.slash_validator(proposer_slashing.proposer_index as usize, spec)?; state.slash_validator(proposer_slashing.proposer_index as usize, spec)?;
} }
@ -254,7 +230,7 @@ pub fn process_proposer_slashings(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure. /// an `Err` describing the invalid object or cause of failure.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn process_attester_slashings( pub fn process_attester_slashings(
state: &mut BeaconState, state: &mut BeaconState,
attester_slashings: &[AttesterSlashing], attester_slashings: &[AttesterSlashing],
@ -296,7 +272,7 @@ pub fn process_attester_slashings(
) )
.map_err(|e| e.into_with_index(i))?; .map_err(|e| e.into_with_index(i))?;
let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing) let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing, spec)
.map_err(|e| e.into_with_index(i))?; .map_err(|e| e.into_with_index(i))?;
for i in slashable_indices { for i in slashable_indices {
@ -312,7 +288,7 @@ pub fn process_attester_slashings(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure. /// an `Err` describing the invalid object or cause of failure.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn process_attestations( pub fn process_attestations(
state: &mut BeaconState, state: &mut BeaconState,
attestations: &[Attestation], attestations: &[Attestation],
@ -342,7 +318,14 @@ pub fn process_attestations(
custody_bitfield: attestation.custody_bitfield.clone(), custody_bitfield: attestation.custody_bitfield.clone(),
inclusion_slot: state.slot, inclusion_slot: state.slot,
}; };
state.latest_attestations.push(pending_attestation);
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
if attestation_epoch == state.current_epoch(spec) {
state.current_epoch_attestations.push(pending_attestation)
} else if attestation_epoch == state.previous_epoch(spec) {
state.previous_epoch_attestations.push(pending_attestation)
}
} }
Ok(()) Ok(())
@ -353,7 +336,7 @@ pub fn process_attestations(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure. /// an `Err` describing the invalid object or cause of failure.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn process_deposits( pub fn process_deposits(
state: &mut BeaconState, state: &mut BeaconState,
deposits: &[Deposit], deposits: &[Deposit],
@ -423,7 +406,7 @@ pub fn process_deposits(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure. /// an `Err` describing the invalid object or cause of failure.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn process_exits( pub fn process_exits(
state: &mut BeaconState, state: &mut BeaconState,
voluntary_exits: &[VoluntaryExit], voluntary_exits: &[VoluntaryExit],
@ -455,7 +438,7 @@ pub fn process_exits(
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
/// an `Err` describing the invalid object or cause of failure. /// an `Err` describing the invalid object or cause of failure.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn process_transfers( pub fn process_transfers(
state: &mut BeaconState, state: &mut BeaconState,
transfers: &[Transfer], transfers: &[Transfer],

View File

@ -67,6 +67,7 @@ impl_from_beacon_state_error!(BlockProcessingError);
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum BlockInvalid { pub enum BlockInvalid {
StateSlotMismatch, StateSlotMismatch,
ParentBlockRootMismatch,
BadSignature, BadSignature,
BadRandaoSignature, BadRandaoSignature,
MaxAttestationsExceeded, MaxAttestationsExceeded,
@ -112,45 +113,53 @@ pub enum AttestationValidationError {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum AttestationInvalid { pub enum AttestationInvalid {
/// Attestation references a pre-genesis slot. /// Attestation references a pre-genesis slot.
/// PreGenesis { genesis: Slot, attestation: Slot },
/// (genesis_slot, attestation_slot)
PreGenesis(Slot, Slot),
/// Attestation included before the inclusion delay. /// Attestation included before the inclusion delay.
/// IncludedTooEarly {
/// (state_slot, inclusion_delay, attestation_slot) state: Slot,
IncludedTooEarly(Slot, u64, Slot), delay: u64,
attestation: Slot,
},
/// Attestation slot is too far in the past to be included in a block. /// Attestation slot is too far in the past to be included in a block.
/// IncludedTooLate { state: Slot, attestation: Slot },
/// (state_slot, attestation_slot)
IncludedTooLate(Slot, Slot),
/// Attestation justified epoch does not match the states current or previous justified epoch. /// Attestation justified epoch does not match the states current or previous justified epoch.
/// ///
/// (attestation_justified_epoch, state_epoch, used_previous_epoch) /// `is_current` is `true` if the attestation was compared to the
WrongJustifiedEpoch(Epoch, Epoch, bool), /// `state.current_justified_epoch`, `false` if compared to `state.previous_justified_epoch`.
WrongJustifiedEpoch {
state: Epoch,
attestation: Epoch,
is_current: bool,
},
/// Attestation justified epoch root does not match root known to the state. /// Attestation justified epoch root does not match root known to the state.
/// ///
/// (state_justified_root, attestation_justified_root) /// `is_current` is `true` if the attestation was compared to the
WrongJustifiedRoot(Hash256, Hash256), /// `state.current_justified_epoch`, `false` if compared to `state.previous_justified_epoch`.
WrongJustifiedRoot {
state: Hash256,
attestation: Hash256,
is_current: bool,
},
/// Attestation crosslink root does not match the state crosslink root for the attestations /// Attestation crosslink root does not match the state crosslink root for the attestations
/// slot. /// slot.
BadLatestCrosslinkRoot, BadPreviousCrosslink,
/// The custody bitfield has some bits set `true`. This is not allowed in phase 0. /// The custody bitfield has some bits set `true`. This is not allowed in phase 0.
CustodyBitfieldHasSetBits, CustodyBitfieldHasSetBits,
/// There are no set bits on the attestation -- an attestation must be signed by at least one /// There are no set bits on the attestation -- an attestation must be signed by at least one
/// validator. /// validator.
AggregationBitfieldIsEmpty, AggregationBitfieldIsEmpty,
/// The custody bitfield length is not the smallest possible size to represent the committee. /// The custody bitfield length is not the smallest possible size to represent the committee.
/// BadCustodyBitfieldLength {
/// (committee_len, bitfield_len) committee_len: usize,
BadCustodyBitfieldLength(usize, usize), bitfield_len: usize,
},
/// The aggregation bitfield length is not the smallest possible size to represent the committee. /// The aggregation bitfield length is not the smallest possible size to represent the committee.
/// BadAggregationBitfieldLength {
/// (committee_len, bitfield_len) committee_len: usize,
BadAggregationBitfieldLength(usize, usize), bitfield_len: usize,
/// There was no known committee for the given shard in the given slot. },
/// /// There was no known committee in this `epoch` for the given shard and slot.
/// (attestation_data_shard, attestation_data_slot) NoCommitteeForShard { shard: u64, slot: Slot },
NoCommitteeForShard(u64, Slot),
/// The validator index was unknown. /// The validator index was unknown.
UnknownValidator(u64), UnknownValidator(u64),
/// The attestation signature verification failed. /// The attestation signature verification failed.
@ -188,6 +197,8 @@ pub enum AttesterSlashingInvalid {
SlashableAttestation2Invalid(SlashableAttestationInvalid), SlashableAttestation2Invalid(SlashableAttestationInvalid),
/// The validator index is unknown. One cannot slash one who does not exist. /// The validator index is unknown. One cannot slash one who does not exist.
UnknownValidator(u64), UnknownValidator(u64),
/// The specified validator has already been withdrawn.
ValidatorAlreadyWithdrawn(u64),
/// There were no indices able to be slashed. /// There were no indices able to be slashed.
NoSlashableIndices, NoSlashableIndices,
} }
@ -264,16 +275,12 @@ pub enum ProposerSlashingInvalid {
/// ///
/// (proposal_1_slot, proposal_2_slot) /// (proposal_1_slot, proposal_2_slot)
ProposalSlotMismatch(Slot, Slot), ProposalSlotMismatch(Slot, Slot),
/// The two proposal have different shards. /// The proposals are identical and therefore not slashable.
/// ProposalsIdentical,
/// (proposal_1_shard, proposal_2_shard)
ProposalShardMismatch(u64, u64),
/// The two proposal have different block roots.
///
/// (proposal_1_root, proposal_2_root)
ProposalBlockRootMismatch(Hash256, Hash256),
/// The specified proposer has already been slashed. /// The specified proposer has already been slashed.
ProposerAlreadySlashed, ProposerAlreadySlashed,
/// The specified proposer has already been withdrawn.
ProposerAlreadyWithdrawn(u64),
/// The first proposal signature was invalid. /// The first proposal signature was invalid.
BadProposal1Signature, BadProposal1Signature,
/// The second proposal signature was invalid. /// The second proposal signature was invalid.
@ -302,9 +309,7 @@ pub enum DepositValidationError {
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum DepositInvalid { pub enum DepositInvalid {
/// The deposit index does not match the state index. /// The deposit index does not match the state index.
/// BadIndex { state: u64, deposit: u64 },
/// (state_index, deposit_index)
BadIndex(u64, u64),
/// The proof-of-possession does not match the given pubkey. /// The proof-of-possession does not match the given pubkey.
BadProofOfPossession, BadProofOfPossession,
/// The withdrawal credentials for the depositing validator did not match the withdrawal /// The withdrawal credentials for the depositing validator did not match the withdrawal
@ -334,11 +339,14 @@ pub enum ExitValidationError {
pub enum ExitInvalid { pub enum ExitInvalid {
/// The specified validator is not in the state's validator registry. /// The specified validator is not in the state's validator registry.
ValidatorUnknown(u64), ValidatorUnknown(u64),
AlreadyExited, /// The specified validator has a non-maximum exit epoch.
AlreadyExited(u64),
/// The specified validator has already initiated exit.
AlreadyInitiatedExited(u64),
/// The exit is for a future epoch. /// The exit is for a future epoch.
/// FutureEpoch { state: Epoch, exit: Epoch },
/// (state_epoch, exit_epoch) /// The validator has not been active for long enough.
FutureEpoch(Epoch, Epoch), TooYoungToLeave { lifespan: Epoch, expected: u64 },
/// The exit signature was not signed by the validator. /// The exit signature was not signed by the validator.
BadSignature, BadSignature,
} }

View File

@ -8,7 +8,7 @@ use types::*;
/// ///
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity. /// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn validate_attestation( pub fn validate_attestation(
state: &BeaconState, state: &BeaconState,
attestation: &Attestation, attestation: &Attestation,
@ -22,7 +22,7 @@ pub fn validate_attestation(
/// ///
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity. /// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn validate_attestation_without_signature( pub fn validate_attestation_without_signature(
state: &BeaconState, state: &BeaconState,
attestation: &Attestation, attestation: &Attestation,
@ -35,74 +35,83 @@ pub fn validate_attestation_without_signature(
/// given state, optionally validating the aggregate signature. /// given state, optionally validating the aggregate signature.
/// ///
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
fn validate_attestation_signature_optional( fn validate_attestation_signature_optional(
state: &BeaconState, state: &BeaconState,
attestation: &Attestation, attestation: &Attestation,
spec: &ChainSpec, spec: &ChainSpec,
verify_signature: bool, verify_signature: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Verify that `attestation.data.slot >= GENESIS_SLOT`. let state_epoch = state.slot.epoch(spec.slots_per_epoch);
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
// Can't submit pre-historic attestations.
verify!( verify!(
attestation.data.slot >= spec.genesis_slot, attestation.data.slot >= spec.genesis_slot,
Invalid::PreGenesis(spec.genesis_slot, attestation.data.slot) Invalid::PreGenesis {
genesis: spec.genesis_slot,
attestation: attestation.data.slot
}
); );
// Verify that `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot`. // Can't submit attestations too far in history.
verify!(
state.slot <= attestation.data.slot + spec.slots_per_epoch,
Invalid::IncludedTooLate {
state: spec.genesis_slot,
attestation: attestation.data.slot
}
);
// Can't submit attestation too quickly.
verify!( verify!(
attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot, attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
Invalid::IncludedTooEarly( Invalid::IncludedTooEarly {
state.slot, state: state.slot,
spec.min_attestation_inclusion_delay, delay: spec.min_attestation_inclusion_delay,
attestation.data.slot attestation: attestation.data.slot
) }
); );
// Verify that `state.slot < attestation.data.slot + SLOTS_PER_EPOCH`. // Verify the justified epoch and root is correct.
if attestation_epoch >= state_epoch {
verify!( verify!(
state.slot < attestation.data.slot + spec.slots_per_epoch, attestation.data.source_epoch == state.current_justified_epoch,
Invalid::IncludedTooLate(state.slot, attestation.data.slot) Invalid::WrongJustifiedEpoch {
state: state.current_justified_epoch,
attestation: attestation.data.source_epoch,
is_current: true,
}
); );
// Verify that `attestation.data.justified_epoch` is equal to `state.justified_epoch` if
// `slot_to_epoch(attestation.data.slot + 1) >= get_current_epoch(state) else
// state.previous_justified_epoch`.
if (attestation.data.slot + 1).epoch(spec.slots_per_epoch) >= state.current_epoch(spec) {
verify!( verify!(
attestation.data.justified_epoch == state.justified_epoch, attestation.data.source_root == state.current_justified_root,
Invalid::WrongJustifiedEpoch( Invalid::WrongJustifiedRoot {
attestation.data.justified_epoch, state: state.current_justified_root,
state.justified_epoch, attestation: attestation.data.source_root,
false is_current: true,
) }
); );
} else { } else {
verify!( verify!(
attestation.data.justified_epoch == state.previous_justified_epoch, attestation.data.source_epoch == state.previous_justified_epoch,
Invalid::WrongJustifiedEpoch( Invalid::WrongJustifiedEpoch {
attestation.data.justified_epoch, state: state.previous_justified_epoch,
state.previous_justified_epoch, attestation: attestation.data.source_epoch,
true is_current: false,
) }
);
verify!(
attestation.data.source_root == state.previous_justified_root,
Invalid::WrongJustifiedRoot {
state: state.previous_justified_root,
attestation: attestation.data.source_root,
is_current: true,
}
); );
} }
// Verify that `attestation.data.justified_block_root` is equal to `get_block_root(state, // Check that the crosslink data is valid.
// get_epoch_start_slot(attestation.data.justified_epoch))`. //
let justified_block_root = *state
.get_block_root(
attestation
.data
.justified_epoch
.start_slot(spec.slots_per_epoch),
&spec,
)
.ok_or(BeaconStateError::InsufficientBlockRoots)?;
verify!(
attestation.data.justified_block_root == justified_block_root,
Invalid::WrongJustifiedRoot(justified_block_root, attestation.data.justified_block_root)
);
// Verify that either: // Verify that either:
// //
// (i)`state.latest_crosslinks[attestation.data.shard] == attestation.data.latest_crosslink`, // (i)`state.latest_crosslinks[attestation.data.shard] == attestation.data.latest_crosslink`,
@ -115,46 +124,59 @@ fn validate_attestation_signature_optional(
epoch: attestation.data.slot.epoch(spec.slots_per_epoch), epoch: attestation.data.slot.epoch(spec.slots_per_epoch),
}; };
verify!( verify!(
(attestation.data.latest_crosslink (attestation.data.previous_crosslink
== state.latest_crosslinks[attestation.data.shard as usize]) == state.latest_crosslinks[attestation.data.shard as usize])
| (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink), | (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink),
Invalid::BadLatestCrosslinkRoot Invalid::BadPreviousCrosslink
); );
// Get the committee for this attestation // Attestation must be non-empty!
let (committee, _shard) = state
.get_crosslink_committees_at_slot(attestation.data.slot, spec)?
.iter()
.find(|(_committee, shard)| *shard == attestation.data.shard)
.ok_or_else(|| {
Error::Invalid(Invalid::NoCommitteeForShard(
attestation.data.shard,
attestation.data.slot,
))
})?;
// Custody bitfield is all zeros (phase 0 requirement).
verify!(
attestation.custody_bitfield.num_set_bits() == 0,
Invalid::CustodyBitfieldHasSetBits
);
// Custody bitfield length is correct.
verify!(
verify_bitfield_length(&attestation.custody_bitfield, committee.len()),
Invalid::BadCustodyBitfieldLength(committee.len(), attestation.custody_bitfield.len())
);
// Aggregation bitfield isn't empty.
verify!( verify!(
attestation.aggregation_bitfield.num_set_bits() != 0, attestation.aggregation_bitfield.num_set_bits() != 0,
Invalid::AggregationBitfieldIsEmpty Invalid::AggregationBitfieldIsEmpty
); );
// Custody bitfield must be empty (be be removed in phase 1)
verify!(
attestation.custody_bitfield.num_set_bits() == 0,
Invalid::CustodyBitfieldHasSetBits
);
// Get the committee for the specific shard that this attestation is for.
let crosslink_committee = state
.get_crosslink_committees_at_slot(
attestation.data.slot,
RelativeEpoch::NextWithoutRegistryChange,
spec,
)?
.iter()
.find(|c| c.shard == attestation.data.shard)
.ok_or_else(|| {
Error::Invalid(Invalid::NoCommitteeForShard {
shard: attestation.data.shard,
slot: attestation.data.slot,
})
})?;
let committee = &crosslink_committee.committee;
// Custody bitfield length is correct.
//
// This is not directly in the spec, but it is inferred.
verify!(
verify_bitfield_length(&attestation.custody_bitfield, committee.len()),
Invalid::BadCustodyBitfieldLength {
committee_len: committee.len(),
bitfield_len: attestation.custody_bitfield.len()
}
);
// Aggregation bitfield length is correct. // Aggregation bitfield length is correct.
//
// This is not directly in the spec, but it is inferred.
verify!( verify!(
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()), verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
Invalid::BadAggregationBitfieldLength( Invalid::BadAggregationBitfieldLength {
committee.len(), committee_len: committee.len(),
attestation.aggregation_bitfield.len() bitfield_len: attestation.custody_bitfield.len()
) }
); );
if verify_signature { if verify_signature {
@ -171,7 +193,7 @@ fn validate_attestation_signature_optional(
)?; )?;
} }
// [TO BE REMOVED IN PHASE 1] Verify that `attestation.data.crosslink_data_root == ZERO_HASH`. // Crosslink data root is zero (to be removed in phase 1).
verify!( verify!(
attestation.data.crosslink_data_root == spec.zero_hash, attestation.data.crosslink_data_root == spec.zero_hash,
Invalid::ShardBlockRootNotZero Invalid::ShardBlockRootNotZero
@ -188,7 +210,7 @@ fn validate_attestation_signature_optional(
/// - `custody_bitfield` does not have a bit for each index of `committee`. /// - `custody_bitfield` does not have a bit for each index of `committee`.
/// - A `validator_index` in `committee` is not in `state.validator_registry`. /// - A `validator_index` in `committee` is not in `state.validator_registry`.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
fn verify_attestation_signature( fn verify_attestation_signature(
state: &BeaconState, state: &BeaconState,
committee: &[usize], committee: &[usize],
@ -204,10 +226,10 @@ fn verify_attestation_signature(
for (i, v) in committee.iter().enumerate() { for (i, v) in committee.iter().enumerate() {
let validator_signed = aggregation_bitfield.get(i).map_err(|_| { let validator_signed = aggregation_bitfield.get(i).map_err(|_| {
Error::Invalid(Invalid::BadAggregationBitfieldLength( Error::Invalid(Invalid::BadAggregationBitfieldLength {
committee.len(), committee_len: committee.len(),
aggregation_bitfield.len(), bitfield_len: aggregation_bitfield.len(),
)) })
})?; })?;
if validator_signed { if validator_signed {
@ -215,10 +237,10 @@ fn verify_attestation_signature(
Ok(bit) => bit, Ok(bit) => bit,
// Invalidate signature if custody_bitfield.len() < committee // Invalidate signature if custody_bitfield.len() < committee
Err(_) => { Err(_) => {
return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength( return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength {
committee.len(), committee_len: committee.len(),
custody_bitfield.len(), bitfield_len: aggregation_bitfield.len(),
))); }));
} }
}; };

View File

@ -7,7 +7,7 @@ use types::*;
/// ///
/// Returns `Ok(())` if the `AttesterSlashing` is valid, otherwise indicates the reason for invalidity. /// Returns `Ok(())` if the `AttesterSlashing` is valid, otherwise indicates the reason for invalidity.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn verify_attester_slashing( pub fn verify_attester_slashing(
state: &BeaconState, state: &BeaconState,
attester_slashing: &AttesterSlashing, attester_slashing: &AttesterSlashing,
@ -41,15 +41,16 @@ pub fn verify_attester_slashing(
/// ///
/// Returns Ok(indices) if `indices.len() > 0`. /// Returns Ok(indices) if `indices.len() > 0`.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn gather_attester_slashing_indices( pub fn gather_attester_slashing_indices(
state: &BeaconState, state: &BeaconState,
attester_slashing: &AttesterSlashing, attester_slashing: &AttesterSlashing,
spec: &ChainSpec,
) -> Result<Vec<u64>, Error> { ) -> Result<Vec<u64>, Error> {
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1; let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2; let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
let mut slashable_indices = vec![]; let mut slashable_indices = Vec::with_capacity(spec.max_indices_per_slashable_vote);
for i in &slashable_attestation_1.validator_indices { for i in &slashable_attestation_1.validator_indices {
let validator = state let validator = state
.validator_registry .validator_registry
@ -57,11 +58,20 @@ pub fn gather_attester_slashing_indices(
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?; .ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?;
if slashable_attestation_2.validator_indices.contains(&i) & !validator.slashed { if slashable_attestation_2.validator_indices.contains(&i) & !validator.slashed {
// TODO: verify that we should reject any slashable attestation which includes a
// withdrawn validator. PH has asked the question on gitter, awaiting response.
verify!(
validator.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch),
Invalid::ValidatorAlreadyWithdrawn(*i)
);
slashable_indices.push(*i); slashable_indices.push(*i);
} }
} }
verify!(!slashable_indices.is_empty(), Invalid::NoSlashableIndices); verify!(!slashable_indices.is_empty(), Invalid::NoSlashableIndices);
slashable_indices.shrink_to_fit();
Ok(slashable_indices) Ok(slashable_indices)
} }

View File

@ -18,7 +18,7 @@ pub type PublicKeyValidatorIndexHashmap = HashMap<PublicKey, u64>;
/// ///
/// Note: this function is incomplete. /// Note: this function is incomplete.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn verify_deposit( pub fn verify_deposit(
state: &BeaconState, state: &BeaconState,
deposit: &Deposit, deposit: &Deposit,
@ -49,26 +49,25 @@ pub fn verify_deposit(
/// Verify that the `Deposit` index is correct. /// Verify that the `Deposit` index is correct.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn verify_deposit_index(state: &BeaconState, deposit: &Deposit) -> Result<(), Error> { pub fn verify_deposit_index(state: &BeaconState, deposit: &Deposit) -> Result<(), Error> {
verify!( verify!(
deposit.index == state.deposit_index, deposit.index == state.deposit_index,
Invalid::BadIndex(state.deposit_index, deposit.index) Invalid::BadIndex {
state: state.deposit_index,
deposit: deposit.index
}
); );
Ok(()) Ok(())
} }
pub fn build_public_key_hashmap(state: &BeaconState) -> PublicKeyValidatorIndexHashmap { /// Returns a `Some(validator index)` if a pubkey already exists in the `validator_registry`,
let mut hashmap = HashMap::with_capacity(state.validator_registry.len()); /// otherwise returns `None`.
///
for (i, validator) in state.validator_registry.iter().enumerate() { /// ## Errors
hashmap.insert(validator.pubkey.clone(), i as u64); ///
} /// Errors if the state's `pubkey_cache` is not current.
hashmap
}
pub fn get_existing_validator_index( pub fn get_existing_validator_index(
state: &BeaconState, state: &BeaconState,
deposit: &Deposit, deposit: &Deposit,
@ -94,12 +93,12 @@ pub fn get_existing_validator_index(
/// Verify that a deposit is included in the state's eth1 deposit root. /// Verify that a deposit is included in the state's eth1 deposit root.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
fn verify_deposit_merkle_proof(state: &BeaconState, deposit: &Deposit, spec: &ChainSpec) -> bool { fn verify_deposit_merkle_proof(state: &BeaconState, deposit: &Deposit, spec: &ChainSpec) -> bool {
let leaf = hash(&get_serialized_deposit_data(deposit)); let leaf = hash(&get_serialized_deposit_data(deposit));
verify_merkle_proof( verify_merkle_proof(
Hash256::from_slice(&leaf), Hash256::from_slice(&leaf),
&deposit.branch, &deposit.proof,
spec.deposit_contract_tree_depth as usize, spec.deposit_contract_tree_depth as usize,
deposit.index as usize, deposit.index as usize,
state.latest_eth1_data.deposit_root, state.latest_eth1_data.deposit_root,
@ -108,7 +107,7 @@ fn verify_deposit_merkle_proof(state: &BeaconState, deposit: &Deposit, spec: &Ch
/// Helper struct for easily getting the serialized data generated by the deposit contract. /// Helper struct for easily getting the serialized data generated by the deposit contract.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
#[derive(Encode)] #[derive(Encode)]
struct SerializedDepositData { struct SerializedDepositData {
amount: u64, amount: u64,
@ -119,7 +118,7 @@ struct SerializedDepositData {
/// Return the serialized data generated by the deposit contract that is used to generate the /// Return the serialized data generated by the deposit contract that is used to generate the
/// merkle proof. /// merkle proof.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
fn get_serialized_deposit_data(deposit: &Deposit) -> Vec<u8> { fn get_serialized_deposit_data(deposit: &Deposit) -> Vec<u8> {
let serialized_deposit_data = SerializedDepositData { let serialized_deposit_data = SerializedDepositData {
amount: deposit.deposit_data.amount, amount: deposit.deposit_data.amount,

View File

@ -7,7 +7,7 @@ use types::*;
/// ///
/// Returns `Ok(())` if the `Exit` is valid, otherwise indicates the reason for invalidity. /// Returns `Ok(())` if the `Exit` is valid, otherwise indicates the reason for invalidity.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn verify_exit( pub fn verify_exit(
state: &BeaconState, state: &BeaconState,
exit: &VoluntaryExit, exit: &VoluntaryExit,
@ -18,15 +18,35 @@ pub fn verify_exit(
.get(exit.validator_index as usize) .get(exit.validator_index as usize)
.ok_or_else(|| Error::Invalid(Invalid::ValidatorUnknown(exit.validator_index)))?; .ok_or_else(|| Error::Invalid(Invalid::ValidatorUnknown(exit.validator_index)))?;
// Verify that the validator has not yet exited.
verify!( verify!(
validator.exit_epoch validator.exit_epoch == spec.far_future_epoch,
> state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec), Invalid::AlreadyExited(exit.validator_index)
Invalid::AlreadyExited
); );
// Verify that the validator has not yet initiated.
verify!(
!validator.initiated_exit,
Invalid::AlreadyInitiatedExited(exit.validator_index)
);
// Exits must specify an epoch when they become valid; they are not valid before then.
verify!( verify!(
state.current_epoch(spec) >= exit.epoch, state.current_epoch(spec) >= exit.epoch,
Invalid::FutureEpoch(state.current_epoch(spec), exit.epoch) Invalid::FutureEpoch {
state: state.current_epoch(spec),
exit: exit.epoch
}
);
// Must have been in the validator set long enough.
let lifespan = state.slot.epoch(spec.slots_per_epoch) - validator.activation_epoch;
verify!(
lifespan >= spec.persistent_committee_period,
Invalid::TooYoungToLeave {
lifespan,
expected: spec.persistent_committee_period,
}
); );
let message = exit.signed_root(); let message = exit.signed_root();

View File

@ -7,7 +7,7 @@ use types::*;
/// ///
/// Returns `Ok(())` if the `ProposerSlashing` is valid, otherwise indicates the reason for invalidity. /// Returns `Ok(())` if the `ProposerSlashing` is valid, otherwise indicates the reason for invalidity.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn verify_proposer_slashing( pub fn verify_proposer_slashing(
proposer_slashing: &ProposerSlashing, proposer_slashing: &ProposerSlashing,
state: &BeaconState, state: &BeaconState,
@ -21,34 +21,28 @@ pub fn verify_proposer_slashing(
})?; })?;
verify!( verify!(
proposer_slashing.proposal_1.slot == proposer_slashing.proposal_2.slot, proposer_slashing.header_1.slot == proposer_slashing.header_2.slot,
Invalid::ProposalSlotMismatch( Invalid::ProposalSlotMismatch(
proposer_slashing.proposal_1.slot, proposer_slashing.header_1.slot,
proposer_slashing.proposal_2.slot proposer_slashing.header_2.slot
) )
); );
verify!( verify!(
proposer_slashing.proposal_1.shard == proposer_slashing.proposal_2.shard, proposer_slashing.header_1 != proposer_slashing.header_2,
Invalid::ProposalShardMismatch( Invalid::ProposalsIdentical
proposer_slashing.proposal_1.shard,
proposer_slashing.proposal_2.shard
)
);
verify!(
proposer_slashing.proposal_1.block_root != proposer_slashing.proposal_2.block_root,
Invalid::ProposalBlockRootMismatch(
proposer_slashing.proposal_1.block_root,
proposer_slashing.proposal_2.block_root
)
); );
verify!(!proposer.slashed, Invalid::ProposerAlreadySlashed); verify!(!proposer.slashed, Invalid::ProposerAlreadySlashed);
verify!( verify!(
verify_proposal_signature( proposer.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch),
&proposer_slashing.proposal_1, Invalid::ProposerAlreadyWithdrawn(proposer_slashing.proposer_index)
);
verify!(
verify_header_signature(
&proposer_slashing.header_1,
&proposer.pubkey, &proposer.pubkey,
&state.fork, &state.fork,
spec spec
@ -56,8 +50,8 @@ pub fn verify_proposer_slashing(
Invalid::BadProposal1Signature Invalid::BadProposal1Signature
); );
verify!( verify!(
verify_proposal_signature( verify_header_signature(
&proposer_slashing.proposal_2, &proposer_slashing.header_2,
&proposer.pubkey, &proposer.pubkey,
&state.fork, &state.fork,
spec spec
@ -71,17 +65,19 @@ pub fn verify_proposer_slashing(
/// Verifies the signature of a proposal. /// Verifies the signature of a proposal.
/// ///
/// Returns `true` if the signature is valid. /// Returns `true` if the signature is valid.
fn verify_proposal_signature( ///
proposal: &Proposal, /// Spec v0.5.0
fn verify_header_signature(
header: &BeaconBlockHeader,
pubkey: &PublicKey, pubkey: &PublicKey,
fork: &Fork, fork: &Fork,
spec: &ChainSpec, spec: &ChainSpec,
) -> bool { ) -> bool {
let message = proposal.signed_root(); let message = header.signed_root();
let domain = spec.get_domain( let domain = spec.get_domain(
proposal.slot.epoch(spec.slots_per_epoch), header.slot.epoch(spec.slots_per_epoch),
Domain::Proposal, Domain::BeaconBlock,
fork, fork,
); );
proposal.signature.verify(&message[..], domain, pubkey) header.signature.verify(&message[..], domain, pubkey)
} }

View File

@ -10,16 +10,16 @@ use types::*;
/// ///
/// Note: this function is incomplete. /// Note: this function is incomplete.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn verify_transfer( pub fn verify_transfer(
state: &BeaconState, state: &BeaconState,
transfer: &Transfer, transfer: &Transfer,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<(), Error> { ) -> Result<(), Error> {
let from_balance = *state let sender_balance = *state
.validator_balances .validator_balances
.get(transfer.from as usize) .get(transfer.sender as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.from)))?; .ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
let total_amount = transfer let total_amount = transfer
.amount .amount
@ -27,19 +27,22 @@ pub fn verify_transfer(
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?; .ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
verify!( verify!(
from_balance >= transfer.amount, sender_balance >= transfer.amount,
Invalid::FromBalanceInsufficient(transfer.amount, from_balance) Invalid::FromBalanceInsufficient(transfer.amount, sender_balance)
); );
verify!( verify!(
from_balance >= transfer.fee, sender_balance >= transfer.fee,
Invalid::FromBalanceInsufficient(transfer.fee, from_balance) Invalid::FromBalanceInsufficient(transfer.fee, sender_balance)
); );
verify!( verify!(
(from_balance == total_amount) (sender_balance == total_amount)
|| (from_balance >= (total_amount + spec.min_deposit_amount)), || (sender_balance >= (total_amount + spec.min_deposit_amount)),
Invalid::InvalidResultingFromBalance(from_balance - total_amount, spec.min_deposit_amount) Invalid::InvalidResultingFromBalance(
sender_balance - total_amount,
spec.min_deposit_amount
)
); );
verify!( verify!(
@ -47,25 +50,25 @@ pub fn verify_transfer(
Invalid::StateSlotMismatch(state.slot, transfer.slot) Invalid::StateSlotMismatch(state.slot, transfer.slot)
); );
let from_validator = state let sender_validator = state
.validator_registry .validator_registry
.get(transfer.from as usize) .get(transfer.sender as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.from)))?; .ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
let epoch = state.slot.epoch(spec.slots_per_epoch); let epoch = state.slot.epoch(spec.slots_per_epoch);
verify!( verify!(
from_validator.is_withdrawable_at(epoch) sender_validator.is_withdrawable_at(epoch)
|| from_validator.activation_epoch == spec.far_future_epoch, || sender_validator.activation_epoch == spec.far_future_epoch,
Invalid::FromValidatorIneligableForTransfer(transfer.from) Invalid::FromValidatorIneligableForTransfer(transfer.sender)
); );
let transfer_withdrawal_credentials = Hash256::from_slice( let transfer_withdrawal_credentials = Hash256::from_slice(
&get_withdrawal_credentials(&transfer.pubkey, spec.bls_withdrawal_prefix_byte)[..], &get_withdrawal_credentials(&transfer.pubkey, spec.bls_withdrawal_prefix_byte)[..],
); );
verify!( verify!(
from_validator.withdrawal_credentials == transfer_withdrawal_credentials, sender_validator.withdrawal_credentials == transfer_withdrawal_credentials,
Invalid::WithdrawalCredentialsMismatch( Invalid::WithdrawalCredentialsMismatch(
from_validator.withdrawal_credentials, sender_validator.withdrawal_credentials,
transfer_withdrawal_credentials transfer_withdrawal_credentials
) )
); );
@ -97,16 +100,17 @@ pub fn execute_transfer(
transfer: &Transfer, transfer: &Transfer,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<(), Error> { ) -> Result<(), Error> {
let from_balance = *state let sender_balance = *state
.validator_balances .validator_balances
.get(transfer.from as usize) .get(transfer.sender as usize)
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.from)))?; .ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
let to_balance = *state let recipient_balance = *state
.validator_balances .validator_balances
.get(transfer.to as usize) .get(transfer.recipient as usize)
.ok_or_else(|| Error::Invalid(Invalid::ToValidatorUnknown(transfer.to)))?; .ok_or_else(|| Error::Invalid(Invalid::ToValidatorUnknown(transfer.recipient)))?;
let proposer_index = state.get_beacon_proposer_index(state.slot, spec)?; let proposer_index =
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
let proposer_balance = state.validator_balances[proposer_index]; let proposer_balance = state.validator_balances[proposer_index];
let total_amount = transfer let total_amount = transfer
@ -114,14 +118,22 @@ pub fn execute_transfer(
.checked_add(transfer.fee) .checked_add(transfer.fee)
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?; .ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
state.validator_balances[transfer.from as usize] = state.validator_balances[transfer.sender as usize] =
from_balance.checked_sub(total_amount).ok_or_else(|| { sender_balance.checked_sub(total_amount).ok_or_else(|| {
Error::Invalid(Invalid::FromBalanceInsufficient(total_amount, from_balance)) Error::Invalid(Invalid::FromBalanceInsufficient(
total_amount,
sender_balance,
))
})?; })?;
state.validator_balances[transfer.to as usize] = to_balance state.validator_balances[transfer.recipient as usize] = recipient_balance
.checked_add(transfer.amount) .checked_add(transfer.amount)
.ok_or_else(|| Error::Invalid(Invalid::ToBalanceOverflow(to_balance, transfer.amount)))?; .ok_or_else(|| {
Error::Invalid(Invalid::ToBalanceOverflow(
recipient_balance,
transfer.amount,
))
})?;
state.validator_balances[proposer_index] = state.validator_balances[proposer_index] =
proposer_balance.checked_add(transfer.fee).ok_or_else(|| { proposer_balance.checked_add(transfer.fee).ok_or_else(|| {

View File

@ -9,7 +9,7 @@ pub enum Error {
/// Advances a state forward by one slot, performing per-epoch processing if required. /// Advances a state forward by one slot, performing per-epoch processing if required.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn per_slot_processing( pub fn per_slot_processing(
state: &mut BeaconState, state: &mut BeaconState,
previous_block_root: Hash256, previous_block_root: Hash256,
@ -22,29 +22,9 @@ pub fn per_slot_processing(
state.slot += 1; state.slot += 1;
update_block_roots(state, previous_block_root, spec);
Ok(()) Ok(())
} }
/// Updates the state's block roots as per-slot processing is performed.
///
/// Spec v0.4.0
pub fn update_block_roots(state: &mut BeaconState, previous_block_root: Hash256, spec: &ChainSpec) {
state.latest_block_roots[(state.slot.as_usize() - 1) % spec.latest_block_roots_length] =
previous_block_root;
if state.slot.as_usize() % spec.latest_block_roots_length == 0 {
let root = merkle_root(&state.latest_block_roots[..]);
state.batched_block_roots.push(root);
}
}
fn merkle_root(_input: &[Hash256]) -> Hash256 {
// TODO: implement correctly.
Hash256::zero()
}
impl From<BeaconStateError> for Error { impl From<BeaconStateError> for Error {
fn from(e: BeaconStateError) -> Error { fn from(e: BeaconStateError) -> Error {
Error::BeaconStateError(e) Error::BeaconStateError(e)

View File

@ -2,11 +2,11 @@ use self::epoch_cache::EpochCache;
use crate::test_utils::TestRandom; use crate::test_utils::TestRandom;
use crate::{validator_registry::get_active_validator_indices, *}; use crate::{validator_registry::get_active_validator_indices, *};
use int_to_bytes::int_to_bytes32; use int_to_bytes::int_to_bytes32;
use log::{debug, trace}; use log::trace;
use pubkey_cache::PubkeyCache; use pubkey_cache::PubkeyCache;
use rand::RngCore; use rand::RngCore;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use ssz::{hash, SignedRoot, TreeHash}; use ssz::{hash, ssz_encode, SignedRoot, TreeHash};
use ssz_derive::{Decode, Encode, TreeHash}; use ssz_derive::{Decode, Encode, TreeHash};
use std::collections::HashMap; use std::collections::HashMap;
use test_random_derive::TestRandom; use test_random_derive::TestRandom;
@ -31,12 +31,14 @@ pub enum Error {
UnableToShuffle, UnableToShuffle,
UnknownValidator, UnknownValidator,
InvalidBitfield, InvalidBitfield,
ValidatorIsWithdrawable,
InsufficientRandaoMixes, InsufficientRandaoMixes,
InsufficientValidators, InsufficientValidators,
InsufficientBlockRoots, InsufficientBlockRoots,
InsufficientIndexRoots, InsufficientIndexRoots,
InsufficientAttestations, InsufficientAttestations,
InsufficientCommittees, InsufficientCommittees,
InsufficientSlashedBalances,
EpochCacheUninitialized(RelativeEpoch), EpochCacheUninitialized(RelativeEpoch),
PubkeyCacheInconsistent, PubkeyCacheInconsistent,
PubkeyCacheIncomplete { PubkeyCacheIncomplete {
@ -377,10 +379,37 @@ impl BeaconState {
} }
} }
/// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`.
///
/// # Errors:
///
/// See `Self::get_randao_mix`.
///
/// Spec v0.5.0
pub fn update_randao_mix(
&mut self,
epoch: Epoch,
signature: &Signature,
spec: &ChainSpec,
) -> Result<(), Error> {
let i = epoch.as_usize() % spec.latest_randao_mixes_length;
let signature_hash = Hash256::from_slice(&hash(&ssz_encode(signature)));
self.latest_randao_mixes[i] = *self.get_randao_mix(epoch, spec)? ^ signature_hash;
Ok(())
}
/// Return the randao mix at a recent ``epoch``. /// Return the randao mix at a recent ``epoch``.
/// ///
/// Spec v0.4.0 /// # Errors:
pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Option<&Hash256> { /// - `InsufficientRandaoMixes` if `self.latest_randao_mixes` is shorter than
/// `spec.latest_randao_mixes_length`.
/// - `EpochOutOfBounds` if the state no longer stores randao mixes for the given `epoch`.
///
/// Spec v0.5.0
pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Result<&Hash256, Error> {
let current_epoch = self.current_epoch(spec); let current_epoch = self.current_epoch(spec);
if (current_epoch - (spec.latest_randao_mixes_length as u64) < epoch) if (current_epoch - (spec.latest_randao_mixes_length as u64) < epoch)
@ -388,8 +417,9 @@ impl BeaconState {
{ {
self.latest_randao_mixes self.latest_randao_mixes
.get(epoch.as_usize() % spec.latest_randao_mixes_length) .get(epoch.as_usize() % spec.latest_randao_mixes_length)
.ok_or_else(|| Error::InsufficientRandaoMixes)
} else { } else {
None Err(Error::EpochOutOfBounds)
} }
} }
@ -418,8 +448,7 @@ impl BeaconState {
/// Spec v0.4.0 /// Spec v0.4.0
pub fn generate_seed(&self, epoch: Epoch, spec: &ChainSpec) -> Result<Hash256, Error> { pub fn generate_seed(&self, epoch: Epoch, spec: &ChainSpec) -> Result<Hash256, Error> {
let mut input = self let mut input = self
.get_randao_mix(epoch - spec.min_seed_lookahead, spec) .get_randao_mix(epoch - spec.min_seed_lookahead, spec)?
.ok_or_else(|| Error::InsufficientRandaoMixes)?
.as_bytes() .as_bytes()
.to_vec(); .to_vec();
@ -601,7 +630,7 @@ impl BeaconState {
/// Initiate an exit for the validator of the given `index`. /// Initiate an exit for the validator of the given `index`.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn initiate_validator_exit(&mut self, validator_index: usize) { pub fn initiate_validator_exit(&mut self, validator_index: usize) {
self.validator_registry[validator_index].initiated_exit = true; self.validator_registry[validator_index].initiated_exit = true;
} }
@ -622,7 +651,7 @@ impl BeaconState {
/// Slash the validator with index ``index``. /// Slash the validator with index ``index``.
/// ///
/// Spec v0.4.0 /// Spec v0.5.0
pub fn slash_validator( pub fn slash_validator(
&mut self, &mut self,
validator_index: usize, validator_index: usize,
@ -634,26 +663,27 @@ impl BeaconState {
.validator_registry .validator_registry
.get(validator_index) .get(validator_index)
.ok_or_else(|| Error::UnknownValidator)?; .ok_or_else(|| Error::UnknownValidator)?;
let effective_balance = self.get_effective_balance(validator_index, spec)?;
// A validator that is withdrawn cannot be slashed.
//
// This constraint will be lifted in Phase 0.
if self.slot if self.slot
>= validator >= validator
.withdrawable_epoch .withdrawable_epoch
.start_slot(spec.slots_per_epoch) .start_slot(spec.slots_per_epoch)
{ {
return Err(Error::SlotOutOfBounds); return Err(Error::ValidatorIsWithdrawable);
} }
self.exit_validator(validator_index, spec); self.exit_validator(validator_index, spec);
let effective_balance = self.get_effective_balance(validator_index, spec)?; self.increment_current_epoch_slashed_balances(effective_balance, spec)?;
self.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length] +=
effective_balance;
let whistleblower_index = let whistleblower_index =
self.get_beacon_proposer_index(self.slot, RelativeEpoch::Current, spec)?; self.get_beacon_proposer_index(self.slot, RelativeEpoch::Current, spec)?;
let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient;
let whistleblower_reward = effective_balance;
safe_add_assign!( safe_add_assign!(
self.validator_balances[whistleblower_index as usize], self.validator_balances[whistleblower_index as usize],
whistleblower_reward whistleblower_reward
@ -662,14 +692,31 @@ impl BeaconState {
self.validator_balances[validator_index], self.validator_balances[validator_index],
whistleblower_reward whistleblower_reward
); );
self.validator_registry[validator_index].slashed = true; self.validator_registry[validator_index].slashed = true;
self.validator_registry[validator_index].withdrawable_epoch = self.validator_registry[validator_index].withdrawable_epoch =
current_epoch + Epoch::from(spec.latest_slashed_exit_length); current_epoch + Epoch::from(spec.latest_slashed_exit_length);
debug!( Ok(())
"Whistleblower {} penalized validator {}.", }
whistleblower_index, validator_index
); /// Increment `self.latest_slashed_balances` with a slashing from the current epoch.
///
/// Spec v0.5.0.
fn increment_current_epoch_slashed_balances(
&mut self,
increment: u64,
spec: &ChainSpec,
) -> Result<(), Error> {
let current_epoch = self.current_epoch(spec);
let slashed_balances_index = current_epoch.as_usize() % spec.latest_slashed_exit_length;
if slashed_balances_index >= self.latest_slashed_balances.len() {
return Err(Error::InsufficientSlashedBalances);
}
self.latest_slashed_balances[slashed_balances_index] += increment;
Ok(()) Ok(())
} }

View File

@ -29,7 +29,7 @@ pub struct ChainSpec {
pub shard_count: u64, pub shard_count: u64,
pub target_committee_size: u64, pub target_committee_size: u64,
pub max_balance_churn_quotient: u64, pub max_balance_churn_quotient: u64,
pub max_indices_per_slashable_vote: u64, pub max_indices_per_slashable_vote: usize,
pub max_exit_dequeues_per_epoch: u64, pub max_exit_dequeues_per_epoch: u64,
pub shuffle_round_count: u8, pub shuffle_round_count: u8,

View File

@ -11,8 +11,8 @@ use test_random_derive::TestRandom;
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
pub struct ProposerSlashing { pub struct ProposerSlashing {
pub proposer_index: u64, pub proposer_index: u64,
pub proposal_1: BeaconBlockHeader, pub header_1: BeaconBlockHeader,
pub proposal_2: BeaconBlockHeader, pub header_2: BeaconBlockHeader,
} }
#[cfg(test)] #[cfg(test)]

View File

@ -25,7 +25,7 @@ impl TestingProposerSlashingBuilder {
let hash_1 = Hash256::from([1; 32]); let hash_1 = Hash256::from([1; 32]);
let hash_2 = Hash256::from([2; 32]); let hash_2 = Hash256::from([2; 32]);
let mut proposal_1 = BeaconBlockHeader { let mut header_1 = BeaconBlockHeader {
slot, slot,
previous_block_root: hash_1, previous_block_root: hash_1,
state_root: hash_1, state_root: hash_1,
@ -33,27 +33,27 @@ impl TestingProposerSlashingBuilder {
signature: Signature::empty_signature(), signature: Signature::empty_signature(),
}; };
let mut proposal_2 = BeaconBlockHeader { let mut header_2 = BeaconBlockHeader {
previous_block_root: hash_2, previous_block_root: hash_2,
..proposal_1.clone() ..header_1.clone()
}; };
proposal_1.signature = { header_1.signature = {
let message = proposal_1.signed_root(); let message = header_1.signed_root();
let epoch = slot.epoch(spec.slots_per_epoch); let epoch = slot.epoch(spec.slots_per_epoch);
signer(proposer_index, &message[..], epoch, Domain::BeaconBlock) signer(proposer_index, &message[..], epoch, Domain::BeaconBlock)
}; };
proposal_2.signature = { header_2.signature = {
let message = proposal_2.signed_root(); let message = header_2.signed_root();
let epoch = slot.epoch(spec.slots_per_epoch); let epoch = slot.epoch(spec.slots_per_epoch);
signer(proposer_index, &message[..], epoch, Domain::BeaconBlock) signer(proposer_index, &message[..], epoch, Domain::BeaconBlock)
}; };
ProposerSlashing { ProposerSlashing {
proposer_index, proposer_index,
proposal_1, header_1,
proposal_2, header_2,
} }
} }
} }