First compiling version of per-block-proc refactor

This commit is contained in:
Paul Hauner 2019-03-06 15:22:45 +11:00
parent a15ed0acd3
commit 40f74c9b26
No known key found for this signature in database
GPG Key ID: D362883A9218FCC6
20 changed files with 907 additions and 586 deletions

View File

@ -10,7 +10,6 @@ harness = false
[dev-dependencies] [dev-dependencies]
criterion = "0.2" criterion = "0.2"
test_harness = { path = "../../beacon_node/beacon_chain/test_harness" }
env_logger = "0.6.0" env_logger = "0.6.0"
[dependencies] [dependencies]

View File

@ -1,344 +0,0 @@
use self::verify_slashable_attestation::verify_slashable_attestation;
use crate::SlotProcessingError;
use hashing::hash;
use int_to_bytes::int_to_bytes32;
use log::{debug, trace};
use ssz::{ssz_encode, TreeHash};
use types::*;
use validate_attestation::validate_attestations;
pub use validate_attestation::{validate_attestation, validate_attestation_without_signature};
mod validate_attestation;
mod verify_slashable_attestation;
const PHASE_0_CUSTODY_BIT: bool = false;
#[derive(Debug, PartialEq)]
pub enum Error {
DBError(String),
StateAlreadyTransitioned,
PresentSlotIsNone,
UnableToDecodeBlock,
MissingParentState(Hash256),
InvalidParentState(Hash256),
MissingBeaconBlock(Hash256),
InvalidBeaconBlock(Hash256),
MissingParentBlock(Hash256),
StateSlotMismatch,
BadBlockSignature,
BadRandaoSignature,
MaxProposerSlashingsExceeded,
BadProposerSlashing,
MaxAttesterSlashingsExceed,
MaxAttestationsExceeded,
BadAttesterSlashing,
InvalidAttestation(AttestationValidationError),
NoBlockRoot,
MaxDepositsExceeded,
BadDeposit,
MaxExitsExceeded,
BadExit,
BadCustodyReseeds,
BadCustodyChallenges,
BadCustodyResponses,
BeaconStateError(BeaconStateError),
SlotProcessingError(SlotProcessingError),
}
#[derive(Debug, PartialEq)]
pub enum AttestationValidationError {
IncludedTooEarly,
IncludedTooLate,
WrongJustifiedSlot,
WrongJustifiedRoot,
BadLatestCrosslinkRoot,
BadSignature,
ShardBlockRootNotZero,
NoBlockRoot,
BeaconStateError(BeaconStateError),
}
macro_rules! ensure {
($condition: expr, $result: expr) => {
if !$condition {
return Err($result);
}
};
}
pub trait BlockProcessable {
fn per_block_processing(&mut self, block: &BeaconBlock, spec: &ChainSpec) -> Result<(), Error>;
fn per_block_processing_without_verifying_block_signature(
&mut self,
block: &BeaconBlock,
spec: &ChainSpec,
) -> Result<(), Error>;
}
impl BlockProcessable for BeaconState {
fn per_block_processing(&mut self, block: &BeaconBlock, spec: &ChainSpec) -> Result<(), Error> {
per_block_processing_signature_optional(self, block, true, spec)
}
fn per_block_processing_without_verifying_block_signature(
&mut self,
block: &BeaconBlock,
spec: &ChainSpec,
) -> Result<(), Error> {
per_block_processing_signature_optional(self, block, false, spec)
}
}
fn per_block_processing_signature_optional(
mut state: &mut BeaconState,
block: &BeaconBlock,
verify_block_signature: bool,
spec: &ChainSpec,
) -> Result<(), Error> {
ensure!(block.slot == state.slot, Error::StateSlotMismatch);
// Building the previous epoch could be delayed until an attestation from a previous epoch is
// included. This is left for future optimisation.
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
/*
* Proposer Signature
*/
let block_proposer_index = state.get_beacon_proposer_index(block.slot, spec)?;
let block_proposer = &state.validator_registry[block_proposer_index];
if verify_block_signature {
ensure!(
bls_verify(
&block_proposer.pubkey,
&block.proposal_root(spec)[..],
&block.signature,
get_domain(&state.fork, state.current_epoch(spec), spec.domain_proposal)
),
Error::BadBlockSignature
);
}
/*
* RANDAO
*/
ensure!(
bls_verify(
&block_proposer.pubkey,
&int_to_bytes32(state.current_epoch(spec).as_u64()),
&block.randao_reveal,
get_domain(&state.fork, state.current_epoch(spec), spec.domain_randao)
),
Error::BadRandaoSignature
);
// TODO: check this is correct.
let new_mix = {
let mut mix = state.latest_randao_mixes
[state.slot.as_usize() % spec.latest_randao_mixes_length]
.to_vec();
mix.append(&mut ssz_encode(&block.randao_reveal));
Hash256::from(&hash(&mix)[..])
};
state.latest_randao_mixes[state.slot.as_usize() % spec.latest_randao_mixes_length] = new_mix;
/*
* Eth1 data
*/
// TODO: Eth1 data processing.
/*
* Proposer slashings
*/
ensure!(
block.body.proposer_slashings.len() as u64 <= spec.max_proposer_slashings,
Error::MaxProposerSlashingsExceeded
);
for proposer_slashing in &block.body.proposer_slashings {
let proposer = state
.validator_registry
.get(proposer_slashing.proposer_index as usize)
.ok_or(Error::BadProposerSlashing)?;
ensure!(
proposer_slashing.proposal_data_1.slot == proposer_slashing.proposal_data_2.slot,
Error::BadProposerSlashing
);
ensure!(
proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard,
Error::BadProposerSlashing
);
ensure!(
proposer_slashing.proposal_data_1.block_root
!= proposer_slashing.proposal_data_2.block_root,
Error::BadProposerSlashing
);
ensure!(
proposer.penalized_epoch > state.current_epoch(spec),
Error::BadProposerSlashing
);
ensure!(
bls_verify(
&proposer.pubkey,
&proposer_slashing.proposal_data_1.hash_tree_root(),
&proposer_slashing.proposal_signature_1,
get_domain(
&state.fork,
proposer_slashing
.proposal_data_1
.slot
.epoch(spec.slots_per_epoch),
spec.domain_proposal
)
),
Error::BadProposerSlashing
);
ensure!(
bls_verify(
&proposer.pubkey,
&proposer_slashing.proposal_data_2.hash_tree_root(),
&proposer_slashing.proposal_signature_2,
get_domain(
&state.fork,
proposer_slashing
.proposal_data_2
.slot
.epoch(spec.slots_per_epoch),
spec.domain_proposal
)
),
Error::BadProposerSlashing
);
state.penalize_validator(proposer_slashing.proposer_index as usize, spec)?;
}
/*
* Attester slashings
*/
ensure!(
block.body.attester_slashings.len() as u64 <= spec.max_attester_slashings,
Error::MaxAttesterSlashingsExceed
);
for attester_slashing in &block.body.attester_slashings {
verify_slashable_attestation(&mut state, &attester_slashing, spec)?;
}
validate_attestations(&mut state, &block, spec);
// Convert each attestation into a `PendingAttestation` and insert into the state.
for attestation in &block.body.attestations {
let pending_attestation = PendingAttestation {
data: attestation.data.clone(),
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
custody_bitfield: attestation.custody_bitfield.clone(),
inclusion_slot: state.slot,
};
state.latest_attestations.push(pending_attestation);
}
/*
* Deposits
*/
ensure!(
block.body.deposits.len() as u64 <= spec.max_deposits,
Error::MaxDepositsExceeded
);
// TODO: verify deposit merkle branches.
for deposit in &block.body.deposits {
debug!(
"Processing deposit for pubkey {:?}",
deposit.deposit_data.deposit_input.pubkey
);
state
.process_deposit(
deposit.deposit_data.deposit_input.pubkey.clone(),
deposit.deposit_data.amount,
deposit
.deposit_data
.deposit_input
.proof_of_possession
.clone(),
deposit.deposit_data.deposit_input.withdrawal_credentials,
None,
spec,
)
.map_err(|_| Error::BadDeposit)?;
}
/*
* Exits
*/
ensure!(
block.body.exits.len() as u64 <= spec.max_exits,
Error::MaxExitsExceeded
);
for exit in &block.body.exits {
let validator = state
.validator_registry
.get(exit.validator_index as usize)
.ok_or(Error::BadExit)?;
ensure!(
validator.exit_epoch
> state.get_entry_exit_effect_epoch(state.current_epoch(spec), spec),
Error::BadExit
);
ensure!(state.current_epoch(spec) >= exit.epoch, Error::BadExit);
let exit_message = {
let exit_struct = VoluntaryExit {
epoch: exit.epoch,
validator_index: exit.validator_index,
signature: spec.empty_signature.clone(),
};
exit_struct.hash_tree_root()
};
ensure!(
bls_verify(
&validator.pubkey,
&exit_message,
&exit.signature,
get_domain(&state.fork, exit.epoch, spec.domain_exit)
),
Error::BadProposerSlashing
);
state.initiate_validator_exit(exit.validator_index as usize);
}
debug!("State transition complete.");
Ok(())
}
fn get_domain(fork: &Fork, epoch: Epoch, domain_type: u64) -> u64 {
fork.get_domain(epoch, domain_type)
}
fn bls_verify(pubkey: &PublicKey, message: &[u8], signature: &Signature, domain: u64) -> bool {
signature.verify(message, domain, pubkey)
}
impl From<AttestationValidationError> for Error {
fn from(e: AttestationValidationError) -> Error {
Error::InvalidAttestation(e)
}
}
impl From<BeaconStateError> for Error {
fn from(e: BeaconStateError) -> Error {
Error::BeaconStateError(e)
}
}
impl From<SlotProcessingError> for Error {
fn from(e: SlotProcessingError) -> Error {
Error::SlotProcessingError(e)
}
}
impl From<BeaconStateError> for AttestationValidationError {
fn from(e: BeaconStateError) -> AttestationValidationError {
AttestationValidationError::BeaconStateError(e)
}
}

