diff --git a/lighthouse/state/block/structs.rs b/lighthouse/state/block/structs.rs index f22bc370d..eb8833a40 100644 --- a/lighthouse/state/block/structs.rs +++ b/lighthouse/state/block/structs.rs @@ -13,7 +13,7 @@ pub const MIN_SSZ_BLOCK_LENGTH: usize = { }; pub const MAX_SSZ_BLOCK_LENGTH: usize = MIN_SSZ_BLOCK_LENGTH + (1 << 24); -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct Block { pub parent_hash: Hash256, pub slot_number: u64, diff --git a/lighthouse/state/block/validation/tests.rs b/lighthouse/state/block/validation/tests.rs index a8d6f8fba..e0fa153c2 100644 --- a/lighthouse/state/block/validation/tests.rs +++ b/lighthouse/state/block/validation/tests.rs @@ -64,6 +64,7 @@ pub struct TestParams { pub validators_per_shard: usize, pub block_slot: u64, pub attestations_justified_slot: u64, + pub parent_proposer_index: usize, pub validation_context_slot: u64, pub validation_context_justified_slot: u64, pub validation_context_finalized_slot: u64, @@ -107,10 +108,9 @@ pub fn setup_block_validation_scenario(params: &TestParams) let parent_block_ssz = serialize_block(&parent_block); stores.block.put_serialized_block(parent_hash.as_ref(), &parent_block_ssz).unwrap(); - let validator_index: usize = 0; let proposer_map = { let mut proposer_map = ProposerMap::new(); - proposer_map.insert(parent_block.slot_number, validator_index); + proposer_map.insert(parent_block.slot_number, params.parent_proposer_index); proposer_map }; @@ -252,7 +252,16 @@ pub fn run_block_validation_scenario( validator_store: stores.validator.clone(), pow_store: stores.pow_chain.clone() }; - context.validate_ssz_block(&ssz_block) + let validation_status = context.validate_ssz_block(&ssz_block); + /* + * If validation returned a block, make sure it's the same block we supplied to it. + * + * I.e., there were no errors during the serialization -> deserialization process. + */ + if let Ok((_, Some(returned_block))) = &validation_status { + assert_eq!(*returned_block, block); + }; + validation_status } fn get_simple_params() -> TestParams { @@ -263,6 +272,7 @@ fn get_simple_params() -> TestParams { let total_validators: usize = validators_per_shard * shard_count as usize; let block_slot = u64::from(cycle_length) * 10000; let attestations_justified_slot = block_slot - u64::from(cycle_length); + let parent_proposer_index = 0; let validation_context_slot = block_slot; let validation_context_justified_slot = attestations_justified_slot; @@ -274,6 +284,7 @@ fn get_simple_params() -> TestParams { shard_count, shards_per_slot, validators_per_shard, + parent_proposer_index, block_slot, attestations_justified_slot, validation_context_slot, @@ -282,39 +293,178 @@ fn get_simple_params() -> TestParams { } } +// TODO: test bad ssz serialization + #[test] -fn test_block_validation_simple_scenario_valid() { +fn test_block_validation_valid() { let params = get_simple_params(); - let no_mutate = |block, attester_map, proposer_map, stores| { + let mutator = |block: Block, attester_map, proposer_map, stores| { + /* + * Do not mutate + */ (block, attester_map, proposer_map, stores) }; let status = run_block_validation_scenario( ¶ms, - no_mutate); + mutator); assert_eq!(status.unwrap().0, BlockStatus::NewBlock); } #[test] -fn test_block_validation_simple_scenario_invalid_unknown_parent_block() { +fn test_block_validation_valid_known_block() { let params = get_simple_params(); - let no_mutate = |mut block: Block, attester_map, proposer_map, stores| { + let mutator = |block: Block, attester_map, proposer_map, stores: TestStore| { + /* + * Pre-store the block in the database + */ + let block_ssz = serialize_block(&block); + let block_hash = canonical_hash(&block_ssz); + stores.block.put_serialized_block(&block_hash, &block_ssz).unwrap(); + (block, attester_map, proposer_map, stores) + }; + + let status = run_block_validation_scenario( + ¶ms, + mutator); + + assert_eq!(status.unwrap(), (BlockStatus::KnownBlock, None)); +} + +#[test] +fn test_block_validation_invalid_future_slot() { + let params = get_simple_params(); + + let mutator = |mut block: Block, attester_map, proposer_map, stores| { + block.slot_number = block.slot_number + 1; + (block, attester_map, proposer_map, stores) + }; + + let status = run_block_validation_scenario( + ¶ms, + mutator); + + assert_eq!(status, Err(SszBlockValidationError::FutureSlot)); +} + +#[test] +fn test_block_validation_invalid_slot_already_finalized() { + let mut params = get_simple_params(); + + params.validation_context_finalized_slot = params.block_slot; + params.validation_context_justified_slot = params.validation_context_finalized_slot + + u64::from(params.cycle_length); + + let mutator = |block, attester_map, proposer_map, stores| { + /* + * Do not mutate + */ + (block, attester_map, proposer_map, stores) + }; + + let status = run_block_validation_scenario( + ¶ms, + mutator); + + assert_eq!(status, Err(SszBlockValidationError::SlotAlreadyFinalized)); +} + +#[test] +fn test_block_validation_invalid_unknown_pow_hash() { + let params = get_simple_params(); + + let mutator = |mut block: Block, attester_map, proposer_map, stores| { + block.pow_chain_ref = Hash256::from("unknown pow hash".as_bytes()); + (block, attester_map, proposer_map, stores) + }; + + let status = run_block_validation_scenario( + ¶ms, + mutator); + + assert_eq!(status, Err(SszBlockValidationError::UnknownPoWChainRef)); +} + +#[test] +fn test_block_validation_invalid_unknown_parent_hash() { + let params = get_simple_params(); + + let mutator = |mut block: Block, attester_map, proposer_map, stores| { block.parent_hash = Hash256::from("unknown parent block".as_bytes()); (block, attester_map, proposer_map, stores) }; let status = run_block_validation_scenario( ¶ms, - no_mutate); + mutator); assert_eq!(status, Err(SszBlockValidationError::UnknownParentHash)); } #[test] -fn test_block_validation_simple_scenario_invalid_2nd_attestation() { +fn test_block_validation_invalid_1st_attestation_signature() { + let params = get_simple_params(); + + let mutator = |mut block: Block, attester_map, proposer_map, stores| { + /* + * Set the second attestaion record to have an invalid signature. + */ + block.attestations[0].aggregate_sig = AggregateSignature::new(); + (block, attester_map, proposer_map, stores) + }; + + let status = run_block_validation_scenario( + ¶ms, + mutator); + + assert_eq!(status, Err(SszBlockValidationError::FirstAttestationSignatureFailed)); +} + +#[test] +fn test_block_validation_invalid_no_parent_proposer_signature() { + let params = get_simple_params(); + + let mutator = |block: Block, attester_map, mut proposer_map: ProposerMap, stores: TestStore| { + /* + * Set the proposer for this slot to be a validator that does not exist. + */ + let ssz = stores.block.get_serialized_block(&block.parent_hash.as_ref()).unwrap().unwrap(); + let parent_block_slot = SszBlock::from_slice(&ssz[..]).unwrap().slot_number(); + proposer_map.insert(parent_block_slot, params.total_validators + 1); + (block, attester_map, proposer_map, stores) + }; + + let status = run_block_validation_scenario( + ¶ms, + mutator); + + assert_eq!(status, Err(SszBlockValidationError::NoProposerSignature)); +} + +#[test] +fn test_block_validation_invalid_bad_proposer_map() { + let params = get_simple_params(); + + let mutator = |block, attester_map, _, stores| { + /* + * Initialize a new, empty proposer map + */ + let proposer_map = ProposerMap::new(); + (block, attester_map, proposer_map, stores) + }; + + let status = run_block_validation_scenario( + ¶ms, + mutator); + + assert_eq!(status, Err(SszBlockValidationError::BadProposerMap)); +} + +#[test] +fn test_block_validation_invalid_2nd_attestation_signature() { let params = get_simple_params(); let mutator = |mut block: Block, attester_map, proposer_map, stores| {