Heavily restructure repo

Separate most modules into crates
This commit is contained in:
Paul Hauner
2018-10-02 16:41:10 +10:00
parent 07bfd7e97d
commit 0fbe4179b3
71 changed files with 150 additions and 2240 deletions

View File

@@ -0,0 +1,11 @@
[package]
name = "types"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
[dependencies]
bls = { path = "../utils/bls" }
boolean-bitfield = { path = "../utils/boolean-bitfield" }
ethereum-types = "0.4.0"
rand = "0.3"
ssz = { path = "../utils/ssz" }

View File

@@ -0,0 +1,30 @@
use super::Hash256;
use super::attestation_record::AttestationRecord;
pub struct ActiveState {
pub pending_attestations: Vec<AttestationRecord>,
pub recent_block_hashes: Vec<Hash256>,
}
impl ActiveState {
/// Returns a new instance where all fields are empty vectors.
pub fn zero() -> Self {
Self {
pending_attestations: vec![],
recent_block_hashes: vec![],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_act_state_zero() {
let a = ActiveState::zero();
assert_eq!(a.pending_attestations.len(), 0);
assert_eq!(a.recent_block_hashes.len(), 0);
}
}

View File

@@ -0,0 +1,137 @@
use super::{ Hash256, Bitfield };
use super::bls::{
AggregateSignature,
BLS_AGG_SIG_BYTE_SIZE,
};
use super::ssz::{
Encodable,
Decodable,
DecodeError,
decode_ssz_list,
SszStream,
};
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)
};
#[derive(Debug, Clone, PartialEq)]
pub struct AttestationRecord {
pub slot: u64,
pub shard_id: u16,
pub oblique_parent_hashes: Vec<Hash256>,
pub shard_block_hash: Hash256,
pub attester_bitfield: Bitfield,
pub justified_slot: u64,
pub justified_block_hash: Hash256,
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_vec(&self.attester_bitfield.to_be_vec());
s.append(&self.justified_slot);
s.append(&self.justified_block_hash);
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 (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,
aggregate_sig,
};
Ok((attestation_record, i))
}
}
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(),
aggregate_sig: AggregateSignature::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::ssz::SszStream;
#[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();
assert_eq!(ssz.len(), MIN_SSZ_ATTESTION_RECORD_LENGTH);
}
#[test]
pub fn test_attestation_record_min_ssz_encode_decode() {
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(&vec![17; 42][..]),
justified_slot: 19,
justified_block_hash: Hash256::from(&vec![15; 32][..]),
aggregate_sig: AggregateSignature::new(),
};
let mut ssz_stream = SszStream::new();
ssz_stream.append(&original);
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);
}
}

View File

@@ -0,0 +1,80 @@
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<AttestationRecord>,
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);
}
}

View File

@@ -0,0 +1,32 @@
pub struct ChainConfig {
pub cycle_length: u8,
pub shard_count: u16,
pub min_committee_size: u64,
pub genesis_time: u64,
}
/*
* Presently this is just some arbitrary time in Sept 2018.
*/
const GENESIS_TIME: u64 = 1_537_488_655;
impl ChainConfig {
pub fn standard() -> Self {
Self {
cycle_length: 64,
shard_count: 1024,
min_committee_size: 128,
genesis_time: GENESIS_TIME, // arbitrary
}
}
#[cfg(test)]
pub fn super_fast_tests() -> Self {
Self {
cycle_length: 2,
shard_count: 2,
min_committee_size: 2,
genesis_time: GENESIS_TIME, // arbitrary
}
}
}

View File

@@ -0,0 +1,62 @@
use super::utils::errors::ParameterError;
use super::utils::types::Hash256;
/*
* Work-in-progress function: not ready for review.
*/
pub fn get_block_hash(
active_state_recent_block_hashes: &[Hash256],
current_block_slot: u64,
slot: u64,
cycle_length: u64, // convert from standard u8
) -> Result<Hash256, ParameterError> {
// active_state must have at 2*cycle_length hashes
assert_error!(
active_state_recent_block_hashes.len() as u64 == cycle_length * 2,
ParameterError::InvalidInput(String::from(
"active state has incorrect number of block hashes"
))
);
let state_start_slot = (current_block_slot)
.checked_sub(cycle_length * 2)
.unwrap_or(0);
assert_error!(
(state_start_slot <= slot) && (slot < current_block_slot),
ParameterError::InvalidInput(String::from("incorrect slot number"))
);
let index = 2 * cycle_length + slot - current_block_slot; // should always be positive
Ok(active_state_recent_block_hashes[index as usize])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_block_hash() {
let block_slot: u64 = 10;
let slot: u64 = 3;
let cycle_length: u64 = 8;
let mut block_hashes: Vec<Hash256> = Vec::new();
for _i in 0..2 * cycle_length {
block_hashes.push(Hash256::random());
}
let result = get_block_hash(
&block_hashes,
block_slot,
slot,
cycle_length)
.unwrap();
assert_eq!(
result,
block_hashes[(2 * cycle_length + slot - block_slot) as usize]
);
}
}

View File

@@ -0,0 +1,3 @@
mod block_hash;
use super::utils;

View File

@@ -0,0 +1,7 @@
use std::collections::HashMap;
/// Maps a (slot, shard_id) to attestation_indices.
pub type AttesterMap = HashMap<(u64, u16), Vec<usize>>;
/// Maps a slot to a block proposer.
pub type ProposerMap = HashMap<u64, usize>;

View File

@@ -0,0 +1,9 @@
mod delegation;
mod shuffling;
pub mod maps;
pub mod attestation_parent_hashes;
use super::utils;
use super::utils::types::Hash256;
pub use self::shuffling::shuffle;

View File

@@ -0,0 +1,2 @@
This module includes the fundamental shuffling function. It does not do the
full validator delegation amongst slots.

View File

@@ -0,0 +1,52 @@
extern crate blake2_rfc;
mod rng;
use self::rng::ShuffleRng;
#[derive(Debug)]
pub enum ShuffleErr {
ExceedsListLength,
}
/// Performs a deterministic, in-place shuffle of a vector of bytes.
/// The final order of the shuffle is determined by successive hashes
/// of the supplied `seed`.
pub fn shuffle(
seed: &[u8],
mut list: Vec<usize>)
-> Result<Vec<usize>, ShuffleErr>
{
let mut rng = ShuffleRng::new(seed);
if list.len() > rng.rand_max as usize {
return Err(ShuffleErr::ExceedsListLength);
}
for i in 0..(list.len() - 1) {
let n = list.len() - i;
let j = rng.rand_range(n as u32) as usize + i;
list.swap(i, j);
}
Ok(list)
}
#[cfg(test)]
mod tests {
use super::*;
use super::blake2_rfc::blake2s::{ blake2s, Blake2sResult };
fn hash(seed: &[u8]) -> Blake2sResult {
blake2s(32, &[], seed)
}
#[test]
fn test_shuffling() {
let seed = hash(b"4kn4driuctg8");
let list: Vec<usize> = (0..12).collect();
let s = shuffle(seed.as_bytes(), list).unwrap();
assert_eq!(
s,
vec![7, 4, 8, 6, 5, 3, 0, 11, 1, 2, 10, 9],
)
}
}

View File