View File

@ -1,61 +0,0 @@
use super::Error;
use types::*;
macro_rules! ensure {
($condition: expr, $result: expr) => {
if !$condition {
return Err($result);
}
};
}
/// Returns `Ok(())` if some `AttesterSlashing` is valid to be included in some `BeaconState`,
/// otherwise returns an `Err`.
pub fn verify_slashable_attestation(
state: &mut BeaconState,
attester_slashing: &AttesterSlashing,
spec: &ChainSpec,
) -> Result<(), Error> {
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
ensure!(
slashable_attestation_1.data != slashable_attestation_2.data,
Error::BadAttesterSlashing
);
ensure!(
slashable_attestation_1.is_double_vote(slashable_attestation_2, spec)
| slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec),
Error::BadAttesterSlashing
);
ensure!(
state.verify_slashable_attestation(&slashable_attestation_1, spec),
Error::BadAttesterSlashing
);
ensure!(
state.verify_slashable_attestation(&slashable_attestation_2, spec),
Error::BadAttesterSlashing
);
let mut slashable_indices = vec![];
for i in &slashable_attestation_1.validator_indices {
let validator = state
.validator_registry
.get(*i as usize)
.ok_or_else(|| Error::BadAttesterSlashing)?;
if slashable_attestation_1.validator_indices.contains(&i)
& !validator.is_penalized_at(state.current_epoch(spec))
{
slashable_indices.push(i);
}
}
ensure!(!slashable_indices.is_empty(), Error::BadAttesterSlashing);
for i in slashable_indices {
state.penalize_validator(*i as usize, spec)?;
}
Ok(())
}

View File

