From 1e3fd1ff806651aa3f55b691051367223961812e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 3 Dec 2018 16:19:31 +1100 Subject: [PATCH 01/18] Add ssz encode/decode for AttestationData --- beacon_chain/types/src/attestation_data.rs | 65 ++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/beacon_chain/types/src/attestation_data.rs b/beacon_chain/types/src/attestation_data.rs index 9088ce058..f0e7ea4c2 100644 --- a/beacon_chain/types/src/attestation_data.rs +++ b/beacon_chain/types/src/attestation_data.rs @@ -1,3 +1,4 @@ +use super::ssz::{Decodable, DecodeError, Encodable, SszStream}; use super::Hash256; #[derive(Debug, Clone, PartialEq)] @@ -11,3 +12,67 @@ pub struct AttestationData { pub justified_slot: u64, pub justified_block_hash: Hash256, } + +impl Encodable for AttestationData { + fn ssz_append(&self, s: &mut SszStream) { + s.append(&self.slot); + s.append(&self.shard); + s.append(&self.beacon_block_hash); + s.append(&self.epoch_boundary_hash); + s.append(&self.shard_block_hash); + s.append(&self.latest_crosslink_hash); + s.append(&self.justified_slot); + s.append(&self.justified_block_hash); + } +} + +impl Decodable for AttestationData { + fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { + let (slot, i) = u64::ssz_decode(bytes, i)?; + let (shard, i) = u64::ssz_decode(bytes, i)?; + let (beacon_block_hash, i) = Hash256::ssz_decode(bytes, i)?; + let (epoch_boundary_hash, i) = Hash256::ssz_decode(bytes, i)?; + let (shard_block_hash, i) = Hash256::ssz_decode(bytes, i)?; + let (latest_crosslink_hash, i) = Hash256::ssz_decode(bytes, i)?; + let (justified_slot, i) = u64::ssz_decode(bytes, i)?; + let (justified_block_hash, i) = Hash256::ssz_decode(bytes, i)?; + + let attestation_data = AttestationData { + slot, + shard, + beacon_block_hash, + epoch_boundary_hash, + shard_block_hash, + latest_crosslink_hash, + justified_slot, + justified_block_hash, + }; + Ok((attestation_data, i)) + } +} + +#[cfg(test)] +mod tests { + use super::super::ssz::ssz_encode; + use super::*; + + #[test] + pub fn test_attestation_record_ssz_round_trip() { + let original = AttestationData { + slot: 42, + shard: 16, + beacon_block_hash: Hash256::from("beacon".as_bytes()), + epoch_boundary_hash: Hash256::from("epoch".as_bytes()), + shard_block_hash: Hash256::from("shard".as_bytes()), + latest_crosslink_hash: Hash256::from("xlink".as_bytes()), + justified_slot: 8, + justified_block_hash: Hash256::from("justified".as_bytes()), + }; + + let ssz = ssz_encode(&original); + + let (decoded, _) = AttestationData::ssz_decode(&ssz, 0).unwrap(); + + assert_eq!(original, decoded); + } +} From bedc1abec00ef72f57b447c0136937cc39bf5232 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 3 Dec 2018 17:13:39 +1100 Subject: [PATCH 02/18] Add failing boolean bitfield test --- beacon_chain/utils/boolean-bitfield/src/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/beacon_chain/utils/boolean-bitfield/src/lib.rs b/beacon_chain/utils/boolean-bitfield/src/lib.rs index e0adc64dd..dae3ed32f 100644 --- a/beacon_chain/utils/boolean-bitfield/src/lib.rs +++ b/beacon_chain/utils/boolean-bitfield/src/lib.rs @@ -153,7 +153,7 @@ impl ssz::Decodable for BooleanBitfield { #[cfg(test)] mod tests { use super::*; - use ssz::SszStream; + use ssz::{ssz_encode, Decodable, SszStream}; #[test] fn test_new_bitfield() { @@ -341,4 +341,12 @@ mod tests { let expected = BooleanBitfield::from_elem(18, true); assert_eq!(field, expected); } + + #[test] + fn test_ssz_round_trip() { + let original = BooleanBitfield::from_bytes(&vec![18; 12][..]); + let ssz = ssz_encode(&original); + let (decoded, _) = BooleanBitfield::ssz_decode(&ssz, 0).unwrap(); + assert_eq!(original, decoded); + } } From 6e0daec1da81fa5b2f1a5b2752fc3376e8a54761 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 4 Dec 2018 14:50:32 +1100 Subject: [PATCH 03/18] Update AttestationData struct --- beacon_chain/types/src/attestation_data.rs | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/beacon_chain/types/src/attestation_data.rs b/beacon_chain/types/src/attestation_data.rs index f0e7ea4c2..0e87ace6a 100644 --- a/beacon_chain/types/src/attestation_data.rs +++ b/beacon_chain/types/src/attestation_data.rs @@ -1,6 +1,17 @@ use super::ssz::{Decodable, DecodeError, Encodable, SszStream}; use super::Hash256; +pub const SSZ_ATTESTION_DATA_LENGTH: usize = { + 8 + // slot + 8 + // shard + 32 + // beacon_block_hash + 32 + // epoch_boundary_hash + 32 + // shard_block_hash + 32 + // latest_crosslink_hash + 8 + // justified_slot + 32 // justified_block_hash +}; + #[derive(Debug, Clone, PartialEq)] pub struct AttestationData { pub slot: u64, @@ -13,6 +24,21 @@ pub struct AttestationData { pub justified_block_hash: Hash256, } +impl AttestationData { + pub fn zero() -> Self { + Self { + slot: 0, + shard: 0, + beacon_block_hash: Hash256::zero(), + epoch_boundary_hash: Hash256::zero(), + shard_block_hash: Hash256::zero(), + latest_crosslink_hash: Hash256::zero(), + justified_slot: 0, + justified_block_hash: Hash256::zero(), + } + } +} + impl Encodable for AttestationData { fn ssz_append(&self, s: &mut SszStream) { s.append(&self.slot); From e0360edde0c2763bb6a3f4eabd0bcda911ec6b07 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 4 Dec 2018 14:50:47 +1100 Subject: [PATCH 04/18] Update `ssz_utils` for new `AttestationRecord`. --- beacon_chain/types/src/attestation_record.rs | 102 ++++++------------ .../src/attestation_ssz_splitter.rs | 89 ++++++++++----- .../utils/ssz_helpers/src/ssz_beacon_block.rs | 4 +- 3 files changed, 97 insertions(+), 98 deletions(-) diff --git a/beacon_chain/types/src/attestation_record.rs b/beacon_chain/types/src/attestation_record.rs index e4ddffd62..9cf4a60e9 100644 --- a/beacon_chain/types/src/attestation_record.rs +++ b/beacon_chain/types/src/attestation_record.rs @@ -1,65 +1,45 @@ +use super::attestation_data::SSZ_ATTESTION_DATA_LENGTH; use super::bls::{AggregateSignature, BLS_AGG_SIG_BYTE_SIZE}; -use super::ssz::{decode_ssz_list, Decodable, DecodeError, Encodable, SszStream}; -use super::{Bitfield, Hash256}; +use super::ssz::{decode_ssz_list, Decodable, DecodeError, Encodable, SszStream, LENGTH_BYTES}; +use super::{AttestationData, Bitfield}; pub const MIN_SSZ_ATTESTION_RECORD_LENGTH: usize = { - 8 + // slot - 2 + // shard_id - 4 + // oblique_parent_hashes (empty list) - 32 + // shard_block_hash - 5 + // attester_bitfield (assuming 1 byte of bitfield) - 8 + // justified_slot - 32 + // justified_block_hash - 4 + BLS_AGG_SIG_BYTE_SIZE // aggregate sig (two 256 bit points) + SSZ_ATTESTION_DATA_LENGTH + // data + 5 + // participation_bitfield (assuming 1 byte of bitfield) + 5 + // custody_bitfield (assuming 1 byte of bitfield) + LENGTH_BYTES + BLS_AGG_SIG_BYTE_SIZE // aggregate sig }; #[derive(Debug, Clone, PartialEq)] pub struct AttestationRecord { - pub slot: u64, - pub shard_id: u16, - pub oblique_parent_hashes: Vec, - pub shard_block_hash: Hash256, - pub attester_bitfield: Bitfield, - pub justified_slot: u64, - pub justified_block_hash: Hash256, + pub data: AttestationData, + pub participation_bitfield: Bitfield, + pub custody_bitfield: Bitfield, pub aggregate_sig: AggregateSignature, } impl Encodable for AttestationRecord { fn ssz_append(&self, s: &mut SszStream) { - s.append(&self.slot); - s.append(&self.shard_id); - s.append_vec(&self.oblique_parent_hashes); - s.append(&self.shard_block_hash); - s.append(&self.attester_bitfield); - s.append(&self.justified_slot); - s.append(&self.justified_block_hash); + s.append(&self.data); + s.append(&self.participation_bitfield); + s.append(&self.custody_bitfield); s.append_vec(&self.aggregate_sig.as_bytes()); } } impl Decodable for AttestationRecord { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { - let (slot, i) = u64::ssz_decode(bytes, i)?; - let (shard_id, i) = u16::ssz_decode(bytes, i)?; - let (oblique_parent_hashes, i) = decode_ssz_list(bytes, i)?; - let (shard_block_hash, i) = Hash256::ssz_decode(bytes, i)?; - let (attester_bitfield, i) = Bitfield::ssz_decode(bytes, i)?; - let (justified_slot, i) = u64::ssz_decode(bytes, i)?; - let (justified_block_hash, i) = Hash256::ssz_decode(bytes, i)?; - // Do aggregate sig decoding properly. + let (data, i) = AttestationData::ssz_decode(bytes, i)?; + let (participation_bitfield, i) = Bitfield::ssz_decode(bytes, i)?; + let (custody_bitfield, i) = Bitfield::ssz_decode(bytes, i)?; let (agg_sig_bytes, i) = decode_ssz_list(bytes, i)?; let aggregate_sig = AggregateSignature::from_bytes(&agg_sig_bytes).map_err(|_| DecodeError::TooShort)?; // also could be TooLong let attestation_record = Self { - slot, - shard_id, - oblique_parent_hashes, - shard_block_hash, - attester_bitfield, - justified_slot, - justified_block_hash, + data, + participation_bitfield, + custody_bitfield, aggregate_sig, }; Ok((attestation_record, i)) @@ -69,13 +49,9 @@ impl Decodable for AttestationRecord { impl AttestationRecord { pub fn zero() -> Self { Self { - slot: 0, - shard_id: 0, - oblique_parent_hashes: vec![], - shard_block_hash: Hash256::zero(), - attester_bitfield: Bitfield::new(), - justified_slot: 0, - justified_block_hash: Hash256::zero(), + data: AttestationData::zero(), + participation_bitfield: Bitfield::new(), + custody_bitfield: Bitfield::new(), aggregate_sig: AggregateSignature::new(), } } @@ -83,45 +59,29 @@ impl AttestationRecord { #[cfg(test)] mod tests { - use super::super::ssz::SszStream; + use super::super::ssz::ssz_encode; use super::*; #[test] pub fn test_attestation_record_min_ssz_length() { let ar = AttestationRecord::zero(); - let mut ssz_stream = SszStream::new(); - ssz_stream.append(&ar); - let ssz = ssz_stream.drain(); + let ssz = ssz_encode(&ar); assert_eq!(ssz.len(), MIN_SSZ_ATTESTION_RECORD_LENGTH); } #[test] - pub fn test_attestation_record_min_ssz_encode_decode() { + pub fn test_attestation_record_ssz_round_trip() { let original = AttestationRecord { - slot: 7, - shard_id: 9, - oblique_parent_hashes: vec![Hash256::from(&vec![14; 32][..])], - shard_block_hash: Hash256::from(&vec![15; 32][..]), - attester_bitfield: Bitfield::from_bytes(&vec![17; 42][..]), - justified_slot: 19, - justified_block_hash: Hash256::from(&vec![15; 32][..]), + data: AttestationData::zero(), + participation_bitfield: Bitfield::from_bytes(&vec![17; 42][..]), + custody_bitfield: Bitfield::from_bytes(&vec![18; 12][..]), aggregate_sig: AggregateSignature::new(), }; - let mut ssz_stream = SszStream::new(); - ssz_stream.append(&original); + let ssz = ssz_encode(&original); + let (decoded, _) = AttestationRecord::ssz_decode(&ssz, 0).unwrap(); - let (decoded, _) = AttestationRecord::ssz_decode(&ssz_stream.drain(), 0).unwrap(); - assert_eq!(original.slot, decoded.slot); - assert_eq!(original.shard_id, decoded.shard_id); - assert_eq!( - original.oblique_parent_hashes, - decoded.oblique_parent_hashes - ); - assert_eq!(original.shard_block_hash, decoded.shard_block_hash); - assert_eq!(original.attester_bitfield, decoded.attester_bitfield); - assert_eq!(original.justified_slot, decoded.justified_slot); - assert_eq!(original.justified_block_hash, decoded.justified_block_hash); + assert_eq!(original, decoded); } } diff --git a/beacon_chain/utils/ssz_helpers/src/attestation_ssz_splitter.rs b/beacon_chain/utils/ssz_helpers/src/attestation_ssz_splitter.rs index a2f05ccb7..6b65ae0aa 100644 --- a/beacon_chain/utils/ssz_helpers/src/attestation_ssz_splitter.rs +++ b/beacon_chain/utils/ssz_helpers/src/attestation_ssz_splitter.rs @@ -1,6 +1,8 @@ +use super::bls::BLS_AGG_SIG_BYTE_SIZE; use super::ssz::decode::decode_length; use super::ssz::LENGTH_BYTES; -use super::types::attestation_record::MIN_SSZ_ATTESTION_RECORD_LENGTH as MIN_LENGTH; +use super::types::attestation_data::SSZ_ATTESTION_DATA_LENGTH; +use super::types::attestation_record::MIN_SSZ_ATTESTION_RECORD_LENGTH; #[derive(Debug, PartialEq)] pub enum AttestationSplitError { @@ -29,52 +31,89 @@ pub fn split_one_attestation( full_ssz: &[u8], index: usize, ) -> Result<(&[u8], usize), AttestationSplitError> { - if full_ssz.len() < MIN_LENGTH { + let length = determine_ssz_attestation_len(full_ssz, index)?; + let end = index + length; + + // The check to ensure that the slice exists _should_ be redundant as it is already checked in + // `determine_ssz_attestation_len`, however it is checked here again for additional safety + // against panics. + match full_ssz.get(index..end) { + None => Err(AttestationSplitError::TooShort), + Some(slice) => Ok((slice, end)), + } +} + +/// Given some SSZ, assume that a serialized `AttestationRecord` begins at the `index` position and +/// attempt to find the length (in bytes) of that serialized `AttestationRecord`. +/// +/// This function does not perform validation on the `AttestationRecord`. It is very likely that +/// given some sufficiently long non-`AttestationRecord` bytes it will not raise an error. +fn determine_ssz_attestation_len( + full_ssz: &[u8], + index: usize, +) -> Result { + if full_ssz.len() < MIN_SSZ_ATTESTION_RECORD_LENGTH { return Err(AttestationSplitError::TooShort); } - let hashes_len = decode_length(full_ssz, index + 10, LENGTH_BYTES) + let data_struct_end = index + SSZ_ATTESTION_DATA_LENGTH; + + // Determine the end of the first bitfield. + let participation_bitfield_len = decode_length(full_ssz, data_struct_end, LENGTH_BYTES) .map_err(|_| AttestationSplitError::TooShort)?; + let participation_bitfield_end = data_struct_end + LENGTH_BYTES + participation_bitfield_len; - let bitfield_len = decode_length(full_ssz, index + hashes_len + 46, LENGTH_BYTES) + // Determine the end of the second bitfield. + let custody_bitfield_len = decode_length(full_ssz, participation_bitfield_end, LENGTH_BYTES) .map_err(|_| AttestationSplitError::TooShort)?; + let custody_bitfield_end = participation_bitfield_end + LENGTH_BYTES + custody_bitfield_len; - // Subtract one because the min length assumes 1 byte of bitfield - let len = MIN_LENGTH - 1 + hashes_len + bitfield_len; + // Determine the very end of the AttestationRecord. + let agg_sig_end = custody_bitfield_end + LENGTH_BYTES + BLS_AGG_SIG_BYTE_SIZE; - if full_ssz.len() < index + len { - return Err(AttestationSplitError::TooShort); + if agg_sig_end > full_ssz.len() { + Err(AttestationSplitError::TooShort) + } else { + Ok(agg_sig_end - index) } - - Ok((&full_ssz[index..(index + len)], index + len)) } #[cfg(test)] mod tests { use super::super::bls::AggregateSignature; use super::super::ssz::{Decodable, SszStream}; - use super::super::types::{AttestationRecord, Bitfield, Hash256}; + use super::super::types::{AttestationData, AttestationRecord, Bitfield, Hash256}; use super::*; fn get_two_records() -> Vec { let a = AttestationRecord { - slot: 7, - shard_id: 9, - oblique_parent_hashes: vec![Hash256::from(&vec![14; 32][..])], - shard_block_hash: Hash256::from(&vec![15; 32][..]), - attester_bitfield: Bitfield::from_bytes(&vec![17; 42][..]), - justified_slot: 19, - justified_block_hash: Hash256::from(&vec![15; 32][..]), + data: AttestationData { + slot: 7, + shard: 9, + beacon_block_hash: Hash256::from("a_beacon".as_bytes()), + epoch_boundary_hash: Hash256::from("a_epoch".as_bytes()), + shard_block_hash: Hash256::from("a_shard".as_bytes()), + latest_crosslink_hash: Hash256::from("a_xlink".as_bytes()), + justified_slot: 19, + justified_block_hash: Hash256::from("a_justified".as_bytes()), + }, + participation_bitfield: Bitfield::from_bytes(&vec![17; 42][..]), + custody_bitfield: Bitfield::from_bytes(&vec![255; 12][..]), aggregate_sig: AggregateSignature::new(), }; let b = AttestationRecord { - slot: 9, - shard_id: 7, - oblique_parent_hashes: vec![Hash256::from(&vec![15; 32][..])], - shard_block_hash: Hash256::from(&vec![14; 32][..]), - attester_bitfield: Bitfield::from_bytes(&vec![19; 42][..]), - justified_slot: 15, - justified_block_hash: Hash256::from(&vec![17; 32][..]), + data: AttestationData { + slot: 9, + shard: 7, + beacon_block_hash: Hash256::from("b_beacon".as_bytes()), + epoch_boundary_hash: Hash256::from("b_epoch".as_bytes()), + shard_block_hash: Hash256::from("b_shard".as_bytes()), + latest_crosslink_hash: Hash256::from("b_xlink".as_bytes()), + justified_slot: 15, + justified_block_hash: Hash256::from("b_justified".as_bytes()), + }, + participation_bitfield: Bitfield::from_bytes(&vec![1; 42][..]), + custody_bitfield: Bitfield::from_bytes(&vec![11; 3][..]), aggregate_sig: AggregateSignature::new(), }; vec![a, b] diff --git a/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs b/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs index d61f6e19c..2427ef4c8 100644 --- a/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs +++ b/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs @@ -293,8 +293,8 @@ mod tests { // will tell us if the hash changes, not that it matches some // canonical reference. let expected_hash = [ - 11, 181, 149, 114, 248, 15, 46, 0, 106, 135, 158, 31, 15, 194, 149, 176, 43, 110, 154, - 26, 253, 67, 18, 139, 250, 84, 144, 219, 3, 208, 50, 145, + 254, 192, 124, 164, 240, 137, 162, 126, 50, 255, 118, 88, 189, 151, 221, 4, 40, 121, + 198, 33, 248, 221, 104, 255, 46, 234, 146, 161, 202, 140, 109, 175, ]; assert_eq!(hash, expected_hash); From 564f13be5fba4cf75979c0e7d2d420cf21c7970a Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 3 Dec 2018 20:45:43 -0800 Subject: [PATCH 05/18] fixes bug with serialization logic for boolean bitfield should match the python impl --- .../utils/boolean-bitfield/src/lib.rs | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/beacon_chain/utils/boolean-bitfield/src/lib.rs b/beacon_chain/utils/boolean-bitfield/src/lib.rs index dae3ed32f..fadb47083 100644 --- a/beacon_chain/utils/boolean-bitfield/src/lib.rs +++ b/beacon_chain/utils/boolean-bitfield/src/lib.rs @@ -23,6 +23,10 @@ impl BooleanBitfield { Default::default() } + pub fn with_capacity(initial_len: usize) -> Self { + Self::from_elem(initial_len, false) + } + /// Create a new bitfield with the given length `initial_len` and all values set to `bit`. pub fn from_elem(inital_len: usize, bit: bool) -> Self { Self { @@ -100,25 +104,10 @@ impl default::Default for BooleanBitfield { } } -// borrowed from bit_vec crate -fn reverse_bits(byte: u8) -> u8 { - let mut result = 0; - for i in 0..8 { - result = result | ((byte >> i) & 1) << (7 - i); - } - result -} - impl ssz::Encodable for BooleanBitfield { // ssz_append encodes Self according to the `ssz` spec. - // Note that we have to flip the endianness of the encoding with `reverse_bits` to account for an implementation detail of `bit-vec` crate. fn ssz_append(&self, s: &mut ssz::SszStream) { - let bytes: Vec = self - .to_bytes() - .iter() - .map(|&byte| reverse_bits(byte)) - .collect(); - s.append_vec(&bytes); + s.append_vec(&self.to_bytes()) } } @@ -134,10 +123,11 @@ impl ssz::Decodable for BooleanBitfield { } else { let bytes = &bytes[(index + 4)..(index + len + 4)]; - let mut field = BooleanBitfield::from_elem(0, false); + let count = len * 8; + let mut field = BooleanBitfield::with_capacity(count); for (byte_index, byte) in bytes.iter().enumerate() { for i in 0..8 { - let bit = byte & (1 << i); + let bit = byte & (128 >> i); if bit != 0 { field.set(8 * byte_index + i, true); } @@ -317,28 +307,39 @@ mod tests { #[test] fn test_ssz_encode() { - let field = BooleanBitfield::from_elem(5, true); + let field = create_test_bitfield(); let mut stream = SszStream::new(); stream.append(&field); - assert_eq!(stream.drain(), vec![0, 0, 0, 1, 31]); + assert_eq!(stream.drain(), vec![0, 0, 0, 2, 225, 192]); let field = BooleanBitfield::from_elem(18, true); let mut stream = SszStream::new(); stream.append(&field); - assert_eq!(stream.drain(), vec![0, 0, 0, 3, 255, 255, 3]); + assert_eq!(stream.drain(), vec![0, 0, 0, 3, 255, 255, 192]); + } + + fn create_test_bitfield() -> BooleanBitfield { + let count = 2 * 8; + let mut field = BooleanBitfield::with_capacity(count); + + let indices = &[0, 1, 2, 7, 8, 9]; + for &i in indices { + field.set(i, true); + } + field } #[test] fn test_ssz_decode() { - let encoded = vec![0, 0, 0, 1, 31]; + let encoded = vec![0, 0, 0, 2, 225, 192]; let (field, _): (BooleanBitfield, usize) = ssz::decode_ssz(&encoded, 0).unwrap(); - let expected = BooleanBitfield::from_elem(5, true); + let expected = create_test_bitfield(); assert_eq!(field, expected); let encoded = vec![0, 0, 0, 3, 255, 255, 3]; let (field, _): (BooleanBitfield, usize) = ssz::decode_ssz(&encoded, 0).unwrap(); - let expected = BooleanBitfield::from_elem(18, true); + let expected = BooleanBitfield::from_bytes(&[255, 255, 3]); assert_eq!(field, expected); } From 3533b8b89200215e44197a107721c1f82b0961ba Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 5 Dec 2018 16:08:03 +1100 Subject: [PATCH 06/18] Add untested attestation validation logic --- Cargo.toml | 3 +- .../attestation_validation/Cargo.toml | 12 + .../attestation_validation/src/enums.rs | 34 ++ .../attestation_validation/src/lib.rs | 18 + .../attestation_validation/src/macros.rs | 19 + .../src/validate_for_block.rs | 33 ++ .../src/validate_for_state.rs | 107 +++++ .../src/validate_signature.rs | 64 +++ beacon_chain/block_validation/Cargo.toml | 14 + .../block_validation/src/block_validation.rs | 371 ++++++++++++++++++ beacon_chain/block_validation/src/lib.rs | 9 + beacon_chain/types/src/attestation_data.rs | 6 + beacon_chain/types/src/chain_config.rs | 13 + beacon_chain/types/src/crosslink_record.rs | 19 +- 14 files changed, 704 insertions(+), 18 deletions(-) create mode 100644 beacon_chain/attestation_validation/Cargo.toml create mode 100644 beacon_chain/attestation_validation/src/enums.rs create mode 100644 beacon_chain/attestation_validation/src/lib.rs create mode 100644 beacon_chain/attestation_validation/src/macros.rs create mode 100644 beacon_chain/attestation_validation/src/validate_for_block.rs create mode 100644 beacon_chain/attestation_validation/src/validate_for_state.rs create mode 100644 beacon_chain/attestation_validation/src/validate_signature.rs create mode 100644 beacon_chain/block_validation/Cargo.toml create mode 100644 beacon_chain/block_validation/src/block_validation.rs create mode 100644 beacon_chain/block_validation/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index f2fe2aa7d..cc77c734f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,8 @@ name = "lighthouse" [workspace] members = [ + "beacon_chain/attestation_validation", + "beacon_chain/block_validation", "beacon_chain/chain", "beacon_chain/naive_fork_choice", "beacon_chain/state-transition", @@ -45,7 +47,6 @@ members = [ "beacon_chain/utils/ssz", "beacon_chain/utils/ssz_helpers", "beacon_chain/utils/vec_shuffle", - "beacon_chain/validation", "beacon_chain/validator_change", "beacon_chain/validator_induction", "beacon_chain/validator_shuffling", diff --git a/beacon_chain/attestation_validation/Cargo.toml b/beacon_chain/attestation_validation/Cargo.toml new file mode 100644 index 000000000..4606d03ec --- /dev/null +++ b/beacon_chain/attestation_validation/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "attestation_validation" +version = "0.1.0" +authors = ["Paul Hauner "] + +[dependencies] +bls = { path = "../utils/bls" } +db = { path = "../../lighthouse/db" } +hashing = { path = "../utils/hashing" } +ssz = { path = "../utils/ssz" } +ssz_helpers = { path = "../utils/ssz_helpers" } +types = { path = "../types" } diff --git a/beacon_chain/attestation_validation/src/enums.rs b/beacon_chain/attestation_validation/src/enums.rs new file mode 100644 index 000000000..3174d1154 --- /dev/null +++ b/beacon_chain/attestation_validation/src/enums.rs @@ -0,0 +1,34 @@ +/// Reasons why an `AttestationRecord` can be invalid. +pub enum Invalid { + AttestationTooRecent, + AttestationTooOld, + JustifiedSlotImpermissable, + JustifiedBlockNotInChain, + JustifiedBlockHashMismatch, + UnknownShard, + ShardBlockHashMismatch, + SignatureInvalid, +} + +/// The outcome of validating the `AttestationRecord`. +/// +/// Distinct from the `Error` enum as an `Outcome` indicates that validation executed sucessfully +/// and determined the validity `AttestationRecord`. +pub enum Outcome { + Valid, + Invalid(Invalid), +} + +/// Errors that prevent this function from correctly validating the `AttestationRecord`. +/// +/// Distinct from the `Outcome` enum as `Errors` indicate that validation encountered an unexpected +/// condition and was unable to perform its duty. +pub enum Error { + BlockHasNoParent, + BadValidatorIndex, + UnableToLookupBlockAtSlot, + OutOfBoundsBitfieldIndex, + PublicKeyCorrupt, + NoPublicKeyForValidator, + DBError(String), +} diff --git a/beacon_chain/attestation_validation/src/lib.rs b/beacon_chain/attestation_validation/src/lib.rs new file mode 100644 index 000000000..8a54a3194 --- /dev/null +++ b/beacon_chain/attestation_validation/src/lib.rs @@ -0,0 +1,18 @@ +extern crate bls; +extern crate db; +extern crate hashing; +extern crate ssz; +extern crate ssz_helpers; +extern crate types; + +#[macro_use] +mod macros; +mod enums; +mod validate_for_block; +mod validate_for_state; +mod validate_signature; + +pub use enums::{Invalid, Outcome, Error}; +pub use validate_for_block::validate_attestation_for_block; +pub use validate_for_state::validate_attestation_data_for_state; +pub use validate_signature::validate_attestation_signature; diff --git a/beacon_chain/attestation_validation/src/macros.rs b/beacon_chain/attestation_validation/src/macros.rs new file mode 100644 index 000000000..faae00fcf --- /dev/null +++ b/beacon_chain/attestation_validation/src/macros.rs @@ -0,0 +1,19 @@ +macro_rules! verify_or { + ($condition: expr, $result: expr) => { + if !$condition { + $result + } + }; +} + +macro_rules! reject { + ($result: expr) => { + return Ok(Outcome::Invalid($result)); + }; +} + +macro_rules! accept { + () => { + Ok(Outcome::Valid) + }; +} diff --git a/beacon_chain/attestation_validation/src/validate_for_block.rs b/beacon_chain/attestation_validation/src/validate_for_block.rs new file mode 100644 index 000000000..0094f5c59 --- /dev/null +++ b/beacon_chain/attestation_validation/src/validate_for_block.rs @@ -0,0 +1,33 @@ +use super::{Error, Invalid, Outcome}; + +/// Check that an attestation is valid to be included in some block. +pub fn validate_attestation_for_block( + attestation_slot: u64, + block_slot: u64, + parent_block_slot: u64, + min_attestation_inclusion_delay: u64, + epoch_length: u64, +) -> Result { + /* + * There is a delay before an attestation may be included in a block, quantified by + * `slots` and defined as `min_attestation_inclusion_delay`. + * + * So, an attestation must be at least `min_attestation_inclusion_delay` slots "older" than the + * block it is contained in. + */ + verify_or!( + attestation_slot <= block_slot.saturating_sub(min_attestation_inclusion_delay), + reject!(Invalid::AttestationTooRecent) + ); + + /* + * A block may not include attestations reference slots more than an epoch length + 1 prior to + * the block slot. + */ + verify_or!( + attestation_slot >= parent_block_slot.saturating_sub(epoch_length + 1), + reject!(Invalid::AttestationTooOld) + ); + + accept!() +} diff --git a/beacon_chain/attestation_validation/src/validate_for_state.rs b/beacon_chain/attestation_validation/src/validate_for_state.rs new file mode 100644 index 000000000..9312dd125 --- /dev/null +++ b/beacon_chain/attestation_validation/src/validate_for_state.rs @@ -0,0 +1,107 @@ +use super::db::stores::{BeaconBlockAtSlotError, BeaconBlockStore}; +use super::db::ClientDB; +use super::types::Hash256; +use super::types::{AttestationData, BeaconState}; +use super::{Error, Invalid, Outcome}; +use std::sync::Arc; + +/// Check that an attestation is valid with reference to some state. +pub fn validate_attestation_data_for_state( + data: &AttestationData, + chain_tip_block_hash: &Hash256, + state: &BeaconState, + block_store: &Arc>, +) -> Result +where + T: ClientDB + Sized, +{ + /* + * The attestation's `justified_slot` must be the same as the last justified slot known to this + * client. + * + * In the case that an attestation references a slot _before_ the latest state transition, it + * is acceptable for the attestation to reference the previous known `justified_slot`. If this + * were not the case, all attestations created _prior_ to the last state recalculation would be + * rejected if a block was justified in that state recalculation. It is both ideal and likely + * that blocks will be justified during a state recalcuation. + */ + { + let permissable_justified_slot = if data.slot >= state.latest_state_recalculation_slot { + state.justified_slot + } else { + state.previous_justified_slot + }; + verify_or!( + data.justified_slot == permissable_justified_slot, + reject!(Invalid::JustifiedSlotImpermissable) + ); + } + + /* + * The `justified_block_hash` in the attestation must match exactly the hash of the block at + * that slot in the local chain. + * + * This condition also infers that the `justified_slot` specified in attestation must exist + * locally. + */ + match block_hash_at_slot(chain_tip_block_hash, data.justified_slot, block_store)? { + None => reject!(Invalid::JustifiedBlockNotInChain), + Some(local_justified_block_hash) => { + verify_or!( + data.justified_block_hash == local_justified_block_hash, + reject!(Invalid::JustifiedBlockHashMismatch) + ); + } + }; + + /* + * The `shard_block_hash` in the state's `latest_crosslinks` must match either the + * `latest_crosslink_hash` or the `shard_block_hash` on the attestation. + * + * TODO: figure out the reasoning behind this. + */ + match state.latest_crosslinks.get(data.shard as usize) { + None => reject!(Invalid::UnknownShard), + Some(crosslink) => { + let local_shard_block_hash = crosslink.shard_block_hash; + let shard_block_hash_is_permissable = { + (local_shard_block_hash == data.latest_crosslink_hash) + || (local_shard_block_hash == data.shard_block_hash) + }; + verify_or!( + shard_block_hash_is_permissable, + reject!(Invalid::ShardBlockHashMismatch) + ); + } + }; + + accept!() +} + +/// Returns the hash (or None) of a block at a slot in the chain that is specified by +/// `chain_tip_hash`. +/// +/// Given that the database stores multiple chains, it is possible for there to be multiple blocks +/// at the given slot. `chain_tip_hash` specifies exactly which chain should be used. +fn block_hash_at_slot( + chain_tip_hash: &Hash256, + slot: u64, + block_store: &Arc>, +) -> Result, Error> +where + T: ClientDB + Sized, +{ + match block_store.block_at_slot(&chain_tip_hash, slot)? { + None => Ok(None), + Some((hash_bytes, _)) => Ok(Some(Hash256::from(&hash_bytes[..]))), + } +} + +impl From for Error { + fn from(e: BeaconBlockAtSlotError) -> Self { + match e { + BeaconBlockAtSlotError::DBError(s) => Error::DBError(s), + _ => Error::UnableToLookupBlockAtSlot, + } + } +} diff --git a/beacon_chain/attestation_validation/src/validate_signature.rs b/beacon_chain/attestation_validation/src/validate_signature.rs new file mode 100644 index 000000000..67ba7d2ec --- /dev/null +++ b/beacon_chain/attestation_validation/src/validate_signature.rs @@ -0,0 +1,64 @@ +use super::bls::{AggregatePublicKey, AggregateSignature}; +use super::db::stores::{ValidatorStore, ValidatorStoreError}; +use super::db::ClientDB; +use super::types::{AttestationData, Bitfield, BitfieldError}; +use super::{Error, Invalid, Outcome}; + +/// Validate that some signature is correct for some attestation data and known validator set. +pub fn validate_attestation_signature( + attestation_data: &AttestationData, + participation_bitfield: &Bitfield, + aggregate_signature: &AggregateSignature, + attestation_indices: &[usize], + validator_store: &ValidatorStore, +) -> Result +where + T: ClientDB + Sized, +{ + let mut agg_pub_key = AggregatePublicKey::new(); + + for i in 0..attestation_indices.len() { + let voted = participation_bitfield.get(i)?; + if voted { + // De-reference the attestation index into a canonical ValidatorRecord index. + let validator = *attestation_indices.get(i).ok_or(Error::BadValidatorIndex)?; + // Load the public key. + let pub_key = validator_store + .get_public_key_by_index(validator)? + .ok_or(Error::NoPublicKeyForValidator)?; + // Aggregate the public key. + agg_pub_key.add(&pub_key); + } + } + + let signed_message = attestation_data_signing_message(attestation_data); + verify_or!( + // TODO: ensure "domain" for aggregate signatures is included. + // https://github.com/sigp/lighthouse/issues/91 + aggregate_signature.verify(&signed_message, &agg_pub_key), + reject!(Invalid::SignatureInvalid) + ); + + accept!() +} + +fn attestation_data_signing_message(attestation_data: &AttestationData) -> Vec { + let mut signed_message = attestation_data.canonical_root().to_vec(); + signed_message.append(&mut vec![0]); + signed_message +} + +impl From for Error { + fn from(error: ValidatorStoreError) -> Self { + match error { + ValidatorStoreError::DBError(s) => Error::DBError(s), + ValidatorStoreError::DecodeError => Error::PublicKeyCorrupt, + } + } +} + +impl From for Error { + fn from(_error: BitfieldError) -> Self { + Error::OutOfBoundsBitfieldIndex + } +} diff --git a/beacon_chain/block_validation/Cargo.toml b/beacon_chain/block_validation/Cargo.toml new file mode 100644 index 000000000..5a68759c6 --- /dev/null +++ b/beacon_chain/block_validation/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "block_validation" +version = "0.1.0" +authors = ["Paul Hauner "] + +[dependencies] +attestation_validation = { path = "../attestation_validation" } +bls = { path = "../utils/bls" } +db = { path = "../../lighthouse/db" } +hashing = { path = "../utils/hashing" } +rayon = "1.0.2" +ssz = { path = "../utils/ssz" } +ssz_helpers = { path = "../utils/ssz_helpers" } +types = { path = "../types" } diff --git a/beacon_chain/block_validation/src/block_validation.rs b/beacon_chain/block_validation/src/block_validation.rs new file mode 100644 index 000000000..7898e17c8 --- /dev/null +++ b/beacon_chain/block_validation/src/block_validation.rs @@ -0,0 +1,371 @@ +extern crate rayon; + +use self::rayon::prelude::*; + +use super::attestation_validation::{AttestationValidationContext, AttestationValidationError}; +use super::db::stores::{BeaconBlockStore, PoWChainStore, ValidatorStore}; +use super::db::{ClientDB, DBError}; +use super::ssz::{Decodable, DecodeError}; +use super::ssz_helpers::attestation_ssz_splitter::{ + split_all_attestations, split_one_attestation, AttestationSplitError, +}; +use super::ssz_helpers::ssz_beacon_block::{SszBeaconBlock, SszBeaconBlockError}; +use super::types::Hash256; +use super::types::{AttestationRecord, AttesterMap, BeaconBlock, ProposerMap}; +use std::sync::{Arc, RwLock}; + +#[derive(Debug, PartialEq)] +pub enum SszBeaconBlockValidationError { + FutureSlot, + SlotAlreadyFinalized, + UnknownPoWChainRef, + UnknownParentHash, + BadAttestationSsz, + BadAncestorHashesSsz, + BadSpecialsSsz, + ParentSlotHigherThanBlockSlot, + AttestationValidationError(AttestationValidationError), + AttestationSignatureFailed, + ProposerAttestationHasObliqueHashes, + NoProposerSignature, + BadProposerMap, + RwLockPoisoned, + DBError(String), +} + +/// The context against which a block should be validated. +pub struct BeaconBlockValidationContext +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, + /// The last justified block hash as per the client's view of the canonical chain. + pub last_justified_block_hash: Hash256, + /// The last finalized slot as per the client's view of the canonical chain. + pub last_finalized_slot: u64, + /// A vec of the hashes of the blocks preceeding the present slot. + pub recent_block_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>, +} + +impl BeaconBlockValidationContext +where + T: ClientDB, +{ + /// Validate some SszBeaconBlock against a block validation context. An SszBeaconBlock varies from a BeaconBlock in + /// that is a read-only structure that reads directly from encoded SSZ. + /// + /// The reason to validate an SzzBeaconBlock 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. + /// + /// 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: &SszBeaconBlock, + ) -> Result + where + T: ClientDB + Sized, + { + /* + * If the block slot corresponds to a slot in the future, return immediately with an error. + * + * It is up to the calling fn to determine what should be done with "future" blocks (e.g., + * cache or discard). + */ + let block_slot = b.slot(); + if block_slot > self.present_slot { + return Err(SszBeaconBlockValidationError::FutureSlot); + } + + /* + * If the block is unknown (assumed unknown because we checked the db earlier in this + * function) and it comes from a slot that is already finalized, drop the block. + * + * If a slot is finalized, there's no point in considering any other blocks for that slot. + * + * TODO: We can more strongly throw away blocks based on the `last_finalized_block` related + * to this `last_finalized_slot`. Namely, any block in a future slot must include the + * `last_finalized_block` in it's chain. + */ + if block_slot <= self.last_finalized_slot { + return Err(SszBeaconBlockValidationError::SlotAlreadyFinalized); + } + + /* + * 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 the block hash is + * "sufficienty deep in the canonical PoW chain". This should be clarified as the spec + * crystallizes. + */ + let pow_chain_reference = b.pow_chain_reference(); + if !self.pow_store.block_hash_exists(b.pow_chain_reference())? { + return Err(SszBeaconBlockValidationError::UnknownPoWChainRef); + } + + /* + * Store a slice of the serialized attestations from the block SSZ. + */ + let attestations_ssz = &b.attestations_without_length(); + + /* + * Get a slice of the first serialized attestation (the 0'th) and decode it into + * a full AttestationRecord object. + * + * The first attestation must be validated separately as it must contain a signature of the + * proposer of the previous block (this is checked later in this function). + */ + let (first_attestation_ssz, next_index) = split_one_attestation(&attestations_ssz, 0)?; + let (first_attestation, _) = AttestationRecord::ssz_decode(&first_attestation_ssz, 0)?; + + /* + * The first attestation may not have oblique hashes. + * + * The presence of oblique hashes in the first attestation would indicate that the proposer + * of the previous block is attesting to some other block than the one they produced. + */ + if !first_attestation.oblique_parent_hashes.is_empty() { + return Err(SszBeaconBlockValidationError::ProposerAttestationHasObliqueHashes); + } + + /* + * Read the parent hash from the block we are validating then attempt to load + * that parent block ssz from the database. + * + * If that parent doesn't exist in the database or is invalid, reject the block. + * + * Also, read the slot from the parent block for later use. + */ + let parent_hash = b + .parent_hash() + .ok_or(SszBeaconBlockValidationError::BadAncestorHashesSsz)?; + let parent_block_slot = match self.block_store.get_serialized_block(&parent_hash)? { + None => return Err(SszBeaconBlockValidationError::UnknownParentHash), + Some(ssz) => { + let parent_block = SszBeaconBlock::from_slice(&ssz[..])?; + parent_block.slot() + } + }; + + /* + * The parent block slot must be less than the block slot. + * + * In other words, the parent must come before the child. + */ + if parent_block_slot >= block_slot { + return Err(SszBeaconBlockValidationError::ParentSlotHigherThanBlockSlot); + } + + /* + * Generate the context in which attestations will be validated. + */ + let attestation_validation_context = Arc::new(AttestationValidationContext { + block_slot, + parent_block_slot, + cycle_length: self.cycle_length, + last_justified_slot: self.last_justified_slot, + recent_block_hashes: self.recent_block_hashes.clone(), + block_store: self.block_store.clone(), + validator_store: self.validator_store.clone(), + attester_map: self.attester_map.clone(), + }); + + /* + * Validate this first attestation. + */ + let attestation_voters = + attestation_validation_context.validate_attestation(&first_attestation)?; + + /* + * Attempt to read load the parent block proposer from the proposer map. Return with an + * error if it fails. + * + * If the signature of proposer for the parent slot was not present in the first (0'th) + * attestation of this block, reject the block. + */ + let parent_block_proposer = self + .proposer_map + .get(&parent_block_slot) + .ok_or(SszBeaconBlockValidationError::BadProposerMap)?; + if !attestation_voters.contains(&parent_block_proposer) { + return Err(SszBeaconBlockValidationError::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)?; + + /* + * 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 mut 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. Also, fail early if the lock has been + * poisoned. + */ + match failure.read() { + Ok(ref option) if option.is_none() => (), + _ => return None, + } + /* + * 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) => { + /* + * If the failure lock isn't poisoned, set it to some error. + */ + if let Ok(mut f) = failure.write() { + *f = Some(SszBeaconBlockValidationError::from(e)); + } + None + } + /* + * Deserialization succeeded and the attestation should be validated. + */ + Ok((attestation, _)) => { + match attestation_validation_context.validate_attestation(&attestation) { + /* + * Attestation validation failed with some error. + */ + Err(e) => { + /* + * If the failure lock isn't poisoned, set it to some error. + */ + if let Ok(mut f) = failure.write() { + *f = Some(SszBeaconBlockValidationError::from(e)); + } + None + } + /* + * Attestation validation succeded. + */ + Ok(_) => Some(attestation), + } + } + } + }).collect(); + + match failure.into_inner() { + Err(_) => return Err(SszBeaconBlockValidationError::RwLockPoisoned), + Ok(failure) => match failure { + Some(error) => return Err(error), + _ => (), + }, + } + + /* + * Add the first attestation to the vec of deserialized attestations at + * index 0. + */ + deserialized_attestations.insert(0, first_attestation); + + let (ancestor_hashes, _) = Decodable::ssz_decode(&b.ancestor_hashes(), 0) + .map_err(|_| SszBeaconBlockValidationError::BadAncestorHashesSsz)?; + let (specials, _) = Decodable::ssz_decode(&b.specials(), 0) + .map_err(|_| SszBeaconBlockValidationError::BadSpecialsSsz)?; + + /* + * If we have reached this point, the block is a new valid block that is worthy of + * processing. + */ + let block = BeaconBlock { + slot: block_slot, + randao_reveal: Hash256::from(b.randao_reveal()), + pow_chain_reference: Hash256::from(pow_chain_reference), + ancestor_hashes, + active_state_root: Hash256::from(b.act_state_root()), + crystallized_state_root: Hash256::from(b.cry_state_root()), + attestations: deserialized_attestations, + specials, + }; + Ok(block) + } +} + +impl From for SszBeaconBlockValidationError { + fn from(e: DBError) -> Self { + SszBeaconBlockValidationError::DBError(e.message) + } +} + +impl From for SszBeaconBlockValidationError { + fn from(e: AttestationSplitError) -> Self { + match e { + AttestationSplitError::TooShort => SszBeaconBlockValidationError::BadAttestationSsz, + } + } +} + +impl From for SszBeaconBlockValidationError { + fn from(e: SszBeaconBlockError) -> Self { + match e { + SszBeaconBlockError::TooShort => { + SszBeaconBlockValidationError::DBError("Bad parent block in db.".to_string()) + } + SszBeaconBlockError::TooLong => { + SszBeaconBlockValidationError::DBError("Bad parent block in db.".to_string()) + } + } + } +} + +impl From for SszBeaconBlockValidationError { + fn from(e: DecodeError) -> Self { + match e { + DecodeError::TooShort => SszBeaconBlockValidationError::BadAttestationSsz, + DecodeError::TooLong => SszBeaconBlockValidationError::BadAttestationSsz, + } + } +} + +impl From for SszBeaconBlockValidationError { + fn from(e: AttestationValidationError) -> Self { + SszBeaconBlockValidationError::AttestationValidationError(e) + } +} + +/* + * Tests for block validation are contained in the root directory "tests" directory (AKA + * "integration tests directory"). + */ diff --git a/beacon_chain/block_validation/src/lib.rs b/beacon_chain/block_validation/src/lib.rs new file mode 100644 index 000000000..c4eae2bd8 --- /dev/null +++ b/beacon_chain/block_validation/src/lib.rs @@ -0,0 +1,9 @@ +extern crate attestation_validation; +extern crate bls; +extern crate db; +extern crate hashing; +extern crate ssz; +extern crate ssz_helpers; +extern crate types; + +pub mod block_validation; diff --git a/beacon_chain/types/src/attestation_data.rs b/beacon_chain/types/src/attestation_data.rs index 0e87ace6a..43c1b8350 100644 --- a/beacon_chain/types/src/attestation_data.rs +++ b/beacon_chain/types/src/attestation_data.rs @@ -37,6 +37,12 @@ impl AttestationData { justified_block_hash: Hash256::zero(), } } + + // TODO: Implement this as a merkle root, once tree_ssz is implemented. + // https://github.com/sigp/lighthouse/issues/92 + pub fn canonical_root(&self) -> Hash256 { + Hash256::zero() + } } impl Encodable for AttestationData { diff --git a/beacon_chain/types/src/chain_config.rs b/beacon_chain/types/src/chain_config.rs index bcc565a75..8428945bb 100644 --- a/beacon_chain/types/src/chain_config.rs +++ b/beacon_chain/types/src/chain_config.rs @@ -2,6 +2,7 @@ use super::ValidatorRegistration; #[derive(Debug, Clone, PartialEq)] pub struct ChainConfig { + // Old, potentially outdated constants pub cycle_length: u8, pub deposit_size_gwei: u64, pub shard_count: u16, @@ -10,6 +11,10 @@ pub struct ChainConfig { pub genesis_time: u64, pub slot_duration_millis: u64, pub initial_validators: Vec, + + // New constants + pub epoch_length: u64, + pub min_attestation_inclusion_delay: u64, } /* @@ -28,6 +33,10 @@ impl ChainConfig { genesis_time: TEST_GENESIS_TIME, slot_duration_millis: 16 * 1000, initial_validators: vec![], + + // New + epoch_length: 64, + min_attestation_inclusion_delay: 4, } } @@ -54,6 +63,10 @@ impl ChainConfig { genesis_time: TEST_GENESIS_TIME, // arbitrary slot_duration_millis: 16 * 1000, initial_validators: vec![], + + // New constants + epoch_length: 64, + min_attestation_inclusion_delay: 4, } } } diff --git a/beacon_chain/types/src/crosslink_record.rs b/beacon_chain/types/src/crosslink_record.rs index d28df4350..3f7ac2f1a 100644 --- a/beacon_chain/types/src/crosslink_record.rs +++ b/beacon_chain/types/src/crosslink_record.rs @@ -2,31 +2,16 @@ use super::Hash256; #[derive(Clone, Debug, PartialEq)] pub struct CrosslinkRecord { - pub recently_changed: bool, pub slot: u64, - pub hash: Hash256, + pub shard_block_hash: Hash256, } impl CrosslinkRecord { /// Generates a new instance where `dynasty` and `hash` are both zero. pub fn zero() -> Self { Self { - recently_changed: false, slot: 0, - hash: Hash256::zero(), + shard_block_hash: Hash256::zero(), } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_crosslink_record_zero() { - let c = CrosslinkRecord::zero(); - assert_eq!(c.recently_changed, false); - assert_eq!(c.slot, 0); - assert!(c.hash.is_zero()); - } -} From 36bda7502b4a449084507342238bdb70a9e5c345 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 6 Dec 2018 18:41:22 +1100 Subject: [PATCH 07/18] Implement signature verification test --- .../attestation_validation/src/enums.rs | 3 + .../src/validate_signature.rs | 85 +++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/beacon_chain/attestation_validation/src/enums.rs b/beacon_chain/attestation_validation/src/enums.rs index 3174d1154..6c94c628c 100644 --- a/beacon_chain/attestation_validation/src/enums.rs +++ b/beacon_chain/attestation_validation/src/enums.rs @@ -1,4 +1,5 @@ /// Reasons why an `AttestationRecord` can be invalid. +#[derive(PartialEq, Debug)] pub enum Invalid { AttestationTooRecent, AttestationTooOld, @@ -14,6 +15,7 @@ pub enum Invalid { /// /// Distinct from the `Error` enum as an `Outcome` indicates that validation executed sucessfully /// and determined the validity `AttestationRecord`. +#[derive(PartialEq, Debug)] pub enum Outcome { Valid, Invalid(Invalid), @@ -23,6 +25,7 @@ pub enum Outcome { /// /// Distinct from the `Outcome` enum as `Errors` indicate that validation encountered an unexpected /// condition and was unable to perform its duty. +#[derive(PartialEq, Debug)] pub enum Error { BlockHasNoParent, BadValidatorIndex, diff --git a/beacon_chain/attestation_validation/src/validate_signature.rs b/beacon_chain/attestation_validation/src/validate_signature.rs index 67ba7d2ec..050f9cdf9 100644 --- a/beacon_chain/attestation_validation/src/validate_signature.rs +++ b/beacon_chain/attestation_validation/src/validate_signature.rs @@ -62,3 +62,88 @@ impl From for Error { Error::OutOfBoundsBitfieldIndex } } + +#[cfg(test)] +mod tests { + use super::super::bls::{Keypair, Signature}; + use super::super::db::MemoryDB; + use super::*; + use std::sync::Arc; + + /* + * TODO: Test cases are not comprehensive. + * https://github.com/sigp/lighthouse/issues/94 + */ + + #[test] + fn test_signature_verification() { + let attestation_data = AttestationData::zero(); + let message = attestation_data_signing_message(&attestation_data); + let signing_keypairs = vec![ + Keypair::random(), + Keypair::random(), + Keypair::random(), + Keypair::random(), + Keypair::random(), + Keypair::random(), + ]; + let non_signing_keypairs = vec![ + Keypair::random(), + Keypair::random(), + Keypair::random(), + Keypair::random(), + Keypair::random(), + Keypair::random(), + ]; + /* + * Signing keypairs first, then non-signing + */ + let mut all_keypairs = signing_keypairs.clone(); + all_keypairs.append(&mut non_signing_keypairs.clone()); + + let attestation_indices: Vec = (0..all_keypairs.len()).collect(); + let mut bitfield = Bitfield::from_elem(all_keypairs.len(), false); + for i in 0..signing_keypairs.len() { + bitfield.set(i, true).unwrap(); + } + + let db = Arc::new(MemoryDB::open()); + let store = ValidatorStore::new(db); + + for (i, keypair) in all_keypairs.iter().enumerate() { + store.put_public_key_by_index(i, &keypair.pk).unwrap(); + } + + let mut agg_sig = AggregateSignature::new(); + for keypair in &signing_keypairs { + let sig = Signature::new(&message, &keypair.sk); + agg_sig.add(&sig); + } + + /* + * Test using all valid parameters. + */ + let outcome = validate_attestation_signature( + &attestation_data, + &bitfield, + &agg_sig, + &attestation_indices, + &store, + ).unwrap(); + assert_eq!(outcome, Outcome::Valid); + + /* + * Add another validator to the bitfield, run validation will all other + * parameters the same and assert that it fails. + */ + bitfield.set(signing_keypairs.len() + 1, true).unwrap(); + let outcome = validate_attestation_signature( + &attestation_data, + &bitfield, + &agg_sig, + &attestation_indices, + &store, + ).unwrap(); + assert_eq!(outcome, Outcome::Invalid(Invalid::SignatureInvalid)); + } +} From 2d2da001b9801d2fb7b159e74e15cf1eee9b6ef7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 6 Dec 2018 19:50:33 +1100 Subject: [PATCH 08/18] Implement tests for attestation validation --- .../src/validate_for_block.rs | 119 +++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/beacon_chain/attestation_validation/src/validate_for_block.rs b/beacon_chain/attestation_validation/src/validate_for_block.rs index 0094f5c59..b0e6d5d60 100644 --- a/beacon_chain/attestation_validation/src/validate_for_block.rs +++ b/beacon_chain/attestation_validation/src/validate_for_block.rs @@ -1,7 +1,7 @@ use super::{Error, Invalid, Outcome}; /// Check that an attestation is valid to be included in some block. -pub fn validate_attestation_for_block( +pub fn validate_attestation_for_block( attestation_slot: u64, block_slot: u64, parent_block_slot: u64, @@ -16,7 +16,9 @@ pub fn validate_attestation_for_block( * block it is contained in. */ verify_or!( - attestation_slot <= block_slot.saturating_sub(min_attestation_inclusion_delay), + // TODO: this differs from the spec as it does not handle underflows correctly. + // https://github.com/sigp/lighthouse/issues/95 + attestation_slot < block_slot.saturating_sub(min_attestation_inclusion_delay - 1), reject!(Invalid::AttestationTooRecent) ); @@ -31,3 +33,116 @@ pub fn validate_attestation_for_block( accept!() } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_inclusion_delay_minimal() { + let parent_block_slot = 99; + let min_attestation_inclusion_delay = 10; + let epoch_length = 20; + let block_slot = 100; + let attestation_slot = block_slot - min_attestation_inclusion_delay; + + let outcome = validate_attestation_for_block( + attestation_slot, + block_slot, + parent_block_slot, + min_attestation_inclusion_delay, + epoch_length, + ); + assert_eq!(outcome, Ok(Outcome::Valid)); + } + + #[test] + fn test_inclusion_delay_maximal() { + let parent_block_slot = 99; + let min_attestation_inclusion_delay = 10; + let epoch_length = 20; + let block_slot = 100; + let attestation_slot = block_slot - epoch_length; + + let outcome = validate_attestation_for_block( + attestation_slot, + block_slot, + parent_block_slot, + min_attestation_inclusion_delay, + epoch_length, + ); + assert_eq!(outcome, Ok(Outcome::Valid)); + } + + #[test] + fn test_inclusion_delay_insufficient() { + let parent_block_slot = 99; + let min_attestation_inclusion_delay = 10; + let epoch_length = 20; + let block_slot = 100; + let attestation_slot = block_slot - (min_attestation_inclusion_delay - 1); + + let outcome = validate_attestation_for_block( + attestation_slot, + block_slot, + parent_block_slot, + min_attestation_inclusion_delay, + epoch_length, + ); + assert_eq!(outcome, Ok(Outcome::Invalid(Invalid::AttestationTooRecent))); + } + + #[test] + fn test_inclusion_delay_first_possible_slot() { + let min_attestation_inclusion_delay = 10; + let epoch_length = 20; + let block_slot = min_attestation_inclusion_delay; + let attestation_slot = 0; + let parent_block_slot = block_slot - 1; + + let outcome = validate_attestation_for_block( + attestation_slot, + block_slot, + parent_block_slot, + min_attestation_inclusion_delay, + epoch_length, + ); + assert_eq!(outcome, Ok(Outcome::Valid)); + } + + #[test] + fn test_inclusion_delay_saturation_non_zero_slot() { + let min_attestation_inclusion_delay = 10; + let epoch_length = 20; + let block_slot = min_attestation_inclusion_delay - 1; + let parent_block_slot = block_slot - 1; + let attestation_slot = 0; + + let outcome = validate_attestation_for_block( + attestation_slot, + block_slot, + parent_block_slot, + min_attestation_inclusion_delay, + epoch_length, + ); + assert_eq!(outcome, Ok(Outcome::Invalid(Invalid::AttestationTooRecent))); + } + + #[test] + fn test_inclusion_delay_saturation_zero_slot() { + let min_attestation_inclusion_delay = 10; + let epoch_length = 20; + let block_slot = min_attestation_inclusion_delay - 1; + let parent_block_slot = block_slot - 1; + let attestation_slot = 0; + + let outcome = validate_attestation_for_block( + attestation_slot, + block_slot, + parent_block_slot, + min_attestation_inclusion_delay, + epoch_length, + ); + assert_eq!(outcome, Ok(Outcome::Invalid(Invalid::AttestationTooRecent))); + } +} From a8bfa4d733f3e1279c62ba1584ea06a5a584d0c2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 7 Dec 2018 08:59:05 +1100 Subject: [PATCH 09/18] Add more attestation validation tests --- .../src/validate_for_block.rs | 109 +++++++++++++++++- 1 file changed, 103 insertions(+), 6 deletions(-) diff --git a/beacon_chain/attestation_validation/src/validate_for_block.rs b/beacon_chain/attestation_validation/src/validate_for_block.rs index b0e6d5d60..7742b5325 100644 --- a/beacon_chain/attestation_validation/src/validate_for_block.rs +++ b/beacon_chain/attestation_validation/src/validate_for_block.rs @@ -37,9 +37,106 @@ pub fn validate_attestation_for_block( #[cfg(test)] mod tests { use super::*; + /* + * Invalid::AttestationTooOld tests. + */ #[test] - fn test_inclusion_delay_minimal() { + fn test_inclusion_too_old_minimal() { + let min_attestation_inclusion_delay = 10; + let epoch_length = 20; + let block_slot = 100; + let parent_block_slot = block_slot - 1; + let attestation_slot = block_slot - min_attestation_inclusion_delay; + + let outcome = validate_attestation_for_block( + attestation_slot, + block_slot, + parent_block_slot, + min_attestation_inclusion_delay, + epoch_length, + ); + assert_eq!(outcome, Ok(Outcome::Valid)); + } + + #[test] + fn test_inclusion_too_old_maximal() { + let min_attestation_inclusion_delay = 10; + let epoch_length = 20; + let block_slot = 100; + let parent_block_slot = block_slot - 1; + let attestation_slot = block_slot - epoch_length + 1; + + let outcome = validate_attestation_for_block( + attestation_slot, + block_slot, + parent_block_slot, + min_attestation_inclusion_delay, + epoch_length, + ); + assert_eq!(outcome, Ok(Outcome::Valid)); + } + + #[test] + fn test_inclusion_too_old_saturating_non_zero_attestation_slot() { + let min_attestation_inclusion_delay = 10; + let epoch_length = 20; + let block_slot = epoch_length + 1; + let parent_block_slot = block_slot - 1; + let attestation_slot = block_slot - min_attestation_inclusion_delay; + + let outcome = validate_attestation_for_block( + attestation_slot, + block_slot, + parent_block_slot, + min_attestation_inclusion_delay, + epoch_length, + ); + assert_eq!(outcome, Ok(Outcome::Valid)); + } + + #[test] + fn test_inclusion_too_old_saturating_zero_attestation_slot() { + let min_attestation_inclusion_delay = 10; + let epoch_length = 20; + let block_slot = epoch_length + 1; + let parent_block_slot = block_slot - 1; + let attestation_slot = 0; + + let outcome = validate_attestation_for_block( + attestation_slot, + block_slot, + parent_block_slot, + min_attestation_inclusion_delay, + epoch_length, + ); + assert_eq!(outcome, Ok(Outcome::Valid)); + } + + #[test] + fn test_inclusion_too_old() { + let min_attestation_inclusion_delay = 10; + let epoch_length = 20; + let block_slot = epoch_length * 2; + let parent_block_slot = block_slot - 1; + let attestation_slot = parent_block_slot - (epoch_length + 2); + + let outcome = validate_attestation_for_block( + attestation_slot, + block_slot, + parent_block_slot, + min_attestation_inclusion_delay, + epoch_length, + ); + assert_eq!(outcome, Ok(Outcome::Invalid(Invalid::AttestationTooOld))); + } + + /* + * Invalid::AttestationTooRecent tests. + */ + + #[test] + fn test_inclusion_too_recent_minimal() { let parent_block_slot = 99; let min_attestation_inclusion_delay = 10; let epoch_length = 20; @@ -57,7 +154,7 @@ mod tests { } #[test] - fn test_inclusion_delay_maximal() { + fn test_inclusion_too_recent_maximal() { let parent_block_slot = 99; let min_attestation_inclusion_delay = 10; let epoch_length = 20; @@ -75,7 +172,7 @@ mod tests { } #[test] - fn test_inclusion_delay_insufficient() { + fn test_inclusion_too_recent_insufficient() { let parent_block_slot = 99; let min_attestation_inclusion_delay = 10; let epoch_length = 20; @@ -93,7 +190,7 @@ mod tests { } #[test] - fn test_inclusion_delay_first_possible_slot() { + fn test_inclusion_too_recent_first_possible_slot() { let min_attestation_inclusion_delay = 10; let epoch_length = 20; let block_slot = min_attestation_inclusion_delay; @@ -111,7 +208,7 @@ mod tests { } #[test] - fn test_inclusion_delay_saturation_non_zero_slot() { + fn test_inclusion_too_recent_saturation_non_zero_slot() { let min_attestation_inclusion_delay = 10; let epoch_length = 20; let block_slot = min_attestation_inclusion_delay - 1; @@ -129,7 +226,7 @@ mod tests { } #[test] - fn test_inclusion_delay_saturation_zero_slot() { + fn test_inclusion_too_recent_saturation_zero_slot() { let min_attestation_inclusion_delay = 10; let epoch_length = 20; let block_slot = min_attestation_inclusion_delay - 1; From 0f9482f9d10b1160fba66269bca13be2cdacc2ab Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 10 Dec 2018 09:09:21 +1100 Subject: [PATCH 10/18] Complete attestation_validation, bar tests --- ...lidate_for_block.rs => block_inclusion.rs} | 1 + ...lidate_for_state.rs => justified_block.rs} | 69 ++++++------------- .../src/justified_slot.rs | 38 ++++++++++ .../attestation_validation/src/lib.rs | 17 +++-- .../attestation_validation/src/shard_block.rs | 46 +++++++++++++ .../{validate_signature.rs => signature.rs} | 0 beacon_chain/types/src/attestation_data.rs | 2 +- beacon_chain/types/src/beacon_block.rs | 2 +- beacon_chain/types/src/beacon_block_body.rs | 0 9 files changed, 119 insertions(+), 56 deletions(-) rename beacon_chain/attestation_validation/src/{validate_for_block.rs => block_inclusion.rs} (99%) rename beacon_chain/attestation_validation/src/{validate_for_state.rs => justified_block.rs} (50%) create mode 100644 beacon_chain/attestation_validation/src/justified_slot.rs create mode 100644 beacon_chain/attestation_validation/src/shard_block.rs rename beacon_chain/attestation_validation/src/{validate_signature.rs => signature.rs} (100%) create mode 100644 beacon_chain/types/src/beacon_block_body.rs diff --git a/beacon_chain/attestation_validation/src/validate_for_block.rs b/beacon_chain/attestation_validation/src/block_inclusion.rs similarity index 99% rename from beacon_chain/attestation_validation/src/validate_for_block.rs rename to beacon_chain/attestation_validation/src/block_inclusion.rs index 7742b5325..76a5c9797 100644 --- a/beacon_chain/attestation_validation/src/validate_for_block.rs +++ b/beacon_chain/attestation_validation/src/block_inclusion.rs @@ -37,6 +37,7 @@ pub fn validate_attestation_for_block( #[cfg(test)] mod tests { use super::*; + /* * Invalid::AttestationTooOld tests. */ diff --git a/beacon_chain/attestation_validation/src/validate_for_state.rs b/beacon_chain/attestation_validation/src/justified_block.rs similarity index 50% rename from beacon_chain/attestation_validation/src/validate_for_state.rs rename to beacon_chain/attestation_validation/src/justified_block.rs index 9312dd125..e910819c8 100644 --- a/beacon_chain/attestation_validation/src/validate_for_state.rs +++ b/beacon_chain/attestation_validation/src/justified_block.rs @@ -1,42 +1,25 @@ use super::db::stores::{BeaconBlockAtSlotError, BeaconBlockStore}; use super::db::ClientDB; +use super::types::AttestationData; use super::types::Hash256; -use super::types::{AttestationData, BeaconState}; use super::{Error, Invalid, Outcome}; use std::sync::Arc; -/// Check that an attestation is valid with reference to some state. -pub fn validate_attestation_data_for_state( +/// Verify that a attestation's `data.justified_block_hash` matches the local hash of the block at the +/// attestation's `data.justified_slot`. +/// +/// `chain_tip_block_hash` is the tip of the chain in which the justified block hash should exist +/// locally. As Lightouse stores multiple chains locally, it is possible to have multiple blocks at +/// the same slot. `chain_tip_block_hash` serves to restrict the lookup to a single chain, where +/// each slot may have exactly zero or one blocks. +pub fn validate_attestation_justified_block_hash( data: &AttestationData, chain_tip_block_hash: &Hash256, - state: &BeaconState, block_store: &Arc>, ) -> Result where T: ClientDB + Sized, { - /* - * The attestation's `justified_slot` must be the same as the last justified slot known to this - * client. - * - * In the case that an attestation references a slot _before_ the latest state transition, it - * is acceptable for the attestation to reference the previous known `justified_slot`. If this - * were not the case, all attestations created _prior_ to the last state recalculation would be - * rejected if a block was justified in that state recalculation. It is both ideal and likely - * that blocks will be justified during a state recalcuation. - */ - { - let permissable_justified_slot = if data.slot >= state.latest_state_recalculation_slot { - state.justified_slot - } else { - state.previous_justified_slot - }; - verify_or!( - data.justified_slot == permissable_justified_slot, - reject!(Invalid::JustifiedSlotImpermissable) - ); - } - /* * The `justified_block_hash` in the attestation must match exactly the hash of the block at * that slot in the local chain. @@ -53,28 +36,6 @@ where ); } }; - - /* - * The `shard_block_hash` in the state's `latest_crosslinks` must match either the - * `latest_crosslink_hash` or the `shard_block_hash` on the attestation. - * - * TODO: figure out the reasoning behind this. - */ - match state.latest_crosslinks.get(data.shard as usize) { - None => reject!(Invalid::UnknownShard), - Some(crosslink) => { - let local_shard_block_hash = crosslink.shard_block_hash; - let shard_block_hash_is_permissable = { - (local_shard_block_hash == data.latest_crosslink_hash) - || (local_shard_block_hash == data.shard_block_hash) - }; - verify_or!( - shard_block_hash_is_permissable, - reject!(Invalid::ShardBlockHashMismatch) - ); - } - }; - accept!() } @@ -105,3 +66,15 @@ impl From for Error { } } } + +#[cfg(test)] +mod tests { + /* + * TODO: Implement tests. + * + * These tests will require the `BeaconBlock` and `BeaconBlockBody` updates, which are not + * yet included in the code base. Adding tests now will result in duplicated work. + * + * https://github.com/sigp/lighthouse/issues/97 + */ +} diff --git a/beacon_chain/attestation_validation/src/justified_slot.rs b/beacon_chain/attestation_validation/src/justified_slot.rs new file mode 100644 index 000000000..c27dfe8f2 --- /dev/null +++ b/beacon_chain/attestation_validation/src/justified_slot.rs @@ -0,0 +1,38 @@ +use super::types::{AttestationData, BeaconState}; +use super::{Error, Invalid, Outcome}; + +/// Verify that an attestation's `data.justified_slot` matches the justified slot known to the +/// `state`. +/// +/// In the case that an attestation references a slot _before_ the latest state transition, is +/// acceptable for the attestation to reference the previous known `justified_slot`. If this were +/// not the case, all attestations created _prior_ to the last state recalculation would be rejected +/// if a block was justified in that state recalculation. It is both ideal and likely that blocks +/// will be justified during a state recalcuation. +pub fn validate_attestation_justified_slot( + data: &AttestationData, + state: &BeaconState, +) -> Result { + let permissable_justified_slot = if data.slot >= state.latest_state_recalculation_slot { + state.justified_slot + } else { + state.previous_justified_slot + }; + verify_or!( + data.justified_slot == permissable_justified_slot, + reject!(Invalid::JustifiedSlotImpermissable) + ); + accept!() +} + +#[cfg(test)] +mod tests { + /* + * TODO: Implement tests. + * + * These tests will require the `BeaconBlock` and `BeaconBlockBody` updates, which are not + * yet included in the code base. Adding tests now will result in duplicated work. + * + * https://github.com/sigp/lighthouse/issues/97 + */ +} diff --git a/beacon_chain/attestation_validation/src/lib.rs b/beacon_chain/attestation_validation/src/lib.rs index 8a54a3194..8544be862 100644 --- a/beacon_chain/attestation_validation/src/lib.rs +++ b/beacon_chain/attestation_validation/src/lib.rs @@ -7,12 +7,17 @@ extern crate types; #[macro_use] mod macros; + +mod block_inclusion; mod enums; -mod validate_for_block; -mod validate_for_state; -mod validate_signature; +mod justified_block; +mod justified_slot; +mod shard_block; +mod signature; pub use enums::{Invalid, Outcome, Error}; -pub use validate_for_block::validate_attestation_for_block; -pub use validate_for_state::validate_attestation_data_for_state; -pub use validate_signature::validate_attestation_signature; +pub use block_inclusion::validate_attestation_for_block; +pub use justified_slot::validate_attestation_justified_slot; +pub use justified_block::validate_attestation_justified_block_hash; +pub use signature::validate_attestation_signature; +pub use shard_block::validate_attestation_data_shard_block_hash; diff --git a/beacon_chain/attestation_validation/src/shard_block.rs b/beacon_chain/attestation_validation/src/shard_block.rs new file mode 100644 index 000000000..7fb48ff62 --- /dev/null +++ b/beacon_chain/attestation_validation/src/shard_block.rs @@ -0,0 +1,46 @@ +use super::db::ClientDB; +use super::types::{AttestationData, BeaconState}; +use super::{Error, Invalid, Outcome}; + +/// Check that an attestation is valid with reference to some state. +pub fn validate_attestation_data_shard_block_hash( + data: &AttestationData, + state: &BeaconState, +) -> Result +where + T: ClientDB + Sized, +{ + /* + * The `shard_block_hash` in the state's `latest_crosslinks` must match either the + * `latest_crosslink_hash` or the `shard_block_hash` on the attestation. + * + * TODO: figure out the reasoning behind this. + */ + match state.latest_crosslinks.get(data.shard as usize) { + None => reject!(Invalid::UnknownShard), + Some(crosslink) => { + let local_shard_block_hash = crosslink.shard_block_hash; + let shard_block_hash_is_permissable = { + (local_shard_block_hash == data.latest_crosslink_hash) + || (local_shard_block_hash == data.shard_block_hash) + }; + verify_or!( + shard_block_hash_is_permissable, + reject!(Invalid::ShardBlockHashMismatch) + ); + } + }; + accept!() +} + +#[cfg(test)] +mod tests { + /* + * TODO: Implement tests. + * + * These tests will require the `BeaconBlock` and `BeaconBlockBody` updates, which are not + * yet included in the code base. Adding tests now will result in duplicated work. + * + * https://github.com/sigp/lighthouse/issues/97 + */ +} diff --git a/beacon_chain/attestation_validation/src/validate_signature.rs b/beacon_chain/attestation_validation/src/signature.rs similarity index 100% rename from beacon_chain/attestation_validation/src/validate_signature.rs rename to beacon_chain/attestation_validation/src/signature.rs diff --git a/beacon_chain/types/src/attestation_data.rs b/beacon_chain/types/src/attestation_data.rs index 43c1b8350..d75c43b9d 100644 --- a/beacon_chain/types/src/attestation_data.rs +++ b/beacon_chain/types/src/attestation_data.rs @@ -12,7 +12,7 @@ pub const SSZ_ATTESTION_DATA_LENGTH: usize = { 32 // justified_block_hash }; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] pub struct AttestationData { pub slot: u64, pub shard: u64, diff --git a/beacon_chain/types/src/beacon_block.rs b/beacon_chain/types/src/beacon_block.rs index e4217fd9a..6d67c907b 100644 --- a/beacon_chain/types/src/beacon_block.rs +++ b/beacon_chain/types/src/beacon_block.rs @@ -15,7 +15,7 @@ pub const MIN_SSZ_BLOCK_LENGTH: usize = { }; pub const MAX_SSZ_BLOCK_LENGTH: usize = MIN_SSZ_BLOCK_LENGTH + (1 << 24); -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Default)] pub struct BeaconBlock { pub slot: u64, pub randao_reveal: Hash256, diff --git a/beacon_chain/types/src/beacon_block_body.rs b/beacon_chain/types/src/beacon_block_body.rs new file mode 100644 index 000000000..e69de29bb From a35a28f522eb6df07b982a57ed7c33f9c27e264e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 10 Dec 2018 09:48:41 +1100 Subject: [PATCH 11/18] Remove old `validation` crate --- .../block_validation/src/block_validation.rs | 18 +- beacon_chain/chain/Cargo.toml | 1 - beacon_chain/chain/src/block_processing.rs | 14 +- beacon_chain/chain/src/lib.rs | 1 - beacon_chain/validation/Cargo.toml | 13 - .../src/attestation_parent_hashes.rs | 229 ----------- .../validation/src/attestation_validation.rs | 251 ------------ .../validation/src/block_validation.rs | 371 ------------------ beacon_chain/validation/src/lib.rs | 12 - .../validation/src/message_generation.rs | 67 ---- .../validation/src/signature_verification.rs | 180 --------- .../tests/attestation_validation/helpers.rs | 219 ----------- .../tests/attestation_validation/mod.rs | 9 - .../tests/attestation_validation/tests.rs | 241 ------------ .../tests/block_validation/helpers.rs | 233 ----------- .../validation/tests/block_validation/mod.rs | 12 - .../tests/block_validation/tests.rs | 264 ------------- beacon_chain/validation/tests/main.rs | 20 - 18 files changed, 4 insertions(+), 2151 deletions(-) delete mode 100644 beacon_chain/validation/Cargo.toml delete mode 100644 beacon_chain/validation/src/attestation_parent_hashes.rs delete mode 100644 beacon_chain/validation/src/attestation_validation.rs delete mode 100644 beacon_chain/validation/src/block_validation.rs delete mode 100644 beacon_chain/validation/src/lib.rs delete mode 100644 beacon_chain/validation/src/message_generation.rs delete mode 100644 beacon_chain/validation/src/signature_verification.rs delete mode 100644 beacon_chain/validation/tests/attestation_validation/helpers.rs delete mode 100644 beacon_chain/validation/tests/attestation_validation/mod.rs delete mode 100644 beacon_chain/validation/tests/attestation_validation/tests.rs delete mode 100644 beacon_chain/validation/tests/block_validation/helpers.rs delete mode 100644 beacon_chain/validation/tests/block_validation/mod.rs delete mode 100644 beacon_chain/validation/tests/block_validation/tests.rs delete mode 100644 beacon_chain/validation/tests/main.rs diff --git a/beacon_chain/block_validation/src/block_validation.rs b/beacon_chain/block_validation/src/block_validation.rs index 7898e17c8..5f295d7fc 100644 --- a/beacon_chain/block_validation/src/block_validation.rs +++ b/beacon_chain/block_validation/src/block_validation.rs @@ -179,24 +179,8 @@ where } /* - * Generate the context in which attestations will be validated. + * TODO: Validate the first attestation. */ - let attestation_validation_context = Arc::new(AttestationValidationContext { - block_slot, - parent_block_slot, - cycle_length: self.cycle_length, - last_justified_slot: self.last_justified_slot, - recent_block_hashes: self.recent_block_hashes.clone(), - block_store: self.block_store.clone(), - validator_store: self.validator_store.clone(), - attester_map: self.attester_map.clone(), - }); - - /* - * Validate this first attestation. - */ - let attestation_voters = - attestation_validation_context.validate_attestation(&first_attestation)?; /* * Attempt to read load the parent block proposer from the proposer map. Return with an diff --git a/beacon_chain/chain/Cargo.toml b/beacon_chain/chain/Cargo.toml index 65a261731..479804f45 100644 --- a/beacon_chain/chain/Cargo.toml +++ b/beacon_chain/chain/Cargo.toml @@ -11,6 +11,5 @@ ssz = { path = "../utils/ssz" } ssz_helpers = { path = "../utils/ssz_helpers" } state-transition = { path = "../state-transition" } types = { path = "../types" } -validation = { path = "../validation" } validator_induction = { path = "../validator_induction" } validator_shuffling = { path = "../validator_shuffling" } diff --git a/beacon_chain/chain/src/block_processing.rs b/beacon_chain/chain/src/block_processing.rs index 1847549fd..bfad68c15 100644 --- a/beacon_chain/chain/src/block_processing.rs +++ b/beacon_chain/chain/src/block_processing.rs @@ -5,7 +5,6 @@ use db::{ClientDB, DBError}; use naive_fork_choice::{naive_fork_choice, ForkChoiceError}; use ssz_helpers::ssz_beacon_block::{SszBeaconBlock, SszBeaconBlockError}; use types::Hash256; -use validation::block_validation::SszBeaconBlockValidationError; pub enum BlockProcessingOutcome { BlockAlreadyKnown, @@ -19,10 +18,11 @@ pub enum BlockProcessingError { ActiveStateRootInvalid, CrystallizedStateRootInvalid, NoHeadHashes, + UnknownParentHash, ForkChoiceFailed(ForkChoiceError), ContextGenerationFailed(BlockValidationContextError), DeserializationFailed(SszBeaconBlockError), - ValidationFailed(SszBeaconBlockValidationError), + ValidationFailed, StateTransitionFailed(StateTransitionError), DBError(String), } @@ -55,9 +55,7 @@ where */ let parent_hash = ssz_block .parent_hash() - .ok_or(BlockProcessingError::ValidationFailed( - SszBeaconBlockValidationError::UnknownParentHash, - ))?; + .ok_or(BlockProcessingError::UnknownParentHash)?; /* * Load the parent block from the database and create an SszBeaconBlock for reading it. @@ -235,12 +233,6 @@ impl From for BlockProcessingError { } } -impl From for BlockProcessingError { - fn from(e: SszBeaconBlockValidationError) -> Self { - BlockProcessingError::ValidationFailed(e) - } -} - impl From for BlockProcessingError { fn from(e: StateTransitionError) -> Self { BlockProcessingError::StateTransitionFailed(e) diff --git a/beacon_chain/chain/src/lib.rs b/beacon_chain/chain/src/lib.rs index 63bb51561..00ae5f325 100644 --- a/beacon_chain/chain/src/lib.rs +++ b/beacon_chain/chain/src/lib.rs @@ -4,7 +4,6 @@ extern crate ssz; extern crate ssz_helpers; extern crate state_transition; extern crate types; -extern crate validation; extern crate validator_induction; extern crate validator_shuffling; diff --git a/beacon_chain/validation/Cargo.toml b/beacon_chain/validation/Cargo.toml deleted file mode 100644 index a202fb5ce..000000000 --- a/beacon_chain/validation/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "validation" -version = "0.1.0" -authors = ["Paul Hauner "] - -[dependencies] -bls = { path = "../utils/bls" } -db = { path = "../../lighthouse/db" } -hashing = { path = "../utils/hashing" } -rayon = "1.0.2" -ssz = { path = "../utils/ssz" } -ssz_helpers = { path = "../utils/ssz_helpers" } -types = { path = "../types" } diff --git a/beacon_chain/validation/src/attestation_parent_hashes.rs b/beacon_chain/validation/src/attestation_parent_hashes.rs deleted file mode 100644 index e1a7d16ee..000000000 --- a/beacon_chain/validation/src/attestation_parent_hashes.rs +++ /dev/null @@ -1,229 +0,0 @@ -use super::types::Hash256; - -#[derive(Debug)] -pub enum ParentHashesError { - BadCurrentHashes, - BadObliqueHashes, - SlotTooHigh, - SlotTooLow, - IntWrapping, -} - -/// This function is used to select the hashes used in -/// the signing of an AttestationRecord. -/// -/// It either returns Result with a vector of length `cycle_length,` or -/// returns an Error. -/// -/// This function corresponds to the `get_signed_parent_hashes` function -/// in the Python reference implentation. -/// -/// See this slide for more information: -/// https://tinyurl.com/ybzn2spw -pub fn attestation_parent_hashes( - cycle_length: u8, - block_slot: u64, - attestation_slot: u64, - current_hashes: &[Hash256], - oblique_hashes: &[Hash256], -) -> Result, ParentHashesError> { - // This cast places a limit on cycle_length. If you change it, check math - // for overflow. - let cycle_length: u64 = u64::from(cycle_length); - - if current_hashes.len() as u64 != (cycle_length * 2) { - return Err(ParentHashesError::BadCurrentHashes); - } - if oblique_hashes.len() as u64 > cycle_length { - return Err(ParentHashesError::BadObliqueHashes); - } - if attestation_slot >= block_slot { - return Err(ParentHashesError::SlotTooHigh); - } - - /* - * Cannot underflow as block_slot cannot be less - * than attestation_slot. - */ - let attestation_distance = block_slot - attestation_slot; - - if attestation_distance > cycle_length { - return Err(ParentHashesError::SlotTooLow); - } - - /* - * Cannot underflow because attestation_distance cannot - * be larger than cycle_length. - */ - let start = cycle_length - attestation_distance; - - /* - * Overflow is potentially impossible, but proof is complicated - * enough to just use checked math. - * - * Arithmetic is: - * start + cycle_length - oblique_hashes.len() - */ - let end = start - .checked_add(cycle_length) - .and_then(|x| x.checked_sub(oblique_hashes.len() as u64)) - .ok_or(ParentHashesError::IntWrapping)?; - - let mut hashes = Vec::new(); - hashes.extend_from_slice(¤t_hashes[(start as usize)..(end as usize)]); - hashes.extend_from_slice(oblique_hashes); - - Ok(hashes) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn get_range_of_hashes(from: usize, to: usize) -> Vec { - (from..to).map(|i| get_hash(&vec![i as u8])).collect() - } - - fn get_hash(value: &[u8]) -> Hash256 { - Hash256::from_slice(value) - } - - #[test] - fn test_get_signed_hashes_oblique_scenario_1() { - /* - * Two oblique hashes. - */ - let cycle_length: u8 = 8; - let block_slot: u64 = 19; - let attestation_slot: u64 = 15; - let current_hashes = get_range_of_hashes(3, 19); - let oblique_hashes = get_range_of_hashes(100, 102); - let result = attestation_parent_hashes( - cycle_length, - block_slot, - attestation_slot, - ¤t_hashes, - &oblique_hashes, - ); - assert!(result.is_ok()); - let result = result.unwrap(); - assert_eq!(result.len(), cycle_length as usize); - - let mut expected_result = get_range_of_hashes(7, 13); - expected_result.append(&mut get_range_of_hashes(100, 102)); - assert_eq!(result, expected_result); - } - - #[test] - fn test_get_signed_hashes_oblique_scenario_2() { - /* - * All oblique hashes. - */ - let cycle_length: u8 = 8; - let block_slot: u64 = 19; - let attestation_slot: u64 = 15; - let current_hashes = get_range_of_hashes(3, 19); - let oblique_hashes = get_range_of_hashes(100, 108); - let result = attestation_parent_hashes( - cycle_length, - block_slot, - attestation_slot, - ¤t_hashes, - &oblique_hashes, - ); - assert!(result.is_ok()); - let result = result.unwrap(); - assert_eq!(result.len(), cycle_length as usize); - - let expected_result = get_range_of_hashes(100, 108); - assert_eq!(result, expected_result); - } - - #[test] - fn test_get_signed_hashes_scenario_1() { - /* - * Google Slides example. - * https://tinyurl.com/ybzn2spw - */ - let cycle_length: u8 = 8; - let block_slot: u64 = 19; - let attestation_slot: u64 = 15; - let current_hashes = get_range_of_hashes(3, 19); - let oblique_hashes = vec![]; - let result = attestation_parent_hashes( - cycle_length, - block_slot, - attestation_slot, - ¤t_hashes, - &oblique_hashes, - ); - assert!(result.is_ok()); - let result = result.unwrap(); - assert_eq!(result.len(), cycle_length as usize); - let expected_result = get_range_of_hashes(7, 15); - assert_eq!(result, expected_result); - } - - #[test] - fn test_get_signed_hashes_scenario_2() { - /* - * Block 1, attestation 0. - */ - let cycle_length: u8 = 8; - let block_slot: u64 = 1; - let attestation_slot: u64 = 0; - let current_hashes = get_range_of_hashes(0, 16); - let oblique_hashes = vec![]; - let result = attestation_parent_hashes( - cycle_length, - block_slot, - attestation_slot, - ¤t_hashes, - &oblique_hashes, - ); - let result = result.unwrap(); - assert_eq!(result.len(), cycle_length as usize); - let expected_result = get_range_of_hashes(7, 15); - assert_eq!(result, expected_result); - } - - #[test] - fn test_get_signed_hashes_scenario_3() { - /* - * attestation_slot too large - */ - let cycle_length: u8 = 8; - let block_slot: u64 = 100; - let attestation_slot: u64 = 100; - let current_hashes = get_range_of_hashes(0, 16); - let oblique_hashes = vec![]; - let result = attestation_parent_hashes( - cycle_length, - block_slot, - attestation_slot, - ¤t_hashes, - &oblique_hashes, - ); - assert!(result.is_err()); - } - - #[test] - fn test_get_signed_hashes_scenario_4() { - /* - * Current hashes too small - */ - let cycle_length: u8 = 8; - let block_slot: u64 = 100; - let attestation_slot: u64 = 99; - let current_hashes = get_range_of_hashes(0, 15); - let oblique_hashes = vec![]; - let result = attestation_parent_hashes( - cycle_length, - block_slot, - attestation_slot, - ¤t_hashes, - &oblique_hashes, - ); - assert!(result.is_err()); - } -} diff --git a/beacon_chain/validation/src/attestation_validation.rs b/beacon_chain/validation/src/attestation_validation.rs deleted file mode 100644 index e31f3ae52..000000000 --- a/beacon_chain/validation/src/attestation_validation.rs +++ /dev/null @@ -1,251 +0,0 @@ -use super::attestation_parent_hashes::{attestation_parent_hashes, ParentHashesError}; -use super::db::stores::{BeaconBlockAtSlotError, BeaconBlockStore, ValidatorStore}; -use super::db::{ClientDB, DBError}; -use super::message_generation::generate_signed_message; -use super::signature_verification::{ - verify_aggregate_signature_for_indices, SignatureVerificationError, -}; -use super::types::Hash256; -use super::types::{AttestationRecord, AttesterMap}; -use std::collections::HashSet; -use std::sync::Arc; - -#[derive(Debug, PartialEq)] -pub enum AttestationValidationError { - ParentSlotTooHigh, - ParentSlotTooLow, - BlockSlotTooHigh, - BlockSlotTooLow, - JustifiedSlotIncorrect, - InvalidJustifiedBlockHash, - TooManyObliqueHashes, - BadCurrentHashes, - BadObliqueHashes, - BadAttesterMap, - IntWrapping, - PublicKeyCorrupt, - NoPublicKeyForValidator, - BadBitfieldLength, - InvalidBitfield, - InvalidBitfieldEndBits, - NoSignatures, - NonZeroTrailingBits, - BadAggregateSignature, - DBError(String), - OutOfBoundsBitfieldIndex, -} - -/// The context against which some attestation should be validated. -pub struct AttestationValidationContext -where - T: ClientDB + Sized, -{ - /// The slot as determined by the system time. - pub block_slot: u64, - /// The slot of the parent of the block that contained this attestation. - pub parent_block_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 recent_block_hashes: Arc>, - /// The store containing block information. - pub block_store: Arc>, - /// The store containing validator information. - pub validator_store: Arc>, - /// A map of (slot, shard_id) to the attestation set of validation indices. - pub attester_map: Arc, -} - -impl AttestationValidationContext -where - T: ClientDB, -{ - /// Validate a (fully deserialized) AttestationRecord against this context. - /// - /// The function will return a HashSet of validator indices (canonical validator indices not - /// attestation indices) if the validation passed successfully, or an error otherwise. - /// - /// The attestation's aggregate signature will be verified, therefore the function must able to - /// access all required validation public keys via the `validator_store`. - pub fn validate_attestation( - &self, - a: &AttestationRecord, - ) -> Result, AttestationValidationError> { - /* - * The attesation slot must be less than or equal to the parent of the slot of the block - * that contained the attestation. - */ - if a.slot > self.parent_block_slot { - return Err(AttestationValidationError::ParentSlotTooHigh); - } - - /* - * The slot of this attestation must not be more than cycle_length + 1 distance - * from the parent_slot of block that contained it. - */ - if a.slot < self - .parent_block_slot - .saturating_sub(u64::from(self.cycle_length).saturating_add(1)) - { - return Err(AttestationValidationError::ParentSlotTooLow); - } - - /* - * The attestation justified slot must not be higher than the last_justified_slot of the - * context. - */ - if a.justified_slot > self.last_justified_slot { - return Err(AttestationValidationError::JustifiedSlotIncorrect); - } - - /* - * There is no need to include more oblique parents hashes than there are blocks - * in a cycle. - */ - if a.oblique_parent_hashes.len() > usize::from(self.cycle_length) { - return Err(AttestationValidationError::TooManyObliqueHashes); - } - - /* - * Retrieve the set of attestation indices for this slot and shard id. - * - * This is an array mapping the order that validators will appear in the bitfield to the - * canonincal index of a validator. - */ - let attestation_indices = self - .attester_map - .get(&(a.slot, a.shard_id)) - .ok_or(AttestationValidationError::BadAttesterMap)?; - - /* - * The bitfield must be no longer than the minimum required to represent each validator in the - * attestation indices for this slot and shard id. - */ - if a.attester_bitfield.num_bytes() != bytes_for_bits(attestation_indices.len()) { - return Err(AttestationValidationError::BadBitfieldLength); - } - - /* - * If there are excess bits in the bitfield because the number of a validators in not a - * multiple of 8, reject this attestation record. - * - * Allow extra set bits would permit mutliple different byte layouts (and therefore hashes) to - * refer to the same AttesationRecord. - */ - if a.attester_bitfield.len() > attestation_indices.len() { - return Err(AttestationValidationError::InvalidBitfieldEndBits); - } - - /* - * Generate the parent hashes for this attestation - */ - let parent_hashes = attestation_parent_hashes( - self.cycle_length, - self.block_slot, - a.slot, - &self.recent_block_hashes, - &a.oblique_parent_hashes, - )?; - - /* - * The specified justified block hash supplied in the attestation must be in the chain at - * the given slot number. - * - * First, we find the latest parent hash from the parent_hashes array. Then, using the - * block store (database) we iterate back through the blocks until we find (or fail to - * find) the justified block hash referenced in the attestation record. - */ - let latest_parent_hash = parent_hashes - .last() - .ok_or(AttestationValidationError::BadCurrentHashes)?; - match self - .block_store - .block_at_slot(&latest_parent_hash, a.justified_slot)? - { - Some((ref hash, _)) if *hash == a.justified_block_hash.to_vec() => (), - _ => return Err(AttestationValidationError::InvalidJustifiedBlockHash), - }; - - /* - * Generate the message that this attestation aggregate signature must sign across. - */ - let signed_message = { - generate_signed_message( - a.slot, - &parent_hashes, - a.shard_id, - &a.shard_block_hash, - a.justified_slot, - ) - }; - - let voted_hashset = verify_aggregate_signature_for_indices( - &signed_message, - &a.aggregate_sig, - &attestation_indices, - &a.attester_bitfield, - &self.validator_store, - )?; - - /* - * If the hashset of voters is None, the signature verification failed. - */ - match voted_hashset { - None => Err(AttestationValidationError::BadAggregateSignature), - Some(hashset) => Ok(hashset), - } - } -} - -fn bytes_for_bits(bits: usize) -> usize { - (bits.saturating_sub(1) / 8) + 1 -} - -impl From for AttestationValidationError { - fn from(e: ParentHashesError) -> Self { - match e { - ParentHashesError::BadCurrentHashes => AttestationValidationError::BadCurrentHashes, - ParentHashesError::BadObliqueHashes => AttestationValidationError::BadObliqueHashes, - ParentHashesError::SlotTooLow => AttestationValidationError::BlockSlotTooLow, - ParentHashesError::SlotTooHigh => AttestationValidationError::BlockSlotTooHigh, - ParentHashesError::IntWrapping => AttestationValidationError::IntWrapping, - } - } -} - -impl From for AttestationValidationError { - fn from(e: BeaconBlockAtSlotError) -> Self { - match e { - BeaconBlockAtSlotError::DBError(s) => AttestationValidationError::DBError(s), - _ => AttestationValidationError::InvalidJustifiedBlockHash, - } - } -} - -impl From for AttestationValidationError { - fn from(e: DBError) -> Self { - AttestationValidationError::DBError(e.message) - } -} - -impl From for AttestationValidationError { - fn from(e: SignatureVerificationError) -> Self { - match e { - SignatureVerificationError::BadValidatorIndex => { - AttestationValidationError::BadAttesterMap - } - SignatureVerificationError::PublicKeyCorrupt => { - AttestationValidationError::PublicKeyCorrupt - } - SignatureVerificationError::NoPublicKeyForValidator => { - AttestationValidationError::NoPublicKeyForValidator - } - SignatureVerificationError::DBError(s) => AttestationValidationError::DBError(s), - SignatureVerificationError::OutOfBoundsBitfieldIndex => { - AttestationValidationError::OutOfBoundsBitfieldIndex - } - } - } -} diff --git a/beacon_chain/validation/src/block_validation.rs b/beacon_chain/validation/src/block_validation.rs deleted file mode 100644 index 7898e17c8..000000000 --- a/beacon_chain/validation/src/block_validation.rs +++ /dev/null @@ -1,371 +0,0 @@ -extern crate rayon; - -use self::rayon::prelude::*; - -use super::attestation_validation::{AttestationValidationContext, AttestationValidationError}; -use super::db::stores::{BeaconBlockStore, PoWChainStore, ValidatorStore}; -use super::db::{ClientDB, DBError}; -use super::ssz::{Decodable, DecodeError}; -use super::ssz_helpers::attestation_ssz_splitter::{ - split_all_attestations, split_one_attestation, AttestationSplitError, -}; -use super::ssz_helpers::ssz_beacon_block::{SszBeaconBlock, SszBeaconBlockError}; -use super::types::Hash256; -use super::types::{AttestationRecord, AttesterMap, BeaconBlock, ProposerMap}; -use std::sync::{Arc, RwLock}; - -#[derive(Debug, PartialEq)] -pub enum SszBeaconBlockValidationError { - FutureSlot, - SlotAlreadyFinalized, - UnknownPoWChainRef, - UnknownParentHash, - BadAttestationSsz, - BadAncestorHashesSsz, - BadSpecialsSsz, - ParentSlotHigherThanBlockSlot, - AttestationValidationError(AttestationValidationError), - AttestationSignatureFailed, - ProposerAttestationHasObliqueHashes, - NoProposerSignature, - BadProposerMap, - RwLockPoisoned, - DBError(String), -} - -/// The context against which a block should be validated. -pub struct BeaconBlockValidationContext -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, - /// The last justified block hash as per the client's view of the canonical chain. - pub last_justified_block_hash: Hash256, - /// The last finalized slot as per the client's view of the canonical chain. - pub last_finalized_slot: u64, - /// A vec of the hashes of the blocks preceeding the present slot. - pub recent_block_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>, -} - -impl BeaconBlockValidationContext -where - T: ClientDB, -{ - /// Validate some SszBeaconBlock against a block validation context. An SszBeaconBlock varies from a BeaconBlock in - /// that is a read-only structure that reads directly from encoded SSZ. - /// - /// The reason to validate an SzzBeaconBlock 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. - /// - /// 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: &SszBeaconBlock, - ) -> Result - where - T: ClientDB + Sized, - { - /* - * If the block slot corresponds to a slot in the future, return immediately with an error. - * - * It is up to the calling fn to determine what should be done with "future" blocks (e.g., - * cache or discard). - */ - let block_slot = b.slot(); - if block_slot > self.present_slot { - return Err(SszBeaconBlockValidationError::FutureSlot); - } - - /* - * If the block is unknown (assumed unknown because we checked the db earlier in this - * function) and it comes from a slot that is already finalized, drop the block. - * - * If a slot is finalized, there's no point in considering any other blocks for that slot. - * - * TODO: We can more strongly throw away blocks based on the `last_finalized_block` related - * to this `last_finalized_slot`. Namely, any block in a future slot must include the - * `last_finalized_block` in it's chain. - */ - if block_slot <= self.last_finalized_slot { - return Err(SszBeaconBlockValidationError::SlotAlreadyFinalized); - } - - /* - * 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 the block hash is - * "sufficienty deep in the canonical PoW chain". This should be clarified as the spec - * crystallizes. - */ - let pow_chain_reference = b.pow_chain_reference(); - if !self.pow_store.block_hash_exists(b.pow_chain_reference())? { - return Err(SszBeaconBlockValidationError::UnknownPoWChainRef); - } - - /* - * Store a slice of the serialized attestations from the block SSZ. - */ - let attestations_ssz = &b.attestations_without_length(); - - /* - * Get a slice of the first serialized attestation (the 0'th) and decode it into - * a full AttestationRecord object. - * - * The first attestation must be validated separately as it must contain a signature of the - * proposer of the previous block (this is checked later in this function). - */ - let (first_attestation_ssz, next_index) = split_one_attestation(&attestations_ssz, 0)?; - let (first_attestation, _) = AttestationRecord::ssz_decode(&first_attestation_ssz, 0)?; - - /* - * The first attestation may not have oblique hashes. - * - * The presence of oblique hashes in the first attestation would indicate that the proposer - * of the previous block is attesting to some other block than the one they produced. - */ - if !first_attestation.oblique_parent_hashes.is_empty() { - return Err(SszBeaconBlockValidationError::ProposerAttestationHasObliqueHashes); - } - - /* - * Read the parent hash from the block we are validating then attempt to load - * that parent block ssz from the database. - * - * If that parent doesn't exist in the database or is invalid, reject the block. - * - * Also, read the slot from the parent block for later use. - */ - let parent_hash = b - .parent_hash() - .ok_or(SszBeaconBlockValidationError::BadAncestorHashesSsz)?; - let parent_block_slot = match self.block_store.get_serialized_block(&parent_hash)? { - None => return Err(SszBeaconBlockValidationError::UnknownParentHash), - Some(ssz) => { - let parent_block = SszBeaconBlock::from_slice(&ssz[..])?; - parent_block.slot() - } - }; - - /* - * The parent block slot must be less than the block slot. - * - * In other words, the parent must come before the child. - */ - if parent_block_slot >= block_slot { - return Err(SszBeaconBlockValidationError::ParentSlotHigherThanBlockSlot); - } - - /* - * Generate the context in which attestations will be validated. - */ - let attestation_validation_context = Arc::new(AttestationValidationContext { - block_slot, - parent_block_slot, - cycle_length: self.cycle_length, - last_justified_slot: self.last_justified_slot, - recent_block_hashes: self.recent_block_hashes.clone(), - block_store: self.block_store.clone(), - validator_store: self.validator_store.clone(), - attester_map: self.attester_map.clone(), - }); - - /* - * Validate this first attestation. - */ - let attestation_voters = - attestation_validation_context.validate_attestation(&first_attestation)?; - - /* - * Attempt to read load the parent block proposer from the proposer map. Return with an - * error if it fails. - * - * If the signature of proposer for the parent slot was not present in the first (0'th) - * attestation of this block, reject the block. - */ - let parent_block_proposer = self - .proposer_map - .get(&parent_block_slot) - .ok_or(SszBeaconBlockValidationError::BadProposerMap)?; - if !attestation_voters.contains(&parent_block_proposer) { - return Err(SszBeaconBlockValidationError::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)?; - - /* - * 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 mut 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. Also, fail early if the lock has been - * poisoned. - */ - match failure.read() { - Ok(ref option) if option.is_none() => (), - _ => return None, - } - /* - * 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) => { - /* - * If the failure lock isn't poisoned, set it to some error. - */ - if let Ok(mut f) = failure.write() { - *f = Some(SszBeaconBlockValidationError::from(e)); - } - None - } - /* - * Deserialization succeeded and the attestation should be validated. - */ - Ok((attestation, _)) => { - match attestation_validation_context.validate_attestation(&attestation) { - /* - * Attestation validation failed with some error. - */ - Err(e) => { - /* - * If the failure lock isn't poisoned, set it to some error. - */ - if let Ok(mut f) = failure.write() { - *f = Some(SszBeaconBlockValidationError::from(e)); - } - None - } - /* - * Attestation validation succeded. - */ - Ok(_) => Some(attestation), - } - } - } - }).collect(); - - match failure.into_inner() { - Err(_) => return Err(SszBeaconBlockValidationError::RwLockPoisoned), - Ok(failure) => match failure { - Some(error) => return Err(error), - _ => (), - }, - } - - /* - * Add the first attestation to the vec of deserialized attestations at - * index 0. - */ - deserialized_attestations.insert(0, first_attestation); - - let (ancestor_hashes, _) = Decodable::ssz_decode(&b.ancestor_hashes(), 0) - .map_err(|_| SszBeaconBlockValidationError::BadAncestorHashesSsz)?; - let (specials, _) = Decodable::ssz_decode(&b.specials(), 0) - .map_err(|_| SszBeaconBlockValidationError::BadSpecialsSsz)?; - - /* - * If we have reached this point, the block is a new valid block that is worthy of - * processing. - */ - let block = BeaconBlock { - slot: block_slot, - randao_reveal: Hash256::from(b.randao_reveal()), - pow_chain_reference: Hash256::from(pow_chain_reference), - ancestor_hashes, - active_state_root: Hash256::from(b.act_state_root()), - crystallized_state_root: Hash256::from(b.cry_state_root()), - attestations: deserialized_attestations, - specials, - }; - Ok(block) - } -} - -impl From for SszBeaconBlockValidationError { - fn from(e: DBError) -> Self { - SszBeaconBlockValidationError::DBError(e.message) - } -} - -impl From for SszBeaconBlockValidationError { - fn from(e: AttestationSplitError) -> Self { - match e { - AttestationSplitError::TooShort => SszBeaconBlockValidationError::BadAttestationSsz, - } - } -} - -impl From for SszBeaconBlockValidationError { - fn from(e: SszBeaconBlockError) -> Self { - match e { - SszBeaconBlockError::TooShort => { - SszBeaconBlockValidationError::DBError("Bad parent block in db.".to_string()) - } - SszBeaconBlockError::TooLong => { - SszBeaconBlockValidationError::DBError("Bad parent block in db.".to_string()) - } - } - } -} - -impl From for SszBeaconBlockValidationError { - fn from(e: DecodeError) -> Self { - match e { - DecodeError::TooShort => SszBeaconBlockValidationError::BadAttestationSsz, - DecodeError::TooLong => SszBeaconBlockValidationError::BadAttestationSsz, - } - } -} - -impl From for SszBeaconBlockValidationError { - fn from(e: AttestationValidationError) -> Self { - SszBeaconBlockValidationError::AttestationValidationError(e) - } -} - -/* - * Tests for block validation are contained in the root directory "tests" directory (AKA - * "integration tests directory"). - */ diff --git a/beacon_chain/validation/src/lib.rs b/beacon_chain/validation/src/lib.rs deleted file mode 100644 index b175111eb..000000000 --- a/beacon_chain/validation/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -extern crate bls; -extern crate db; -extern crate hashing; -extern crate ssz; -extern crate ssz_helpers; -extern crate types; - -mod attestation_parent_hashes; -pub mod attestation_validation; -pub mod block_validation; -mod message_generation; -mod signature_verification; diff --git a/beacon_chain/validation/src/message_generation.rs b/beacon_chain/validation/src/message_generation.rs deleted file mode 100644 index 9aa988fba..000000000 --- a/beacon_chain/validation/src/message_generation.rs +++ /dev/null @@ -1,67 +0,0 @@ -use super::hashing::canonical_hash; -use super::ssz::SszStream; -use super::types::Hash256; - -/// Generates the message used to validate the signature provided with an AttestationRecord. -/// -/// Ensures that the signer of the message has a view of the chain that is compatible with ours. -pub fn generate_signed_message( - slot: u64, - parent_hashes: &[Hash256], - shard_id: u16, - shard_block_hash: &Hash256, - justified_slot: u64, -) -> Vec { - /* - * Note: it's a little risky here to use SSZ, because the encoding is not necessarily SSZ - * (for example, SSZ might change whilst this doesn't). - * - * I have suggested switching this to ssz here: - * https://github.com/ethereum/eth2.0-specs/issues/5 - * - * If this doesn't happen, it would be safer to not use SSZ at all. - */ - let mut ssz_stream = SszStream::new(); - ssz_stream.append(&slot); - ssz_stream.append_vec(&parent_hashes.to_vec()); - ssz_stream.append(&shard_id); - ssz_stream.append(shard_block_hash); - ssz_stream.append(&justified_slot); - let bytes = ssz_stream.drain(); - canonical_hash(&bytes) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_generate_signed_message() { - let slot = 93; - let parent_hashes: Vec = (0..12).map(|i| Hash256::from(i as u64)).collect(); - let shard_id = 15; - let shard_block_hash = Hash256::from("shard_block_hash".as_bytes()); - let justified_slot = 18; - - let output = generate_signed_message( - slot, - &parent_hashes, - shard_id, - &shard_block_hash, - justified_slot, - ); - - /* - * Note: this is not some well-known test vector, it's simply the result of running - * this and printing the output. - * - * Once well-known test vectors are established, they should be placed here. - */ - let expected = vec![ - 149, 99, 94, 229, 72, 144, 233, 14, 164, 16, 143, 53, 94, 48, 118, 179, 33, 181, 172, - 215, 2, 191, 176, 18, 188, 172, 137, 178, 236, 66, 74, 120, - ]; - - assert_eq!(output, expected); - } -} diff --git a/beacon_chain/validation/src/signature_verification.rs b/beacon_chain/validation/src/signature_verification.rs deleted file mode 100644 index 59fa59dcb..000000000 --- a/beacon_chain/validation/src/signature_verification.rs +++ /dev/null @@ -1,180 +0,0 @@ -use super::bls::{AggregatePublicKey, AggregateSignature}; -use super::db::stores::{ValidatorStore, ValidatorStoreError}; -use super::db::ClientDB; -use super::types::{Bitfield, BitfieldError}; -use std::collections::HashSet; - -#[derive(Debug, PartialEq)] -pub enum SignatureVerificationError { - BadValidatorIndex, - PublicKeyCorrupt, - NoPublicKeyForValidator, - DBError(String), - OutOfBoundsBitfieldIndex, -} - -impl From for SignatureVerificationError { - fn from(_error: BitfieldError) -> Self { - SignatureVerificationError::OutOfBoundsBitfieldIndex - } -} - -/// Verify an aggregate signature across the supplied message. -/// -/// The public keys used for verification are collected by mapping -/// each true bitfield bit to canonical ValidatorRecord index through -/// the attestation_indicies map. -/// -/// Each public key is loaded from the store on-demand. -pub fn verify_aggregate_signature_for_indices( - message: &[u8], - agg_sig: &AggregateSignature, - attestation_indices: &[usize], - bitfield: &Bitfield, - validator_store: &ValidatorStore, -) -> Result>, SignatureVerificationError> -where - T: ClientDB + Sized, -{ - let mut voters = HashSet::new(); - let mut agg_pub_key = AggregatePublicKey::new(); - - for i in 0..attestation_indices.len() { - let voted = bitfield.get(i)?; - if voted { - /* - * De-reference the attestation index into a canonical ValidatorRecord index. - */ - let validator = *attestation_indices - .get(i) - .ok_or(SignatureVerificationError::BadValidatorIndex)?; - /* - * Load the validators public key from our store. - */ - let pub_key = validator_store - .get_public_key_by_index(validator)? - .ok_or(SignatureVerificationError::NoPublicKeyForValidator)?; - /* - * Add the validators public key to the aggregate public key. - */ - agg_pub_key.add(&pub_key); - /* - * Add validator to the set of voters for this attestation record. - */ - voters.insert(validator); - } - } - /* - * Verify the aggregate public key against the aggregate signature. - * - * This verification will only succeed if the exact set of public keys - * were added to the aggregate public key as those that signed the aggregate signature. - */ - if agg_sig.verify(&message, &agg_pub_key) { - Ok(Some(voters)) - } else { - Ok(None) - } -} - -impl From for SignatureVerificationError { - fn from(error: ValidatorStoreError) -> Self { - match error { - ValidatorStoreError::DBError(s) => SignatureVerificationError::DBError(s), - ValidatorStoreError::DecodeError => SignatureVerificationError::PublicKeyCorrupt, - } - } -} - -#[cfg(test)] -mod tests { - use super::super::bls::{Keypair, Signature}; - use super::super::db::MemoryDB; - use super::*; - use std::sync::Arc; - - /* - * Cases that still need testing: - * - * - No signatures. - * - Database failure. - * - Unknown validator index. - * - Extra validator on signature. - */ - - #[test] - fn test_signature_verification() { - let message = "cats".as_bytes(); - let signing_keypairs = vec![ - Keypair::random(), - Keypair::random(), - Keypair::random(), - Keypair::random(), - Keypair::random(), - Keypair::random(), - ]; - let non_signing_keypairs = vec![ - Keypair::random(), - Keypair::random(), - Keypair::random(), - Keypair::random(), - Keypair::random(), - Keypair::random(), - ]; - /* - * Signing keypairs first, then non-signing - */ - let mut all_keypairs = signing_keypairs.clone(); - all_keypairs.append(&mut non_signing_keypairs.clone()); - - let attestation_indices: Vec = (0..all_keypairs.len()).collect(); - let mut bitfield = Bitfield::from_elem(all_keypairs.len(), false); - for i in 0..signing_keypairs.len() { - bitfield.set(i, true).unwrap(); - } - - let db = Arc::new(MemoryDB::open()); - let store = ValidatorStore::new(db); - - for (i, keypair) in all_keypairs.iter().enumerate() { - store.put_public_key_by_index(i, &keypair.pk).unwrap(); - } - - let mut agg_sig = AggregateSignature::new(); - for keypair in &signing_keypairs { - let sig = Signature::new(&message, &keypair.sk); - agg_sig.add(&sig); - } - - /* - * Test using all valid parameters. - */ - let voters = verify_aggregate_signature_for_indices( - &message, - &agg_sig, - &attestation_indices, - &bitfield, - &store, - ).unwrap(); - - let voters = voters.unwrap(); - (0..signing_keypairs.len()).for_each(|i| assert!(voters.contains(&i))); - (signing_keypairs.len()..non_signing_keypairs.len()) - .for_each(|i| assert!(!voters.contains(&i))); - - /* - * Add another validator to the bitfield, run validation will all other - * parameters the same and assert that it fails. - */ - bitfield.set(signing_keypairs.len() + 1, true).unwrap(); - let voters = verify_aggregate_signature_for_indices( - &message, - &agg_sig, - &attestation_indices, - &bitfield, - &store, - ).unwrap(); - - assert_eq!(voters, None); - } -} diff --git a/beacon_chain/validation/tests/attestation_validation/helpers.rs b/beacon_chain/validation/tests/attestation_validation/helpers.rs deleted file mode 100644 index 680f979da..000000000 --- a/beacon_chain/validation/tests/attestation_validation/helpers.rs +++ /dev/null @@ -1,219 +0,0 @@ -use std::sync::Arc; - -use super::bls::{AggregateSignature, Keypair, SecretKey, Signature}; -use super::db::stores::{BeaconBlockStore, ValidatorStore}; -use super::db::MemoryDB; -use super::hashing::canonical_hash; -use super::ssz::SszStream; -use super::types::{AttestationRecord, AttesterMap, BeaconBlock, Bitfield, Hash256}; -use super::validation::attestation_validation::AttestationValidationContext; - -pub struct TestStore { - pub db: Arc, - pub validator: Arc>, - pub block: Arc>, -} - -impl TestStore { - pub fn new() -> Self { - let db = Arc::new(MemoryDB::open()); - let validator = Arc::new(ValidatorStore::new(db.clone())); - let block = Arc::new(BeaconBlockStore::new(db.clone())); - Self { - db, - validator, - block, - } - } -} - -pub struct TestRig { - pub attestation: AttestationRecord, - pub context: AttestationValidationContext, - pub stores: TestStore, - pub attester_count: usize, -} - -fn generate_message_hash( - slot: u64, - parent_hashes: &[Hash256], - shard_id: u16, - shard_block_hash: &Hash256, - justified_slot: u64, -) -> Vec { - let mut stream = SszStream::new(); - stream.append(&slot); - stream.append_vec(&parent_hashes.to_vec()); - stream.append(&shard_id); - stream.append(shard_block_hash); - stream.append(&justified_slot); - let bytes = stream.drain(); - canonical_hash(&bytes) -} - -pub fn generate_attestation( - shard_id: u16, - shard_block_hash: &Hash256, - block_slot: u64, - attestation_slot: u64, - justified_slot: u64, - justified_block_hash: &Hash256, - cycle_length: u8, - parent_hashes: &[Hash256], - signing_keys: &[Option], - block_store: &BeaconBlockStore, -) -> AttestationRecord { - let mut attester_bitfield = Bitfield::from_elem(signing_keys.len(), false); - 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] - }; - - /* - * Create a justified block at the correct slot and store it in the db. - */ - create_block_at_slot(&block_store, &justified_block_hash, justified_slot); - - /* - * Generate the message that will be signed across for this attr record. - */ - let attestation_message = generate_message_hash( - attestation_slot, - parent_hashes_slice, - shard_id, - shard_block_hash, - justified_slot, - ); - - for (i, secret_key) in signing_keys.iter().enumerate() { - /* - * If the signing key is Some, set the bitfield bit to true - * and sign the aggregate sig. - */ - if let Some(sk) = secret_key { - attester_bitfield.set(i, true).unwrap(); - let sig = Signature::new(&attestation_message, sk); - aggregate_sig.add(&sig); - } - } - - AttestationRecord { - slot: attestation_slot, - shard_id, - oblique_parent_hashes: vec![], - shard_block_hash: shard_block_hash.clone(), - attester_bitfield, - justified_slot, - justified_block_hash: justified_block_hash.clone(), - aggregate_sig, - } -} - -/// Create a minimum viable block at some slot. -/// -/// Allows the validation function to read the block and verify its slot. -pub fn create_block_at_slot(block_store: &BeaconBlockStore, hash: &Hash256, slot: u64) { - let mut justified_block = BeaconBlock::zero(); - justified_block.attestations.push(AttestationRecord::zero()); - justified_block.slot = slot; - let mut s = SszStream::new(); - s.append(&justified_block); - let justified_block_ssz = s.drain(); - block_store - .put_serialized_block(&hash.to_vec(), &justified_block_ssz) - .unwrap(); -} - -/// Inserts a justified_block_hash in a position that will be referenced by an attestation record. -pub fn insert_justified_block_hash( - parent_hashes: &mut Vec, - justified_block_hash: &Hash256, - block_slot: u64, - attestation_slot: u64, -) { - let attestation_parent_hash_index = - parent_hashes.len() - 1 - (block_slot as usize - attestation_slot as usize); - parent_hashes[attestation_parent_hash_index] = justified_block_hash.clone(); -} - -pub fn setup_attestation_validation_test(shard_id: u16, attester_count: usize) -> TestRig { - let stores = TestStore::new(); - - let block_slot = 10000; - let cycle_length: u8 = 64; - let mut parent_hashes: Vec = (0..(cycle_length * 2)) - .map(|i| Hash256::from(i as u64)) - .collect(); - let attestation_slot = block_slot - 1; - let parent_block_slot = attestation_slot; - let last_justified_slot = attestation_slot - 1; - let justified_block_hash = Hash256::from("justified_block".as_bytes()); - let shard_block_hash = Hash256::from("shard_block".as_bytes()); - - /* - * Insert the required justified_block_hash into parent_hashes - */ - insert_justified_block_hash( - &mut parent_hashes, - &justified_block_hash, - block_slot, - attestation_slot, - ); - - let parent_hashes = Arc::new(parent_hashes); - - let mut keypairs = vec![]; - let mut signing_keys = vec![]; - let mut attester_map = AttesterMap::new(); - let mut attesters = vec![]; - - /* - * Generate a random keypair for each validator and clone it into the - * list of keypairs. Store it in the database. - */ - for i in 0..attester_count { - let keypair = Keypair::random(); - keypairs.push(keypair.clone()); - stores - .validator - .put_public_key_by_index(i, &keypair.pk) - .unwrap(); - signing_keys.push(Some(keypair.sk.clone())); - attesters.push(i); - } - attester_map.insert((attestation_slot, shard_id), attesters); - - let context: AttestationValidationContext = AttestationValidationContext { - block_slot, - parent_block_slot, - cycle_length, - last_justified_slot, - recent_block_hashes: parent_hashes.clone(), - block_store: stores.block.clone(), - validator_store: stores.validator.clone(), - attester_map: Arc::new(attester_map), - }; - let attestation = generate_attestation( - shard_id, - &shard_block_hash, - block_slot, - attestation_slot, - last_justified_slot, - &justified_block_hash, - cycle_length, - &parent_hashes.clone(), - &signing_keys, - &stores.block, - ); - - TestRig { - attestation, - context, - stores, - attester_count, - } -} diff --git a/beacon_chain/validation/tests/attestation_validation/mod.rs b/beacon_chain/validation/tests/attestation_validation/mod.rs deleted file mode 100644 index aaaa1200c..000000000 --- a/beacon_chain/validation/tests/attestation_validation/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod helpers; -mod tests; - -use super::bls; -use super::db; -use super::hashing; -use super::ssz; -use super::types; -use super::validation; diff --git a/beacon_chain/validation/tests/attestation_validation/tests.rs b/beacon_chain/validation/tests/attestation_validation/tests.rs deleted file mode 100644 index e4a86b3e0..000000000 --- a/beacon_chain/validation/tests/attestation_validation/tests.rs +++ /dev/null @@ -1,241 +0,0 @@ -use std::sync::Arc; - -use super::bls::AggregateSignature; -use super::helpers::{create_block_at_slot, setup_attestation_validation_test, TestRig}; -use super::types::AttesterMap; -use super::types::Hash256; -use super::validation::attestation_validation::AttestationValidationError; - -fn generic_rig() -> TestRig { - let shard_id = 10; - let validator_count = 2; - setup_attestation_validation_test(shard_id, validator_count) -} - -#[test] -fn test_attestation_validation_valid() { - let rig = generic_rig(); - - let result = rig.context.validate_attestation(&rig.attestation); - - let voter_map = result.unwrap(); - assert_eq!(voter_map.len(), 2); -} - -#[test] -fn test_attestation_validation_invalid_parent_slot_too_high() { - let mut rig = generic_rig(); - - rig.context.parent_block_slot = rig.attestation.slot - 1; - - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!(result, Err(AttestationValidationError::ParentSlotTooHigh)); -} - -#[test] -fn test_attestation_validation_invalid_parent_slot_too_low() { - let mut rig = generic_rig(); - - rig.attestation.slot = rig.context.parent_block_slot - u64::from(rig.context.cycle_length) - 2; - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!(result, Err(AttestationValidationError::ParentSlotTooLow)); -} - -#[test] -fn test_attestation_validation_invalid_block_slot_too_high() { - let mut rig = generic_rig(); - - rig.context.block_slot = rig.attestation.slot - 1; - - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!(result, Err(AttestationValidationError::BlockSlotTooHigh)); -} - -#[test] -fn test_attestation_validation_invalid_block_slot_too_low() { - let mut rig = generic_rig(); - - rig.context.block_slot = rig.context.block_slot + u64::from(rig.context.cycle_length); - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!(result, Err(AttestationValidationError::BlockSlotTooLow)); -} - -#[test] -fn test_attestation_validation_invalid_justified_slot_incorrect() { - let mut rig = generic_rig(); - - let original = rig.attestation.justified_slot; - rig.attestation.justified_slot = original - 1; - // Ensures we don't get a bad justified block error instead. - create_block_at_slot( - &rig.stores.block, - &rig.attestation.justified_block_hash, - rig.attestation.justified_slot, - ); - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!( - result, - Err(AttestationValidationError::BadAggregateSignature) - ); - - rig.attestation.justified_slot = original + 1; - // Ensures we don't get a bad justified block error instead. - create_block_at_slot( - &rig.stores.block, - &rig.attestation.justified_block_hash, - rig.attestation.justified_slot, - ); - // Ensures we don't get an error that the last justified slot is ahead of the context justified - // slot. - rig.context.last_justified_slot = rig.attestation.justified_slot; - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!( - result, - Err(AttestationValidationError::BadAggregateSignature) - ); -} - -#[test] -fn test_attestation_validation_invalid_too_many_oblique() { - let mut rig = generic_rig(); - - let obliques: Vec = (0..(rig.context.cycle_length + 1)) - .map(|i| Hash256::from((i * 2) as u64)) - .collect(); - - rig.attestation.oblique_parent_hashes = obliques; - - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!( - result, - Err(AttestationValidationError::TooManyObliqueHashes) - ); -} - -#[test] -fn test_attestation_validation_invalid_bad_attester_map() { - let mut rig = generic_rig(); - - rig.context.attester_map = Arc::new(AttesterMap::new()); - - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!(result, Err(AttestationValidationError::BadAttesterMap)); -} - -#[test] -fn test_attestation_validation_invalid_bad_bitfield_length() { - let mut rig = generic_rig(); - - /* - * Extend the bitfield by one byte - * - * We take advantage of the fact that setting a bit outside the current bounds will grow the bitvector. - */ - let one_byte_higher = rig.attester_count + 8; - rig.attestation - .attester_bitfield - .set(one_byte_higher, false); - - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!(result, Err(AttestationValidationError::BadBitfieldLength)); -} - -#[test] -fn test_attestation_validation_invalid_invalid_bitfield_end_bit() { - let mut rig = generic_rig(); - - let one_bit_high = rig.attester_count + 1; - rig.attestation.attester_bitfield.set(one_bit_high, true); - - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!( - result, - Err(AttestationValidationError::InvalidBitfieldEndBits) - ); -} - -#[test] -fn test_attestation_validation_invalid_invalid_bitfield_end_bit_with_irreguar_bitfield_len() { - let mut rig = generic_rig(); - - /* - * This test ensure that if the number of attesters is "irregular" (with respect to the - * bitfield), and there is a invalid bit is set, validation will still fail. - * - * "Irregular" here means that number of validators + 1 is not a clean multiple of eight. - * - * This test exists to ensure that the application can distinguish between the highest set - * bit in a bitfield and the byte length of that bitfield - */ - let one_bit_high = rig.attester_count + 1; - assert!( - one_bit_high % 8 != 0, - "the test is ineffective in this case." - ); - rig.attestation.attester_bitfield.set(one_bit_high, true); - - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!( - result, - Err(AttestationValidationError::InvalidBitfieldEndBits) - ); -} - -#[test] -fn test_attestation_validation_invalid_unknown_justified_block_hash() { - let mut rig = generic_rig(); - - rig.attestation.justified_block_hash = Hash256::from("unknown block hash".as_bytes()); - - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!( - result, - Err(AttestationValidationError::InvalidJustifiedBlockHash) - ); -} - -#[test] -fn test_attestation_validation_invalid_unknown_justified_block_hash_wrong_slot() { - let rig = generic_rig(); - - /* - * justified_block_hash points to a block with a slot that is too high. - */ - create_block_at_slot( - &rig.stores.block, - &rig.attestation.justified_block_hash, - rig.attestation.justified_slot + 1, - ); - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!( - result, - Err(AttestationValidationError::InvalidJustifiedBlockHash) - ); - - /* - * justified_block_hash points to a block with a slot that is too low. - */ - create_block_at_slot( - &rig.stores.block, - &rig.attestation.justified_block_hash, - rig.attestation.justified_slot - 1, - ); - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!( - result, - Err(AttestationValidationError::InvalidJustifiedBlockHash) - ); -} - -#[test] -fn test_attestation_validation_invalid_empty_signature() { - let mut rig = generic_rig(); - - rig.attestation.aggregate_sig = AggregateSignature::new(); - - let result = rig.context.validate_attestation(&rig.attestation); - assert_eq!( - result, - Err(AttestationValidationError::BadAggregateSignature) - ); -} diff --git a/beacon_chain/validation/tests/block_validation/helpers.rs b/beacon_chain/validation/tests/block_validation/helpers.rs deleted file mode 100644 index 40db77a85..000000000 --- a/beacon_chain/validation/tests/block_validation/helpers.rs +++ /dev/null @@ -1,233 +0,0 @@ -use std::sync::Arc; - -use super::attestation_validation::helpers::{generate_attestation, insert_justified_block_hash}; -use super::bls::Keypair; -use super::db::stores::{BeaconBlockStore, PoWChainStore, ValidatorStore}; -use super::db::MemoryDB; -use super::ssz::SszStream; -use super::ssz_helpers::ssz_beacon_block::SszBeaconBlock; -use super::types::{AttestationRecord, AttesterMap, BeaconBlock, Hash256, ProposerMap}; -use super::validation::block_validation::{ - BeaconBlockValidationContext, SszBeaconBlockValidationError, -}; - -#[derive(Debug)] -pub struct BeaconBlockTestParams { - 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, - pub parent_proposer_index: usize, - pub validation_context_slot: u64, - pub validation_context_justified_slot: u64, - pub validation_context_justified_block_hash: Hash256, - pub validation_context_finalized_slot: u64, -} - -pub struct TestStore { - pub db: Arc, - pub block: Arc>, - pub pow_chain: Arc>, - pub validator: Arc>, -} - -impl TestStore { - pub fn new() -> Self { - let db = Arc::new(MemoryDB::open()); - let block = Arc::new(BeaconBlockStore::new(db.clone())); - let pow_chain = Arc::new(PoWChainStore::new(db.clone())); - let validator = Arc::new(ValidatorStore::new(db.clone())); - Self { - db, - block, - pow_chain, - validator, - } - } -} - -type ParentHashes = Vec; - -/// Setup for a block validation function, without actually executing the -/// block validation function. -pub fn setup_block_validation_scenario( - params: &BeaconBlockTestParams, -) -> ( - BeaconBlock, - ParentHashes, - AttesterMap, - ProposerMap, - TestStore, -) { - let stores = TestStore::new(); - - 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 mut 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 ancestor_hashes = vec![parent_hash.clone(); 32]; - 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()); - - /* - * Store a valid PoW chain ref - */ - stores - .pow_chain - .put_block_hash(pow_chain_ref.as_ref()) - .unwrap(); - - /* - * Generate a minimum viable parent block and store it in the database. - */ - let mut parent_block = BeaconBlock::zero(); - let parent_attestation = AttestationRecord::zero(); - parent_block.slot = block_slot - 1; - parent_block.attestations.push(parent_attestation); - let parent_block_ssz = serialize_block(&parent_block); - stores - .block - .put_serialized_block(parent_hash.as_ref(), &parent_block_ssz) - .unwrap(); - - let proposer_map = { - let mut proposer_map = ProposerMap::new(); - proposer_map.insert(parent_block.slot, params.parent_proposer_index); - proposer_map - }; - - 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![]; - - /* - * Insert the required justified_block_hash into parent_hashes - */ - insert_justified_block_hash( - &mut parent_hashes, - &justified_block_hash, - block_slot, - attestation_slot, - ); - /* - * For each shard in this slot, generate an attestation. - */ - for shard in 0..shards_per_slot { - let mut signing_keys = vec![]; - let mut attesters = vec![]; - /* - * Generate a random keypair for each validator and clone it into the - * list of keypairs. Store it in the database. - */ - for _ in 0..validators_per_shard { - let keypair = Keypair::random(); - keypairs.push(keypair.clone()); - stores - .validator - .put_public_key_by_index(i, &keypair.pk) - .unwrap(); - signing_keys.push(Some(keypair.sk.clone())); - attesters.push(i); - i += 1; - } - attester_map.insert((attestation_slot, shard), attesters); - - let attestation = generate_attestation( - shard, - &shard_block_hash, - block_slot, - attestation_slot, - attestations_justified_slot, - &justified_block_hash, - cycle_length, - &parent_hashes, - &signing_keys[..], - &stores.block, - ); - attestations.push(attestation); - } - (attester_map, attestations, keypairs) - }; - - let block = BeaconBlock { - slot: block_slot, - randao_reveal, - pow_chain_reference: pow_chain_ref, - ancestor_hashes, - active_state_root, - crystallized_state_root, - attestations, - specials: vec![], - }; - - (block, parent_hashes, attester_map, proposer_map, stores) -} - -/// Helper function to take some BeaconBlock and SSZ serialize it. -pub fn serialize_block(b: &BeaconBlock) -> 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( - params: &BeaconBlockTestParams, - mutator_func: F, -) -> Result -where - F: FnOnce(BeaconBlock, AttesterMap, ProposerMap, TestStore) - -> (BeaconBlock, 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 = SszBeaconBlock::from_slice(&ssz_bytes[..]).unwrap(); - - let context = BeaconBlockValidationContext { - present_slot: params.validation_context_slot, - cycle_length: params.cycle_length, - last_justified_slot: params.validation_context_justified_slot, - last_justified_block_hash: params.validation_context_justified_block_hash, - last_finalized_slot: params.validation_context_finalized_slot, - recent_block_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(), - }; - let block_hash = Hash256::from(&ssz_block.block_hash()[..]); - let validation_result = 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(returned_block) = &validation_result { - assert_eq!(*returned_block, block); - }; - validation_result -} diff --git a/beacon_chain/validation/tests/block_validation/mod.rs b/beacon_chain/validation/tests/block_validation/mod.rs deleted file mode 100644 index 0ec0f17ee..000000000 --- a/beacon_chain/validation/tests/block_validation/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod helpers; -mod tests; - -use super::bls; -use super::db; -use super::hashing; -use super::ssz; -use super::ssz_helpers; -use super::types; -use super::validation; - -use super::attestation_validation; diff --git a/beacon_chain/validation/tests/block_validation/tests.rs b/beacon_chain/validation/tests/block_validation/tests.rs deleted file mode 100644 index cdbe14498..000000000 --- a/beacon_chain/validation/tests/block_validation/tests.rs +++ /dev/null @@ -1,264 +0,0 @@ -use super::bls::AggregateSignature; -use super::hashing::canonical_hash; -use super::helpers::{ - run_block_validation_scenario, serialize_block, BeaconBlockTestParams, TestStore, -}; -use super::ssz_helpers::ssz_beacon_block::SszBeaconBlock; -use super::types::{BeaconBlock, Hash256, ProposerMap}; -use super::validation::attestation_validation::AttestationValidationError; -use super::validation::block_validation::SszBeaconBlockValidationError; - -fn get_simple_params() -> BeaconBlockTestParams { - 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); - let parent_proposer_index = 0; - - let validation_context_slot = block_slot; - let validation_context_justified_slot = attestations_justified_slot; - let validation_context_justified_block_hash = Hash256::from("justified_hash".as_bytes()); - let validation_context_finalized_slot = 0; - - BeaconBlockTestParams { - total_validators, - cycle_length, - shard_count, - shards_per_slot, - validators_per_shard, - parent_proposer_index, - block_slot, - attestations_justified_slot, - validation_context_slot, - validation_context_justified_slot, - validation_context_justified_block_hash, - validation_context_finalized_slot, - } -} - -// TODO: test bad ssz serialization - -#[test] -fn test_block_validation_valid() { - let params = get_simple_params(); - - let mutator = |block: BeaconBlock, attester_map, proposer_map, stores| { - /* - * Do not mutate - */ - (block, attester_map, proposer_map, stores) - }; - - let status = run_block_validation_scenario(¶ms, mutator); - - assert!(status.is_ok()) -} - -#[test] -fn test_block_validation_valid_known_block() { - let params = get_simple_params(); - - let mutator = |block: BeaconBlock, 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); - - /* - * This function does _not_ check if a block is already known. - * - * Known blocks will appear as valid blocks. - */ - assert!(status.is_ok()) -} - -#[test] -fn test_block_validation_parent_slot_too_high() { - let params = get_simple_params(); - - let mutator = |mut block: BeaconBlock, attester_map, proposer_map, stores| { - block.slot = params.validation_context_justified_slot + 1; - (block, attester_map, proposer_map, stores) - }; - - let status = run_block_validation_scenario(¶ms, mutator); - - assert_eq!( - status, - Err(SszBeaconBlockValidationError::ParentSlotHigherThanBlockSlot) - ); -} - -#[test] -fn test_block_validation_invalid_future_slot() { - let params = get_simple_params(); - - let mutator = |mut block: BeaconBlock, attester_map, proposer_map, stores| { - block.slot = block.slot + 1; - (block, attester_map, proposer_map, stores) - }; - - let status = run_block_validation_scenario(¶ms, mutator); - - assert_eq!(status, Err(SszBeaconBlockValidationError::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(SszBeaconBlockValidationError::SlotAlreadyFinalized) - ); -} - -#[test] -fn test_block_validation_invalid_unknown_pow_hash() { - let params = get_simple_params(); - - let mutator = |mut block: BeaconBlock, attester_map, proposer_map, stores| { - block.pow_chain_reference = 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(SszBeaconBlockValidationError::UnknownPoWChainRef) - ); -} - -#[test] -fn test_block_validation_invalid_unknown_parent_hash() { - let params = get_simple_params(); - - let mutator = |mut block: BeaconBlock, attester_map, proposer_map, stores| { - block.ancestor_hashes[0] = Hash256::from("unknown parent block".as_bytes()); - (block, attester_map, proposer_map, stores) - }; - - let status = run_block_validation_scenario(¶ms, mutator); - - assert_eq!( - status, - Err(SszBeaconBlockValidationError::UnknownParentHash) - ); -} - -#[test] -fn test_block_validation_invalid_1st_attestation_signature() { - let params = get_simple_params(); - - let mutator = |mut block: BeaconBlock, 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(SszBeaconBlockValidationError::AttestationValidationError( - AttestationValidationError::BadAggregateSignature - )) - ); -} - -#[test] -fn test_block_validation_invalid_no_parent_proposer_signature() { - let params = get_simple_params(); - - let mutator = - |block: BeaconBlock, attester_map, mut proposer_map: ProposerMap, stores: TestStore| { - /* - * Set the proposer for this slot to be a validator that does not exist. - */ - let ssz = { - let parent_hash = block.parent_hash().unwrap().as_ref(); - stores - .block - .get_serialized_block(parent_hash) - .unwrap() - .unwrap() - }; - let parent_block_slot = SszBeaconBlock::from_slice(&ssz[..]).unwrap().slot(); - 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(SszBeaconBlockValidationError::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(SszBeaconBlockValidationError::BadProposerMap)); -} - -#[test] -fn test_block_validation_invalid_2nd_attestation_signature() { - let params = get_simple_params(); - - let mutator = |mut block: BeaconBlock, 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(¶ms, mutator); - - assert_eq!( - status, - Err(SszBeaconBlockValidationError::AttestationValidationError( - AttestationValidationError::BadAggregateSignature - )) - ); -} diff --git a/beacon_chain/validation/tests/main.rs b/beacon_chain/validation/tests/main.rs deleted file mode 100644 index a68931ed7..000000000 --- a/beacon_chain/validation/tests/main.rs +++ /dev/null @@ -1,20 +0,0 @@ -extern crate validation; - -extern crate bls; -extern crate db; -extern crate hashing; -extern crate ssz; -extern crate ssz_helpers; -extern crate types; - -#[cfg(test)] -mod attestation_validation; -#[cfg(test)] -mod block_validation; - -/* -use lighthouse::bls; -use lighthouse::db; -use lighthouse::state; -use lighthouse::utils; -*/ From 7ea701aa30932fdee35f1831320a4ccf44b82a82 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 10 Dec 2018 09:54:25 +1100 Subject: [PATCH 12/18] Remove old block processing code --- Cargo.toml | 1 - beacon_chain/block_validation/Cargo.toml | 14 - .../block_validation/src/block_validation.rs | 355 ------------------ beacon_chain/block_validation/src/lib.rs | 9 - beacon_chain/chain/src/block_context.rs | 93 ----- beacon_chain/chain/src/block_processing.rs | 225 +---------- beacon_chain/chain/src/lib.rs | 1 - 7 files changed, 7 insertions(+), 691 deletions(-) delete mode 100644 beacon_chain/block_validation/Cargo.toml delete mode 100644 beacon_chain/block_validation/src/block_validation.rs delete mode 100644 beacon_chain/block_validation/src/lib.rs delete mode 100644 beacon_chain/chain/src/block_context.rs diff --git a/Cargo.toml b/Cargo.toml index cc77c734f..60b600efd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,6 @@ name = "lighthouse" [workspace] members = [ "beacon_chain/attestation_validation", - "beacon_chain/block_validation", "beacon_chain/chain", "beacon_chain/naive_fork_choice", "beacon_chain/state-transition", diff --git a/beacon_chain/block_validation/Cargo.toml b/beacon_chain/block_validation/Cargo.toml deleted file mode 100644 index 5a68759c6..000000000 --- a/beacon_chain/block_validation/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "block_validation" -version = "0.1.0" -authors = ["Paul Hauner "] - -[dependencies] -attestation_validation = { path = "../attestation_validation" } -bls = { path = "../utils/bls" } -db = { path = "../../lighthouse/db" } -hashing = { path = "../utils/hashing" } -rayon = "1.0.2" -ssz = { path = "../utils/ssz" } -ssz_helpers = { path = "../utils/ssz_helpers" } -types = { path = "../types" } diff --git a/beacon_chain/block_validation/src/block_validation.rs b/beacon_chain/block_validation/src/block_validation.rs deleted file mode 100644 index 5f295d7fc..000000000 --- a/beacon_chain/block_validation/src/block_validation.rs +++ /dev/null @@ -1,355 +0,0 @@ -extern crate rayon; - -use self::rayon::prelude::*; - -use super::attestation_validation::{AttestationValidationContext, AttestationValidationError}; -use super::db::stores::{BeaconBlockStore, PoWChainStore, ValidatorStore}; -use super::db::{ClientDB, DBError}; -use super::ssz::{Decodable, DecodeError}; -use super::ssz_helpers::attestation_ssz_splitter::{ - split_all_attestations, split_one_attestation, AttestationSplitError, -}; -use super::ssz_helpers::ssz_beacon_block::{SszBeaconBlock, SszBeaconBlockError}; -use super::types::Hash256; -use super::types::{AttestationRecord, AttesterMap, BeaconBlock, ProposerMap}; -use std::sync::{Arc, RwLock}; - -#[derive(Debug, PartialEq)] -pub enum SszBeaconBlockValidationError { - FutureSlot, - SlotAlreadyFinalized, - UnknownPoWChainRef, - UnknownParentHash, - BadAttestationSsz, - BadAncestorHashesSsz, - BadSpecialsSsz, - ParentSlotHigherThanBlockSlot, - AttestationValidationError(AttestationValidationError), - AttestationSignatureFailed, - ProposerAttestationHasObliqueHashes, - NoProposerSignature, - BadProposerMap, - RwLockPoisoned, - DBError(String), -} - -/// The context against which a block should be validated. -pub struct BeaconBlockValidationContext -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, - /// The last justified block hash as per the client's view of the canonical chain. - pub last_justified_block_hash: Hash256, - /// The last finalized slot as per the client's view of the canonical chain. - pub last_finalized_slot: u64, - /// A vec of the hashes of the blocks preceeding the present slot. - pub recent_block_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>, -} - -impl BeaconBlockValidationContext -where - T: ClientDB, -{ - /// Validate some SszBeaconBlock against a block validation context. An SszBeaconBlock varies from a BeaconBlock in - /// that is a read-only structure that reads directly from encoded SSZ. - /// - /// The reason to validate an SzzBeaconBlock 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. - /// - /// 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: &SszBeaconBlock, - ) -> Result - where - T: ClientDB + Sized, - { - /* - * If the block slot corresponds to a slot in the future, return immediately with an error. - * - * It is up to the calling fn to determine what should be done with "future" blocks (e.g., - * cache or discard). - */ - let block_slot = b.slot(); - if block_slot > self.present_slot { - return Err(SszBeaconBlockValidationError::FutureSlot); - } - - /* - * If the block is unknown (assumed unknown because we checked the db earlier in this - * function) and it comes from a slot that is already finalized, drop the block. - * - * If a slot is finalized, there's no point in considering any other blocks for that slot. - * - * TODO: We can more strongly throw away blocks based on the `last_finalized_block` related - * to this `last_finalized_slot`. Namely, any block in a future slot must include the - * `last_finalized_block` in it's chain. - */ - if block_slot <= self.last_finalized_slot { - return Err(SszBeaconBlockValidationError::SlotAlreadyFinalized); - } - - /* - * 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 the block hash is - * "sufficienty deep in the canonical PoW chain". This should be clarified as the spec - * crystallizes. - */ - let pow_chain_reference = b.pow_chain_reference(); - if !self.pow_store.block_hash_exists(b.pow_chain_reference())? { - return Err(SszBeaconBlockValidationError::UnknownPoWChainRef); - } - - /* - * Store a slice of the serialized attestations from the block SSZ. - */ - let attestations_ssz = &b.attestations_without_length(); - - /* - * Get a slice of the first serialized attestation (the 0'th) and decode it into - * a full AttestationRecord object. - * - * The first attestation must be validated separately as it must contain a signature of the - * proposer of the previous block (this is checked later in this function). - */ - let (first_attestation_ssz, next_index) = split_one_attestation(&attestations_ssz, 0)?; - let (first_attestation, _) = AttestationRecord::ssz_decode(&first_attestation_ssz, 0)?; - - /* - * The first attestation may not have oblique hashes. - * - * The presence of oblique hashes in the first attestation would indicate that the proposer - * of the previous block is attesting to some other block than the one they produced. - */ - if !first_attestation.oblique_parent_hashes.is_empty() { - return Err(SszBeaconBlockValidationError::ProposerAttestationHasObliqueHashes); - } - - /* - * Read the parent hash from the block we are validating then attempt to load - * that parent block ssz from the database. - * - * If that parent doesn't exist in the database or is invalid, reject the block. - * - * Also, read the slot from the parent block for later use. - */ - let parent_hash = b - .parent_hash() - .ok_or(SszBeaconBlockValidationError::BadAncestorHashesSsz)?; - let parent_block_slot = match self.block_store.get_serialized_block(&parent_hash)? { - None => return Err(SszBeaconBlockValidationError::UnknownParentHash), - Some(ssz) => { - let parent_block = SszBeaconBlock::from_slice(&ssz[..])?; - parent_block.slot() - } - }; - - /* - * The parent block slot must be less than the block slot. - * - * In other words, the parent must come before the child. - */ - if parent_block_slot >= block_slot { - return Err(SszBeaconBlockValidationError::ParentSlotHigherThanBlockSlot); - } - - /* - * TODO: Validate the first attestation. - */ - - /* - * Attempt to read load the parent block proposer from the proposer map. Return with an - * error if it fails. - * - * If the signature of proposer for the parent slot was not present in the first (0'th) - * attestation of this block, reject the block. - */ - let parent_block_proposer = self - .proposer_map - .get(&parent_block_slot) - .ok_or(SszBeaconBlockValidationError::BadProposerMap)?; - if !attestation_voters.contains(&parent_block_proposer) { - return Err(SszBeaconBlockValidationError::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)?; - - /* - * 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 mut 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. Also, fail early if the lock has been - * poisoned. - */ - match failure.read() { - Ok(ref option) if option.is_none() => (), - _ => return None, - } - /* - * 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) => { - /* - * If the failure lock isn't poisoned, set it to some error. - */ - if let Ok(mut f) = failure.write() { - *f = Some(SszBeaconBlockValidationError::from(e)); - } - None - } - /* - * Deserialization succeeded and the attestation should be validated. - */ - Ok((attestation, _)) => { - match attestation_validation_context.validate_attestation(&attestation) { - /* - * Attestation validation failed with some error. - */ - Err(e) => { - /* - * If the failure lock isn't poisoned, set it to some error. - */ - if let Ok(mut f) = failure.write() { - *f = Some(SszBeaconBlockValidationError::from(e)); - } - None - } - /* - * Attestation validation succeded. - */ - Ok(_) => Some(attestation), - } - } - } - }).collect(); - - match failure.into_inner() { - Err(_) => return Err(SszBeaconBlockValidationError::RwLockPoisoned), - Ok(failure) => match failure { - Some(error) => return Err(error), - _ => (), - }, - } - - /* - * Add the first attestation to the vec of deserialized attestations at - * index 0. - */ - deserialized_attestations.insert(0, first_attestation); - - let (ancestor_hashes, _) = Decodable::ssz_decode(&b.ancestor_hashes(), 0) - .map_err(|_| SszBeaconBlockValidationError::BadAncestorHashesSsz)?; - let (specials, _) = Decodable::ssz_decode(&b.specials(), 0) - .map_err(|_| SszBeaconBlockValidationError::BadSpecialsSsz)?; - - /* - * If we have reached this point, the block is a new valid block that is worthy of - * processing. - */ - let block = BeaconBlock { - slot: block_slot, - randao_reveal: Hash256::from(b.randao_reveal()), - pow_chain_reference: Hash256::from(pow_chain_reference), - ancestor_hashes, - active_state_root: Hash256::from(b.act_state_root()), - crystallized_state_root: Hash256::from(b.cry_state_root()), - attestations: deserialized_attestations, - specials, - }; - Ok(block) - } -} - -impl From for SszBeaconBlockValidationError { - fn from(e: DBError) -> Self { - SszBeaconBlockValidationError::DBError(e.message) - } -} - -impl From for SszBeaconBlockValidationError { - fn from(e: AttestationSplitError) -> Self { - match e { - AttestationSplitError::TooShort => SszBeaconBlockValidationError::BadAttestationSsz, - } - } -} - -impl From for SszBeaconBlockValidationError { - fn from(e: SszBeaconBlockError) -> Self { - match e { - SszBeaconBlockError::TooShort => { - SszBeaconBlockValidationError::DBError("Bad parent block in db.".to_string()) - } - SszBeaconBlockError::TooLong => { - SszBeaconBlockValidationError::DBError("Bad parent block in db.".to_string()) - } - } - } -} - -impl From for SszBeaconBlockValidationError { - fn from(e: DecodeError) -> Self { - match e { - DecodeError::TooShort => SszBeaconBlockValidationError::BadAttestationSsz, - DecodeError::TooLong => SszBeaconBlockValidationError::BadAttestationSsz, - } - } -} - -impl From for SszBeaconBlockValidationError { - fn from(e: AttestationValidationError) -> Self { - SszBeaconBlockValidationError::AttestationValidationError(e) - } -} - -/* - * Tests for block validation are contained in the root directory "tests" directory (AKA - * "integration tests directory"). - */ diff --git a/beacon_chain/block_validation/src/lib.rs b/beacon_chain/block_validation/src/lib.rs deleted file mode 100644 index c4eae2bd8..000000000 --- a/beacon_chain/block_validation/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -extern crate attestation_validation; -extern crate bls; -extern crate db; -extern crate hashing; -extern crate ssz; -extern crate ssz_helpers; -extern crate types; - -pub mod block_validation; diff --git a/beacon_chain/chain/src/block_context.rs b/beacon_chain/chain/src/block_context.rs deleted file mode 100644 index 75c4352ec..000000000 --- a/beacon_chain/chain/src/block_context.rs +++ /dev/null @@ -1,93 +0,0 @@ -use super::BeaconChain; -use db::stores::BeaconBlockAtSlotError; -use db::ClientDB; -use ssz_helpers::ssz_beacon_block::SszBeaconBlock; -use std::sync::Arc; -use types::Hash256; -use validation::block_validation::BeaconBlockValidationContext; - -pub enum BlockValidationContextError { - UnknownCrystallizedState, - UnknownActiveState, - UnknownAttesterProposerMaps, - NoParentHash, - UnknownJustifiedBlock, - BlockAlreadyKnown, - BlockSlotLookupError(BeaconBlockAtSlotError), -} - -impl From for BlockValidationContextError { - fn from(e: BeaconBlockAtSlotError) -> BlockValidationContextError { - BlockValidationContextError::BlockSlotLookupError(e) - } -} - -impl BeaconChain -where - T: ClientDB + Sized, -{ - pub(crate) fn block_validation_context( - &self, - block: &SszBeaconBlock, - parent_block: &SszBeaconBlock, - present_slot: u64, - ) -> Result, BlockValidationContextError> { - /* - * Load the crystallized state for this block from our caches. - * - * Fail if the crystallized state is unknown. - */ - let cry_state_root = Hash256::from(parent_block.cry_state_root()); - let cry_state = self - .crystallized_states - .get(&cry_state_root) - .ok_or(BlockValidationContextError::UnknownCrystallizedState)?; - - /* - * Load the active state for this block from our caches. - * - * Fail if the active state is unknown. - */ - let act_state_root = Hash256::from(parent_block.act_state_root()); - let act_state = self - .active_states - .get(&act_state_root) - .ok_or(BlockValidationContextError::UnknownActiveState)?; - - /* - * Learn the last justified slot from the crystallized state and load - * the hash of this block from the database - */ - let last_justified_slot = cry_state.last_justified_slot; - let parent_block_hash = block - .parent_hash() - .ok_or(BlockValidationContextError::NoParentHash)?; - let (last_justified_block_hash, _) = self - .store - .block - .block_at_slot(&parent_block_hash, last_justified_slot)? - .ok_or(BlockValidationContextError::UnknownJustifiedBlock)?; - - /* - * Load the attester and proposer maps for the crystallized state. - */ - let (attester_map, proposer_map) = self - .attester_proposer_maps - .get(&cry_state_root) - .ok_or(BlockValidationContextError::UnknownAttesterProposerMaps)?; - - Ok(BeaconBlockValidationContext { - present_slot, - cycle_length: self.config.cycle_length, - last_justified_slot: cry_state.last_justified_slot, - last_justified_block_hash: Hash256::from(&last_justified_block_hash[..]), - last_finalized_slot: self.last_finalized_slot, - recent_block_hashes: Arc::new(act_state.recent_block_hashes.clone()), - proposer_map: proposer_map.clone(), - attester_map: attester_map.clone(), - block_store: self.store.block.clone(), - validator_store: self.store.validator.clone(), - pow_store: self.store.pow_chain.clone(), - }) - } -} diff --git a/beacon_chain/chain/src/block_processing.rs b/beacon_chain/chain/src/block_processing.rs index bfad68c15..8954e48d8 100644 --- a/beacon_chain/chain/src/block_processing.rs +++ b/beacon_chain/chain/src/block_processing.rs @@ -1,9 +1,5 @@ -use super::block_context::BlockValidationContextError; -use super::state_transition::StateTransitionError; use super::BeaconChain; -use db::{ClientDB, DBError}; -use naive_fork_choice::{naive_fork_choice, ForkChoiceError}; -use ssz_helpers::ssz_beacon_block::{SszBeaconBlock, SszBeaconBlockError}; +use db::ClientDB; use types::Hash256; pub enum BlockProcessingOutcome { @@ -13,18 +9,8 @@ pub enum BlockProcessingOutcome { NewForkBlock, } -pub enum BlockProcessingError { - ParentBlockNotFound, - ActiveStateRootInvalid, - CrystallizedStateRootInvalid, - NoHeadHashes, - UnknownParentHash, - ForkChoiceFailed(ForkChoiceError), - ContextGenerationFailed(BlockValidationContextError), - DeserializationFailed(SszBeaconBlockError), - ValidationFailed, - StateTransitionFailed(StateTransitionError), - DBError(String), +pub enum Error { + NotImplemented, } impl BeaconChain @@ -35,206 +21,9 @@ where &mut self, ssz: &[u8], present_slot: u64, - ) -> Result<(BlockProcessingOutcome, Hash256), BlockProcessingError> { - /* - * Generate a SszBlock to read directly from the serialized SSZ. - */ - let ssz_block = SszBeaconBlock::from_slice(ssz)?; - let block_hash = Hash256::from(&ssz_block.block_hash()[..]); - - /* - * If this block is already known, return immediately and indicate the the block is - * known. Don't attempt to deserialize the block. - */ - if self.store.block.block_exists(&block_hash)? { - return Ok((BlockProcessingOutcome::BlockAlreadyKnown, block_hash)); - } - - /* - * Determine the hash of the blocks parent - */ - let parent_hash = ssz_block - .parent_hash() - .ok_or(BlockProcessingError::UnknownParentHash)?; - - /* - * Load the parent block from the database and create an SszBeaconBlock for reading it. - */ - let parent_block_ssz_bytes = self - .store - .block - .get_serialized_block(&parent_hash[..])? - .ok_or(BlockProcessingError::ParentBlockNotFound)?; - let parent_ssz_block = SszBeaconBlock::from_slice(&parent_block_ssz_bytes)?; - - /* - * Generate the context in which to validate this block. - */ - let validation_context = - self.block_validation_context(&ssz_block, &parent_ssz_block, present_slot)?; - - /* - * Validate the block against the context, checking signatures, parent_hashes, etc. - */ - let block = validation_context.validate_ssz_block(&ssz_block)?; - - let (new_act_state, new_cry_state_option) = { - /* - * Load the states from memory. - * - * Note: this is the second time we load these, the first was in - * `block_validation_context`. Theres an opportunity for some opimisation here. - * It was left out because it made the code more cumbersome. - */ - let act_state = self - .active_states - .get(&block.active_state_root) - .ok_or(BlockValidationContextError::UnknownActiveState)?; - let cry_state = self - .crystallized_states - .get(&block.crystallized_state_root) - .ok_or(BlockValidationContextError::UnknownCrystallizedState)?; - - self.transition_states(act_state, cry_state, &block, &block_hash)? - }; - - /* - * Calculate the new active state root and ensure the block state root matches. - */ - let new_act_state_root = new_act_state.canonical_root(); - if new_act_state_root != block.active_state_root { - return Err(BlockProcessingError::ActiveStateRootInvalid); - } - - /* - * Determine the crystallized state root and ensure the block state root matches. - * - * If a new crystallized state was created, store it in memory. - */ - let (new_cry_state_root, cry_state_transitioned) = match new_cry_state_option { - None => { - /* - * A new crystallized state was not created, therefore the - * `crystallized_state_root` of this block must match its parent. - */ - if Hash256::from(parent_ssz_block.cry_state_root()) != block.crystallized_state_root - { - return Err(BlockProcessingError::ActiveStateRootInvalid); - } - // Return the old root - (block.crystallized_state_root, false) - } - Some(new_cry_state) => { - /* - * A new crystallized state was created. Check to ensure the crystallized - * state root in the block is the same as the calculated on this node. - */ - let cry_state_root = new_cry_state.canonical_root(); - if cry_state_root != block.crystallized_state_root { - return Err(BlockProcessingError::ActiveStateRootInvalid); - } - /* - * Store the new crystallized state in memory. - */ - self.crystallized_states - .insert(cry_state_root, new_cry_state); - // Return the new root - (cry_state_root, true) - } - }; - - /* - * Store the new block as a leaf in the block tree. - */ - let mut new_head_block_hashes = self.head_block_hashes.clone(); - let new_parent_head_hash_index = match new_head_block_hashes - .iter() - .position(|x| *x == Hash256::from(parent_hash)) - { - Some(i) => { - new_head_block_hashes[i] = block_hash.clone(); - i - } - None => { - new_head_block_hashes.push(block_hash.clone()); - new_head_block_hashes.len() - 1 - } - }; - - /* - * Store the new block in the database. - */ - self.store - .block - .put_serialized_block(&block_hash[..], ssz_block.block_ssz())?; - - /* - * Store the active state in memory. - */ - self.active_states.insert(new_act_state_root, new_act_state); - - let new_canonical_head_block_hash_index = - match naive_fork_choice(&self.head_block_hashes, self.store.block.clone())? { - None => { - /* - * Fork choice failed, therefore the block, active state and crystallized state - * can be removed from storage (i.e., forgotten). - */ - if cry_state_transitioned { - // A new crystallized state was generated, so it should be deleted. - self.crystallized_states.remove(&new_cry_state_root); - } - self.active_states.remove(&new_act_state_root); - self.store.block.delete_block(&block_hash[..])?; - return Err(BlockProcessingError::NoHeadHashes); - } - Some(i) => i, - }; - - if new_canonical_head_block_hash_index != self.canonical_head_block_hash { - /* - * The block caused a re-org (switch of chains). - */ - Ok((BlockProcessingOutcome::NewReorgBlock, block_hash)) - } else { - /* - * The block did not cause a re-org. - */ - if new_parent_head_hash_index == self.canonical_head_block_hash { - Ok((BlockProcessingOutcome::NewCanonicalBlock, block_hash)) - } else { - Ok((BlockProcessingOutcome::NewForkBlock, block_hash)) - } - } - } -} - -impl From for BlockProcessingError { - fn from(e: BlockValidationContextError) -> Self { - BlockProcessingError::ContextGenerationFailed(e) - } -} - -impl From for BlockProcessingError { - fn from(e: SszBeaconBlockError) -> Self { - BlockProcessingError::DeserializationFailed(e) - } -} - -impl From for BlockProcessingError { - fn from(e: DBError) -> Self { - BlockProcessingError::DBError(e.message) - } -} - -impl From for BlockProcessingError { - fn from(e: ForkChoiceError) -> Self { - BlockProcessingError::ForkChoiceFailed(e) - } -} - -impl From for BlockProcessingError { - fn from(e: StateTransitionError) -> Self { - BlockProcessingError::StateTransitionFailed(e) + ) -> Result<(BlockProcessingOutcome, Hash256), Error> { + // TODO: block processing has been removed. + // https://github.com/sigp/lighthouse/issues/98 + Err(Error::NotImplemented) } } diff --git a/beacon_chain/chain/src/lib.rs b/beacon_chain/chain/src/lib.rs index 00ae5f325..17cda320f 100644 --- a/beacon_chain/chain/src/lib.rs +++ b/beacon_chain/chain/src/lib.rs @@ -7,7 +7,6 @@ extern crate types; extern crate validator_induction; extern crate validator_shuffling; -mod block_context; mod block_processing; mod genesis; mod maps; From 3ae82c0710729645acce28cd1c3090b5ed12f1dd Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 10 Dec 2018 10:48:02 +1100 Subject: [PATCH 13/18] Strip out anitquated parts of chain crate --- beacon_chain/chain/src/block_processing.rs | 4 +- beacon_chain/chain/src/genesis.rs | 79 ++++++---------------- beacon_chain/chain/src/lib.rs | 43 ++---------- beacon_chain/chain/tests/main.rs | 7 -- 4 files changed, 27 insertions(+), 106 deletions(-) delete mode 100644 beacon_chain/chain/tests/main.rs diff --git a/beacon_chain/chain/src/block_processing.rs b/beacon_chain/chain/src/block_processing.rs index 8954e48d8..b576bdbce 100644 --- a/beacon_chain/chain/src/block_processing.rs +++ b/beacon_chain/chain/src/block_processing.rs @@ -19,8 +19,8 @@ where { pub fn process_block( &mut self, - ssz: &[u8], - present_slot: u64, + _ssz: &[u8], + _present_slot: u64, ) -> Result<(BlockProcessingOutcome, Hash256), Error> { // TODO: block processing has been removed. // https://github.com/sigp/lighthouse/issues/98 diff --git a/beacon_chain/chain/src/genesis.rs b/beacon_chain/chain/src/genesis.rs index fb0bc58f8..d9b634062 100644 --- a/beacon_chain/chain/src/genesis.rs +++ b/beacon_chain/chain/src/genesis.rs @@ -1,22 +1,24 @@ -use super::{ActiveState, BeaconChainError, ChainConfig, CrystallizedState}; -use types::{CrosslinkRecord, Hash256, ValidatorStatus}; +use super::{ActiveState, ChainConfig, CrystallizedState}; +use types::ValidatorStatus; use validator_induction::ValidatorInductor; use validator_shuffling::{shard_and_committees_for_cycle, ValidatorAssignmentError}; -pub const INITIAL_FORK_VERSION: u32 = 0; +#[derive(Debug, PartialEq)] +pub enum Error { + ValidationAssignmentError(ValidatorAssignmentError), + NotImplemented, +} -impl From for BeaconChainError { - fn from(_: ValidatorAssignmentError) -> BeaconChainError { - BeaconChainError::InvalidGenesis +impl From for Error { + fn from(e: ValidatorAssignmentError) -> Error { + Error::ValidationAssignmentError(e) } } /// Initialize a new ChainHead with genesis parameters. /// /// Used when syncing a chain from scratch. -pub fn genesis_states( - config: &ChainConfig, -) -> Result<(ActiveState, CrystallizedState), ValidatorAssignmentError> { +pub fn genesis_states(config: &ChainConfig) -> Result<(ActiveState, CrystallizedState), Error> { /* * Parse the ValidatorRegistrations into ValidatorRecords and induct them. * @@ -35,63 +37,17 @@ pub fn genesis_states( * * Crystallizedstate stores two cycles, so we simply repeat the same assignment twice. */ - let shard_and_committee_for_slots = { + let _shard_and_committee_for_slots = { let mut a = shard_and_committees_for_cycle(&vec![0; 32], &validators, 0, &config)?; let mut b = a.clone(); a.append(&mut b); a }; - /* - * Set all the crosslink records to reference zero hashes. - */ - let crosslinks = { - let mut c = vec![]; - for _ in 0..config.shard_count { - c.push(CrosslinkRecord { - recently_changed: false, - slot: 0, - hash: Hash256::zero(), - }); - } - c - }; + // TODO: implement genesis for `BeaconState` + // https://github.com/sigp/lighthouse/issues/99 - /* - * Initialize a genesis `Crystallizedstate` - */ - let crystallized_state = CrystallizedState { - validator_set_change_slot: 0, - validators: validators.to_vec(), - crosslinks, - last_state_recalculation_slot: 0, - last_finalized_slot: 0, - last_justified_slot: 0, - justified_streak: 0, - shard_and_committee_for_slots, - deposits_penalized_in_period: vec![], - validator_set_delta_hash_chain: Hash256::zero(), - pre_fork_version: INITIAL_FORK_VERSION, - post_fork_version: INITIAL_FORK_VERSION, - fork_slot_number: 0, - }; - - /* - * Set all recent block hashes to zero. - */ - let recent_block_hashes = vec![Hash256::zero(); config.cycle_length as usize]; - - /* - * Create an active state. - */ - let active_state = ActiveState { - pending_attestations: vec![], - pending_specials: vec![], - recent_block_hashes, - randao_mix: Hash256::zero(), - }; - - Ok((active_state, crystallized_state)) + Err(Error::NotImplemented) } #[cfg(test)] @@ -99,6 +55,10 @@ mod tests { extern crate bls; extern crate validator_induction; + // TODO: implement genesis for `BeaconState` + // https://github.com/sigp/lighthouse/issues/99 + // + /* use self::bls::{create_proof_of_possession, Keypair}; use super::*; use types::{Address, Hash256, ValidatorRegistration}; @@ -190,4 +150,5 @@ mod tests { ); assert_eq!(cry.validators.len(), good_validator_count); } + */ } diff --git a/beacon_chain/chain/src/lib.rs b/beacon_chain/chain/src/lib.rs index 17cda320f..0837f3baf 100644 --- a/beacon_chain/chain/src/lib.rs +++ b/beacon_chain/chain/src/lib.rs @@ -14,7 +14,7 @@ mod stores; mod transition; use db::ClientDB; -use genesis::genesis_states; +use genesis::{genesis_states, Error as GenesisError}; use maps::{generate_attester_and_proposer_maps, AttesterAndProposerMapError}; use std::collections::HashMap; use std::sync::Arc; @@ -26,6 +26,7 @@ pub enum BeaconChainError { InvalidGenesis, InsufficientValidators, UnableToGenerateMaps(AttesterAndProposerMapError), + GenesisError(GenesisError), DBError(String), } @@ -101,42 +102,8 @@ impl From for BeaconChainError { } } -#[cfg(test)] -mod tests { - use super::*; - use db::stores::*; - use db::MemoryDB; - use std::sync::Arc; - use types::ValidatorRegistration; - - #[test] - fn test_new_chain() { - let mut config = ChainConfig::standard(); - config.cycle_length = 4; - config.shard_count = 4; - let db = Arc::new(MemoryDB::open()); - let store = BeaconChainStore { - block: Arc::new(BeaconBlockStore::new(db.clone())), - pow_chain: Arc::new(PoWChainStore::new(db.clone())), - validator: Arc::new(ValidatorStore::new(db.clone())), - }; - - for _ in 0..config.cycle_length * 2 { - config - .initial_validators - .push(ValidatorRegistration::random()) - } - - let chain = BeaconChain::new(store, config.clone()).unwrap(); - let (act, cry) = genesis_states(&config).unwrap(); - - assert_eq!(chain.last_finalized_slot, 0); - assert_eq!(chain.canonical_block_hash(), Hash256::zero()); - - let stored_act = chain.active_states.get(&Hash256::zero()).unwrap(); - assert_eq!(act, *stored_act); - - let stored_cry = chain.crystallized_states.get(&Hash256::zero()).unwrap(); - assert_eq!(cry, *stored_cry); +impl From for BeaconChainError { + fn from(e: GenesisError) -> BeaconChainError { + BeaconChainError::GenesisError(e) } } diff --git a/beacon_chain/chain/tests/main.rs b/beacon_chain/chain/tests/main.rs deleted file mode 100644 index 8b926693e..000000000 --- a/beacon_chain/chain/tests/main.rs +++ /dev/null @@ -1,7 +0,0 @@ -extern crate chain; - -#[cfg(test)] -mod tests { - use chain::{BeaconChain, BeaconChainError}; - -} From 4d43de1ceae1feb8d0b644bce525864498f7879b Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 10 Dec 2018 20:32:44 -0800 Subject: [PATCH 14/18] rustfmt edits --- beacon_chain/validation/src/attestation_validation.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/beacon_chain/validation/src/attestation_validation.rs b/beacon_chain/validation/src/attestation_validation.rs index e31f3ae52..4953a92c3 100644 --- a/beacon_chain/validation/src/attestation_validation.rs +++ b/beacon_chain/validation/src/attestation_validation.rs @@ -85,9 +85,10 @@ where * The slot of this attestation must not be more than cycle_length + 1 distance * from the parent_slot of block that contained it. */ - if a.slot < self - .parent_block_slot - .saturating_sub(u64::from(self.cycle_length).saturating_add(1)) + if a.slot + < self + .parent_block_slot + .saturating_sub(u64::from(self.cycle_length).saturating_add(1)) { return Err(AttestationValidationError::ParentSlotTooLow); } From 1ffd9e10b3215f897eec3bb9a17bdf45070f6ae7 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 10 Dec 2018 20:33:43 -0800 Subject: [PATCH 15/18] Fixes bug with attestation validation that arose from change to API --- beacon_chain/validation/src/attestation_validation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_chain/validation/src/attestation_validation.rs b/beacon_chain/validation/src/attestation_validation.rs index 4953a92c3..5d45bfcd2 100644 --- a/beacon_chain/validation/src/attestation_validation.rs +++ b/beacon_chain/validation/src/attestation_validation.rs @@ -135,7 +135,7 @@ where * Allow extra set bits would permit mutliple different byte layouts (and therefore hashes) to * refer to the same AttesationRecord. */ - if a.attester_bitfield.len() > attestation_indices.len() { + if a.attester_bitfield.num_set_bits() > attestation_indices.len() { return Err(AttestationValidationError::InvalidBitfieldEndBits); } From 6c2c42e6b779eccd55159ed76fad5a0e0d8af6ed Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Mon, 10 Dec 2018 20:34:35 -0800 Subject: [PATCH 16/18] Adds custom `std::cmp::PartialEq` impl Two bitfields now match if they contain the same information. There were some discrepancies before when comparing fields with the same bits set but came from different sources, e.g. off the wire vs created in memory, due to the existence of unset bits in the high byte. --- beacon_chain/utils/boolean-bitfield/src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/beacon_chain/utils/boolean-bitfield/src/lib.rs b/beacon_chain/utils/boolean-bitfield/src/lib.rs index fadb47083..98518d70c 100644 --- a/beacon_chain/utils/boolean-bitfield/src/lib.rs +++ b/beacon_chain/utils/boolean-bitfield/src/lib.rs @@ -3,11 +3,12 @@ extern crate ssz; use bit_vec::BitVec; +use std::cmp; use std::default; /// A BooleanBitfield represents a set of booleans compactly stored as a vector of bits. /// The BooleanBitfield is given a fixed size during construction. Reads outside of the current size return an out-of-bounds error. Writes outside of the current size expand the size of the set. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct BooleanBitfield(BitVec); /// Error represents some reason a request against a bitfield was not satisfied @@ -104,6 +105,14 @@ impl default::Default for BooleanBitfield { } } +impl cmp::PartialEq for BooleanBitfield { + /// Determines equality by comparing the `ssz` encoding of the two candidates. + /// This method ensures that the presence of high-order (empty) bits in the highest byte do not exclude equality when they are in fact representing the same information. + fn eq(&self, other: &Self) -> bool { + ssz::ssz_encode(self) == ssz::ssz_encode(other) + } +} + impl ssz::Encodable for BooleanBitfield { // ssz_append encodes Self according to the `ssz` spec. fn ssz_append(&self, s: &mut ssz::SszStream) { From 4d0641636eb37b553aac0b320012d26d9d179bac Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 12 Dec 2018 12:16:11 +1100 Subject: [PATCH 17/18] Rename `AttestationRecord` -> `Attestation` --- beacon_chain/types/src/active_state.rs | 4 +- .../{attestation_record.rs => attestation.rs} | 14 +++---- beacon_chain/types/src/beacon_block.rs | 4 +- beacon_chain/types/src/lib.rs | 4 +- .../src/attestation_ssz_splitter.rs | 30 +++++++-------- .../utils/ssz_helpers/src/ssz_beacon_block.rs | 38 +++++++++---------- .../db/src/stores/beacon_block_store.rs | 14 +++++-- 7 files changed, 57 insertions(+), 51 deletions(-) rename beacon_chain/types/src/{attestation_record.rs => attestation.rs} (89%) diff --git a/beacon_chain/types/src/active_state.rs b/beacon_chain/types/src/active_state.rs index cd5548cef..5661fd60e 100644 --- a/beacon_chain/types/src/active_state.rs +++ b/beacon_chain/types/src/active_state.rs @@ -1,9 +1,9 @@ use super::Hash256; -use super::{AttestationRecord, SpecialRecord}; +use super::{Attestation, SpecialRecord}; #[derive(Debug, PartialEq)] pub struct ActiveState { - pub pending_attestations: Vec, + pub pending_attestations: Vec, pub pending_specials: Vec, pub recent_block_hashes: Vec, pub randao_mix: Hash256, diff --git a/beacon_chain/types/src/attestation_record.rs b/beacon_chain/types/src/attestation.rs similarity index 89% rename from beacon_chain/types/src/attestation_record.rs rename to beacon_chain/types/src/attestation.rs index 9cf4a60e9..2c2015cd3 100644 --- a/beacon_chain/types/src/attestation_record.rs +++ b/beacon_chain/types/src/attestation.rs @@ -11,14 +11,14 @@ pub const MIN_SSZ_ATTESTION_RECORD_LENGTH: usize = { }; #[derive(Debug, Clone, PartialEq)] -pub struct AttestationRecord { +pub struct Attestation { pub data: AttestationData, pub participation_bitfield: Bitfield, pub custody_bitfield: Bitfield, pub aggregate_sig: AggregateSignature, } -impl Encodable for AttestationRecord { +impl Encodable for Attestation { fn ssz_append(&self, s: &mut SszStream) { s.append(&self.data); s.append(&self.participation_bitfield); @@ -27,7 +27,7 @@ impl Encodable for AttestationRecord { } } -impl Decodable for AttestationRecord { +impl Decodable for Attestation { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { let (data, i) = AttestationData::ssz_decode(bytes, i)?; let (participation_bitfield, i) = Bitfield::ssz_decode(bytes, i)?; @@ -46,7 +46,7 @@ impl Decodable for AttestationRecord { } } -impl AttestationRecord { +impl Attestation { pub fn zero() -> Self { Self { data: AttestationData::zero(), @@ -64,7 +64,7 @@ mod tests { #[test] pub fn test_attestation_record_min_ssz_length() { - let ar = AttestationRecord::zero(); + let ar = Attestation::zero(); let ssz = ssz_encode(&ar); assert_eq!(ssz.len(), MIN_SSZ_ATTESTION_RECORD_LENGTH); @@ -72,7 +72,7 @@ mod tests { #[test] pub fn test_attestation_record_ssz_round_trip() { - let original = AttestationRecord { + let original = Attestation { data: AttestationData::zero(), participation_bitfield: Bitfield::from_bytes(&vec![17; 42][..]), custody_bitfield: Bitfield::from_bytes(&vec![18; 12][..]), @@ -80,7 +80,7 @@ mod tests { }; let ssz = ssz_encode(&original); - let (decoded, _) = AttestationRecord::ssz_decode(&ssz, 0).unwrap(); + let (decoded, _) = Attestation::ssz_decode(&ssz, 0).unwrap(); assert_eq!(original, decoded); } diff --git a/beacon_chain/types/src/beacon_block.rs b/beacon_chain/types/src/beacon_block.rs index 6d67c907b..35ad18497 100644 --- a/beacon_chain/types/src/beacon_block.rs +++ b/beacon_chain/types/src/beacon_block.rs @@ -1,4 +1,4 @@ -use super::attestation_record::AttestationRecord; +use super::attestation::Attestation; use super::special_record::SpecialRecord; use super::ssz::{Decodable, DecodeError, Encodable, SszStream}; use super::Hash256; @@ -23,7 +23,7 @@ pub struct BeaconBlock { pub ancestor_hashes: Vec, pub active_state_root: Hash256, pub crystallized_state_root: Hash256, - pub attestations: Vec, + pub attestations: Vec, pub specials: Vec, } diff --git a/beacon_chain/types/src/lib.rs b/beacon_chain/types/src/lib.rs index 6ef752e04..f86d5f024 100644 --- a/beacon_chain/types/src/lib.rs +++ b/beacon_chain/types/src/lib.rs @@ -5,7 +5,7 @@ extern crate ssz; pub mod active_state; pub mod attestation_data; -pub mod attestation_record; +pub mod attestation; pub mod beacon_block; pub mod beacon_state; pub mod candidate_pow_receipt_root_record; @@ -25,7 +25,7 @@ use std::collections::HashMap; pub use active_state::ActiveState; pub use attestation_data::AttestationData; -pub use attestation_record::AttestationRecord; +pub use attestation::Attestation; pub use beacon_block::BeaconBlock; pub use beacon_state::BeaconState; pub use chain_config::ChainConfig; diff --git a/beacon_chain/utils/ssz_helpers/src/attestation_ssz_splitter.rs b/beacon_chain/utils/ssz_helpers/src/attestation_ssz_splitter.rs index 6b65ae0aa..a216e0ec2 100644 --- a/beacon_chain/utils/ssz_helpers/src/attestation_ssz_splitter.rs +++ b/beacon_chain/utils/ssz_helpers/src/attestation_ssz_splitter.rs @@ -1,15 +1,15 @@ use super::bls::BLS_AGG_SIG_BYTE_SIZE; use super::ssz::decode::decode_length; use super::ssz::LENGTH_BYTES; +use super::types::attestation::MIN_SSZ_ATTESTION_RECORD_LENGTH; use super::types::attestation_data::SSZ_ATTESTION_DATA_LENGTH; -use super::types::attestation_record::MIN_SSZ_ATTESTION_RECORD_LENGTH; #[derive(Debug, PartialEq)] pub enum AttestationSplitError { TooShort, } -/// Given some ssz slice, find the bounds of each serialized AttestationRecord and return a vec of +/// Given some ssz slice, find the bounds of each serialized Attestation and return a vec of /// slices point to each. pub fn split_all_attestations<'a>( full_ssz: &'a [u8], @@ -25,7 +25,7 @@ pub fn split_all_attestations<'a>( Ok(v) } -/// Given some ssz slice, find the bounds of one serialized AttestationRecord +/// Given some ssz slice, find the bounds of one serialized Attestation /// and return a slice pointing to that. pub fn split_one_attestation( full_ssz: &[u8], @@ -43,11 +43,11 @@ pub fn split_one_attestation( } } -/// Given some SSZ, assume that a serialized `AttestationRecord` begins at the `index` position and -/// attempt to find the length (in bytes) of that serialized `AttestationRecord`. +/// Given some SSZ, assume that a serialized `Attestation` begins at the `index` position and +/// attempt to find the length (in bytes) of that serialized `Attestation`. /// -/// This function does not perform validation on the `AttestationRecord`. It is very likely that -/// given some sufficiently long non-`AttestationRecord` bytes it will not raise an error. +/// This function does not perform validation on the `Attestation`. It is very likely that +/// given some sufficiently long non-`Attestation` bytes it will not raise an error. fn determine_ssz_attestation_len( full_ssz: &[u8], index: usize, @@ -68,7 +68,7 @@ fn determine_ssz_attestation_len( .map_err(|_| AttestationSplitError::TooShort)?; let custody_bitfield_end = participation_bitfield_end + LENGTH_BYTES + custody_bitfield_len; - // Determine the very end of the AttestationRecord. + // Determine the very end of the Attestation. let agg_sig_end = custody_bitfield_end + LENGTH_BYTES + BLS_AGG_SIG_BYTE_SIZE; if agg_sig_end > full_ssz.len() { @@ -82,11 +82,11 @@ fn determine_ssz_attestation_len( mod tests { use super::super::bls::AggregateSignature; use super::super::ssz::{Decodable, SszStream}; - use super::super::types::{AttestationData, AttestationRecord, Bitfield, Hash256}; + use super::super::types::{Attestation, AttestationData, Bitfield, Hash256}; use super::*; - fn get_two_records() -> Vec { - let a = AttestationRecord { + fn get_two_records() -> Vec { + let a = Attestation { data: AttestationData { slot: 7, shard: 9, @@ -101,7 +101,7 @@ mod tests { custody_bitfield: Bitfield::from_bytes(&vec![255; 12][..]), aggregate_sig: AggregateSignature::new(), }; - let b = AttestationRecord { + let b = Attestation { data: AttestationData { slot: 9, shard: 7, @@ -133,7 +133,7 @@ mod tests { let ssz = ssz_stream.drain(); let (a_ssz, i) = split_one_attestation(&ssz, 0).unwrap(); assert_eq!(i, ssz.len()); - let (decoded_a, _) = AttestationRecord::ssz_decode(a_ssz, 0).unwrap(); + let (decoded_a, _) = Attestation::ssz_decode(a_ssz, 0).unwrap(); assert_eq!(a, decoded_a); /* @@ -144,8 +144,8 @@ mod tests { ssz_stream.append(&b); let ssz = ssz_stream.drain(); let ssz_vec = split_all_attestations(&ssz, 0).unwrap(); - let (decoded_a, _) = AttestationRecord::ssz_decode(ssz_vec[0], 0).unwrap(); - let (decoded_b, _) = AttestationRecord::ssz_decode(ssz_vec[1], 0).unwrap(); + let (decoded_a, _) = Attestation::ssz_decode(ssz_vec[0], 0).unwrap(); + let (decoded_b, _) = Attestation::ssz_decode(ssz_vec[1], 0).unwrap(); assert_eq!(a, decoded_a); assert_eq!(b, decoded_b); diff --git a/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs b/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs index 2427ef4c8..5c8059541 100644 --- a/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs +++ b/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs @@ -217,7 +217,7 @@ mod tests { use super::super::ssz::encode::encode_length; use super::super::ssz::SszStream; use super::super::types::Hash256; - use super::super::types::{AttestationRecord, BeaconBlock, SpecialRecord}; + use super::super::types::{Attestation, BeaconBlock, SpecialRecord}; use super::*; fn get_block_ssz(b: &BeaconBlock) -> Vec { @@ -232,7 +232,7 @@ mod tests { ssz_stream.drain() } - fn get_attestation_record_ssz(ar: &AttestationRecord) -> Vec { + fn get_attestation_record_ssz(ar: &Attestation) -> Vec { let mut ssz_stream = SszStream::new(); ssz_stream.append(ar); ssz_stream.drain() @@ -250,7 +250,7 @@ mod tests { #[test] fn test_ssz_block_single_attestation_record_one_byte_short() { let mut b = BeaconBlock::zero(); - b.attestations = vec![AttestationRecord::zero()]; + b.attestations = vec![Attestation::zero()]; let ssz = get_block_ssz(&b); assert_eq!( @@ -262,7 +262,7 @@ mod tests { #[test] fn test_ssz_block_single_attestation_record_one_byte_long() { let mut b = BeaconBlock::zero(); - b.attestations = vec![AttestationRecord::zero()]; + b.attestations = vec![Attestation::zero()]; let mut ssz = get_block_ssz(&b); let original_len = ssz.len(); ssz.push(42); @@ -275,7 +275,7 @@ mod tests { #[test] fn test_ssz_block_single_attestation_record() { let mut b = BeaconBlock::zero(); - b.attestations = vec![AttestationRecord::zero()]; + b.attestations = vec![Attestation::zero()]; let ssz = get_block_ssz(&b); assert!(SszBeaconBlock::from_slice(&ssz[..]).is_ok()); @@ -284,7 +284,7 @@ mod tests { #[test] fn test_ssz_block_block_hash() { let mut block = BeaconBlock::zero(); - block.attestations.push(AttestationRecord::zero()); + block.attestations.push(Attestation::zero()); let serialized = get_block_ssz(&block); let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); let hash = ssz_block.block_hash(); @@ -311,7 +311,7 @@ mod tests { #[test] fn test_ssz_block_slot() { let mut block = BeaconBlock::zero(); - block.attestations.push(AttestationRecord::zero()); + block.attestations.push(Attestation::zero()); block.slot = 42; let serialized = get_block_ssz(&block); @@ -323,7 +323,7 @@ mod tests { #[test] fn test_ssz_block_randao_reveal() { let mut block = BeaconBlock::zero(); - block.attestations.push(AttestationRecord::zero()); + block.attestations.push(Attestation::zero()); let reference_hash = Hash256::from([42_u8; 32]); block.randao_reveal = reference_hash.clone(); @@ -404,14 +404,14 @@ mod tests { #[test] fn test_ssz_block_attestations() { /* - * Single AttestationRecord + * Single Attestation */ let mut block = BeaconBlock::zero(); - block.attestations.push(AttestationRecord::zero()); + block.attestations.push(Attestation::zero()); let serialized = get_block_ssz(&block); let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); - let ssz_ar = get_attestation_record_ssz(&AttestationRecord::zero()); + let ssz_ar = get_attestation_record_ssz(&Attestation::zero()); let mut expected = encode_length(ssz_ar.len(), LENGTH_PREFIX_BYTES); expected.append(&mut ssz_ar.to_vec()); @@ -419,16 +419,16 @@ mod tests { assert_eq!(ssz_block.attestations(), &expected[..]); /* - * Multiple AttestationRecords + * Multiple Attestations */ let mut block = BeaconBlock::zero(); - block.attestations.push(AttestationRecord::zero()); - block.attestations.push(AttestationRecord::zero()); + block.attestations.push(Attestation::zero()); + block.attestations.push(Attestation::zero()); let serialized = get_block_ssz(&block); let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); - let mut ssz_ar = get_attestation_record_ssz(&AttestationRecord::zero()); - ssz_ar.append(&mut get_attestation_record_ssz(&AttestationRecord::zero())); + let mut ssz_ar = get_attestation_record_ssz(&Attestation::zero()); + ssz_ar.append(&mut get_attestation_record_ssz(&Attestation::zero())); let mut expected = encode_length(ssz_ar.len(), LENGTH_PREFIX_BYTES); expected.append(&mut ssz_ar.to_vec()); @@ -439,7 +439,7 @@ mod tests { #[test] fn test_ssz_block_pow_chain_reference() { let mut block = BeaconBlock::zero(); - block.attestations.push(AttestationRecord::zero()); + block.attestations.push(Attestation::zero()); let reference_hash = Hash256::from([42_u8; 32]); block.pow_chain_reference = reference_hash.clone(); @@ -455,7 +455,7 @@ mod tests { #[test] fn test_ssz_block_act_state_root() { let mut block = BeaconBlock::zero(); - block.attestations.push(AttestationRecord::zero()); + block.attestations.push(Attestation::zero()); let reference_hash = Hash256::from([42_u8; 32]); block.active_state_root = reference_hash.clone(); @@ -468,7 +468,7 @@ mod tests { #[test] fn test_ssz_block_cry_state_root() { let mut block = BeaconBlock::zero(); - block.attestations.push(AttestationRecord::zero()); + block.attestations.push(Attestation::zero()); let reference_hash = Hash256::from([42_u8; 32]); block.crystallized_state_root = reference_hash.clone(); diff --git a/lighthouse/db/src/stores/beacon_block_store.rs b/lighthouse/db/src/stores/beacon_block_store.rs index 84cea2d0d..cad353eb2 100644 --- a/lighthouse/db/src/stores/beacon_block_store.rs +++ b/lighthouse/db/src/stores/beacon_block_store.rs @@ -87,7 +87,7 @@ mod tests { extern crate types; use self::ssz::SszStream; - use self::types::attestation_record::AttestationRecord; + use self::types::attestation::Attestation; use self::types::beacon_block::BeaconBlock; use self::types::Hash256; @@ -182,7 +182,10 @@ mod tests { let hash = &Hash256::from("some hash".as_bytes()).to_vec(); db.put(DB_COLUMN, hash, ssz).unwrap(); - assert_eq!(store.block_at_slot(hash, 42), Err(BeaconBlockAtSlotError::InvalidBeaconBlock)); + assert_eq!( + store.block_at_slot(hash, 42), + Err(BeaconBlockAtSlotError::InvalidBeaconBlock) + ); } #[test] @@ -195,7 +198,10 @@ mod tests { let other_hash = &Hash256::from("another hash".as_bytes()).to_vec(); db.put(DB_COLUMN, hash, ssz).unwrap(); - assert_eq!(store.block_at_slot(other_hash, 42), Err(BeaconBlockAtSlotError::UnknownBeaconBlock)); + assert_eq!( + store.block_at_slot(other_hash, 42), + Err(BeaconBlockAtSlotError::UnknownBeaconBlock) + ); } #[test] @@ -244,7 +250,7 @@ mod tests { let blocks = (0..5).into_iter().map(|_| { let mut block = BeaconBlock::zero(); - let ar = AttestationRecord::zero(); + let ar = Attestation::zero(); block.attestations.push(ar); block }); From 211d3961a0ed136c8916490ead467bc8e9928fc3 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 12 Dec 2018 16:08:01 +1100 Subject: [PATCH 18/18] Add `ChainSpec` structure. It includes all constants from the [Constants](https://github.com/ethereum/eth2.0-specs/blob/master/specs/core/0_beacon-chain.md#attestation) section in the spec, except for things that are clearly enums. My reasoning is that these enums are not so much "chain specification" and this struct should be reserved for items that "configure" the chain. --- Cargo.toml | 1 + beacon_chain/spec/Cargo.toml | 7 ++++ beacon_chain/spec/src/foundation.rs | 61 +++++++++++++++++++++++++++++ beacon_chain/spec/src/lib.rs | 58 +++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 beacon_chain/spec/Cargo.toml create mode 100644 beacon_chain/spec/src/foundation.rs create mode 100644 beacon_chain/spec/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 60b600efd..856c2dae3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "beacon_chain/attestation_validation", "beacon_chain/chain", "beacon_chain/naive_fork_choice", + "beacon_chain/spec", "beacon_chain/state-transition", "beacon_chain/types", "beacon_chain/utils/active-validators", diff --git a/beacon_chain/spec/Cargo.toml b/beacon_chain/spec/Cargo.toml new file mode 100644 index 000000000..a2eac0692 --- /dev/null +++ b/beacon_chain/spec/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "spec" +version = "0.1.0" +authors = ["Paul Hauner "] + +[dependencies] +types = { path = "../types" } diff --git a/beacon_chain/spec/src/foundation.rs b/beacon_chain/spec/src/foundation.rs new file mode 100644 index 000000000..5bf25e1e7 --- /dev/null +++ b/beacon_chain/spec/src/foundation.rs @@ -0,0 +1,61 @@ +use super::ChainSpec; + +use types::{Address, Hash256}; + +impl ChainSpec { + /// Returns a `ChainSpec` compatible with the specification from Ethereum Foundation. + pub fn foundation() -> Self { + Self { + /* + * Misc + */ + shard_count: 1_024, + target_committee_size: 256, + ejection_balance: 16, + max_balance_churn_quotient: 32, + gwei_per_eth: u64::pow(10, 9), + beacon_chain_shard_number: u64::max_value(), + bls_withdrawal_prefix_byte: 0x00, + max_casper_votes: 1_024, + /* + * Deposit contract + */ + deposit_contract_address: Address::from("TBD".as_bytes()), + deposit_contract_tree_depth: 32, + min_deposit: 1, + max_deposit: 32, + /* + * Initial Values + */ + initial_fork_version: 0, + initial_slot_number: 0, + zero_hash: Hash256::zero(), + /* + * Time parameters + */ + slot_duration: 6, + min_attestation_inclusion_delay: 4, + epoch_length: 64, + min_validator_registry_change_interval: 256, + pow_receipt_root_voting_period: 1_024, + shard_persistent_committee_change_period: u64::pow(2, 17), + collective_penalty_calculation_period: u64::pow(2, 20), + zero_balance_validator_ttl: u64::pow(2, 22), + /* + * Reward and penalty quotients + */ + base_reward_quotient: 2_048, + whistleblower_reward_quotient: 512, + includer_reward_quotient: 8, + inactivity_penalty_quotient: u64::pow(2, 34), + /* + * Max operations per block + */ + max_proposer_slashings: 16, + max_casper_slashings: 15, + max_attestations: 128, + max_deposits: 16, + max_exits: 16, + } + } +} diff --git a/beacon_chain/spec/src/lib.rs b/beacon_chain/spec/src/lib.rs new file mode 100644 index 000000000..a15575115 --- /dev/null +++ b/beacon_chain/spec/src/lib.rs @@ -0,0 +1,58 @@ +extern crate types; + +mod foundation; + +use types::{Address, Hash256}; + +pub struct ChainSpec { + /* + * Misc + */ + pub shard_count: u64, + pub target_committee_size: u64, + pub ejection_balance: u64, + pub max_balance_churn_quotient: u64, + pub gwei_per_eth: u64, + pub beacon_chain_shard_number: u64, + pub bls_withdrawal_prefix_byte: u8, + pub max_casper_votes: u64, + /* + * Deposit contract + */ + pub deposit_contract_address: Address, + pub deposit_contract_tree_depth: u64, + pub min_deposit: u64, + pub max_deposit: u64, + /* + * Initial Values + */ + pub initial_fork_version: u64, + pub initial_slot_number: u64, + pub zero_hash: Hash256, + /* + * Time parameters + */ + pub slot_duration: u64, + pub min_attestation_inclusion_delay: u64, + pub epoch_length: u64, + pub min_validator_registry_change_interval: u64, + pub pow_receipt_root_voting_period: u64, + pub shard_persistent_committee_change_period: u64, + pub collective_penalty_calculation_period: u64, + pub zero_balance_validator_ttl: u64, + /* + * Reward and penalty quotients + */ + pub base_reward_quotient: u64, + pub whistleblower_reward_quotient: u64, + pub includer_reward_quotient: u64, + pub inactivity_penalty_quotient: u64, + /* + * Max operations per block + */ + pub max_proposer_slashings: u64, + pub max_casper_slashings: u64, + pub max_attestations: u64, + pub max_deposits: u64, + pub max_exits: u64, +}