Rename beacon_chain/ -> eth2/

This commit is contained in:
Paul Hauner
2019-01-22 16:16:02 +11:00
parent 87c73b1af9
commit e16f9e0aec
92 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
[package]
name = "attestation_validation"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
bls = { path = "../utils/bls" }
db = { path = "../../beacon_node/db" }
hashing = { path = "../utils/hashing" }
ssz = { path = "../utils/ssz" }
types = { path = "../types" }

View File

@@ -0,0 +1,246 @@
use super::{Error, Invalid, Outcome};
/// Check that an attestation is valid to be included in some block.
pub fn validate_attestation_for_block(
attestation_slot: u64,
block_slot: u64,
parent_block_slot: u64,
min_attestation_inclusion_delay: u64,
epoch_length: u64,
) -> Result<Outcome, Error> {
/*
* There is a delay before an attestation may be included in a block, quantified by
* `slots` and defined as `min_attestation_inclusion_delay`.
*
* So, an attestation must be at least `min_attestation_inclusion_delay` slots "older" than the
* block it is contained in.
*/
verify_or!(
// TODO: this differs from the spec as it does not handle underflows correctly.
// https://github.com/sigp/lighthouse/issues/95
attestation_slot < block_slot.saturating_sub(min_attestation_inclusion_delay - 1),
reject!(Invalid::AttestationTooRecent)
);
/*
* A block may not include attestations reference slots more than an epoch length + 1 prior to
* the block slot.
*/
verify_or!(
attestation_slot >= parent_block_slot.saturating_sub(epoch_length + 1),
reject!(Invalid::AttestationTooOld)
);
accept!()
}
#[cfg(test)]
mod tests {
use super::*;
/*
* Invalid::AttestationTooOld tests.
*/
#[test]
fn test_inclusion_too_old_minimal() {
let min_attestation_inclusion_delay = 10;
let epoch_length = 20;
let block_slot = 100;
let parent_block_slot = block_slot - 1;
let attestation_slot = block_slot - min_attestation_inclusion_delay;
let outcome = validate_attestation_for_block(
attestation_slot,
block_slot,
parent_block_slot,
min_attestation_inclusion_delay,
epoch_length,
);
assert_eq!(outcome, Ok(Outcome::Valid));
}
#[test]
fn test_inclusion_too_old_maximal() {
let min_attestation_inclusion_delay = 10;
let epoch_length = 20;
let block_slot = 100;
let parent_block_slot = block_slot - 1;
let attestation_slot = block_slot - epoch_length + 1;
let outcome = validate_attestation_for_block(
attestation_slot,
block_slot,
parent_block_slot,
min_attestation_inclusion_delay,
epoch_length,
);
assert_eq!(outcome, Ok(Outcome::Valid));
}
#[test]
fn test_inclusion_too_old_saturating_non_zero_attestation_slot() {
let min_attestation_inclusion_delay = 10;
let epoch_length = 20;
let block_slot = epoch_length + 1;
let parent_block_slot = block_slot - 1;
let attestation_slot = block_slot - min_attestation_inclusion_delay;
let outcome = validate_attestation_for_block(
attestation_slot,
block_slot,
parent_block_slot,
min_attestation_inclusion_delay,
epoch_length,
);
assert_eq!(outcome, Ok(Outcome::Valid));
}
#[test]
fn test_inclusion_too_old_saturating_zero_attestation_slot() {
let min_attestation_inclusion_delay = 10;
let epoch_length = 20;
let block_slot = epoch_length + 1;
let parent_block_slot = block_slot - 1;
let attestation_slot = 0;
let outcome = validate_attestation_for_block(
attestation_slot,
block_slot,
parent_block_slot,
min_attestation_inclusion_delay,
epoch_length,
);
assert_eq!(outcome, Ok(Outcome::Valid));
}
#[test]
fn test_inclusion_too_old() {
let min_attestation_inclusion_delay = 10;
let epoch_length = 20;
let block_slot = epoch_length * 2;
let parent_block_slot = block_slot - 1;
let attestation_slot = parent_block_slot - (epoch_length + 2);
let outcome = validate_attestation_for_block(
attestation_slot,
block_slot,
parent_block_slot,
min_attestation_inclusion_delay,
epoch_length,
);
assert_eq!(outcome, Ok(Outcome::Invalid(Invalid::AttestationTooOld)));
}
/*
* Invalid::AttestationTooRecent tests.
*/
#[test]
fn test_inclusion_too_recent_minimal() {
let parent_block_slot = 99;
let min_attestation_inclusion_delay = 10;
let epoch_length = 20;
let block_slot = 100;
let attestation_slot = block_slot - min_attestation_inclusion_delay;
let outcome = validate_attestation_for_block(
attestation_slot,
block_slot,
parent_block_slot,
min_attestation_inclusion_delay,
epoch_length,
);
assert_eq!(outcome, Ok(Outcome::Valid));
}
#[test]
fn test_inclusion_too_recent_maximal() {
let parent_block_slot = 99;
let min_attestation_inclusion_delay = 10;
let epoch_length = 20;
let block_slot = 100;
let attestation_slot = block_slot - epoch_length;
let outcome = validate_attestation_for_block(
attestation_slot,
block_slot,
parent_block_slot,
min_attestation_inclusion_delay,
epoch_length,
);
assert_eq!(outcome, Ok(Outcome::Valid));
}
#[test]
fn test_inclusion_too_recent_insufficient() {
let parent_block_slot = 99;
let min_attestation_inclusion_delay = 10;
let epoch_length = 20;
let block_slot = 100;
let attestation_slot = block_slot - (min_attestation_inclusion_delay - 1);
let outcome = validate_attestation_for_block(
attestation_slot,
block_slot,
parent_block_slot,
min_attestation_inclusion_delay,
epoch_length,
);
assert_eq!(outcome, Ok(Outcome::Invalid(Invalid::AttestationTooRecent)));
}
#[test]
fn test_inclusion_too_recent_first_possible_slot() {
let min_attestation_inclusion_delay = 10;
let epoch_length = 20;
let block_slot = min_attestation_inclusion_delay;
let attestation_slot = 0;
let parent_block_slot = block_slot - 1;
let outcome = validate_attestation_for_block(
attestation_slot,
block_slot,
parent_block_slot,
min_attestation_inclusion_delay,
epoch_length,
);
assert_eq!(outcome, Ok(Outcome::Valid));
}
#[test]
fn test_inclusion_too_recent_saturation_non_zero_slot() {
let min_attestation_inclusion_delay = 10;
let epoch_length = 20;
let block_slot = min_attestation_inclusion_delay - 1;
let parent_block_slot = block_slot - 1;
let attestation_slot = 0;
let outcome = validate_attestation_for_block(
attestation_slot,
block_slot,
parent_block_slot,
min_attestation_inclusion_delay,
epoch_length,
);
assert_eq!(outcome, Ok(Outcome::Invalid(Invalid::AttestationTooRecent)));
}
#[test]
fn test_inclusion_too_recent_saturation_zero_slot() {
let min_attestation_inclusion_delay = 10;
let epoch_length = 20;
let block_slot = min_attestation_inclusion_delay - 1;
let parent_block_slot = block_slot - 1;
let attestation_slot = 0;
let outcome = validate_attestation_for_block(
attestation_slot,
block_slot,
parent_block_slot,
min_attestation_inclusion_delay,
epoch_length,
);
assert_eq!(outcome, Ok(Outcome::Invalid(Invalid::AttestationTooRecent)));
}
}

View File

@@ -0,0 +1,37 @@
/// Reasons why an `AttestationRecord` can be invalid.
#[derive(PartialEq, Debug)]
pub enum Invalid {
AttestationTooRecent,
AttestationTooOld,
JustifiedSlotImpermissable,
JustifiedBlockNotInChain,
JustifiedBlockHashMismatch,
UnknownShard,
ShardBlockHashMismatch,
SignatureInvalid,
}
/// The outcome of validating the `AttestationRecord`.
///
/// Distinct from the `Error` enum as an `Outcome` indicates that validation executed sucessfully
/// and determined the validity `AttestationRecord`.
#[derive(PartialEq, Debug)]
pub enum Outcome {
Valid,
Invalid(Invalid),
}
/// Errors that prevent this function from correctly validating the `AttestationRecord`.
///
/// Distinct from the `Outcome` enum as `Errors` indicate that validation encountered an unexpected
/// condition and was unable to perform its duty.
#[derive(PartialEq, Debug)]
pub enum Error {
BlockHasNoParent,
BadValidatorIndex,
UnableToLookupBlockAtSlot,
OutOfBoundsBitfieldIndex,
PublicKeyCorrupt,
NoPublicKeyForValidator,
DBError(String),
}

View File

@@ -0,0 +1,80 @@
use super::db::stores::{BeaconBlockAtSlotError, BeaconBlockStore};
use super::db::ClientDB;
use super::types::AttestationData;
use super::types::Hash256;
use super::{Error, Invalid, Outcome};
use std::sync::Arc;
/// Verify that a attestation's `data.justified_block_hash` matches the local hash of the block at the
/// attestation's `data.justified_slot`.
///
/// `chain_tip_block_hash` is the tip of the chain in which the justified block hash should exist
/// locally. As Lightouse stores multiple chains locally, it is possible to have multiple blocks at
/// the same slot. `chain_tip_block_hash` serves to restrict the lookup to a single chain, where
/// each slot may have exactly zero or one blocks.
pub fn validate_attestation_justified_block_hash<T>(
data: &AttestationData,
chain_tip_block_hash: &Hash256,
block_store: &Arc<BeaconBlockStore<T>>,
) -> Result<Outcome, Error>
where
T: ClientDB + Sized,
{
/*
* The `justified_block_hash` in the attestation must match exactly the hash of the block at
* that slot in the local chain.
*
* This condition also infers that the `justified_slot` specified in attestation must exist
* locally.
*/
match block_hash_at_slot(chain_tip_block_hash, data.justified_slot, block_store)? {
None => reject!(Invalid::JustifiedBlockNotInChain),
Some(local_justified_block_hash) => {
verify_or!(
data.justified_block_hash == local_justified_block_hash,
reject!(Invalid::JustifiedBlockHashMismatch)
);
}
};
accept!()
}
/// Returns the hash (or None) of a block at a slot in the chain that is specified by
/// `chain_tip_hash`.
///
/// Given that the database stores multiple chains, it is possible for there to be multiple blocks
/// at the given slot. `chain_tip_hash` specifies exactly which chain should be used.
fn block_hash_at_slot<T>(
chain_tip_hash: &Hash256,
slot: u64,
block_store: &Arc<BeaconBlockStore<T>>,
) -> Result<Option<Hash256>, Error>
where
T: ClientDB + Sized,
{
match block_store.block_at_slot(&chain_tip_hash, slot)? {
None => Ok(None),
Some((hash_bytes, _)) => Ok(Some(Hash256::from(&hash_bytes[..]))),
}
}
impl From<BeaconBlockAtSlotError> for Error {
fn from(e: BeaconBlockAtSlotError) -> Self {
match e {
BeaconBlockAtSlotError::DBError(s) => Error::DBError(s),
_ => Error::UnableToLookupBlockAtSlot,
}
}
}
#[cfg(test)]
mod tests {
/*
* TODO: Implement tests.
*
* These tests will require the `BeaconBlock` and `BeaconBlockBody` updates, which are not
* yet included in the code base. Adding tests now will result in duplicated work.
*
* https://github.com/sigp/lighthouse/issues/97
*/
}

View File

@@ -0,0 +1,39 @@
use super::types::{AttestationData, BeaconState};
use super::{Error, Invalid, Outcome};
/// Verify that an attestation's `data.justified_slot` matches the justified slot known to the
/// `state`.
///
/// In the case that an attestation references a slot _before_ the latest state transition, is
/// acceptable for the attestation to reference the previous known `justified_slot`. If this were
/// not the case, all attestations created _prior_ to the last state recalculation would be rejected
/// if a block was justified in that state recalculation. It is both ideal and likely that blocks
/// will be justified during a state recalcuation.
pub fn validate_attestation_justified_slot(
data: &AttestationData,
state: &BeaconState,
epoch_length: u64,
) -> Result<Outcome, Error> {
let permissable_justified_slot = if data.slot >= state.slot - (state.slot % epoch_length) {
state.justified_slot
} else {
state.previous_justified_slot
};
verify_or!(
data.justified_slot == permissable_justified_slot,
reject!(Invalid::JustifiedSlotImpermissable)
);
accept!()
}
#[cfg(test)]
mod tests {
/*
* TODO: Implement tests.
*
* These tests will require the `BeaconBlock` and `BeaconBlockBody` updates, which are not
* yet included in the code base. Adding tests now will result in duplicated work.
*
* https://github.com/sigp/lighthouse/issues/97
*/
}

View File

@@ -0,0 +1,22 @@
extern crate bls;
extern crate db;
extern crate hashing;
extern crate ssz;
extern crate types;
#[macro_use]
mod macros;
mod block_inclusion;
mod enums;
mod justified_block;
mod justified_slot;
mod shard_block;
mod signature;
pub use crate::block_inclusion::validate_attestation_for_block;
pub use crate::enums::{Error, Invalid, Outcome};
pub use crate::justified_block::validate_attestation_justified_block_hash;
pub use crate::justified_slot::validate_attestation_justified_slot;
pub use crate::shard_block::validate_attestation_data_shard_block_hash;
pub use crate::signature::validate_attestation_signature;

View File

@@ -0,0 +1,19 @@
macro_rules! verify_or {
($condition: expr, $result: expr) => {
if !$condition {
$result
}
};
}
macro_rules! reject {
($result: expr) => {
return Ok(Outcome::Invalid($result));
};
}
macro_rules! accept {
() => {
Ok(Outcome::Valid)
};
}

View File

@@ -0,0 +1,46 @@
use super::db::ClientDB;
use super::types::{AttestationData, BeaconState};
use super::{Error, Invalid, Outcome};
/// Check that an attestation is valid with reference to some state.
pub fn validate_attestation_data_shard_block_hash<T>(
data: &AttestationData,
state: &BeaconState,
) -> Result<Outcome, Error>
where
T: ClientDB + Sized,
{
/*
* The `shard_block_hash` in the state's `latest_crosslinks` must match either the
* `latest_crosslink_hash` or the `shard_block_hash` on the attestation.
*
* TODO: figure out the reasoning behind this.
*/
match state.latest_crosslinks.get(data.shard as usize) {
None => reject!(Invalid::UnknownShard),
Some(crosslink) => {
let local_shard_block_hash = crosslink.shard_block_root;
let shard_block_hash_is_permissable = {
(local_shard_block_hash == data.latest_crosslink_hash)
|| (local_shard_block_hash == data.shard_block_hash)
};
verify_or!(
shard_block_hash_is_permissable,
reject!(Invalid::ShardBlockHashMismatch)
);
}
};
accept!()
}
#[cfg(test)]
mod tests {
/*
* TODO: Implement tests.
*
* These tests will require the `BeaconBlock` and `BeaconBlockBody` updates, which are not
* yet included in the code base. Adding tests now will result in duplicated work.
*
* https://github.com/sigp/lighthouse/issues/97
*/
}

View File

@@ -0,0 +1,151 @@
use super::bls::{AggregatePublicKey, AggregateSignature};
use super::db::stores::{ValidatorStore, ValidatorStoreError};
use super::db::ClientDB;
use super::types::{AttestationData, Bitfield, BitfieldError};
use super::{Error, Invalid, Outcome};
/// Validate that some signature is correct for some attestation data and known validator set.
pub fn validate_attestation_signature<T>(
attestation_data: &AttestationData,
participation_bitfield: &Bitfield,
aggregate_signature: &AggregateSignature,
attestation_indices: &[usize],
validator_store: &ValidatorStore<T>,
) -> Result<Outcome, Error>
where
T: ClientDB + Sized,
{
let mut agg_pub_key = AggregatePublicKey::new();
for i in 0..attestation_indices.len() {
let voted = participation_bitfield.get(i)?;
if voted {
// De-reference the attestation index into a canonical ValidatorRecord index.
let validator = *attestation_indices.get(i).ok_or(Error::BadValidatorIndex)?;
// Load the public key.
let pub_key = validator_store
.get_public_key_by_index(validator)?
.ok_or(Error::NoPublicKeyForValidator)?;
// Aggregate the public key.
agg_pub_key.add(&pub_key.as_raw());
}
}
let signed_message = attestation_data_signing_message(attestation_data);
verify_or!(
// TODO: ensure "domain" for aggregate signatures is included.
// https://github.com/sigp/lighthouse/issues/91
aggregate_signature.verify(&signed_message, &agg_pub_key),
reject!(Invalid::SignatureInvalid)
);
accept!()
}
fn attestation_data_signing_message(attestation_data: &AttestationData) -> Vec<u8> {
let mut signed_message = attestation_data.canonical_root().to_vec();
signed_message.append(&mut vec![0]);
signed_message
}
impl From<ValidatorStoreError> for Error {
fn from(error: ValidatorStoreError) -> Self {
match error {
ValidatorStoreError::DBError(s) => Error::DBError(s),
ValidatorStoreError::DecodeError => Error::PublicKeyCorrupt,
}
}
}
impl From<BitfieldError> for Error {
fn from(_error: BitfieldError) -> Self {
Error::OutOfBoundsBitfieldIndex
}
}
#[cfg(test)]
mod tests {
use super::super::bls::{Keypair, Signature};
use super::super::db::MemoryDB;
use super::*;
use std::sync::Arc;
/*
* TODO: Test cases are not comprehensive.
* https://github.com/sigp/lighthouse/issues/94
*/
#[test]
fn test_signature_verification() {
let attestation_data = AttestationData::zero();
let message = attestation_data_signing_message(&attestation_data);
let signing_keypairs = vec![
Keypair::random(),
Keypair::random(),
Keypair::random(),
Keypair::random(),
Keypair::random(),
Keypair::random(),
];
let non_signing_keypairs = vec![
Keypair::random(),
Keypair::random(),
Keypair::random(),
Keypair::random(),
Keypair::random(),
Keypair::random(),
];
/*
* Signing keypairs first, then non-signing
*/
let mut all_keypairs = signing_keypairs.clone();
all_keypairs.append(&mut non_signing_keypairs.clone());
let attestation_indices: Vec<usize> = (0..all_keypairs.len()).collect();
let mut bitfield = Bitfield::from_elem(all_keypairs.len(), false);
for i in 0..signing_keypairs.len() {
bitfield.set(i, true).unwrap();
}
let db = Arc::new(MemoryDB::open());
let store = ValidatorStore::new(db);
for (i, keypair) in all_keypairs.iter().enumerate() {
store.put_public_key_by_index(i, &keypair.pk).unwrap();
}
let mut agg_sig = AggregateSignature::new();
for keypair in &signing_keypairs {
let sig = Signature::new(&message, &keypair.sk);
agg_sig.add(&sig);
}
/*
* Test using all valid parameters.
*/
let outcome = validate_attestation_signature(
&attestation_data,
&bitfield,
&agg_sig,
&attestation_indices,
&store,
)
.unwrap();
assert_eq!(outcome, Outcome::Valid);
/*
* Add another validator to the bitfield, run validation will all other
* parameters the same and assert that it fails.
*/
bitfield.set(signing_keypairs.len() + 1, true).unwrap();
let outcome = validate_attestation_signature(
&attestation_data,
&bitfield,
&agg_sig,
&attestation_indices,
&store,
)
.unwrap();
assert_eq!(outcome, Outcome::Invalid(Invalid::SignatureInvalid));
}
}

