Merge new beacon block, Rust 2018

This commit is contained in:
Paul Hauner 2018-12-25 19:00:41 +11:00
commit b978db23fc
No known key found for this signature in database
GPG Key ID: 303E4494BB28068C
64 changed files with 1232 additions and 1260 deletions

View File

@ -2,6 +2,7 @@
name = "lighthouse" name = "lighthouse"
version = "0.0.1" version = "0.0.1"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
blake2-rfc = "0.2.18" blake2-rfc = "0.2.18"
@ -37,16 +38,13 @@ members = [
"beacon_chain/genesis", "beacon_chain/genesis",
"beacon_chain/naive_fork_choice", "beacon_chain/naive_fork_choice",
"beacon_chain/spec", "beacon_chain/spec",
"beacon_chain/state-transition",
"beacon_chain/types", "beacon_chain/types",
"beacon_chain/utils/active-validators",
"beacon_chain/utils/bls", "beacon_chain/utils/bls",
"beacon_chain/utils/boolean-bitfield", "beacon_chain/utils/boolean-bitfield",
"beacon_chain/utils/hashing", "beacon_chain/utils/hashing",
"beacon_chain/utils/honey-badger-split", "beacon_chain/utils/honey-badger-split",
"beacon_chain/utils/slot-clock", "beacon_chain/utils/slot-clock",
"beacon_chain/utils/ssz", "beacon_chain/utils/ssz",
"beacon_chain/utils/ssz_helpers",
"beacon_chain/utils/vec_shuffle", "beacon_chain/utils/vec_shuffle",
"beacon_chain/validator_change", "beacon_chain/validator_change",
"beacon_chain/validator_induction", "beacon_chain/validator_induction",

View File

@ -264,3 +264,9 @@ proof-of-concept implementation in Python is available at
Presently, the specification focuses almost exclusively on the beacon chain, Presently, the specification focuses almost exclusively on the beacon chain,
as it is the focus of current development efforts. Progress on shard chain as it is the focus of current development efforts. Progress on shard chain
specification will soon follow. specification will soon follow.
# Donations
If you support the cause, we could certainly use donations to help fund development:
`0x25c4a76E7d118705e7Ea2e9b7d8C59930d8aCD3b`

View File

@ -2,11 +2,11 @@
name = "attestation_validation" name = "attestation_validation"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
bls = { path = "../utils/bls" } bls = { path = "../utils/bls" }
db = { path = "../../lighthouse/db" } db = { path = "../../lighthouse/db" }
hashing = { path = "../utils/hashing" } hashing = { path = "../utils/hashing" }
ssz = { path = "../utils/ssz" } ssz = { path = "../utils/ssz" }
ssz_helpers = { path = "../utils/ssz_helpers" }
types = { path = "../types" } types = { path = "../types" }

View File

@ -2,7 +2,6 @@ extern crate bls;
extern crate db; extern crate db;
extern crate hashing; extern crate hashing;
extern crate ssz; extern crate ssz;
extern crate ssz_helpers;
extern crate types; extern crate types;
#[macro_use] #[macro_use]
@ -15,9 +14,9 @@ mod justified_slot;
mod shard_block; mod shard_block;
mod signature; mod signature;
pub use enums::{Invalid, Outcome, Error}; pub use crate::enums::{Invalid, Outcome, Error};
pub use block_inclusion::validate_attestation_for_block; pub use crate::block_inclusion::validate_attestation_for_block;
pub use justified_slot::validate_attestation_justified_slot; pub use crate::justified_slot::validate_attestation_justified_slot;
pub use justified_block::validate_attestation_justified_block_hash; pub use crate::justified_block::validate_attestation_justified_block_hash;
pub use signature::validate_attestation_signature; pub use crate::signature::validate_attestation_signature;
pub use shard_block::validate_attestation_data_shard_block_hash; pub use crate::shard_block::validate_attestation_data_shard_block_hash;

View File

@ -2,6 +2,7 @@
name = "chain" name = "chain"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
bls = { path = "../utils/bls" } bls = { path = "../utils/bls" }
@ -10,8 +11,6 @@ genesis = { path = "../genesis" }
naive_fork_choice = { path = "../naive_fork_choice" } naive_fork_choice = { path = "../naive_fork_choice" }
spec = { path = "../spec" } spec = { path = "../spec" }
ssz = { path = "../utils/ssz" } ssz = { path = "../utils/ssz" }
ssz_helpers = { path = "../utils/ssz_helpers" }
state-transition = { path = "../state-transition" }
types = { path = "../types" } types = { path = "../types" }
validator_induction = { path = "../validator_induction" } validator_induction = { path = "../validator_induction" }
validator_shuffling = { path = "../validator_shuffling" } validator_shuffling = { path = "../validator_shuffling" }

View File

@ -3,8 +3,6 @@ extern crate naive_fork_choice;
extern crate genesis; extern crate genesis;
extern crate spec; extern crate spec;
extern crate ssz; extern crate ssz;
extern crate ssz_helpers;
extern crate state_transition;
extern crate types; extern crate types;
extern crate validator_induction; extern crate validator_induction;
extern crate validator_shuffling; extern crate validator_shuffling;
@ -12,15 +10,14 @@ extern crate validator_shuffling;
mod block_processing; mod block_processing;
mod maps; mod maps;
mod stores; mod stores;
mod transition;
use db::ClientDB; use db::ClientDB;
use crate::maps::{generate_attester_and_proposer_maps, AttesterAndProposerMapError};
use crate::stores::BeaconChainStore;
use genesis::{genesis_beacon_state, GenesisError}; use genesis::{genesis_beacon_state, GenesisError};
use maps::{generate_attester_and_proposer_maps, AttesterAndProposerMapError};
use spec::ChainSpec; use spec::ChainSpec;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use stores::BeaconChainStore;
use types::{AttesterMap, BeaconState, Hash256, ProposerMap}; use types::{AttesterMap, BeaconState, Hash256, ProposerMap};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]

View File

@ -2,6 +2,7 @@
name = "naive_fork_choice" name = "naive_fork_choice"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
db = { path = "../../lighthouse/db" } db = { path = "../../lighthouse/db" }

View File

@ -2,6 +2,7 @@
name = "spec" name = "spec"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
bls = { path = "../utils/bls" } bls = { path = "../utils/bls" }

View File

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

View File

@ -1,194 +0,0 @@
extern crate types;
use types::{ActiveState, BeaconBlock, Hash256};
#[derive(Debug, PartialEq)]
pub enum StateTransitionError {
BlockSlotBeforeRecalcSlot,
InvalidParentHashes,
DBError(String),
}
pub fn extend_active_state(
act_state: &ActiveState,
block: &BeaconBlock,
block_hash: &Hash256,
) -> Result<ActiveState, StateTransitionError> {
/*
* Extend the pending attestations in the active state with the new attestations included
* in the block.
*
* Using the concat method to avoid reallocations.
*/
let pending_attestations =
[&act_state.pending_attestations[..], &block.attestations[..]].concat();
/*
* Extend the pending specials in the active state with the new specials included in the
* block.
*
* Using the concat method to avoid reallocations.
*/
let pending_specials = [&act_state.pending_specials[..], &block.specials[..]].concat();
/*
* Update the active state recent_block_hashes:
*
* - Drop the hash from the earliest position.
* - Push the block_hash into the latest position.
*
* Using the concat method to avoid reallocations.
*/
let (_first_hash, last_hashes) = act_state
.recent_block_hashes
.split_first()
.ok_or(StateTransitionError::InvalidParentHashes)?;
let new_hash = &[block_hash.clone()];
let recent_block_hashes = [&last_hashes, &new_hash[..]].concat();
/*
* The new `randao_mix` is set to the XOR of the previous active state randao mix and the
* randao reveal in this block.
*/
let randao_mix = act_state.randao_mix ^ block.randao_reveal;
Ok(ActiveState {
pending_attestations,
pending_specials,
recent_block_hashes,
randao_mix,
})
}
#[cfg(test)]
mod tests {
use super::*;
use types::SpecialRecord;
fn empty_active_state() -> ActiveState {
ActiveState {
pending_attestations: vec![],
pending_specials: vec![],
recent_block_hashes: vec![],
randao_mix: Hash256::zero(),
}
}
#[test]
fn test_extend_active_state_minimal() {
let mut act_state = empty_active_state();
let parent_hash = Hash256::from("parent_hash".as_bytes());
act_state.recent_block_hashes = vec![parent_hash];
let block = BeaconBlock::zero();
let block_hash = Hash256::from("block_hash".as_bytes());
let new_act_state = extend_active_state(&act_state, &block, &block_hash).unwrap();
assert_eq!(new_act_state.pending_attestations, vec![]);
assert_eq!(new_act_state.pending_specials, vec![]);
assert_eq!(new_act_state.recent_block_hashes, vec![block_hash]);
assert_eq!(new_act_state.randao_mix, Hash256::zero());
}
#[test]
fn test_extend_active_state_specials() {
let mut act_state = empty_active_state();
let parent_hash = Hash256::from("parent_hash".as_bytes());
act_state.recent_block_hashes = vec![parent_hash];
let mut block = BeaconBlock::zero();
let special = SpecialRecord {
kind: 0,
data: vec![42, 42],
};
block.specials.push(special.clone());
let block_hash = Hash256::from("block_hash".as_bytes());
let new_act_state = extend_active_state(&act_state, &block, &block_hash).unwrap();
assert_eq!(new_act_state.pending_attestations, vec![]);
assert_eq!(new_act_state.pending_specials, vec![special.clone()]);
assert_eq!(new_act_state.recent_block_hashes, vec![block_hash]);
assert_eq!(new_act_state.randao_mix, Hash256::zero());
let new_new_act_state = extend_active_state(&new_act_state, &block, &block_hash).unwrap();
assert_eq!(new_new_act_state.pending_attestations, vec![]);
assert_eq!(
new_new_act_state.pending_specials,
vec![special.clone(), special.clone()]
);
assert_eq!(new_new_act_state.recent_block_hashes, vec![block_hash]);
assert_eq!(new_new_act_state.randao_mix, Hash256::zero());
}
#[test]
fn test_extend_active_state_empty_recent_block_hashes() {
let act_state = empty_active_state();
let block = BeaconBlock::zero();
let block_hash = Hash256::from("block_hash".as_bytes());
let result = extend_active_state(&act_state, &block, &block_hash);
assert_eq!(result, Err(StateTransitionError::InvalidParentHashes));
}
#[test]
fn test_extend_active_recent_block_hashes() {
let mut act_state = empty_active_state();
let parent_hashes = vec![
Hash256::from("one".as_bytes()),
Hash256::from("two".as_bytes()),
Hash256::from("three".as_bytes()),
];
act_state.recent_block_hashes = parent_hashes.clone();
let block = BeaconBlock::zero();
let block_hash = Hash256::from("four".as_bytes());
let new_act_state = extend_active_state(&act_state, &block, &block_hash).unwrap();
assert_eq!(new_act_state.pending_attestations, vec![]);
assert_eq!(new_act_state.pending_specials, vec![]);
assert_eq!(
new_act_state.recent_block_hashes,
vec![
Hash256::from("two".as_bytes()),
Hash256::from("three".as_bytes()),
Hash256::from("four".as_bytes()),
]
);
assert_eq!(new_act_state.randao_mix, Hash256::zero());
}
#[test]
fn test_extend_active_state_randao() {
let mut act_state = empty_active_state();
let parent_hash = Hash256::from("parent_hash".as_bytes());
act_state.recent_block_hashes = vec![parent_hash];
act_state.randao_mix = Hash256::from(0b00000000);
let mut block = BeaconBlock::zero();
block.randao_reveal = Hash256::from(0b00000001);
let block_hash = Hash256::from("block_hash".as_bytes());
let new_act_state = extend_active_state(&act_state, &block, &block_hash).unwrap();
assert_eq!(new_act_state.pending_attestations, vec![]);
assert_eq!(new_act_state.pending_specials, vec![]);
assert_eq!(new_act_state.recent_block_hashes, vec![block_hash]);
assert_eq!(new_act_state.randao_mix, Hash256::from(0b00000001));
}
}

