diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 8634bb0e129..a4f2be87ef4 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -283,26 +283,25 @@ pub fn process_proposer_slashings( verify_signatures: VerifySignatures, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { - // Verify proposer slashings in parallel. + // Verify and apply proposer slashings in series. + // We have to verify in series because an invalid block may contain multiple slashings + // for the same validator, and we need to correctly detect and reject that. proposer_slashings - .par_iter() + .into_iter() .enumerate() .try_for_each(|(i, proposer_slashing)| { verify_proposer_slashing(proposer_slashing, &state, verify_signatures, spec) - .map_err(|e| e.into_with_index(i)) - })?; + .map_err(|e| e.into_with_index(i))?; - // Update the state. - for proposer_slashing in proposer_slashings { - slash_validator( - state, - proposer_slashing.signed_header_1.message.proposer_index as usize, - None, - spec, - )?; - } + slash_validator( + state, + proposer_slashing.signed_header_1.message.proposer_index as usize, + None, + spec, + )?; - Ok(()) + Ok(()) + }) } /// Validates each `AttesterSlashing` and updates the state, short-circuiting on an invalid object. diff --git a/eth2/state_processing/src/per_block_processing/tests.rs b/eth2/state_processing/src/per_block_processing/tests.rs index f942cc29f56..22f30bd265a 100644 --- a/eth2/state_processing/src/per_block_processing/tests.rs +++ b/eth2/state_processing/src/per_block_processing/tests.rs @@ -1029,6 +1029,36 @@ fn invalid_proposer_slashing_not_slashable() { ); } +#[test] +fn invalid_proposer_slashing_duplicate_slashing() { + let spec = MainnetEthSpec::default_spec(); + let builder = get_builder(&spec, SLOT_OFFSET, VALIDATOR_COUNT); + let test_task = ProposerSlashingTestTask::Valid; + let (mut block, mut state) = + builder.build_with_proposer_slashing(test_task, 1, None, None, &spec); + + let slashing = block.message.body.proposer_slashings[0].clone(); + let slashed_proposer = slashing.signed_header_1.message.proposer_index; + block.message.body.proposer_slashings.push(slashing); + + let result = per_block_processing( + &mut state, + &block, + None, + BlockSignatureStrategy::NoVerification, + &spec, + ); + + // Expecting ProposerNotSlashable because we've already slashed the validator + assert_eq!( + result, + Err(BlockProcessingError::ProposerSlashingInvalid { + index: 1, + reason: ProposerSlashingInvalid::ProposerNotSlashable(slashed_proposer) + }) + ); +} + #[test] fn invalid_bad_proposal_1_signature() { let spec = MainnetEthSpec::default_spec();