@@ -0,0 +1,129 @@
use super::blake2_rfc::blake2s::{ Blake2s, Blake2sResult };
const SEED_SIZE_BYTES: usize = 32;
const RAND_BYTES: usize = 3; // 24 / 8
const RAND_MAX: u32 = 16_777_216; // 2**24
/// A pseudo-random number generator which given a seed
/// uses successive blake2s hashing to generate "entropy".
pub struct ShuffleRng {
seed: Blake2sResult,
idx: usize,
pub rand_max: u32,
}
impl ShuffleRng {
/// Create a new instance given some "seed" bytes.
pub fn new(initial_seed: &[u8]) -> Self {
Self {
seed: hash(initial_seed),
idx: 0,
rand_max: RAND_MAX,
}
}
/// "Regenerates" the seed by hashing it.
fn rehash_seed(&mut self) {
self.seed = hash(self.seed.as_bytes());
self.idx = 0;
}
/// Extracts 3 bytes from the `seed`. Rehashes seed if required.
fn rand(&mut self) -> u32 {
self.idx += RAND_BYTES;
if self.idx >= SEED_SIZE_BYTES {
self.rehash_seed();
self.rand()
} else {
int_from_byte_slice(
self.seed.as_bytes(),
self.idx - RAND_BYTES,
)
}
}
/// Generate a random u32 below the specified maximum `n`.
///
/// Provides a filtered result from a higher-level rng, by discarding
/// results which may bias the output. Because of this, execution time is
/// not linear and may potentially be infinite.
pub fn rand_range(&mut self, n: u32) -> u32 {
assert!(n < RAND_MAX, "RAND_MAX exceed");
let mut x = self.rand();
while x >= self.rand_max - (self.rand_max % n) {
x = self.rand();
}
x % n
}
}
/// Reads the next three bytes of `source`, starting from `offset` and
/// interprets those bytes as a 24 bit big-endian integer.
/// Returns that integer.
fn int_from_byte_slice(source: &[u8], offset: usize) -> u32 {
(
u32::from(source[offset + 2])) |
(u32::from(source[offset + 1]) << 8) |
(u32::from(source[offset ]) << 16
)
}
/// Peform a blake2s hash on the given bytes.
fn hash(bytes: &[u8]) -> Blake2sResult {
let mut hasher = Blake2s::new(SEED_SIZE_BYTES);
hasher.update(bytes);
hasher.finalize()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shuffling_int_from_slice() {
let mut x = int_from_byte_slice(
&[0, 0, 1],
0);
assert_eq!((x as u32), 1);
x = int_from_byte_slice(
&[0, 1, 1],
0);
assert_eq!(x, 257);
x = int_from_byte_slice(
&[1, 1, 1],
0);
assert_eq!(x, 65793);
x = int_from_byte_slice(
&[255, 1, 1],
0);
assert_eq!(x, 16711937);
x = int_from_byte_slice(
&[255, 255, 255],
0);
assert_eq!(x, 16777215);
x = int_from_byte_slice(
&[0x8f, 0xbb, 0xc7],
0);
assert_eq!(x, 9419719);
}
#[test]
fn test_shuffling_hash_fn() {
let digest = hash(hash(b"4kn4driuctg8").as_bytes()); // double-hash is intentional
let digest_bytes = digest.as_bytes();
let expected = [
0xff, 0xff, 0xff, 0x8f, 0xbb, 0xc7, 0xab, 0x64, 0x43, 0x9a,
0xe5, 0x12, 0x44, 0xd8, 0x70, 0xcf, 0xe5, 0x79, 0xf6, 0x55,
0x6b, 0xbd, 0x81, 0x43, 0xc5, 0xcd, 0x70, 0x2b, 0xbe, 0xe3,
0x87, 0xc7,
];
assert_eq!(digest_bytes.len(), expected.len());
assert_eq!(digest_bytes, expected)
}
}

View File

