diff --git a/eth2/state_processing/Cargo.toml b/eth2/state_processing/Cargo.toml index 85ac8b42ad..bdc14c1d74 100644 --- a/eth2/state_processing/Cargo.toml +++ b/eth2/state_processing/Cargo.toml @@ -10,7 +10,6 @@ harness = false [dev-dependencies] criterion = "0.2" -test_harness = { path = "../../beacon_node/beacon_chain/test_harness" } env_logger = "0.6.0" [dependencies] diff --git a/eth2/state_processing/src/block_processable.rs b/eth2/state_processing/src/block_processable.rs deleted file mode 100644 index 6f4d7268ec..0000000000 --- a/eth2/state_processing/src/block_processable.rs +++ /dev/null @@ -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 for Error { - fn from(e: AttestationValidationError) -> Error { - Error::InvalidAttestation(e) - } -} - -impl From for Error { - fn from(e: BeaconStateError) -> Error { - Error::BeaconStateError(e) - } -} - -impl From for Error { - fn from(e: SlotProcessingError) -> Error { - Error::SlotProcessingError(e) - } -} - -impl From for AttestationValidationError { - fn from(e: BeaconStateError) -> AttestationValidationError { - AttestationValidationError::BeaconStateError(e) - } -} diff --git a/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs b/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs deleted file mode 100644 index a406af24ee..0000000000 --- a/eth2/state_processing/src/block_processable/verify_slashable_attestation.rs +++ /dev/null @@ -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(()) -} diff --git a/eth2/state_processing/src/errors.rs b/eth2/state_processing/src/errors.rs index 5eceb0be0a..89cd82b242 100644 --- a/eth2/state_processing/src/errors.rs +++ b/eth2/state_processing/src/errors.rs @@ -1,9 +1,98 @@ use types::BeaconStateError; +macro_rules! impl_from_beacon_state_error { + ($type: ident) => { + impl From 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 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 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: 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 for BlockInvalid { + fn into(self) -> BlockProcessingError { + BlockProcessingError::Invalid(self) + } +} + +/* + * Attestation Validation + */ + #[derive(Debug, PartialEq)] pub enum AttestationValidationError { + /// The `Attestation` is invalid. Invalid(AttestationInvalid), - ProcessingError(BeaconStateError), + /// Encountered a `BeaconStateError` whilst attempting to determine validity. + BeaconStateError(BeaconStateError), } #[derive(Debug, PartialEq)] @@ -23,8 +112,132 @@ pub enum AttestationInvalid { ShardBlockRootNotZero, } -impl From for AttestationValidationError { - fn from(e: BeaconStateError) -> AttestationValidationError { - AttestationValidationError::ProcessingError(e) +impl_from_beacon_state_error!(AttestationValidationError); +impl_into_with_index_with_beacon_error!(AttestationValidationError, AttestationInvalid); + +/* + * `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 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); diff --git a/eth2/state_processing/src/lib.rs b/eth2/state_processing/src/lib.rs index 0a4343e003..ec883ddca3 100644 --- a/eth2/state_processing/src/lib.rs +++ b/eth2/state_processing/src/lib.rs @@ -1,13 +1,13 @@ #[macro_use] mod macros; -mod block_processable; +pub mod per_block_processing; // mod epoch_processable; -mod errors; +pub mod errors; // mod slot_processable; -pub use block_processable::{ - validate_attestation, validate_attestation_without_signature, BlockProcessable, - Error as BlockProcessingError, +pub use errors::{BlockInvalid, BlockProcessingError}; +pub use per_block_processing::{ + per_block_processing, per_block_processing_without_verifying_block_signature, }; // pub use epoch_processable::{EpochProcessable, Error as EpochProcessingError}; // pub use slot_processable::{Error as SlotProcessingError, SlotProcessable}; diff --git a/eth2/state_processing/src/macros.rs b/eth2/state_processing/src/macros.rs index 6ccc54ba96..5a0d0dc114 100644 --- a/eth2/state_processing/src/macros.rs +++ b/eth2/state_processing/src/macros.rs @@ -1,8 +1,13 @@ -#[macro_use] -macro_rules! ensure { - ($condition: expr, $result: ident) => { +macro_rules! verify { + ($condition: expr, $result: expr) => { if !$condition { - return Err(Error::Invalid(Invalid::$result)); + return Err(Error::Invalid($result)); } }; } + +macro_rules! invalid { + ($result: expr) => { + return Err(Error::Invalid($result)); + }; +} diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs new file mode 100644 index 0000000000..06fe3b5569 --- /dev/null +++ b/eth2/state_processing/src/per_block_processing.rs @@ -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) + } +} diff --git a/eth2/state_processing/src/block_processable/validate_attestation.rs b/eth2/state_processing/src/per_block_processing/validate_attestation.rs similarity index 83% rename from eth2/state_processing/src/block_processable/validate_attestation.rs rename to eth2/state_processing/src/per_block_processing/validate_attestation.rs index 1b422711cd..8214e8d9ae 100644 --- a/eth2/state_processing/src/block_processable/validate_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/validate_attestation.rs @@ -3,27 +3,6 @@ use ssz::TreeHash; use types::beacon_state::helpers::*; 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. /// /// Spec v0.4.0 @@ -48,7 +27,7 @@ pub fn validate_attestation_without_signature( /// Validate an attestation, optionally checking the aggregate signature. /// -/// Spec v0.2.0 +/// Spec v0.4.0 fn validate_attestation_signature_optional( state: &BeaconState, attestation: &Attestation, @@ -56,38 +35,41 @@ fn validate_attestation_signature_optional( verify_signature: bool, ) -> Result<(), Error> { // 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`. - ensure!( + verify!( attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot, - IncludedTooEarly + Invalid::IncludedTooEarly ); // Verify that `state.slot < attestation.data.slot + SLOTS_PER_EPOCH`. - ensure!( + verify!( 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 // `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) { - ensure!( + verify!( attestation.data.justified_epoch == state.justified_epoch, - WrongJustifiedSlot + Invalid::WrongJustifiedSlot ); } else { - ensure!( + verify!( 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, // get_epoch_start_slot(attestation.data.justified_epoch))`. - ensure!( + verify!( attestation.data.justified_block_root == *state .get_block_root( @@ -98,7 +80,7 @@ fn validate_attestation_signature_optional( &spec ) .ok_or(BeaconStateError::InsufficientBlockRoots)?, - WrongJustifiedRoot + Invalid::WrongJustifiedRoot ); // Verify that either: @@ -112,59 +94,61 @@ fn validate_attestation_signature_optional( crosslink_data_root: attestation.data.crosslink_data_root, epoch: attestation.data.slot.epoch(spec.slots_per_epoch), }; - ensure!( + verify!( (attestation.data.latest_crosslink == state.latest_crosslinks[attestation.data.shard as usize]) | (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink), - BadLatestCrosslinkRoot + Invalid::BadLatestCrosslinkRoot ); // Get the committee for this attestation let (committee, _shard) = state .get_crosslink_committees_at_slot(attestation.data.slot, spec)? .iter() - .find(|(committee, shard)| *shard == attestation.data.shard) + .find(|(_committee, shard)| *shard == attestation.data.shard) .ok_or_else(|| Error::Invalid(Invalid::NoCommitteeForShard))?; // Custody bitfield is all zeros (phase 0 requirement). - ensure!( + verify!( attestation.custody_bitfield.num_set_bits() == 0, - CustodyBitfieldHasSetBits + Invalid::CustodyBitfieldHasSetBits ); // Custody bitfield length is correct. - ensure!( + verify!( verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()), - BadCustodyBitfieldLength + Invalid::BadCustodyBitfieldLength ); // Aggregation bitfield isn't empty. - ensure!( + verify!( attestation.aggregation_bitfield.num_set_bits() != 0, - AggregationBitfieldIsEmpty + Invalid::AggregationBitfieldIsEmpty ); // Aggregation bitfield length is correct. - ensure!( + verify!( verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()), - BadAggregationBitfieldLength + Invalid::BadAggregationBitfieldLength ); if verify_signature { - ensure!( + let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch); + verify!( verify_attestation_signature( state, committee, + attestation_epoch, &attestation.custody_bitfield, &attestation.data, &attestation.aggregate_signature, spec ), - BadSignature + Invalid::BadSignature ); } // [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, - ShardBlockRootNotZero + Invalid::ShardBlockRootNotZero ); Ok(()) @@ -177,9 +161,12 @@ fn validate_attestation_signature_optional( /// - `aggregate_signature` was not signed correctly. /// - `custody_bitfield` does not have a bit for each index of `committee`. /// - A `validator_index` in `committee` is not in `state.validator_registry`. +/// +/// Spec v0.4.0 fn verify_attestation_signature( state: &BeaconState, committee: &[usize], + attestation_epoch: Epoch, custody_bitfield: &Bitfield, attestation_data: &AttestationData, aggregate_signature: &AggregateSignature, @@ -234,5 +221,7 @@ fn verify_attestation_signature( 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[..]) } diff --git a/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs b/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs new file mode 100644 index 0000000000..b12ffa5aeb --- /dev/null +++ b/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs @@ -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, 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) +} diff --git a/eth2/state_processing/src/per_block_processing/verify_deposit.rs b/eth2/state_processing/src/per_block_processing/verify_deposit.rs new file mode 100644 index 0000000000..20ed2f0b2e --- /dev/null +++ b/eth2/state_processing/src/per_block_processing/verify_deposit.rs @@ -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(()) +} diff --git a/eth2/state_processing/src/per_block_processing/verify_exit.rs b/eth2/state_processing/src/per_block_processing/verify_exit.rs new file mode 100644 index 0000000000..d4c2f7baa9 --- /dev/null +++ b/eth2/state_processing/src/per_block_processing/verify_exit.rs @@ -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(()) +} diff --git a/eth2/state_processing/src/per_block_processing/verify_proposer_slashing.rs b/eth2/state_processing/src/per_block_processing/verify_proposer_slashing.rs new file mode 100644 index 0000000000..a7b344b2c3 --- /dev/null +++ b/eth2/state_processing/src/per_block_processing/verify_proposer_slashing.rs @@ -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) +} diff --git a/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs b/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs new file mode 100644 index 0000000000..cd0f9a201d --- /dev/null +++ b/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs @@ -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(()) +} diff --git a/eth2/state_processing/src/per_block_processing/verify_transfer.rs b/eth2/state_processing/src/per_block_processing/verify_transfer.rs new file mode 100644 index 0000000000..0dee9a7d25 --- /dev/null +++ b/eth2/state_processing/src/per_block_processing/verify_transfer.rs @@ -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(()) +} diff --git a/eth2/types/src/attester_slashing/builder.rs b/eth2/types/src/attester_slashing/builder.rs index 6638eb2a50..bdb46b1546 100644 --- a/eth2/types/src/attester_slashing/builder.rs +++ b/eth2/types/src/attester_slashing/builder.rs @@ -12,7 +12,7 @@ impl AttesterSlashingBuilder { /// - `validator_index: u64` /// - `message: &[u8]` /// - `epoch: Epoch` - /// - `domain: u64` + /// - `domain: Domain` /// /// Where domain is a domain "constant" (e.g., `spec.domain_attestation`). pub fn double_vote( @@ -21,7 +21,7 @@ impl AttesterSlashingBuilder { spec: &ChainSpec, ) -> AttesterSlashing where - F: Fn(u64, &[u8], Epoch, u64) -> Signature, + F: Fn(u64, &[u8], Epoch, Domain) -> Signature, { let double_voted_slot = Slot::new(0); let shard = 0; @@ -75,12 +75,7 @@ impl AttesterSlashingBuilder { custody_bit: attestation.custody_bitfield.get(i).unwrap(), }; let message = attestation_data_and_custody_bit.hash_tree_root(); - let signature = signer( - *validator_index, - &message[..], - epoch, - spec.domain_attestation, - ); + let signature = signer(*validator_index, &message[..], epoch, Domain::Attestation); attestation.aggregate_signature.add(&signature); } }; diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 2394c2c0dd..76b97b21de 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -112,6 +112,7 @@ pub struct BeaconState { // Ethereum 1.0 chain data pub latest_eth1_data: Eth1Data, pub eth1_data_votes: Vec, + pub deposit_index: u64, // Caching (not in the spec) pub cache_index_offset: usize, @@ -187,6 +188,7 @@ impl BeaconState { */ latest_eth1_data, eth1_data_votes: vec![], + deposit_index: 0, /* * 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( &genesis_state.validator_registry, spec.genesis_epoch, @@ -566,92 +570,6 @@ impl BeaconState { .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. /// /// Spec v0.4.0 @@ -1163,8 +1081,11 @@ impl BeaconState { proof_of_possession.verify( &proof_of_possession_data.hash_tree_root(), - self.fork - .get_domain(self.slot.epoch(spec.slots_per_epoch), spec.domain_deposit), + spec.get_domain( + self.slot.epoch(spec.slots_per_epoch), + Domain::Deposit, + &self.fork, + ), &pubkey, ) } @@ -1338,6 +1259,7 @@ impl Encodable for BeaconState { s.append(&self.batched_block_roots); s.append(&self.latest_eth1_data); 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 (latest_eth1_data, i) = <_>::ssz_decode(bytes, i)?; let (eth1_data_votes, i) = <_>::ssz_decode(bytes, i)?; + let (deposit_index, i) = <_>::ssz_decode(bytes, i)?; Ok(( Self { @@ -1396,6 +1319,7 @@ impl Decodable for BeaconState { batched_block_roots, latest_eth1_data, eth1_data_votes, + deposit_index, cache_index_offset: 0, 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.latest_eth1_data.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) } } @@ -1472,6 +1397,7 @@ impl TestRandom for BeaconState { batched_block_roots: <_>::random_for_test(rng), latest_eth1_data: <_>::random_for_test(rng), eth1_data_votes: <_>::random_for_test(rng), + deposit_index: <_>::random_for_test(rng), cache_index_offset: 0, caches: vec![EpochCache::empty(); CACHED_EPOCHS], } diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index dea0797553..789bb6c0cc 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -1,8 +1,17 @@ -use crate::{Address, Epoch, Hash256, Slot}; +use crate::{Address, Epoch, Fork, Hash256, Slot}; use bls::Signature; const GWEI: u64 = 1_000_000_000; +pub enum Domain { + Deposit, + Attestation, + Proposal, + Exit, + Randao, + Transfer, +} + /// Holds all the "constants" for a BeaconChain. /// /// Spec v0.4.0 @@ -85,14 +94,20 @@ pub struct ChainSpec { /* * 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, - pub domain_attestation: u64, - pub domain_proposal: u64, - pub domain_exit: u64, - pub domain_randao: u64, - pub domain_transfer: u64, + domain_deposit: u64, + domain_attestation: u64, + domain_proposal: u64, + domain_exit: u64, + domain_randao: u64, + domain_transfer: u64, } + impl ChainSpec { /// Return the number of committees in one epoch. /// @@ -107,6 +122,23 @@ impl ChainSpec { ) * 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. /// /// Spec v0.4.0 diff --git a/eth2/types/src/fork.rs b/eth2/types/src/fork.rs index 6c524a2d97..0acd6da906 100644 --- a/eth2/types/src/fork.rs +++ b/eth2/types/src/fork.rs @@ -24,14 +24,6 @@ impl Fork { } 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)] diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index f88cc6a17c..e595684bc1 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -43,7 +43,7 @@ pub use crate::beacon_block_body::BeaconBlockBody; pub use crate::beacon_state::{ 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::deposit::Deposit; pub use crate::deposit_data::DepositData; diff --git a/eth2/types/src/proposer_slashing/builder.rs b/eth2/types/src/proposer_slashing/builder.rs index 8dc1abfbe4..1110bdf30c 100644 --- a/eth2/types/src/proposer_slashing/builder.rs +++ b/eth2/types/src/proposer_slashing/builder.rs @@ -12,12 +12,12 @@ impl ProposerSlashingBuilder { /// - `validator_index: u64` /// - `message: &[u8]` /// - `epoch: Epoch` - /// - `domain: u64` + /// - `domain: Domain` /// /// Where domain is a domain "constant" (e.g., `spec.domain_attestation`). pub fn double_vote(proposer_index: u64, signer: F, spec: &ChainSpec) -> ProposerSlashing where - F: Fn(u64, &[u8], Epoch, u64) -> Signature, + F: Fn(u64, &[u8], Epoch, Domain) -> Signature, { let slot = Slot::new(0); let shard = 0; @@ -39,15 +39,13 @@ impl ProposerSlashingBuilder { proposal_1.signature = { let message = proposal_1.signed_root(); let epoch = slot.epoch(spec.slots_per_epoch); - let domain = spec.domain_proposal; - signer(proposer_index, &message[..], epoch, domain) + signer(proposer_index, &message[..], epoch, Domain::Proposal) }; proposal_2.signature = { let message = proposal_2.signed_root(); let epoch = slot.epoch(spec.slots_per_epoch); - let domain = spec.domain_proposal; - signer(proposer_index, &message[..], epoch, domain) + signer(proposer_index, &message[..], epoch, Domain::Proposal) }; ProposerSlashing {