use crate::consensus_context::ConsensusContext; use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid}; use rayon::prelude::*; use safe_arith::{ArithError, SafeArith}; use signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set}; use std::borrow::Cow; use tree_hash::TreeHash; use types::*; pub use self::verify_attester_slashing::{ get_slashable_indices, get_slashable_indices_modular, verify_attester_slashing, }; pub use self::verify_proposer_slashing::verify_proposer_slashing; pub use altair::sync_committee::process_sync_aggregate; pub use block_signature_verifier::{BlockSignatureVerifier, ParallelSignatureSets}; pub use eip4844::eip4844::process_blob_kzg_commitments; pub use is_valid_indexed_attestation::is_valid_indexed_attestation; pub use process_operations::process_operations; pub use verify_attestation::{ verify_attestation_for_block_inclusion, verify_attestation_for_state, }; #[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))] pub use verify_bls_to_execution_change::verify_bls_to_execution_change; pub use verify_deposit::{ get_existing_validator_index, verify_deposit_merkle_proof, verify_deposit_signature, }; pub use verify_exit::verify_exit; pub mod altair; pub mod block_signature_verifier; pub mod eip4844; pub mod errors; mod is_valid_indexed_attestation; pub mod process_operations; pub mod signature_sets; pub mod tests; mod verify_attestation; mod verify_attester_slashing; #[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))] mod verify_bls_to_execution_change; mod verify_deposit; mod verify_exit; mod verify_proposer_slashing; #[cfg(feature = "withdrawals-processing")] use crate::common::decrease_balance; #[cfg(feature = "arbitrary-fuzz")] use arbitrary::Arbitrary; /// The strategy to be used when validating the block's signatures. #[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))] #[derive(PartialEq, Clone, Copy, Debug)] pub enum BlockSignatureStrategy { /// Do not validate any signature. Use with caution. NoVerification, /// Validate each signature individually, as its object is being processed. VerifyIndividual, /// Validate only the randao reveal signature. VerifyRandao, /// Verify all signatures in bulk at the beginning of block processing. VerifyBulk, } /// The strategy to be used when validating the block's signatures. #[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))] #[derive(PartialEq, Clone, Copy)] pub enum VerifySignatures { /// Validate all signatures encountered. True, /// Do not validate any signature. Use with caution. False, } impl VerifySignatures { pub fn is_true(self) -> bool { self == VerifySignatures::True } } /// Control verification of the latest block header. #[cfg_attr(feature = "arbitrary-fuzz", derive(Arbitrary))] #[derive(PartialEq, Clone, Copy)] pub enum VerifyBlockRoot { True, False, } /// Updates the state for a new block, whilst validating that the block is valid, optionally /// checking the block proposer signature. /// /// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise /// returns an error describing why the block was invalid or how the function failed to execute. /// /// If `block_root` is `Some`, this root is used for verification of the proposer's signature. If it /// is `None` the signing root is computed from scratch. This parameter only exists to avoid /// re-calculating the root when it is already known. Note `block_root` should be equal to the /// tree hash root of the block, NOT the signing root of the block. This function takes /// care of mixing in the domain. pub fn per_block_processing>( state: &mut BeaconState, signed_block: &SignedBeaconBlock, block_signature_strategy: BlockSignatureStrategy, verify_block_root: VerifyBlockRoot, ctxt: &mut ConsensusContext, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { let block = signed_block.message(); // Verify that the `SignedBeaconBlock` instantiation matches the fork at `signed_block.slot()`. signed_block .fork_name(spec) .map_err(BlockProcessingError::InconsistentBlockFork)?; // Verify that the `BeaconState` instantiation matches the fork at `state.slot()`. state .fork_name(spec) .map_err(BlockProcessingError::InconsistentStateFork)?; let verify_signatures = match block_signature_strategy { BlockSignatureStrategy::VerifyBulk => { // Verify all signatures in the block at once. let block_root = Some(ctxt.get_current_block_root(signed_block)?); let proposer_index = Some(ctxt.get_proposer_index(state, spec)?); block_verify!( BlockSignatureVerifier::verify_entire_block( state, |i| get_pubkey_from_state(state, i), |pk_bytes| pk_bytes.decompress().ok().map(Cow::Owned), signed_block, block_root, proposer_index, spec ) .is_ok(), BlockProcessingError::BulkSignatureVerificationFailed ); VerifySignatures::False } BlockSignatureStrategy::VerifyIndividual => VerifySignatures::True, BlockSignatureStrategy::NoVerification => VerifySignatures::False, BlockSignatureStrategy::VerifyRandao => VerifySignatures::False, }; let proposer_index = process_block_header( state, block.temporary_block_header(), verify_block_root, ctxt, spec, )?; if verify_signatures.is_true() { verify_block_signature(state, signed_block, ctxt, spec)?; } let verify_randao = if let BlockSignatureStrategy::VerifyRandao = block_signature_strategy { VerifySignatures::True } else { verify_signatures }; // Ensure the current and previous epoch caches are built. state.build_committee_cache(RelativeEpoch::Previous, spec)?; state.build_committee_cache(RelativeEpoch::Current, spec)?; // The call to the `process_execution_payload` must happen before the call to the // `process_randao` as the former depends on the `randao_mix` computed with the reveal of the // previous block. if is_execution_enabled(state, block.body()) { let payload = block.body().execution_payload()?; #[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))] process_withdrawals::(state, payload, spec)?; process_execution_payload::(state, payload, spec)?; } process_randao(state, block, verify_randao, ctxt, spec)?; process_eth1_data(state, block.body().eth1_data())?; process_operations(state, block.body(), verify_signatures, ctxt, spec)?; if let Ok(sync_aggregate) = block.body().sync_aggregate() { process_sync_aggregate( state, sync_aggregate, proposer_index, verify_signatures, spec, )?; } process_blob_kzg_commitments(block.body())?; Ok(()) } /// Processes the block header, returning the proposer index. pub fn process_block_header( state: &mut BeaconState, block_header: BeaconBlockHeader, verify_block_root: VerifyBlockRoot, ctxt: &mut ConsensusContext, spec: &ChainSpec, ) -> Result> { // Verify that the slots match verify!( block_header.slot == state.slot(), HeaderInvalid::StateSlotMismatch ); // Verify that the block is newer than the latest block header verify!( block_header.slot > state.latest_block_header().slot, HeaderInvalid::OlderThanLatestBlockHeader { block_slot: block_header.slot, latest_block_header_slot: state.latest_block_header().slot, } ); // Verify that proposer index is the correct index let proposer_index = block_header.proposer_index; let state_proposer_index = ctxt.get_proposer_index(state, spec)?; verify!( proposer_index == state_proposer_index, HeaderInvalid::ProposerIndexMismatch { block_proposer_index: proposer_index, state_proposer_index, } ); if verify_block_root == VerifyBlockRoot::True { let expected_previous_block_root = state.latest_block_header().tree_hash_root(); verify!( block_header.parent_root == expected_previous_block_root, HeaderInvalid::ParentBlockRootMismatch { state: expected_previous_block_root, block: block_header.parent_root, } ); } *state.latest_block_header_mut() = block_header; // Verify proposer is not slashed verify!( !state.get_validator(proposer_index as usize)?.slashed, HeaderInvalid::ProposerSlashed(proposer_index) ); Ok(proposer_index) } /// Verifies the signature of a block. /// /// Spec v0.12.1 pub fn verify_block_signature>( state: &BeaconState, block: &SignedBeaconBlock, ctxt: &mut ConsensusContext, spec: &ChainSpec, ) -> Result<(), BlockOperationError> { let block_root = Some(ctxt.get_current_block_root(block)?); let proposer_index = Some(ctxt.get_proposer_index(state, spec)?); verify!( block_proposal_signature_set( state, |i| get_pubkey_from_state(state, i), block, block_root, proposer_index, spec )? .verify(), HeaderInvalid::ProposalSignatureInvalid ); Ok(()) } /// Verifies the `randao_reveal` against the block's proposer pubkey and updates /// `state.latest_randao_mixes`. pub fn process_randao>( state: &mut BeaconState, block: BeaconBlockRef<'_, T, Payload>, verify_signatures: VerifySignatures, ctxt: &mut ConsensusContext, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { if verify_signatures.is_true() { // Verify RANDAO reveal signature. let proposer_index = ctxt.get_proposer_index(state, spec)?; block_verify!( randao_signature_set( state, |i| get_pubkey_from_state(state, i), block, Some(proposer_index), spec )? .verify(), BlockProcessingError::RandaoSignatureInvalid ); } // Update the current epoch RANDAO mix. state.update_randao_mix(state.current_epoch(), block.body().randao_reveal())?; Ok(()) } /// Update the `state.eth1_data_votes` based upon the `eth1_data` provided. pub fn process_eth1_data( state: &mut BeaconState, eth1_data: &Eth1Data, ) -> Result<(), Error> { if let Some(new_eth1_data) = get_new_eth1_data(state, eth1_data)? { *state.eth1_data_mut() = new_eth1_data; } state.eth1_data_votes_mut().push(eth1_data.clone())?; Ok(()) } /// Returns `Ok(Some(eth1_data))` if adding the given `eth1_data` to `state.eth1_data_votes` would /// result in a change to `state.eth1_data`. pub fn get_new_eth1_data( state: &BeaconState, eth1_data: &Eth1Data, ) -> Result, ArithError> { let num_votes = state .eth1_data_votes() .iter() .filter(|vote| *vote == eth1_data) .count(); // The +1 is to account for the `eth1_data` supplied to the function. if num_votes.safe_add(1)?.safe_mul(2)? > T::SlotsPerEth1VotingPeriod::to_usize() { Ok(Some(eth1_data.clone())) } else { Ok(None) } } /// Performs *partial* verification of the `payload`. /// /// The verification is partial, since the execution payload is not verified against an execution /// engine. That is expected to be performed by an upstream function. /// /// ## Specification /// /// Contains a partial set of checks from the `process_execution_payload` function: /// /// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/beacon-chain.md#process_execution_payload pub fn partially_verify_execution_payload<'payload, T: EthSpec, Payload: AbstractExecPayload>( state: &BeaconState, payload: Payload::Ref<'payload>, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { if is_merge_transition_complete(state) { block_verify!( payload.parent_hash() == state.latest_execution_payload_header()?.block_hash(), BlockProcessingError::ExecutionHashChainIncontiguous { expected: state.latest_execution_payload_header()?.block_hash(), found: payload.parent_hash(), } ); } block_verify!( payload.prev_randao() == *state.get_randao_mix(state.current_epoch())?, BlockProcessingError::ExecutionRandaoMismatch { expected: *state.get_randao_mix(state.current_epoch())?, found: payload.prev_randao(), } ); let timestamp = compute_timestamp_at_slot(state, spec)?; block_verify!( payload.timestamp() == timestamp, BlockProcessingError::ExecutionInvalidTimestamp { expected: timestamp, found: payload.timestamp(), } ); Ok(()) } /// Calls `partially_verify_execution_payload` and then updates the payload header in the `state`. /// /// ## Specification /// /// Partially equivalent to the `process_execution_payload` function: /// /// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/beacon-chain.md#process_execution_payload pub fn process_execution_payload<'payload, T: EthSpec, Payload: AbstractExecPayload>( state: &mut BeaconState, payload: Payload::Ref<'payload>, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { partially_verify_execution_payload::(state, payload, spec)?; match state.latest_execution_payload_header_mut()? { ExecutionPayloadHeaderRefMut::Merge(header_mut) => { match payload.to_execution_payload_header() { ExecutionPayloadHeader::Merge(header) => *header_mut = header, _ => return Err(BlockProcessingError::IncorrectStateType), } } ExecutionPayloadHeaderRefMut::Capella(header_mut) => { match payload.to_execution_payload_header() { ExecutionPayloadHeader::Capella(header) => *header_mut = header, _ => return Err(BlockProcessingError::IncorrectStateType), } } ExecutionPayloadHeaderRefMut::Eip4844(header_mut) => { match payload.to_execution_payload_header() { ExecutionPayloadHeader::Eip4844(header) => *header_mut = header, _ => return Err(BlockProcessingError::IncorrectStateType), } } } Ok(()) } /// These functions will definitely be called before the merge. Their entire purpose is to check if /// the merge has happened or if we're on the transition block. Thus we don't want to propagate /// errors from the `BeaconState` being an earlier variant than `BeaconStateMerge` as we'd have to /// repeaetedly write code to treat these errors as false. /// https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#is_merge_transition_complete pub fn is_merge_transition_complete(state: &BeaconState) -> bool { // We must check defaultness against the payload header with 0x0 roots, as that's what's meant // by `ExecutionPayloadHeader()` in the spec. state .latest_execution_payload_header() .map(|header| !header.is_default_with_zero_roots()) .unwrap_or(false) } /// https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#is_merge_transition_block pub fn is_merge_transition_block>( state: &BeaconState, body: BeaconBlockBodyRef, ) -> bool { // For execution payloads in blocks (which may be headers) we must check defaultness against // the payload with `transactions_root` equal to the tree hash of the empty list. body.execution_payload() .map(|payload| { !is_merge_transition_complete(state) && !payload.is_default_with_empty_roots() }) .unwrap_or(false) } /// https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#is_execution_enabled pub fn is_execution_enabled>( state: &BeaconState, body: BeaconBlockBodyRef, ) -> bool { is_merge_transition_block(state, body) || is_merge_transition_complete(state) } /// https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#compute_timestamp_at_slot pub fn compute_timestamp_at_slot( state: &BeaconState, spec: &ChainSpec, ) -> Result { let slots_since_genesis = state.slot().as_u64().safe_sub(spec.genesis_slot.as_u64())?; slots_since_genesis .safe_mul(spec.seconds_per_slot) .and_then(|since_genesis| state.genesis_time().safe_add(since_genesis)) } /// FIXME: add link to this function once the spec is stable #[cfg(feature = "withdrawals")] pub fn get_expected_withdrawals( state: &BeaconState, spec: &ChainSpec, ) -> Result, BlockProcessingError> { let epoch = state.current_epoch(); let mut withdrawal_index = state.next_withdrawal_index()?; let mut validator_index = state.next_withdrawal_validator_index()?; let mut withdrawals = vec![]; if cfg!(not(feature = "withdrawals-processing")) { return Ok(withdrawals.into()); } for _ in 0..state.validators().len() { let validator = state.get_validator(validator_index as usize)?; let balance = *state.balances().get(validator_index as usize).ok_or( BeaconStateError::BalancesOutOfBounds(validator_index as usize), )?; if validator.is_fully_withdrawable_at(balance, epoch, spec) { withdrawals.push(Withdrawal { index: withdrawal_index, validator_index, address: validator .get_eth1_withdrawal_address(spec) .ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?, amount: balance, }); withdrawal_index.safe_add_assign(1)?; } else if validator.is_partially_withdrawable_validator(balance, spec) { withdrawals.push(Withdrawal { index: withdrawal_index, validator_index, address: validator .get_eth1_withdrawal_address(spec) .ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?, amount: balance.safe_sub(spec.max_effective_balance)?, }); withdrawal_index.safe_add_assign(1)?; } if withdrawals.len() == T::max_withdrawals_per_payload() { break; } validator_index = validator_index .safe_add(1)? .safe_rem(state.validators().len() as u64)?; } Ok(withdrawals.into()) } /// FIXME: add link to this function once the spec is stable #[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))] pub fn process_withdrawals<'payload, T: EthSpec, Payload: AbstractExecPayload>( state: &mut BeaconState, payload: Payload::Ref<'payload>, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { match state { BeaconState::Merge(_) => Ok(()), BeaconState::Capella(_) | BeaconState::Eip4844(_) => { let expected_withdrawals = get_expected_withdrawals(state, spec)?; let expected_root = expected_withdrawals.tree_hash_root(); let withdrawals_root = payload.withdrawals_root()?; if expected_root != withdrawals_root { return Err(BlockProcessingError::WithdrawalsRootMismatch { expected: expected_root, found: withdrawals_root, }); } for withdrawal in expected_withdrawals.iter() { decrease_balance( state, withdrawal.validator_index as usize, withdrawal.amount, )?; } if let Some(latest_withdrawal) = expected_withdrawals.last() { *state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?; let next_validator_index = latest_withdrawal .validator_index .safe_add(1)? .safe_rem(state.validators().len() as u64)?; *state.next_withdrawal_validator_index_mut()? = next_validator_index; } Ok(()) } // these shouldn't even be encountered but they're here for completeness BeaconState::Base(_) | BeaconState::Altair(_) => Ok(()), } }