@@ -0,0 +1,29 @@
use super::Hash256;
#[derive(Clone)]
pub struct CrosslinkRecord {
pub dynasty: u64,
pub hash: Hash256,
}
impl CrosslinkRecord {
/// Generates a new instance where `dynasty` and `hash` are both zero.
pub fn zero() -> Self {
Self {
dynasty: 0,
hash: Hash256::zero(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crosslink_record_zero() {
let c = CrosslinkRecord::zero();
assert_eq!(c.dynasty, 0);
assert!(c.hash.is_zero());
}
}

View File

@@ -0,0 +1,65 @@
use super::validator_record::ValidatorRecord;
use super::crosslink_record::CrosslinkRecord;
use super::shard_and_committee::ShardAndCommittee;
use super::ethereum_types::U256;
use super::Hash256;
pub struct CrystallizedState {
pub validators: Vec<ValidatorRecord>,
pub epoch_number: u64,
pub indicies_for_heights: Vec<ShardAndCommittee>,
pub last_justified_slot: u64,
pub justified_streak: u16,
pub last_finalized_slot: u64,
pub current_dynasty: u64,
pub crosslinking_shard_start: u16,
pub crosslink_records: Vec<CrosslinkRecord>,
pub total_deposits: U256,
pub dynasty_seed: Hash256,
pub dynasty_seed_last_reset: u64,
}
impl CrystallizedState {
/// Returns a new instance where all fields are either zero or an
/// empty vector.
pub fn zero() -> Self {
Self {
validators: vec![],
epoch_number: 0,
indicies_for_heights: vec![],
last_justified_slot: 0,
justified_streak: 0,
last_finalized_slot: 0,
current_dynasty: 0,
crosslinking_shard_start: 0,
crosslink_records: vec![],
total_deposits: U256::zero(),
dynasty_seed: Hash256::zero(),
dynasty_seed_last_reset: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cry_state_zero() {
let c = CrystallizedState::zero();
assert_eq!(c.validators.len(), 0);
assert_eq!(c.epoch_number, 0);
assert_eq!(c.indicies_for_heights.len(), 0);
assert_eq!(c.last_justified_slot, 0);
assert_eq!(c.justified_streak, 0);
assert_eq!(c.last_finalized_slot, 0);
assert_eq!(c.current_dynasty, 0);
assert_eq!(c.crosslinking_shard_start, 0);
assert_eq!(c.crosslink_records.len(), 0);
assert!(c.total_deposits.is_zero());
assert!(c.dynasty_seed.is_zero());
assert_eq!(c.dynasty_seed_last_reset, 0);
}
}

View File

@@ -0,0 +1,49 @@
extern crate ethereum_types;
extern crate bls;
extern crate boolean_bitfield;
extern crate ssz;
pub mod active_state;
pub mod attestation_record;
pub mod crystallized_state;
pub mod chain_config;
pub mod block;
pub mod crosslink_record;
pub mod shard_and_committee;
pub mod validator_record;
use self::ethereum_types::{
H256,
H160,
U256
};
use self::boolean_bitfield::BooleanBitfield;
use std::collections::HashMap;
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 crosslink_record::CrosslinkRecord;
pub use shard_and_committee::ShardAndCommittee;
pub use validator_record::ValidatorRecord;
pub type Hash256 = H256;
pub type Address = H160;
pub type EthBalance = U256;
pub type Bitfield = BooleanBitfield;
/// Maps a (slot, shard_id) to attestation_indices.
pub type AttesterMap = HashMap<(u64, u16), Vec<usize>>;
/// Maps a slot to a block proposer.
pub type ProposerMap = HashMap<u64, usize>;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

View File

@@ -0,0 +1,20 @@
extern crate rlp;
extern crate ethereum_types;
extern crate blake2_rfc as blake2;
extern crate bytes;
extern crate ssz;
mod common;
pub mod active_state;
pub mod attestation_record;
pub mod crystallized_state;
pub mod chain_config;
pub mod block;
pub mod crosslink_record;
pub mod shard_and_committee;
pub mod validator_record;
use super::bls;
use super::db;
use super::utils;

View File

@@ -0,0 +1,28 @@
#[derive(Clone,Debug)]
pub struct ShardAndCommittee {
pub shard_id: u16,
pub committee: Vec<usize>
}
impl ShardAndCommittee {
/// Returns a new instance where the `shard_id` is zero and the
/// committee is an empty vector.
pub fn zero() -> Self {
Self {
shard_id: 0,
committee: vec![],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shard_and_committee_zero() {
let s = ShardAndCommittee::zero();
assert_eq!(s.shard_id, 0);
assert_eq!(s.committee.len(), 0);
}
}

View File

@@ -0,0 +1,67 @@
use super::{
Hash256,
Address,
EthBalance,
};
use super::bls::{
PublicKey,
Keypair
};
pub struct ValidatorRecord {
pub pubkey: PublicKey,
pub withdrawal_shard: u16,
pub withdrawal_address: Address,
pub randao_commitment: Hash256,
pub balance: EthBalance,
pub start_dynasty: u64,
pub end_dynasty: u64,
}
impl ValidatorRecord {
/// Generates a new instance where the keypair is generated using
/// `rand::thread_rng` entropy and all other fields are set to zero.
///
/// Returns the new instance and new keypair.
pub fn zero_with_thread_rand_keypair() -> (Self, Keypair) {
let keypair = Keypair::random();
let s = Self {
pubkey: keypair.pk.clone(),
withdrawal_shard: 0,
withdrawal_address: Address::zero(),
randao_commitment: Hash256::zero(),
balance: EthBalance::zero(),
start_dynasty: 0,
end_dynasty: 0,
};
(s, keypair)
}
}
impl Clone for ValidatorRecord {
fn clone(&self) -> ValidatorRecord {
ValidatorRecord {
pubkey: self.pubkey.clone(),
..*self
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validator_record_zero_rand_keypair() {
let (v, _kp) = ValidatorRecord::zero_with_thread_rand_keypair();
// TODO: check keys
assert_eq!(v.withdrawal_shard, 0);
assert!(v.withdrawal_address.is_zero());
assert!(v.randao_commitment.is_zero());
assert!(v.balance.is_zero());
assert_eq!(v.start_dynasty, 0);
assert_eq!(v.end_dynasty, 0);
}
}

View File

@@ -0,0 +1,7 @@
[package]
name = "bls"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
[dependencies]
bls-aggregates = { git = "https://github.com/sigp/signature-schemes" }

View File

@@ -0,0 +1,10 @@
extern crate bls_aggregates;
pub use self::bls_aggregates::AggregateSignature;
pub use self::bls_aggregates::AggregatePublicKey;
pub use self::bls_aggregates::Signature;
pub use self::bls_aggregates::Keypair;
pub use self::bls_aggregates::PublicKey;
pub use self::bls_aggregates::SecretKey;
pub const BLS_AGG_SIG_BYTE_SIZE: usize = 97;

View File

@@ -0,0 +1,7 @@
[package]
name = "boolean-bitfield"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
[dependencies]
ssz = { path = "../ssz" }

View File

@@ -0,0 +1,7 @@
# Boolean Bitfield
A work-in-progress implementation of an unbounded boolean bitfield.
Based upon a `Vec<u8>`
Documentation TBC...

View File

@@ -0,0 +1,334 @@
/*
* Implemenation of a bitfield as a vec. Only
* supports bytes (Vec<u8>) as the underlying
* storage.
*
* A future implementation should be more efficient,
* this is just to get the job done for now.
*/
extern crate ssz;
use std::cmp::max;
#[derive(Eq, Clone, Default, Debug)]
pub struct BooleanBitfield{
len: usize,
vec: Vec<u8>
}
impl BooleanBitfield {
/// Create a new bitfield with a length of zero.
pub fn new() -> Self {
Self {
len: 0,
vec: vec![0]
}
}
/// Create a new bitfield of a certain capacity
pub fn with_capacity(capacity: usize) -> Self {
let mut vec = Vec::with_capacity(capacity / 8 + 1);
vec.push(0);
Self {
len: 0,
vec
}
}
/// Read the value of a bit.
///
/// Will return `true` if the bit has been set to `true`
/// without then being set to `False`.
pub fn get_bit(&self, i: usize) -> bool {
let bit = |i: usize| i % 8;
let byte = |i: usize| i / 8;
if byte(i) >= self.vec.len() {
false
} else {
self.vec[byte(i)] & (1 << (bit(i) as u8)) != 0
}
}
/// Set the value of a bit.
///
/// If this bit is larger than the length of the underlying byte
/// array it will be extended.
pub fn set_bit(&mut self, i: usize, to: bool) {
let bit = |i: usize| i % 8;
let byte = |i: usize| i / 8;
self.len = max(self.len, i + 1);
if byte(i) >= self.vec.len() {
self.vec.resize(byte(i) + 1, 0);
}
if to {
self.vec[byte(i)] =
self.vec[byte(i)] | (1 << (bit(i) as u8))
} else {
self.vec[byte(i)] =
self.vec[byte(i)] & !(1 << (bit(i) as u8))
}
}
/// Return the "length" of this bitfield. Length is defined as
/// the highest bit that has been set.
///
/// Note: this is distinct from the length of the underlying
/// vector.
pub fn len(&self) -> usize { self.len }
/// True if no bits have ever been set. A bit that is set and then
/// unset will still count to the length of the bitfield.
///
/// Note: this is distinct from the length of the underlying
/// vector.
pub fn is_empty(&self) -> bool { self.len == 0 }
/// The number of bytes required to represent the bitfield.
pub fn num_bytes(&self) -> usize { self.vec.len() }
/// Iterate through the underlying vector and count the number of
/// true bits.
pub fn num_true_bits(&self) -> u64 {
let mut count: u64 = 0;
for byte in &self.vec {
for bit in 0..8 {
if byte & (1 << (bit as u8)) != 0 {
count += 1;
}
}
}
count
}
/// Iterate through the underlying vector and find the highest
/// set bit. Useful for instantiating a new instance from
/// some set of bytes.
pub fn compute_length(bytes: &[u8]) -> usize {
for byte in (0..bytes.len()).rev() {
for bit in (0..8).rev() {
if bytes[byte] & (1 << (bit as u8)) != 0 {
return (byte * 8) + bit + 1
}
}
}
0
}
/// Get the byte at a position, assuming big-endian encoding.
pub fn get_byte(&self, n: usize) -> Option<&u8> {
self.vec.get(n)
}
/// Clone and return the underlying byte array (`Vec<u8>`).
pub fn to_vec(&self) -> Vec<u8> {
self.vec.clone()
}
/// Clone and return the underlying byte array (`Vec<u8>`) in big-endinan format.
pub fn to_be_vec(&self) -> Vec<u8> {
let mut o = self.vec.clone();
o.reverse();
o
}
}
impl<'a> From<&'a [u8]> for BooleanBitfield {
fn from(input: &[u8]) -> Self {
let mut vec = input.to_vec();
vec.reverse();
BooleanBitfield {
vec,
len: BooleanBitfield::compute_length(input)
}
}
}
impl PartialEq for BooleanBitfield {
fn eq(&self, other: &BooleanBitfield) -> bool {
(self.vec == other.vec) &
(self.len == other.len)
}
}
impl ssz::Encodable for BooleanBitfield {
fn ssz_append(&self, s: &mut ssz::SszStream) {
s.append_vec(&self.to_vec());
}
}
impl ssz::Decodable for BooleanBitfield {
fn ssz_decode(bytes: &[u8], index: usize)
-> Result<(Self, usize), ssz::DecodeError>
{
let len = ssz::decode::decode_length(
bytes,
index,
ssz::LENGTH_BYTES)?;
if (ssz::LENGTH_BYTES + len) > bytes.len() {
return Err(ssz::DecodeError::TooShort);
}
if len == 0 {
Ok((BooleanBitfield::new(),
index + ssz::LENGTH_BYTES))
} else {
let b = BooleanBitfield::
from(&bytes[(index + 4)..(index + len + 4)]);
let index = index + ssz::LENGTH_BYTES + len;
Ok((b, index))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ssz::Decodable;
#[test]
fn test_new_from_slice() {
let s = [0];
let b = BooleanBitfield::from(&s[..]);
assert_eq!(b.len, 0);
let s = [255];
let b = BooleanBitfield::from(&s[..]);
assert_eq!(b.len, 8);
let s = [0, 1];
let b = BooleanBitfield::from(&s[..]);
assert_eq!(b.len, 9);
let s = [31];
let b = BooleanBitfield::from(&s[..]);
assert_eq!(b.len, 5);
}
#[test]
fn test_ssz_encoding() {
let mut b = BooleanBitfield::new();
b.set_bit(8, true);
let mut stream = ssz::SszStream::new();
stream.append(&b);
assert_eq!(stream.drain(), vec![0, 0, 0, 2, 0, 1]);
}
/*
#[test]
fn test_ssz_decoding() {
/*
* Correct input
*/
let input = vec![0, 0, 0, 2, 0, 1];
let (b, i) = BooleanBitfield::ssz_decode(&input, 0).unwrap();
assert_eq!(i, 6);
assert_eq!(b.num_true_bits(), 1);
assert_eq!(b.get_bit(8), true);
/*
* Input too long
*/
let mut input = vec![0, 0, 0, 2, 0, 1];
input.push(42);
let (b, i) = BooleanBitfield::ssz_decode(&input, 0).unwrap();
assert_eq!(i, 6);
assert_eq!(b.num_true_bits(), 1);
assert_eq!(b.get_bit(8), true);
/*
* Input too short
*/
let input = vec![0, 0, 0, 2, 1];
let res = BooleanBitfield::ssz_decode(&input, 0);
assert_eq!(res, Err(ssz::DecodeError::TooShort));
}
*/
#[test]
fn test_new_bitfield_len() {
let b = BooleanBitfield::new();
assert_eq!(b.len(), 0);
assert_eq!(b.to_be_vec(), vec![0]);
let b = BooleanBitfield::with_capacity(100);
assert_eq!(b.len(), 0);
assert_eq!(b.to_be_vec(), vec![0]);
}
#[test]
fn test_bitfield_set() {
let mut b = BooleanBitfield::new();
b.set_bit(0, false);
assert_eq!(b.to_be_vec(), [0]);
b = BooleanBitfield::new();
b.set_bit(7, true);
assert_eq!(b.to_be_vec(), [128]);
b.set_bit(7, false);
assert_eq!(b.to_be_vec(), [0]);
assert_eq!(b.len(), 8);
b = BooleanBitfield::new();
b.set_bit(7, true);
b.set_bit(0, true);
assert_eq!(b.to_be_vec(), [129]);
b.set_bit(7, false);
assert_eq!(b.to_be_vec(), [1]);
assert_eq!(b.len(), 8);
b = BooleanBitfield::new();
b.set_bit(8, true);
assert_eq!(b.to_be_vec(), [1, 0]);
assert_eq!(b.len(), 9);
b.set_bit(8, false);
assert_eq!(b.to_be_vec(), [0, 0]);
assert_eq!(b.len(), 9);
b = BooleanBitfield::new();
b.set_bit(15, true);
assert_eq!(b.to_be_vec(), [128, 0]);
b.set_bit(15, false);
assert_eq!(b.to_be_vec(), [0, 0]);
assert_eq!(b.len(), 16);
b = BooleanBitfield::new();
b.set_bit(8, true);
b.set_bit(15, true);
assert_eq!(b.to_be_vec(), [129, 0]);
b.set_bit(15, false);
assert_eq!(b.to_be_vec(), [1, 0]);
assert_eq!(b.len(), 16);
}
#[test]
fn test_bitfield_get() {
let test_nums = vec![0, 8, 15, 42, 1337];
for i in test_nums {
let mut b = BooleanBitfield::new();
assert_eq!(b.get_bit(i), false);
b.set_bit(i, true);
assert_eq!(b.get_bit(i), true);
b.set_bit(i, true);
}
}
#[test]
fn test_bitfield_num_true_bits() {
let mut b = BooleanBitfield::new();
assert_eq!(b.num_true_bits(), 0);
b.set_bit(15, true);
assert_eq!(b.num_true_bits(), 1);
b.set_bit(15, false);
assert_eq!(b.num_true_bits(), 0);
b.set_bit(0, true);
b.set_bit(7, true);
b.set_bit(8, true);
b.set_bit(1337, true);
assert_eq!(b.num_true_bits(), 4);
}
}

View File

@@ -0,0 +1,7 @@
[package]
name = "hashing"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
[dependencies]
blake2-rfc = "0.2.18"

View File

@@ -0,0 +1,8 @@
extern crate blake2_rfc;
use self::blake2_rfc::blake2b::blake2b;
pub fn canonical_hash(input: &[u8]) -> Vec<u8> {
let result = blake2b(64, &[], input);
result.as_bytes()[0..32].to_vec()
}

View File

@@ -0,0 +1,8 @@
[package]
name = "ssz"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
[dependencies]
bytes = "0.4.9"
ethereum-types = "0.4.0"

View File

@@ -0,0 +1,543 @@
# simpleserialize (ssz) [WIP]
This is currently a ***Work In Progress*** crate.
SimpleSerialize is a serialization protocol described by Vitalik Buterin. The
method is tentatively intended for use in the Ethereum Beacon Chain as
described in the [Ethereum 2.1 Spec](https://notes.ethereum.org/s/Syj3QZSxm).
The Beacon Chain specification is the core, canonical specification which we
are following.
The current reference implementation has been described in the [Beacon Chain
Repository](https://github.com/ethereum/beacon_chain/blob/master/ssz/ssz.py).
*Please Note: This implementation is presently a placeholder until the final
spec is decided.*\
*Do not rely upon it for reference.*
## Table of Contents
* [SimpleSerialize Overview](#simpleserialize-overview)
+ [Serialize/Encode](#serializeencode)
- [int or uint: 8/16/24/32/64/256](#int-or-uint-816243264256)
- [Address](#address)
- [Hash32](#hash32)
- [Bytes](#bytes)
- [List](#list)
+ [Deserialize/Decode](#deserializedecode)
- [Int or Uint: 8/16/24/32/64/256](#int-or-uint-816243264256)
- [Address](#address-1)
- [Hash32](#hash32-1)
- [Bytes](#bytes-1)
- [List](#list-1)
* [Technical Overview](#technical-overview)
* [Building](#building)
+ [Installing Rust](#installing-rust)
* [Dependencies](#dependencies)
+ [bytes v0.4.9](#bytes-v049)
+ [ethereum-types](#ethereum-types)
* [Interface](#interface)
+ [Encodable](#encodable)
+ [Decodable](#decodable)
+ [SszStream](#sszstream)
- [new()](#new)
- [append(&mut self, value: &E) -> &mut Self](#appendmut-self-value-e---mut-self)
- [append_encoded_val(&mut self, vec: &Vec)](#append_encoded_valmut-self-vec-vec)
- [append_vec(&mut self, vec: &Vec)](#append_vecmut-self-vec-vec)
- [drain(self) -> Vec](#drainself---vec)
+ [decode_ssz(ssz_bytes: &(u8), index: usize) -> Result](#decode_sszssz_bytes-u8-index-usize---resultt-usize-decodeerror)
+ [decode_ssz_list(ssz_bytes: &(u8), index: usize) -> Result, usize), DecodeError>](#decode_ssz_listssz_bytes-u8-index-usize---resultvec-usize-decodeerror)
+ [decode_length(bytes: &(u8), index: usize, length_bytes: usize) -> Result](#decode_lengthbytes-u8-index-usize-length_bytes-usize---resultusize-decodeerror)
* [Usage](#usage)
+ [Serializing/Encoding](#serializingencoding)
- [Rust](#rust)
* [Deserializing/Decoding](#deserializingdecoding)
- [Rust](#rust-1)
---
## SimpleSerialize Overview
The ``simpleserialize`` method for serialization follows simple byte conversion,
making it effective and efficient for encoding and decoding.
The decoding requires knowledge of the data **type** and the order of the
serialization.
Syntax:
| Shorthand | Meaning |
|:-------------|:----------------------------------------------------|
| `big` | ``big endian`` |
| `to_bytes` | convert to bytes. Params: ``(size, byte order)`` |
| `from_bytes` | convert from bytes. Params: ``(bytes, byte order)`` |
| `value` | the value to serialize |
| `rawbytes` | raw encoded/serialized bytes |
| `len(value)` | get the length of the value. (number of bytes etc) |
### Serialize/Encode
#### int or uint: 8/16/24/32/64/256
Convert directly to bytes the size of the int. (e.g. ``int16 = 2 bytes``)
All integers are serialized as **big endian**.
| Check to perform | Code |
|:-----------------------|:------------------------|
| Int size is not 0 | ``int_size > 0`` |
| Size is a byte integer | ``int_size % 8 == 0`` |
| Value is less than max | ``2**int_size > value`` |
```python
buffer_size = int_size / 8
return value.to_bytes(buffer_size, 'big')
```
#### Address
The address should already come as a hash/byte format. Ensure that length is
**20**.
| Check to perform | Code |
|:-----------------------|:---------------------|
| Length is correct (20) | ``len(value) == 20`` |
```python
assert( len(value) == 20 )
return value
```
#### Hash32
The hash32 should already be a 32 byte length serialized data format. The safety
check ensures the 32 byte length is satisfied.
| Check to perform | Code |
|:-----------------------|:---------------------|
| Length is correct (32) | ``len(value) == 32`` |
```python
assert( len(value) == 32 )
return value
```
#### Bytes
For general `byte` type:
1. Get the length/number of bytes; Encode into a 4 byte integer.
2. Append the value to the length and return: ``[ length_bytes ] + [
value_bytes ]``
```python
byte_length = (len(value)).to_bytes(4, 'big')
return byte_length + value
```
#### List
For lists of values, get the length of the list and then serialize the value
of each item in the list:
1. For each item in list:
1. serialize.
2. append to string.
2. Get size of serialized string. Encode into a 4 byte integer.
```python
serialized_list_string = ''
for item in value:
serialized_list_string += serialize(item)
serialized_len = len(serialized_list_string)
return serialized_len + serialized_list_string
```
### Deserialize/Decode
The decoding requires knowledge of the type of the item to be decoded. When
performing decoding on an entire serialized string, it also requires knowledge
of what order the objects have been serialized in.
Note: Each return will provide ``deserialized_object, new_index`` keeping track
of the new index.
At each step, the following checks should be made:
| Check Type | Check |
|:-------------------------|:----------------------------------------------------------|
| Ensure sufficient length | ``length(rawbytes) > current_index + deserialize_length`` |
#### Int or Uint: 8/16/24/32/64/256
Convert directly from bytes into integer utilising the number of bytes the same
size as the integer length. (e.g. ``int16 == 2 bytes``)
All integers are interpreted as **big endian**.
```python
byte_length = int_size / 8
new_index = current_index + int_size
return int.from_bytes(rawbytes[current_index:current_index+int_size], 'big'), new_index
```
#### Address
Return the 20 bytes.
```python
new_index = current_index + 20
return rawbytes[current_index:current_index+20], new_index
```
#### Hash32
Return the 32 bytes.
```python
new_index = current_index + 32
return rawbytes[current_index:current_index+32], new_index
```
#### Bytes
Get the length of the bytes, return the bytes.
```python
bytes_length = int.from_bytes(rawbytes[current_index:current_index+4], 'big')
new_index = current_index + 4 + bytes_lenth
return rawbytes[current_index+4:current_index+4+bytes_length], new_index
```
#### List
Deserailize each object in the list.
1. Get the length of the serialized list.
2. Loop through deseralizing each item in the list until you reach the
entire length of the list.
| Check type | code |
|:------------------------------------|:--------------------------------------|
| rawbytes has enough left for length | ``len(rawbytes) > current_index + 4`` |
```python
total_length = int.from_bytes(rawbytes[current_index:current_index+4], 'big')
new_index = current_index + 4 + total_length
item_index = current_index + 4
deserialized_list = []
while item_index < new_index:
object, item_index = deserialize(rawbytes, item_index, item_type)
deserialized_list.append(object)
return deserialized_list, new_index
```
## Technical Overview
The SimpleSerialize is a simple method for serializing objects for use in the
Ethereum beacon chain proposed by Vitalik Buterin. There are currently two
implementations denoting the functionality, the [Reference
Implementation](https://github.com/ethereum/beacon_chain/blob/master/ssz/ssz.py)
and the [Module](https://github.com/ethereum/research/tree/master/py_ssz) in
Ethereum research. It is being developed as a crate for the [**Rust programming
language**](https://www.rust-lang.org).
The crate will provide the functionality to serialize several types in
accordance with the spec and provide a serialized stream of bytes.
## Building
ssz currently builds on **rust v1.27.1**
### Installing Rust
The [**Rustup**](https://rustup.rs/) tool provides functionality to easily
manage rust on your local instance. It is a recommended method for installing
rust.
Installing on Linux or OSX:
```bash
curl https://sh.rustup.rs -sSf | sh
```
Installing on Windows:
* 32 Bit: [ https://win.rustup.rs/i686 ](https://win.rustup.rs/i686)
* 64 Bit: [ https://win.rustup.rs/x86_64 ](https://win.rustup.rs/x86_64)
## Dependencies
All dependencies are listed in the ``Cargo.toml`` file.
To build and install all related dependencies:
```bash
cargo build
```
### bytes v0.4.9
The `bytes` crate provides effective Byte Buffer implementations and
interfaces.
Documentation: [ https://docs.rs/bytes/0.4.9/bytes/ ](https://docs.rs/bytes/0.4.9/bytes/)
### ethereum-types
The `ethereum-types` provide primitives for types that are commonly used in the
ethereum protocol. This crate is provided by [Parity](https://www.parity.io/).
Github: [ https://github.com/paritytech/primitives ](https://github.com/paritytech/primitives)
---
## Interface
### Encodable
A type is **Encodable** if it has a valid ``ssz_append`` function. This is
used to ensure that the object/type can be serialized.
```rust
pub trait Encodable {
fn ssz_append(&self, s: &mut SszStream);
}
```
### Decodable
A type is **Decodable** if it has a valid ``ssz_decode`` function. This is
used to ensure the object is deserializable.
```rust
pub trait Decodable: Sized {
fn ssz_decode(bytes: &[u8], index: usize) -> Result<(Self, usize), DecodeError>;
}
```
### SszStream
The main implementation is the `SszStream` struct. The struct contains a
buffer of bytes, a Vector of `uint8`.
#### new()
Create a new, empty instance of the SszStream.
**Example**
```rust
let mut ssz = SszStream::new()
```
#### append<E>(&mut self, value: &E) -> &mut Self
Appends a value that can be encoded into the stream.
| Parameter | Description |
|:---------:|:-----------------------------------------|
| ``value`` | Encodable value to append to the stream. |
**Example**
```rust
ssz.append(&x)
```
#### append_encoded_val(&mut self, vec: &Vec<u8>)
Appends some ssz encoded bytes to the stream.
| Parameter | Description |
|:---------:|:----------------------------------|
| ``vec`` | A vector of serialized ssz bytes. |
**Example**
```rust
let mut a = [0, 1];
ssz.append_encoded_val(&a.to_vec());
```
#### append_vec<E>(&mut self, vec: &Vec<E>)
Appends some vector (list) of encodable values to the stream.
| Parameter | Description |
|:---------:|:----------------------------------------------|
| ``vec`` | Vector of Encodable objects to be serialized. |
**Example**
```rust
ssz.append_vec(attestations);
```
#### drain(self) -> Vec<u8>
Consumes the ssz stream and returns the buffer of bytes.
**Example**
```rust
ssz.drain()
```
### decode_ssz<T>(ssz_bytes: &[u8], index: usize) -> Result<(T, usize), DecodeError>
Decodes a single ssz serialized value of type `T`. Note: `T` must be decodable.
| Parameter | Description |
|:-------------:|:------------------------------------|
| ``ssz_bytes`` | Serialized list of bytes. |
| ``index`` | Starting index to deserialize from. |
**Returns**
| Return Value | Description |
|:-------------------:|:----------------------------------------------|
| ``Tuple(T, usize)`` | Returns the tuple of the type and next index. |
| ``DecodeError`` | Error if the decoding could not be performed. |
**Example**
```rust
let res: Result<(u16, usize), DecodeError> = decode_ssz(&encoded_ssz, 0);
```
### decode_ssz_list<T>(ssz_bytes: &[u8], index: usize) -> Result<(Vec<T>, usize), DecodeError>
Decodes a list of serialized values into a vector.
| Parameter | Description |
|:-------------:|:------------------------------------|
| ``ssz_bytes`` | Serialized list of bytes. |
| ``index`` | Starting index to deserialize from. |
**Returns**
| Return Value | Description |
|:------------------------:|:----------------------------------------------|
| ``Tuple(Vec<T>, usize)`` | Returns the tuple of the type and next index. |
| ``DecodeError`` | Error if the decoding could not be performed. |
**Example**
```rust
let decoded: Result<(Vec<usize>, usize), DecodeError> = decode_ssz_list( &encoded_ssz, 0);
```
### decode_length(bytes: &[u8], index: usize, length_bytes: usize) -> Result<usize, DecodeError>
Deserializes the "length" value in the serialized bytes from the index. The
length of bytes is given (usually 4 stated in the reference implementation) and
is often the value appended to the list infront of the actual serialized
object.
| Parameter | Description |
|:----------------:|:-------------------------------------------|
| ``bytes`` | Serialized list of bytes. |
| ``index`` | Starting index to deserialize from. |
| ``length_bytes`` | Number of bytes to deserialize into usize. |
**Returns**
| Return Value | Description |
|:---------------:|:-----------------------------------------------------------|
| ``usize`` | The length of the serialized object following this length. |
| ``DecodeError`` | Error if the decoding could not be performed. |
**Example**
```rust
let length_of_serialized: Result<usize, DecodeError> = decode_length(&encoded, 0, 4);
```
---
## Usage
### Serializing/Encoding
#### Rust
Create the `simpleserialize` stream that will produce the serialized objects.
```rust
let mut ssz = SszStream::new();
```
Encode the values that you need by using the ``append(..)`` method on the `SszStream`.
The **append** function is how the value gets serialized.
```rust
let x: u64 = 1 << 32;
ssz.append(&x);
```
To get the serialized byte vector use ``drain()`` on the `SszStream`.
```rust
ssz.drain()
```
**Example**
```rust
// 1 << 32 = 4294967296;
// As bytes it should equal: [0,0,0,1,0,0,0]
let x: u64 = 1 << 32;
// Create the new ssz stream
let mut ssz = SszStream::new();
// Serialize x
ssz.append(&x);
// Check that it is correct.
assert_eq!(ssz.drain(), vec![0,0,0,1,0,0,0]);
```
## Deserializing/Decoding
#### Rust
From the `simpleserialize` bytes, we are converting to the object.
```rust
let ssz = vec![0, 0, 8, 255, 255, 255, 255, 255, 255, 255, 255];
// Returns the result and the next index to decode.
let (result, index): (u64, usize) = decode_ssz(&ssz, 3).unwrap();
// Check for correctness
// 2**64-1 = 18446744073709551615
assert_eq!(result, 18446744073709551615);
// Index = 3 (initial index) + 8 (8 byte int) = 11
assert_eq!(index, 11);
```
Decoding a list of items:
```rust
// Encoded/Serialized list with junk numbers at the front
let serialized_list = vec![ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 32, 0, 0, 0,
0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0,
0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 15];
// Returns the result (Vector of usize) and the index of the next
let decoded: (Vec<usize>, usize) = decode_ssz_list(&serialized_list, 10).unwrap();
// Check for correctness
assert_eq!(decoded.0, vec![15,15,15,15]);
assert_eq!(decoded.1, 46);
```

View File

@@ -0,0 +1,209 @@
use super::{
LENGTH_BYTES,
};
#[derive(Debug, PartialEq)]
pub enum DecodeError {
TooShort,
TooLong,
}
pub trait Decodable: Sized {
fn ssz_decode(bytes: &[u8], index: usize) -> Result<(Self, usize), DecodeError>;
}
/// Decode the given bytes for the given type
///
/// The single ssz encoded value will be decoded as the given type at the
/// given index.
pub fn decode_ssz<T>(ssz_bytes: &[u8], index: usize)
-> Result<(T, usize), DecodeError>
where T: Decodable
{
if index >= ssz_bytes.len() {
return Err(DecodeError::TooShort)
}
T::ssz_decode(ssz_bytes, index)
}
/// Decode a vector (list) of encoded bytes.
///
/// Each element in the list will be decoded and placed into the vector.
pub fn decode_ssz_list<T>(ssz_bytes: &[u8], index: usize)
-> Result<(Vec<T>, usize), DecodeError>
where T: Decodable
{
if index + LENGTH_BYTES > ssz_bytes.len() {
return Err(DecodeError::TooShort);
};
// get the length
let serialized_length = match decode_length(ssz_bytes, index, LENGTH_BYTES) {
Err(v) => return Err(v),
Ok(v) => v,
};
let final_len: usize = index + LENGTH_BYTES + serialized_length;
if final_len > ssz_bytes.len() {
return Err(DecodeError::TooShort);
};
let mut tmp_index = index + LENGTH_BYTES;
let mut res_vec: Vec<T> = Vec::new();
while tmp_index < final_len {
match T::ssz_decode(ssz_bytes, tmp_index) {
Err(v) => return Err(v),
Ok(v) => {
tmp_index = v.1;
res_vec.push(v.0);
},
};
};
Ok((res_vec, final_len))
}
/// Given some number of bytes, interpret the first four
/// bytes as a 32-bit big-endian integer and return the
/// result.
pub fn decode_length(bytes: &[u8], index: usize, length_bytes: usize)
-> Result<usize, DecodeError>
{
if bytes.len() < length_bytes {
return Err(DecodeError::TooShort);
};
let mut len: usize = 0;
for i in index..index+length_bytes {
let offset = (index+length_bytes - i - 1) * 8;
len = ((bytes[i] as usize) << offset) | len;
};
Ok(len)
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::encode::encode_length;
#[test]
fn test_ssz_decode_length() {
let decoded = decode_length(
&vec![0, 0, 0, 1],
0,
LENGTH_BYTES);
assert_eq!(decoded.unwrap(), 1);
let decoded = decode_length(
&vec![0, 0, 1, 0],
0,
LENGTH_BYTES);
assert_eq!(decoded.unwrap(), 256);
let decoded = decode_length(
&vec![0, 0, 1, 255],
0,
LENGTH_BYTES);
assert_eq!(decoded.unwrap(), 511);
let decoded = decode_length(
&vec![255, 255, 255, 255],
0,
LENGTH_BYTES);
assert_eq!(decoded.unwrap(), 4294967295);
}
#[test]
fn test_encode_decode_length() {
let params: Vec<usize> = vec![
0, 1, 2, 3, 7, 8, 16,
2^8, 2^8 + 1,
2^16, 2^16 + 1,
2^24, 2^24 + 1,
2^32,
];
for i in params {
let decoded = decode_length(
&encode_length(i, LENGTH_BYTES),
0,
LENGTH_BYTES).unwrap();
assert_eq!(i, decoded);
}
}
#[test]
fn test_decode_ssz_list() {
// u16
let v: Vec<u16> = vec![10, 10, 10, 10];
let decoded: (Vec<u16>, usize) = decode_ssz_list(
&vec![0, 0, 0, 8, 0, 10, 0, 10, 0, 10, 0, 10],
0
).unwrap();
assert_eq!(decoded.0, v);
assert_eq!(decoded.1, 12);
// u32
let v: Vec<u32> = vec![10, 10, 10, 10];
let decoded: (Vec<u32>, usize) = decode_ssz_list(
&vec![
0, 0, 0, 16,
0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10
],
0
).unwrap();
assert_eq!(decoded.0, v);
assert_eq!(decoded.1, 20);
// u64
let v: Vec<u64> = vec![10,10,10,10];
let decoded: (Vec<u64>, usize) = decode_ssz_list(
&vec![0, 0, 0, 32,
0, 0, 0, 0, 0, 0, 0, 10,
0, 0, 0, 0, 0, 0, 0, 10,
0, 0, 0, 0, 0, 0, 0, 10,
0, 0, 0, 0, 0, 0, 0, 10,
],
0
).unwrap();
assert_eq!(decoded.0, v);
assert_eq!(decoded.1, 36);
// Check that it can accept index
let v: Vec<usize> = vec![15,15,15,15];
let decoded: (Vec<usize>, usize) = decode_ssz_list(
&vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
0, 0, 0, 32,
0, 0, 0, 0, 0, 0, 0, 15,
0, 0, 0, 0, 0, 0, 0, 15,
0, 0, 0, 0, 0, 0, 0, 15,
0, 0, 0, 0, 0, 0, 0, 15,
],
10
).unwrap();
assert_eq!(decoded.0, v);
assert_eq!(decoded.1, 46);
// Check that length > bytes throws error
let decoded: Result<(Vec<usize>, usize), DecodeError> = decode_ssz_list(
&vec![0, 0, 0, 32,
0, 0, 0, 0, 0, 0, 0, 15,
],
0
);
assert_eq!(decoded, Err(DecodeError::TooShort));
// Check that incorrect index throws error
let decoded: Result<(Vec<usize>, usize), DecodeError> = decode_ssz_list(
&vec![
0, 0, 0, 0, 0, 0, 0, 15,
],
16
);
assert_eq!(decoded, Err(DecodeError::TooShort));
}
}

View File

@@ -0,0 +1,145 @@
use super::{
LENGTH_BYTES,
MAX_LIST_SIZE,
};
#[derive(Debug)]
pub enum EncodeError {
ListTooLong,
}
pub trait Encodable {
fn ssz_append(&self, s: &mut SszStream);
}
/// Provides a buffer for appending ssz-encodable values.
///
/// Use the `append()` fn to add a value to a list, then use
/// the `drain()` method to consume the struct and return the
/// ssz encoded bytes.
pub struct SszStream {
buffer: Vec<u8>
}
impl SszStream {
/// Create a new, empty stream for writing ssz values.
pub fn new() -> Self {
SszStream {
buffer: Vec::new()
}
}
/// Append some ssz encodable value to the stream.
pub fn append<E>(&mut self, value: &E) -> &mut Self
where E: Encodable
{
value.ssz_append(self);
self
}
/// Append some ssz encoded bytes to the stream.
///
/// The length of the supplied bytes will be concatenated
/// to the stream before the supplied bytes.
pub fn append_encoded_val(&mut self, vec: &Vec<u8>) {
self.buffer.extend_from_slice(
&encode_length(vec.len(),
LENGTH_BYTES));
self.buffer.extend_from_slice(&vec);
}
/// Append some ssz encoded bytes to the stream without calculating length
///
/// The raw bytes will be concatenated to the stream.
pub fn append_encoded_raw(&mut self, vec: &Vec<u8>) {
self.buffer.extend_from_slice(&vec);
}
/// Append some vector (list) of encodable values to the stream.
///
/// The length of the list will be concatenated to the stream, then
/// each item in the vector will be encoded and concatenated.
pub fn append_vec<E>(&mut self, vec: &Vec<E>)
where E: Encodable
{
let mut list_stream = SszStream::new();
for item in vec {
item.ssz_append(&mut list_stream);
}
self.append_encoded_val(&list_stream.drain());
}
/// Consume the stream and return the underlying bytes.
pub fn drain(self) -> Vec<u8> {
self.buffer
}
}
/// Encode some length into a ssz size prefix.
///
/// The ssz size prefix is 4 bytes, which is treated as a continuious
/// 32bit big-endian integer.
pub fn encode_length(len: usize, length_bytes: usize) -> Vec<u8> {
assert!(length_bytes > 0); // For sanity
assert!((len as usize) < 2usize.pow(length_bytes as u32 * 8));
let mut header: Vec<u8> = vec![0; length_bytes];
for i in 0..length_bytes {
let offset = (length_bytes - i - 1) * 8;
header[i] = ((len >> offset) & 0xff) as u8;
};
header
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn test_encode_length_0_bytes_panic() {
encode_length(0, 0);
}
#[test]
fn test_encode_length_4_bytes() {
assert_eq!(
encode_length(0, LENGTH_BYTES),
vec![0; 4]
);
assert_eq!(
encode_length(1, LENGTH_BYTES),
vec![0, 0, 0, 1]
);
assert_eq!(
encode_length(255, LENGTH_BYTES),
vec![0, 0, 0, 255]
);
assert_eq!(
encode_length(256, LENGTH_BYTES),
vec![0, 0, 1, 0]
);
assert_eq!(
encode_length(4294967295, LENGTH_BYTES), // 2^(3*8) - 1
vec![255, 255, 255, 255]
);
}
#[test]
#[should_panic]
fn test_encode_length_4_bytes_panic() {
encode_length(4294967296, LENGTH_BYTES); // 2^(3*8)
}
#[test]
fn test_encode_list() {
let test_vec: Vec<u16> = vec![256; 12];
let mut stream = SszStream::new();
stream.append_vec(&test_vec);
let ssz = stream.drain();
assert_eq!(ssz.len(), 4 + (12 * 2));
assert_eq!(ssz[0..4], *vec![0, 0, 0, 24]);
assert_eq!(ssz[4..6], *vec![1, 0]);
}
}

View File

@@ -0,0 +1,231 @@
use super::ethereum_types::H256;
use super::{
DecodeError,
Decodable,
};
macro_rules! impl_decodable_for_uint {
($type: ident, $bit_size: expr) => {
impl Decodable for $type {
fn ssz_decode(bytes: &[u8], index: usize)
-> Result<(Self, usize), DecodeError>
{
assert!((0 < $bit_size) &
($bit_size <= 64) &
($bit_size % 8 == 0));
let max_bytes = $bit_size / 8;
if bytes.len() >= (index + max_bytes) {
let end_bytes = index + max_bytes;
let mut result: $type = 0;
for i in index..end_bytes {
let offset = ((index + max_bytes) - i - 1) * 8;
result = ((bytes[i] as $type) << offset) | result;
};
Ok((result, end_bytes))
} else {
Err(DecodeError::TooShort)
}
}
}
}
}
impl_decodable_for_uint!(u16, 16);
impl_decodable_for_uint!(u32, 32);
impl_decodable_for_uint!(u64, 64);
impl_decodable_for_uint!(usize, 64);
impl Decodable for u8 {
fn ssz_decode(bytes: &[u8], index: usize)
-> Result<(Self, usize), DecodeError>
{
if index >= bytes.len() {
Err(DecodeError::TooShort)
} else {
Ok((bytes[index], index + 1))
}
}
}
impl Decodable for H256 {
fn ssz_decode(bytes: &[u8], index: usize)
-> Result<(Self, usize), DecodeError>
{
if bytes.len() < 32 {
return Err(DecodeError::TooShort)
}
else if bytes.len() - 32 < index {
return Err(DecodeError::TooShort)
}
else {
return Ok((H256::from(&bytes[index..(index + 32)]),
index + 32));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::{
DecodeError,
decode_ssz,
};
#[test]
fn test_ssz_decode_h256() {
/*
* Input is exact length
*/
let input = vec![42_u8; 32];
let (decoded, i) = H256::ssz_decode(&input, 0).unwrap();
assert_eq!(decoded.to_vec(), input);
assert_eq!(i, 32);
/*
* Input is too long
*/
let mut input = vec![42_u8; 32];
input.push(12);
let (decoded, i) = H256::ssz_decode(&input, 0).unwrap();
assert_eq!(decoded.to_vec()[..], input[0..32]);
assert_eq!(i, 32);
/*
* Input is too short
*/
let input = vec![42_u8; 31];
let res = H256::ssz_decode(&input, 0);
assert_eq!(res, Err(DecodeError::TooShort));
}
#[test]
fn test_ssz_decode_u16() {
let ssz = vec![0, 0];
let (result, index): (u16, usize) = decode_ssz(&ssz, 0).unwrap();
assert_eq!(result, 0);
assert_eq!(index, 2);
let ssz = vec![0, 16];
let (result, index): (u16, usize) = decode_ssz(&ssz, 0).unwrap();
assert_eq!(result, 16);
assert_eq!(index, 2);
let ssz = vec![1, 0];
let (result, index): (u16, usize) = decode_ssz(&ssz, 0).unwrap();
assert_eq!(result, 256);
assert_eq!(index, 2);
let ssz = vec![255, 255];
let (result, index): (u16, usize) = decode_ssz(&ssz, 0).unwrap();
assert_eq!(index, 2);
assert_eq!(result, 65535);
let ssz = vec![1];
let result: Result<(u16, usize), DecodeError> =
decode_ssz(&ssz, 0);
assert_eq!(result, Err(DecodeError::TooShort));
}
#[test]
fn test_ssz_decode_u32() {
let ssz = vec![0, 0, 0, 0];
let (result, index): (u32, usize) = decode_ssz(&ssz, 0).unwrap();
assert_eq!(result, 0);
assert_eq!(index, 4);
let ssz = vec![0, 0, 1, 0];
let (result, index): (u32, usize) = decode_ssz(&ssz, 0).unwrap();
assert_eq!(index, 4);
assert_eq!(result, 256);
let ssz = vec![255, 255, 255, 0, 0, 1, 0];
let (result, index): (u32, usize) = decode_ssz(&ssz, 3).unwrap();
assert_eq!(index, 7);
assert_eq!(result, 256);
let ssz = vec![0,200, 1, 0];
let (result, index): (u32, usize) = decode_ssz(&ssz, 0).unwrap();
assert_eq!(index, 4);
assert_eq!(result, 13107456);
let ssz = vec![255, 255, 255, 255];
let (result, index): (u32, usize) = decode_ssz(&ssz, 0).unwrap();
assert_eq!(index, 4);
assert_eq!(result, 4294967295);
let ssz = vec![0, 0, 1];
let result: Result<(u32, usize), DecodeError> =
decode_ssz(&ssz, 0);
assert_eq!(result, Err(DecodeError::TooShort));
}
#[test]
fn test_ssz_decode_u64() {
let ssz = vec![0, 0, 0, 0, 0, 0, 0, 0];
let (result, index): (u64, usize) = decode_ssz(&ssz, 0).unwrap();
assert_eq!(index, 8);
assert_eq!(result, 0);
let ssz = vec![255, 255, 255, 255, 255, 255, 255, 255];
let (result, index): (u64, usize) = decode_ssz(&ssz, 0).unwrap();
assert_eq!(index, 8);
assert_eq!(result, 18446744073709551615);
let ssz = vec![0, 0, 8, 255, 0, 0, 0, 0, 0, 0, 0];
let (result, index): (u64, usize) = decode_ssz(&ssz, 3).unwrap();
assert_eq!(index, 11);
assert_eq!(result, 18374686479671623680);
let ssz = vec![0,0,0,0,0,0,0];
let result: Result<(u64, usize), DecodeError> =
decode_ssz(&ssz, 0);
assert_eq!(result, Err(DecodeError::TooShort));
}
#[test]
fn test_ssz_decode_usize() {
let ssz = vec![0, 0, 0, 0, 0, 0, 0, 0];
let (result, index): (usize, usize) = decode_ssz(&ssz, 0).unwrap();
assert_eq!(index, 8);
assert_eq!(result, 0);
let ssz = vec![0, 0, 8, 255, 255, 255, 255, 255, 255, 255, 255];
let (result, index): (usize, usize) = decode_ssz(&ssz, 3).unwrap();
assert_eq!(index, 11);
assert_eq!(result, 18446744073709551615);
let ssz = vec![255, 255, 255, 255, 255, 255, 255, 255, 255];
let (result, index): (usize, usize) = decode_ssz(&ssz, 0).unwrap();
assert_eq!(index, 8);
assert_eq!(result, 18446744073709551615);
let ssz = vec![0, 0, 0, 0, 0, 0, 1];
let result: Result<(usize, usize), DecodeError> =
decode_ssz(&ssz, 0);
assert_eq!(result, Err(DecodeError::TooShort));
}
#[test]
fn test_decode_ssz_bounds() {
let err: Result<(u16, usize), DecodeError> = decode_ssz(
&vec![1],
2
);
assert_eq!(err, Err(DecodeError::TooShort));
let err: Result<(u16,usize), DecodeError> = decode_ssz(
&vec![0, 0, 0, 0],
3
);
assert_eq!(err, Err(DecodeError::TooShort));
let result: u16 = decode_ssz(
&vec![0,0,0,0,1],
3
).unwrap().0;
assert_eq!(result, 1);
}
}

View File

@@ -0,0 +1,188 @@
extern crate bytes;
use super::{
Encodable,
SszStream
};
use super::ethereum_types::H256;
use self::bytes::{ BytesMut, BufMut };
/*
* Note: there is a "to_bytes" function for integers
* in Rust nightly. When it is in stable, we should
* use it instead.
*/
macro_rules! impl_encodable_for_uint {
($type: ident, $bit_size: expr) => {
impl Encodable for $type {
fn ssz_append(&self, s: &mut SszStream)
{
// Ensure bit size is valid
assert!((0 < $bit_size) &&
($bit_size % 8 == 0) &&
(2_u128.pow($bit_size) > *self as u128));
// Serialize to bytes
let mut buf = BytesMut::with_capacity($bit_size/8);
// Match bit size with encoding
match $bit_size {
8 => buf.put_u8(*self as u8),
16 => buf.put_u16_be(*self as u16),
32 => buf.put_u32_be(*self as u32),
64 => buf.put_u64_be(*self as u64),
_ => { ; }
}
// Append bytes to the SszStream
s.append_encoded_raw(&mut buf.to_vec());
}
}
}
}
impl_encodable_for_uint!(u8, 8);
impl_encodable_for_uint!(u16, 16);
impl_encodable_for_uint!(u32, 32);
impl_encodable_for_uint!(u64, 64);
impl_encodable_for_uint!(usize, 64);
impl Encodable for H256 {
fn ssz_append(&self, s: &mut SszStream) {
s.append_encoded_raw(&self.to_vec());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ssz_encode_h256() {
let h = H256::zero();
let mut ssz = SszStream::new();
ssz.append(&h);
assert_eq!(ssz.drain(), vec![0; 32]);
}
#[test]
fn test_ssz_encode_u8() {
let x: u8 = 0;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0]);
let x: u8 = 1;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![1]);
let x: u8 = 100;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![100]);
let x: u8 = 255;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![255]);
}
#[test]
fn test_ssz_encode_u16() {
let x: u16 = 1;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0, 1]);
let x: u16 = 100;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0, 100]);
let x: u16 = 1 << 8;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![1, 0]);
let x: u16 = 65535;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![255, 255]);
}
#[test]
fn test_ssz_encode_u32() {
let x: u32 = 1;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0, 0, 0, 1]);
let x: u32 = 100;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0, 0, 0, 100]);
let x: u32 = 1 << 16;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0, 1, 0, 0]);
let x: u32 = 1 << 24;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![1, 0, 0, 0]);
let x: u32 = !0;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![255, 255, 255, 255]);
}
#[test]
fn test_ssz_encode_u64() {
let x: u64 = 1;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0, 0, 0, 0, 0, 0, 0, 1]);
let x: u64 = 100;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0, 0, 0, 0, 0, 0, 0, 100]);
let x: u64 = 1 << 32;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0, 0, 0, 1, 0, 0, 0, 0]);
let x: u64 = !0;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![255, 255, 255, 255, 255, 255, 255, 255]);
}
#[test]
fn test_ssz_encode_usize() {
let x: usize = 1;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0, 0, 0, 0, 0, 0, 0, 1]);
let x: usize = 100;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0, 0, 0, 0, 0, 0, 0, 100]);
let x: usize = 1 << 32;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![0, 0, 0, 1, 0, 0, 0, 0]);
let x: usize = !0;
let mut ssz = SszStream::new();
ssz.append(&x);
assert_eq!(ssz.drain(), vec![255, 255, 255, 255, 255, 255, 255, 255]);
}
}

View File

@@ -0,0 +1,31 @@
/*
* This is a WIP of implementing an alternative
* serialization strategy. It attempts to follow Vitalik's
* "simpleserialize" format here:
* https://github.com/ethereum/beacon_chain/blob/master/beacon_chain/utils/simpleserialize.py
*
* This implementation is not final and would almost certainly
* have issues.
*/
extern crate bytes;
extern crate ethereum_types;
pub mod decode;
mod encode;
mod impl_encode;
mod impl_decode;
pub use decode::{
Decodable,
DecodeError,
decode_ssz,
decode_ssz_list,
};
pub use encode::{
Encodable,
SszStream,
};
pub const LENGTH_BYTES: usize = 4;
pub const MAX_LIST_SIZE : usize = 1 << (4 * 8);

View File

@@ -0,0 +1,10 @@
[package]
name = "ssz_helpers"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
[dependencies]
bls = { path = "../bls" }
hashing = { path = "../hashing" }
types = { path = "../../types" }
ssz = { path = "../ssz" }

View File

@@ -0,0 +1,139 @@
use super::types::attestation_record::MIN_SSZ_ATTESTION_RECORD_LENGTH as MIN_LENGTH;
use super::ssz::LENGTH_BYTES;
use super::ssz::decode::decode_length;
#[derive(Debug, PartialEq)]
pub enum AttestationSplitError {
TooShort,
}
/// Given some ssz slice, find the bounds of each serialized AttestationRecord and return a vec of
/// slices point to each.
pub fn split_all_attestations<'a>(full_ssz: &'a [u8], index: usize)
-> Result<Vec<&'a [u8]>, AttestationSplitError>
{
let mut v = vec![];
let mut index = index;
while index < full_ssz.len() - 1 {
let (slice, i) = split_one_attestation(full_ssz, index)?;
v.push(slice);
index = i;
}
Ok(v)
}
/// Given some ssz slice, find the bounds of one serialized AttestationRecord
/// and return a slice pointing to that.
pub fn split_one_attestation(full_ssz: &[u8], index: usize)
-> Result<(&[u8], usize), AttestationSplitError>
{
if full_ssz.len() < MIN_LENGTH {
return Err(AttestationSplitError::TooShort);
}
let hashes_len = decode_length(full_ssz, index + 10, LENGTH_BYTES)
.map_err(|_| AttestationSplitError::TooShort)?;
let bitfield_len = decode_length(
full_ssz, index + hashes_len + 46,
LENGTH_BYTES)
.map_err(|_| AttestationSplitError::TooShort)?;
// Subtract one because the min length assumes 1 byte of bitfield
let len = MIN_LENGTH - 1
+ hashes_len
+ bitfield_len;
if full_ssz.len() < index + len {
return Err(AttestationSplitError::TooShort);
}
Ok((&full_ssz[index..(index + len)], index + len))
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::types::{
AttestationRecord,
Hash256,
Bitfield,
};
use super::super::bls::AggregateSignature;
use super::super::ssz::{
SszStream,
Decodable,
};
fn get_two_records() -> Vec<AttestationRecord> {
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(&vec![17; 42][..]),
justified_slot: 19,
justified_block_hash: Hash256::from(&vec![15; 32][..]),
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(&vec![19; 42][..]),
justified_slot: 15,
justified_block_hash: Hash256::from(&vec![17; 32][..]),
aggregate_sig: AggregateSignature::new(),
};
vec![a, b]
}
#[test]
fn test_attestation_ssz_split() {
let ars = get_two_records();
let a = ars[0].clone();
let b = ars[1].clone();
/*
* Test split one
*/
let mut ssz_stream = SszStream::new();
ssz_stream.append(&a);
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();
assert_eq!(a, decoded_a);
/*
* Test split two
*/
let mut ssz_stream = SszStream::new();
ssz_stream.append(&a);
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();
assert_eq!(a, decoded_a);
assert_eq!(b, decoded_b);
/*
* Test split two with shortened ssz
*/
let mut ssz_stream = SszStream::new();
ssz_stream.append(&a);
ssz_stream.append(&b);
let ssz = ssz_stream.drain();
let ssz = &ssz[0..ssz.len() - 1];
assert!(split_all_attestations(&ssz, 0).is_err());
}
}

View File

@@ -0,0 +1,7 @@
extern crate bls;
extern crate hashing;
extern crate types;
extern crate ssz;
pub mod attestation_ssz_splitter;
pub mod ssz_block;

View File

@@ -0,0 +1,358 @@
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<Self, SszBlockError>
{
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<u8> {
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<u8> {
let mut ssz_stream = SszStream::new();
ssz_stream.append(b);
ssz_stream.drain()
}
fn get_attestation_record_ssz(ar: &AttestationRecord) -> Vec<u8> {
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()[..]);
}
}