#![cfg(all(test, not(feature = "fake_crypto")))] use crate::per_block_processing; use crate::per_block_processing::errors::{ AttestationInvalid, AttesterSlashingInvalid, BlockOperationError, BlockProcessingError, DepositInvalid, HeaderInvalid, IndexedAttestationInvalid, IntoWithIndex, ProposerSlashingInvalid, }; use crate::{ per_block_processing::process_operations, BlockSignatureStrategy, VerifyBlockRoot, VerifySignatures, }; use beacon_chain::test_utils::{BeaconChainHarness, EphemeralHarnessType}; use lazy_static::lazy_static; use ssz_types::Bitfield; use test_utils::generate_deterministic_keypairs; use types::*; pub const MAX_VALIDATOR_COUNT: usize = 97; pub const NUM_DEPOSITS: u64 = 1; pub const VALIDATOR_COUNT: usize = 64; pub const EPOCH_OFFSET: u64 = 4; pub const NUM_ATTESTATIONS: u64 = 1; lazy_static! { /// A cached set of keys. static ref KEYPAIRS: Vec = generate_deterministic_keypairs(MAX_VALIDATOR_COUNT); } fn get_harness( epoch_offset: u64, num_validators: usize, ) -> BeaconChainHarness> { // Set the state and block to be in the last slot of the `epoch_offset`th epoch. let last_slot_of_epoch = (MainnetEthSpec::genesis_epoch() + epoch_offset).end_slot(E::slots_per_epoch()); let harness = BeaconChainHarness::builder(E::default()) .default_spec() .keypairs(KEYPAIRS[0..num_validators].to_vec()) .fresh_ephemeral_store() .build(); let state = harness.get_current_state(); if last_slot_of_epoch > Slot::new(0) { harness.add_attested_blocks_at_slots( state, Hash256::zero(), (1..last_slot_of_epoch.as_u64()) .map(Slot::new) .collect::>() .as_slice(), (0..num_validators).collect::>().as_slice(), ); } harness } #[test] fn valid_block_ok() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let state = harness.get_current_state(); let slot = state.slot(); let (block, mut state) = harness.make_block_return_pre_state(state, slot + Slot::new(1)); let result = per_block_processing( &mut state, &block, None, BlockSignatureStrategy::VerifyIndividual, VerifyBlockRoot::True, &spec, ); assert!(result.is_ok()); } #[test] fn invalid_block_header_state_slot() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let state = harness.get_current_state(); let slot = state.slot() + Slot::new(1); let (signed_block, mut state) = harness.make_block_return_pre_state(state, slot); let (mut block, signature) = signed_block.deconstruct(); *block.slot_mut() = slot + Slot::new(1); let result = per_block_processing( &mut state, &SignedBeaconBlock::from_block(block, signature), None, BlockSignatureStrategy::VerifyIndividual, VerifyBlockRoot::True, &spec, ); assert_eq!( result, Err(BlockProcessingError::HeaderInvalid { reason: HeaderInvalid::StateSlotMismatch }) ); } #[test] fn invalid_parent_block_root() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let state = harness.get_current_state(); let slot = state.slot(); let (signed_block, mut state) = harness.make_block_return_pre_state(state, slot + Slot::new(1)); let (mut block, signature) = signed_block.deconstruct(); *block.parent_root_mut() = Hash256::from([0xAA; 32]); let result = per_block_processing( &mut state, &SignedBeaconBlock::from_block(block, signature), None, BlockSignatureStrategy::VerifyIndividual, VerifyBlockRoot::True, &spec, ); assert_eq!( result, Err(BlockProcessingError::HeaderInvalid { reason: HeaderInvalid::ParentBlockRootMismatch { state: state.latest_block_header().canonical_root(), block: Hash256::from([0xAA; 32]) } }) ); } #[test] fn invalid_block_signature() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let state = harness.get_current_state(); let slot = state.slot(); let (signed_block, mut state) = harness.make_block_return_pre_state(state, slot + Slot::new(1)); let (block, _) = signed_block.deconstruct(); let result = per_block_processing( &mut state, &SignedBeaconBlock::from_block(block, Signature::empty()), None, BlockSignatureStrategy::VerifyIndividual, VerifyBlockRoot::True, &spec, ); // should get a BadSignature error assert_eq!( result, Err(BlockProcessingError::HeaderInvalid { reason: HeaderInvalid::ProposalSignatureInvalid }) ); } #[test] fn invalid_randao_reveal_signature() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let state = harness.get_current_state(); let slot = state.slot(); let (signed_block, mut state) = harness.make_block_with_modifier(state, slot + 1, |block| { *block.body_mut().randao_reveal_mut() = Signature::empty(); }); let result = per_block_processing( &mut state, &signed_block, None, BlockSignatureStrategy::VerifyIndividual, VerifyBlockRoot::True, &spec, ); // should get a BadRandaoSignature error assert_eq!(result, Err(BlockProcessingError::RandaoSignatureInvalid)); } #[test] fn valid_4_deposits() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut state = harness.get_current_state(); let (deposits, state) = harness.make_deposits(&mut state, 4, None, None); let deposits = VariableList::from(deposits); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; *head_block.to_mut().body_mut().deposits_mut() = deposits; let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec); // Expecting Ok because these are valid deposits. assert_eq!(result, Ok(())); } #[test] fn invalid_deposit_deposit_count_too_big() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut state = harness.get_current_state(); let (deposits, state) = harness.make_deposits(&mut state, 1, None, None); let deposits = VariableList::from(deposits); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; *head_block.to_mut().body_mut().deposits_mut() = deposits; let big_deposit_count = NUM_DEPOSITS + 1; state.eth1_data_mut().deposit_count = big_deposit_count; let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec); // Expecting DepositCountInvalid because we incremented the deposit_count assert_eq!( result, Err(BlockProcessingError::DepositCountInvalid { expected: big_deposit_count as usize, found: 1 }) ); } #[test] fn invalid_deposit_count_too_small() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut state = harness.get_current_state(); let (deposits, state) = harness.make_deposits(&mut state, 1, None, None); let deposits = VariableList::from(deposits); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; *head_block.to_mut().body_mut().deposits_mut() = deposits; let small_deposit_count = NUM_DEPOSITS - 1; state.eth1_data_mut().deposit_count = small_deposit_count; let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec); // Expecting DepositCountInvalid because we decremented the deposit_count assert_eq!( result, Err(BlockProcessingError::DepositCountInvalid { expected: small_deposit_count as usize, found: 1 }) ); } #[test] fn invalid_deposit_bad_merkle_proof() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut state = harness.get_current_state(); let (deposits, state) = harness.make_deposits(&mut state, 1, None, None); let deposits = VariableList::from(deposits); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; *head_block.to_mut().body_mut().deposits_mut() = deposits; let bad_index = state.eth1_deposit_index() as usize; // Manually offsetting deposit count and index to trigger bad merkle proof state.eth1_data_mut().deposit_count += 1; *state.eth1_deposit_index_mut() += 1; let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec); // Expecting BadMerkleProof because the proofs were created with different indices assert_eq!( result, Err(BlockProcessingError::DepositInvalid { index: bad_index, reason: DepositInvalid::BadMerkleProof }) ); } #[test] fn invalid_deposit_wrong_sig() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut state = harness.get_current_state(); let (deposits, state) = harness.make_deposits(&mut state, 1, None, Some(SignatureBytes::empty())); let deposits = VariableList::from(deposits); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; *head_block.to_mut().body_mut().deposits_mut() = deposits; let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec); // Expecting Ok(()) even though the block signature does not correspond to the correct public key assert_eq!(result, Ok(())); } #[test] fn invalid_deposit_invalid_pub_key() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut state = harness.get_current_state(); let (deposits, state) = harness.make_deposits(&mut state, 1, Some(PublicKeyBytes::empty()), None); let deposits = VariableList::from(deposits); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; *head_block.to_mut().body_mut().deposits_mut() = deposits; let result = process_operations::process_deposits(state, head_block.body().deposits(), &spec); // Expecting Ok(()) even though we passed in invalid publickeybytes in the public key field of the deposit data. assert_eq!(result, Ok(())); } #[test] fn invalid_attestation_no_committee_for_index() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut state = harness.get_current_state(); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; head_block.to_mut().body_mut().attestations_mut()[0] .data .index += 1; let result = process_operations::process_attestations( &mut state, head_block.body(), head_block.proposer_index(), VerifySignatures::True, &spec, ); // Expecting NoCommitee because we manually set the attestation's index to be invalid assert_eq!( result, Err(BlockProcessingError::AttestationInvalid { index: 0, reason: AttestationInvalid::BadCommitteeIndex }) ); } #[test] fn invalid_attestation_wrong_justified_checkpoint() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut state = harness.get_current_state(); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; let old_justified_checkpoint = head_block.body().attestations()[0].data.source; let mut new_justified_checkpoint = old_justified_checkpoint; new_justified_checkpoint.epoch += Epoch::new(1); head_block.to_mut().body_mut().attestations_mut()[0] .data .source = new_justified_checkpoint; let result = process_operations::process_attestations( &mut state, head_block.body(), head_block.proposer_index(), VerifySignatures::True, &spec, ); // Expecting WrongJustifiedCheckpoint because we manually set the // source field of the AttestationData object to be invalid assert_eq!( result, Err(BlockProcessingError::AttestationInvalid { index: 0, reason: AttestationInvalid::WrongJustifiedCheckpoint { state: old_justified_checkpoint, attestation: new_justified_checkpoint, is_current: true, } }) ); } #[test] fn invalid_attestation_bad_aggregation_bitfield_len() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut state = harness.get_current_state(); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; head_block.to_mut().body_mut().attestations_mut()[0].aggregation_bits = Bitfield::with_capacity(spec.target_committee_size).unwrap(); let result = process_operations::process_attestations( &mut state, head_block.body(), head_block.proposer_index(), VerifySignatures::True, &spec, ); // Expecting InvalidBitfield because the size of the aggregation_bitfield is bigger than the commitee size. assert_eq!( result, Err(BlockProcessingError::BeaconStateError( BeaconStateError::InvalidBitfield )) ); } #[test] fn invalid_attestation_bad_signature() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, 97); // minimal number of required validators for this test let mut state = harness.get_current_state(); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; head_block.to_mut().body_mut().attestations_mut()[0].signature = AggregateSignature::empty(); let result = process_operations::process_attestations( &mut state, head_block.body(), head_block.proposer_index(), VerifySignatures::True, &spec, ); // Expecting BadSignature because we're signing with invalid secret_keys assert_eq!( result, Err(BlockProcessingError::AttestationInvalid { index: 0, reason: AttestationInvalid::BadIndexedAttestation( IndexedAttestationInvalid::BadSignature ) }) ); } #[test] fn invalid_attestation_included_too_early() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut state = harness.get_current_state(); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; let new_attesation_slot = head_block.body().attestations()[0].data.slot + Slot::new(MainnetEthSpec::slots_per_epoch()); head_block.to_mut().body_mut().attestations_mut()[0] .data .slot = new_attesation_slot; let result = process_operations::process_attestations( &mut state, head_block.body(), head_block.proposer_index(), VerifySignatures::True, &spec, ); // Expecting IncludedTooEarly because the shard included in the crosslink is bigger than expected assert_eq!( result, Err(BlockProcessingError::AttestationInvalid { index: 0, reason: AttestationInvalid::IncludedTooEarly { state: state.slot(), delay: spec.min_attestation_inclusion_delay, attestation: new_attesation_slot, } }) ); } #[test] fn invalid_attestation_included_too_late() { let spec = MainnetEthSpec::default_spec(); // note to maintainer: might need to increase validator count if we get NoCommittee let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut state = harness.get_current_state(); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; let new_attesation_slot = head_block.body().attestations()[0].data.slot - Slot::new(MainnetEthSpec::slots_per_epoch()); head_block.to_mut().body_mut().attestations_mut()[0] .data .slot = new_attesation_slot; let result = process_operations::process_attestations( &mut state, head_block.body(), head_block.proposer_index(), VerifySignatures::True, &spec, ); assert_eq!( result, Err(BlockProcessingError::AttestationInvalid { index: 0, reason: AttestationInvalid::IncludedTooLate { state: state.slot(), attestation: new_attesation_slot, } }) ); } #[test] fn invalid_attestation_target_epoch_slot_mismatch() { let spec = MainnetEthSpec::default_spec(); // note to maintainer: might need to increase validator count if we get NoCommittee let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut state = harness.get_current_state(); let mut head_block = harness.chain.head_beacon_block().unwrap().deconstruct().0; head_block.to_mut().body_mut().attestations_mut()[0] .data .target .epoch += Epoch::new(1); let result = process_operations::process_attestations( &mut state, head_block.body(), head_block.proposer_index(), VerifySignatures::True, &spec, ); assert_eq!( result, Err(BlockProcessingError::AttestationInvalid { index: 0, reason: AttestationInvalid::TargetEpochSlotMismatch { target_epoch: Epoch::new(EPOCH_OFFSET + 1), slot_epoch: Epoch::new(EPOCH_OFFSET), } }) ); } #[test] fn valid_insert_attester_slashing() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let attester_slashing = harness.make_attester_slashing(vec![1, 2]); let mut state = harness.get_current_state(); let result = process_operations::process_attester_slashings( &mut state, &[attester_slashing], VerifySignatures::True, &spec, ); // Expecting Ok(()) because attester slashing is valid assert_eq!(result, Ok(())); } #[test] fn invalid_attester_slashing_not_slashable() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut attester_slashing = harness.make_attester_slashing(vec![1, 2]); attester_slashing.attestation_1 = attester_slashing.attestation_2.clone(); let mut state = harness.get_current_state(); let result = process_operations::process_attester_slashings( &mut state, &[attester_slashing], VerifySignatures::True, &spec, ); // Expecting NotSlashable because the two attestations are the same assert_eq!( result, Err(BlockProcessingError::AttesterSlashingInvalid { index: 0, reason: AttesterSlashingInvalid::NotSlashable }) ); } #[test] fn invalid_attester_slashing_1_invalid() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut attester_slashing = harness.make_attester_slashing(vec![1, 2]); attester_slashing.attestation_1.attesting_indices = VariableList::from(vec![2, 1]); let mut state = harness.get_current_state(); let result = process_operations::process_attester_slashings( &mut state, &[attester_slashing], VerifySignatures::True, &spec, ); assert_eq!( result, Err( BlockOperationError::Invalid(AttesterSlashingInvalid::IndexedAttestation1Invalid( BlockOperationError::Invalid( IndexedAttestationInvalid::BadValidatorIndicesOrdering(0) ) )) .into_with_index(0) ) ); } #[test] fn invalid_attester_slashing_2_invalid() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut attester_slashing = harness.make_attester_slashing(vec![1, 2]); attester_slashing.attestation_2.attesting_indices = VariableList::from(vec![2, 1]); let mut state = harness.get_current_state(); let result = process_operations::process_attester_slashings( &mut state, &[attester_slashing], VerifySignatures::True, &spec, ); assert_eq!( result, Err( BlockOperationError::Invalid(AttesterSlashingInvalid::IndexedAttestation2Invalid( BlockOperationError::Invalid( IndexedAttestationInvalid::BadValidatorIndicesOrdering(0) ) )) .into_with_index(0) ) ); } #[test] fn valid_insert_proposer_slashing() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let proposer_slashing = harness.make_proposer_slashing(1); let mut state = harness.get_current_state(); let result = process_operations::process_proposer_slashings( &mut state, &[proposer_slashing], VerifySignatures::True, &spec, ); // Expecting Ok(_) because we inserted a valid proposer slashing assert!(result.is_ok()); } #[test] fn invalid_proposer_slashing_proposals_identical() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut proposer_slashing = harness.make_proposer_slashing(1); proposer_slashing.signed_header_1.message = proposer_slashing.signed_header_2.message.clone(); let mut state = harness.get_current_state(); let result = process_operations::process_proposer_slashings( &mut state, &[proposer_slashing], VerifySignatures::True, &spec, ); // Expecting ProposalsIdentical because we the two headers are identical assert_eq!( result, Err(BlockProcessingError::ProposerSlashingInvalid { index: 0, reason: ProposerSlashingInvalid::ProposalsIdentical }) ); } #[test] fn invalid_proposer_slashing_proposer_unknown() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut proposer_slashing = harness.make_proposer_slashing(1); proposer_slashing.signed_header_1.message.proposer_index = 3_141_592; proposer_slashing.signed_header_2.message.proposer_index = 3_141_592; let mut state = harness.get_current_state(); let result = process_operations::process_proposer_slashings( &mut state, &[proposer_slashing], VerifySignatures::True, &spec, ); // Expecting ProposerUnknown because validator_index is unknown assert_eq!( result, Err(BlockProcessingError::ProposerSlashingInvalid { index: 0, reason: ProposerSlashingInvalid::ProposerUnknown(3_141_592) }) ); } #[test] fn invalid_proposer_slashing_duplicate_slashing() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let proposer_slashing = harness.make_proposer_slashing(1); let mut state = harness.get_current_state(); let result_1 = process_operations::process_proposer_slashings( &mut state, &[proposer_slashing.clone()], VerifySignatures::False, &spec, ); assert!(result_1.is_ok()); let result_2 = process_operations::process_proposer_slashings( &mut state, &[proposer_slashing], VerifySignatures::False, &spec, ); // Expecting ProposerNotSlashable because we've already slashed the validator assert_eq!( result_2, Err(BlockProcessingError::ProposerSlashingInvalid { index: 0, reason: ProposerSlashingInvalid::ProposerNotSlashable(1) }) ); } #[test] fn invalid_bad_proposal_1_signature() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut proposer_slashing = harness.make_proposer_slashing(1); proposer_slashing.signed_header_1.signature = Signature::empty(); let mut state = harness.get_current_state(); let result = process_operations::process_proposer_slashings( &mut state, &[proposer_slashing], VerifySignatures::True, &spec, ); // Expecting BadProposal1Signature because signature of proposal 1 is invalid assert_eq!( result, Err(BlockProcessingError::ProposerSlashingInvalid { index: 0, reason: ProposerSlashingInvalid::BadProposal1Signature }) ); } #[test] fn invalid_bad_proposal_2_signature() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut proposer_slashing = harness.make_proposer_slashing(1); proposer_slashing.signed_header_2.signature = Signature::empty(); let mut state = harness.get_current_state(); let result = process_operations::process_proposer_slashings( &mut state, &[proposer_slashing], VerifySignatures::True, &spec, ); // Expecting BadProposal2Signature because signature of proposal 2 is invalid assert_eq!( result, Err(BlockProcessingError::ProposerSlashingInvalid { index: 0, reason: ProposerSlashingInvalid::BadProposal2Signature }) ); } #[test] fn invalid_proposer_slashing_proposal_epoch_mismatch() { let spec = MainnetEthSpec::default_spec(); let harness = get_harness::(EPOCH_OFFSET, VALIDATOR_COUNT); let mut proposer_slashing = harness.make_proposer_slashing(1); proposer_slashing.signed_header_1.message.slot = Slot::new(0); proposer_slashing.signed_header_2.message.slot = Slot::new(128); let mut state = harness.get_current_state(); let result = process_operations::process_proposer_slashings( &mut state, &[proposer_slashing], VerifySignatures::False, &spec, ); // Expecting ProposalEpochMismatch because the two epochs are different assert_eq!( result, Err(BlockProcessingError::ProposerSlashingInvalid { index: 0, reason: ProposerSlashingInvalid::ProposalSlotMismatch( Slot::from(0_u64), Slot::from(128_u64) ) }) ); }