diff --git a/lighthouse/state/block/validation/tests.rs b/lighthouse/state/block/validation/tests.rs index b2584b677..e5522e37f 100644 --- a/lighthouse/state/block/validation/tests.rs +++ b/lighthouse/state/block/validation/tests.rs @@ -6,6 +6,7 @@ use self::ssz::{ use std::sync::Arc; use super::{ validate_ssz_block, + SszBlockValidationError, BlockStatus, AttesterMap, ProposerMap, @@ -54,162 +55,267 @@ impl TestStore { } } -pub fn generate_attestations_for_slot(attestation_slot: u64, - block_slot: u64, - shard_count: u16, - validators_per_shard: usize, - cycle_length: u8, - parent_hashes: &[Hash256], - shard_block_hash: &Hash256, - justified_block_hash: &Hash256, - justified_slot: u64, - stores: &TestStore) - -> (AttesterMap, Vec, Vec) -{ - let mut i = 0; - let mut attester_map = AttesterMap::new(); - let mut attestations = vec![]; - let mut keypairs = vec![]; - for shard in 0..shard_count { - let mut attesters = vec![]; - let mut attester_bitfield = Bitfield::new(); - let mut aggregate_sig = AggregateSignature::new(); - - let parent_hashes_slice = { - let distance: usize = (block_slot - attestation_slot) as usize; - let last: usize = parent_hashes.len() - distance; - let first: usize = last - usize::from(cycle_length); - &parent_hashes[first..last] - }; - - let attestation_message = { - let mut stream = SszStream::new(); - stream.append(&attestation_slot); - stream.append_vec(&parent_hashes_slice.to_vec()); - stream.append(&shard); - stream.append(shard_block_hash); - stream.append(&justified_slot); - let bytes = stream.drain(); - canonical_hash(&bytes) - }; - - - - for attestation_index in 0..validators_per_shard { - /* - * Add the attester to the attestation indices for this shard. - */ - attesters.push(i); - /* - * Set the voters bit on the bitfield to true. - */ - attester_bitfield.set_bit(attestation_index, true); - /* - * Generate a random keypair for this validatior and clone it into the - * list of keypairs. - */ - let keypair = Keypair::random(); - keypairs.push(keypair.clone()); - /* - * Store the validators public key in the database. - */ - stores.validator.put_public_key_by_index(i, &keypair.pk).unwrap(); - /* - * Generate a new signature and aggregate it on the rolling signature. - */ - let sig = Signature::new(&attestation_message, &keypair.sk); - aggregate_sig.add(&sig); - /* - * Increment the validator counter to monotonically assign validators. - */ - i += 1; - } - - attester_map.insert((attestation_slot, shard), attesters); - let attestation = AttestationRecord { - slot: attestation_slot, - shard_id: shard, - oblique_parent_hashes: vec![], - shard_block_hash: *shard_block_hash, - attester_bitfield, - justified_slot, - justified_block_hash: *justified_block_hash, - aggregate_sig, - }; - attestations.push(attestation); - } - (attester_map, attestations, keypairs) +#[derive(Debug)] +pub struct TestParams { + pub total_validators: usize, + pub cycle_length: u8, + pub shard_count: u16, + pub shards_per_slot: u16, + pub validators_per_shard: usize, + pub block_slot: u64, + pub attestations_justified_slot: u64, } +type ParentHashes = Vec; -#[test] -fn test_block_validation() { +/// Setup for a block validation function, without actually executing the +/// block validation function. +pub fn setup_block_validation_scenario(params: &TestParams) + -> (Block, ParentHashes, AttesterMap, ProposerMap, TestStore) +{ let stores = TestStore::new(); - let cycle_length: u8 = 2; - let shard_count: u16 = 2; - let present_slot = u64::from(cycle_length) * 10000; - let justified_slot = present_slot - u64::from(cycle_length); - let justified_block_hash = Hash256::from("justified_hash".as_bytes()); - let shard_block_hash = Hash256::from("shard_hash".as_bytes()); + let cycle_length = params.cycle_length; + let shards_per_slot = params.shards_per_slot; + let validators_per_shard = params.validators_per_shard; + let block_slot = params.block_slot; + let attestations_justified_slot = params.attestations_justified_slot; + let parent_hashes: Vec = (0..(cycle_length * 2)) .map(|i| Hash256::from(i as u64)) .collect(); + let parent_hash = Hash256::from("parent_hash".as_bytes()); + let randao_reveal = Hash256::from("randao_reveal".as_bytes()); + let justified_block_hash = Hash256::from("justified_hash".as_bytes()); let pow_chain_ref = Hash256::from("pow_chain".as_bytes()); let active_state_root = Hash256::from("active_state".as_bytes()); let crystallized_state_root = Hash256::from("cry_state".as_bytes()); + let shard_block_hash = Hash256::from("shard_block_hash".as_bytes()); stores.pow_chain.put_block_hash(pow_chain_ref.as_ref()).unwrap(); stores.block.put_block(justified_block_hash.as_ref(), &vec![42]).unwrap(); - let validators_per_shard = 10; - - let block_slot = present_slot; let validator_index: usize = 0; let proposer_map = { let mut proposer_map = ProposerMap::new(); - proposer_map.insert(present_slot, validator_index); + proposer_map.insert(block_slot, validator_index); proposer_map }; + /* let attestation_slot = block_slot - 1; let (attester_map, attestations, _keypairs) = generate_attestations_for_slot( attestation_slot, block_slot, - shard_count, + shards_per_slot, validators_per_shard, cycle_length, &parent_hashes, - &shard_block_hash, + &Hash256::from("shard_hash".as_bytes()), &justified_block_hash, - justified_slot, + attestations_justified_slot, &stores); + */ + let (attester_map, attestations, _keypairs) = { + let mut i = 0; + let attestation_slot = block_slot - 1; + let mut attester_map = AttesterMap::new(); + let mut attestations = vec![]; + let mut keypairs = vec![]; + for shard in 0..shards_per_slot { + let mut attesters = vec![]; + let mut attester_bitfield = Bitfield::new(); + let mut aggregate_sig = AggregateSignature::new(); + + let parent_hashes_slice = { + let distance: usize = (block_slot - attestation_slot) as usize; + let last: usize = parent_hashes.len() - distance; + let first: usize = last - usize::from(cycle_length); + &parent_hashes[first..last] + }; + + let attestation_message = { + let mut stream = SszStream::new(); + stream.append(&attestation_slot); + stream.append_vec(&parent_hashes_slice.to_vec()); + stream.append(&shard); + stream.append(&shard_block_hash); + stream.append(&attestations_justified_slot); + let bytes = stream.drain(); + canonical_hash(&bytes) + }; + + + + for attestation_index in 0..validators_per_shard { + /* + * Add the attester to the attestation indices for this shard. + */ + attesters.push(i); + /* + * Set the voters bit on the bitfield to true. + */ + attester_bitfield.set_bit(attestation_index, true); + /* + * Generate a random keypair for this validatior and clone it into the + * list of keypairs. + */ + let keypair = Keypair::random(); + keypairs.push(keypair.clone()); + /* + * Store the validators public key in the database. + */ + stores.validator.put_public_key_by_index(i, &keypair.pk).unwrap(); + /* + * Generate a new signature and aggregate it on the rolling signature. + */ + let sig = Signature::new(&attestation_message, &keypair.sk); + aggregate_sig.add(&sig); + /* + * Increment the validator counter to monotonically assign validators. + */ + i += 1; + } + + attester_map.insert((attestation_slot, shard), attesters); + let attestation = AttestationRecord { + slot: attestation_slot, + shard_id: shard, + oblique_parent_hashes: vec![], + shard_block_hash, + attester_bitfield, + justified_slot: attestations_justified_slot, + justified_block_hash, + aggregate_sig, + }; + attestations.push(attestation); + } + (attester_map, attestations, keypairs) + }; let block = Block { - parent_hash: Hash256::from("parent".as_bytes()), + parent_hash, slot_number: block_slot, - randao_reveal: Hash256::from("randao".as_bytes()), + randao_reveal, attestations, pow_chain_ref, active_state_root, crystallized_state_root, }; - let mut stream = SszStream::new(); - stream.append(&block); - let serialized_block = stream.drain(); - let ssz_block = SszBlock::from_slice(&serialized_block[..]).unwrap(); + (block, + parent_hashes, + attester_map, + proposer_map, + stores) +} - let status = validate_ssz_block( +/// Helper function to take some Block and SSZ serialize it. +pub fn serialize_block(b: &Block) -> Vec { + let mut stream = SszStream::new(); + stream.append(b); + stream.drain() +} + +/// Setup and run a block validation scenario, given some parameters. +/// +/// Returns the Result returned from the block validation function. +pub fn run_block_validation_scenario( + validation_slot: u64, + validation_last_justified_slot: u64, + params: &TestParams, + mutator_func: F) + -> Result + where F: FnOnce(Block, AttesterMap, ProposerMap, TestStore) + -> (Block, AttesterMap, ProposerMap, TestStore) +{ + let (block, + parent_hashes, + attester_map, + proposer_map, + stores) = setup_block_validation_scenario(¶ms); + + let (block, + attester_map, + proposer_map, + stores) = mutator_func(block, attester_map, proposer_map, stores); + + let ssz_bytes = serialize_block(&block); + let ssz_block = SszBlock::from_slice(&ssz_bytes[..]) + .unwrap(); + + validate_ssz_block( &ssz_block, - present_slot, - cycle_length, - justified_slot, + validation_slot, + params.cycle_length, + validation_last_justified_slot, &Arc::new(parent_hashes), &Arc::new(proposer_map), &Arc::new(attester_map), &stores.block.clone(), &stores.validator.clone(), - &stores.pow_chain.clone()).unwrap(); - - assert_eq!(status, BlockStatus::NewBlock); + &stores.pow_chain.clone()) +} + +fn get_simple_params() -> TestParams { + let validators_per_shard: usize = 5; + let cycle_length: u8 = 2; + let shard_count: u16 = 4; + let shards_per_slot: u16 = shard_count / u16::from(cycle_length); + 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); + + TestParams { + total_validators, + cycle_length, + shard_count, + shards_per_slot, + validators_per_shard, + block_slot, + attestations_justified_slot, + } +} + +#[test] +fn test_block_validation_simple_scenario_valid() { + let params = get_simple_params(); + + let validation_slot = params.block_slot; + let validation_last_justified_slot = params.attestations_justified_slot; + + let no_mutate = |block, attester_map, proposer_map, stores| { + (block, attester_map, proposer_map, stores) + }; + + let status = run_block_validation_scenario( + validation_slot, + validation_last_justified_slot, + ¶ms, + no_mutate); + + assert_eq!(status.unwrap(), BlockStatus::NewBlock); +} + +#[test] +fn test_block_validation_simple_scenario_invalid_2nd_attestation() { + let params = get_simple_params(); + + let validation_slot = params.block_slot; + let validation_last_justified_slot = params.attestations_justified_slot; + + let mutator = |mut block: Block, attester_map, proposer_map, stores| { + /* + * Set the second attestaion record to have an invalid signature. + */ + block.attestations[1].aggregate_sig = AggregateSignature::new(); + (block, attester_map, proposer_map, stores) + }; + + let status = run_block_validation_scenario( + validation_slot, + validation_last_justified_slot, + ¶ms, + mutator); + + assert_eq!(status, Err(SszBlockValidationError::AttestationSignatureFailed)); }