13
eth2/genesis/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "genesis"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
bls = { path = "../utils/bls" }
spec = { path = "../spec" }
ssz = { path = "../utils/ssz" }
types = { path = "../types" }
validator_induction = { path = "../validator_induction" }
validator_shuffling = { path = "../validator_shuffling" }

View File

@@ -0,0 +1,89 @@
use spec::ChainSpec;
use types::{BeaconBlock, BeaconBlockBody, Hash256};
/// Generate a genesis BeaconBlock.
pub fn genesis_beacon_block(state_root: Hash256, spec: &ChainSpec) -> BeaconBlock {
BeaconBlock {
slot: spec.genesis_slot_number,
parent_root: spec.zero_hash,
state_root,
randao_reveal: spec.zero_hash,
candidate_pow_receipt_root: spec.zero_hash,
signature: spec.empty_signature.clone(),
body: BeaconBlockBody {
proposer_slashings: vec![],
casper_slashings: vec![],
attestations: vec![],
custody_reseeds: vec![],
custody_challenges: vec![],
custody_responses: vec![],
deposits: vec![],
exits: vec![],
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use bls::Signature;
#[test]
fn test_genesis() {
let spec = ChainSpec::foundation();
let state_root = Hash256::from("cats".as_bytes());
// This only checks that the function runs without panic.
genesis_beacon_block(state_root, &spec);
}
#[test]
fn test_zero_items() {
let spec = ChainSpec::foundation();
let state_root = Hash256::zero();
let genesis_block = genesis_beacon_block(state_root, &spec);
assert!(genesis_block.slot == 0);
assert!(genesis_block.parent_root.is_zero());
assert!(genesis_block.randao_reveal.is_zero());
assert!(genesis_block.candidate_pow_receipt_root.is_zero()); // aka deposit_root
}
#[test]
fn test_beacon_body() {
let spec = ChainSpec::foundation();
let state_root = Hash256::zero();
let genesis_block = genesis_beacon_block(state_root, &spec);
// Custody items are not being implemented until phase 1 so tests to be added later
assert!(genesis_block.body.proposer_slashings.is_empty());
assert!(genesis_block.body.casper_slashings.is_empty());
assert!(genesis_block.body.attestations.is_empty());
assert!(genesis_block.body.deposits.is_empty());
assert!(genesis_block.body.exits.is_empty());
}
#[test]
fn test_signature() {
let spec = ChainSpec::foundation();
let state_root = Hash256::zero();
let genesis_block = genesis_beacon_block(state_root, &spec);
// Signature should consist of [bytes48(0), bytes48(0)]
// Note this is implemented using Apache Milagro BLS which requires one extra byte -> 97bytes
let raw_sig = genesis_block.signature.as_raw();
let raw_sig_bytes = raw_sig.as_bytes();
for item in raw_sig_bytes.iter() {
assert!(*item == 0);
}
assert_eq!(genesis_block.signature, Signature::empty_signature());
}
}

View File

@@ -0,0 +1,222 @@
use spec::ChainSpec;
use types::{BeaconState, CrosslinkRecord, ForkData};
use validator_shuffling::{shard_and_committees_for_cycle, ValidatorAssignmentError};
#[derive(Debug, PartialEq)]
pub enum Error {
NoValidators,
ValidationAssignmentError(ValidatorAssignmentError),
NotImplemented,
}
pub fn genesis_beacon_state(spec: &ChainSpec) -> Result<BeaconState, Error> {
/*
* Assign the validators to shards, using all zeros as the seed.
*/
let _shard_and_committee_for_slots = {
let mut a = shard_and_committees_for_cycle(&[0; 32], &spec.initial_validators, 0, &spec)?;
let mut b = a.clone();
a.append(&mut b);
a
};
let initial_crosslink = CrosslinkRecord {
slot: spec.genesis_slot_number,
shard_block_root: spec.zero_hash,
};
Ok(BeaconState {
/*
* Misc
*/
slot: spec.genesis_slot_number,
genesis_time: spec.genesis_time,
fork_data: ForkData {
pre_fork_version: spec.genesis_fork_version,
post_fork_version: spec.genesis_fork_version,
fork_slot: spec.genesis_slot_number,
},
/*
* Validator registry
*/
validator_registry: spec.initial_validators.clone(),
validator_balances: spec.initial_balances.clone(),
validator_registry_latest_change_slot: spec.genesis_slot_number,
validator_registry_exit_count: 0,
validator_registry_delta_chain_tip: spec.zero_hash,
/*
* Randomness and committees
*/
latest_randao_mixes: vec![spec.zero_hash; spec.latest_randao_mixes_length as usize],
latest_vdf_outputs: vec![
spec.zero_hash;
(spec.latest_randao_mixes_length / spec.epoch_length) as usize
],
previous_epoch_start_shard: spec.genesis_start_shard,
current_epoch_start_shard: spec.genesis_start_shard,
previous_epoch_calculation_slot: spec.genesis_slot_number,
current_epoch_calculation_slot: spec.genesis_slot_number,
previous_epoch_randao_mix: spec.zero_hash,
current_epoch_randao_mix: spec.zero_hash,
/*
* Custody challenges
*/
custody_challenges: vec![],
/*
* Finality
*/
previous_justified_slot: spec.genesis_slot_number,
justified_slot: spec.genesis_slot_number,
justification_bitfield: 0,
finalized_slot: spec.genesis_slot_number,
/*
* Recent state
*/
latest_crosslinks: vec![initial_crosslink; spec.shard_count as usize],
latest_block_roots: vec![spec.zero_hash; spec.latest_block_roots_length as usize],
latest_penalized_exit_balances: vec![0; spec.latest_penalized_exit_length as usize],
latest_attestations: vec![],
batched_block_roots: vec![],
/*
* PoW receipt root
*/
processed_pow_receipt_root: spec.processed_pow_receipt_root,
candidate_pow_receipt_roots: vec![],
})
}
impl From<ValidatorAssignmentError> for Error {
fn from(e: ValidatorAssignmentError) -> Error {
Error::ValidationAssignmentError(e)
}
}
#[cfg(test)]
mod tests {
use super::*;
use types::Hash256;
#[test]
fn test_genesis_state() {
let spec = ChainSpec::foundation();
let state = genesis_beacon_state(&spec).unwrap();
assert_eq!(
state.validator_registry.len(),
spec.initial_validators.len()
);
}
#[test]
fn test_genesis_state_misc() {
let spec = ChainSpec::foundation();
let state = genesis_beacon_state(&spec).unwrap();
assert_eq!(state.slot, 0);
assert_eq!(state.genesis_time, spec.genesis_time);
assert_eq!(state.fork_data.pre_fork_version, 0);
assert_eq!(state.fork_data.post_fork_version, 0);
assert_eq!(state.fork_data.fork_slot, 0);
}
#[test]
fn test_genesis_state_validators() {
let spec = ChainSpec::foundation();
let state = genesis_beacon_state(&spec).unwrap();
assert_eq!(state.validator_registry, spec.initial_validators);
assert_eq!(state.validator_balances, spec.initial_balances);
assert!(state.validator_registry_latest_change_slot == 0);
assert!(state.validator_registry_exit_count == 0);
assert_eq!(state.validator_registry_delta_chain_tip, Hash256::zero());
}
#[test]
fn test_genesis_state_randomness_committees() {
let spec = ChainSpec::foundation();
let state = genesis_beacon_state(&spec).unwrap();
// Array of size 8,192 each being zero_hash
assert_eq!(state.latest_randao_mixes.len(), 8_192);
for item in state.latest_randao_mixes.iter() {
assert_eq!(*item, Hash256::zero());
}
// Array of size 8,192 each being a zero hash
assert_eq!(state.latest_vdf_outputs.len(), (8_192 / 64));
for item in state.latest_vdf_outputs.iter() {
assert_eq!(*item, Hash256::zero());
}
// TODO: Check shard and committee shuffling requires solving issue:
// https://github.com/sigp/lighthouse/issues/151
// initial_shuffling = get_shuffling(Hash256::zero(), &state.validator_registry, 0, 0)
// initial_shuffling = initial_shuffling.append(initial_shuffling.clone());
}
// Custody not implemented until Phase 1
#[test]
fn test_genesis_state_custody() {}
#[test]
fn test_genesis_state_finanilty() {
let spec = ChainSpec::foundation();
let state = genesis_beacon_state(&spec).unwrap();
assert_eq!(state.previous_justified_slot, 0);
assert_eq!(state.justified_slot, 0);
assert_eq!(state.justification_bitfield, 0);
assert_eq!(state.finalized_slot, 0);
}
#[test]
fn test_genesis_state_recent_state() {
let spec = ChainSpec::foundation();
let state = genesis_beacon_state(&spec).unwrap();
// Test latest_crosslinks
assert_eq!(state.latest_crosslinks.len(), 1_024);
for link in state.latest_crosslinks.iter() {
assert_eq!(link.slot, 0);
assert_eq!(link.shard_block_root, Hash256::zero());
}
// Test latest_block_roots
assert_eq!(state.latest_block_roots.len(), 8_192);
for block in state.latest_block_roots.iter() {
assert_eq!(*block, Hash256::zero());
}
// Test latest_penalized_exit_balances
assert_eq!(state.latest_penalized_exit_balances.len(), 8_192);
for item in state.latest_penalized_exit_balances.iter() {
assert!(*item == 0);
}
// Test latest_attestations
assert!(state.latest_attestations.is_empty());
// batched_block_roots
assert!(state.batched_block_roots.is_empty());
}
#[test]
fn test_genesis_state_deposit_root() {
let spec = ChainSpec::foundation();
let state = genesis_beacon_state(&spec).unwrap();
assert_eq!(
state.processed_pow_receipt_root,
spec.processed_pow_receipt_root
);
assert!(state.candidate_pow_receipt_roots.is_empty());
}
}

10
eth2/genesis/src/lib.rs Normal file
View File

@@ -0,0 +1,10 @@
extern crate spec;
extern crate types;
extern crate validator_induction;
extern crate validator_shuffling;
mod beacon_block;
mod beacon_state;
pub use crate::beacon_block::genesis_beacon_block;
pub use crate::beacon_state::{genesis_beacon_state, Error as GenesisError};

View File

@@ -0,0 +1,10 @@
[package]
name = "naive_fork_choice"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
db = { path = "../../beacon_node/db" }
ssz = { path = "../utils/ssz" }
types = { path = "../types" }

View File

@@ -0,0 +1,97 @@
extern crate db;
extern crate ssz;
extern crate types;
use db::stores::BeaconBlockStore;
use db::{ClientDB, DBError};
use ssz::{Decodable, DecodeError};
use std::sync::Arc;
use types::{BeaconBlock, Hash256};
pub enum ForkChoiceError {
BadSszInDatabase,
MissingBlock,
DBError(String),
}
pub fn naive_fork_choice<T>(
head_block_hashes: &[Hash256],
block_store: &Arc<BeaconBlockStore<T>>,
) -> Result<Option<usize>, ForkChoiceError>
where
T: ClientDB + Sized,
{
let mut head_blocks: Vec<(usize, BeaconBlock)> = vec![];
/*
* Load all the head_block hashes from the DB as SszBeaconBlocks.
*/
for (index, block_hash) in head_block_hashes.iter().enumerate() {
let ssz = block_store
.get(&block_hash)?
.ok_or(ForkChoiceError::MissingBlock)?;
let (block, _) = BeaconBlock::ssz_decode(&ssz, 0)?;
head_blocks.push((index, block));
}
/*
* Loop through all the head blocks and find the highest slot.
*/
let highest_slot: Option<u64> = None;
for (_, block) in &head_blocks {
let slot = block.slot;
match highest_slot {
None => Some(slot),
Some(winning_slot) => {
if slot > winning_slot {
Some(slot)
} else {
Some(winning_slot)
}
}
};
}
/*
* Loop through all the highest blocks and sort them by highest hash.
*
* Ultimately, the index of the head_block hash with the highest slot and highest block
* hash will be the winner.
*/
match highest_slot {
None => Ok(None),
Some(highest_slot) => {
let mut highest_blocks = vec![];
for (index, block) in head_blocks {
if block.slot == highest_slot {
highest_blocks.push((index, block))
}
}
highest_blocks.sort_by(|a, b| head_block_hashes[a.0].cmp(&head_block_hashes[b.0]));
let (index, _) = highest_blocks[0];
Ok(Some(index))
}
}
}
impl From<DecodeError> for ForkChoiceError {
fn from(_: DecodeError) -> Self {
ForkChoiceError::BadSszInDatabase
}
}
impl From<DBError> for ForkChoiceError {
fn from(e: DBError) -> Self {
ForkChoiceError::DBError(e.message)
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_naive_fork_choice() {
assert_eq!(2 + 2, 4);
}
}

9
eth2/spec/Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[package]
name = "spec"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
bls = { path = "../utils/bls" }
types = { path = "../types" }

140
eth2/spec/src/foundation.rs Normal file
View File

@@ -0,0 +1,140 @@
use super::ChainSpec;
use bls::{Keypair, PublicKey, SecretKey, Signature};
use types::{Address, Hash256, ValidatorRecord};
/// The size of a validators deposit in GWei.
pub const DEPOSIT_GWEI: u64 = 32_000_000_000;
impl ChainSpec {
/// Returns a `ChainSpec` compatible with the specification from Ethereum Foundation.
///
/// Of course, the actual foundation specs are unknown at this point so these are just a rough
/// estimate.
pub fn foundation() -> Self {
Self {
/*
* Misc
*/
shard_count: 1_024,
target_committee_size: 128,
ejection_balance: 16,
max_balance_churn_quotient: 32,
gwei_per_eth: u64::pow(10, 9),
beacon_chain_shard_number: u64::max_value(),
max_casper_votes: 1_024,
latest_block_roots_length: 8_192,
latest_randao_mixes_length: 8_192,
latest_penalized_exit_length: 8_192,
max_withdrawals_per_epoch: 4,
/*
* Deposit contract
*/
deposit_contract_address: Address::from("TBD".as_bytes()),
deposit_contract_tree_depth: 32,
min_deposit: 1,
max_deposit: 32,
/*
* Initial Values
*/
genesis_fork_version: 0,
genesis_slot_number: 0,
genesis_start_shard: 0,
far_future_slot: u64::max_value(),
zero_hash: Hash256::zero(),
empty_signature: Signature::empty_signature(),
bls_withdrawal_prefix_byte: 0x00,
/*
* Time parameters
*/
slot_duration: 6,
min_attestation_inclusion_delay: 4,
epoch_length: 64,
seed_lookahead: 64,
entry_exit_delay: 256,
pow_receipt_root_voting_period: 1_024,
min_validator_withdrawal_time: u64::pow(2, 14),
/*
* Reward and penalty quotients
*/
base_reward_quotient: 1_024,
whistleblower_reward_quotient: 512,
includer_reward_quotient: 8,
inactivity_penalty_quotient: u64::pow(2, 24),
/*
* Max operations per block
*/
max_proposer_slashings: 16,
max_casper_slashings: 16,
max_attestations: 128,
max_deposits: 16,
max_exits: 16,
/*
* Intialization parameters
*/
initial_validators: initial_validators_for_testing(),
initial_balances: initial_balances_for_testing(),
genesis_time: 1_544_672_897,
processed_pow_receipt_root: Hash256::from("pow_root".as_bytes()),
}
}
}
/// Generate a set of validator records to use with testing until the real chain starts.
fn initial_validators_for_testing() -> Vec<ValidatorRecord> {
// Some dummy private keys to start with.
let key_strings = vec![
"jzjxxgjajfjrmgodszzsgqccmhnyvetcuxobhtynojtpdtbj",
"gpeehcjudxdijzhjgirfuhahmnjutlchjmoffxmimbdejakd",
"ntrrdwwebodokuwaclhoqreqyodngoyhurvesghjfxeswoaj",
"cibmzkqrzdgdlrvqaxinwpvyhcgjkeysrsjkqtkcxvznsvth",
"erqrfuahdwprsstkawggounxmihzhrvbhchcyiwtaypqcedr",
];
let mut initial_validators = Vec::with_capacity(key_strings.len());
for key_string in key_strings {
let keypair = {
let secret_key = match SecretKey::from_bytes(&key_string.as_bytes()) {
Ok(key) => key,
Err(_) => unreachable!(), // Keys are static and should not fail.
};
let public_key = PublicKey::from_secret_key(&secret_key);
Keypair {
sk: secret_key,
pk: public_key,
}
};
let validator_record = ValidatorRecord {
pubkey: keypair.pk.clone(),
withdrawal_credentials: Hash256::zero(),
randao_commitment: Hash256::zero(),
randao_layers: 0,
activation_slot: u64::max_value(),
exit_slot: u64::max_value(),
withdrawal_slot: u64::max_value(),
penalized_slot: u64::max_value(),
exit_count: 0,
status_flags: None,
custody_commitment: Hash256::zero(),
latest_custody_reseed_slot: 0,
penultimate_custody_reseed_slot: 0,
};
initial_validators.push(validator_record);
}
initial_validators
}
fn initial_balances_for_testing() -> Vec<u64> {
vec![DEPOSIT_GWEI; 4]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_foundation_spec_can_be_constructed() {
let _ = ChainSpec::foundation();
}
}

74
eth2/spec/src/lib.rs Normal file
View File

@@ -0,0 +1,74 @@
extern crate bls;
extern crate types;
mod foundation;
use bls::Signature;
use types::{Address, Hash256, ValidatorRecord};
#[derive(PartialEq, Debug)]
pub struct ChainSpec {
/*
* Misc
*/
pub shard_count: u64,
pub target_committee_size: u64,
pub ejection_balance: u64,
pub max_balance_churn_quotient: u64,
pub gwei_per_eth: u64,
pub beacon_chain_shard_number: u64,
pub max_casper_votes: u64,
pub latest_block_roots_length: u64,
pub latest_randao_mixes_length: u64,
pub latest_penalized_exit_length: u64,
pub max_withdrawals_per_epoch: u64,
/*
* Deposit contract
*/
pub deposit_contract_address: Address,
pub deposit_contract_tree_depth: u64,
pub min_deposit: u64,
pub max_deposit: u64,
/*
* Initial Values
*/
pub genesis_fork_version: u64,
pub genesis_slot_number: u64,
pub genesis_start_shard: u64,
pub far_future_slot: u64,
pub zero_hash: Hash256,
pub empty_signature: Signature,
pub bls_withdrawal_prefix_byte: u8,
/*
* Time parameters
*/
pub slot_duration: u64,
pub min_attestation_inclusion_delay: u64,
pub epoch_length: u64,
pub seed_lookahead: u64,
pub entry_exit_delay: u64,
pub pow_receipt_root_voting_period: u64, // a.k.a. deposit_root_voting_period
pub min_validator_withdrawal_time: u64,
/*
* Reward and penalty quotients
*/
pub base_reward_quotient: u64,
pub whistleblower_reward_quotient: u64,
pub includer_reward_quotient: u64,
pub inactivity_penalty_quotient: u64,
/*
* Max operations per block
*/
pub max_proposer_slashings: u64,
pub max_casper_slashings: u64,
pub max_attestations: u64,
pub max_deposits: u64,
pub max_exits: u64,
/*
* Intialization parameters
*/
pub initial_validators: Vec<ValidatorRecord>,
pub initial_balances: Vec<u64>,
pub genesis_time: u64,
pub processed_pow_receipt_root: Hash256,
}

13
eth2/types/Cargo.toml Normal file
View File

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

View File

@@ -0,0 +1,79 @@
use super::bls::AggregateSignature;
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::{AttestationData, Bitfield};
use crate::test_utils::TestRandom;
use rand::RngCore;
#[derive(Debug, Clone, PartialEq)]
pub struct Attestation {
pub data: AttestationData,
pub participation_bitfield: Bitfield,
pub custody_bitfield: Bitfield,
pub aggregate_sig: AggregateSignature,
}
impl Encodable for Attestation {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.data);
s.append(&self.participation_bitfield);
s.append(&self.custody_bitfield);
s.append(&self.aggregate_sig);
}
}
impl Decodable for Attestation {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (data, i) = AttestationData::ssz_decode(bytes, i)?;
let (participation_bitfield, i) = Bitfield::ssz_decode(bytes, i)?;
let (custody_bitfield, i) = Bitfield::ssz_decode(bytes, i)?;
let (aggregate_sig, i) = AggregateSignature::ssz_decode(bytes, i)?;
let attestation_record = Self {
data,
participation_bitfield,
custody_bitfield,
aggregate_sig,
};
Ok((attestation_record, i))
}
}
impl Attestation {
pub fn zero() -> Self {
Self {
data: AttestationData::zero(),
participation_bitfield: Bitfield::new(),
custody_bitfield: Bitfield::new(),
aggregate_sig: AggregateSignature::new(),
}
}
}
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)]
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 = Attestation::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,119 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::Hash256;
use crate::test_utils::TestRandom;
use rand::RngCore;
pub const SSZ_ATTESTION_DATA_LENGTH: usize = {
8 + // slot
8 + // shard
32 + // beacon_block_hash
32 + // epoch_boundary_hash
32 + // shard_block_hash
32 + // latest_crosslink_hash
8 + // justified_slot
32 // justified_block_hash
};
#[derive(Debug, Clone, PartialEq, Default)]
pub struct AttestationData {
pub slot: u64,
pub shard: u64,
pub beacon_block_hash: Hash256,
pub epoch_boundary_hash: Hash256,
pub shard_block_hash: Hash256,
pub latest_crosslink_hash: Hash256,
pub justified_slot: u64,
pub justified_block_hash: Hash256,
}
impl AttestationData {
pub fn zero() -> Self {
Self {
slot: 0,
shard: 0,
beacon_block_hash: Hash256::zero(),
epoch_boundary_hash: Hash256::zero(),
shard_block_hash: Hash256::zero(),
latest_crosslink_hash: Hash256::zero(),
justified_slot: 0,
justified_block_hash: Hash256::zero(),
}
}
// TODO: Implement this as a merkle root, once tree_ssz is implemented.
// https://github.com/sigp/lighthouse/issues/92
pub fn canonical_root(&self) -> Hash256 {
Hash256::zero()
}
}
impl Encodable for AttestationData {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.slot);
s.append(&self.shard);
s.append(&self.beacon_block_hash);
s.append(&self.epoch_boundary_hash);
s.append(&self.shard_block_hash);
s.append(&self.latest_crosslink_hash);
s.append(&self.justified_slot);
s.append(&self.justified_block_hash);
}
}
impl Decodable for AttestationData {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (slot, i) = u64::ssz_decode(bytes, i)?;
let (shard, i) = u64::ssz_decode(bytes, i)?;
let (beacon_block_hash, i) = Hash256::ssz_decode(bytes, i)?;
let (epoch_boundary_hash, i) = Hash256::ssz_decode(bytes, i)?;
let (shard_block_hash, i) = Hash256::ssz_decode(bytes, i)?;
let (latest_crosslink_hash, i) = Hash256::ssz_decode(bytes, i)?;
let (justified_slot, i) = u64::ssz_decode(bytes, i)?;
let (justified_block_hash, i) = Hash256::ssz_decode(bytes, i)?;
let attestation_data = AttestationData {
slot,
shard,
beacon_block_hash,
epoch_boundary_hash,
shard_block_hash,
latest_crosslink_hash,
justified_slot,
justified_block_hash,
};
Ok((attestation_data, i))
}
}
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)]
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 = AttestationData::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 rand::RngCore;
use crate::test_utils::TestRandom;
use super::AttestationData;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct AttestationDataAndCustodyBit {
pub data: AttestationData,
pub custody_bit: bool,
}
impl Encodable for AttestationDataAndCustodyBit {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.data);
s.append(&self.custody_bit);
}
}
impl Decodable for AttestationDataAndCustodyBit {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (data, i) = <_>::ssz_decode(bytes, i)?;
let (custody_bit, i) = <_>::ssz_decode(bytes, i)?;
let attestation_data_and_custody_bit = AttestationDataAndCustodyBit {
data,
custody_bit,
};
Ok((attestation_data_and_custody_bit, i))
}
}
impl<T: RngCore> TestRandom<T> for AttestationDataAndCustodyBit {
fn random_for_test(rng: &mut T) -> Self {
Self {
data: <_>::random_for_test(rng),
custody_bit: <_>::random_for_test(rng),
}
}
}
#[cfg(test)]
mod test {
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use super::*;
use super::super::ssz::ssz_encode;
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = AttestationDataAndCustodyBit::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,94 @@
use super::ssz::{ssz_encode, Decodable, DecodeError, Encodable, SszStream};
use super::{BeaconBlockBody, Hash256};
use crate::test_utils::TestRandom;
use bls::Signature;
use hashing::canonical_hash;
use rand::RngCore;
#[derive(Debug, PartialEq, Clone)]
pub struct BeaconBlock {
pub slot: u64,
pub parent_root: Hash256,
pub state_root: Hash256,
pub randao_reveal: Hash256,
pub candidate_pow_receipt_root: Hash256,
pub signature: Signature,
pub body: BeaconBlockBody,
}
impl BeaconBlock {
pub fn canonical_root(&self) -> Hash256 {
// TODO: implement tree hashing.
// https://github.com/sigp/lighthouse/issues/70
Hash256::from(&canonical_hash(&ssz_encode(self))[..])
}
}
impl Encodable for BeaconBlock {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.slot);
s.append(&self.parent_root);
s.append(&self.state_root);
s.append(&self.randao_reveal);
s.append(&self.candidate_pow_receipt_root);
s.append(&self.signature);
s.append(&self.body);
}
}
impl Decodable for BeaconBlock {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (slot, i) = <_>::ssz_decode(bytes, i)?;
let (parent_root, i) = <_>::ssz_decode(bytes, i)?;
let (state_root, i) = <_>::ssz_decode(bytes, i)?;
let (randao_reveal, i) = <_>::ssz_decode(bytes, i)?;
let (candidate_pow_receipt_root, i) = <_>::ssz_decode(bytes, i)?;
let (signature, i) = <_>::ssz_decode(bytes, i)?;
let (body, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
slot,
parent_root,
state_root,
randao_reveal,
candidate_pow_receipt_root,
signature,
body,
},
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)]
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 = BeaconBlock::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,95 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::{Attestation, CasperSlashing, Deposit, Exit, ProposerSlashing};
use crate::test_utils::TestRandom;
use rand::RngCore;
// The following types are just dummy classes as they will not be defined until
// Phase 1 (Sharding phase)
type CustodyReseed = usize;
type CustodyChallenge = usize;
type CustodyResponse = usize;
#[derive(Debug, PartialEq, Clone, Default)]
pub struct BeaconBlockBody {
pub proposer_slashings: Vec<ProposerSlashing>,
pub casper_slashings: Vec<CasperSlashing>,
pub attestations: Vec<Attestation>,
pub custody_reseeds: Vec<CustodyReseed>,
pub custody_challenges: Vec<CustodyChallenge>,
pub custody_responses: Vec<CustodyResponse>,
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.custody_reseeds);
s.append_vec(&self.custody_challenges);
s.append_vec(&self.custody_responses);
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 (custody_reseeds, i) = <_>::ssz_decode(bytes, i)?;
let (custody_challenges, i) = <_>::ssz_decode(bytes, i)?;
let (custody_responses, 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,
custody_reseeds,
custody_challenges,
custody_responses,
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),
custody_reseeds: <_>::random_for_test(rng),
custody_challenges: <_>::random_for_test(rng),
custody_responses: <_>::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::{SeedableRng, TestRandom, XorShiftRng};
#[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,219 @@
use super::candidate_pow_receipt_root_record::CandidatePoWReceiptRootRecord;
use super::crosslink_record::CrosslinkRecord;
use super::fork_data::ForkData;
use super::pending_attestation_record::PendingAttestationRecord;
use super::validator_record::ValidatorRecord;
use super::Hash256;
use crate::test_utils::TestRandom;
use hashing::canonical_hash;
use rand::RngCore;
use ssz::{ssz_encode, Decodable, DecodeError, Encodable, SszStream};
// Custody will not be added to the specs until Phase 1 (Sharding Phase) so dummy class used.
type CustodyChallenge = usize;
#[derive(Debug, PartialEq, Clone, Default)]
pub struct BeaconState {
// Misc
pub slot: u64,
pub genesis_time: u64,
pub fork_data: ForkData,
// Validator registry
pub validator_registry: Vec<ValidatorRecord>,
pub validator_balances: Vec<u64>,
pub validator_registry_latest_change_slot: u64,
pub validator_registry_exit_count: u64,
pub validator_registry_delta_chain_tip: Hash256,
// Randomness and committees
pub latest_randao_mixes: Vec<Hash256>,
pub latest_vdf_outputs: Vec<Hash256>,
pub previous_epoch_start_shard: u64,
pub current_epoch_start_shard: u64,
pub previous_epoch_calculation_slot: u64,
pub current_epoch_calculation_slot: u64,
pub previous_epoch_randao_mix: Hash256,
pub current_epoch_randao_mix: Hash256,
// Custody challenges
pub custody_challenges: Vec<CustodyChallenge>,
// Finality
pub previous_justified_slot: u64,
pub justified_slot: u64,
pub justification_bitfield: u64,
pub finalized_slot: u64,
// Recent state
pub latest_crosslinks: Vec<CrosslinkRecord>,
pub latest_block_roots: Vec<Hash256>,
pub latest_penalized_exit_balances: Vec<u64>,
pub latest_attestations: Vec<PendingAttestationRecord>,
pub batched_block_roots: Vec<Hash256>,
// PoW receipt root (a.k.a. deposit root)
pub processed_pow_receipt_root: Hash256,
pub candidate_pow_receipt_roots: Vec<CandidatePoWReceiptRootRecord>,
}
impl BeaconState {
pub fn canonical_root(&self) -> Hash256 {
// TODO: implement tree hashing.
// https://github.com/sigp/lighthouse/issues/70
Hash256::from(&canonical_hash(&ssz_encode(self))[..])
}
}
impl Encodable for BeaconState {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.slot);
s.append(&self.genesis_time);
s.append(&self.fork_data);
s.append(&self.validator_registry);
s.append(&self.validator_balances);
s.append(&self.validator_registry_latest_change_slot);
s.append(&self.validator_registry_exit_count);
s.append(&self.validator_registry_delta_chain_tip);
s.append(&self.latest_randao_mixes);
s.append(&self.latest_vdf_outputs);
s.append(&self.previous_epoch_start_shard);
s.append(&self.current_epoch_start_shard);
s.append(&self.previous_epoch_calculation_slot);
s.append(&self.current_epoch_calculation_slot);
s.append(&self.previous_epoch_randao_mix);
s.append(&self.current_epoch_randao_mix);
s.append(&self.custody_challenges);
s.append(&self.previous_justified_slot);
s.append(&self.justified_slot);
s.append(&self.justification_bitfield);
s.append(&self.finalized_slot);
s.append(&self.latest_crosslinks);
s.append(&self.latest_block_roots);
s.append(&self.latest_penalized_exit_balances);
s.append(&self.latest_attestations);
s.append(&self.batched_block_roots);
s.append(&self.processed_pow_receipt_root);
s.append(&self.candidate_pow_receipt_roots);
}
}
impl Decodable for BeaconState {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (slot, i) = <_>::ssz_decode(bytes, i)?;
let (genesis_time, i) = <_>::ssz_decode(bytes, i)?;
let (fork_data, i) = <_>::ssz_decode(bytes, i)?;
let (validator_registry, i) = <_>::ssz_decode(bytes, i)?;
let (validator_balances, i) = <_>::ssz_decode(bytes, i)?;
let (validator_registry_latest_change_slot, i) = <_>::ssz_decode(bytes, i)?;
let (validator_registry_exit_count, i) = <_>::ssz_decode(bytes, i)?;
let (validator_registry_delta_chain_tip, i) = <_>::ssz_decode(bytes, i)?;
let (latest_randao_mixes, i) = <_>::ssz_decode(bytes, i)?;
let (latest_vdf_outputs, i) = <_>::ssz_decode(bytes, i)?;
let (previous_epoch_start_shard, i) = <_>::ssz_decode(bytes, i)?;
let (current_epoch_start_shard, i) = <_>::ssz_decode(bytes, i)?;
let (previous_epoch_calculation_slot, i) = <_>::ssz_decode(bytes, i)?;
let (current_epoch_calculation_slot, i) = <_>::ssz_decode(bytes, i)?;
let (previous_epoch_randao_mix, i) = <_>::ssz_decode(bytes, i)?;
let (current_epoch_randao_mix, i) = <_>::ssz_decode(bytes, i)?;
let (custody_challenges, i) = <_>::ssz_decode(bytes, i)?;
let (previous_justified_slot, i) = <_>::ssz_decode(bytes, i)?;
let (justified_slot, i) = <_>::ssz_decode(bytes, i)?;
let (justification_bitfield, i) = <_>::ssz_decode(bytes, i)?;
let (finalized_slot, i) = <_>::ssz_decode(bytes, i)?;
let (latest_crosslinks, i) = <_>::ssz_decode(bytes, i)?;
let (latest_block_roots, i) = <_>::ssz_decode(bytes, i)?;
let (latest_penalized_exit_balances, i) = <_>::ssz_decode(bytes, i)?;
let (latest_attestations, i) = <_>::ssz_decode(bytes, i)?;
let (batched_block_roots, i) = <_>::ssz_decode(bytes, i)?;
let (processed_pow_receipt_root, i) = <_>::ssz_decode(bytes, i)?;
let (candidate_pow_receipt_roots, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
slot,
genesis_time,
fork_data,
validator_registry,
validator_balances,
validator_registry_latest_change_slot,
validator_registry_exit_count,
validator_registry_delta_chain_tip,
latest_randao_mixes,
latest_vdf_outputs,
previous_epoch_start_shard,
current_epoch_start_shard,
previous_epoch_calculation_slot,
current_epoch_calculation_slot,
previous_epoch_randao_mix,
current_epoch_randao_mix,
custody_challenges,
previous_justified_slot,
justified_slot,
justification_bitfield,
finalized_slot,
latest_crosslinks,
latest_block_roots,
latest_penalized_exit_balances,
latest_attestations,
batched_block_roots,
processed_pow_receipt_root,
candidate_pow_receipt_roots,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for BeaconState {
fn random_for_test(rng: &mut T) -> Self {
Self {
slot: <_>::random_for_test(rng),
genesis_time: <_>::random_for_test(rng),
fork_data: <_>::random_for_test(rng),
validator_registry: <_>::random_for_test(rng),
validator_balances: <_>::random_for_test(rng),
validator_registry_latest_change_slot: <_>::random_for_test(rng),
validator_registry_exit_count: <_>::random_for_test(rng),
validator_registry_delta_chain_tip: <_>::random_for_test(rng),
latest_randao_mixes: <_>::random_for_test(rng),
latest_vdf_outputs: <_>::random_for_test(rng),
previous_epoch_start_shard: <_>::random_for_test(rng),
current_epoch_start_shard: <_>::random_for_test(rng),
previous_epoch_calculation_slot: <_>::random_for_test(rng),
current_epoch_calculation_slot: <_>::random_for_test(rng),
previous_epoch_randao_mix: <_>::random_for_test(rng),
current_epoch_randao_mix: <_>::random_for_test(rng),
custody_challenges: <_>::random_for_test(rng),
previous_justified_slot: <_>::random_for_test(rng),
justified_slot: <_>::random_for_test(rng),
justification_bitfield: <_>::random_for_test(rng),
finalized_slot: <_>::random_for_test(rng),
latest_crosslinks: <_>::random_for_test(rng),
latest_block_roots: <_>::random_for_test(rng),
latest_penalized_exit_balances: <_>::random_for_test(rng),
latest_attestations: <_>::random_for_test(rng),
batched_block_roots: <_>::random_for_test(rng),
processed_pow_receipt_root: <_>::random_for_test(rng),
candidate_pow_receipt_roots: <_>::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 = BeaconState::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::Hash256;
use crate::test_utils::TestRandom;
use rand::RngCore;
// Note: this is refer to as DepositRootVote in specs
#[derive(Debug, PartialEq, Clone)]
pub struct CandidatePoWReceiptRootRecord {
pub candidate_pow_receipt_root: Hash256,
pub votes: u64,
}
impl Encodable for CandidatePoWReceiptRootRecord {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.candidate_pow_receipt_root);
s.append(&self.votes);
}
}
impl Decodable for CandidatePoWReceiptRootRecord {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (candidate_pow_receipt_root, i) = <_>::ssz_decode(bytes, i)?;
let (votes, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
candidate_pow_receipt_root,
votes,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for CandidatePoWReceiptRootRecord {
fn random_for_test(rng: &mut T) -> Self {
Self {
candidate_pow_receipt_root: <_>::random_for_test(rng),
votes: <_>::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 = CandidatePoWReceiptRootRecord::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,59 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::SlashableVoteData;
use crate::test_utils::TestRandom;
use rand::RngCore;
#[derive(Debug, PartialEq, Clone)]
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::{SeedableRng, TestRandom, XorShiftRng};
#[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,69 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::Hash256;
use crate::test_utils::TestRandom;
use rand::RngCore;
#[derive(Clone, Debug, PartialEq)]
pub struct CrosslinkRecord {
pub slot: u64,
pub shard_block_root: Hash256,
}
impl CrosslinkRecord {
/// Generates a new instance where `dynasty` and `hash` are both zero.
pub fn zero() -> Self {
Self {
slot: 0,
shard_block_root: Hash256::zero(),
}
}
}
impl Encodable for CrosslinkRecord {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.slot);
s.append(&self.shard_block_root);
}
}
impl Decodable for CrosslinkRecord {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (slot, i) = <_>::ssz_decode(bytes, i)?;
let (shard_block_root, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
slot,
shard_block_root,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for CrosslinkRecord {
fn random_for_test(rng: &mut T) -> Self {
Self {
slot: <_>::random_for_test(rng),
shard_block_root: <_>::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 = CrosslinkRecord::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

64
eth2/types/src/deposit.rs Normal file
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,64 @@
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::{SeedableRng, TestRandom, XorShiftRng};
#[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,75 @@
use super::ssz::{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 custody_commitment: Hash256,
pub proof_of_possession: Signature,
}
impl Encodable for DepositInput {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.pubkey);
s.append(&self.withdrawal_credentials);
s.append(&self.randao_commitment);
s.append(&self.custody_commitment);
s.append(&self.proof_of_possession);
}
}
impl Decodable for DepositInput {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (pubkey, i) = <_>::ssz_decode(bytes, i)?;
let (withdrawal_credentials, i) = <_>::ssz_decode(bytes, i)?;
let (randao_commitment, i) = <_>::ssz_decode(bytes, i)?;
let (custody_commitment, i) = <_>::ssz_decode(bytes, i)?;
let (proof_of_possession, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
pubkey,
withdrawal_credentials,
randao_commitment,
custody_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),
custody_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::{SeedableRng, TestRandom, XorShiftRng};
#[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);
}
}

64
eth2/types/src/exit.rs Normal file
View File

@@ -0,0 +1,64 @@
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::{SeedableRng, TestRandom, XorShiftRng};
#[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

@@ -0,0 +1,63 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use crate::test_utils::TestRandom;
use rand::RngCore;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ForkData {
pub pre_fork_version: u64,
pub post_fork_version: u64,
pub fork_slot: u64,
}
impl Encodable for ForkData {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.pre_fork_version);
s.append(&self.post_fork_version);
s.append(&self.fork_slot);
}
}
impl Decodable for ForkData {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (pre_fork_version, i) = <_>::ssz_decode(bytes, i)?;
let (post_fork_version, i) = <_>::ssz_decode(bytes, i)?;
let (fork_slot, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
pre_fork_version,
post_fork_version,
fork_slot,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for ForkData {
fn random_for_test(rng: &mut T) -> Self {
Self {
pre_fork_version: <_>::random_for_test(rng),
post_fork_version: <_>::random_for_test(rng),
fork_slot: <_>::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 = ForkData::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

68
eth2/types/src/lib.rs Normal file
View File

@@ -0,0 +1,68 @@
extern crate bls;
extern crate boolean_bitfield;
extern crate ethereum_types;
extern crate ssz;
pub mod test_utils;
pub mod attestation;
pub mod attestation_data;
pub mod beacon_block;
pub mod beacon_block_body;
pub mod beacon_state;
pub mod candidate_pow_receipt_root_record;
pub mod casper_slashing;
pub mod crosslink_record;
pub mod deposit;
pub mod deposit_data;
pub mod deposit_input;
pub mod exit;
pub mod fork_data;
pub mod pending_attestation_record;
pub mod proposal_signed_data;
pub mod proposer_slashing;
pub mod shard_committee;
pub mod shard_reassignment_record;
pub mod slashable_vote_data;
pub mod special_record;
pub mod validator_record;
pub mod validator_registry;
pub mod readers;
use self::ethereum_types::{H160, H256, U256};
use std::collections::HashMap;
pub use crate::attestation::Attestation;
pub use crate::attestation_data::AttestationData;
pub use crate::beacon_block::BeaconBlock;
pub use crate::beacon_block_body::BeaconBlockBody;
pub use crate::beacon_state::BeaconState;
pub use crate::casper_slashing::CasperSlashing;
pub use crate::crosslink_record::CrosslinkRecord;
pub use crate::deposit::Deposit;
pub use crate::deposit_data::DepositData;
pub use crate::deposit_input::DepositInput;
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::shard_committee::ShardCommittee;
pub use crate::slashable_vote_data::SlashableVoteData;
pub use crate::special_record::{SpecialRecord, SpecialRecordKind};
pub use crate::validator_record::{StatusFlags as ValidatorStatusFlags, ValidatorRecord};
pub type Hash256 = H256;
pub type Address = H160;
pub type EthBalance = U256;
pub type Bitfield = boolean_bitfield::BooleanBitfield;
pub type BitfieldError = boolean_bitfield::Error;
/// Maps a (slot, shard_id) to attestation_indices.
pub type AttesterMap = HashMap<(u64, u64), Vec<usize>>;
/// Maps a slot to a block proposer.
pub type ProposerMap = HashMap<u64, usize>;
pub use bls::{AggregatePublicKey, AggregateSignature, PublicKey, Signature};

View File

@@ -0,0 +1,69 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use super::{AttestationData, Bitfield};
use crate::test_utils::TestRandom;
use rand::RngCore;
#[derive(Debug, Clone, PartialEq)]
pub struct PendingAttestationRecord {
pub data: AttestationData,
pub participation_bitfield: Bitfield,
pub custody_bitfield: Bitfield,
pub slot_included: u64,
}
impl Encodable for PendingAttestationRecord {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.data);
s.append(&self.participation_bitfield);
s.append(&self.custody_bitfield);
s.append(&self.slot_included);
}
}
impl Decodable for PendingAttestationRecord {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (data, i) = <_>::ssz_decode(bytes, i)?;
let (participation_bitfield, i) = <_>::ssz_decode(bytes, i)?;
let (custody_bitfield, i) = <_>::ssz_decode(bytes, i)?;
let (slot_included, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
data,
participation_bitfield,
custody_bitfield,
slot_included,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for PendingAttestationRecord {
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),
slot_included: <_>::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 = PendingAttestationRecord::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::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::{SeedableRng, TestRandom, XorShiftRng};
#[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,75 @@
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)]
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::{SeedableRng, TestRandom, XorShiftRng};
#[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,40 @@
use crate::{BeaconBlock, Hash256};
use std::fmt::Debug;
/// The `BeaconBlockReader` provides interfaces for reading a subset of fields of a `BeaconBlock`.
///
/// The purpose of this trait is to allow reading from either;
/// - a standard `BeaconBlock` struct, or
/// - a SSZ serialized byte array.
///
/// Note: presently, direct SSZ reading has not been implemented so this trait is being used for
/// "future proofing".
pub trait BeaconBlockReader: Debug + PartialEq {
fn slot(&self) -> u64;
fn parent_root(&self) -> Hash256;
fn state_root(&self) -> Hash256;
fn canonical_root(&self) -> Hash256;
fn into_beacon_block(self) -> Option<BeaconBlock>;
}
impl BeaconBlockReader for BeaconBlock {
fn slot(&self) -> u64 {
self.slot
}
fn parent_root(&self) -> Hash256 {
self.parent_root
}
fn state_root(&self) -> Hash256 {
self.state_root
}
fn canonical_root(&self) -> Hash256 {
self.canonical_root()
}
fn into_beacon_block(self) -> Option<BeaconBlock> {
Some(self)
}
}

View File

@@ -0,0 +1,5 @@
mod block_reader;
mod state_reader;
pub use self::block_reader::BeaconBlockReader;
pub use self::state_reader::BeaconStateReader;

View File

@@ -0,0 +1,30 @@
use crate::{BeaconState, Hash256};
use std::fmt::Debug;
/// The `BeaconStateReader` provides interfaces for reading a subset of fields of a `BeaconState`.
///
/// The purpose of this trait is to allow reading from either;
/// - a standard `BeaconState` struct, or
/// - a SSZ serialized byte array.
///
/// Note: presently, direct SSZ reading has not been implemented so this trait is being used for
/// "future proofing".
pub trait BeaconStateReader: Debug + PartialEq {
fn slot(&self) -> u64;
fn canonical_root(&self) -> Hash256;
fn into_beacon_state(self) -> Option<BeaconState>;
}
impl BeaconStateReader for BeaconState {
fn slot(&self) -> u64 {
self.slot
}
fn canonical_root(&self) -> Hash256 {
self.canonical_root()
}
fn into_beacon_state(self) -> Option<BeaconState> {
Some(self)
}
}

View File

@@ -0,0 +1,52 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use crate::test_utils::TestRandom;
use rand::RngCore;
#[derive(Clone, Debug, PartialEq)]
pub struct ShardCommittee {
pub shard: u64,
pub committee: Vec<usize>,
}
impl Encodable for ShardCommittee {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.shard);
s.append(&self.committee);
}
}
impl Decodable for ShardCommittee {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (shard, i) = <_>::ssz_decode(bytes, i)?;
let (committee, i) = <_>::ssz_decode(bytes, i)?;
Ok((Self { shard, committee }, i))
}
}
impl<T: RngCore> TestRandom<T> for ShardCommittee {
fn random_for_test(rng: &mut T) -> Self {
Self {
shard: <_>::random_for_test(rng),
committee: <_>::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 = ShardCommittee::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,63 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
use crate::test_utils::TestRandom;
use rand::RngCore;
#[derive(Debug, PartialEq, Clone)]
pub struct ShardReassignmentRecord {
pub validator_index: u64,
pub shard: u64,
pub slot: u64,
}
impl Encodable for ShardReassignmentRecord {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.validator_index);
s.append(&self.shard);
s.append(&self.slot);
}
}
impl Decodable for ShardReassignmentRecord {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (validator_index, i) = <_>::ssz_decode(bytes, i)?;
let (shard, i) = <_>::ssz_decode(bytes, i)?;
let (slot, i) = <_>::ssz_decode(bytes, i)?;
Ok((
Self {
validator_index,
shard,
slot,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for ShardReassignmentRecord {
fn random_for_test(rng: &mut T) -> Self {
Self {
validator_index: <_>::random_for_test(rng),
shard: <_>::random_for_test(rng),
slot: <_>::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 = ShardReassignmentRecord::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,70 @@
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)]
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::{SeedableRng, TestRandom, XorShiftRng};
#[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,132 @@
use super::ssz::{Decodable, DecodeError, Encodable, SszStream};
/// The value of the "type" field of SpecialRecord.
///
/// Note: this value must serialize to a u8 and therefore must not be greater than 255.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum SpecialRecordKind {
Logout = 0,
CasperSlashing = 1,
RandaoChange = 2,
}
/// The structure used in the `BeaconBlock.specials` field.
#[derive(Debug, PartialEq, Clone)]
pub struct SpecialRecord {
pub kind: u8,
pub data: Vec<u8>,
}
impl SpecialRecord {
pub fn logout(data: &[u8]) -> Self {
Self {
kind: SpecialRecordKind::Logout as u8,
data: data.to_vec(),
}
}
pub fn casper_slashing(data: &[u8]) -> Self {
Self {
kind: SpecialRecordKind::CasperSlashing as u8,
data: data.to_vec(),
}
}
pub fn randao_change(data: &[u8]) -> Self {
Self {
kind: SpecialRecordKind::RandaoChange as u8,
data: data.to_vec(),
}
}
/// Match `self.kind` to a `SpecialRecordKind`.
///
/// Returns `None` if `self.kind` is an unknown value.
pub fn resolve_kind(&self) -> Option<SpecialRecordKind> {
match self.kind {
x if x == SpecialRecordKind::Logout as u8 => Some(SpecialRecordKind::Logout),
x if x == SpecialRecordKind::CasperSlashing as u8 => {
Some(SpecialRecordKind::CasperSlashing)
}
x if x == SpecialRecordKind::RandaoChange as u8 => {
Some(SpecialRecordKind::RandaoChange)
}
_ => None,
}
}
}
impl Encodable for SpecialRecord {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.kind);
s.append_vec(&self.data);
}
}
impl Decodable for SpecialRecord {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (kind, i) = u8::ssz_decode(bytes, i)?;
let (data, i) = Decodable::ssz_decode(bytes, i)?;
Ok((SpecialRecord { kind, data }, i))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_special_record_ssz_encode() {
let s = SpecialRecord::logout(&vec![]);
let mut ssz_stream = SszStream::new();
ssz_stream.append(&s);
let ssz = ssz_stream.drain();
assert_eq!(ssz, vec![0, 0, 0, 0, 0]);
let s = SpecialRecord::casper_slashing(&vec![]);
let mut ssz_stream = SszStream::new();
ssz_stream.append(&s);
let ssz = ssz_stream.drain();
assert_eq!(ssz, vec![1, 0, 0, 0, 0]);
let s = SpecialRecord::randao_change(&vec![]);
let mut ssz_stream = SszStream::new();
ssz_stream.append(&s);
let ssz = ssz_stream.drain();
assert_eq!(ssz, vec![2, 0, 0, 0, 0]);
let s = SpecialRecord::randao_change(&vec![42, 43, 44]);
let mut ssz_stream = SszStream::new();
ssz_stream.append(&s);
let ssz = ssz_stream.drain();
assert_eq!(ssz, vec![2, 0, 0, 0, 3, 42, 43, 44]);
}
#[test]
pub fn test_special_record_ssz_encode_decode() {
let s = SpecialRecord::randao_change(&vec![13, 16, 14]);
let mut ssz_stream = SszStream::new();
ssz_stream.append(&s);
let ssz = ssz_stream.drain();
let (s_decoded, _) = SpecialRecord::ssz_decode(&ssz, 0).unwrap();
assert_eq!(s, s_decoded);
}
#[test]
pub fn test_special_record_resolve_kind() {
let s = SpecialRecord::logout(&vec![]);
assert_eq!(s.resolve_kind(), Some(SpecialRecordKind::Logout));
let s = SpecialRecord::casper_slashing(&vec![]);
assert_eq!(s.resolve_kind(), Some(SpecialRecordKind::CasperSlashing));
let s = SpecialRecord::randao_change(&vec![]);
assert_eq!(s.resolve_kind(), Some(SpecialRecordKind::RandaoChange));
let s = SpecialRecord {
kind: 88,
data: vec![],
};
assert_eq!(s.resolve_kind(), None);
}
}

View File

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

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,49 @@
use rand::RngCore;
pub use rand::{prng::XorShiftRng, SeedableRng};
pub mod address;
pub mod aggregate_signature;
pub mod bitfield;
pub mod hash256;
pub mod public_key;
pub mod secret_key;
pub mod signature;
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> TestRandom<T> for usize {
fn random_for_test(rng: &mut T) -> Self {
rng.next_u32() as usize
}
}
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

@@ -0,0 +1,213 @@
use super::bls::PublicKey;
use super::Hash256;
use crate::test_utils::TestRandom;
use rand::RngCore;
use ssz::{Decodable, DecodeError, Encodable, SszStream};
const STATUS_FLAG_INITIATED_EXIT: u8 = 1;
const STATUS_FLAG_WITHDRAWABLE: u8 = 2;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum StatusFlags {
InitiatedExit,
Withdrawable,
}
struct StatusFlagsDecodeError;
impl From<StatusFlagsDecodeError> for DecodeError {
fn from(_: StatusFlagsDecodeError) -> DecodeError {
DecodeError::Invalid
}
}
/// Handles the serialization logic for the `status_flags` field of the `ValidatorRecord`.
fn status_flag_to_byte(flag: Option<StatusFlags>) -> u8 {
if let Some(flag) = flag {
match flag {
StatusFlags::InitiatedExit => STATUS_FLAG_INITIATED_EXIT,
StatusFlags::Withdrawable => STATUS_FLAG_WITHDRAWABLE,
}
} else {
0
}
}
/// Handles the deserialization logic for the `status_flags` field of the `ValidatorRecord`.
fn status_flag_from_byte(flag: u8) -> Result<Option<StatusFlags>, StatusFlagsDecodeError> {
match flag {
0 => Ok(None),
1 => Ok(Some(StatusFlags::InitiatedExit)),
2 => Ok(Some(StatusFlags::Withdrawable)),
_ => Err(StatusFlagsDecodeError),
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ValidatorRecord {
pub pubkey: PublicKey,
pub withdrawal_credentials: Hash256,
pub randao_commitment: Hash256,
pub randao_layers: u64,
pub activation_slot: u64,
pub exit_slot: u64,
pub withdrawal_slot: u64,
pub penalized_slot: u64,
pub exit_count: u64,
pub status_flags: Option<StatusFlags>,
pub custody_commitment: Hash256,
pub latest_custody_reseed_slot: u64,
pub penultimate_custody_reseed_slot: u64,
}
impl ValidatorRecord {
/// This predicate indicates if the validator represented by this record is considered "active" at `slot`.
pub fn is_active_at(&self, slot: u64) -> bool {
self.activation_slot <= slot && slot < self.exit_slot
}
}
impl Default for ValidatorRecord {
/// Yields a "default" `ValidatorRecord`. Primarily used for testing.
fn default() -> Self {
Self {
pubkey: PublicKey::default(),
withdrawal_credentials: Hash256::default(),
randao_commitment: Hash256::default(),
randao_layers: 0,
activation_slot: std::u64::MAX,
exit_slot: std::u64::MAX,
withdrawal_slot: std::u64::MAX,
penalized_slot: std::u64::MAX,
exit_count: 0,
status_flags: None,
custody_commitment: Hash256::default(),
latest_custody_reseed_slot: 0, // NOTE: is `GENESIS_SLOT`
penultimate_custody_reseed_slot: 0, // NOTE: is `GENESIS_SLOT`
}
}
}
impl<T: RngCore> TestRandom<T> for StatusFlags {
fn random_for_test(rng: &mut T) -> Self {
let options = vec![StatusFlags::InitiatedExit, StatusFlags::Withdrawable];
options[(rng.next_u32() as usize) % options.len()].clone()
}
}
impl Encodable for ValidatorRecord {
fn ssz_append(&self, s: &mut SszStream) {
s.append(&self.pubkey);
s.append(&self.withdrawal_credentials);
s.append(&self.randao_commitment);
s.append(&self.randao_layers);
s.append(&self.activation_slot);
s.append(&self.exit_slot);
s.append(&self.withdrawal_slot);
s.append(&self.penalized_slot);
s.append(&self.exit_count);
s.append(&status_flag_to_byte(self.status_flags));
s.append(&self.custody_commitment);
s.append(&self.latest_custody_reseed_slot);
s.append(&self.penultimate_custody_reseed_slot);
}
}
impl Decodable for ValidatorRecord {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (pubkey, i) = <_>::ssz_decode(bytes, i)?;
let (withdrawal_credentials, i) = <_>::ssz_decode(bytes, i)?;
let (randao_commitment, i) = <_>::ssz_decode(bytes, i)?;
let (randao_layers, i) = <_>::ssz_decode(bytes, i)?;
let (activation_slot, i) = <_>::ssz_decode(bytes, i)?;
let (exit_slot, i) = <_>::ssz_decode(bytes, i)?;
let (withdrawal_slot, i) = <_>::ssz_decode(bytes, i)?;
let (penalized_slot, i) = <_>::ssz_decode(bytes, i)?;
let (exit_count, i) = <_>::ssz_decode(bytes, i)?;
let (status_flags_byte, i): (u8, usize) = <_>::ssz_decode(bytes, i)?;
let (custody_commitment, i) = <_>::ssz_decode(bytes, i)?;
let (latest_custody_reseed_slot, i) = <_>::ssz_decode(bytes, i)?;
let (penultimate_custody_reseed_slot, i) = <_>::ssz_decode(bytes, i)?;
let status_flags = status_flag_from_byte(status_flags_byte)?;
Ok((
Self {
pubkey,
withdrawal_credentials,
randao_commitment,
randao_layers,
activation_slot,
exit_slot,
withdrawal_slot,
penalized_slot,
exit_count,
status_flags,
custody_commitment,
latest_custody_reseed_slot,
penultimate_custody_reseed_slot,
},
i,
))
}
}
impl<T: RngCore> TestRandom<T> for ValidatorRecord {
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),
randao_layers: <_>::random_for_test(rng),
activation_slot: <_>::random_for_test(rng),
exit_slot: <_>::random_for_test(rng),
withdrawal_slot: <_>::random_for_test(rng),
penalized_slot: <_>::random_for_test(rng),
exit_count: <_>::random_for_test(rng),
status_flags: Some(<_>::random_for_test(rng)),
custody_commitment: <_>::random_for_test(rng),
latest_custody_reseed_slot: <_>::random_for_test(rng),
penultimate_custody_reseed_slot: <_>::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 = ValidatorRecord::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
#[test]
fn test_validator_can_be_active() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut validator = ValidatorRecord::random_for_test(&mut rng);
let activation_slot = u64::random_for_test(&mut rng);
let exit_slot = activation_slot + 234;
validator.activation_slot = activation_slot;
validator.exit_slot = exit_slot;
for slot in (activation_slot - 100)..(exit_slot + 100) {
if slot < activation_slot {
assert!(!validator.is_active_at(slot));
} else if slot >= exit_slot {
assert!(!validator.is_active_at(slot));
} else {
assert!(validator.is_active_at(slot));
}
}
}
}

View File

@@ -0,0 +1,12 @@
use super::{Address, Hash256};
use bls::{PublicKey, Signature};
/// The information gathered from the PoW chain validator registration function.
#[derive(Debug, Clone, PartialEq)]
pub struct ValidatorRegistration {
pub pubkey: PublicKey,
pub withdrawal_shard: u64,
pub withdrawal_address: Address,
pub randao_commitment: Hash256,
pub proof_of_possession: Signature,
}

View File

@@ -0,0 +1,171 @@
/// Contains logic to manipulate a `&[ValidatorRecord]`.
/// For now, we avoid defining a newtype and just have flat functions here.
use super::validator_record::*;
/// Given an indexed sequence of `validators`, return the indices corresponding to validators that are active at `slot`.
pub fn get_active_validator_indices(validators: &[ValidatorRecord], slot: u64) -> Vec<usize> {
validators
.iter()
.enumerate()
.filter_map(|(index, validator)| {
if validator.is_active_at(slot) {
Some(index)
} else {
None
}
})
.collect::<Vec<_>>()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
fn can_get_empty_active_validator_indices() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let validators = vec![];
let some_slot = u64::random_for_test(&mut rng);
let indices = get_active_validator_indices(&validators, some_slot);
assert_eq!(indices, vec![]);
}
#[test]
fn can_get_no_active_validator_indices() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut validators = vec![];
let count_validators = 10;
for _ in 0..count_validators {
validators.push(ValidatorRecord::default())
}
let some_slot = u64::random_for_test(&mut rng);
let indices = get_active_validator_indices(&validators, some_slot);
assert_eq!(indices, vec![]);
}
#[test]
fn can_get_all_active_validator_indices() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let count_validators = 10;
let some_slot = u64::random_for_test(&mut rng);
let mut validators = (0..count_validators)
.into_iter()
.map(|_| {
let mut validator = ValidatorRecord::default();
let activation_offset = u64::random_for_test(&mut rng);
let exit_offset = u64::random_for_test(&mut rng);
validator.activation_slot = some_slot.checked_sub(activation_offset).unwrap_or(0);
validator.exit_slot = some_slot.checked_add(exit_offset).unwrap_or(std::u64::MAX);
validator
})
.collect::<Vec<_>>();
// test boundary condition by ensuring that at least one validator in the list just activated
if let Some(validator) = validators.get_mut(0) {
validator.activation_slot = some_slot;
}
let indices = get_active_validator_indices(&validators, some_slot);
assert_eq!(
indices,
(0..count_validators).into_iter().collect::<Vec<_>>()
);
}
fn set_validators_to_default_entry_exit(validators: &mut [ValidatorRecord]) {
for validator in validators.iter_mut() {
validator.activation_slot = std::u64::MAX;
validator.exit_slot = std::u64::MAX;
}
}
// sets all `validators` to be active as of some slot prior to `slot`. returns the activation slot.
fn set_validators_to_activated(validators: &mut [ValidatorRecord], slot: u64) -> u64 {
let activation_slot = slot - 10;
for validator in validators.iter_mut() {
validator.activation_slot = activation_slot;
}
activation_slot
}
// sets all `validators` to be exited as of some slot before `slot`.
fn set_validators_to_exited(
validators: &mut [ValidatorRecord],
slot: u64,
activation_slot: u64,
) {
assert!(activation_slot < slot);
let mut exit_slot = activation_slot + 10;
while exit_slot >= slot {
exit_slot -= 1;
}
assert!(activation_slot < exit_slot && exit_slot < slot);
for validator in validators.iter_mut() {
validator.exit_slot = exit_slot;
}
}
#[test]
fn can_get_some_active_validator_indices() {
let mut rng = XorShiftRng::from_seed([42; 16]);
const COUNT_PARTITIONS: usize = 3;
const COUNT_VALIDATORS: usize = 3 * COUNT_PARTITIONS;
let some_slot: u64 = u64::random_for_test(&mut rng);
let mut validators = (0..COUNT_VALIDATORS)
.into_iter()
.map(|_| {
let mut validator = ValidatorRecord::default();
let activation_offset = u64::random_for_test(&mut rng);
let exit_offset = u64::random_for_test(&mut rng);
validator.activation_slot = some_slot.checked_sub(activation_offset).unwrap_or(0);
validator.exit_slot = some_slot.checked_add(exit_offset).unwrap_or(std::u64::MAX);
validator
})
.collect::<Vec<_>>();
// we partition the set into partitions based on lifecycle:
for (i, chunk) in validators.chunks_exact_mut(COUNT_PARTITIONS).enumerate() {
match i {
0 => {
// 1. not activated (Default::default())
set_validators_to_default_entry_exit(chunk);
}
1 => {
// 2. activated, but not exited
set_validators_to_activated(chunk, some_slot);
// test boundary condition by ensuring that at least one validator in the list just activated
if let Some(validator) = chunk.get_mut(0) {
validator.activation_slot = some_slot;
}
}
2 => {
// 3. exited
let activation_slot = set_validators_to_activated(chunk, some_slot);
set_validators_to_exited(chunk, some_slot, activation_slot);
// test boundary condition by ensuring that at least one validator in the list just exited
if let Some(validator) = chunk.get_mut(0) {
validator.exit_slot = some_slot;
}
}
_ => unreachable!(
"constants local to this test not in sync with generation of test case"
),
}
}
let indices = get_active_validator_indices(&validators, some_slot);
assert_eq!(indices, vec![3, 4, 5]);
}
}

11
eth2/utils/bls/Cargo.toml Normal file
View File

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

View File

@@ -0,0 +1,65 @@
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, Default, Eq)]
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], aggregate_public_key: &AggregatePublicKey) -> bool {
self.0.verify(msg, aggregate_public_key)
}
}
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