@ -1,9 +1,98 @@
use types::BeaconStateError; use types::BeaconStateError;
macro_rules! impl_from_beacon_state_error {
($type: ident) => {
impl From<BeaconStateError> for $type {
fn from(e: BeaconStateError) -> $type {
$type::BeaconStateError(e)
}
}
};
}
macro_rules! impl_into_with_index_with_beacon_error {
($error_type: ident, $invalid_type: ident) => {
impl IntoWithIndex<BlockProcessingError> for $error_type {
fn into_with_index(self, i: usize) -> BlockProcessingError {
match self {
$error_type::Invalid(e) => {
BlockProcessingError::Invalid(BlockInvalid::$invalid_type(i, e))
}
$error_type::BeaconStateError(e) => BlockProcessingError::BeaconStateError(e),
}
}
}
};
}
macro_rules! impl_into_with_index_without_beacon_error {
($error_type: ident, $invalid_type: ident) => {
impl IntoWithIndex<BlockProcessingError> for $error_type {
fn into_with_index(self, i: usize) -> BlockProcessingError {
match self {
$error_type::Invalid(e) => {
BlockProcessingError::Invalid(BlockInvalid::$invalid_type(i, e))
}
}
}
}
};
}
pub trait IntoWithIndex<T>: Sized {
fn into_with_index(self, i: usize) -> T;
}
/*
* Block Validation
*/
#[derive(Debug, PartialEq)]
pub enum BlockProcessingError {
/// The `BeaconBlock` is invalid.
Invalid(BlockInvalid),
BeaconStateError(BeaconStateError),
}
impl_from_beacon_state_error!(BlockProcessingError);
#[derive(Debug, PartialEq)]
pub enum BlockInvalid {
StateSlotMismatch,
BadSignature,
BadRandaoSignature,
MaxAttestationsExceeded,
MaxAttesterSlashingsExceed,
MaxProposerSlashingsExceeded,
MaxDepositsExceeded,
MaxExitsExceeded,
MaxTransfersExceed,
AttestationInvalid(usize, AttestationInvalid),
AttesterSlashingInvalid(usize, AttesterSlashingInvalid),
ProposerSlashingInvalid(usize, ProposerSlashingInvalid),
DepositInvalid(usize, DepositInvalid),
// TODO: merge this into the `DepositInvalid` error.
DepositProcessingFailed(usize),
ExitInvalid(usize, ExitInvalid),
TransferInvalid(usize, TransferInvalid),
}
impl Into<BlockProcessingError> for BlockInvalid {
fn into(self) -> BlockProcessingError {
BlockProcessingError::Invalid(self)
}
}
/*
* Attestation Validation
*/
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum AttestationValidationError { pub enum AttestationValidationError {
/// The `Attestation` is invalid.
Invalid(AttestationInvalid), Invalid(AttestationInvalid),
ProcessingError(BeaconStateError), /// Encountered a `BeaconStateError` whilst attempting to determine validity.
BeaconStateError(BeaconStateError),
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -23,8 +112,132 @@ pub enum AttestationInvalid {
ShardBlockRootNotZero, ShardBlockRootNotZero,
} }
impl From<BeaconStateError> for AttestationValidationError { impl_from_beacon_state_error!(AttestationValidationError);
fn from(e: BeaconStateError) -> AttestationValidationError { impl_into_with_index_with_beacon_error!(AttestationValidationError, AttestationInvalid);
AttestationValidationError::ProcessingError(e)
/*
* `AttesterSlashing` Validation
*/
#[derive(Debug, PartialEq)]
pub enum AttesterSlashingValidationError {
/// The `SlashableAttestation` is invalid.
Invalid(AttesterSlashingInvalid),
/// Encountered a `BeaconStateError` whilst attempting to determine validity.
BeaconStateError(BeaconStateError),
}
#[derive(Debug, PartialEq)]
pub enum AttesterSlashingInvalid {
AttestationDataIdentical,
NotSlashable,
SlashableAttestation1Invalid(SlashableAttestationInvalid),
SlashableAttestation2Invalid(SlashableAttestationInvalid),
UnknownValidator,
NoSlashableIndices,
}
impl_from_beacon_state_error!(AttesterSlashingValidationError);
impl_into_with_index_with_beacon_error!(AttesterSlashingValidationError, AttesterSlashingInvalid);
/*
* `SlashableAttestation` Validation
*/
#[derive(Debug, PartialEq)]
pub enum SlashableAttestationValidationError {
Invalid(SlashableAttestationInvalid),
}
#[derive(Debug, PartialEq)]
pub enum SlashableAttestationInvalid {
CustodyBitfieldHasSetBits,
NoValidatorIndices,
BadValidatorIndicesOrdering,
BadCustodyBitfieldLength,
MaxIndicesExceed,
UnknownValidator,
BadSignature,
}
impl Into<SlashableAttestationInvalid> for SlashableAttestationValidationError {
fn into(self) -> SlashableAttestationInvalid {
match self {
SlashableAttestationValidationError::Invalid(e) => e,
}
} }
} }
/*
* `ProposerSlashing` Validation
*/
#[derive(Debug, PartialEq)]
pub enum ProposerSlashingValidationError {
Invalid(ProposerSlashingInvalid),
}
#[derive(Debug, PartialEq)]
pub enum ProposerSlashingInvalid {
ProposerUnknown,
ProposalSlotMismatch,
ProposalShardMismatch,
ProposalBlockRootMismatch,
ProposerAlreadySlashed,
BadProposal1Signature,
BadProposal2Signature,
}
impl_into_with_index_without_beacon_error!(
ProposerSlashingValidationError,
ProposerSlashingInvalid
);
/*
* `Deposit` Validation
*/
#[derive(Debug, PartialEq)]
pub enum DepositValidationError {
Invalid(DepositInvalid),
}
#[derive(Debug, PartialEq)]
pub enum DepositInvalid {
BadIndex,
}
impl_into_with_index_without_beacon_error!(DepositValidationError, DepositInvalid);
/*
* `Exit` Validation
*/
#[derive(Debug, PartialEq)]
pub enum ExitValidationError {
Invalid(ExitInvalid),
}
#[derive(Debug, PartialEq)]
pub enum ExitInvalid {
ValidatorUnknown,
AlreadyExited,
FutureEpoch,
BadSignature,
}
impl_into_with_index_without_beacon_error!(ExitValidationError, ExitInvalid);
/*
* `Transfer` Validation
*/
#[derive(Debug, PartialEq)]
pub enum TransferValidationError {
Invalid(TransferInvalid),
}
#[derive(Debug, PartialEq)]
pub enum TransferInvalid {}
impl_into_with_index_without_beacon_error!(TransferValidationError, TransferInvalid);

View File

@ -1,13 +1,13 @@
#[macro_use] #[macro_use]
mod macros; mod macros;
mod block_processable; pub mod per_block_processing;
// mod epoch_processable; // mod epoch_processable;
mod errors; pub mod errors;
// mod slot_processable; // mod slot_processable;
pub use block_processable::{ pub use errors::{BlockInvalid, BlockProcessingError};
validate_attestation, validate_attestation_without_signature, BlockProcessable, pub use per_block_processing::{
Error as BlockProcessingError, per_block_processing, per_block_processing_without_verifying_block_signature,
}; };
// pub use epoch_processable::{EpochProcessable, Error as EpochProcessingError}; // pub use epoch_processable::{EpochProcessable, Error as EpochProcessingError};
// pub use slot_processable::{Error as SlotProcessingError, SlotProcessable}; // pub use slot_processable::{Error as SlotProcessingError, SlotProcessable};

View File

@ -1,8 +1,13 @@
#[macro_use] macro_rules! verify {
macro_rules! ensure { ($condition: expr, $result: expr) => {
($condition: expr, $result: ident) => {
if !$condition { if !$condition {
return Err(Error::Invalid(Invalid::$result)); return Err(Error::Invalid($result));
} }
}; };
} }
macro_rules! invalid {
($result: expr) => {
return Err(Error::Invalid($result));
};
}

View File

