Further development on block validation

- Return a fully deserialized block from validate_ssz_block
- Ensure the parent_hash field is handled
This commit is contained in:
Paul Hauner 2018-09-29 15:38:54 +09:30
parent e0e8aa98f4
commit bc27be147f
No known key found for this signature in database
GPG Key ID: 303E4494BB28068C
5 changed files with 129 additions and 37 deletions

View File

@ -35,6 +35,13 @@ impl<T: ClientDB> BlockStore<T> {
{ {
self.db.exists(DB_COLUMN, hash) self.db.exists(DB_COLUMN, hash)
} }
pub fn block_exists_in_canonical_chain(&self, hash: &[u8])
-> Result<bool, DBError>
{
// TODO: implement logic for canonical chain
self.db.exists(DB_COLUMN, hash)
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -13,6 +13,7 @@ pub const MIN_SSZ_BLOCK_LENGTH: usize = {
}; };
pub const MAX_SSZ_BLOCK_LENGTH: usize = MIN_SSZ_BLOCK_LENGTH + (1 << 24); pub const MAX_SSZ_BLOCK_LENGTH: usize = MIN_SSZ_BLOCK_LENGTH + (1 << 24);
#[derive(Debug, PartialEq)]
pub struct Block { pub struct Block {
pub parent_hash: Hash256, pub parent_hash: Hash256,
pub slot_number: u64, pub slot_number: u64,

View File

@ -5,7 +5,10 @@ mod tests;
mod benches; mod benches;
use super::attestation_record; use super::attestation_record;
use super::SszBlock; use super::{
SszBlock,
Block,
};
use super::db; use super::db;
use super::ssz; use super::ssz;
use super::utils; use super::utils;

View File

@ -93,6 +93,7 @@ pub fn setup_block_validation_scenario(params: &TestParams)
stores.pow_chain.put_block_hash(pow_chain_ref.as_ref()).unwrap(); stores.pow_chain.put_block_hash(pow_chain_ref.as_ref()).unwrap();
stores.block.put_block(justified_block_hash.as_ref(), &vec![42]).unwrap(); stores.block.put_block(justified_block_hash.as_ref(), &vec![42]).unwrap();
stores.block.put_block(parent_hash.as_ref(), &vec![42]).unwrap();
let validator_index: usize = 0; let validator_index: usize = 0;
let proposer_map = { let proposer_map = {
@ -224,7 +225,7 @@ pub fn run_block_validation_scenario<F>(
validation_last_justified_slot: u64, validation_last_justified_slot: u64,
params: &TestParams, params: &TestParams,
mutator_func: F) mutator_func: F)
-> Result<BlockStatus, SszBlockValidationError> -> Result<(BlockStatus, Option<Block>), SszBlockValidationError>
where F: FnOnce(Block, AttesterMap, ProposerMap, TestStore) where F: FnOnce(Block, AttesterMap, ProposerMap, TestStore)
-> (Block, AttesterMap, ProposerMap, TestStore) -> (Block, AttesterMap, ProposerMap, TestStore)
{ {
@ -277,7 +278,7 @@ fn get_simple_params() -> TestParams {
} }
#[test] #[test]
fn test_block_validation_simple_scenario_valid() { fn test_block_validation_simple_scenario_valid_in_canonical_chain() {
let params = get_simple_params(); let params = get_simple_params();
let validation_slot = params.block_slot; let validation_slot = params.block_slot;
@ -293,7 +294,28 @@ fn test_block_validation_simple_scenario_valid() {
&params, &params,
no_mutate); no_mutate);
assert_eq!(status.unwrap(), BlockStatus::NewBlock); assert_eq!(status.unwrap().0, BlockStatus::NewBlockInCanonicalChain);
}
#[test]
fn test_block_validation_simple_scenario_valid_not_in_canonical_chain() {
let params = get_simple_params();
let validation_slot = params.block_slot;
let validation_last_justified_slot = params.attestations_justified_slot;
let no_mutate = |mut block: Block, attester_map, proposer_map, stores| {
block.parent_hash = Hash256::from("not in canonical chain".as_bytes());
(block, attester_map, proposer_map, stores)
};
let status = run_block_validation_scenario(
validation_slot,
validation_last_justified_slot,
&params,
no_mutate);
assert_eq!(status.unwrap().0, BlockStatus::NewBlockInForkChain);
} }
#[test] #[test]

View File

@ -19,7 +19,10 @@ use super::{
AttesterMap, AttesterMap,
ProposerMap, ProposerMap,
}; };
use super::SszBlock; use super::{
SszBlock,
Block,
};
use super::db::{ use super::db::{
ClientDB, ClientDB,
DBError, DBError,
@ -37,7 +40,8 @@ use super::utils::types::Hash256;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum BlockStatus { pub enum BlockStatus {
NewBlock, NewBlockInCanonicalChain,
NewBlockInForkChain,
KnownBlock, KnownBlock,
} }
@ -45,6 +49,7 @@ pub enum BlockStatus {
pub enum SszBlockValidationError { pub enum SszBlockValidationError {
FutureSlot, FutureSlot,
UnknownPoWChainRef, UnknownPoWChainRef,
UnknownParentHash,
BadAttestationSsz, BadAttestationSsz,
AttestationValidationError(AttestationValidationError), AttestationValidationError(AttestationValidationError),
AttestationSignatureFailed, AttestationSignatureFailed,
@ -68,6 +73,9 @@ pub enum SszBlockValidationError {
/// ///
/// This function will determine if the block is new, already known or invalid (either /// This function will determine if the block is new, already known or invalid (either
/// intrinsically or due to some application error.) /// 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)] #[allow(dead_code)]
pub fn validate_ssz_block<T>(b: &SszBlock, pub fn validate_ssz_block<T>(b: &SszBlock,
expected_slot: u64, expected_slot: u64,
@ -79,29 +87,28 @@ pub fn validate_ssz_block<T>(b: &SszBlock,
block_store: &Arc<BlockStore<T>>, block_store: &Arc<BlockStore<T>>,
validator_store: &Arc<ValidatorStore<T>>, validator_store: &Arc<ValidatorStore<T>>,
pow_store: &Arc<PoWChainStore<T>>) pow_store: &Arc<PoWChainStore<T>>)
-> Result<BlockStatus, SszBlockValidationError> -> Result<(BlockStatus, Option<Block>), SszBlockValidationError>
where T: ClientDB + Sized where T: ClientDB + Sized
{ {
/*
* If this block is already known, return immediately.
*/
if block_store.block_exists(&b.block_hash())? {
return Ok(BlockStatus::KnownBlock);
}
/*
* Copy the block slot (will be used multiple times)
*/
let block_slot = b.slot_number();
/* /*
* If the block slot corresponds to a slot in the future (according to the local time), * If the block slot corresponds to a slot in the future (according to the local time),
* drop it. * drop it.
*/ */
let block_slot = b.slot_number();
if block_slot > expected_slot { if block_slot > expected_slot {
return Err(SszBlockValidationError::FutureSlot); return Err(SszBlockValidationError::FutureSlot);
} }
/*
* 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 PoW chain hash is not known to us, drop it. * If the PoW chain hash is not known to us, drop it.
* *
@ -110,6 +117,7 @@ pub fn validate_ssz_block<T>(b: &SszBlock,
* Note: it is not clear what a "known" PoW chain ref is. Likely, * Note: it is not clear what a "known" PoW chain ref is. Likely,
* it means "sufficienty deep in the canonical PoW chain". * 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())? { if !pow_store.block_hash_exists(b.pow_chain_ref())? {
return Err(SszBlockValidationError::UnknownPoWChainRef); return Err(SszBlockValidationError::UnknownPoWChainRef);
} }
@ -175,21 +183,44 @@ pub fn validate_ssz_block<T>(b: &SszBlock,
/* /*
* Verify each other AttestationRecord. * Verify each other AttestationRecord.
* *
* Note: this uses the `rayon` library to do "sometimes" parallelization. Put simply, * This uses the `rayon` library to do "sometimes" parallelization. Put simply,
* if there's some spare threads the verification of attestation records will happen * if there are some spare threads, the verification of attestation records will happen
* concurrently. * 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: Option<SszBlockValidationError> = None; let failure: RwLock<Option<SszBlockValidationError>> = RwLock::new(None);
let failure = RwLock::new(failure); let deserialized_attestations: Vec<AttestationRecord> = other_attestations
other_attestations.par_iter() .par_iter()
.for_each(|attestation| { .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() { if let Some(_) = *failure.read().unwrap() {
() return None;
}; }
match AttestationRecord::ssz_decode(&attestation, 0) { /*
Ok((a, _)) => { * If there has not been a failure yet, attempt to serialize and validate the
* attestation.
*/
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( let result = validate_attestation(
&a, &attestation,
block_slot, block_slot,
cycle_length, cycle_length,
last_justified_slot, last_justified_slot,
@ -198,23 +229,31 @@ pub fn validate_ssz_block<T>(b: &SszBlock,
&validator_store, &validator_store,
&attester_map); &attester_map);
match result { match result {
/*
* Attestation validation failed with some error.
*/
Err(e) => { Err(e) => {
let mut failure = failure.write().unwrap(); let mut failure = failure.write().unwrap();
*failure = Some(SszBlockValidationError::from(e)); *failure = Some(SszBlockValidationError::from(e));
None
} }
/*
* Attestation validation failed due to a bad signature.
*/
Ok(None) => { Ok(None) => {
let mut failure = failure.write().unwrap(); let mut failure = failure.write().unwrap();
*failure = Some(SszBlockValidationError::AttestationSignatureFailed); *failure = Some(SszBlockValidationError::AttestationSignatureFailed);
None
} }
_ => () /*
* Attestation validation succeded.
*/
Ok(_) => Some(attestation)
} }
} }
Err(e) => { }
let mut failure = failure.write().unwrap(); })
*failure = Some(SszBlockValidationError::from(e)); .collect();
}
};
});
match failure.into_inner() { match failure.into_inner() {
Err(_) => return Err(SszBlockValidationError::RwLockPoisoned), Err(_) => return Err(SszBlockValidationError::RwLockPoisoned),
@ -231,7 +270,27 @@ pub fn validate_ssz_block<T>(b: &SszBlock,
* If we have reached this point, the block is a new valid block that is worthy of * If we have reached this point, the block is a new valid block that is worthy of
* processing. * processing.
*/ */
Ok(BlockStatus::NewBlock)
/*
* 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<DBError> for SszBlockValidationError { impl From<DBError> for SszBlockValidationError {