@@ -0,0 +1,16 @@
use super::{PublicKey, SecretKey};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Keypair {
pub sk: SecretKey,
pub pk: PublicKey,
}
impl Keypair {
/// Instantiate a Keypair using SecretKey::random().
pub fn random() -> Self {
let sk = SecretKey::random();
let pk = PublicKey::from_secret_key(&sk);
Keypair { sk, pk }
}
}

42
eth2/utils/bls/src/lib.rs Normal file
View File

@@ -0,0 +1,42 @@
extern crate bls_aggregates;
extern crate hashing;
extern crate ssz;
mod aggregate_signature;
mod keypair;
mod public_key;
mod secret_key;
mod signature;
pub use crate::aggregate_signature::AggregateSignature;
pub use crate::keypair::Keypair;
pub use crate::public_key::PublicKey;
pub use crate::secret_key::SecretKey;
pub use crate::signature::Signature;
pub use self::bls_aggregates::AggregatePublicKey;
pub const BLS_AGG_SIG_BYTE_SIZE: usize = 97;
use hashing::canonical_hash;
use ssz::ssz_encode;
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
/// was signed by the secret key that corresponds to that public key.
pub fn verify_proof_of_possession(sig: &Signature, pubkey: &PublicKey) -> bool {
let mut hash = canonical_hash(&ssz_encode(pubkey));
extend_if_needed(&mut hash);
sig.verify_hashed(&hash, &pubkey)
}
pub fn create_proof_of_possession(keypair: &Keypair) -> Signature {
let mut hash = canonical_hash(&ssz_encode(&keypair.pk));
extend_if_needed(&mut hash);
Signature::new_hashed(&hash, &keypair.sk)
}