@ -0,0 +1,268 @@
use self::verify_proposer_slashing::verify_proposer_slashing;
use crate::errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex};
use hashing::hash;
use log::debug;
use ssz::{ssz_encode, SignedRoot, TreeHash};
use types::*;
pub use self::verify_attester_slashing::verify_attester_slashing;
pub use validate_attestation::{validate_attestation, validate_attestation_without_signature};
pub use verify_deposit::verify_deposit;
pub use verify_exit::verify_exit;
pub use verify_transfer::verify_transfer;
mod validate_attestation;
mod verify_attester_slashing;
mod verify_deposit;
mod verify_exit;
mod verify_proposer_slashing;
mod verify_slashable_attestation;
mod verify_transfer;
pub fn per_block_processing(
state: &mut BeaconState,
block: &BeaconBlock,
spec: &ChainSpec,
) -> Result<(), Error> {
per_block_processing_signature_optional(state, block, true, spec)
}
pub fn per_block_processing_without_verifying_block_signature(
state: &mut BeaconState,
block: &BeaconBlock,
spec: &ChainSpec,
) -> Result<(), Error> {
per_block_processing_signature_optional(state, block, false, spec)
}
fn per_block_processing_signature_optional(
mut state: &mut BeaconState,
block: &BeaconBlock,
should_verify_block_signature: bool,
spec: &ChainSpec,
) -> Result<(), Error> {
// Verify that `block.slot == state.slot`.
verify!(block.slot == state.slot, Invalid::StateSlotMismatch);
// Get the epoch for future ergonomics.
let epoch = block.slot.epoch(spec.slots_per_epoch);
// Ensure the current epoch cache is built.
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
// Let `proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]`.
let block_proposer =
&state.validator_registry[state.get_beacon_proposer_index(block.slot, spec)?];
// Block signature
if should_verify_block_signature {
verify!(
verify_block_signature(&block, &block_proposer, &state.fork, spec,),
Invalid::BadSignature
);
}
// Randao
// Verify that `bls_verify(pubkey=proposer.pubkey,
// 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!(
block.randao_reveal.verify(
&state.current_epoch(spec).hash_tree_root()[..],
spec.get_domain(epoch, Domain::Randao, &state.fork),
&block_proposer.pubkey
),
Invalid::BadRandaoSignature
);
// Update the state's RANDAO mix with the one revealed in the block.
update_randao(&mut state, &block.randao_reveal, spec)?;
// Eth1 Data
// Either increment the eth1_data vote count, or add a new eth1_data.
let matching_eth1_vote_index = state
.eth1_data_votes
.iter()
.position(|vote| vote.eth1_data == block.eth1_data);
if let Some(index) = matching_eth1_vote_index {
state.eth1_data_votes[index].vote_count += 1;
} else {
state.eth1_data_votes.push(Eth1DataVote {
eth1_data: block.eth1_data.clone(),
vote_count: 1,
});
}
//Proposer slashings
verify!(
block.body.proposer_slashings.len() as u64 <= spec.max_proposer_slashings,
Invalid::MaxProposerSlashingsExceeded
);
for (i, proposer_slashing) in block.body.proposer_slashings.iter().enumerate() {
verify_proposer_slashing(proposer_slashing, &state, spec)
.map_err(|e| e.into_with_index(i))?;
state.slash_validator(proposer_slashing.proposer_index as usize, spec)?;
}
// Attester Slashings
verify!(
block.body.attester_slashings.len() as u64 <= spec.max_attester_slashings,
Invalid::MaxAttesterSlashingsExceed
);
for (i, attester_slashing) in block.body.attester_slashings.iter().enumerate() {
let slashable_indices = verify_attester_slashing(&state, &attester_slashing, spec)
.map_err(|e| e.into_with_index(i))?;
for i in slashable_indices {
state.slash_validator(i as usize, spec)?;
}
}
// Attestations
verify!(
block.body.attestations.len() as u64 <= spec.max_attestations,
Invalid::MaxAttestationsExceeded
);
for (i, attestation) in block.body.attestations.iter().enumerate() {
// Build the previous epoch cache only if required by an attestation.
if attestation.data.slot.epoch(spec.slots_per_epoch) == state.previous_epoch(spec) {
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
}
validate_attestation(&mut state, attestation, spec).map_err(|e| e.into_with_index(i))?;
let pending_attestation = PendingAttestation {
data: attestation.data.clone(),
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
custody_bitfield: attestation.custody_bitfield.clone(),
inclusion_slot: state.slot,
};
state.latest_attestations.push(pending_attestation);
}
// Deposits
verify!(
block.body.deposits.len() as u64 <= spec.max_deposits,
Invalid::MaxDepositsExceeded
);
for (i, deposit) in block.body.deposits.iter().enumerate() {
verify_deposit(&mut state, deposit, spec).map_err(|e| e.into_with_index(i))?;
state
.process_deposit(
deposit.deposit_data.deposit_input.pubkey.clone(),
deposit.deposit_data.amount,
deposit
.deposit_data
.deposit_input
.proof_of_possession
.clone(),
deposit.deposit_data.deposit_input.withdrawal_credentials,
None,
spec,
)
.map_err(|_| Error::Invalid(Invalid::DepositProcessingFailed(i)))?;
state.deposit_index += 1;
}
// Exits
verify!(
block.body.voluntary_exits.len() as u64 <= spec.max_voluntary_exits,
Invalid::MaxExitsExceeded
);
for (i, exit) in block.body.voluntary_exits.iter().enumerate() {
verify_exit(&state, exit, spec).map_err(|e| e.into_with_index(i))?;
state.initiate_validator_exit(exit.validator_index as usize);
}
// Transfers
verify!(
block.body.transfers.len() as u64 <= spec.max_transfers,
Invalid::MaxTransfersExceed
);
for (i, transfer) in block.body.transfers.iter().enumerate() {
verify_transfer(&state, transfer, spec).map_err(|e| e.into_with_index(i))?;
let block_proposer = state.get_beacon_proposer_index(state.slot, spec)?;
state.validator_balances[transfer.from as usize] -= transfer.amount + transfer.fee;
state.validator_balances[transfer.to as usize] += transfer.amount + transfer.fee;
state.validator_balances[block_proposer as usize] += transfer.fee;
}
debug!("State transition complete.");
Ok(())
}
/// Verifies the signature of a block.
///
/// Spec v0.4.0
pub fn verify_block_signature(
block: &BeaconBlock,
block_proposer: &Validator,
fork: &Fork,
spec: &ChainSpec,
) -> bool {
// Let proposal = `Proposal(block.slot, BEACON_CHAIN_SHARD_NUMBER, signed_root(block,
// "signature"), block.signature)`.
let proposal = Proposal {
slot: block.slot,
shard: spec.beacon_chain_shard_number,
block_root: Hash256::from(&block.signed_root()[..]),
signature: block.signature.clone(),
};
let domain = spec.get_domain(
block.slot.epoch(spec.slots_per_epoch),
Domain::Proposal,
fork,
);
// Verify that `bls_verify(pubkey=proposer.pubkey, message_hash=signed_root(proposal,
// "signature"), signature=proposal.signature, domain=get_domain(state.fork,
// get_current_epoch(state), DOMAIN_PROPOSAL))`.
proposal
.signature
.verify(&proposal.signed_root()[..], domain, &block_proposer.pubkey)
}
/// 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(&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)
}
}

View File

