From e0360edde0c2763bb6a3f4eabd0bcda911ec6b07 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 4 Dec 2018 14:50:47 +1100 Subject: [PATCH] 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 e4ddffd62d..9cf4a60e9b 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 a2f05ccb7d..6b65ae0aa3 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 d61f6e19cd..2427ef4c8e 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);