From a862c82b3728e2d993b8eba4c93c1c03f93cb453 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 15 Oct 2018 14:57:14 +1100 Subject: [PATCH] Rename Block -> BeaconBlock - Update types::block - Update ssz_helpers::SszBlock - Update db::stores::block_store - Add new fields to types::Block - Update SszBlock as per new Block fields --- beacon_chain/types/src/beacon_block.rs | 105 ++++ beacon_chain/types/src/block.rs | 80 --- beacon_chain/types/src/lib.rs | 4 +- beacon_chain/utils/ssz_helpers/src/lib.rs | 2 +- .../utils/ssz_helpers/src/ssz_beacon_block.rs | 470 ++++++++++++++++++ .../utils/ssz_helpers/src/ssz_block.rs | 358 ------------- .../{block_store.rs => beacon_block_store.rs} | 65 +-- lighthouse/db/src/stores/mod.rs | 8 +- 8 files changed, 616 insertions(+), 476 deletions(-) create mode 100644 beacon_chain/types/src/beacon_block.rs delete mode 100644 beacon_chain/types/src/block.rs create mode 100644 beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs delete mode 100644 beacon_chain/utils/ssz_helpers/src/ssz_block.rs rename lighthouse/db/src/stores/{block_store.rs => beacon_block_store.rs} (75%) diff --git a/beacon_chain/types/src/beacon_block.rs b/beacon_chain/types/src/beacon_block.rs new file mode 100644 index 000000000..e75a3e6bb --- /dev/null +++ b/beacon_chain/types/src/beacon_block.rs @@ -0,0 +1,105 @@ +use super::Hash256; +use super::attestation_record::AttestationRecord; +use super::special_record::SpecialRecord; +use super::ssz::{ Encodable, SszStream }; + +pub const MIN_SSZ_BLOCK_LENGTH: usize = { + 8 + // slot + 32 + // randao_reveal + 32 + // pow_chain_reference + 4 + // ancestor hashes (assuming empty) + 32 + // active_state_root + 32 + // crystallized_state_root + 4 + // attestations (assuming empty) + 4 // specials (assuming empty) +}; +pub const MAX_SSZ_BLOCK_LENGTH: usize = MIN_SSZ_BLOCK_LENGTH + (1 << 24); + +#[derive(Debug, PartialEq, Clone)] +pub struct BeaconBlock { + pub slot: u64, + pub randao_reveal: Hash256, + pub pow_chain_reference: Hash256, + pub ancestor_hashes: Vec, + pub active_state_root: Hash256, + pub crystallized_state_root: Hash256, + pub attestations: Vec, + pub specials: Vec, +} + +impl BeaconBlock { + pub fn zero() -> Self { + Self { + slot: 0, + randao_reveal: Hash256::zero(), + pow_chain_reference: Hash256::zero(), + ancestor_hashes: vec![], + active_state_root: Hash256::zero(), + crystallized_state_root: Hash256::zero(), + attestations: vec![], + specials: vec![], + } + } + + /// Return a reference to `ancestor_hashes[0]`. + /// + /// The first hash in `ancestor_hashes` is the parent of the block. + pub fn parent_hash(&self) -> Option<&Hash256> { + self.ancestor_hashes.get(0) + } +} + +impl Encodable for BeaconBlock { + fn ssz_append(&self, s: &mut SszStream) { + s.append(&self.slot); + s.append(&self.randao_reveal); + s.append(&self.pow_chain_reference); + s.append_vec(&self.ancestor_hashes.to_vec()); + s.append(&self.active_state_root); + s.append(&self.crystallized_state_root); + s.append_vec(&self.attestations); + s.append_vec(&self.specials); + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_block_zero() { + let b = BeaconBlock::zero(); + assert_eq!(b.slot, 0); + assert!(b.randao_reveal.is_zero()); + assert!(b.pow_chain_reference.is_zero()); + assert_eq!(b.ancestor_hashes, vec![Hash256::zero()]); + assert!(b.active_state_root.is_zero()); + assert!(b.crystallized_state_root.is_zero()); + assert_eq!(b.attestations.len(), 0); + assert_eq!(b.specials.len(), 0); + } + + #[test] + pub fn test_block_min_ssz_length() { + let b = BeaconBlock::zero(); + + let mut ssz_stream = SszStream::new(); + ssz_stream.append(&b); + let ssz = ssz_stream.drain(); + + assert_eq!(ssz.len(), MIN_SSZ_BLOCK_LENGTH); + } + + #[test] + pub fn test_block_parent_hash() { + let mut b = BeaconBlock::zero(); + b.ancestor_hashes = vec![ + Hash256::from("cats".as_bytes()), + Hash256::from("dogs".as_bytes()), + Hash256::from("birds".as_bytes()), + ]; + + assert_eq!(b.parent_hash().unwrap(), &Hash256::from("cats".as_bytes())); + } +} diff --git a/beacon_chain/types/src/block.rs b/beacon_chain/types/src/block.rs deleted file mode 100644 index ee84773d0..000000000 --- a/beacon_chain/types/src/block.rs +++ /dev/null @@ -1,80 +0,0 @@ -use super::Hash256; -use super::attestation_record::AttestationRecord; -use super::ssz::{ Encodable, SszStream }; - -pub const MIN_SSZ_BLOCK_LENGTH: usize = { - 32 + // parent_hash - 8 + // slot_number - 32 + // randao_reveal - 4 + // attestations (assuming zero) - 32 + // pow_chain_ref - 32 + // active_state_root - 32 // crystallized_state_root -}; -pub const MAX_SSZ_BLOCK_LENGTH: usize = MIN_SSZ_BLOCK_LENGTH + (1 << 24); - -#[derive(Debug, PartialEq, Clone)] -pub struct Block { - pub parent_hash: Hash256, - pub slot_number: u64, - pub randao_reveal: Hash256, - pub attestations: Vec, - pub pow_chain_ref: Hash256, - pub active_state_root: Hash256, - pub crystallized_state_root: Hash256, -} - -impl Block { - pub fn zero() -> Self { - Self { - parent_hash: Hash256::zero(), - slot_number: 0, - randao_reveal: Hash256::zero(), - attestations: vec![], - pow_chain_ref: Hash256::zero(), - active_state_root: Hash256::zero(), - crystallized_state_root: Hash256::zero(), - } - } -} - -impl Encodable for Block { - fn ssz_append(&self, s: &mut SszStream) { - s.append(&self.parent_hash); - s.append(&self.slot_number); - s.append(&self.randao_reveal); - s.append_vec(&self.attestations); - s.append(&self.pow_chain_ref); - s.append(&self.active_state_root); - s.append(&self.crystallized_state_root); - } -} - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_block_zero() { - let b = Block::zero(); - assert!(b.parent_hash.is_zero()); - assert_eq!(b.slot_number, 0); - assert!(b.randao_reveal.is_zero()); - assert_eq!(b.attestations.len(), 0); - assert!(b.pow_chain_ref.is_zero()); - assert!(b.active_state_root.is_zero()); - assert!(b.crystallized_state_root.is_zero()); - } - - #[test] - pub fn test_block_min_ssz_length() { - let b = Block::zero(); - - let mut ssz_stream = SszStream::new(); - ssz_stream.append(&b); - let ssz = ssz_stream.drain(); - - assert_eq!(ssz.len(), MIN_SSZ_BLOCK_LENGTH); - } -} diff --git a/beacon_chain/types/src/lib.rs b/beacon_chain/types/src/lib.rs index c7cc3ac12..14dfa792a 100644 --- a/beacon_chain/types/src/lib.rs +++ b/beacon_chain/types/src/lib.rs @@ -7,7 +7,7 @@ pub mod active_state; pub mod attestation_record; pub mod crystallized_state; pub mod chain_config; -pub mod block; +pub mod beacon_block; pub mod crosslink_record; pub mod shard_and_committee; pub mod special_record; @@ -25,7 +25,7 @@ pub use active_state::ActiveState; pub use attestation_record::AttestationRecord; pub use crystallized_state::CrystallizedState; pub use chain_config::ChainConfig; -pub use block::Block; +pub use beacon_block::BeaconBlock; pub use crosslink_record::CrosslinkRecord; pub use shard_and_committee::ShardAndCommittee; pub use special_record::{ SpecialRecord, SpecialRecordKind }; diff --git a/beacon_chain/utils/ssz_helpers/src/lib.rs b/beacon_chain/utils/ssz_helpers/src/lib.rs index 6f313e8a0..64ee3b1b9 100644 --- a/beacon_chain/utils/ssz_helpers/src/lib.rs +++ b/beacon_chain/utils/ssz_helpers/src/lib.rs @@ -4,4 +4,4 @@ extern crate types; extern crate ssz; pub mod attestation_ssz_splitter; -pub mod ssz_block; +pub mod ssz_beacon_block; diff --git a/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs b/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs new file mode 100644 index 000000000..b46b1f2db --- /dev/null +++ b/beacon_chain/utils/ssz_helpers/src/ssz_beacon_block.rs @@ -0,0 +1,470 @@ +use super::ssz::decode::{ + decode_length, + Decodable, +}; +use super::hashing::canonical_hash; +use super::types::beacon_block::{ + MIN_SSZ_BLOCK_LENGTH, + MAX_SSZ_BLOCK_LENGTH, +}; +use super::types::attestation_record::MIN_SSZ_ATTESTION_RECORD_LENGTH; + +#[derive(Debug, PartialEq)] +pub enum SszBeaconBlockError { + TooShort, + TooLong, +} + +const LENGTH_BYTES: usize = 4; + +/// Allows for reading of block values directly from serialized ssz bytes. +/// +/// The purpose of this struct is to provide the functionality to read block fields directly from +/// some serialized SSZ slice allowing us to read the block without fully +/// de-serializing it. +/// +/// This struct should be as "zero-copy" as possible. The `ssz` field is a reference to some slice +/// and each function reads from that slice. +/// +/// Use this to perform intial checks before we fully de-serialize a block. It should only really +/// be used to verify blocks that come in from the network, for internal operations we should use a +/// full `BeaconBlock`. +#[derive(Debug, PartialEq)] +pub struct SszBeaconBlock<'a> { + ssz: &'a [u8], + // Ancestors + ancestors_position: usize, + ancestors_len: usize, + // Attestations + attestations_position: usize, + attestations_len: usize, + // Specials + specials_position: usize, + specials_len: usize, +} + +impl<'a> SszBeaconBlock<'a> { + /// Create a new instance from a slice reference. + /// + /// This function will validate the length of the ssz string, however it will not validate the + /// contents. + /// + /// The returned `SszBeaconBlock` instance will contain a `len` field which can be used to determine + /// how many bytes were read from the slice. In the case of multiple, sequentually serialized + /// blocks `len` can be used to assume the location of the next serialized block. + pub fn from_slice(vec: &'a [u8]) + -> Result + { + let untrimmed_ssz = &vec[..]; + + /* + * Ensure the SSZ is long enough to be a block + */ + if vec.len() < MIN_SSZ_BLOCK_LENGTH { + return Err(SszBeaconBlockError::TooShort); + } + + /* + * Ensure the SSZ slice isn't longer than is possible for a block. + */ + if vec.len() > MAX_SSZ_BLOCK_LENGTH { + return Err(SszBeaconBlockError::TooLong); + } + + /* + * Determine how many bytes are used to store ancestor hashes. + */ + let ancestors_position = + 8 + // slot + 32 + // randao_reveal + 32; // pow_chain_reference + let ancestors_len = decode_length(untrimmed_ssz, ancestors_position, LENGTH_BYTES) + .map_err(|_| SszBeaconBlockError::TooShort)?; + + /* + * Determine how many bytes are used to store attestation records. + */ + let attestations_position = + ancestors_position + LENGTH_BYTES + ancestors_len + // end of ancestor bytes + 32 + // active_state_root + 32; // crystallized_state_root + let attestations_len = decode_length(untrimmed_ssz, attestations_position, LENGTH_BYTES) + .map_err(|_| SszBeaconBlockError::TooShort)?; + + /* + * Determine how many bytes are used to store specials. + */ + let specials_position = + attestations_position + // location of attestations + LENGTH_BYTES + // attestations length prefix + attestations_len; // # of attestation bytes + let specials_len = decode_length(untrimmed_ssz, specials_position, LENGTH_BYTES) + .map_err(|_| SszBeaconBlockError::TooShort)?; + + /* + * Now that all variable field lengths are known (ancestors, attestations, specials) we can + * know the exact length of the block and reject it if the slice is too short. + */ + let block_ssz_len = { + MIN_SSZ_BLOCK_LENGTH + ancestors_len + attestations_len + specials_len + }; + if vec.len() < block_ssz_len { + println!("block_ssz_len: {:?}, len: {:?}", block_ssz_len, vec.len()); + return Err(SszBeaconBlockError::TooShort); + } + + Ok(Self{ + ssz: &untrimmed_ssz[0..block_ssz_len], + ancestors_position, + ancestors_len, + attestations_position, + attestations_len, + specials_position, + specials_len, + }) + } + + pub fn len(&self) -> usize { self.ssz.len() } + + /// Return the canonical hash for this block. + pub fn block_hash(&self) -> Vec { + canonical_hash(self.ssz) + } + + /// Return the bytes representing `ancestor_hashes[0]`. + /// + /// The first hash in `ancestor_hashes` is the parent of the block. + pub fn parent_hash(&self) -> Option<&[u8]> { + let ancestor_ssz = self.ancestor_hashes(); + if ancestor_ssz.len() >= 32 { + Some(&ancestor_ssz[0..32]) + } else { + None + } + } + + /// Return the `slot` field. + pub fn slot(&self) -> u64 { + /* + * An error should be unreachable from this decode + * because we checked the length of the array at + * the initalization of this struct. + * + * If you can make this function panic, please report + * it to paul@sigmaprime.io + */ + if let Ok((n, _)) = u64::ssz_decode(&self.ssz, 0) { + n + } else { + unreachable!(); + } + } + + /// Return the `randao_reveal` field. + pub fn randao_reveal(&self) -> &[u8] { + let start = 8; // slot is 8 bytes + &self.ssz[start..start + 32] + } + + /// Return the `pow_chain_reference` field. + pub fn pow_chain_reference(&self) -> &[u8] { + let start = + 8 + // slot + 32; // randao_reveal + &self.ssz[start..start + 32] + } + + /// Return the serialized `ancestor_hashes` bytes. + pub fn ancestor_hashes(&self) -> &[u8] { + let start = self.ancestors_position + LENGTH_BYTES; + &self.ssz[start..start + self.ancestors_len] + } + + /// Return the `active_state_root` field. + pub fn act_state_root(&self) -> &[u8] { + let start = self.ancestors_position + LENGTH_BYTES + self.ancestors_len; + &self.ssz[start..(start + 32)] + } + + /// Return the `active_state_root` field. + pub fn cry_state_root(&self) -> &[u8] { + let start = + self.ancestors_position + LENGTH_BYTES + self.ancestors_len + // ancestors + 32; // active_state_root; + &self.ssz[start..(start + 32)] + } + + /// Return the serialized `attestations` bytes. + pub fn attestations(&self) -> &[u8] { + let start = self.attestations_position + LENGTH_BYTES; + &self.ssz[start..(start + self.attestations_len)] + } + + /// Return the serialized `specials` bytes. + pub fn specials(&self) -> &[u8] { + let start = self.specials_position + LENGTH_BYTES; + &self.ssz[start..(start + self.specials_len)] + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use super::super::types::{ + AttestationRecord, + BeaconBlock, + SpecialRecord, + }; + use super::super::ssz::SszStream; + use super::super::types::Hash256; + + fn get_block_ssz(b: &BeaconBlock) -> Vec { + let mut ssz_stream = SszStream::new(); + ssz_stream.append(b); + ssz_stream.drain() + } + + fn get_special_record_ssz(sr: &SpecialRecord) -> Vec { + let mut ssz_stream = SszStream::new(); + ssz_stream.append(sr); + ssz_stream.drain() + } + + fn get_attestation_record_ssz(ar: &AttestationRecord) -> Vec { + let mut ssz_stream = SszStream::new(); + ssz_stream.append(ar); + ssz_stream.drain() + } + + #[test] + fn test_ssz_block_zero_attestation_records() { + let mut b = BeaconBlock::zero(); + b.attestations = vec![]; + let ssz = get_block_ssz(&b); + + + assert!(SszBeaconBlock::from_slice(&ssz[..]).is_ok()); + } + + #[test] + fn test_ssz_block_single_attestation_record_one_byte_short() { + let mut b = BeaconBlock::zero(); + b.attestations = vec![AttestationRecord::zero()]; + let ssz = get_block_ssz(&b); + + assert_eq!( + SszBeaconBlock::from_slice(&ssz[0..(ssz.len() - 1)]), + Err(SszBeaconBlockError::TooShort) + ); + } + + #[test] + fn test_ssz_block_single_attestation_record_one_byte_long() { + let mut b = BeaconBlock::zero(); + b.attestations = vec![AttestationRecord::zero()]; + let mut ssz = get_block_ssz(&b); + let original_len = ssz.len(); + ssz.push(42); + + let ssz_block = SszBeaconBlock::from_slice(&ssz[..]).unwrap(); + + assert_eq!(ssz_block.len(), original_len); + } + + #[test] + fn test_ssz_block_single_attestation_record() { + let mut b = BeaconBlock::zero(); + b.attestations = vec![AttestationRecord::zero()]; + let ssz = get_block_ssz(&b); + + assert!(SszBeaconBlock::from_slice(&ssz[..]).is_ok()); + } + + #[test] + fn test_ssz_block_attestations_length() { + let mut block = BeaconBlock::zero(); + block.attestations.push(AttestationRecord::zero()); + + let serialized = get_block_ssz(&block); + let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); + + assert_eq!(ssz_block.attestations_len, MIN_SSZ_ATTESTION_RECORD_LENGTH); + } + + #[test] + fn test_ssz_block_block_hash() { + let mut block = BeaconBlock::zero(); + block.attestations.push(AttestationRecord::zero()); + let serialized = get_block_ssz(&block); + let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); + let hash = ssz_block.block_hash(); + // Note: this hash was not generated by some external program, + // it was simply printed then copied into the code. This test + // 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 + ]; + assert_eq!(hash, expected_hash); + + /* + * Test if you give the SszBeaconBlock too many ssz bytes + */ + let mut too_long = serialized.clone(); + too_long.push(42); + let ssz_block = SszBeaconBlock::from_slice(&too_long).unwrap(); + let hash = ssz_block.block_hash(); + assert_eq!(hash, expected_hash); + } + + #[test] + fn test_ssz_block_slot() { + let mut block = BeaconBlock::zero(); + block.attestations.push(AttestationRecord::zero()); + block.slot = 42; + + let serialized = get_block_ssz(&block); + let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); + + assert_eq!(ssz_block.slot(), 42); + } + + #[test] + fn test_ssz_block_randao_reveal() { + let mut block = BeaconBlock::zero(); + block.attestations.push(AttestationRecord::zero()); + let reference_hash = Hash256::from([42_u8; 32]); + block.randao_reveal = reference_hash.clone(); + + let serialized = get_block_ssz(&block); + let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); + + assert_eq!(ssz_block.randao_reveal(), &reference_hash.to_vec()[..]); + } + + #[test] + fn test_ssz_block_ancestor_hashes() { + let mut block = BeaconBlock::zero(); + let h = Hash256::from(&vec![42_u8; 32][..]); + block.ancestor_hashes.push(h); + + let serialized = get_block_ssz(&block); + let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); + + assert_eq!(ssz_block.ancestor_hashes(), &h.to_vec()[..]); + } + + #[test] + fn test_ssz_block_parent_hash() { + let mut block = BeaconBlock::zero(); + block.ancestor_hashes = vec![ + Hash256::from("cats".as_bytes()), + Hash256::from("dogs".as_bytes()), + Hash256::from("birds".as_bytes()), + ]; + + let serialized = get_block_ssz(&block); + let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); + + assert_eq!(ssz_block.parent_hash().unwrap(), &Hash256::from("cats".as_bytes()).to_vec()[..]); + } + + #[test] + fn test_ssz_block_specials() { + /* + * Without data + */ + let mut block = BeaconBlock::zero(); + let s = SpecialRecord::logout(&[]); + block.specials.push(s.clone()); + + let serialized = get_block_ssz(&block); + let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); + let sr_ssz = get_special_record_ssz(&s); + + assert_eq!(ssz_block.specials(), &sr_ssz[..]); + + /* + * With data + */ + let mut block = BeaconBlock::zero(); + let s = SpecialRecord::randao_change(&[16, 17, 18]); + block.specials.push(s.clone()); + + let serialized = get_block_ssz(&block); + let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); + let sr_ssz = get_special_record_ssz(&s); + + assert_eq!(ssz_block.specials(), &sr_ssz[..]); + } + + #[test] + fn test_ssz_block_attestations() { + /* + * Single AttestationRecord + */ + let mut block = BeaconBlock::zero(); + block.attestations.push(AttestationRecord::zero()); + + let serialized = get_block_ssz(&block); + let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); + let ssz_ar = get_attestation_record_ssz(&AttestationRecord::zero()); + + assert_eq!(ssz_block.attestations(), &ssz_ar[..]); + + /* + * Multiple AttestationRecords + */ + let mut block = BeaconBlock::zero(); + block.attestations.push(AttestationRecord::zero()); + block.attestations.push(AttestationRecord::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())); + + assert_eq!(ssz_block.attestations(), &ssz_ar[..]); + } + + #[test] + fn test_ssz_block_pow_chain_reference() { + let mut block = BeaconBlock::zero(); + block.attestations.push(AttestationRecord::zero()); + let reference_hash = Hash256::from([42_u8; 32]); + block.pow_chain_reference = reference_hash.clone(); + + let serialized = get_block_ssz(&block); + let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); + + assert_eq!(ssz_block.pow_chain_reference(), &reference_hash.to_vec()[..]); + } + + #[test] + fn test_ssz_block_act_state_root() { + let mut block = BeaconBlock::zero(); + block.attestations.push(AttestationRecord::zero()); + let reference_hash = Hash256::from([42_u8; 32]); + block.active_state_root = reference_hash.clone(); + + let serialized = get_block_ssz(&block); + let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); + + assert_eq!(ssz_block.act_state_root(), &reference_hash.to_vec()[..]); + } + + #[test] + fn test_ssz_block_cry_state_root() { + let mut block = BeaconBlock::zero(); + block.attestations.push(AttestationRecord::zero()); + let reference_hash = Hash256::from([42_u8; 32]); + block.crystallized_state_root = reference_hash.clone(); + + let serialized = get_block_ssz(&block); + let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap(); + + assert_eq!(ssz_block.cry_state_root(), &reference_hash.to_vec()[..]); + } +} diff --git a/beacon_chain/utils/ssz_helpers/src/ssz_block.rs b/beacon_chain/utils/ssz_helpers/src/ssz_block.rs deleted file mode 100644 index 7dd2ddc6d..000000000 --- a/beacon_chain/utils/ssz_helpers/src/ssz_block.rs +++ /dev/null @@ -1,358 +0,0 @@ -use super::ssz::decode::{ - decode_length, - Decodable, -}; -use super::hashing::canonical_hash; -use super::types::block::{ - MIN_SSZ_BLOCK_LENGTH, - MAX_SSZ_BLOCK_LENGTH, -}; -use super::types::attestation_record::MIN_SSZ_ATTESTION_RECORD_LENGTH; - -#[derive(Debug, PartialEq)] -pub enum SszBlockError { - TooShort, - TooLong, -} - -const LENGTH_BYTES: usize = 4; - -/// Allows for reading of block values directly from serialized ssz bytes. -/// -/// The purpose of this struct is to provide the functionality to read block fields directly from -/// some serialized SSZ slice allowing us to read the block without fully -/// de-serializing it. -/// -/// This struct should be as "zero-copy" as possible. The `ssz` field is a reference to some slice -/// and each function reads from that slice. -/// -/// Use this to perform intial checks before we fully de-serialize a block. It should only really -/// be used to verify blocks that come in from the network, for internal operations we should use a -/// full `Block`. -#[derive(Debug, PartialEq)] -pub struct SszBlock<'a> { - ssz: &'a [u8], - attestation_len: usize, - pub len: usize, -} - -impl<'a> SszBlock<'a> { - /// Create a new instance from a slice reference. - /// - /// This function will validate the length of the ssz string, however it will not validate the - /// contents. - /// - /// The returned `SszBlock` instance will contain a `len` field which can be used to determine - /// how many bytes were read from the slice. In the case of multiple, sequentually serialized - /// blocks `len` can be used to assume the location of the next serialized block. - pub fn from_slice(vec: &'a [u8]) - -> Result - { - let untrimmed_ssz = &vec[..]; - /* - * Ensure the SSZ is long enough to be a block with - * one attestation record (not necessarily a valid - * attestation record). - */ - if vec.len() < MIN_SSZ_BLOCK_LENGTH + MIN_SSZ_ATTESTION_RECORD_LENGTH { - return Err(SszBlockError::TooShort); - } - /* - * Ensure the SSZ slice isn't longer than is possible for a block. - */ - if vec.len() > MAX_SSZ_BLOCK_LENGTH { - return Err(SszBlockError::TooLong); - } - /* - * Determine how many bytes are used to store attestation records. - */ - let attestation_len = decode_length(untrimmed_ssz, 72, LENGTH_BYTES) - .map_err(|_| SszBlockError::TooShort)?; - /* - * The block only has one variable field, `attestations`, therefore - * the size of the block must be the minimum size, plus the length - * of the attestations. - */ - let block_ssz_len = { - MIN_SSZ_BLOCK_LENGTH + attestation_len - }; - if vec.len() < block_ssz_len { - return Err(SszBlockError::TooShort); - } - Ok(Self{ - ssz: &untrimmed_ssz[0..block_ssz_len], - attestation_len, - len: block_ssz_len, - }) - } - - /// Return the canonical hash for this block. - pub fn block_hash(&self) -> Vec { - canonical_hash(self.ssz) - } - - /// Return the `parent_hash` field. - pub fn parent_hash(&self) -> &[u8] { - &self.ssz[0..32] - } - - /// Return the `slot_number` field. - pub fn slot_number(&self) -> u64 { - /* - * An error should be unreachable from this decode - * because we checked the length of the array at - * the initalization of this struct. - * - * If you can make this function panic, please report - * it to paul@sigmaprime.io - */ - if let Ok((n, _)) = u64::ssz_decode(&self.ssz, 32) { - n - } else { - unreachable!(); - } - } - - /// Return the `randao_reveal` field. - pub fn randao_reveal(&self) -> &[u8] { - &self.ssz[40..72] - } - - /// Return the `attestations` field. - pub fn attestations(&self) -> &[u8] { - let start = 72 + LENGTH_BYTES; - &self.ssz[start..(start + self.attestation_len)] - } - - /// Return the `pow_chain_ref` field. - pub fn pow_chain_ref(&self) -> &[u8] { - let start = self.len - (32 * 3); - &self.ssz[start..(start + 32)] - } - - /// Return the `active_state_root` field. - pub fn act_state_root(&self) -> &[u8] { - let start = self.len - (32 * 2); - &self.ssz[start..(start + 32)] - } - - /// Return the `active_state_root` field. - pub fn cry_state_root(&self) -> &[u8] { - let start = self.len - 32; - &self.ssz[start..(start + 32)] - } -} - -#[cfg(test)] -mod tests { - use super::*; - use super::super::types::{ - AttestationRecord, - Block, - }; - use super::super::ssz::SszStream; - use super::super::types::Hash256; - - fn get_block_ssz(b: &Block) -> Vec { - let mut ssz_stream = SszStream::new(); - ssz_stream.append(b); - ssz_stream.drain() - } - - fn get_attestation_record_ssz(ar: &AttestationRecord) -> Vec { - let mut ssz_stream = SszStream::new(); - ssz_stream.append(ar); - ssz_stream.drain() - } - - #[test] - fn test_ssz_block_zero_attestation_records() { - let mut b = Block::zero(); - b.attestations = vec![]; - let ssz = get_block_ssz(&b); - - assert_eq!( - SszBlock::from_slice(&ssz[..]), - Err(SszBlockError::TooShort) - ); - } - - #[test] - fn test_ssz_block_single_attestation_record_one_byte_short() { - let mut b = Block::zero(); - b.attestations = vec![AttestationRecord::zero()]; - let ssz = get_block_ssz(&b); - - assert_eq!( - SszBlock::from_slice(&ssz[0..(ssz.len() - 1)]), - Err(SszBlockError::TooShort) - ); - } - - #[test] - fn test_ssz_block_single_attestation_record_one_byte_long() { - let mut b = Block::zero(); - b.attestations = vec![AttestationRecord::zero()]; - let mut ssz = get_block_ssz(&b); - let original_len = ssz.len(); - ssz.push(42); - - let ssz_block = SszBlock::from_slice(&ssz[..]).unwrap(); - - assert_eq!(ssz_block.len, original_len); - } - - #[test] - fn test_ssz_block_single_attestation_record() { - let mut b = Block::zero(); - b.attestations = vec![AttestationRecord::zero()]; - let ssz = get_block_ssz(&b); - - assert!(SszBlock::from_slice(&ssz[..]).is_ok()); - } - - #[test] - fn test_ssz_block_attestation_length() { - let mut block = Block::zero(); - block.attestations.push(AttestationRecord::zero()); - - let serialized = get_block_ssz(&block); - let ssz_block = SszBlock::from_slice(&serialized).unwrap(); - - assert_eq!(ssz_block.attestation_len, MIN_SSZ_ATTESTION_RECORD_LENGTH); - } - - #[test] - fn test_ssz_block_block_hash() { - let mut block = Block::zero(); - block.attestations.push(AttestationRecord::zero()); - let serialized = get_block_ssz(&block); - let ssz_block = SszBlock::from_slice(&serialized).unwrap(); - let hash = ssz_block.block_hash(); - // Note: this hash was not generated by some external program, - // it was simply printed then copied into the code. This test - // will tell us if the hash changes, not that it matches some - // canonical reference. - let expected_hash = [ - 64, 176, 117, 210, 228, 229, 237, 100, 66, 66, 98, - 252, 31, 111, 218, 27, 160, 57, 164, 12, 15, 164, - 66, 102, 142, 36, 2, 196, 121, 54, 242, 3 - ]; - assert_eq!(hash, expected_hash); - - /* - * Test if you give the SszBlock too many ssz bytes - */ - let mut too_long = serialized.clone(); - too_long.push(42); - let ssz_block = SszBlock::from_slice(&too_long).unwrap(); - let hash = ssz_block.block_hash(); - assert_eq!(hash, expected_hash); - } - - #[test] - fn test_ssz_block_parent_hash() { - let mut block = Block::zero(); - block.attestations.push(AttestationRecord::zero()); - let reference_hash = Hash256::from([42_u8; 32]); - block.parent_hash = reference_hash.clone(); - - let serialized = get_block_ssz(&block); - let ssz_block = SszBlock::from_slice(&serialized).unwrap(); - - assert_eq!(ssz_block.parent_hash(), &reference_hash.to_vec()[..]); - } - - #[test] - fn test_ssz_block_slot_number() { - let mut block = Block::zero(); - block.attestations.push(AttestationRecord::zero()); - block.slot_number = 42; - - let serialized = get_block_ssz(&block); - let ssz_block = SszBlock::from_slice(&serialized).unwrap(); - - assert_eq!(ssz_block.slot_number(), 42); - } - - #[test] - fn test_ssz_block_randao_reveal() { - let mut block = Block::zero(); - block.attestations.push(AttestationRecord::zero()); - let reference_hash = Hash256::from([42_u8; 32]); - block.randao_reveal = reference_hash.clone(); - - let serialized = get_block_ssz(&block); - let ssz_block = SszBlock::from_slice(&serialized).unwrap(); - - assert_eq!(ssz_block.randao_reveal(), &reference_hash.to_vec()[..]); - } - - #[test] - fn test_ssz_block_attestations() { - /* - * Single AttestationRecord - */ - let mut block = Block::zero(); - block.attestations.push(AttestationRecord::zero()); - - let serialized = get_block_ssz(&block); - let ssz_block = SszBlock::from_slice(&serialized).unwrap(); - let ssz_ar = get_attestation_record_ssz(&AttestationRecord::zero()); - - assert_eq!(ssz_block.attestations(), &ssz_ar[..]); - - /* - * Multiple AttestationRecords - */ - let mut block = Block::zero(); - block.attestations.push(AttestationRecord::zero()); - block.attestations.push(AttestationRecord::zero()); - - let serialized = get_block_ssz(&block); - let ssz_block = SszBlock::from_slice(&serialized).unwrap(); - let mut ssz_ar = get_attestation_record_ssz(&AttestationRecord::zero()); - ssz_ar.append(&mut get_attestation_record_ssz(&AttestationRecord::zero())); - - assert_eq!(ssz_block.attestations(), &ssz_ar[..]); - } - - #[test] - fn test_ssz_block_pow_chain_ref() { - let mut block = Block::zero(); - block.attestations.push(AttestationRecord::zero()); - let reference_hash = Hash256::from([42_u8; 32]); - block.pow_chain_ref = reference_hash.clone(); - - let serialized = get_block_ssz(&block); - let ssz_block = SszBlock::from_slice(&serialized).unwrap(); - - assert_eq!(ssz_block.pow_chain_ref(), &reference_hash.to_vec()[..]); - } - - #[test] - fn test_ssz_block_act_state_root() { - let mut block = Block::zero(); - block.attestations.push(AttestationRecord::zero()); - let reference_hash = Hash256::from([42_u8; 32]); - block.active_state_root = reference_hash.clone(); - - let serialized = get_block_ssz(&block); - let ssz_block = SszBlock::from_slice(&serialized).unwrap(); - - assert_eq!(ssz_block.act_state_root(), &reference_hash.to_vec()[..]); - } - - #[test] - fn test_ssz_block_cry_state_root() { - let mut block = Block::zero(); - block.attestations.push(AttestationRecord::zero()); - let reference_hash = Hash256::from([42_u8; 32]); - block.crystallized_state_root = reference_hash.clone(); - - let serialized = get_block_ssz(&block); - let ssz_block = SszBlock::from_slice(&serialized).unwrap(); - - assert_eq!(ssz_block.cry_state_root(), &reference_hash.to_vec()[..]); - } -} diff --git a/lighthouse/db/src/stores/block_store.rs b/lighthouse/db/src/stores/beacon_block_store.rs similarity index 75% rename from lighthouse/db/src/stores/block_store.rs rename to lighthouse/db/src/stores/beacon_block_store.rs index eb4c11a9c..932675e41 100644 --- a/lighthouse/db/src/stores/block_store.rs +++ b/lighthouse/db/src/stores/beacon_block_store.rs @@ -1,8 +1,7 @@ extern crate ssz_helpers; -use self::ssz_helpers::ssz_block::{ - SszBlock, - SszBlockError, +use self::ssz_helpers::ssz_beacon_block::{ + SszBeaconBlock, }; use std::sync::Arc; use super::{ @@ -12,19 +11,19 @@ use super::{ use super::BLOCKS_DB_COLUMN as DB_COLUMN; #[derive(Clone, Debug, PartialEq)] -pub enum BlockAtSlotError { - UnknownBlock, - InvalidBlock, +pub enum BeaconBlockAtSlotError { + UnknownBeaconBlock, + InvalidBeaconBlock, DBError(String), } -pub struct BlockStore +pub struct BeaconBlockStore where T: ClientDB { db: Arc, } -impl BlockStore { +impl BeaconBlockStore { pub fn new(db: Arc) -> Self { Self { db, @@ -66,26 +65,31 @@ impl BlockStore { /// /// If a block is found, a tuple of (block_hash, serialized_block) is returned. pub fn block_at_slot(&self, head_hash: &[u8], slot: u64) - -> Result, Vec)>, BlockAtSlotError> + -> Result, Vec)>, BeaconBlockAtSlotError> { match self.get_serialized_block(head_hash)? { - None => Err(BlockAtSlotError::UnknownBlock), + None => Err(BeaconBlockAtSlotError::UnknownBeaconBlock), Some(ssz) => { - let block = SszBlock::from_slice(&ssz) - .map_err(|_| BlockAtSlotError::InvalidBlock)?; - match block.slot_number() { + let block = SszBeaconBlock::from_slice(&ssz) + .map_err(|_| BeaconBlockAtSlotError::InvalidBeaconBlock)?; + match block.slot() { s if s == slot => Ok(Some((head_hash.to_vec(), ssz.to_vec()))), s if s < slot => Ok(None), - _ => self.block_at_slot(block.parent_hash(), slot) + _ => { + match block.parent_hash() { + Some(parent_hash) => self.block_at_slot(parent_hash, slot), + None => Err(BeaconBlockAtSlotError::UnknownBeaconBlock) + } + } } } } } } -impl From for BlockAtSlotError { +impl From for BeaconBlockAtSlotError { fn from(e: DBError) -> Self { - BlockAtSlotError::DBError(e.message) + BeaconBlockAtSlotError::DBError(e.message) } } @@ -94,7 +98,7 @@ mod tests { extern crate ssz; extern crate types; - use self::types::block::Block; + use self::types::beacon_block::BeaconBlock; use self::types::attestation_record::AttestationRecord; use self::types::Hash256; use self::ssz::SszStream; @@ -107,7 +111,7 @@ mod tests { #[test] fn test_block_store_on_memory_db() { let db = Arc::new(MemoryDB::open()); - let bs = Arc::new(BlockStore::new(db.clone())); + let bs = Arc::new(BeaconBlockStore::new(db.clone())); let thread_count = 10; let write_count = 10; @@ -146,11 +150,11 @@ mod tests { #[test] fn test_block_at_slot() { let db = Arc::new(MemoryDB::open()); - let bs = Arc::new(BlockStore::new(db.clone())); + let bs = Arc::new(BeaconBlockStore::new(db.clone())); let blocks = (0..5).into_iter() .map(|_| { - let mut block = Block::zero(); + let mut block = BeaconBlock::zero(); let ar = AttestationRecord::zero(); block.attestations.push(ar); block @@ -175,8 +179,7 @@ mod tests { let slots = [0, 1, 3, 4, 5]; for (i, mut block) in blocks.enumerate() { - block.parent_hash = parent_hashes[i]; - block.slot_number = slots[i]; + block.slot = slots[i]; let mut s = SszStream::new(); s.append(&block); let ssz = s.drain(); @@ -184,23 +187,23 @@ mod tests { } let tuple = bs.block_at_slot(&hashes[4], 5).unwrap().unwrap(); - let block = SszBlock::from_slice(&tuple.1).unwrap(); - assert_eq!(block.slot_number(), 5); + let block = SszBeaconBlock::from_slice(&tuple.1).unwrap(); + assert_eq!(block.slot(), 5); assert_eq!(tuple.0, hashes[4].to_vec()); let tuple = bs.block_at_slot(&hashes[4], 4).unwrap().unwrap(); - let block = SszBlock::from_slice(&tuple.1).unwrap(); - assert_eq!(block.slot_number(), 4); + let block = SszBeaconBlock::from_slice(&tuple.1).unwrap(); + assert_eq!(block.slot(), 4); assert_eq!(tuple.0, hashes[3].to_vec()); let tuple = bs.block_at_slot(&hashes[4], 3).unwrap().unwrap(); - let block = SszBlock::from_slice(&tuple.1).unwrap(); - assert_eq!(block.slot_number(), 3); + let block = SszBeaconBlock::from_slice(&tuple.1).unwrap(); + assert_eq!(block.slot(), 3); assert_eq!(tuple.0, hashes[2].to_vec()); let tuple = bs.block_at_slot(&hashes[4], 0).unwrap().unwrap(); - let block = SszBlock::from_slice(&tuple.1).unwrap(); - assert_eq!(block.slot_number(), 0); + let block = SszBeaconBlock::from_slice(&tuple.1).unwrap(); + assert_eq!(block.slot(), 0); assert_eq!(tuple.0, hashes[0].to_vec()); let ssz = bs.block_at_slot(&hashes[4], 2).unwrap(); @@ -210,6 +213,6 @@ mod tests { assert_eq!(ssz, None); let ssz = bs.block_at_slot(&Hash256::from("unknown".as_bytes()), 2); - assert_eq!(ssz, Err(BlockAtSlotError::UnknownBlock)); + assert_eq!(ssz, Err(BeaconBlockAtSlotError::UnknownBeaconBlock)); } } diff --git a/lighthouse/db/src/stores/mod.rs b/lighthouse/db/src/stores/mod.rs index e65803714..aa49489f7 100644 --- a/lighthouse/db/src/stores/mod.rs +++ b/lighthouse/db/src/stores/mod.rs @@ -3,13 +3,13 @@ use super::{ DBError, }; -mod block_store; +mod beacon_block_store; mod pow_chain_store; mod validator_store; -pub use self::block_store::{ - BlockStore, - BlockAtSlotError, +pub use self::beacon_block_store::{ + BeaconBlockStore, + BeaconBlockAtSlotError, }; pub use self::pow_chain_store::PoWChainStore; pub use self::validator_store::{