@ -3,27 +3,6 @@ use ssz::TreeHash;
use types::beacon_state::helpers::*; use types::beacon_state::helpers::*;
use types::*; use types::*;
/// Validate the attestations in some block, converting each into a `PendingAttestation` which is
/// then added to `state.latest_attestations`.
///
/// Spec v0.4.0
pub fn validate_attestations(
state: &BeaconState,
block: &BeaconBlock,
spec: &ChainSpec,
) -> Result<(), Error> {
ensure!(
block.body.attestations.len() as u64 <= spec.max_attestations,
MaxAttestationsExceeded
);
for attestation in &block.body.attestations {
validate_attestation(&state, attestation, spec)?;
}
Ok(())
}
/// Validate an attestation, checking the aggregate signature. /// Validate an attestation, checking the aggregate signature.
/// ///
/// Spec v0.4.0 /// Spec v0.4.0
@ -48,7 +27,7 @@ pub fn validate_attestation_without_signature(
/// Validate an attestation, optionally checking the aggregate signature. /// Validate an attestation, optionally checking the aggregate signature.
/// ///
/// Spec v0.2.0 /// Spec v0.4.0
fn validate_attestation_signature_optional( fn validate_attestation_signature_optional(
state: &BeaconState, state: &BeaconState,
attestation: &Attestation, attestation: &Attestation,
@ -56,38 +35,41 @@ fn validate_attestation_signature_optional(
verify_signature: bool, verify_signature: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
// Verify that `attestation.data.slot >= GENESIS_SLOT`. // Verify that `attestation.data.slot >= GENESIS_SLOT`.
ensure!(attestation.data.slot >= spec.genesis_slot, PreGenesis); verify!(
attestation.data.slot >= spec.genesis_slot,
Invalid::PreGenesis
);
// Verify that `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot`. // Verify that `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot`.
ensure!( verify!(
attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot, attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
IncludedTooEarly Invalid::IncludedTooEarly
); );
// Verify that `state.slot < attestation.data.slot + SLOTS_PER_EPOCH`. // Verify that `state.slot < attestation.data.slot + SLOTS_PER_EPOCH`.
ensure!( verify!(
state.slot < attestation.data.slot + spec.slots_per_epoch, state.slot < attestation.data.slot + spec.slots_per_epoch,
IncludedTooLate Invalid::IncludedTooLate
); );
// Verify that `attestation.data.justified_epoch` is equal to `state.justified_epoch` if // 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 // `slot_to_epoch(attestation.data.slot + 1) >= get_current_epoch(state) else
// state.previous_justified_epoch`. // state.previous_justified_epoch`.
if (attestation.data.slot + 1).epoch(spec.slots_per_epoch) >= state.current_epoch(spec) { if (attestation.data.slot + 1).epoch(spec.slots_per_epoch) >= state.current_epoch(spec) {
ensure!( verify!(
attestation.data.justified_epoch == state.justified_epoch, attestation.data.justified_epoch == state.justified_epoch,
WrongJustifiedSlot Invalid::WrongJustifiedSlot
); );
} else { } else {
ensure!( verify!(
attestation.data.justified_epoch == state.previous_justified_epoch, attestation.data.justified_epoch == state.previous_justified_epoch,
WrongJustifiedSlot Invalid::WrongJustifiedSlot
); );
} }
// Verify that `attestation.data.justified_block_root` is equal to `get_block_root(state, // Verify that `attestation.data.justified_block_root` is equal to `get_block_root(state,
// get_epoch_start_slot(attestation.data.justified_epoch))`. // get_epoch_start_slot(attestation.data.justified_epoch))`.
ensure!( verify!(
attestation.data.justified_block_root attestation.data.justified_block_root
== *state == *state
.get_block_root( .get_block_root(
@ -98,7 +80,7 @@ fn validate_attestation_signature_optional(
&spec &spec
) )
.ok_or(BeaconStateError::InsufficientBlockRoots)?, .ok_or(BeaconStateError::InsufficientBlockRoots)?,
WrongJustifiedRoot Invalid::WrongJustifiedRoot
); );
// Verify that either: // Verify that either:
@ -112,59 +94,61 @@ fn validate_attestation_signature_optional(
crosslink_data_root: attestation.data.crosslink_data_root, crosslink_data_root: attestation.data.crosslink_data_root,
epoch: attestation.data.slot.epoch(spec.slots_per_epoch), epoch: attestation.data.slot.epoch(spec.slots_per_epoch),
}; };
ensure!( verify!(
(attestation.data.latest_crosslink (attestation.data.latest_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),
BadLatestCrosslinkRoot Invalid::BadLatestCrosslinkRoot
); );
// Get the committee for this attestation // Get the committee for this attestation
let (committee, _shard) = state let (committee, _shard) = state
.get_crosslink_committees_at_slot(attestation.data.slot, spec)? .get_crosslink_committees_at_slot(attestation.data.slot, spec)?
.iter() .iter()
.find(|(committee, shard)| *shard == attestation.data.shard) .find(|(_committee, shard)| *shard == attestation.data.shard)
.ok_or_else(|| Error::Invalid(Invalid::NoCommitteeForShard))?; .ok_or_else(|| Error::Invalid(Invalid::NoCommitteeForShard))?;
// Custody bitfield is all zeros (phase 0 requirement). // Custody bitfield is all zeros (phase 0 requirement).
ensure!( verify!(
attestation.custody_bitfield.num_set_bits() == 0, attestation.custody_bitfield.num_set_bits() == 0,
CustodyBitfieldHasSetBits Invalid::CustodyBitfieldHasSetBits
); );
// Custody bitfield length is correct. // Custody bitfield length is correct.
ensure!( verify!(
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()), verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
BadCustodyBitfieldLength Invalid::BadCustodyBitfieldLength
); );
// Aggregation bitfield isn't empty. // Aggregation bitfield isn't empty.
ensure!( verify!(
attestation.aggregation_bitfield.num_set_bits() != 0, attestation.aggregation_bitfield.num_set_bits() != 0,
AggregationBitfieldIsEmpty Invalid::AggregationBitfieldIsEmpty
); );
// Aggregation bitfield length is correct. // Aggregation bitfield length is correct.
ensure!( verify!(
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()), verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
BadAggregationBitfieldLength Invalid::BadAggregationBitfieldLength
); );
if verify_signature { if verify_signature {
ensure!( let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
verify!(
verify_attestation_signature( verify_attestation_signature(
state, state,
committee, committee,
attestation_epoch,
&attestation.custody_bitfield, &attestation.custody_bitfield,
&attestation.data, &attestation.data,
&attestation.aggregate_signature, &attestation.aggregate_signature,
spec spec
), ),
BadSignature Invalid::BadSignature
); );
} }
// [TO BE REMOVED IN PHASE 1] Verify that `attestation.data.crosslink_data_root == ZERO_HASH`. // [TO BE REMOVED IN PHASE 1] Verify that `attestation.data.crosslink_data_root == ZERO_HASH`.
ensure!( verify!(
attestation.data.crosslink_data_root == spec.zero_hash, attestation.data.crosslink_data_root == spec.zero_hash,
ShardBlockRootNotZero Invalid::ShardBlockRootNotZero
); );
Ok(()) Ok(())
@ -177,9 +161,12 @@ fn validate_attestation_signature_optional(
/// - `aggregate_signature` was not signed correctly. /// - `aggregate_signature` was not signed correctly.
/// - `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
fn verify_attestation_signature( fn verify_attestation_signature(
state: &BeaconState, state: &BeaconState,
committee: &[usize], committee: &[usize],
attestation_epoch: Epoch,
custody_bitfield: &Bitfield, custody_bitfield: &Bitfield,
attestation_data: &AttestationData, attestation_data: &AttestationData,
aggregate_signature: &AggregateSignature, aggregate_signature: &AggregateSignature,
@ -234,5 +221,7 @@ fn verify_attestation_signature(
keys.push(&aggregate_pubs[1]); keys.push(&aggregate_pubs[1]);
} }
aggregate_signature.verify_multiple(&messages[..], spec.domain_attestation, &keys[..]) let domain = spec.get_domain(attestation_epoch, Domain::Attestation, &state.fork);
aggregate_signature.verify_multiple(&messages[..], domain, &keys[..])
} }

View File

@ -0,0 +1,49 @@
use super::verify_slashable_attestation::verify_slashable_attestation;
use crate::errors::{AttesterSlashingInvalid as Invalid, AttesterSlashingValidationError as Error};
use types::*;
/// Returns `Ok(())` if some `AttesterSlashing` is valid to be included in some `BeaconState`,
/// otherwise returns an `Err`.
///
/// Returns the slashable indices from the `AttesterSlashing`.
///
/// Spec v0.4.0
pub fn verify_attester_slashing(
state: &BeaconState,
attester_slashing: &AttesterSlashing,
spec: &ChainSpec,
) -> Result<Vec<u64>, Error> {
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
verify!(
slashable_attestation_1.data != slashable_attestation_2.data,
Invalid::AttestationDataIdentical
);
verify!(
slashable_attestation_1.is_double_vote(slashable_attestation_2, spec)
| slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec),
Invalid::NotSlashable
);
verify_slashable_attestation(state, &slashable_attestation_1, spec)
.map_err(|e| Error::Invalid(Invalid::SlashableAttestation1Invalid(e.into())))?;
verify_slashable_attestation(state, &slashable_attestation_2, spec)
.map_err(|e| Error::Invalid(Invalid::SlashableAttestation2Invalid(e.into())))?;
let mut slashable_indices = vec![];
for i in &slashable_attestation_1.validator_indices {
let validator = state
.validator_registry
.get(*i as usize)
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator))?;
if slashable_attestation_1.validator_indices.contains(&i) & !validator.slashed {
slashable_indices.push(*i);
}
}
verify!(!slashable_indices.is_empty(), Invalid::NoSlashableIndices);
Ok(slashable_indices)
}

