diff --git a/lighthouse/state/block/validation/benches.rs b/lighthouse/state/block/validation/benches.rs index 0aca5a03a..3b93e6786 100644 --- a/lighthouse/state/block/validation/benches.rs +++ b/lighthouse/state/block/validation/benches.rs @@ -5,7 +5,7 @@ use self::test::Bencher; use std::sync::Arc; use super::{ - validate_ssz_block, + BlockValidationContext, AttesterMap, ProposerMap, }; @@ -50,17 +50,18 @@ fn bench_block_validation_scenario( let proposer_map = Arc::new(proposer_map); let attester_map = Arc::new(attester_map); b.iter(|| { - validate_ssz_block( - &ssz_block, - validation_slot, - params.cycle_length, - validation_last_justified_slot, - &parent_hashes.clone(), - &proposer_map.clone(), - &attester_map.clone(), - &stores.block.clone(), - &stores.validator.clone(), - &stores.pow_chain.clone()) + let context = BlockValidationContext { + present_slot: validation_slot, + cycle_length: params.cycle_length, + last_justified_slot: validation_last_justified_slot, + parent_hashes: parent_hashes.clone(), + proposer_map: proposer_map.clone(), + attester_map: attester_map.clone(), + block_store: stores.block.clone(), + validator_store: stores.validator.clone(), + pow_store: stores.pow_chain.clone() + }; + context.validate_ssz_block(&ssz_block) }); } diff --git a/lighthouse/state/block/validation/mod.rs b/lighthouse/state/block/validation/mod.rs index 23a485aad..e2382da65 100644 --- a/lighthouse/state/block/validation/mod.rs +++ b/lighthouse/state/block/validation/mod.rs @@ -18,7 +18,7 @@ use super::common::maps::{ ProposerMap, }; pub use self::validate_ssz_block::{ - validate_ssz_block, + BlockValidationContext, SszBlockValidationError, BlockStatus, }; diff --git a/lighthouse/state/block/validation/tests.rs b/lighthouse/state/block/validation/tests.rs index 52975959f..a70a25ece 100644 --- a/lighthouse/state/block/validation/tests.rs +++ b/lighthouse/state/block/validation/tests.rs @@ -5,7 +5,7 @@ use self::ssz::{ }; use std::sync::Arc; use super::{ - validate_ssz_block, + BlockValidationContext, SszBlockValidationError, BlockStatus, AttesterMap, @@ -101,21 +101,7 @@ pub fn setup_block_validation_scenario(params: &TestParams) 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, - shards_per_slot, - validators_per_shard, - cycle_length, - &parent_hashes, - &Hash256::from("shard_hash".as_bytes()), - &justified_block_hash, - attestations_justified_slot, - &stores); - */ + let (attester_map, attestations, _keypairs) = { let mut i = 0; let attestation_slot = block_slot - 1; @@ -244,17 +230,18 @@ pub fn run_block_validation_scenario( let ssz_block = SszBlock::from_slice(&ssz_bytes[..]) .unwrap(); - validate_ssz_block( - &ssz_block, - 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()) + let context = BlockValidationContext { + present_slot: validation_slot, + cycle_length: params.cycle_length, + last_justified_slot: validation_last_justified_slot, + parent_hashes: Arc::new(parent_hashes), + proposer_map: Arc::new(proposer_map), + attester_map: Arc::new(attester_map), + block_store: stores.block.clone(), + validator_store: stores.validator.clone(), + pow_store: stores.pow_chain.clone() + }; + context.validate_ssz_block(&ssz_block) } fn get_simple_params() -> TestParams { diff --git a/lighthouse/state/block/validation/validate_ssz_block.rs b/lighthouse/state/block/validation/validate_ssz_block.rs index abd30f7dd..0ad688145 100644 --- a/lighthouse/state/block/validation/validate_ssz_block.rs +++ b/lighthouse/state/block/validation/validate_ssz_block.rs @@ -60,237 +60,256 @@ pub enum SszBlockValidationError { DatabaseError(String), } -/// Validate some SszBlock. An SszBlock varies from a Block in that is a read-only structure -/// that reads directly from encoded SSZ. -/// -/// The reason to validate an SzzBlock is to avoid decoding it in its entirety if there is -/// a suspicion that the block might be invalid. Such a suspicion should be applied to -/// all blocks coming from the network. -/// -/// Of course, this function will only be more efficient if a block is already serialized. -/// Serializing a complete block and then validating with this function will be less -/// efficient than just validating the original block. -/// -/// This function will determine if the block is new, already known or invalid (either -/// intrinsically or due to some application error.) -/// -/// Note: this function does not implement randao_reveal checking as it is not in the -/// specification. -#[allow(dead_code)] -pub fn validate_ssz_block(b: &SszBlock, - expected_slot: u64, - cycle_length: u8, - last_justified_slot: u64, - parent_hashes: &Arc>, - proposer_map: &Arc, - attester_map: &Arc, - block_store: &Arc>, - validator_store: &Arc>, - pow_store: &Arc>) - -> Result<(BlockStatus, Option), SszBlockValidationError> +/// The context against which a block should be validated. +pub struct BlockValidationContext where T: ClientDB + Sized { + /// The slot as determined by the system time. + pub present_slot: u64, + /// The cycle_length as determined by the chain configuration. + pub cycle_length: u8, + /// The last justified slot as per the client's view of the canonical chain. + pub last_justified_slot: u64, + /// A vec of the hashes of the blocks preceeding the present slot. + pub parent_hashes: Arc>, + /// A map of slots to a block proposer validation index. + pub proposer_map: Arc, + /// A map of (slot, shard_id) to the attestation set of validation indices. + pub attester_map: Arc, + /// The store containing block information. + pub block_store: Arc>, + /// The store containing validator information. + pub validator_store: Arc>, + /// The store containing information about the proof-of-work chain. + pub pow_store: Arc>, +} - /* - * If the block slot corresponds to a slot in the future (according to the local time), - * drop it. - */ - let block_slot = b.slot_number(); - if block_slot > expected_slot { - return Err(SszBlockValidationError::FutureSlot); - } +impl BlockValidationContext + where T: ClientDB +{ + /// Validate some SszBlock against a block validation context. An SszBlock varies from a Block in + /// that is a read-only structure that reads directly from encoded SSZ. + /// + /// The reason to validate an SzzBlock is to avoid decoding it in its entirety if there is + /// a suspicion that the block might be invalid. Such a suspicion should be applied to + /// all blocks coming from the network. + /// + /// Of course, this function will only be more efficient if a block is already serialized. + /// Serializing a complete block and then validating with this function will be less + /// efficient than just validating the original block. + /// + /// This function will determine if the block is new, already known or invalid (either + /// intrinsically or due to some application error.) + /// + /// Note: this function does not implement randao_reveal checking as it is not in the + /// specification. + #[allow(dead_code)] + pub fn validate_ssz_block(&self, b: &SszBlock) + -> Result<(BlockStatus, Option), SszBlockValidationError> + where T: ClientDB + Sized + { - /* - * If this block is already known, return immediately. - */ - let block_hash = &b.block_hash(); - if block_store.block_exists(&block_hash)? { - return Ok((BlockStatus::KnownBlock, None)); - } + /* + * If the block slot corresponds to a slot in the future (according to the local time), + * drop it. + */ + let block_slot = b.slot_number(); + if block_slot > self.present_slot { + return Err(SszBlockValidationError::FutureSlot); + } + + /* + * If this block is already known, return immediately. + */ + let block_hash = &b.block_hash(); + if self.block_store.block_exists(&block_hash)? { + return Ok((BlockStatus::KnownBlock, None)); + } - /* - * If the PoW chain hash is not known to us, drop it. - * - * We only accept blocks that reference a known PoW hash. - * - * Note: it is not clear what a "known" PoW chain ref is. Likely, - * it means "sufficienty deep in the canonical PoW chain". - */ - let pow_chain_ref = b.pow_chain_ref(); - if !pow_store.block_hash_exists(b.pow_chain_ref())? { - return Err(SszBlockValidationError::UnknownPoWChainRef); - } + /* + * If the PoW chain hash is not known to us, drop it. + * + * We only accept blocks that reference a known PoW hash. + * + * Note: it is not clear what a "known" PoW chain ref is. Likely, + * it means "sufficienty deep in the canonical PoW chain". + */ + let pow_chain_ref = b.pow_chain_ref(); + if !self.pow_store.block_hash_exists(b.pow_chain_ref())? { + return Err(SszBlockValidationError::UnknownPoWChainRef); + } - /* - * Store a reference to the serialized attestations from the block. - */ - let attestations_ssz = &b.attestations(); + /* + * Store a reference to the serialized attestations from the block. + */ + let attestations_ssz = &b.attestations(); - /* - * Get a slice of the first serialized attestation (the 0th) and decode it into - * a full AttestationRecord object. - */ - let (first_attestation_ssz, next_index) = split_one_attestation( - &attestations_ssz, - 0)?; - let (first_attestation, _) = AttestationRecord::ssz_decode( - &first_attestation_ssz, 0)?; + /* + * Get a slice of the first serialized attestation (the 0th) and decode it into + * a full AttestationRecord object. + */ + let (first_attestation_ssz, next_index) = split_one_attestation( + &attestations_ssz, + 0)?; + let (first_attestation, _) = AttestationRecord::ssz_decode( + &first_attestation_ssz, 0)?; - /* - * Validate this first attestation. - * - * It is a requirement that the block proposer for this slot - * must have signed the 0th attestation record. - */ - let attestation_voters = validate_attestation( - &first_attestation, - block_slot, - cycle_length, - last_justified_slot, - &parent_hashes, - &block_store, - &validator_store, - &attester_map)?; + /* + * Validate this first attestation. + * + * It is a requirement that the block proposer for this slot + * must have signed the 0th attestation record. + */ + let attestation_voters = validate_attestation( + &first_attestation, + block_slot, + self.cycle_length, + self.last_justified_slot, + &self.parent_hashes, + &self.block_store, + &self.validator_store, + &self.attester_map)?; - /* - * If the set of voters is None, the attestation was invalid. - */ - let attestation_voters = attestation_voters - .ok_or(SszBlockValidationError:: - FirstAttestationSignatureFailed)?; + /* + * If the set of voters is None, the attestation was invalid. + */ + let attestation_voters = attestation_voters + .ok_or(SszBlockValidationError:: + FirstAttestationSignatureFailed)?; - /* - * Read the proposer from the map of slot -> validator index. - */ - let proposer = proposer_map.get(&block_slot) - .ok_or(SszBlockValidationError::BadProposerMap)?; + /* + * Read the proposer from the map of slot -> validator index. + */ + let proposer = self.proposer_map.get(&block_slot) + .ok_or(SszBlockValidationError::BadProposerMap)?; - /* - * If the proposer for this slot was not a voter, reject the block. - */ - if !attestation_voters.contains(&proposer) { - return Err(SszBlockValidationError::NoProposerSignature); - } + /* + * If the proposer for this slot was not a voter, reject the block. + */ + if !attestation_voters.contains(&proposer) { + return Err(SszBlockValidationError::NoProposerSignature); + } - /* - * Split the remaining attestations into a vector of slices, each containing - * a single serialized attestation record. - */ - let other_attestations = split_all_attestations(attestations_ssz, - next_index)?; + /* + * Split the remaining attestations into a vector of slices, each containing + * a single serialized attestation record. + */ + let other_attestations = split_all_attestations(attestations_ssz, + next_index)?; - /* - * Verify each other AttestationRecord. - * - * This uses the `rayon` library to do "sometimes" parallelization. Put simply, - * if there are some spare threads, the verification of attestation records will happen - * concurrently. - * - * There is a thread-safe `failure` variable which is set whenever an attestation fails - * validation. This is so all attestation validation is halted if a single bad attestation - * is found. - */ - let failure: RwLock> = RwLock::new(None); - let deserialized_attestations: Vec = other_attestations - .par_iter() - .filter_map(|attestation_ssz| { - /* - * If some thread has set the `failure` variable to `Some(error)` the abandon - * attestation serialization and validation. - */ - if let Some(_) = *failure.read().unwrap() { - return None; - } - /* - * If there has not been a failure yet, attempt to serialize and validate the - * attestation. - */ - match AttestationRecord::ssz_decode(&attestation_ssz, 0) { + /* + * Verify each other AttestationRecord. + * + * This uses the `rayon` library to do "sometimes" parallelization. Put simply, + * if there are some spare threads, the verification of attestation records will happen + * concurrently. + * + * There is a thread-safe `failure` variable which is set whenever an attestation fails + * validation. This is so all attestation validation is halted if a single bad attestation + * is found. + */ + let failure: RwLock> = RwLock::new(None); + let deserialized_attestations: Vec = other_attestations + .par_iter() + .filter_map(|attestation_ssz| { /* - * Deserialization failed, therefore the block is invalid. + * If some thread has set the `failure` variable to `Some(error)` the abandon + * attestation serialization and validation. */ - Err(e) => { - let mut failure = failure.write().unwrap(); - *failure = Some(SszBlockValidationError::from(e)); - None + if let Some(_) = *failure.read().unwrap() { + return None; } /* - * Deserialization succeeded and the attestation should be validated. + * If there has not been a failure yet, attempt to serialize and validate the + * attestation. */ - Ok((attestation, _)) => { - let result = validate_attestation( - &attestation, - block_slot, - cycle_length, - last_justified_slot, - &parent_hashes, - &block_store, - &validator_store, - &attester_map); - match result { - /* - * Attestation validation failed with some error. - */ - Err(e) => { - let mut failure = failure.write().unwrap(); - *failure = Some(SszBlockValidationError::from(e)); - None + match AttestationRecord::ssz_decode(&attestation_ssz, 0) { + /* + * Deserialization failed, therefore the block is invalid. + */ + Err(e) => { + let mut failure = failure.write().unwrap(); + *failure = Some(SszBlockValidationError::from(e)); + None + } + /* + * Deserialization succeeded and the attestation should be validated. + */ + Ok((attestation, _)) => { + let result = validate_attestation( + &attestation, + block_slot, + self.cycle_length, + self.last_justified_slot, + &self.parent_hashes, + &self.block_store, + &self.validator_store, + &self.attester_map); + match result { + /* + * Attestation validation failed with some error. + */ + Err(e) => { + let mut failure = failure.write().unwrap(); + *failure = Some(SszBlockValidationError::from(e)); + None + } + /* + * Attestation validation failed due to a bad signature. + */ + Ok(None) => { + let mut failure = failure.write().unwrap(); + *failure = Some(SszBlockValidationError::AttestationSignatureFailed); + None + } + /* + * Attestation validation succeded. + */ + Ok(_) => Some(attestation) } - /* - * Attestation validation failed due to a bad signature. - */ - Ok(None) => { - let mut failure = failure.write().unwrap(); - *failure = Some(SszBlockValidationError::AttestationSignatureFailed); - None - } - /* - * Attestation validation succeded. - */ - Ok(_) => Some(attestation) } } - } - }) - .collect(); + }) + .collect(); - match failure.into_inner() { - Err(_) => return Err(SszBlockValidationError::RwLockPoisoned), - Ok(failure) => { - match failure { - Some(error) => return Err(error), - _ => () - } + match failure.into_inner() { + Err(_) => return Err(SszBlockValidationError::RwLockPoisoned), + Ok(failure) => { + match failure { + Some(error) => return Err(error), + _ => () + } + } } + + /* + * If we have reached this point, the block is a new valid block that is worthy of + * processing. + */ + + /* + * If the block's parent_hash _is_ in the canonical chain, the block is a + * new block in the canonical chain. Otherwise, it's a new block in a fork chain. + */ + let parent_hash = b.parent_hash(); + let status = if self.block_store.block_exists_in_canonical_chain(&parent_hash)? { + BlockStatus::NewBlockInCanonicalChain + } else { + BlockStatus::NewBlockInForkChain + }; + let block = Block { + parent_hash: Hash256::from(parent_hash), + slot_number: block_slot, + randao_reveal: Hash256::from(b.randao_reveal()), + attestations: deserialized_attestations, + pow_chain_ref: Hash256::from(pow_chain_ref), + active_state_root: Hash256::from(b.act_state_root()), + crystallized_state_root: Hash256::from(b.cry_state_root()), + }; + Ok((status, Some(block))) } - - /* - * If we have reached this point, the block is a new valid block that is worthy of - * processing. - */ - - /* - * If the block's parent_hash _is_ in the canonical chain, the block is a - * new block in the canonical chain. Otherwise, it's a new block in a fork chain. - */ - let parent_hash = b.parent_hash(); - let status = if block_store.block_exists_in_canonical_chain(&parent_hash)? { - BlockStatus::NewBlockInCanonicalChain - } else { - BlockStatus::NewBlockInForkChain - }; - let block = Block { - parent_hash: Hash256::from(parent_hash), - slot_number: block_slot, - randao_reveal: Hash256::from(b.randao_reveal()), - attestations: deserialized_attestations, - pow_chain_ref: Hash256::from(pow_chain_ref), - active_state_root: Hash256::from(b.act_state_root()), - crystallized_state_root: Hash256::from(b.cry_state_root()), - }; - Ok((status, Some(block))) } impl From for SszBlockValidationError {