View File

@@ -0,0 +1,83 @@
use super::SecretKey;
use bls_aggregates::PublicKey as RawPublicKey;
use hex::encode as hex_encode;
use ssz::{decode_ssz_list, ssz_encode, Decodable, DecodeError, Encodable, SszStream};
use std::default;
use std::hash::{Hash, Hasher};
/// A single BLS signature.
///
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, Clone, Eq)]
pub struct PublicKey(RawPublicKey);
impl PublicKey {
pub fn from_secret_key(secret_key: &SecretKey) -> Self {
PublicKey(RawPublicKey::from_secret_key(secret_key.as_raw()))
}
/// Returns the underlying signature.
pub fn as_raw(&self) -> &RawPublicKey {
&self.0
}
/// Returns the last 6 bytes of the SSZ encoding of the public key, as a hex string.
///
/// Useful for providing a short identifier to the user.
pub fn concatenated_hex_id(&self) -> String {
let bytes = ssz_encode(self);
let end_bytes = &bytes[bytes.len().saturating_sub(6)..bytes.len()];
hex_encode(end_bytes)
}
}
impl default::Default for PublicKey {
fn default() -> Self {
let secret_key = SecretKey::random();
PublicKey::from_secret_key(&secret_key)
}
}
impl Encodable for PublicKey {
fn ssz_append(&self, s: &mut SszStream) {
s.append_vec(&self.0.as_bytes());
}
}
impl Decodable for PublicKey {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (sig_bytes, i) = decode_ssz_list(bytes, i)?;
let raw_sig = RawPublicKey::from_bytes(&sig_bytes).map_err(|_| DecodeError::TooShort)?;
Ok((PublicKey(raw_sig), i))
}
}
impl PartialEq for PublicKey {
fn eq(&self, other: &PublicKey) -> bool {
ssz_encode(self) == ssz_encode(other)
}
}
impl Hash for PublicKey {
fn hash<H: Hasher>(&self, state: &mut H) {
ssz_encode(self).hash(state)
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
#[test]
pub fn test_ssz_round_trip() {
let sk = SecretKey::random();
let original = PublicKey::from_secret_key(&sk);
let bytes = ssz_encode(&original);
let (decoded, _) = PublicKey::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@@ -0,0 +1,59 @@
use bls_aggregates::{DecodeError as BlsDecodeError, SecretKey as RawSecretKey};
use ssz::{decode_ssz_list, Decodable, DecodeError, Encodable, SszStream};
/// 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, Eq)]
pub struct SecretKey(RawSecretKey);
impl SecretKey {
pub fn random() -> Self {
SecretKey(RawSecretKey::random())
}
/// Instantiate a SecretKey from existing bytes.
///
/// Note: this is _not_ SSZ decoding.
pub fn from_bytes(bytes: &[u8]) -> Result<SecretKey, BlsDecodeError> {
Ok(SecretKey(RawSecretKey::from_bytes(bytes)?))
}
/// Returns the underlying secret key.
pub fn as_raw(&self) -> &RawSecretKey {
&self.0
}
}
impl Encodable for SecretKey {
fn ssz_append(&self, s: &mut SszStream) {
s.append_vec(&self.0.as_bytes());
}
}
impl Decodable for SecretKey {
fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> {
let (sig_bytes, i) = decode_ssz_list(bytes, i)?;
let raw_sig = RawSecretKey::from_bytes(&sig_bytes).map_err(|_| DecodeError::TooShort)?;
Ok((SecretKey(raw_sig), i))
}
}
#[cfg(test)]
mod tests {
use super::super::ssz::ssz_encode;
use super::*;
#[test]
pub fn test_ssz_round_trip() {
let original =
SecretKey::from_bytes("jzjxxgjajfjrmgodszzsgqccmhnyvetcuxobhtynojtpdtbj".as_bytes())
.unwrap();
let bytes = ssz_encode(&original);
let (decoded, _) = SecretKey::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@@ -0,0 +1,89 @@
use super::ssz::{decode_ssz_list, Decodable, DecodeError, Encodable, SszStream};
use super::{PublicKey, SecretKey};
use bls_aggregates::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, Eq)]
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.as_raw()))
}
/// 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.as_raw()))
}
/// Verify the Signature against a PublicKey.
pub fn verify(&self, msg: &[u8], pk: &PublicKey) -> bool {
self.0.verify(msg, pk.as_raw())
}
/// 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.as_raw())
}
/// Returns the underlying signature.
pub fn as_raw(&self) -> &RawSignature {
&self.0
}
/// Returns a new empty signature.
pub fn empty_signature() -> Self {
let empty: Vec<u8> = vec![0; 97];
Signature(RawSignature::from_bytes(&empty).unwrap())
}
}
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);
}
#[test]
pub fn test_empty_signature() {
let sig = Signature::empty_signature();
let sig_as_bytes: Vec<u8> = sig.as_raw().as_bytes();
assert_eq!(sig_as_bytes.len(), 97);
for one_byte in sig_as_bytes.iter() {
assert_eq!(*one_byte, 0);
}
}
}