View File

@ -0,0 +1,24 @@
use crate::errors::{DepositInvalid as Invalid, DepositValidationError as Error};
use ssz::TreeHash;
use types::beacon_state::helpers::verify_bitfield_length;
use types::*;
/// Verify validity of ``slashable_attestation`` fields.
///
/// Returns `Ok(())` if all fields are valid.
///
/// Spec v0.4.0
pub fn verify_deposit(
state: &BeaconState,
deposit: &Deposit,
spec: &ChainSpec,
) -> Result<(), Error> {
// TODO: verify serialized deposit data.
// TODO: verify deposit index.
verify!(deposit.index == state.deposit_index, Invalid::BadIndex);
// TODO: verify merkle branch.
Ok(())
}

View File

@ -0,0 +1,41 @@
use crate::errors::{ExitInvalid as Invalid, ExitValidationError as Error};
use ssz::SignedRoot;
use types::*;
/// Verify validity of ``slashable_attestation`` fields.
///
/// Returns `Ok(())` if all fields are valid.
///
/// Spec v0.4.0
pub fn verify_exit(
state: &BeaconState,
exit: &VoluntaryExit,
spec: &ChainSpec,
) -> Result<(), Error> {
let validator = state
.validator_registry
.get(exit.validator_index as usize)
.ok_or(Error::Invalid(Invalid::ValidatorUnknown))?;
verify!(
validator.exit_epoch
> state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec),
Invalid::AlreadyExited
);
verify!(
state.current_epoch(spec) >= exit.epoch,
Invalid::FutureEpoch
);
let message = exit.signed_root();
let domain = spec.get_domain(exit.epoch, Domain::Exit, &state.fork);
verify!(
exit.signature
.verify(&message[..], domain, &validator.pubkey),
Invalid::BadSignature
);
Ok(())
}

View File

@ -0,0 +1,71 @@
use crate::errors::{ProposerSlashingInvalid as Invalid, ProposerSlashingValidationError as Error};
use ssz::SignedRoot;
use types::*;
/// Returns `Ok(())` if some `ProposerSlashing` is valid to be included in some `BeaconState`,
/// otherwise returns an `Err`.
///
/// Spec v0.4.0
pub fn verify_proposer_slashing(
proposer_slashing: &ProposerSlashing,
state: &BeaconState,
spec: &ChainSpec,
) -> Result<(), Error> {
let proposer = state
.validator_registry
.get(proposer_slashing.proposer_index as usize)
.ok_or(Error::Invalid(Invalid::ProposerUnknown))?;
verify!(
proposer_slashing.proposal_1.slot == proposer_slashing.proposal_2.slot,
Invalid::ProposalSlotMismatch
);
verify!(
proposer_slashing.proposal_1.shard == proposer_slashing.proposal_2.shard,
Invalid::ProposalShardMismatch
);
verify!(
proposer_slashing.proposal_1.block_root != proposer_slashing.proposal_2.block_root,
Invalid::ProposalBlockRootMismatch
);
verify!(!proposer.slashed, Invalid::ProposerAlreadySlashed);
verify!(
verify_proposal_signature(
&proposer_slashing.proposal_1,
&proposer.pubkey,
&state.fork,
spec
),
Invalid::BadProposal1Signature
);
verify!(
verify_proposal_signature(
&proposer_slashing.proposal_2,
&proposer.pubkey,
&state.fork,
spec
),
Invalid::BadProposal2Signature
);
Ok(())
}
fn verify_proposal_signature(
proposal: &Proposal,
pubkey: &PublicKey,
fork: &Fork,
spec: &ChainSpec,
) -> bool {
let message = proposal.signed_root();
let domain = spec.get_domain(
proposal.slot.epoch(spec.slots_per_epoch),
Domain::Proposal,
fork,
);
proposal.signature.verify(&message[..], domain, pubkey)
}

View File

@ -0,0 +1,105 @@
use crate::errors::{
SlashableAttestationInvalid as Invalid, SlashableAttestationValidationError as Error,
};
use ssz::TreeHash;
use types::beacon_state::helpers::verify_bitfield_length;
use types::*;
/// Verify validity of ``slashable_attestation`` fields.
///
/// Returns `Ok(())` if all fields are valid.
///
/// Spec v0.4.0
pub fn verify_slashable_attestation(
state: &BeaconState,
slashable_attestation: &SlashableAttestation,
spec: &ChainSpec,
) -> Result<(), Error> {
if slashable_attestation.custody_bitfield.num_set_bits() > 0 {
invalid!(Invalid::CustodyBitfieldHasSetBits);
}
if slashable_attestation.validator_indices.is_empty() {
invalid!(Invalid::NoValidatorIndices);
}
for i in 0..(slashable_attestation.validator_indices.len() - 1) {
if slashable_attestation.validator_indices[i]
>= slashable_attestation.validator_indices[i + 1]
{
invalid!(Invalid::BadValidatorIndicesOrdering);
}
}
if !verify_bitfield_length(
&slashable_attestation.custody_bitfield,
slashable_attestation.validator_indices.len(),
) {
invalid!(Invalid::BadCustodyBitfieldLength);
}
if slashable_attestation.validator_indices.len() > spec.max_indices_per_slashable_vote as usize
{
invalid!(Invalid::MaxIndicesExceed);
}
// TODO: this signature verification could likely be replaced with:
//
// super::validate_attestation::validate_attestation_signature(..)
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
let mut message_exists = vec![false; 2];
for (i, v) in slashable_attestation.validator_indices.iter().enumerate() {
let custody_bit = match slashable_attestation.custody_bitfield.get(i) {
Ok(bit) => bit,
Err(_) => unreachable!(),
};
message_exists[custody_bit as usize] = true;
match state.validator_registry.get(*v as usize) {
Some(validator) => {
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
}
None => invalid!(Invalid::UnknownValidator),
};
}
let message_0 = AttestationDataAndCustodyBit {
data: slashable_attestation.data.clone(),
custody_bit: false,
}
.hash_tree_root();
let message_1 = AttestationDataAndCustodyBit {
data: slashable_attestation.data.clone(),
custody_bit: true,
}
.hash_tree_root();
let mut messages = vec![];
let mut keys = vec![];
if message_exists[0] {
messages.push(&message_0[..]);
keys.push(&aggregate_pubs[0]);
}
if message_exists[1] {
messages.push(&message_1[..]);
keys.push(&aggregate_pubs[1]);
}
let domain = {
let epoch = slashable_attestation.data.slot.epoch(spec.slots_per_epoch);
spec.get_domain(epoch, Domain::Attestation, &state.fork)
};
verify!(
slashable_attestation
.aggregate_signature
.verify_multiple(&messages[..], domain, &keys[..]),
Invalid::BadSignature
);
Ok(())
}