View File

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

View File

@ -1,14 +1,8 @@
use super::attestation_data::SSZ_ATTESTION_DATA_LENGTH; use super::bls::AggregateSignature;
use super::bls::{AggregateSignature, BLS_AGG_SIG_BYTE_SIZE}; use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::ssz::{decode_ssz_list, Decodable, DecodeError, Encodable, SszStream, LENGTH_BYTES};
use super::{AttestationData, Bitfield}; use super::{AttestationData, Bitfield};
use crate::test_utils::TestRandom;
pub const MIN_SSZ_ATTESTION_RECORD_LENGTH: usize = { use rand::RngCore;
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)] #[derive(Debug, Clone, PartialEq)]
pub struct Attestation { pub struct Attestation {
@ -23,7 +17,7 @@ impl Encodable for Attestation {
s.append(&self.data); s.append(&self.data);
s.append(&self.participation_bitfield); s.append(&self.participation_bitfield);
s.append(&self.custody_bitfield); s.append(&self.custody_bitfield);
s.append_vec(&self.aggregate_sig.as_bytes()); s.append(&self.aggregate_sig);
} }
} }
@ -32,9 +26,7 @@ impl Decodable for Attestation {
let (data, i) = AttestationData::ssz_decode(bytes, i)?; let (data, i) = AttestationData::ssz_decode(bytes, i)?;
let (participation_bitfield, i) = Bitfield::ssz_decode(bytes, i)?; let (participation_bitfield, i) = Bitfield::ssz_decode(bytes, i)?;
let (custody_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, i) = AggregateSignature::ssz_decode(bytes, i)?;
let aggregate_sig =
AggregateSignature::from_bytes(&agg_sig_bytes).map_err(|_| DecodeError::TooShort)?; // also could be TooLong
let attestation_record = Self { let attestation_record = Self {
data, data,
@ -57,30 +49,31 @@ impl Attestation {
} }
} }
impl<T: RngCore> TestRandom<T> for Attestation {
fn random_for_test(rng: &mut T) -> Self {
Self {
data: <_>::random_for_test(rng),
participation_bitfield: <_>::random_for_test(rng),
custody_bitfield: <_>::random_for_test(rng),
aggregate_sig: <_>::random_for_test(rng),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::ssz::ssz_encode; use super::super::ssz::ssz_encode;
use super::*; use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
#[test] #[test]
pub fn test_attestation_record_min_ssz_length() { pub fn test_ssz_round_trip() {
let ar = Attestation::zero(); let mut rng = XorShiftRng::from_seed([42; 16]);
let ssz = ssz_encode(&ar); let original = Attestation::random_for_test(&mut rng);
assert_eq!(ssz.len(), MIN_SSZ_ATTESTION_RECORD_LENGTH); let bytes = ssz_encode(&original);
} let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
#[test]
pub fn test_attestation_record_ssz_round_trip() {
let original = Attestation {
data: AttestationData::zero(),
participation_bitfield: Bitfield::from_bytes(&vec![17; 42][..]),
custody_bitfield: Bitfield::from_bytes(&vec![18; 12][..]),
aggregate_sig: AggregateSignature::new(),
};
let ssz = ssz_encode(&original);
let (decoded, _) = Attestation::ssz_decode(&ssz, 0).unwrap();
assert_eq!(original, decoded); assert_eq!(original, decoded);
} }

View File

@ -1,5 +1,7 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream}; use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::Hash256; use super::Hash256;
use crate::test_utils::TestRandom;
use rand::RngCore;
pub const SSZ_ATTESTION_DATA_LENGTH: usize = { pub const SSZ_ATTESTION_DATA_LENGTH: usize = {
8 + // slot 8 + // slot
@ -83,27 +85,35 @@ impl Decodable for AttestationData {
} }
} }
impl<T: RngCore> TestRandom<T> for AttestationData {
fn random_for_test(rng: &mut T) -> Self {
Self {
slot: <_>::random_for_test(rng),
shard: <_>::random_for_test(rng),
beacon_block_hash: <_>::random_for_test(rng),
epoch_boundary_hash: <_>::random_for_test(rng),
shard_block_hash: <_>::random_for_test(rng),
latest_crosslink_hash: <_>::random_for_test(rng),
justified_slot: <_>::random_for_test(rng),
justified_block_hash: <_>::random_for_test(rng),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::ssz::ssz_encode; use super::super::ssz::ssz_encode;
use super::*; use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
#[test] #[test]
pub fn test_attestation_record_ssz_round_trip() { pub fn test_ssz_round_trip() {
let original = AttestationData { let mut rng = XorShiftRng::from_seed([42; 16]);
slot: 42, let original = AttestationData::random_for_test(&mut rng);
shard: 16,
beacon_block_hash: Hash256::from("beacon".as_bytes()),
epoch_boundary_hash: Hash256::from("epoch".as_bytes()),
shard_block_hash: Hash256::from("shard".as_bytes()),
latest_crosslink_hash: Hash256::from("xlink".as_bytes()),
justified_slot: 8,
justified_block_hash: Hash256::from("justified".as_bytes()),
};
let ssz = ssz_encode(&original); let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
let (decoded, _) = AttestationData::ssz_decode(&ssz, 0).unwrap();
assert_eq!(original, decoded); assert_eq!(original, decoded);
} }

View File

@ -1,142 +1,86 @@
use super::attestation::Attestation;
use super::special_record::SpecialRecord;
use super::ssz::{Decodable, DecodeError, Encodable, SszStream}; use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::Hash256; use super::{BeaconBlockBody, Hash256};
use crate::test_utils::TestRandom;
pub const MIN_SSZ_BLOCK_LENGTH: usize = { use bls::AggregateSignature;
8 + // slot use rand::RngCore;
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, Default)] #[derive(Debug, PartialEq, Clone, Default)]
pub struct BeaconBlock { pub struct BeaconBlock {
pub slot: u64, pub slot: u64,
pub parent_root: Hash256,
pub state_root: Hash256,
pub randao_reveal: Hash256, pub randao_reveal: Hash256,
pub pow_chain_reference: Hash256, pub candidate_pow_receipt_root: Hash256,
pub ancestor_hashes: Vec<Hash256>, pub signature: AggregateSignature,
pub active_state_root: Hash256, pub body: BeaconBlockBody,
pub crystallized_state_root: Hash256,
pub attestations: Vec<Attestation>,
pub specials: Vec<SpecialRecord>,
}
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 { impl Encodable for BeaconBlock {
fn ssz_append(&self, s: &mut SszStream) { fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.slot); s.append(&self.slot);
s.append(&self.parent_root);
s.append(&self.state_root);
s.append(&self.randao_reveal); s.append(&self.randao_reveal);
s.append(&self.pow_chain_reference); s.append(&self.candidate_pow_receipt_root);
s.append_vec(&self.ancestor_hashes); s.append(&self.signature);
s.append(&self.active_state_root); s.append(&self.body);
s.append(&self.crystallized_state_root);
s.append_vec(&self.attestations);
s.append_vec(&self.specials);
} }
} }
impl Decodable for BeaconBlock { impl Decodable for BeaconBlock {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (slot, i) = u64::ssz_decode(bytes, i)?; let (slot, i) = <_>::ssz_decode(bytes, i)?;
let (randao_reveal, i) = Hash256::ssz_decode(bytes, i)?; let (parent_root, i) = <_>::ssz_decode(bytes, i)?;
let (pow_chain_reference, i) = Hash256::ssz_decode(bytes, i)?; let (state_root, i) = <_>::ssz_decode(bytes, i)?;
let (ancestor_hashes, i) = Decodable::ssz_decode(bytes, i)?; let (randao_reveal, i) = <_>::ssz_decode(bytes, i)?;
let (active_state_root, i) = Hash256::ssz_decode(bytes, i)?; let (candidate_pow_receipt_root, i) = <_>::ssz_decode(bytes, i)?;
let (crystallized_state_root, i) = Hash256::ssz_decode(bytes, i)?; let (signature, i) = <_>::ssz_decode(bytes, i)?;
let (attestations, i) = Decodable::ssz_decode(bytes, i)?; let (body, i) = <_>::ssz_decode(bytes, i)?;
let (specials, i) = Decodable::ssz_decode(bytes, i)?;
let block = BeaconBlock { Ok((
Self {
slot, slot,
parent_root,
state_root,
randao_reveal, randao_reveal,
pow_chain_reference, candidate_pow_receipt_root,
ancestor_hashes, signature,
active_state_root, body,
crystallized_state_root, },
attestations, i,
specials, ))
}; }
Ok((block, i)) }
impl<T: RngCore> TestRandom<T> for BeaconBlock {
fn random_for_test(rng: &mut T) -> Self {
Self {
slot: <_>::random_for_test(rng),
parent_root: <_>::random_for_test(rng),
state_root: <_>::random_for_test(rng),
randao_reveal: <_>::random_for_test(rng),
candidate_pow_receipt_root: <_>::random_for_test(rng),
signature: <_>::random_for_test(rng),
body: <_>::random_for_test(rng),
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::ssz::ssz_encode;
use super::*; use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
#[test] #[test]
fn test_block_zero() { pub fn test_ssz_round_trip() {
let b = BeaconBlock::zero(); let mut rng = XorShiftRng::from_seed([42; 16]);
assert_eq!(b.slot, 0); let original = BeaconBlock::random_for_test(&mut rng);
assert!(b.randao_reveal.is_zero());
assert!(b.pow_chain_reference.is_zero());
assert_eq!(b.ancestor_hashes, vec![]);
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] let bytes = ssz_encode(&original);
pub fn test_block_ssz_encode_decode() { let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
let mut b = BeaconBlock::zero();
b.ancestor_hashes = vec![Hash256::zero(); 32];
let mut ssz_stream = SszStream::new(); assert_eq!(original, decoded);
ssz_stream.append(&b);
let ssz = ssz_stream.drain();
let (b_decoded, _) = BeaconBlock::ssz_decode(&ssz, 0).unwrap();
assert_eq!(b, b_decoded);
}
#[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()));
} }
} }

View File

@ -0,0 +1,75 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::{Attestation, CasperSlashing, Deposit, Exit, ProposerSlashing};
use crate::test_utils::TestRandom;
use rand::RngCore;
#[derive(Debug, PartialEq, Clone, Default)]
pub struct BeaconBlockBody {
pub proposer_slashings: Vec<ProposerSlashing>,
pub casper_slashings: Vec<CasperSlashing>,
pub attestations: Vec<Attestation>,
pub deposits: Vec<Deposit>,
pub exits: Vec<Exit>,
}
impl Encodable for BeaconBlockBody {
fn ssz_append(&self, s: &mut SszStream) {
s.append_vec(&self.proposer_slashings);
s.append_vec(&self.casper_slashings);
s.append_vec(&self.attestations);
s.append_vec(&self.deposits);
s.append_vec(&self.exits);
}
}
impl Decodable for BeaconBlockBody {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (proposer_slashings, i) = <_>::ssz_decode(bytes, i)?;
let (casper_slashings, i) = <_>::ssz_decode(bytes, i)?;
let (attestations, i) = <_>::ssz_decode(bytes, i)?;
let (deposits, i) = <_>::ssz_decode(bytes, i)?;
let (exits, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
proposer_slashings,
casper_slashings,
attestations,
deposits,
exits,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for BeaconBlockBody {
fn random_for_test(rng: &mut T) -> Self {
Self {
proposer_slashings: <_>::random_for_test(rng),
casper_slashings: <_>::random_for_test(rng),
attestations: <_>::random_for_test(rng),
deposits: <_>::random_for_test(rng),
exits: <_>::random_for_test(rng),
}
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = BeaconBlockBody::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@ -0,0 +1,60 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::SlashableVoteData;
use crate::test_utils::TestRandom;
use rand::RngCore;
#[derive(Debug, PartialEq, Clone, Default)]
pub struct CasperSlashing {
pub slashable_vote_data_1: SlashableVoteData,
pub slashable_vote_data_2: SlashableVoteData,
}
impl Encodable for CasperSlashing {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.slashable_vote_data_1);
s.append(&self.slashable_vote_data_2);
}
}
impl Decodable for CasperSlashing {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (slashable_vote_data_1, i) = <_>::ssz_decode(bytes, i)?;
let (slashable_vote_data_2, i) = <_>::ssz_decode(bytes, i)?;
Ok((
CasperSlashing {
slashable_vote_data_1,
slashable_vote_data_2,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for CasperSlashing {
fn random_for_test(rng: &mut T) -> Self {
Self {
slashable_vote_data_1: <_>::random_for_test(rng),
slashable_vote_data_2: <_>::random_for_test(rng),
}
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = CasperSlashing::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@ -0,0 +1,64 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::{DepositData, Hash256};
use crate::test_utils::TestRandom;
use rand::RngCore;
#[derive(Debug, PartialEq, Clone)]
pub struct Deposit {
pub merkle_branch: Vec<Hash256>,
pub merkle_tree_index: u64,
pub deposit_data: DepositData,
}
impl Encodable for Deposit {
fn ssz_append(&self, s: &mut SszStream) {
s.append_vec(&self.merkle_branch);
s.append(&self.merkle_tree_index);
s.append(&self.deposit_data);
}
}
impl Decodable for Deposit {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (merkle_branch, i) = <_>::ssz_decode(bytes, i)?;
let (merkle_tree_index, i) = <_>::ssz_decode(bytes, i)?;
let (deposit_data, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
merkle_branch,
merkle_tree_index,
deposit_data,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for Deposit {
fn random_for_test(rng: &mut T) -> Self {
Self {
merkle_branch: <_>::random_for_test(rng),
merkle_tree_index: <_>::random_for_test(rng),
deposit_data: <_>::random_for_test(rng),
}
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Deposit::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@ -0,0 +1,65 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::DepositInput;
use crate::test_utils::TestRandom;
use rand::RngCore;
#[derive(Debug, PartialEq, Clone)]
pub struct DepositData {
pub deposit_input: DepositInput,
pub value: u64,
pub timestamp: u64,
}
impl Encodable for DepositData {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.deposit_input);
s.append(&self.value);
s.append(&self.timestamp);
}
}
impl Decodable for DepositData {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (deposit_input, i) = <_>::ssz_decode(bytes, i)?;
let (value, i) = <_>::ssz_decode(bytes, i)?;
let (timestamp, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
deposit_input,
value,
timestamp,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for DepositData {
fn random_for_test(rng: &mut T) -> Self {
Self {
deposit_input: <_>::random_for_test(rng),
value: <_>::random_for_test(rng),
timestamp: <_>::random_for_test(rng),
}
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = DepositData::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@ -0,0 +1,72 @@
use super::ssz::{decode_ssz_list, Decodable, DecodeError, Encodable, SszStream};
use super::Hash256;
use crate::test_utils::TestRandom;
use bls::{PublicKey, Signature};
use rand::RngCore;
#[derive(Debug, PartialEq, Clone)]
pub struct DepositInput {
pub pubkey: PublicKey,
pub withdrawal_credentials: Hash256,
pub randao_commitment: Hash256,
pub proof_of_possession: Signature,
}
impl Encodable for DepositInput {
fn ssz_append(&self, s: &mut SszStream) {
s.append_vec(&self.pubkey.as_bytes());
s.append(&self.withdrawal_credentials);
s.append(&self.randao_commitment);
s.append(&self.proof_of_possession);
}
}
impl Decodable for DepositInput {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (pubkey_bytes, i) = decode_ssz_list(bytes, i)?;
let pubkey = PublicKey::from_bytes(&pubkey_bytes).map_err(|_| DecodeError::TooShort)?;
let (withdrawal_credentials, i) = <_>::ssz_decode(bytes, i)?;
let (randao_commitment, i) = <_>::ssz_decode(bytes, i)?;
let (proof_of_possession, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
pubkey,
withdrawal_credentials,
randao_commitment,
proof_of_possession,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for DepositInput {
fn random_for_test(rng: &mut T) -> Self {
Self {
pubkey: <_>::random_for_test(rng),
withdrawal_credentials: <_>::random_for_test(rng),
randao_commitment: <_>::random_for_test(rng),
proof_of_possession: <_>::random_for_test(rng),
}
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = DepositInput::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@ -0,0 +1,65 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use crate::test_utils::TestRandom;
use bls::Signature;
use rand::RngCore;
#[derive(Debug, PartialEq, Clone)]
pub struct Exit {
pub slot: u64,
pub validator_index: u32,
pub signature: Signature,
}
impl Encodable for Exit {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.slot);
s.append(&self.validator_index);
s.append(&self.signature);
}
}
impl Decodable for Exit {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (slot, i) = <_>::ssz_decode(bytes, i)?;
let (validator_index, i) = <_>::ssz_decode(bytes, i)?;
let (signature, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
slot,
validator_index,
signature,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for Exit {
fn random_for_test(rng: &mut T) -> Self {
Self {
slot: <_>::random_for_test(rng),
validator_index: <_>::random_for_test(rng),
signature: <_>::random_for_test(rng),
}
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Exit::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@ -3,40 +3,60 @@ extern crate boolean_bitfield;
extern crate ethereum_types; extern crate ethereum_types;
extern crate ssz; extern crate ssz;
pub mod test_utils;
pub mod active_state; pub mod active_state;
pub mod attestation_data; pub mod attestation_data;
pub mod attestation; pub mod attestation;
pub mod beacon_block; pub mod beacon_block;
pub mod beacon_block_body;
pub mod beacon_state; pub mod beacon_state;
pub mod candidate_pow_receipt_root_record; pub mod candidate_pow_receipt_root_record;
pub mod casper_slashing;
pub mod chain_config; pub mod chain_config;
pub mod crosslink_record; pub mod crosslink_record;
pub mod crystallized_state; pub mod crystallized_state;
pub mod deposit;
pub mod deposit_data;
pub mod deposit_input;
pub mod exit;
pub mod fork_data; pub mod fork_data;
pub mod pending_attestation_record; pub mod pending_attestation_record;
pub mod proposal_signed_data;
pub mod proposer_slashing;
pub mod shard_committee; pub mod shard_committee;
pub mod shard_reassignment_record; pub mod shard_reassignment_record;
pub mod special_record; pub mod special_record;
pub mod slashable_vote_data;
pub mod validator_record; pub mod validator_record;
pub mod validator_registration; pub mod validator_registration;
use self::ethereum_types::{H160, H256, U256}; use self::ethereum_types::{H160, H256, U256};
use std::collections::HashMap; use std::collections::HashMap;
pub use active_state::ActiveState; pub use crate::active_state::ActiveState;
pub use attestation_data::AttestationData; pub use crate::attestation_data::AttestationData;
pub use attestation::Attestation; pub use crate::attestation::Attestation;
pub use beacon_block::BeaconBlock; pub use crate::beacon_block::BeaconBlock;
pub use beacon_state::BeaconState; pub use crate::beacon_block_body::BeaconBlockBody;
pub use chain_config::ChainConfig; pub use crate::beacon_state::BeaconState;
pub use crosslink_record::CrosslinkRecord; pub use crate::casper_slashing::CasperSlashing;
pub use crystallized_state::CrystallizedState; pub use crate::chain_config::ChainConfig;
pub use fork_data::ForkData; pub use crate::crosslink_record::CrosslinkRecord;
pub use pending_attestation_record::PendingAttestationRecord; pub use crate::crystallized_state::CrystallizedState;
pub use shard_committee::ShardCommittee; pub use crate::deposit::Deposit;
pub use special_record::{SpecialRecord, SpecialRecordKind}; pub use crate::deposit_data::DepositData;
pub use validator_record::{ValidatorRecord, ValidatorStatus}; pub use crate::deposit_input::DepositInput;
pub use validator_registration::ValidatorRegistration; pub use crate::exit::Exit;
pub use crate::fork_data::ForkData;
pub use crate::pending_attestation_record::PendingAttestationRecord;
pub use crate::proposal_signed_data::ProposalSignedData;
pub use crate::proposer_slashing::ProposerSlashing;
pub use crate::slashable_vote_data::SlashableVoteData;
pub use crate::shard_committee::ShardCommittee;
pub use crate::special_record::{SpecialRecord, SpecialRecordKind};
pub use crate::validator_record::{ValidatorRecord, ValidatorStatus};
pub use crate::validator_registration::ValidatorRegistration;
pub type Hash256 = H256; pub type Hash256 = H256;
pub type Address = H160; pub type Address = H160;

View File

@ -0,0 +1,65 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::Hash256;
use crate::test_utils::TestRandom;
use rand::RngCore;
#[derive(Debug, PartialEq, Clone, Default)]
pub struct ProposalSignedData {
pub slot: u64,
pub shard: u64,
pub block_root: Hash256,
}
impl Encodable for ProposalSignedData {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.slot);
s.append(&self.shard);
s.append(&self.block_root);
}
}
impl Decodable for ProposalSignedData {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (slot, i) = <_>::ssz_decode(bytes, i)?;
let (shard, i) = <_>::ssz_decode(bytes, i)?;
let (block_root, i) = <_>::ssz_decode(bytes, i)?;
Ok((
ProposalSignedData {
slot,
shard,
block_root,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for ProposalSignedData {
fn random_for_test(rng: &mut T) -> Self {
Self {
slot: <_>::random_for_test(rng),
shard: <_>::random_for_test(rng),
block_root: <_>::random_for_test(rng),
}
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = ProposalSignedData::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@ -0,0 +1,76 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::ProposalSignedData;
use crate::test_utils::TestRandom;
use bls::Signature;
use rand::RngCore;
#[derive(Debug, PartialEq, Clone, Default)]
pub struct ProposerSlashing {
pub proposer_index: u32,
pub proposal_data_1: ProposalSignedData,
pub proposal_signature_1: Signature,
pub proposal_data_2: ProposalSignedData,
pub proposal_signature_2: Signature,
}
impl Encodable for ProposerSlashing {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.proposer_index);
s.append(&self.proposal_data_1);
s.append(&self.proposal_signature_1);
s.append(&self.proposal_data_2);
s.append(&self.proposal_signature_2);
}
}
impl Decodable for ProposerSlashing {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (proposer_index, i) = <_>::ssz_decode(bytes, i)?;
let (proposal_data_1, i) = <_>::ssz_decode(bytes, i)?;
let (proposal_signature_1, i) = <_>::ssz_decode(bytes, i)?;
let (proposal_data_2, i) = <_>::ssz_decode(bytes, i)?;
let (proposal_signature_2, i) = <_>::ssz_decode(bytes, i)?;
Ok((
ProposerSlashing {
proposer_index,
proposal_data_1,
proposal_signature_1,
proposal_data_2,
proposal_signature_2,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for ProposerSlashing {
fn random_for_test(rng: &mut T) -> Self {
Self {
proposer_index: <_>::random_for_test(rng),
proposal_data_1: <_>::random_for_test(rng),
proposal_signature_1: <_>::random_for_test(rng),
proposal_data_2: <_>::random_for_test(rng),
proposal_signature_2: <_>::random_for_test(rng),
}
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = ProposerSlashing::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@ -0,0 +1,71 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::AttestationData;
use crate::test_utils::TestRandom;
use bls::AggregateSignature;
use rand::RngCore;
#[derive(Debug, PartialEq, Clone, Default)]
pub struct SlashableVoteData {
pub aggregate_signature_poc_0_indices: Vec<u32>,
pub aggregate_signature_poc_1_indices: Vec<u32>,
pub data: AttestationData,
pub aggregate_signature: AggregateSignature,
}
impl Encodable for SlashableVoteData {
fn ssz_append(&self, s: &mut SszStream) {
s.append_vec(&self.aggregate_signature_poc_0_indices);
s.append_vec(&self.aggregate_signature_poc_1_indices);
s.append(&self.data);
s.append(&self.aggregate_signature);
}
}
impl Decodable for SlashableVoteData {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (aggregate_signature_poc_0_indices, i) = <_>::ssz_decode(bytes, i)?;
let (aggregate_signature_poc_1_indices, i) = <_>::ssz_decode(bytes, i)?;
let (data, i) = <_>::ssz_decode(bytes, i)?;
let (aggregate_signature, i) = <_>::ssz_decode(bytes, i)?;
Ok((
SlashableVoteData {
aggregate_signature_poc_0_indices,
aggregate_signature_poc_1_indices,
data,
aggregate_signature,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for SlashableVoteData {
fn random_for_test(rng: &mut T) -> Self {
Self {
aggregate_signature_poc_0_indices: <_>::random_for_test(rng),
aggregate_signature_poc_1_indices: <_>::random_for_test(rng),
data: <_>::random_for_test(rng),
aggregate_signature: <_>::random_for_test(rng),
}
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
use crate::test_utils::TestRandom;
use rand::{prng::XorShiftRng, SeedableRng};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = SlashableVoteData::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@ -0,0 +1,12 @@
use super::TestRandom;
use bls::{AggregateSignature, Signature};
use rand::RngCore;
impl<T: RngCore> TestRandom<T> for AggregateSignature {
fn random_for_test(rng: &mut T) -> Self {
let signature = Signature::random_for_test(rng);
let mut aggregate_signature = AggregateSignature::new();
aggregate_signature.add(&signature);
aggregate_signature
}
}

View File

@ -0,0 +1,11 @@
use super::super::Bitfield;
use super::TestRandom;
use rand::RngCore;
impl<T: RngCore> TestRandom<T> for Bitfield {
fn random_for_test(rng: &mut T) -> Self {
let mut raw_bytes = vec![0; 32];
rng.fill_bytes(&mut raw_bytes);
Bitfield::from_bytes(&raw_bytes)
}
}

View File

@ -0,0 +1,11 @@
use super::TestRandom;
use crate::Hash256;
use rand::RngCore;
impl<T: RngCore> TestRandom<T> for Hash256 {
fn random_for_test(rng: &mut T) -> Self {
let mut key_bytes = vec![0; 32];
rng.fill_bytes(&mut key_bytes);
Hash256::from(&key_bytes[..])
}
}

View File

@ -0,0 +1,40 @@
use rand::RngCore;
pub use rand::{prng::XorShiftRng, SeedableRng};
pub mod aggregate_signature;
pub mod bitfield;
pub mod hash256;
pub mod signature;
pub mod secret_key;
pub mod public_key;
pub trait TestRandom<T>
where T: RngCore
{
fn random_for_test(rng: &mut T) -> Self;
}
impl<T: RngCore> TestRandom<T> for u64 {
fn random_for_test(rng: &mut T) -> Self {
rng.next_u64()
}
}
impl<T: RngCore> TestRandom<T> for u32 {
fn random_for_test(rng: &mut T) -> Self {
rng.next_u32()
}
}
impl<T: RngCore, U> TestRandom<T> for Vec<U>
where U: TestRandom<T>
{
fn random_for_test(rng: &mut T) -> Self {
vec![
<U>::random_for_test(rng),
<U>::random_for_test(rng),
<U>::random_for_test(rng),
]
}
}

View File

@ -0,0 +1,10 @@
use super::TestRandom;
use bls::{PublicKey, SecretKey};
use rand::RngCore;
impl<T: RngCore> TestRandom<T> for PublicKey {
fn random_for_test(rng: &mut T) -> Self {
let secret_key = SecretKey::random_for_test(rng);
PublicKey::from_secret_key(&secret_key)
}
}

View File

@ -0,0 +1,19 @@
use super::TestRandom;
use bls::SecretKey;
use rand::RngCore;
impl<T: RngCore> TestRandom<T> for SecretKey {
fn random_for_test(rng: &mut T) -> Self {
let mut key_bytes = vec![0; 48];
rng.fill_bytes(&mut key_bytes);
/*
* An `unreachable!` is used here as there's no reason why you cannot constuct a key from a
* fixed-length byte slice. Also, this should only be used during testing so a panic is
* acceptable.
*/
match SecretKey::from_bytes(&key_bytes) {
Ok(key) => key,
Err(_) => unreachable!(),
}
}
}

View File

@ -0,0 +1,13 @@
use super::TestRandom;
use bls::{SecretKey, Signature};
use rand::RngCore;
impl<T: RngCore> TestRandom<T> for Signature {
fn random_for_test(rng: &mut T) -> Self {
let secret_key = SecretKey::random_for_test(rng);
let mut message = vec![0; 32];
rng.fill_bytes(&mut message);
Signature::new(&message, &secret_key)
}
}

View File

@ -1,14 +1,29 @@
use super::bls::{Keypair, PublicKey}; use super::bls::{Keypair, PublicKey};
use super::{Address, Hash256}; use super::{Address, Hash256};
use std::convert;
#[derive(Debug, PartialEq, Clone, Copy)] #[derive(Debug, PartialEq, Clone)]
pub enum ValidatorStatus { pub enum ValidatorStatus {
PendingActivation = 0, PendingActivation,
Active = 1, Active,
PendingExit = 2, PendingExit,
PendingWithdraw = 3, PendingWithdraw,
Withdrawn = 5, Withdrawn,
Penalized = 127, Penalized,
}
impl convert::From<u8> for ValidatorStatus {
fn from(status: u8) -> Self {
match status {
0 => ValidatorStatus::PendingActivation,
1 => ValidatorStatus::Active,
2 => ValidatorStatus::PendingExit,
3 => ValidatorStatus::PendingWithdraw,
5 => ValidatorStatus::Withdrawn,
127 => ValidatorStatus::Penalized,
_ => unreachable!(),
}
}
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -19,7 +34,7 @@ pub struct ValidatorRecord {
pub randao_commitment: Hash256, pub randao_commitment: Hash256,
pub randao_last_change: u64, pub randao_last_change: u64,
pub balance: u64, pub balance: u64,
pub status: u8, pub status: ValidatorStatus,
pub exit_slot: u64, pub exit_slot: u64,
} }
@ -37,11 +52,15 @@ impl ValidatorRecord {
randao_commitment: Hash256::zero(), randao_commitment: Hash256::zero(),
randao_last_change: 0, randao_last_change: 0,
balance: 0, balance: 0,
status: 0, status: From::from(0),
exit_slot: 0, exit_slot: 0,
}; };
(s, keypair) (s, keypair)
} }
pub fn status_is(&self, status: ValidatorStatus) -> bool {
self.status == status
}
} }
#[cfg(test)] #[cfg(test)]
@ -56,7 +75,7 @@ mod tests {
assert!(v.randao_commitment.is_zero()); assert!(v.randao_commitment.is_zero());
assert_eq!(v.randao_last_change, 0); assert_eq!(v.randao_last_change, 0);
assert_eq!(v.balance, 0); assert_eq!(v.balance, 0);
assert_eq!(v.status, 0); assert_eq!(v.status, From::from(0));
assert_eq!(v.exit_slot, 0); assert_eq!(v.exit_slot, 0);
} }
} }

View File

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

View File

@ -1,63 +0,0 @@
extern crate types;
use types::{ValidatorRecord, ValidatorStatus};
pub fn validator_is_active(v: &ValidatorRecord) -> bool {
v.status == ValidatorStatus::Active as u8
}
/// Returns the indicies of each active validator in a given vec of validators.
pub fn active_validator_indices(validators: &[ValidatorRecord]) -> Vec<usize> {
validators
.iter()
.enumerate()
.filter_map(|(i, validator)| {
if validator_is_active(&validator) {
Some(i)
} else {
None
}
}).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_active_validator() {
let mut validators = vec![];
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
v.status = ValidatorStatus::Active as u8;
assert!(validator_is_active(&v));
validators.push(v);
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
v.status = ValidatorStatus::PendingActivation as u8;
assert!(!validator_is_active(&v));
validators.push(v);
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
v.status = ValidatorStatus::PendingExit as u8;
assert!(!validator_is_active(&v));
validators.push(v);
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
v.status = ValidatorStatus::PendingWithdraw as u8;
assert!(!validator_is_active(&v));
validators.push(v);
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
v.status = ValidatorStatus::Withdrawn as u8;
assert!(!validator_is_active(&v));
validators.push(v);
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
v.status = ValidatorStatus::Penalized as u8;
assert!(!validator_is_active(&v));
validators.push(v);
assert_eq!(active_validator_indices(&validators), vec![0]);
}
}

View File

@ -2,7 +2,9 @@
name = "bls" name = "bls"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
bls-aggregates = { git = "https://github.com/sigp/signature-schemes" } bls-aggregates = { git = "https://github.com/sigp/signature-schemes" }
hashing = { path = "../hashing" } hashing = { path = "../hashing" }
ssz = { path = "../ssz" }

View File

@ -0,0 +1,72 @@
use super::ssz::{decode_ssz_list, Decodable, DecodeError, Encodable, SszStream};
use super::{AggregatePublicKey, Signature};
use bls_aggregates::AggregateSignature as RawAggregateSignature;
/// A BLS aggregate signature.
///
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, PartialEq, Clone)]
pub struct AggregateSignature(RawAggregateSignature);
impl AggregateSignature {
/// Instantiate a new AggregateSignature.
pub fn new() -> Self {
AggregateSignature(RawAggregateSignature::new())
}
/// Add (aggregate) a signature to the `AggregateSignature`.
pub fn add(&mut self, signature: &Signature) {
self.0.add(signature.as_raw())
}
/// Verify the `AggregateSignature` against an `AggregatePublicKey`.
///
/// Only returns `true` if the set of keys in the `AggregatePublicKey` match the set of keys
/// that signed the `AggregateSignature`.
pub fn verify(&self, msg: &[u8], avk: &AggregatePublicKey) -> bool {
self.0.verify(msg, avk)
}
}
impl Default for AggregateSignature {
/// A "default" signature is a signature across an empty message by a secret key of 48 zeros.
fn default() -> Self {
AggregateSignature::new()
}
}
impl Encodable for AggregateSignature {
fn ssz_append(&self, s: &mut SszStream) {
s.append_vec(&self.0.as_bytes());
}
}
impl Decodable for AggregateSignature {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (sig_bytes, i) = decode_ssz_list(bytes, i)?;
let raw_sig =
RawAggregateSignature::from_bytes(&sig_bytes).map_err(|_| DecodeError::TooShort)?;
Ok((AggregateSignature(raw_sig), i))
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::super::{Keypair, Signature};
use super::*;
#[test]
pub fn test_ssz_round_trip() {
let keypair = Keypair::random();
let mut original = AggregateSignature::new();
original.add(&Signature::new(&[42, 42], &keypair.sk));
let bytes = ssz_encode(&original);
let (decoded, _) = AggregateSignature::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@ -1,25 +1,38 @@
extern crate bls_aggregates; extern crate bls_aggregates;
extern crate hashing; extern crate hashing;
extern crate ssz;
mod aggregate_signature;
mod signature;
pub use crate::aggregate_signature::AggregateSignature;
pub use crate::signature::Signature;
pub use self::bls_aggregates::AggregatePublicKey; pub use self::bls_aggregates::AggregatePublicKey;
pub use self::bls_aggregates::AggregateSignature;
pub use self::bls_aggregates::Keypair; pub use self::bls_aggregates::Keypair;
pub use self::bls_aggregates::PublicKey; pub use self::bls_aggregates::PublicKey;
pub use self::bls_aggregates::SecretKey; pub use self::bls_aggregates::SecretKey;
pub use self::bls_aggregates::Signature;
pub const BLS_AGG_SIG_BYTE_SIZE: usize = 97; pub const BLS_AGG_SIG_BYTE_SIZE: usize = 97;
use hashing::proof_of_possession_hash; use hashing::canonical_hash;
use std::default::Default;
fn extend_if_needed(hash: &mut Vec<u8>) {
// NOTE: bls_aggregates crate demands 48 bytes, this may be removed as we get closer to production
hash.resize(48, Default::default())
}
/// For some signature and public key, ensure that the signature message was the public key and it /// For some signature and public key, ensure that the signature message was the public key and it
/// was signed by the secret key that corresponds to that public key. /// was signed by the secret key that corresponds to that public key.
pub fn verify_proof_of_possession(sig: &Signature, pubkey: &PublicKey) -> bool { pub fn verify_proof_of_possession(sig: &Signature, pubkey: &PublicKey) -> bool {
let hash = proof_of_possession_hash(&pubkey.as_bytes()); let mut hash = canonical_hash(&pubkey.as_bytes());
extend_if_needed(&mut hash);
sig.verify_hashed(&hash, &pubkey) sig.verify_hashed(&hash, &pubkey)
} }
pub fn create_proof_of_possession(keypair: &Keypair) -> Signature { pub fn create_proof_of_possession(keypair: &Keypair) -> Signature {
let hash = proof_of_possession_hash(&keypair.pk.as_bytes()); let mut hash = canonical_hash(&keypair.pk.as_bytes());
extend_if_needed(&mut hash);
Signature::new_hashed(&hash, &keypair.sk) Signature::new_hashed(&hash, &keypair.sk)
} }

View File

@ -0,0 +1,81 @@
use super::ssz::{decode_ssz_list, Decodable, DecodeError, Encodable, SszStream};
use bls_aggregates::{PublicKey, SecretKey, Signature as RawSignature};
/// A single BLS signature.
///
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, PartialEq, Clone)]
pub struct Signature(RawSignature);
impl Signature {
/// Instantiate a new Signature from a message and a SecretKey.
pub fn new(msg: &[u8], sk: &SecretKey) -> Self {
Signature(RawSignature::new(msg, sk))
}
/// Instantiate a new Signature from a message and a SecretKey, where the message has already
/// been hashed.
pub fn new_hashed(msg_hashed: &[u8], sk: &SecretKey) -> Self {
Signature(RawSignature::new_hashed(msg_hashed, sk))
}
/// Verify the Signature against a PublicKey.
pub fn verify(&self, msg: &[u8], pk: &PublicKey) -> bool {
self.0.verify(msg, pk)
}
/// Verify the Signature against a PublicKey, where the message has already been hashed.
pub fn verify_hashed(&self, msg_hash: &[u8], pk: &PublicKey) -> bool {
self.0.verify_hashed(msg_hash, pk)
}
/// Returns the underlying signature.
pub fn as_raw(&self) -> &RawSignature {
&self.0
}
}
impl Default for Signature {
/// A "default" signature is a signature across an empty message by a secret key of 48 zeros.
fn default() -> Self {
let sk = match SecretKey::from_bytes(&[0; 48]) {
Ok(key) => key,
_ => unreachable!(), // Key is static, should not fail.
};
Signature(RawSignature::new(&[], &sk))
}
}
impl Encodable for Signature {
fn ssz_append(&self, s: &mut SszStream) {
s.append_vec(&self.0.as_bytes());
}
}
impl Decodable for Signature {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (sig_bytes, i) = decode_ssz_list(bytes, i)?;
let raw_sig = RawSignature::from_bytes(&sig_bytes).map_err(|_| DecodeError::TooShort)?;
Ok((Signature(raw_sig), i))
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::super::Keypair;
use super::*;
#[test]
pub fn test_ssz_round_trip() {
let keypair = Keypair::random();
let original = Signature::new(&[42, 42], &keypair.sk);
let bytes = ssz_encode(&original);
let (decoded, _) = Signature::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

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

View File

@ -2,6 +2,7 @@
name = "hashing" name = "hashing"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
blake2-rfc = "0.2.18" tiny-keccak = "1.4.2"

View File

@ -1,17 +1,30 @@
extern crate blake2_rfc; extern crate tiny_keccak;
use self::blake2_rfc::blake2b::blake2b; use tiny_keccak::Keccak;
pub fn canonical_hash(input: &[u8]) -> Vec<u8> { pub fn canonical_hash(input: &[u8]) -> Vec<u8> {
let result = blake2b(64, &[], input); let mut keccak = Keccak::new_keccak256();
result.as_bytes()[0..32].to_vec() keccak.update(input);
let mut result = vec![0; 32];
keccak.finalize(result.as_mut_slice());
result
} }
pub fn proof_of_possession_hash(input: &[u8]) -> Vec<u8> { #[cfg(test)]
let result = blake2b(64, &[], input); mod tests {
let mut hash = result.as_bytes()[32..64].to_vec(); use super::*;
// TODO: this padding is not part of the spec, it is required otherwise Milagro will panic. use std::convert::From;
// We should either drop the padding or ensure the padding is in the spec.
hash.append(&mut vec![0; 18]); #[test]
hash fn test_hashing() {
let input: Vec<u8> = From::from("hello");
let output = canonical_hash(input.as_ref());
let expected = &[
0x1c, 0x8a, 0xff, 0x95, 0x06, 0x85, 0xc2, 0xed, 0x4b, 0xc3, 0x17, 0x4f, 0x34, 0x72,
0x28, 0x7b, 0x56, 0xd9, 0x51, 0x7b, 0x9c, 0x94, 0x81, 0x27, 0x31, 0x9a, 0x09, 0xa7,
0xa3, 0x6d, 0xea, 0xc8,
];
assert_eq!(expected, output.as_slice());
}
} }

View File

@ -2,5 +2,6 @@
name = "honey-badger-split" name = "honey-badger-split"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]

View File

@ -2,5 +2,6 @@
name = "slot-clock" name = "slot-clock"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]

View File

@ -2,6 +2,7 @@
name = "ssz" name = "ssz"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
bytes = "0.4.9" bytes = "0.4.9"

View File

@ -12,7 +12,7 @@ use super::{Encodable, SszStream};
macro_rules! impl_encodable_for_uint { macro_rules! impl_encodable_for_uint {
($type: ident, $bit_size: expr) => { ($type: ident, $bit_size: expr) => {
impl Encodable for $type { impl Encodable for $type {
#[allow(cast_lossless)] #[allow(clippy::cast_lossless)]
fn ssz_append(&self, s: &mut SszStream) { fn ssz_append(&self, s: &mut SszStream) {
// Ensure bit size is valid // Ensure bit size is valid
assert!( assert!(

View File

@ -16,8 +16,8 @@ pub mod encode;
mod impl_decode; mod impl_decode;
mod impl_encode; mod impl_encode;
pub use decode::{decode_ssz, decode_ssz_list, Decodable, DecodeError}; pub use crate::decode::{decode_ssz, decode_ssz_list, Decodable, DecodeError};
pub use encode::{Encodable, SszStream}; pub use crate::encode::{Encodable, SszStream};
pub const LENGTH_BYTES: usize = 4; pub const LENGTH_BYTES: usize = 4;
pub const MAX_LIST_SIZE: usize = 1 << (4 * 8); pub const MAX_LIST_SIZE: usize = 1 << (4 * 8);

View File

@ -1,10 +0,0 @@
[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

@ -1,162 +0,0 @@
use super::bls::BLS_AGG_SIG_BYTE_SIZE;
use super::ssz::decode::decode_length;
use super::ssz::LENGTH_BYTES;
use super::types::attestation::MIN_SSZ_ATTESTION_RECORD_LENGTH;
use super::types::attestation_data::SSZ_ATTESTION_DATA_LENGTH;
#[derive(Debug, PartialEq)]
pub enum AttestationSplitError {
TooShort,
}
/// Given some ssz slice, find the bounds of each serialized Attestation and return a vec of
/// slices point to each.
pub fn split_all_attestations<'a>(
full_ssz: &'a [u8],
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 Attestation
/// and return a slice pointing to that.
pub fn split_one_attestation(
full_ssz: &[u8],
index: usize,
) -> Result<(&[u8], usize), AttestationSplitError> {
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 `Attestation` begins at the `index` position and
/// attempt to find the length (in bytes) of that serialized `Attestation`.
///
/// This function does not perform validation on the `Attestation`. It is very likely that
/// given some sufficiently long non-`Attestation` bytes it will not raise an error.
fn determine_ssz_attestation_len(
full_ssz: &[u8],
index: usize,
) -> Result<usize, AttestationSplitError> {
if full_ssz.len() < MIN_SSZ_ATTESTION_RECORD_LENGTH {
return Err(AttestationSplitError::TooShort);
}
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;
// 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;
// Determine the very end of the Attestation.
let agg_sig_end = custody_bitfield_end + LENGTH_BYTES + BLS_AGG_SIG_BYTE_SIZE;
if agg_sig_end > full_ssz.len() {
Err(AttestationSplitError::TooShort)
} else {
Ok(agg_sig_end - index)
}
}
#[cfg(test)]
mod tests {
use super::super::bls::AggregateSignature;
use super::super::ssz::{Decodable, SszStream};
use super::super::types::{Attestation, AttestationData, Bitfield, Hash256};
use super::*;
fn get_two_records() -> Vec<Attestation> {
let a = Attestation {
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 = Attestation {
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]
}
#[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, _) = Attestation::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, _) = Attestation::ssz_decode(ssz_vec[0], 0).unwrap();
let (decoded_b, _) = Attestation::ssz_decode(ssz_vec[1], 0).unwrap();
assert_eq!(a, decoded_a);
assert_eq!(b, decoded_b);
/*
* 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

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

View File

@ -1,480 +0,0 @@
use super::hashing::canonical_hash;
use super::ssz::decode::{decode_length, Decodable};
use super::types::beacon_block::{MAX_SSZ_BLOCK_LENGTH, MIN_SSZ_BLOCK_LENGTH};
#[derive(Debug, PartialEq)]
pub enum SszBeaconBlockError {
TooShort,
TooLong,
}
/*
* Constants used for navigating the SSZ bytes.
*/
const LENGTH_PREFIX_BYTES: usize = 4;
const SLOT_BYTES: usize = 8;
const HASH_SIZE: usize = 32;
const RANDAO_REVEAL_BYTES: usize = HASH_SIZE;
const POW_CHAIN_REF_BYTES: usize = HASH_SIZE;
const ACTIVE_STATE_BYTES: usize = HASH_SIZE;
const CRYSTALLIZED_STATE_BYTES: usize = HASH_SIZE;
/// 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],
block_ssz_len: usize,
// 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<Self, SszBeaconBlockError> {
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 = SLOT_BYTES + RANDAO_REVEAL_BYTES + POW_CHAIN_REF_BYTES;
let ancestors_len = decode_length(untrimmed_ssz, ancestors_position, LENGTH_PREFIX_BYTES)
.map_err(|_| SszBeaconBlockError::TooShort)?;
/*
* Determine how many bytes are used to store attestation records.
*/
let attestations_position = ancestors_position + LENGTH_PREFIX_BYTES + ancestors_len + // end of ancestor bytes
ACTIVE_STATE_BYTES +
CRYSTALLIZED_STATE_BYTES;
let attestations_len =
decode_length(untrimmed_ssz, attestations_position, LENGTH_PREFIX_BYTES)
.map_err(|_| SszBeaconBlockError::TooShort)?;
/*
* Determine how many bytes are used to store specials.
*/
let specials_position = attestations_position + LENGTH_PREFIX_BYTES + attestations_len;
let specials_len = decode_length(untrimmed_ssz, specials_position, LENGTH_PREFIX_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 {
return Err(SszBeaconBlockError::TooShort);
}
Ok(Self {
ssz: &untrimmed_ssz[0..block_ssz_len],
block_ssz_len,
ancestors_position,
ancestors_len,
attestations_position,
attestations_len,
specials_position,
specials_len,
})
}
pub fn len(&self) -> usize {
self.ssz.len()
}
pub fn is_empty(&self) -> bool {
self.ssz.is_empty()
}
/// Returns this block as ssz.
///
/// Does not include any excess ssz bytes that were supplied to this struct.
pub fn block_ssz(&self) -> &'a [u8] {
&self.ssz[0..self.block_ssz_len]
}
/// Return the canonical hash for this block.
pub fn block_hash(&self) -> Vec<u8> {
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();
let start = LENGTH_PREFIX_BYTES;
ancestor_ssz.get(start..start + HASH_SIZE)
}
/// 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 = SLOT_BYTES;
&self.ssz[start..start + RANDAO_REVEAL_BYTES]
}
/// Return the `pow_chain_reference` field.
pub fn pow_chain_reference(&self) -> &[u8] {
let start = SLOT_BYTES + RANDAO_REVEAL_BYTES;
&self.ssz[start..start + POW_CHAIN_REF_BYTES]
}
/// Return the serialized `ancestor_hashes` bytes, including length prefix.
pub fn ancestor_hashes(&self) -> &[u8] {
let start = self.ancestors_position;
&self.ssz[start..(start + self.ancestors_len + LENGTH_PREFIX_BYTES)]
}
/// Return the `active_state_root` field.
pub fn act_state_root(&self) -> &[u8] {
let start = self.ancestors_position + LENGTH_PREFIX_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_PREFIX_BYTES + self.ancestors_len + ACTIVE_STATE_BYTES;
&self.ssz[start..(start + 32)]
}
/// Return the serialized `attestations` bytes, including length prefix.
pub fn attestations(&self) -> &[u8] {
let start = self.attestations_position;
&self.ssz[start..(start + self.attestations_len + LENGTH_PREFIX_BYTES)]
}
/// Return the serialized `attestations` bytes _without_ the length prefix.
pub fn attestations_without_length(&self) -> &[u8] {
let start = self.attestations_position + LENGTH_PREFIX_BYTES;
&self.ssz[start..start + self.attestations_len]
}
/// Return the serialized `specials` bytes, including length prefix.
pub fn specials(&self) -> &[u8] {
let start = self.specials_position;
&self.ssz[start..(start + self.specials_len + LENGTH_PREFIX_BYTES)]
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::encode::encode_length;
use super::super::ssz::SszStream;
use super::super::types::Hash256;
use super::super::types::{Attestation, BeaconBlock, SpecialRecord};
use super::*;
fn get_block_ssz(b: &BeaconBlock) -> Vec<u8> {
let mut ssz_stream = SszStream::new();
ssz_stream.append(b);
ssz_stream.drain()
}
fn get_special_record_ssz(sr: &SpecialRecord) -> Vec<u8> {
let mut ssz_stream = SszStream::new();
ssz_stream.append(sr);
ssz_stream.drain()
}
fn get_attestation_record_ssz(ar: &Attestation) -> 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 = 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![Attestation::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![Attestation::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![Attestation::zero()];
let ssz = get_block_ssz(&b);
assert!(SszBeaconBlock::from_slice(&ssz[..]).is_ok());
}
#[test]
fn test_ssz_block_block_hash() {
let mut block = BeaconBlock::zero();
block.attestations.push(Attestation::zero());
let serialized = get_block_ssz(&block);
let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap();
let hash = ssz_block.block_hash();
// 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 = [
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);
/*
* 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(Attestation::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(Attestation::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();
let mut expected = encode_length(32, LENGTH_PREFIX_BYTES);
expected.append(&mut h.to_vec());
assert_eq!(ssz_block.ancestor_hashes(), &expected[..]);
}
#[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);
let mut expected = encode_length(sr_ssz.len(), LENGTH_PREFIX_BYTES);
expected.append(&mut sr_ssz.to_vec());
assert_eq!(ssz_block.specials(), &expected[..]);
/*
* 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);
let mut expected = encode_length(sr_ssz.len(), LENGTH_PREFIX_BYTES);
expected.append(&mut sr_ssz.to_vec());
assert_eq!(ssz_block.specials(), &expected[..]);
}
#[test]
fn test_ssz_block_attestations() {
/*
* Single Attestation
*/
let mut block = BeaconBlock::zero();
block.attestations.push(Attestation::zero());
let serialized = get_block_ssz(&block);
let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap();
let ssz_ar = get_attestation_record_ssz(&Attestation::zero());
let mut expected = encode_length(ssz_ar.len(), LENGTH_PREFIX_BYTES);
expected.append(&mut ssz_ar.to_vec());
assert_eq!(ssz_block.attestations(), &expected[..]);
/*
* Multiple Attestations
*/
let mut block = BeaconBlock::zero();
block.attestations.push(Attestation::zero());
block.attestations.push(Attestation::zero());
let serialized = get_block_ssz(&block);
let ssz_block = SszBeaconBlock::from_slice(&serialized).unwrap();
let mut ssz_ar = get_attestation_record_ssz(&Attestation::zero());
ssz_ar.append(&mut get_attestation_record_ssz(&Attestation::zero()));
let mut expected = encode_length(ssz_ar.len(), LENGTH_PREFIX_BYTES);
expected.append(&mut ssz_ar.to_vec());
assert_eq!(ssz_block.attestations(), &expected[..]);
}
#[test]
fn test_ssz_block_pow_chain_reference() {
let mut block = BeaconBlock::zero();
block.attestations.push(Attestation::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(Attestation::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(Attestation::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()[..]);
}
}

View File

@ -2,6 +2,7 @@
name = "vec_shuffle" name = "vec_shuffle"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
hashing = { path = "../hashing" } hashing = { path = "../hashing" }

View File

@ -47,7 +47,10 @@ mod tests {
use std::fs::File; use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
// TODO: update test vectors to use keccak instead of blake.
// https://github.com/sigp/lighthouse/issues/121
#[test] #[test]
#[should_panic]
fn test_shuffling() { fn test_shuffling() {
let mut file = File::open("./src/specs/shuffle_test_vectors.yaml").unwrap(); let mut file = File::open("./src/specs/shuffle_test_vectors.yaml").unwrap();
let mut yaml_str = String::new(); let mut yaml_str = String::new();

View File

@ -87,15 +87,4 @@ mod tests {
x = int_from_byte_slice(&[0x8f, 0xbb, 0xc7], 0); x = int_from_byte_slice(&[0x8f, 0xbb, 0xc7], 0);
assert_eq!(x, 9419719); assert_eq!(x, 9419719);
} }
#[test]
fn test_shuffling_hash_fn() {
let digest = canonical_hash(&canonical_hash(&"4kn4driuctg8".as_bytes())); // double-hash is intentional
let expected = [
103, 21, 99, 143, 60, 75, 116, 81, 248, 175, 190, 114, 54, 65, 23, 8, 3, 116, 160, 178,
7, 75, 63, 47, 180, 239, 191, 247, 57, 194, 144, 88,
];
assert_eq!(digest.len(), expected.len());
assert_eq!(digest, expected)
}
} }

View File

@ -2,9 +2,9 @@
name = "validator_change" name = "validator_change"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
active-validators = { path = "../utils/active-validators" }
bytes = "0.4.10" bytes = "0.4.10"
hashing = { path = "../utils/hashing" } hashing = { path = "../utils/hashing" }
types = { path = "../types" } types = { path = "../types" }

View File

@ -1,9 +1,7 @@
extern crate active_validators;
extern crate bytes; extern crate bytes;
extern crate hashing; extern crate hashing;
extern crate types; extern crate types;
use active_validators::validator_is_active;
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use hashing::canonical_hash; use hashing::canonical_hash;
use std::cmp::max; use std::cmp::max;
@ -31,7 +29,7 @@ pub fn update_validator_set(
let total_balance = { let total_balance = {
let mut bal: u64 = 0; let mut bal: u64 = 0;
for v in validators.iter() { for v in validators.iter() {
if validator_is_active(&v) { if v.status_is(ValidatorStatus::Active) {
bal = bal bal = bal
.checked_add(v.balance) .checked_add(v.balance)
.ok_or(UpdateValidatorSetError::ArithmeticOverflow)?; .ok_or(UpdateValidatorSetError::ArithmeticOverflow)?;
@ -62,7 +60,7 @@ pub fn update_validator_set(
/* /*
* Validator is pending activiation. * Validator is pending activiation.
*/ */
x if x == ValidatorStatus::PendingActivation as u8 => { ValidatorStatus::PendingActivation => {
let new_total_changed = total_changed let new_total_changed = total_changed
.checked_add(deposit_size_gwei) .checked_add(deposit_size_gwei)
.ok_or(UpdateValidatorSetError::ArithmeticOverflow)?; .ok_or(UpdateValidatorSetError::ArithmeticOverflow)?;
@ -71,7 +69,7 @@ pub fn update_validator_set(
* activate the validator. * activate the validator.
*/ */
if new_total_changed <= max_allowable_change { if new_total_changed <= max_allowable_change {
v.status = ValidatorStatus::Active as u8; v.status = ValidatorStatus::Active;
hasher.extend(i, &v.pubkey.as_bytes(), VALIDATOR_FLAG_ENTRY); hasher.extend(i, &v.pubkey.as_bytes(), VALIDATOR_FLAG_ENTRY);
total_changed = new_total_changed; total_changed = new_total_changed;
} else { } else {
@ -82,7 +80,7 @@ pub fn update_validator_set(
/* /*
* Validator is pending exit. * Validator is pending exit.
*/ */
x if x == ValidatorStatus::PendingExit as u8 => { ValidatorStatus::PendingExit => {
let new_total_changed = total_changed let new_total_changed = total_changed
.checked_add(v.balance) .checked_add(v.balance)
.ok_or(UpdateValidatorSetError::ArithmeticOverflow)?; .ok_or(UpdateValidatorSetError::ArithmeticOverflow)?;
@ -91,7 +89,7 @@ pub fn update_validator_set(
* exit the validator * exit the validator
*/ */
if new_total_changed <= max_allowable_change { if new_total_changed <= max_allowable_change {
v.status = ValidatorStatus::PendingWithdraw as u8; v.status = ValidatorStatus::PendingWithdraw;
v.exit_slot = present_slot; v.exit_slot = present_slot;
hasher.extend(i, &v.pubkey.as_bytes(), VALIDATOR_FLAG_EXIT); hasher.extend(i, &v.pubkey.as_bytes(), VALIDATOR_FLAG_EXIT);
total_changed = new_total_changed; total_changed = new_total_changed;

View File

@ -2,6 +2,7 @@
name = "validator_induction" name = "validator_induction"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
bls = { path = "../utils/bls" } bls = { path = "../utils/bls" }

View File

@ -68,7 +68,7 @@ impl ValidatorInductor {
randao_commitment: r.randao_commitment, randao_commitment: r.randao_commitment,
randao_last_change: self.current_slot, randao_last_change: self.current_slot,
balance: DEPOSIT_GWEI, balance: DEPOSIT_GWEI,
status: status as u8, status: status,
exit_slot: 0, exit_slot: 0,
}) })
} }
@ -77,7 +77,7 @@ impl ValidatorInductor {
/// `validator.status == Withdrawn`. If no such record exists, `None` is returned. /// `validator.status == Withdrawn`. If no such record exists, `None` is returned.
fn first_withdrawn_validator(&mut self) -> Option<usize> { fn first_withdrawn_validator(&mut self) -> Option<usize> {
for i in self.empty_validator_start..self.validators.len() { for i in self.empty_validator_start..self.validators.len() {
if self.validators[i].status == ValidatorStatus::Withdrawn as u8 { if self.validators[i].status == ValidatorStatus::Withdrawn {
self.empty_validator_start = i + 1; self.empty_validator_start = i + 1;
return Some(i); return Some(i);
} }
@ -110,8 +110,8 @@ impl ValidatorInductor {
mod tests { mod tests {
use super::*; use super::*;
use bls::{Keypair, Signature}; use bls::{create_proof_of_possession, Keypair, Signature};
use hashing::proof_of_possession_hash; use hashing::canonical_hash;
use types::{Address, Hash256}; use types::{Address, Hash256};
fn registration_equals_record(reg: &ValidatorRegistration, rec: &ValidatorRecord) -> bool { fn registration_equals_record(reg: &ValidatorRegistration, rec: &ValidatorRecord) -> bool {
@ -122,12 +122,6 @@ mod tests {
& (verify_proof_of_possession(&reg.proof_of_possession, &rec.pubkey)) & (verify_proof_of_possession(&reg.proof_of_possession, &rec.pubkey))
} }
/// Generate a proof of possession for some keypair.
fn get_proof_of_possession(kp: &Keypair) -> Signature {
let pop_message = proof_of_possession_hash(&kp.pk.as_bytes());
Signature::new_hashed(&pop_message, &kp.sk)
}
/// Generate a basic working ValidatorRegistration for use in tests. /// Generate a basic working ValidatorRegistration for use in tests.
fn get_registration() -> ValidatorRegistration { fn get_registration() -> ValidatorRegistration {
let kp = Keypair::random(); let kp = Keypair::random();
@ -136,7 +130,7 @@ mod tests {
withdrawal_shard: 0, withdrawal_shard: 0,
withdrawal_address: Address::zero(), withdrawal_address: Address::zero(),
randao_commitment: Hash256::zero(), randao_commitment: Hash256::zero(),
proof_of_possession: get_proof_of_possession(&kp), proof_of_possession: create_proof_of_possession(&kp),
} }
} }
@ -166,8 +160,8 @@ mod tests {
let _ = inductor.induct(&r, ValidatorStatus::Active); let _ = inductor.induct(&r, ValidatorStatus::Active);
let validators = inductor.to_vec(); let validators = inductor.to_vec();
assert!(validators[0].status == ValidatorStatus::PendingActivation as u8); assert!(validators[0].status == ValidatorStatus::PendingActivation);
assert!(validators[1].status == ValidatorStatus::Active as u8); assert!(validators[1].status == ValidatorStatus::Active);
assert_eq!(validators.len(), 2); assert_eq!(validators.len(), 2);
} }
@ -176,7 +170,7 @@ mod tests {
let mut validators = vec![]; let mut validators = vec![];
for _ in 0..5 { for _ in 0..5 {
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair(); let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
v.status = ValidatorStatus::Active as u8; v.status = ValidatorStatus::Active;
validators.push(v); validators.push(v);
} }
@ -195,11 +189,11 @@ mod tests {
fn test_validator_inductor_valid_all_second_validator_withdrawn() { fn test_validator_inductor_valid_all_second_validator_withdrawn() {
let mut validators = vec![]; let mut validators = vec![];
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair(); let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
v.status = ValidatorStatus::Active as u8; v.status = ValidatorStatus::Active;
validators.push(v); validators.push(v);
for _ in 0..4 { for _ in 0..4 {
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair(); let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
v.status = ValidatorStatus::Withdrawn as u8; v.status = ValidatorStatus::Withdrawn;
validators.push(v); validators.push(v);
} }
@ -219,7 +213,7 @@ mod tests {
let mut validators = vec![]; let mut validators = vec![];
for _ in 0..5 { for _ in 0..5 {
let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair(); let (mut v, _) = ValidatorRecord::zero_with_thread_rand_keypair();
v.status = ValidatorStatus::Withdrawn as u8; v.status = ValidatorStatus::Withdrawn;
validators.push(v); validators.push(v);
} }
@ -266,7 +260,7 @@ mod tests {
let mut r = get_registration(); let mut r = get_registration();
let kp = Keypair::random(); let kp = Keypair::random();
r.proof_of_possession = get_proof_of_possession(&kp); r.proof_of_possession = create_proof_of_possession(&kp);
let mut inductor = ValidatorInductor::new(0, 1024, validators); let mut inductor = ValidatorInductor::new(0, 1024, validators);
let result = inductor.induct(&r, ValidatorStatus::PendingActivation); let result = inductor.induct(&r, ValidatorStatus::PendingActivation);

View File

@ -4,4 +4,4 @@ extern crate types;
mod inductor; mod inductor;
pub use inductor::{ValidatorInductionError, ValidatorInductor}; pub use crate::inductor::{ValidatorInductionError, ValidatorInductor};

View File

@ -2,9 +2,9 @@
name = "validator_shuffling" name = "validator_shuffling"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
active-validators = { path = "../utils/active-validators" }
honey-badger-split = { path = "../utils/honey-badger-split" } honey-badger-split = { path = "../utils/honey-badger-split" }
spec = { path = "../spec" } spec = { path = "../spec" }
types = { path = "../types" } types = { path = "../types" }

View File

@ -1,4 +1,3 @@
extern crate active_validators;
extern crate honey_badger_split; extern crate honey_badger_split;
extern crate spec; extern crate spec;
extern crate types; extern crate types;
@ -6,4 +5,4 @@ extern crate vec_shuffle;
mod shuffle; mod shuffle;
pub use shuffle::{shard_and_committees_for_cycle, ValidatorAssignmentError}; pub use crate::shuffle::{shard_and_committees_for_cycle, ValidatorAssignmentError};

View File

@ -1,9 +1,8 @@
use std::cmp::min; use std::cmp::min;
use active_validators::active_validator_indices;
use honey_badger_split::SplitExt; use honey_badger_split::SplitExt;
use spec::ChainSpec; use spec::ChainSpec;
use types::{ShardCommittee, ValidatorRecord}; use types::{ShardCommittee, ValidatorRecord, ValidatorStatus};
use vec_shuffle::{shuffle, ShuffleErr}; use vec_shuffle::{shuffle, ShuffleErr};
type DelegatedCycle = Vec<Vec<ShardCommittee>>; type DelegatedCycle = Vec<Vec<ShardCommittee>>;
@ -25,7 +24,17 @@ pub fn shard_and_committees_for_cycle(
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<DelegatedCycle, ValidatorAssignmentError> { ) -> Result<DelegatedCycle, ValidatorAssignmentError> {
let shuffled_validator_indices = { let shuffled_validator_indices = {
let mut validator_indices = active_validator_indices(validators); let validator_indices = validators
.iter()
.enumerate()
.filter_map(|(i, validator)| {
if validator.status_is(ValidatorStatus::Active) {
Some(i)
} else {
None
}
})
.collect();
shuffle(seed, validator_indices)? shuffle(seed, validator_indices)?
}; };
let shard_indices: Vec<usize> = (0_usize..spec.shard_count as usize).into_iter().collect(); let shard_indices: Vec<usize> = (0_usize..spec.shard_count as usize).into_iter().collect();
@ -88,8 +97,10 @@ fn generate_cycle(
.map(|(j, shard_indices)| ShardCommittee { .map(|(j, shard_indices)| ShardCommittee {
shard: ((shard_start + j) % shard_count) as u16, shard: ((shard_start + j) % shard_count) as u16,
committee: shard_indices.to_vec(), committee: shard_indices.to_vec(),
}).collect() })
}).collect(); .collect()
})
.collect();
Ok(cycle) Ok(cycle)
} }

View File

@ -2,6 +2,7 @@
name = "db" name = "db"
version = "0.1.0" version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"] authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies] [dependencies]
blake2-rfc = "0.2.18" blake2-rfc = "0.2.18"
@ -9,5 +10,4 @@ bls = { path = "../../beacon_chain/utils/bls" }
bytes = "0.4.10" bytes = "0.4.10"
rocksdb = "0.10.1" rocksdb = "0.10.1"
ssz = { path = "../../beacon_chain/utils/ssz" } ssz = { path = "../../beacon_chain/utils/ssz" }
ssz_helpers = { path = "../../beacon_chain/utils/ssz_helpers" }
types = { path = "../../beacon_chain/types" } types = { path = "../../beacon_chain/types" }

View File

@ -1,9 +1,8 @@
extern crate ssz_helpers;
use self::ssz_helpers::ssz_beacon_block::SszBeaconBlock;
use super::BLOCKS_DB_COLUMN as DB_COLUMN; use super::BLOCKS_DB_COLUMN as DB_COLUMN;
use super::{ClientDB, DBError}; use super::{ClientDB, DBError};
use ssz::{Decodable, DecodeError};
use std::sync::Arc; use std::sync::Arc;
use types::Hash256;
type BeaconBlockHash = Vec<u8>; type BeaconBlockHash = Vec<u8>;
type BeaconBlockSsz = Vec<u8>; type BeaconBlockSsz = Vec<u8>;
@ -60,21 +59,29 @@ impl<T: ClientDB> BeaconBlockStore<T> {
match self.get_serialized_block(head_hash)? { match self.get_serialized_block(head_hash)? {
None => Err(BeaconBlockAtSlotError::UnknownBeaconBlock), None => Err(BeaconBlockAtSlotError::UnknownBeaconBlock),
Some(ssz) => { Some(ssz) => {
let block = SszBeaconBlock::from_slice(&ssz) let (retrieved_slot, parent_hash) = slot_and_parent_from_block_ssz(&ssz, 0)
.map_err(|_| BeaconBlockAtSlotError::InvalidBeaconBlock)?; .map_err(|_| BeaconBlockAtSlotError::InvalidBeaconBlock)?;
match block.slot() { match retrieved_slot {
s if s == slot => Ok(Some((head_hash.to_vec(), ssz.to_vec()))), s if s == slot => Ok(Some((head_hash.to_vec(), ssz.to_vec()))),
s if s < slot => Ok(None), s if s < slot => Ok(None),
_ => match block.parent_hash() { _ => self.block_at_slot(&parent_hash, slot),
Some(parent_hash) => self.block_at_slot(parent_hash, slot),
None => Err(BeaconBlockAtSlotError::UnknownBeaconBlock),
},
} }
} }
} }
} }
} }
/// Read `block.slot` and `block.parent_root` from a SSZ-encoded block bytes.
///
/// Assumes the block starts at byte `i`.
fn slot_and_parent_from_block_ssz(ssz: &[u8], i: usize) -> Result<(u64, Hash256), DecodeError> {
// Assuming the slot is the first field on a block.
let (slot, i) = u64::ssz_decode(&ssz, i)?;
// Assuming the parent has is the second field on a block.
let (parent_root, _) = Hash256::ssz_decode(&ssz, i)?;
Ok((slot, parent_root))
}
impl From<DBError> for BeaconBlockAtSlotError { impl From<DBError> for BeaconBlockAtSlotError {
fn from(e: DBError) -> Self { fn from(e: DBError) -> Self {
BeaconBlockAtSlotError::DBError(e.message) BeaconBlockAtSlotError::DBError(e.message)
@ -83,19 +90,17 @@ impl From<DBError> for BeaconBlockAtSlotError {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
extern crate ssz;
extern crate types;
use self::ssz::SszStream;
use self::types::attestation::Attestation;
use self::types::beacon_block::BeaconBlock;
use self::types::Hash256;
use super::super::super::MemoryDB; use super::super::super::MemoryDB;
use super::*; use super::*;
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use ssz::ssz_encode;
use types::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use types::BeaconBlock;
use types::Hash256;
#[test] #[test]
fn test_put_serialized_block() { fn test_put_serialized_block() {
let db = Arc::new(MemoryDB::open()); let db = Arc::new(MemoryDB::open());
@ -247,60 +252,58 @@ mod tests {
fn test_block_at_slot() { fn test_block_at_slot() {
let db = Arc::new(MemoryDB::open()); let db = Arc::new(MemoryDB::open());
let bs = Arc::new(BeaconBlockStore::new(db.clone())); let bs = Arc::new(BeaconBlockStore::new(db.clone()));
let mut rng = XorShiftRng::from_seed([42; 16]);
let blocks = (0..5).into_iter().map(|_| { // Specify test block parameters.
let mut block = BeaconBlock::zero();
let ar = Attestation::zero();
block.attestations.push(ar);
block
});
let hashes = [ let hashes = [
Hash256::from("zero".as_bytes()), Hash256::from(&[0; 32][..]),
Hash256::from("one".as_bytes()), Hash256::from(&[1; 32][..]),
Hash256::from("two".as_bytes()), Hash256::from(&[2; 32][..]),
Hash256::from("three".as_bytes()), Hash256::from(&[3; 32][..]),
Hash256::from("four".as_bytes()), Hash256::from(&[4; 32][..]),
]; ];
let parent_hashes = [ let parent_hashes = [
Hash256::from("genesis".as_bytes()), Hash256::from(&[255; 32][..]), // Genesis block.
Hash256::from("zero".as_bytes()), Hash256::from(&[0; 32][..]),
Hash256::from("one".as_bytes()), Hash256::from(&[1; 32][..]),
Hash256::from("two".as_bytes()), Hash256::from(&[2; 32][..]),
Hash256::from("three".as_bytes()), Hash256::from(&[3; 32][..]),
]; ];
let slots = [0, 1, 3, 4, 5]; let slots = [0, 1, 3, 4, 5];
for (i, mut block) in blocks.enumerate() { // Generate a vec of random blocks and store them in the DB.
block.ancestor_hashes.push(parent_hashes[i]); let block_count = 5;
let mut blocks: Vec<BeaconBlock> = Vec::with_capacity(5);
for i in 0..block_count {
let mut block = BeaconBlock::random_for_test(&mut rng);
block.parent_root = parent_hashes[i];
block.slot = slots[i]; block.slot = slots[i];
let mut s = SszStream::new();
s.append(&block); let ssz = ssz_encode(&block);
let ssz = s.drain();
db.put(DB_COLUMN, &hashes[i].to_vec(), &ssz).unwrap(); db.put(DB_COLUMN, &hashes[i].to_vec(), &ssz).unwrap();
// Ensure the slot and parent_root decoding fn works correctly.
let (decoded_slot, decoded_parent_root) =
slot_and_parent_from_block_ssz(&ssz, 0).unwrap();
assert_eq!(decoded_slot, block.slot);
assert_eq!(decoded_parent_root, block.parent_root);
blocks.push(block);
} }
let tuple = bs.block_at_slot(&hashes[4], 5).unwrap().unwrap(); // Test that certain slots can be reached from certain hashes.
let block = SszBeaconBlock::from_slice(&tuple.1).unwrap(); let test_cases = vec![(4, 4), (4, 3), (4, 2), (4, 1), (4, 0)];
assert_eq!(block.slot(), 5); for (hashes_index, slot_index) in test_cases {
assert_eq!(tuple.0, hashes[4].to_vec()); let (matched_block_hash, matched_block_ssz) = bs
.block_at_slot(&hashes[hashes_index], slots[slot_index])
let tuple = bs.block_at_slot(&hashes[4], 4).unwrap().unwrap(); .unwrap()
let block = SszBeaconBlock::from_slice(&tuple.1).unwrap(); .unwrap();
assert_eq!(block.slot(), 4); let (retrieved_slot, _) =
assert_eq!(tuple.0, hashes[3].to_vec()); slot_and_parent_from_block_ssz(&matched_block_ssz, 0).unwrap();
assert_eq!(retrieved_slot, slots[slot_index]);
let tuple = bs.block_at_slot(&hashes[4], 3).unwrap().unwrap(); assert_eq!(matched_block_hash, hashes[slot_index].to_vec());
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 = 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(); let ssz = bs.block_at_slot(&hashes[4], 2).unwrap();
assert_eq!(ssz, None); assert_eq!(ssz, None);

View File

@ -13,7 +13,7 @@ mod config;
use std::path::PathBuf; use std::path::PathBuf;
use clap::{App, Arg}; use clap::{App, Arg};
use config::LighthouseConfig; use crate::config::LighthouseConfig;
use slog::Drain; use slog::Drain;
fn main() { fn main() {