mirror of
https://github.com/sigp/lighthouse.git
synced 2026-03-09 19:51:47 +00:00
Rename beacon_chain/ -> eth2/
This commit is contained in:
12
eth2/attestation_validation/Cargo.toml
Normal file
12
eth2/attestation_validation/Cargo.toml
Normal 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" }
|
||||
246
eth2/attestation_validation/src/block_inclusion.rs
Normal file
246
eth2/attestation_validation/src/block_inclusion.rs
Normal 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)));
|
||||
}
|
||||
}
|
||||
37
eth2/attestation_validation/src/enums.rs
Normal file
37
eth2/attestation_validation/src/enums.rs
Normal 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),
|
||||
}
|
||||
80
eth2/attestation_validation/src/justified_block.rs
Normal file
80
eth2/attestation_validation/src/justified_block.rs
Normal 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
|
||||
*/
|
||||
}
|
||||
39
eth2/attestation_validation/src/justified_slot.rs
Normal file
39
eth2/attestation_validation/src/justified_slot.rs
Normal 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
|
||||
*/
|
||||
}
|
||||
22
eth2/attestation_validation/src/lib.rs
Normal file
22
eth2/attestation_validation/src/lib.rs
Normal 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;
|
||||
19
eth2/attestation_validation/src/macros.rs
Normal file
19
eth2/attestation_validation/src/macros.rs
Normal 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)
|
||||
};
|
||||
}
|
||||
46
eth2/attestation_validation/src/shard_block.rs
Normal file
46
eth2/attestation_validation/src/shard_block.rs
Normal 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
|
||||
*/
|
||||
}
|
||||
151
eth2/attestation_validation/src/signature.rs
Normal file
151
eth2/attestation_validation/src/signature.rs
Normal 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
13
eth2/genesis/Cargo.toml
Normal 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" }
|
||||
89
eth2/genesis/src/beacon_block.rs
Normal file
89
eth2/genesis/src/beacon_block.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
222
eth2/genesis/src/beacon_state.rs
Normal file
222
eth2/genesis/src/beacon_state.rs
Normal 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
10
eth2/genesis/src/lib.rs
Normal 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};
|
||||
10
eth2/naive_fork_choice/Cargo.toml
Normal file
10
eth2/naive_fork_choice/Cargo.toml
Normal 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" }
|
||||
97
eth2/naive_fork_choice/src/lib.rs
Normal file
97
eth2/naive_fork_choice/src/lib.rs
Normal 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
9
eth2/spec/Cargo.toml
Normal 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
140
eth2/spec/src/foundation.rs
Normal 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
74
eth2/spec/src/lib.rs
Normal 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
13
eth2/types/Cargo.toml
Normal 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" }
|
||||
79
eth2/types/src/attestation.rs
Normal file
79
eth2/types/src/attestation.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
119
eth2/types/src/attestation_data.rs
Normal file
119
eth2/types/src/attestation_data.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
60
eth2/types/src/attestation_data_and_custody_bit.rs
Normal file
60
eth2/types/src/attestation_data_and_custody_bit.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
94
eth2/types/src/beacon_block.rs
Normal file
94
eth2/types/src/beacon_block.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
95
eth2/types/src/beacon_block_body.rs
Normal file
95
eth2/types/src/beacon_block_body.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
219
eth2/types/src/beacon_state.rs
Normal file
219
eth2/types/src/beacon_state.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
60
eth2/types/src/candidate_pow_receipt_root_record.rs
Normal file
60
eth2/types/src/candidate_pow_receipt_root_record.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
59
eth2/types/src/casper_slashing.rs
Normal file
59
eth2/types/src/casper_slashing.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
69
eth2/types/src/crosslink_record.rs
Normal file
69
eth2/types/src/crosslink_record.rs
Normal 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
64
eth2/types/src/deposit.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
64
eth2/types/src/deposit_data.rs
Normal file
64
eth2/types/src/deposit_data.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
75
eth2/types/src/deposit_input.rs
Normal file
75
eth2/types/src/deposit_input.rs
Normal 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
64
eth2/types/src/exit.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
63
eth2/types/src/fork_data.rs
Normal file
63
eth2/types/src/fork_data.rs
Normal 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
68
eth2/types/src/lib.rs
Normal 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};
|
||||
69
eth2/types/src/pending_attestation_record.rs
Normal file
69
eth2/types/src/pending_attestation_record.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
64
eth2/types/src/proposal_signed_data.rs
Normal file
64
eth2/types/src/proposal_signed_data.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
75
eth2/types/src/proposer_slashing.rs
Normal file
75
eth2/types/src/proposer_slashing.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
40
eth2/types/src/readers/block_reader.rs
Normal file
40
eth2/types/src/readers/block_reader.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
5
eth2/types/src/readers/mod.rs
Normal file
5
eth2/types/src/readers/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod block_reader;
|
||||
mod state_reader;
|
||||
|
||||
pub use self::block_reader::BeaconBlockReader;
|
||||
pub use self::state_reader::BeaconStateReader;
|
||||
30
eth2/types/src/readers/state_reader.rs
Normal file
30
eth2/types/src/readers/state_reader.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
52
eth2/types/src/shard_committee.rs
Normal file
52
eth2/types/src/shard_committee.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
63
eth2/types/src/shard_reassignment_record.rs
Normal file
63
eth2/types/src/shard_reassignment_record.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
70
eth2/types/src/slashable_vote_data.rs
Normal file
70
eth2/types/src/slashable_vote_data.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
132
eth2/types/src/special_record.rs
Normal file
132
eth2/types/src/special_record.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
11
eth2/types/src/test_utils/address.rs
Normal file
11
eth2/types/src/test_utils/address.rs
Normal 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[..])
|
||||
}
|
||||
}
|
||||
12
eth2/types/src/test_utils/aggregate_signature.rs
Normal file
12
eth2/types/src/test_utils/aggregate_signature.rs
Normal 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
|
||||
}
|
||||
}
|
||||
11
eth2/types/src/test_utils/bitfield.rs
Normal file
11
eth2/types/src/test_utils/bitfield.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
11
eth2/types/src/test_utils/hash256.rs
Normal file
11
eth2/types/src/test_utils/hash256.rs
Normal 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[..])
|
||||
}
|
||||
}
|
||||
49
eth2/types/src/test_utils/mod.rs
Normal file
49
eth2/types/src/test_utils/mod.rs
Normal 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),
|
||||
]
|
||||
}
|
||||
}
|
||||
10
eth2/types/src/test_utils/public_key.rs
Normal file
10
eth2/types/src/test_utils/public_key.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
19
eth2/types/src/test_utils/secret_key.rs
Normal file
19
eth2/types/src/test_utils/secret_key.rs
Normal 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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
13
eth2/types/src/test_utils/signature.rs
Normal file
13
eth2/types/src/test_utils/signature.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
213
eth2/types/src/validator_record.rs
Normal file
213
eth2/types/src/validator_record.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
eth2/types/src/validator_registration.rs
Normal file
12
eth2/types/src/validator_registration.rs
Normal 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,
|
||||
}
|
||||
171
eth2/types/src/validator_registry.rs
Normal file
171
eth2/types/src/validator_registry.rs
Normal 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
11
eth2/utils/bls/Cargo.toml
Normal 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" }
|
||||
65
eth2/utils/bls/src/aggregate_signature.rs
Normal file
65
eth2/utils/bls/src/aggregate_signature.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
16
eth2/utils/bls/src/keypair.rs
Normal file
16
eth2/utils/bls/src/keypair.rs
Normal 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
42
eth2/utils/bls/src/lib.rs
Normal 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)
|
||||
}
|
||||
83
eth2/utils/bls/src/public_key.rs
Normal file
83
eth2/utils/bls/src/public_key.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
59
eth2/utils/bls/src/secret_key.rs
Normal file
59
eth2/utils/bls/src/secret_key.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
89
eth2/utils/bls/src/signature.rs
Normal file
89
eth2/utils/bls/src/signature.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
eth2/utils/boolean-bitfield/Cargo.toml
Normal file
9
eth2/utils/boolean-bitfield/Cargo.toml
Normal 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"
|
||||
3
eth2/utils/boolean-bitfield/README.md
Normal file
3
eth2/utils/boolean-bitfield/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Boolean Bitfield
|
||||
|
||||
Implements a set of boolean as a tightly-packed vector of bits.
|
||||
362
eth2/utils/boolean-bitfield/src/lib.rs
Normal file
362
eth2/utils/boolean-bitfield/src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
8
eth2/utils/hashing/Cargo.toml
Normal file
8
eth2/utils/hashing/Cargo.toml
Normal 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"
|
||||
30
eth2/utils/hashing/src/lib.rs
Normal file
30
eth2/utils/hashing/src/lib.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
7
eth2/utils/honey-badger-split/Cargo.toml
Normal file
7
eth2/utils/honey-badger-split/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "honey-badger-split"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
85
eth2/utils/honey-badger-split/src/lib.rs
Normal file
85
eth2/utils/honey-badger-split/src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
7
eth2/utils/slot_clock/Cargo.toml
Normal file
7
eth2/utils/slot_clock/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "slot_clock"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Hauner <paul@paulhauner.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
11
eth2/utils/slot_clock/src/lib.rs
Normal file
11
eth2/utils/slot_clock/src/lib.rs
Normal 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>;
|
||||
}
|
||||
135
eth2/utils/slot_clock/src/system_time_slot_clock.rs
Normal file
135
eth2/utils/slot_clock/src/system_time_slot_clock.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
43
eth2/utils/slot_clock/src/testing_slot_clock.rs
Normal file
43
eth2/utils/slot_clock/src/testing_slot_clock.rs
Normal 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)));
|
||||
}
|
||||
}
|
||||
9
eth2/utils/ssz/Cargo.toml
Normal file
9
eth2/utils/ssz/Cargo.toml
Normal 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
543
eth2/utils/ssz/README.md
Normal 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);
|
||||
```
|
||||
193
eth2/utils/ssz/src/decode.rs
Normal file
193
eth2/utils/ssz/src/decode.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
124
eth2/utils/ssz/src/encode.rs
Normal file
124
eth2/utils/ssz/src/encode.rs
Normal 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]);
|
||||
}
|
||||
}
|
||||
218
eth2/utils/ssz/src/impl_decode.rs
Normal file
218
eth2/utils/ssz/src/impl_decode.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
201
eth2/utils/ssz/src/impl_encode.rs
Normal file
201
eth2/utils/ssz/src/impl_encode.rs
Normal 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
33
eth2/utils/ssz/src/lib.rs
Normal 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()
|
||||
}
|
||||
11
eth2/utils/vec_shuffle/Cargo.toml
Normal file
11
eth2/utils/vec_shuffle/Cargo.toml
Normal 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"
|
||||
81
eth2/utils/vec_shuffle/src/lib.rs
Normal file
81
eth2/utils/vec_shuffle/src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
90
eth2/utils/vec_shuffle/src/rng.rs
Normal file
90
eth2/utils/vec_shuffle/src/rng.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
131
eth2/utils/vec_shuffle/src/specs/shuffle_test_vectors.yaml
Normal file
131
eth2/utils/vec_shuffle/src/specs/shuffle_test_vectors.yaml
Normal 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
|
||||
11
eth2/validator_change/Cargo.toml
Normal file
11
eth2/validator_change/Cargo.toml
Normal 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" }
|
||||
142
eth2/validator_change/src/lib.rs
Normal file
142
eth2/validator_change/src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
11
eth2/validator_induction/Cargo.toml
Normal file
11
eth2/validator_induction/Cargo.toml
Normal 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" }
|
||||
185
eth2/validator_induction/src/inductor.rs
Normal file
185
eth2/validator_induction/src/inductor.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
8
eth2/validator_induction/src/lib.rs
Normal file
8
eth2/validator_induction/src/lib.rs
Normal 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};
|
||||
11
eth2/validator_shuffling/Cargo.toml
Normal file
11
eth2/validator_shuffling/Cargo.toml
Normal 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" }
|
||||
8
eth2/validator_shuffling/src/lib.rs
Normal file
8
eth2/validator_shuffling/src/lib.rs
Normal 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};
|
||||
279
eth2/validator_shuffling/src/shuffle.rs
Normal file
279
eth2/validator_shuffling/src/shuffle.rs
Normal 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."
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user