View File

@ -0,0 +1,19 @@
use crate::errors::{TransferInvalid as Invalid, TransferValidationError as Error};
use ssz::TreeHash;
use types::beacon_state::helpers::verify_bitfield_length;
use types::*;
/// Verify validity of ``slashable_attestation`` fields.
///
/// Returns `Ok(())` if all fields are valid.
///
/// Spec v0.4.0
pub fn verify_transfer(
state: &BeaconState,
transfer: &Transfer,
spec: &ChainSpec,
) -> Result<(), Error> {
// TODO: verify transfer.
Ok(())
}

View File

@ -12,7 +12,7 @@ impl AttesterSlashingBuilder {
/// - `validator_index: u64` /// - `validator_index: u64`
/// - `message: &[u8]` /// - `message: &[u8]`
/// - `epoch: Epoch` /// - `epoch: Epoch`
/// - `domain: u64` /// - `domain: Domain`
/// ///
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`). /// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
pub fn double_vote<F>( pub fn double_vote<F>(
@ -21,7 +21,7 @@ impl AttesterSlashingBuilder {
spec: &ChainSpec, spec: &ChainSpec,
) -> AttesterSlashing ) -> AttesterSlashing
where where
F: Fn(u64, &[u8], Epoch, u64) -> Signature, F: Fn(u64, &[u8], Epoch, Domain) -> Signature,
{ {
let double_voted_slot = Slot::new(0); let double_voted_slot = Slot::new(0);
let shard = 0; let shard = 0;
@ -75,12 +75,7 @@ impl AttesterSlashingBuilder {
custody_bit: attestation.custody_bitfield.get(i).unwrap(), custody_bit: attestation.custody_bitfield.get(i).unwrap(),
}; };
let message = attestation_data_and_custody_bit.hash_tree_root(); let message = attestation_data_and_custody_bit.hash_tree_root();
let signature = signer( let signature = signer(*validator_index, &message[..], epoch, Domain::Attestation);
*validator_index,
&message[..],
epoch,
spec.domain_attestation,
);
attestation.aggregate_signature.add(&signature); attestation.aggregate_signature.add(&signature);
} }
}; };

View File

@ -112,6 +112,7 @@ pub struct BeaconState {
// Ethereum 1.0 chain data // Ethereum 1.0 chain data
pub latest_eth1_data: Eth1Data, pub latest_eth1_data: Eth1Data,
pub eth1_data_votes: Vec<Eth1DataVote>, pub eth1_data_votes: Vec<Eth1DataVote>,
pub deposit_index: u64,
// Caching (not in the spec) // Caching (not in the spec)
pub cache_index_offset: usize, pub cache_index_offset: usize,
@ -187,6 +188,7 @@ impl BeaconState {
*/ */
latest_eth1_data, latest_eth1_data,
eth1_data_votes: vec![], eth1_data_votes: vec![],
deposit_index: 0,
/* /*
* Caching (not in spec) * Caching (not in spec)
@ -224,6 +226,8 @@ impl BeaconState {
} }
} }
genesis_state.deposit_index = initial_validator_deposits.len() as u64;
let genesis_active_index_root = hash_tree_root(get_active_validator_indices( let genesis_active_index_root = hash_tree_root(get_active_validator_indices(
&genesis_state.validator_registry, &genesis_state.validator_registry,
spec.genesis_epoch, spec.genesis_epoch,
@ -566,92 +570,6 @@ impl BeaconState {
.fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)) .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec))
} }
/// Verify validity of ``slashable_attestation`` fields.
///
/// Spec v0.4.0
pub fn verify_slashable_attestation(
&self,
slashable_attestation: &SlashableAttestation,
spec: &ChainSpec,
) -> bool {
if slashable_attestation.custody_bitfield.num_set_bits() > 0 {
return false;
}
if slashable_attestation.validator_indices.is_empty() {
return false;
}
for i in 0..(slashable_attestation.validator_indices.len() - 1) {
if slashable_attestation.validator_indices[i]
>= slashable_attestation.validator_indices[i + 1]
{
return false;
}
}
if !verify_bitfield_length(
&slashable_attestation.custody_bitfield,
slashable_attestation.validator_indices.len(),
) {
return false;
}
if slashable_attestation.validator_indices.len()
> spec.max_indices_per_slashable_vote as usize
{
return false;
}
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
let mut message_exists = vec![false; 2];
for (i, v) in slashable_attestation.validator_indices.iter().enumerate() {
let custody_bit = match slashable_attestation.custody_bitfield.get(i) {
Ok(bit) => bit,
Err(_) => unreachable!(),
};
message_exists[custody_bit as usize] = true;
match self.validator_registry.get(*v as usize) {
Some(validator) => {
aggregate_pubs[custody_bit as usize].add(&validator.pubkey);
}
None => return false,
};
}
let message_0 = AttestationDataAndCustodyBit {
data: slashable_attestation.data.clone(),
custody_bit: false,
}
.hash_tree_root();
let message_1 = AttestationDataAndCustodyBit {
data: slashable_attestation.data.clone(),
custody_bit: true,
}
.hash_tree_root();
let mut messages = vec![];
let mut keys = vec![];
if message_exists[0] {
messages.push(&message_0[..]);
keys.push(&aggregate_pubs[0]);
}
if message_exists[1] {
messages.push(&message_1[..]);
keys.push(&aggregate_pubs[1]);
}
slashable_attestation.aggregate_signature.verify_multiple(
&messages[..],
spec.domain_attestation,
&keys[..],
)
}
/// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect. /// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect.
/// ///
/// Spec v0.4.0 /// Spec v0.4.0
@ -1163,8 +1081,11 @@ impl BeaconState {
proof_of_possession.verify( proof_of_possession.verify(
&proof_of_possession_data.hash_tree_root(), &proof_of_possession_data.hash_tree_root(),
self.fork spec.get_domain(
.get_domain(self.slot.epoch(spec.slots_per_epoch), spec.domain_deposit), self.slot.epoch(spec.slots_per_epoch),
Domain::Deposit,
&self.fork,
),
&pubkey, &pubkey,
) )
} }
@ -1338,6 +1259,7 @@ impl Encodable for BeaconState {
s.append(&self.batched_block_roots); s.append(&self.batched_block_roots);
s.append(&self.latest_eth1_data); s.append(&self.latest_eth1_data);
s.append(&self.eth1_data_votes); s.append(&self.eth1_data_votes);
s.append(&self.deposit_index);
} }
} }
@ -1368,6 +1290,7 @@ impl Decodable for BeaconState {
let (batched_block_roots, i) = <_>::ssz_decode(bytes, i)?; let (batched_block_roots, i) = <_>::ssz_decode(bytes, i)?;
let (latest_eth1_data, i) = <_>::ssz_decode(bytes, i)?; let (latest_eth1_data, i) = <_>::ssz_decode(bytes, i)?;
let (eth1_data_votes, i) = <_>::ssz_decode(bytes, i)?; let (eth1_data_votes, i) = <_>::ssz_decode(bytes, i)?;
let (deposit_index, i) = <_>::ssz_decode(bytes, i)?;
Ok(( Ok((
Self { Self {
@ -1396,6 +1319,7 @@ impl Decodable for BeaconState {
batched_block_roots, batched_block_roots,
latest_eth1_data, latest_eth1_data,
eth1_data_votes, eth1_data_votes,
deposit_index,
cache_index_offset: 0, cache_index_offset: 0,
caches: vec![EpochCache::empty(); CACHED_EPOCHS], caches: vec![EpochCache::empty(); CACHED_EPOCHS],
}, },
@ -1440,6 +1364,7 @@ impl TreeHash for BeaconState {
result.append(&mut self.batched_block_roots.hash_tree_root_internal()); result.append(&mut self.batched_block_roots.hash_tree_root_internal());
result.append(&mut self.latest_eth1_data.hash_tree_root_internal()); result.append(&mut self.latest_eth1_data.hash_tree_root_internal());
result.append(&mut self.eth1_data_votes.hash_tree_root_internal()); result.append(&mut self.eth1_data_votes.hash_tree_root_internal());
result.append(&mut self.deposit_index.hash_tree_root_internal());
hash(&result) hash(&result)
} }
} }
@ -1472,6 +1397,7 @@ impl<T: RngCore> TestRandom<T> for BeaconState {
batched_block_roots: <_>::random_for_test(rng), batched_block_roots: <_>::random_for_test(rng),
latest_eth1_data: <_>::random_for_test(rng), latest_eth1_data: <_>::random_for_test(rng),
eth1_data_votes: <_>::random_for_test(rng), eth1_data_votes: <_>::random_for_test(rng),
deposit_index: <_>::random_for_test(rng),
cache_index_offset: 0, cache_index_offset: 0,
caches: vec![EpochCache::empty(); CACHED_EPOCHS], caches: vec![EpochCache::empty(); CACHED_EPOCHS],
} }

View File

@ -1,8 +1,17 @@
use crate::{Address, Epoch, Hash256, Slot}; use crate::{Address, Epoch, Fork, Hash256, Slot};
use bls::Signature; use bls::Signature;
const GWEI: u64 = 1_000_000_000; const GWEI: u64 = 1_000_000_000;
pub enum Domain {
Deposit,
Attestation,
Proposal,
Exit,
Randao,
Transfer,
}
/// Holds all the "constants" for a BeaconChain. /// Holds all the "constants" for a BeaconChain.
/// ///
/// Spec v0.4.0 /// Spec v0.4.0
@ -85,14 +94,20 @@ pub struct ChainSpec {
/* /*
* Signature domains * Signature domains
*
* Fields should be private to prevent accessing a domain that hasn't been modified to suit
* some `Fork`.
*
* Use `ChainSpec::get_domain(..)` to access these values.
*/ */
pub domain_deposit: u64, domain_deposit: u64,
pub domain_attestation: u64, domain_attestation: u64,
pub domain_proposal: u64, domain_proposal: u64,
pub domain_exit: u64, domain_exit: u64,
pub domain_randao: u64, domain_randao: u64,
pub domain_transfer: u64, domain_transfer: u64,
} }
impl ChainSpec { impl ChainSpec {
/// Return the number of committees in one epoch. /// Return the number of committees in one epoch.
/// ///
@ -107,6 +122,23 @@ impl ChainSpec {
) * self.slots_per_epoch ) * self.slots_per_epoch
} }
/// Get the domain number that represents the fork meta and signature domain.
///
/// Spec v0.4.0
pub fn get_domain(&self, epoch: Epoch, domain: Domain, fork: &Fork) -> u64 {
let domain_constant = match domain {
Domain::Deposit => self.domain_deposit,
Domain::Attestation => self.domain_attestation,
Domain::Proposal => self.domain_proposal,
Domain::Exit => self.domain_exit,
Domain::Randao => self.domain_randao,
Domain::Transfer => self.domain_transfer,
};
let fork_version = fork.get_fork_version(epoch);
fork_version * u64::pow(2, 32) + domain_constant
}
/// Returns a `ChainSpec` compatible with the Ethereum Foundation specification. /// Returns a `ChainSpec` compatible with the Ethereum Foundation specification.
/// ///
/// Spec v0.4.0 /// Spec v0.4.0

View File

@ -24,14 +24,6 @@ impl Fork {
} }
self.current_version self.current_version
} }
/// Get the domain number that represents the fork meta and signature domain.
///
/// Spec v0.4.0
pub fn get_domain(&self, epoch: Epoch, domain_type: u64) -> u64 {
let fork_version = self.get_fork_version(epoch);
fork_version * u64::pow(2, 32) + domain_type
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -43,7 +43,7 @@ pub use crate::beacon_block_body::BeaconBlockBody;
pub use crate::beacon_state::{ pub use crate::beacon_state::{
BeaconState, Error as BeaconStateError, InclusionError, RelativeEpoch, BeaconState, Error as BeaconStateError, InclusionError, RelativeEpoch,
}; };
pub use crate::chain_spec::ChainSpec; pub use crate::chain_spec::{ChainSpec, Domain};
pub use crate::crosslink::Crosslink; pub use crate::crosslink::Crosslink;
pub use crate::deposit::Deposit; pub use crate::deposit::Deposit;
pub use crate::deposit_data::DepositData; pub use crate::deposit_data::DepositData;

View File

@ -12,12 +12,12 @@ impl ProposerSlashingBuilder {
/// - `validator_index: u64` /// - `validator_index: u64`
/// - `message: &[u8]` /// - `message: &[u8]`
/// - `epoch: Epoch` /// - `epoch: Epoch`
/// - `domain: u64` /// - `domain: Domain`
/// ///
/// Where domain is a domain "constant" (e.g., `spec.domain_attestation`). /// Where domain is a domain "constant" (e.g., `spec.domain_attestation`).
pub fn double_vote<F>(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing pub fn double_vote<F>(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing
where where
F: Fn(u64, &[u8], Epoch, u64) -> Signature, F: Fn(u64, &[u8], Epoch, Domain) -> Signature,
{ {
let slot = Slot::new(0); let slot = Slot::new(0);
let shard = 0; let shard = 0;
@ -39,15 +39,13 @@ impl ProposerSlashingBuilder {
proposal_1.signature = { proposal_1.signature = {
let message = proposal_1.signed_root(); let message = proposal_1.signed_root();
let epoch = slot.epoch(spec.slots_per_epoch); let epoch = slot.epoch(spec.slots_per_epoch);
let domain = spec.domain_proposal; signer(proposer_index, &message[..], epoch, Domain::Proposal)
signer(proposer_index, &message[..], epoch, domain)
}; };
proposal_2.signature = { proposal_2.signature = {
let message = proposal_2.signed_root(); let message = proposal_2.signed_root();
let epoch = slot.epoch(spec.slots_per_epoch); let epoch = slot.epoch(spec.slots_per_epoch);
let domain = spec.domain_proposal; signer(proposer_index, &message[..], epoch, Domain::Proposal)
signer(proposer_index, &message[..], epoch, domain)
}; };
ProposerSlashing { ProposerSlashing {