View File

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

View File

@@ -0,0 +1,3 @@
# Boolean Bitfield
Implements a set of boolean as a tightly-packed vector of bits.

View File

@@ -0,0 +1,362 @@
extern crate bit_vec;
extern crate ssz;
use bit_vec::BitVec;
use std::cmp;
use std::default;
/// A BooleanBitfield represents a set of booleans compactly stored as a vector of bits.
/// The BooleanBitfield is given a fixed size during construction. Reads outside of the current size return an out-of-bounds error. Writes outside of the current size expand the size of the set.
#[derive(Debug, Clone)]
pub struct BooleanBitfield(BitVec);
/// Error represents some reason a request against a bitfield was not satisfied
#[derive(Debug, PartialEq)]
pub enum Error {
/// OutOfBounds refers to indexing into a bitfield where no bits exist; returns the illegal index and the current size of the bitfield, respectively
OutOfBounds(usize, usize),
}
impl BooleanBitfield {
/// Create a new bitfield.
pub fn new() -> Self {
Default::default()
}
pub fn with_capacity(initial_len: usize) -> Self {
Self::from_elem(initial_len, false)
}
/// Create a new bitfield with the given length `initial_len` and all values set to `bit`.
pub fn from_elem(inital_len: usize, bit: bool) -> Self {
Self {
0: BitVec::from_elem(inital_len, bit),
}
}
/// Create a new bitfield using the supplied `bytes` as input
pub fn from_bytes(bytes: &[u8]) -> Self {
Self {
0: BitVec::from_bytes(bytes),
}
}
/// Read the value of a bit.
///
/// If the index is in bounds, then result is Ok(value) where value is `true` if the bit is 1 and `false` if the bit is 0.
/// If the index is out of bounds, we return an error to that extent.
pub fn get(&self, i: usize) -> Result<bool, Error> {
match self.0.get(i) {
Some(value) => Ok(value),
None => Err(Error::OutOfBounds(i, self.0.len())),
}
}
/// Set the value of a bit.
///
/// If the index is out of bounds, we expand the size of the underlying set to include the new index.
/// Returns the previous value if there was one.
pub fn set(&mut self, i: usize, value: bool) -> Option<bool> {
let previous = match self.get(i) {
Ok(previous) => Some(previous),
Err(Error::OutOfBounds(_, len)) => {
let new_len = i - len + 1;
self.0.grow(new_len, false);
None
}
};
self.0.set(i, value);
previous
}
/// Returns the index of the highest set bit. Some(n) if some bit is set, None otherwise.
pub fn highest_set_bit(&self) -> Option<usize> {
self.0.iter().rposition(|bit| bit)
}
/// Returns the number of bits in this bitfield.
pub fn len(&self) -> usize {
self.0.len()
}
/// Returns the number of bytes required to represent this bitfield.
pub fn num_bytes(&self) -> usize {
self.to_bytes().len()
}
/// Returns the number of `1` bits in the bitfield
pub fn num_set_bits(&self) -> usize {
self.0.iter().filter(|&bit| bit).count()
}
/// Returns a vector of bytes representing the bitfield
/// Note that this returns the bit layout of the underlying implementation in the `bit-vec` crate.
pub fn to_bytes(&self) -> Vec<u8> {
self.0.to_bytes()
}
}
impl default::Default for BooleanBitfield {
/// default provides the "empty" bitfield
/// Note: the empty bitfield is set to the `0` byte.
fn default() -> Self {
Self::from_elem(8, false)
}
}
impl cmp::PartialEq for BooleanBitfield {
/// Determines equality by comparing the `ssz` encoding of the two candidates.
/// This method ensures that the presence of high-order (empty) bits in the highest byte do not exclude equality when they are in fact representing the same information.
fn eq(&self, other: &Self) -> bool {
ssz::ssz_encode(self) == ssz::ssz_encode(other)
}
}
impl ssz::Encodable for BooleanBitfield {
// ssz_append encodes Self according to the `ssz` spec.
fn ssz_append(&self, s: &mut ssz::SszStream) {
s.append_vec(&self.to_bytes())
}
}
impl ssz::Decodable for BooleanBitfield {
fn ssz_decode(bytes: &[u8], index: usize) -> Result<(Self, usize), ssz::DecodeError> {
let len = ssz::decode::decode_length(bytes, index, ssz::LENGTH_BYTES)?;
if (ssz::LENGTH_BYTES + len) > bytes.len() {
return Err(ssz::DecodeError::TooShort);
}
if len == 0 {
Ok((BooleanBitfield::new(), index + ssz::LENGTH_BYTES))
} else {
let bytes = &bytes[(index + 4)..(index + len + 4)];
let count = len * 8;
let mut field = BooleanBitfield::with_capacity(count);
for (byte_index, byte) in bytes.iter().enumerate() {
for i in 0..8 {
let bit = byte & (128 >> i);
if bit != 0 {
field.set(8 * byte_index + i, true);
}
}
}
let index = index + ssz::LENGTH_BYTES + len;
Ok((field, index))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ssz::{ssz_encode, Decodable, SszStream};
#[test]
fn test_new_bitfield() {
let mut field = BooleanBitfield::new();
let original_len = field.len();
for i in 0..100 {
if i < original_len {
assert!(!field.get(i).unwrap());
} else {
assert!(field.get(i).is_err());
}
let previous = field.set(i, true);
if i < original_len {
assert!(!previous.unwrap());
} else {
assert!(previous.is_none());
}
}
}
#[test]
fn test_empty_bitfield() {
let mut field = BooleanBitfield::from_elem(0, false);
let original_len = field.len();
assert_eq!(original_len, 0);
for i in 0..100 {
if i < original_len {
assert!(!field.get(i).unwrap());
} else {
assert!(field.get(i).is_err());
}
let previous = field.set(i, true);
if i < original_len {
assert!(!previous.unwrap());
} else {
assert!(previous.is_none());
}
}
assert_eq!(field.len(), 100);
assert_eq!(field.num_set_bits(), 100);
}
const INPUT: &[u8] = &[0b0000_0010, 0b0000_0010];
#[test]
fn test_get_from_bitfield() {
let field = BooleanBitfield::from_bytes(INPUT);
let unset = field.get(0).unwrap();
assert!(!unset);
let set = field.get(6).unwrap();
assert!(set);
let set = field.get(14).unwrap();
assert!(set);
}
#[test]
fn test_set_for_bitfield() {
let mut field = BooleanBitfield::from_bytes(INPUT);
let previous = field.set(10, true).unwrap();
assert!(!previous);
let previous = field.get(10).unwrap();
assert!(previous);
let previous = field.set(6, false).unwrap();
assert!(previous);
let previous = field.get(6).unwrap();
assert!(!previous);
}
#[test]
fn test_highest_set_bit() {
let field = BooleanBitfield::from_bytes(INPUT);
assert_eq!(field.highest_set_bit().unwrap(), 14);
let field = BooleanBitfield::from_bytes(&[0b0000_0011]);
assert_eq!(field.highest_set_bit().unwrap(), 7);
let field = BooleanBitfield::new();
assert_eq!(field.highest_set_bit(), None);
}
#[test]
fn test_len() {
let field = BooleanBitfield::from_bytes(INPUT);
assert_eq!(field.len(), 16);
let field = BooleanBitfield::new();
assert_eq!(field.len(), 8);
}
#[test]
fn test_num_set_bits() {
let field = BooleanBitfield::from_bytes(INPUT);
assert_eq!(field.num_set_bits(), 2);
let field = BooleanBitfield::new();
assert_eq!(field.num_set_bits(), 0);
}
#[test]
fn test_to_bytes() {
let field = BooleanBitfield::from_bytes(INPUT);
assert_eq!(field.to_bytes(), INPUT);
let field = BooleanBitfield::new();
assert_eq!(field.to_bytes(), vec![0]);
}
#[test]
fn test_out_of_bounds() {
let mut field = BooleanBitfield::from_bytes(INPUT);
let out_of_bounds_index = field.len();
assert!(field.set(out_of_bounds_index, true).is_none());
assert!(field.len() == out_of_bounds_index + 1);
assert!(field.get(out_of_bounds_index).unwrap());
for i in 0..100 {
if i <= out_of_bounds_index {
assert!(field.set(i, true).is_some());
} else {
assert!(field.set(i, true).is_none());
}
}
}
#[test]
fn test_grows_with_false() {
let input_all_set: &[u8] = &[0b1111_1111, 0b1111_1111];
let mut field = BooleanBitfield::from_bytes(input_all_set);
// Define `a` and `b`, where both are out of bounds and `b` is greater than `a`.
let a = field.len();
let b = a + 1;
// Ensure `a` is out-of-bounds for test integrity.
assert!(field.get(a).is_err());
// Set `b` to `true`. Also, for test integrity, ensure it was previously out-of-bounds.
assert!(field.set(b, true).is_none());
// Ensure that `a` wasn't also set to `true` during the grow.
assert_eq!(field.get(a), Ok(false));
assert_eq!(field.get(b), Ok(true));
}
#[test]
fn test_num_bytes() {
let field = BooleanBitfield::from_bytes(INPUT);
assert_eq!(field.num_bytes(), 2);
let field = BooleanBitfield::from_elem(2, true);
assert_eq!(field.num_bytes(), 1);
let field = BooleanBitfield::from_elem(13, true);
assert_eq!(field.num_bytes(), 2);
}
#[test]
fn test_ssz_encode() {
let field = create_test_bitfield();
let mut stream = SszStream::new();
stream.append(&field);
assert_eq!(stream.drain(), vec![0, 0, 0, 2, 225, 192]);
let field = BooleanBitfield::from_elem(18, true);
let mut stream = SszStream::new();
stream.append(&field);
assert_eq!(stream.drain(), vec![0, 0, 0, 3, 255, 255, 192]);
}
fn create_test_bitfield() -> BooleanBitfield {
let count = 2 * 8;
let mut field = BooleanBitfield::with_capacity(count);
let indices = &[0, 1, 2, 7, 8, 9];
for &i in indices {
field.set(i, true);
}
field
}
#[test]
fn test_ssz_decode() {
let encoded = vec![0, 0, 0, 2, 225, 192];
let (field, _): (BooleanBitfield, usize) = ssz::decode_ssz(&encoded, 0).unwrap();
let expected = create_test_bitfield();
assert_eq!(field, expected);
let encoded = vec![0, 0, 0, 3, 255, 255, 3];
let (field, _): (BooleanBitfield, usize) = ssz::decode_ssz(&encoded, 0).unwrap();
let expected = BooleanBitfield::from_bytes(&[255, 255, 3]);
assert_eq!(field, expected);
}
#[test]
fn test_ssz_round_trip() {
let original = BooleanBitfield::from_bytes(&vec![18; 12][..]);
let ssz = ssz_encode(&original);
let (decoded, _) = BooleanBitfield::ssz_decode(&ssz, 0).unwrap();
assert_eq!(original, decoded);
}
}

View File

@@ -0,0 +1,8 @@
[package]
name = "hashing"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
tiny-keccak = "1.4.2"

View File

@@ -0,0 +1,30 @@
extern crate tiny_keccak;
use tiny_keccak::Keccak;
pub fn canonical_hash(input: &[u8]) -> Vec<u8> {
let mut keccak = Keccak::new_keccak256();
keccak.update(input);
let mut result = vec![0; 32];
keccak.finalize(result.as_mut_slice());
result
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::From;
#[test]
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

@@ -0,0 +1,7 @@
[package]
name = "honey-badger-split"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]

View File

@@ -0,0 +1,85 @@
/// A function for splitting a list into N pieces.
///
/// We have titled it the "honey badger split" because of its robustness. It don't care.
/// Iterator for the honey_badger_split function
pub struct Split<'a, T: 'a> {
n: usize,
current_pos: usize,
list: &'a [T],
list_length: usize,
}
impl<'a, T> Iterator for Split<'a, T> {
type Item = &'a [T];
fn next(&mut self) -> Option<Self::Item> {
self.current_pos += 1;
if self.current_pos <= self.n {
match self.list.get(
self.list_length * (self.current_pos - 1) / self.n
..self.list_length * self.current_pos / self.n,
) {
Some(v) => Some(v),
None => unreachable!(),
}
} else {
None
}
}
}
/// Splits a slice into chunks of size n. All postive n values are applicable,
/// hence the honey_badger prefix.
///
/// Returns an iterator over the original list.
pub trait SplitExt<T> {
fn honey_badger_split(&self, n: usize) -> Split<T>;
}
impl<T> SplitExt<T> for [T] {
fn honey_badger_split(&self, n: usize) -> Split<T> {
Split {
n,
current_pos: 0,
list: &self,
list_length: self.len(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_honey_badger_split() {
/*
* These test cases are generated from the eth2.0 spec `split()`
* function at commit cbd254a.
*/
let input: Vec<usize> = vec![0, 1, 2, 3];
let output: Vec<&[usize]> = input.honey_badger_split(2).collect();
assert_eq!(output, vec![&[0, 1], &[2, 3]]);
let input: Vec<usize> = vec![0, 1, 2, 3];
let output: Vec<&[usize]> = input.honey_badger_split(6).collect();
let expected: Vec<&[usize]> = vec![&[], &[0], &[1], &[], &[2], &[3]];
assert_eq!(output, expected);
let input: Vec<usize> = vec![0, 1, 2, 3];
let output: Vec<&[usize]> = input.honey_badger_split(10).collect();
let expected: Vec<&[usize]> = vec![&[], &[], &[0], &[], &[1], &[], &[], &[2], &[], &[3]];
assert_eq!(output, expected);
let input: Vec<usize> = vec![0];
let output: Vec<&[usize]> = input.honey_badger_split(5).collect();
let expected: Vec<&[usize]> = vec![&[], &[], &[], &[], &[0]];
assert_eq!(output, expected);
let input: Vec<usize> = vec![0, 1, 2];
let output: Vec<&[usize]> = input.honey_badger_split(2).collect();
let expected: Vec<&[usize]> = vec![&[0], &[1, 2]];
assert_eq!(output, expected);
}
}

View File

@@ -0,0 +1,7 @@
[package]
name = "slot_clock"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]

View File

@@ -0,0 +1,11 @@
mod system_time_slot_clock;
mod testing_slot_clock;
pub use crate::system_time_slot_clock::{Error as SystemTimeSlotClockError, SystemTimeSlotClock};
pub use crate::testing_slot_clock::{Error as TestingSlotClockError, TestingSlotClock};
pub trait SlotClock: Send + Sync {
type Error;
fn present_slot(&self) -> Result<Option<u64>, Self::Error>;
}

View File

@@ -0,0 +1,135 @@
use super::SlotClock;
use std::time::{Duration, SystemTime};
pub use std::time::SystemTimeError;
#[derive(Debug)]
pub enum Error {
SlotDurationIsZero,
SystemTimeError(SystemTimeError),
}
/// Determines the present slot based upon the present system time.
pub struct SystemTimeSlotClock {
genesis_seconds: u64,
slot_duration_seconds: u64,
}
impl SystemTimeSlotClock {
/// Create a new `SystemTimeSlotClock`.
///
/// Returns an Error if `slot_duration_seconds == 0`.
pub fn new(
genesis_seconds: u64,
slot_duration_seconds: u64,
) -> Result<SystemTimeSlotClock, Error> {
if slot_duration_seconds == 0 {
Err(Error::SlotDurationIsZero)
} else {
Ok(Self {
genesis_seconds,
slot_duration_seconds,
})
}
}
}
impl SlotClock for SystemTimeSlotClock {
type Error = Error;
fn present_slot(&self) -> Result<Option<u64>, Error> {
let syslot_time = SystemTime::now();
let duration_since_epoch = syslot_time.duration_since(SystemTime::UNIX_EPOCH)?;
let duration_since_genesis =
duration_since_epoch.checked_sub(Duration::from_secs(self.genesis_seconds));
match duration_since_genesis {
None => Ok(None),
Some(d) => Ok(slot_from_duration(self.slot_duration_seconds, d)),
}
}
}
impl From<SystemTimeError> for Error {
fn from(e: SystemTimeError) -> Error {
Error::SystemTimeError(e)
}
}
fn slot_from_duration(slot_duration_seconds: u64, duration: Duration) -> Option<u64> {
duration.as_secs().checked_div(slot_duration_seconds)
}
#[cfg(test)]
mod tests {
use super::*;
/*
* Note: these tests are using actual system times and could fail if they are executed on a
* very slow machine.
*/
#[test]
fn test_slot_now() {
let slot_time = 100;
let now = SystemTime::now();
let since_epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
let genesis = since_epoch.as_secs() - slot_time * 89;
let clock = SystemTimeSlotClock {
genesis_seconds: genesis,
slot_duration_seconds: slot_time,
};
assert_eq!(clock.present_slot().unwrap(), Some(89));
let clock = SystemTimeSlotClock {
genesis_seconds: since_epoch.as_secs(),
slot_duration_seconds: slot_time,
};
assert_eq!(clock.present_slot().unwrap(), Some(0));
let clock = SystemTimeSlotClock {
genesis_seconds: since_epoch.as_secs() - slot_time * 42 - 5,
slot_duration_seconds: slot_time,
};
assert_eq!(clock.present_slot().unwrap(), Some(42));
}
#[test]
fn test_slot_from_duration() {
let slot_time = 100;
assert_eq!(
slot_from_duration(slot_time, Duration::from_secs(0)),
Some(0)
);
assert_eq!(
slot_from_duration(slot_time, Duration::from_secs(10)),
Some(0)
);
assert_eq!(
slot_from_duration(slot_time, Duration::from_secs(100)),
Some(1)
);
assert_eq!(
slot_from_duration(slot_time, Duration::from_secs(101)),
Some(1)
);
assert_eq!(
slot_from_duration(slot_time, Duration::from_secs(1000)),
Some(10)
);
}
#[test]
fn test_slot_from_duration_slot_time_zero() {
let slot_time = 0;
assert_eq!(slot_from_duration(slot_time, Duration::from_secs(0)), None);
assert_eq!(slot_from_duration(slot_time, Duration::from_secs(10)), None);
assert_eq!(
slot_from_duration(slot_time, Duration::from_secs(1000)),
None
);
}
}

View File

@@ -0,0 +1,43 @@
use super::SlotClock;
#[derive(Debug, PartialEq)]
pub enum Error {}
/// Determines the present slot based upon the present system time.
pub struct TestingSlotClock {
slot: u64,
}
impl TestingSlotClock {
/// Create a new `TestingSlotClock`.
///
/// Returns an Error if `slot_duration_seconds == 0`.
pub fn new(slot: u64) -> TestingSlotClock {
TestingSlotClock { slot }
}
pub fn set_slot(&mut self, slot: u64) {
self.slot = slot;
}
}
impl SlotClock for TestingSlotClock {
type Error = Error;
fn present_slot(&self) -> Result<Option<u64>, Error> {
Ok(Some(self.slot))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slot_now() {
let mut clock = TestingSlotClock::new(10);
assert_eq!(clock.present_slot(), Ok(Some(10)));
clock.set_slot(123);
assert_eq!(clock.present_slot(), Ok(Some(123)));
}
}

View File

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

543
eth2/utils/ssz/README.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

33
eth2/utils/ssz/src/lib.rs Normal file
View File

@@ -0,0 +1,33 @@
/*
* This is a WIP of implementing an alternative
* serialization strategy. It attempts to follow Vitalik's
* "simpleserialize" format here:
* https://github.com/ethereum/beacon_chain/blob/master/beacon_chain/utils/simpleserialize.py
*
* This implementation is not final and would almost certainly
* have issues.
*/
extern crate bytes;
extern crate ethereum_types;
pub mod decode;
pub mod encode;
mod impl_decode;
mod impl_encode;
pub use crate::decode::{decode_ssz, decode_ssz_list, Decodable, DecodeError};
pub use crate::encode::{Encodable, SszStream};
pub const LENGTH_BYTES: usize = 4;
pub const MAX_LIST_SIZE: usize = 1 << (4 * 8);
/// Convenience function to SSZ encode an object supporting ssz::Encode.
pub fn ssz_encode<T>(val: &T) -> Vec<u8>
where
T: Encodable,
{
let mut ssz_stream = SszStream::new();
ssz_stream.append(val);
ssz_stream.drain()
}

View File

@@ -0,0 +1,11 @@
[package]
name = "vec_shuffle"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
hashing = { path = "../hashing" }
[dev-dependencies]
yaml-rust = "0.4.2"

View File

@@ -0,0 +1,81 @@
/// A library for performing deterministic, pseudo-random shuffling on a vector.
///
/// This library is designed to confirm to the Ethereum 2.0 specification.
extern crate hashing;
mod rng;
use self::rng::ShuffleRng;
#[derive(Debug)]
pub enum ShuffleErr {
ExceedsListLength,
}
/// Performs a deterministic, in-place shuffle of a vector.
///
/// The final order of the shuffle is determined by successive hashes
/// of the supplied `seed`.
///
/// This is a Fisher-Yates-Durtstenfeld shuffle.
pub fn shuffle<T>(seed: &[u8], mut list: Vec<T>) -> Result<Vec<T>, ShuffleErr> {
let mut rng = ShuffleRng::new(seed);
if list.len() > rng.rand_max as usize {
return Err(ShuffleErr::ExceedsListLength);
}
if list.is_empty() {
return Ok(list);
}
for i in 0..(list.len() - 1) {
let n = list.len() - i;
let j = rng.rand_range(n as u32) as usize + i;
list.swap(i, j);
}
Ok(list)
}
#[cfg(test)]
mod tests {
extern crate yaml_rust;
use self::yaml_rust::yaml;
use std::{fs::File, io::prelude::*, path::PathBuf};
use super::{hashing::canonical_hash, *};
#[test]
fn test_shuffling() {
let mut file = {
let mut file_path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
file_path_buf.push("src/specs/shuffle_test_vectors.yaml");
File::open(file_path_buf).unwrap()
};
let mut yaml_str = String::new();
file.read_to_string(&mut yaml_str).unwrap();
let docs = yaml::YamlLoader::load_from_str(&yaml_str).unwrap();
let doc = &docs[0];
let test_cases = doc["test_cases"].as_vec().unwrap();
for test_case in test_cases {
let input = test_case["input"].clone().into_vec().unwrap();
let output = test_case["output"].clone().into_vec().unwrap();
let seed_bytes = test_case["seed"].as_str().unwrap().as_bytes();
let seed = if seed_bytes.len() > 0 {
canonical_hash(seed_bytes)
} else {
vec![]
};
assert_eq!(shuffle(&seed, input).unwrap(), output);
}
}
}

View File

@@ -0,0 +1,90 @@
use super::hashing::canonical_hash;
const SEED_SIZE_BYTES: usize = 32;
const RAND_BYTES: usize = 3; // 24 / 8
const RAND_MAX: u32 = 16_777_215; // 2 ** (rand_bytes * 8) - 1
/// A pseudo-random number generator which given a seed
/// uses successive blake2s hashing to generate "entropy".
pub struct ShuffleRng {
seed: Vec<u8>,
idx: usize,
pub rand_max: u32,
}
impl ShuffleRng {
/// Create a new instance given some "seed" bytes.
pub fn new(initial_seed: &[u8]) -> Self {
Self {
seed: canonical_hash(initial_seed),
idx: 0,
rand_max: RAND_MAX,
}
}
/// "Regenerates" the seed by hashing it.
fn rehash_seed(&mut self) {
self.seed = canonical_hash(&self.seed);
self.idx = 0;
}
/// Extracts 3 bytes from the `seed`. Rehashes seed if required.
fn rand(&mut self) -> u32 {
self.idx += RAND_BYTES;
if self.idx >= SEED_SIZE_BYTES {
self.rehash_seed();
self.rand()
} else {
int_from_byte_slice(&self.seed, self.idx - RAND_BYTES)
}
}
/// Generate a random u32 below the specified maximum `n`.
///
/// Provides a filtered result from a higher-level rng, by discarding
/// results which may bias the output. Because of this, execution time is
/// not linear and may potentially be infinite.
pub fn rand_range(&mut self, n: u32) -> u32 {
assert!(n < RAND_MAX, "RAND_MAX exceed");
let mut x = self.rand();
while x >= self.rand_max - (self.rand_max % n) {
x = self.rand();
}
x % n
}
}
/// Reads the next three bytes of `source`, starting from `offset` and
/// interprets those bytes as a 24 bit big-endian integer.
/// Returns that integer.
fn int_from_byte_slice(source: &[u8], offset: usize) -> u32 {
(u32::from(source[offset + 2]))
| (u32::from(source[offset + 1]) << 8)
| (u32::from(source[offset]) << 16)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shuffling_int_from_slice() {
let mut x = int_from_byte_slice(&[0, 0, 1], 0);
assert_eq!((x as u32), 1);
x = int_from_byte_slice(&[0, 1, 1], 0);
assert_eq!(x, 257);
x = int_from_byte_slice(&[1, 1, 1], 0);
assert_eq!(x, 65793);
x = int_from_byte_slice(&[255, 1, 1], 0);
assert_eq!(x, 16711937);
x = int_from_byte_slice(&[255, 255, 255], 0);
assert_eq!(x, 16777215);
x = int_from_byte_slice(&[0x8f, 0xbb, 0xc7], 0);
assert_eq!(x, 9419719);
}
}

View File

@@ -0,0 +1,131 @@
title: Shuffling Algorithm Tests
summary: Test vectors for shuffling a list based upon a seed.
test_suite: Shuffling
test_cases:
- input: []
output: []
seed: ''
- input: [0]
output: [0]
seed: ''
- input: [255]
output: [255]
seed: ''
- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [2, 1, 1, 5, 6, 6, 6, 2, 4, 4]
seed: ''
- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
output: [4, 9, 6, 8, 13, 3, 2, 11, 5, 1, 12, 7, 10]
seed: ''
- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [2, 1, 1, 5, 6, 6, 6, 2, 4, 65]
seed: ''
- input: []
output: []
seed: 4kn4driuctg8
- input: [0]
output: [0]
seed: 4kn4driuctg8
- input: [255]
output: [255]
seed: 4kn4driuctg8
- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [2, 4, 4, 2, 1, 1, 6, 5, 6, 6]
seed: 4kn4driuctg8
- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
output: [7, 6, 3, 12, 11, 1, 8, 13, 10, 5, 9, 4, 2]
seed: 4kn4driuctg8
- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [2, 4, 65, 2, 1, 1, 6, 5, 6, 6]
seed: 4kn4driuctg8
- input: []
output: []
seed: ytre1p
- input: [0]
output: [0]
seed: ytre1p
- input: [255]
output: [255]
seed: ytre1p
- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [6, 1, 1, 5, 6, 2, 6, 2, 4, 4]
seed: ytre1p
- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
output: [6, 2, 3, 4, 8, 5, 12, 9, 7, 11, 10, 1, 13]
seed: ytre1p
- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [6, 1, 1, 5, 6, 2, 6, 2, 4, 65]
seed: ytre1p
- input: []
output: []
seed: mytobcffnkvj
- input: [0]
output: [0]
seed: mytobcffnkvj
- input: [255]
output: [255]
seed: mytobcffnkvj
- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [2, 4, 1, 1, 6, 4, 6, 5, 6, 2]
seed: mytobcffnkvj
- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
output: [11, 5, 9, 7, 2, 4, 12, 10, 8, 1, 6, 3, 13]
seed: mytobcffnkvj
- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [2, 65, 1, 1, 6, 4, 6, 5, 6, 2]
seed: mytobcffnkvj
- input: []
output: []
seed: myzu3g7evxp5nkvj
- input: [0]
output: [0]
seed: myzu3g7evxp5nkvj
- input: [255]
output: [255]
seed: myzu3g7evxp5nkvj
- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [6, 2, 1, 4, 2, 6, 5, 6, 4, 1]
seed: myzu3g7evxp5nkvj
- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
output: [2, 1, 11, 3, 9, 7, 8, 13, 4, 10, 5, 6, 12]
seed: myzu3g7evxp5nkvj
- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [6, 2, 1, 4, 2, 6, 5, 6, 65, 1]
seed: myzu3g7evxp5nkvj
- input: []
output: []
seed: xdpli1jsx5xb
- input: [0]
output: [0]
seed: xdpli1jsx5xb
- input: [255]
output: [255]
seed: xdpli1jsx5xb
- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [2, 1, 2, 4, 6, 6, 5, 6, 1, 4]
seed: xdpli1jsx5xb
- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
output: [5, 8, 12, 9, 11, 4, 7, 13, 1, 3, 2, 10, 6]
seed: xdpli1jsx5xb
- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [2, 1, 2, 65, 6, 6, 5, 6, 1, 4]
seed: xdpli1jsx5xb
- input: []
output: []
seed: oab3mbb3xe8qsx5xb
- input: [0]
output: [0]
seed: oab3mbb3xe8qsx5xb
- input: [255]
output: [255]
seed: oab3mbb3xe8qsx5xb
- input: [4, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [6, 2, 1, 1, 6, 2, 4, 4, 6, 5]
seed: oab3mbb3xe8qsx5xb
- input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
output: [1, 8, 5, 13, 2, 10, 7, 11, 12, 6, 3, 4, 9]
seed: oab3mbb3xe8qsx5xb
- input: [65, 6, 2, 6, 1, 4, 6, 2, 1, 5]
output: [6, 2, 1, 1, 6, 2, 4, 65, 6, 5]
seed: oab3mbb3xe8qsx5xb

View File

@@ -0,0 +1,11 @@
[package]
name = "validator_change"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
bytes = "0.4.10"
hashing = { path = "../utils/hashing" }
ssz = { path = "../utils/ssz" }
types = { path = "../types" }

View File

@@ -0,0 +1,142 @@
extern crate bytes;
extern crate hashing;
extern crate types;
use bytes::{BufMut, BytesMut};
use hashing::canonical_hash;
use ssz::ssz_encode;
use std::cmp::max;
use types::{Hash256, ValidatorRecord, ValidatorStatus};
pub enum UpdateValidatorSetError {
ArithmeticOverflow,
}
const VALIDATOR_FLAG_ENTRY: u8 = 0;
const VALIDATOR_FLAG_EXIT: u8 = 1;
pub fn update_validator_set(
validators: &mut Vec<ValidatorRecord>,
hash_chain: Hash256,
present_slot: u64,
deposit_size_gwei: u64,
max_validator_churn_quotient: u64,
) -> Result<(), UpdateValidatorSetError> {
/*
* Total balance of all active validators.
*
* Return an error if an overflow occurs.
*/
let total_balance = {
let mut bal: u64 = 0;
for v in validators.iter() {
if v.status_is(ValidatorStatus::Active) {
bal = bal
.checked_add(v.balance)
.ok_or(UpdateValidatorSetError::ArithmeticOverflow)?;
}
}
bal
};
/*
* Note: this is not the maximum allowable change, it can actually be higher.
*/
let max_allowable_change = {
let double_deposit_size = deposit_size_gwei
.checked_mul(2)
.ok_or(UpdateValidatorSetError::ArithmeticOverflow)?;
max(
double_deposit_size,
total_balance / max_validator_churn_quotient,
)
};
let mut hasher = ValidatorChangeHashChain {
bytes: hash_chain.to_vec(),
};
let mut total_changed: u64 = 0;
for (i, v) in validators.iter_mut().enumerate() {
match v.status {
/*
* Validator is pending activiation.
*/
ValidatorStatus::PendingActivation => {
let new_total_changed = total_changed
.checked_add(deposit_size_gwei)
.ok_or(UpdateValidatorSetError::ArithmeticOverflow)?;
/*
* If entering this validator would not exceed the max balance delta,
* activate the validator.
*/
if new_total_changed <= max_allowable_change {
v.status = ValidatorStatus::Active;
hasher.extend(i, &ssz_encode(&v.pubkey), VALIDATOR_FLAG_ENTRY);
total_changed = new_total_changed;
} else {
// Entering the validator would exceed the balance delta.
break;
}
}
/*
* Validator is pending exit.
*/
ValidatorStatus::PendingExit => {
let new_total_changed = total_changed
.checked_add(v.balance)
.ok_or(UpdateValidatorSetError::ArithmeticOverflow)?;
/*
* If exiting this validator would not exceed the max balance delta,
* exit the validator
*/
if new_total_changed <= max_allowable_change {
v.status = ValidatorStatus::PendingWithdraw;
v.exit_slot = present_slot;
hasher.extend(i, &ssz_encode(&v.pubkey), VALIDATOR_FLAG_EXIT);
total_changed = new_total_changed;
} else {
// Exiting the validator would exceed the balance delta.
break;
}
}
_ => (),
};
if total_changed >= max_allowable_change {
break;
}
}
Ok(())
}
pub struct ValidatorChangeHashChain {
bytes: Vec<u8>,
}
impl ValidatorChangeHashChain {
pub fn extend(&mut self, index: usize, pubkey: &Vec<u8>, flag: u8) {
let mut message = self.bytes.clone();
message.append(&mut serialize_validator_change_record(index, pubkey, flag));
self.bytes = canonical_hash(&message);
}
}
fn serialize_validator_change_record(index: usize, pubkey: &Vec<u8>, flag: u8) -> Vec<u8> {
let mut buf = BytesMut::with_capacity(68);
buf.put_u8(flag);
let index_bytes = {
let mut buf = BytesMut::with_capacity(8);
buf.put_u64_be(index as u64);
buf.take()[8 - 3..8].to_vec()
};
buf.put(index_bytes);
buf.put(pubkey);
buf.take().to_vec()
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

View File

@@ -0,0 +1,11 @@
[package]
name = "validator_induction"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
bls = { path = "../utils/bls" }
hashing = { path = "../utils/hashing" }
types = { path = "../types" }
spec = { path = "../spec" }

View File

@@ -0,0 +1,185 @@
use bls::verify_proof_of_possession;
use spec::ChainSpec;
use types::{BeaconState, Deposit, ValidatorRecord};
#[derive(Debug, PartialEq, Clone)]
pub enum ValidatorInductionError {
InvalidShard,
InvaidProofOfPossession,
InvalidWithdrawalCredentials,
}
pub fn process_deposit(
state: &mut BeaconState,
deposit: &Deposit,
spec: &ChainSpec,
) -> Result<(), ValidatorInductionError> {
let deposit_input = &deposit.deposit_data.deposit_input;
let deposit_data = &deposit.deposit_data;
// TODO: Update the signature validation as defined in the spec once issues #91 and #70 are completed
if !verify_proof_of_possession(&deposit_input.proof_of_possession, &deposit_input.pubkey) {
return Err(ValidatorInductionError::InvaidProofOfPossession);
}
let validator_index = state
.validator_registry
.iter()
.position(|validator| validator.pubkey == deposit_input.pubkey);
match validator_index {
Some(i) => {
if state.validator_registry[i].withdrawal_credentials
== deposit_input.withdrawal_credentials
{
state.validator_balances[i] += deposit_data.value;
return Ok(());
}
Err(ValidatorInductionError::InvalidWithdrawalCredentials)
}
None => {
let validator = ValidatorRecord {
pubkey: deposit_input.pubkey.clone(),
withdrawal_credentials: deposit_input.withdrawal_credentials,
randao_commitment: deposit_input.randao_commitment,
randao_layers: 0,
activation_slot: spec.far_future_slot,
exit_slot: spec.far_future_slot,
withdrawal_slot: spec.far_future_slot,
penalized_slot: spec.far_future_slot,
exit_count: 0,
status_flags: None,
custody_commitment: deposit_input.custody_commitment,
latest_custody_reseed_slot: 0,
penultimate_custody_reseed_slot: 0,
};
let _index = state.validator_registry.len();
state.validator_registry.push(validator);
state.validator_balances.push(deposit_data.value);
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use types::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use bls::{create_proof_of_possession, Keypair};
/// The size of a validators deposit in GWei.
pub const DEPOSIT_GWEI: u64 = 32_000_000_000;
fn get_deposit() -> Deposit {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut deposit = Deposit::random_for_test(&mut rng);
let kp = Keypair::random();
deposit.deposit_data.deposit_input.pubkey = kp.pk.clone();
deposit.deposit_data.deposit_input.proof_of_possession = create_proof_of_possession(&kp);
deposit
}
fn get_validator() -> ValidatorRecord {
let mut rng = XorShiftRng::from_seed([42; 16]);
ValidatorRecord::random_for_test(&mut rng)
}
fn deposit_equals_record(dep: &Deposit, val: &ValidatorRecord) -> bool {
(dep.deposit_data.deposit_input.pubkey == val.pubkey)
& (dep.deposit_data.deposit_input.withdrawal_credentials == val.withdrawal_credentials)
& (dep.deposit_data.deposit_input.randao_commitment == val.randao_commitment)
& (verify_proof_of_possession(
&dep.deposit_data.deposit_input.proof_of_possession,
&val.pubkey,
))
}
#[test]
fn test_process_deposit_valid_empty_validators() {
let mut state = BeaconState::default();
let mut deposit = get_deposit();
let spec = ChainSpec::foundation();
deposit.deposit_data.value = DEPOSIT_GWEI;
let result = process_deposit(&mut state, &deposit, &spec);
assert_eq!(result.unwrap(), ());
assert!(deposit_equals_record(
&deposit,
&state.validator_registry[0]
));
assert_eq!(state.validator_registry.len(), 1);
assert_eq!(state.validator_balances.len(), 1);
}
#[test]
fn test_process_deposits_empty_validators() {
let mut state = BeaconState::default();
let spec = ChainSpec::foundation();
for i in 0..5 {
let mut deposit = get_deposit();
let result = process_deposit(&mut state, &deposit, &spec);
deposit.deposit_data.value = DEPOSIT_GWEI;
assert_eq!(result.unwrap(), ());
assert!(deposit_equals_record(
&deposit,
&state.validator_registry[i]
));
assert_eq!(state.validator_registry.len(), i + 1);
assert_eq!(state.validator_balances.len(), i + 1);
}
}
#[test]
fn test_process_deposit_top_out() {
let mut state = BeaconState::default();
let spec = ChainSpec::foundation();
let mut deposit = get_deposit();
let mut validator = get_validator();
deposit.deposit_data.value = DEPOSIT_GWEI;
validator.pubkey = deposit.deposit_data.deposit_input.pubkey.clone();
validator.withdrawal_credentials =
deposit.deposit_data.deposit_input.withdrawal_credentials;
validator.randao_commitment = deposit.deposit_data.deposit_input.randao_commitment;
state.validator_registry.push(validator);
state.validator_balances.push(DEPOSIT_GWEI);
let result = process_deposit(&mut state, &deposit, &spec);
assert_eq!(result.unwrap(), ());
assert!(deposit_equals_record(
&deposit,
&state.validator_registry[0]
));
assert_eq!(state.validator_balances[0], DEPOSIT_GWEI * 2);
assert_eq!(state.validator_registry.len(), 1);
assert_eq!(state.validator_balances.len(), 1);
}
#[test]
fn test_process_deposit_invalid_proof_of_possession() {
let mut state = BeaconState::default();
let mut deposit = get_deposit();
let spec = ChainSpec::foundation();
deposit.deposit_data.value = DEPOSIT_GWEI;
deposit.deposit_data.deposit_input.proof_of_possession =
create_proof_of_possession(&Keypair::random());
let result = process_deposit(&mut state, &deposit, &spec);
assert_eq!(
result,
Err(ValidatorInductionError::InvaidProofOfPossession)
);
assert_eq!(state.validator_registry.len(), 0);
assert_eq!(state.validator_balances.len(), 0);
}
}

View File

@@ -0,0 +1,8 @@
extern crate bls;
extern crate hashing;
extern crate spec;
extern crate types;
mod inductor;
pub use crate::inductor::{process_deposit, ValidatorInductionError};

View File

@@ -0,0 +1,11 @@
[package]
name = "validator_shuffling"
version = "0.1.0"
authors = ["Paul Hauner <paul@paulhauner.com>"]
edition = "2018"
[dependencies]
honey-badger-split = { path = "../utils/honey-badger-split" }
spec = { path = "../spec" }
types = { path = "../types" }
vec_shuffle = { path = "../utils/vec_shuffle" }

View File

@@ -0,0 +1,8 @@
extern crate honey_badger_split;
extern crate spec;
extern crate types;
extern crate vec_shuffle;
mod shuffle;
pub use crate::shuffle::{shard_and_committees_for_cycle, ValidatorAssignmentError};

View File

@@ -0,0 +1,279 @@
use std::cmp::min;
use honey_badger_split::SplitExt;
use spec::ChainSpec;
use types::validator_registry::get_active_validator_indices;
use types::{ShardCommittee, ValidatorRecord};
use vec_shuffle::{shuffle, ShuffleErr};
type DelegatedCycle = Vec<Vec<ShardCommittee>>;
#[derive(Debug, PartialEq)]
pub enum ValidatorAssignmentError {
TooManyValidators,
TooFewShards,
}
/// Delegates active validators into slots for a given cycle, given a random seed.
/// Returns a vector or ShardAndComitte vectors representing the shards and committiees for
/// each slot.
/// References get_new_shuffling (ethereum 2.1 specification)
pub fn shard_and_committees_for_cycle(
seed: &[u8],
validators: &[ValidatorRecord],
crosslinking_shard_start: u16,
spec: &ChainSpec,
) -> Result<DelegatedCycle, ValidatorAssignmentError> {
let shuffled_validator_indices = {
let validator_indices = get_active_validator_indices(validators, 0);
shuffle(seed, validator_indices)?
};
let shard_indices: Vec<usize> = (0_usize..spec.shard_count as usize).into_iter().collect();
let crosslinking_shard_start = crosslinking_shard_start as usize;
let epoch_length = spec.epoch_length as usize;
let min_committee_size = spec.target_committee_size as usize;
generate_cycle(
&shuffled_validator_indices,
&shard_indices,
crosslinking_shard_start,
epoch_length,
min_committee_size,
)
}
/// Given the validator list, delegates the validators into slots and comittees for a given cycle.
fn generate_cycle(
validator_indices: &[usize],
shard_indices: &[usize],
crosslinking_shard_start: usize,
epoch_length: usize,
min_committee_size: usize,
) -> Result<DelegatedCycle, ValidatorAssignmentError> {
let validator_count = validator_indices.len();
let shard_count = shard_indices.len();
if shard_count / epoch_length == 0 {
return Err(ValidatorAssignmentError::TooFewShards);
}
let (committees_per_slot, slots_per_committee) = {
if validator_count >= epoch_length * min_committee_size {
let committees_per_slot = min(
validator_count / epoch_length / (min_committee_size * 2) + 1,
shard_count / epoch_length,
);
let slots_per_committee = 1;
(committees_per_slot, slots_per_committee)
} else {
let committees_per_slot = 1;
let mut slots_per_committee = 1;
while (validator_count * slots_per_committee < epoch_length * min_committee_size)
& (slots_per_committee < epoch_length)
{
slots_per_committee *= 2;
}
(committees_per_slot, slots_per_committee)
}
};
let cycle = validator_indices
.honey_badger_split(epoch_length)
.enumerate()
.map(|(i, slot_indices)| {
let shard_start =
crosslinking_shard_start + i * committees_per_slot / slots_per_committee;
slot_indices
.honey_badger_split(committees_per_slot)
.enumerate()
.map(|(j, shard_indices)| ShardCommittee {
shard: ((shard_start + j) % shard_count) as u64,
committee: shard_indices.to_vec(),
})
.collect()
})
.collect();
Ok(cycle)
}
impl From<ShuffleErr> for ValidatorAssignmentError {
fn from(e: ShuffleErr) -> ValidatorAssignmentError {
match e {
ShuffleErr::ExceedsListLength => ValidatorAssignmentError::TooManyValidators,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn generate_cycle_helper(
validator_count: &usize,
shard_count: &usize,
crosslinking_shard_start: usize,
epoch_length: usize,
min_committee_size: usize,
) -> (
Vec<usize>,
Vec<usize>,
Result<DelegatedCycle, ValidatorAssignmentError>,
) {
let validator_indices: Vec<usize> = (0_usize..*validator_count).into_iter().collect();
let shard_indices: Vec<usize> = (0_usize..*shard_count).into_iter().collect();
let result = generate_cycle(
&validator_indices,
&shard_indices,
crosslinking_shard_start,
epoch_length,
min_committee_size,
);
(validator_indices, shard_indices, result)
}
#[allow(dead_code)]
fn print_cycle(cycle: &DelegatedCycle) {
cycle.iter().enumerate().for_each(|(i, slot)| {
println!("slot {:?}", &i);
slot.iter().enumerate().for_each(|(i, sac)| {
println!(
"#{:?}\tshard={}\tcommittee.len()={}",
&i,
&sac.shard,
&sac.committee.len()
)
})
});
}
fn flatten_validators(cycle: &DelegatedCycle) -> Vec<usize> {
let mut flattened = vec![];
for slot in cycle.iter() {
for sac in slot.iter() {
for validator in sac.committee.iter() {
flattened.push(*validator);
}
}
}
flattened
}
fn flatten_and_dedup_shards(cycle: &DelegatedCycle) -> Vec<usize> {
let mut flattened = vec![];
for slot in cycle.iter() {
for sac in slot.iter() {
flattened.push(sac.shard as usize);
}
}
flattened.dedup();
flattened
}
fn flatten_shards_in_slots(cycle: &DelegatedCycle) -> Vec<Vec<usize>> {
let mut shards_in_slots: Vec<Vec<usize>> = vec![];
for slot in cycle.iter() {
let mut shards: Vec<usize> = vec![];
for sac in slot.iter() {
shards.push(sac.shard as usize);
}
shards_in_slots.push(shards);
}
shards_in_slots
}
// TODO: Improve these tests to check committee lengths
#[test]
fn test_generate_cycle() {
let validator_count: usize = 100;
let shard_count: usize = 20;
let crosslinking_shard_start: usize = 0;
let epoch_length: usize = 20;
let min_committee_size: usize = 10;
let (validators, shards, result) = generate_cycle_helper(
&validator_count,
&shard_count,
crosslinking_shard_start,
epoch_length,
min_committee_size,
);
let cycle = result.unwrap();
let assigned_validators = flatten_validators(&cycle);
let assigned_shards = flatten_and_dedup_shards(&cycle);
let shards_in_slots = flatten_shards_in_slots(&cycle);
let expected_shards = shards.get(0..10).unwrap();
assert_eq!(
assigned_validators, validators,
"Validator assignment incorrect"
);
assert_eq!(
assigned_shards, expected_shards,
"Shard assignment incorrect"
);
let expected_shards_in_slots: Vec<Vec<usize>> = vec![
vec![0],
vec![0], // Each line is 2 slots..
vec![1],
vec![1],
vec![2],
vec![2],
vec![3],
vec![3],
vec![4],
vec![4],
vec![5],
vec![5],
vec![6],
vec![6],
vec![7],
vec![7],
vec![8],
vec![8],
vec![9],
vec![9],
];
// assert!(compare_shards_in_slots(&cycle, &expected_shards_in_slots));
assert_eq!(
expected_shards_in_slots, shards_in_slots,
"Shard assignment incorrect."
)
}
#[test]
// Check that the committees per slot is upper bounded by shard count
fn test_generate_cycle_committees_bounded() {
let validator_count: usize = 523;
let shard_count: usize = 31;
let crosslinking_shard_start: usize = 0;
let epoch_length: usize = 11;
let min_committee_size: usize = 5;
let (validators, shards, result) = generate_cycle_helper(
&validator_count,
&shard_count,
crosslinking_shard_start,
epoch_length,
min_committee_size,
);
let cycle = result.unwrap();
let assigned_validators = flatten_validators(&cycle);
let assigned_shards = flatten_and_dedup_shards(&cycle);
let shards_in_slots = flatten_shards_in_slots(&cycle);
let expected_shards = shards.get(0..22).unwrap();
let expected_shards_in_slots: Vec<Vec<usize>> = (0_usize..11_usize)
.map(|x| vec![2 * x, 2 * x + 1])
.collect();
assert_eq!(
assigned_validators, validators,
"Validator assignment incorrect"
);
assert_eq!(
assigned_shards, expected_shards,
"Shard assignment incorrect"
);
// assert!(compare_shards_in_slots(&cycle, &expected_shards_in_slots));
assert_eq!(
expected_shards_in_slots, shards_in_slots,
"Shard assignment incorrect."
)
}
}