Updates network branch to v0.5.0

This commit is contained in:
Age Manning
2019-03-18 18:17:37 +11:00
174 changed files with 7711 additions and 3924 deletions
+15 -27
View File
@@ -1,15 +1,26 @@
use super::{AggregateSignature, AttestationData, Bitfield};
use crate::test_utils::TestRandom;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz::TreeHash;
use ssz_derive::{Decode, Encode, SignedRoot, TreeHash};
use test_random_derive::TestRandom;
/// Details an attestation that can be slashable.
///
/// Spec v0.4.0
#[derive(Debug, Clone, PartialEq, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)]
/// Spec v0.5.0
#[derive(
Debug,
Clone,
PartialEq,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
TestRandom,
SignedRoot,
)]
pub struct Attestation {
pub aggregation_bitfield: Bitfield,
pub data: AttestationData,
@@ -20,29 +31,6 @@ pub struct Attestation {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[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);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Attestation::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(Attestation);
}
+14 -31
View File
@@ -1,20 +1,21 @@
use crate::test_utils::TestRandom;
use crate::{Crosslink, Epoch, Hash256, Slot};
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz::TreeHash;
use ssz_derive::{Decode, Encode, SignedRoot, TreeHash};
use test_random_derive::TestRandom;
/// The data upon which an attestation is based.
///
/// Spec v0.4.0
/// Spec v0.5.0
#[derive(
Debug,
Clone,
PartialEq,
Default,
Serialize,
Deserialize,
Hash,
Encode,
Decode,
@@ -23,14 +24,19 @@ use test_random_derive::TestRandom;
SignedRoot,
)]
pub struct AttestationData {
// LMD GHOST vote
pub slot: Slot,
pub shard: u64,
pub beacon_block_root: Hash256,
pub epoch_boundary_root: Hash256,
// FFG Vote
pub source_epoch: Epoch,
pub source_root: Hash256,
pub target_root: Hash256,
// Crosslink Vote
pub shard: u64,
pub previous_crosslink: Crosslink,
pub crosslink_data_root: Hash256,
pub latest_crosslink: Crosslink,
pub justified_epoch: Epoch,
pub justified_block_root: Hash256,
}
impl Eq for AttestationData {}
@@ -38,29 +44,6 @@ impl Eq for AttestationData {}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[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);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = AttestationData::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(AttestationData);
}
@@ -6,7 +6,7 @@ use ssz_derive::{Decode, Encode, TreeHash};
/// Used for pairing an attestation with a proof-of-custody.
///
/// Spec v0.4.0
/// Spec v0.5.0
#[derive(Debug, Clone, PartialEq, Default, Serialize, Encode, Decode, TreeHash)]
pub struct AttestationDataAndCustodyBit {
pub data: AttestationData,
@@ -25,31 +25,6 @@ impl<T: RngCore> TestRandom<T> for AttestationDataAndCustodyBit {
#[cfg(test)]
mod test {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[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);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = AttestationDataAndCustodyBit::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(AttestationDataAndCustodyBit);
}
+9
View File
@@ -0,0 +1,9 @@
use crate::*;
use serde_derive::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
pub struct AttestationDuty {
pub slot: Slot,
pub shard: Shard,
pub committee_index: usize,
}
+4 -31
View File
@@ -1,17 +1,13 @@
use crate::{test_utils::TestRandom, SlashableAttestation};
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
mod builder;
pub use builder::AttesterSlashingBuilder;
/// Two conflicting attestations.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
/// Spec v0.5.0
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
pub struct AttesterSlashing {
pub slashable_attestation_1: SlashableAttestation,
pub slashable_attestation_2: SlashableAttestation,
@@ -20,29 +16,6 @@ pub struct AttesterSlashing {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = AttesterSlashing::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = AttesterSlashing::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(AttesterSlashing);
}
+60 -41
View File
@@ -1,40 +1,50 @@
use crate::test_utils::TestRandom;
use crate::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Slot};
use crate::*;
use bls::Signature;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz::TreeHash;
use ssz_derive::{Decode, Encode, SignedRoot, TreeHash};
use test_random_derive::TestRandom;
/// A block of the `BeaconChain`.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)]
/// Spec v0.5.0
#[derive(
Debug,
PartialEq,
Clone,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
TestRandom,
SignedRoot,
)]
pub struct BeaconBlock {
pub slot: Slot,
pub parent_root: Hash256,
pub previous_block_root: Hash256,
pub state_root: Hash256,
pub randao_reveal: Signature,
pub eth1_data: Eth1Data,
pub body: BeaconBlockBody,
pub signature: Signature,
}
impl BeaconBlock {
/// Produce the first block of the Beacon Chain.
pub fn genesis(state_root: Hash256, spec: &ChainSpec) -> BeaconBlock {
/// Returns an empty block to be used during genesis.
///
/// Spec v0.5.0
pub fn empty(spec: &ChainSpec) -> BeaconBlock {
BeaconBlock {
slot: spec.genesis_slot,
parent_root: spec.zero_hash,
state_root,
randao_reveal: spec.empty_signature.clone(),
eth1_data: Eth1Data {
deposit_root: spec.zero_hash,
block_hash: spec.zero_hash,
},
signature: spec.empty_signature.clone(),
previous_block_root: spec.zero_hash,
state_root: spec.zero_hash,
body: BeaconBlockBody {
randao_reveal: spec.empty_signature.clone(),
eth1_data: Eth1Data {
deposit_root: spec.zero_hash,
block_hash: spec.zero_hash,
},
proposer_slashings: vec![],
attester_slashings: vec![],
attestations: vec![],
@@ -42,41 +52,50 @@ impl BeaconBlock {
voluntary_exits: vec![],
transfers: vec![],
},
signature: spec.empty_signature.clone(),
}
}
/// Returns the `hash_tree_root` of the block.
///
/// Spec v0.5.0
pub fn canonical_root(&self) -> Hash256 {
Hash256::from_slice(&self.hash_tree_root()[..])
}
/// Returns a full `BeaconBlockHeader` of this block.
///
/// Note: This method is used instead of an `Into` impl to avoid a `Clone` of an entire block
/// when you want to have the block _and_ the header.
///
/// Note: performs a full tree-hash of `self.body`.
///
/// Spec v0.5.0
pub fn into_header(&self) -> BeaconBlockHeader {
BeaconBlockHeader {
slot: self.slot,
previous_block_root: self.previous_block_root,
state_root: self.state_root,
block_body_root: Hash256::from_slice(&self.body.hash_tree_root()[..]),
signature: self.signature.clone(),
}
}
/// Returns a "temporary" header, where the `state_root` is `spec.zero_hash`.
///
/// Spec v0.5.0
pub fn into_temporary_header(&self, spec: &ChainSpec) -> BeaconBlockHeader {
BeaconBlockHeader {
state_root: spec.zero_hash,
signature: spec.empty_signature.clone(),
..self.into_header()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[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);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = BeaconBlock::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(BeaconBlock);
}
+7 -28
View File
@@ -1,15 +1,17 @@
use super::{Attestation, AttesterSlashing, Deposit, ProposerSlashing, Transfer, VoluntaryExit};
use crate::test_utils::TestRandom;
use crate::*;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
/// The body of a `BeaconChain` block, containing operations.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Default, Serialize, Encode, Decode, TreeHash, TestRandom)]
/// Spec v0.5.0
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
pub struct BeaconBlockBody {
pub randao_reveal: Signature,
pub eth1_data: Eth1Data,
pub proposer_slashings: Vec<ProposerSlashing>,
pub attester_slashings: Vec<AttesterSlashing>,
pub attestations: Vec<Attestation>,
@@ -21,29 +23,6 @@ pub struct BeaconBlockBody {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[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);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = BeaconBlockBody::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(BeaconBlockBody);
}
+47
View File
@@ -0,0 +1,47 @@
use crate::test_utils::TestRandom;
use crate::*;
use bls::Signature;
use rand::RngCore;
use serde_derive::{Deserialize, Serialize};
use ssz::TreeHash;
use ssz_derive::{Decode, Encode, SignedRoot, TreeHash};
use test_random_derive::TestRandom;
/// A header of a `BeaconBlock`.
///
/// Spec v0.5.0
#[derive(
Debug,
PartialEq,
Clone,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
TestRandom,
SignedRoot,
)]
pub struct BeaconBlockHeader {
pub slot: Slot,
pub previous_block_root: Hash256,
pub state_root: Hash256,
pub block_body_root: Hash256,
pub signature: Signature,
}
impl BeaconBlockHeader {
/// Returns the `hash_tree_root` of the header.
///
/// Spec v0.5.0
pub fn canonical_root(&self) -> Hash256 {
Hash256::from_slice(&self.hash_tree_root()[..])
}
}
#[cfg(test)]
mod tests {
use super::*;
ssz_tests!(BeaconBlockHeader);
}
File diff suppressed because it is too large Load Diff
-263
View File
@@ -1,263 +0,0 @@
use crate::*;
use bls::create_proof_of_possession;
/// Builds a `BeaconState` for use in testing or benchmarking.
///
/// Building the `BeaconState` is a three step processes:
///
/// 1. Create a new `BeaconStateBuilder`.
/// 2. Call `Self::build()` or `Self::build_fast()` generate a `BeaconState`.
/// 3. (Optional) Use builder functions to modify the `BeaconState`.
/// 4. Call `Self::cloned_state()` to obtain a `BeaconState` cloned from this struct.
///
/// Step (2) happens prior to step (3) because some functionality requires an existing
/// `BeaconState`.
///
/// Step (4) produces a clone of the BeaconState and doesn't consume the `BeaconStateBuilder` to
/// allow access to `self.keypairs` and `self.spec`.
pub struct BeaconStateBuilder {
pub validator_count: usize,
pub state: Option<BeaconState>,
pub genesis_time: u64,
pub latest_eth1_data: Eth1Data,
pub spec: ChainSpec,
pub keypairs: Vec<Keypair>,
}
impl BeaconStateBuilder {
/// Create a new builder with the given number of validators.
pub fn new(validator_count: usize) -> Self {
let genesis_time = 10_000_000;
let latest_eth1_data = Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
};
let spec = ChainSpec::foundation();
Self {
validator_count,
state: None,
genesis_time,
latest_eth1_data,
spec,
keypairs: vec![],
}
}
/// Builds a `BeaconState` using the `BeaconState::genesis(..)` function.
///
/// Each validator is assigned a unique, randomly-generated keypair and all
/// proof-of-possessions are verified during genesis.
pub fn build(&mut self) -> Result<(), BeaconStateError> {
self.keypairs = (0..self.validator_count)
.collect::<Vec<usize>>()
.iter()
.map(|_| Keypair::random())
.collect();
let initial_validator_deposits = self
.keypairs
.iter()
.map(|keypair| Deposit {
branch: vec![], // branch verification is not specified.
index: 0, // index verification is not specified.
deposit_data: DepositData {
amount: 32_000_000_000, // 32 ETH (in Gwei)
timestamp: self.genesis_time - 1,
deposit_input: DepositInput {
pubkey: keypair.pk.clone(),
withdrawal_credentials: Hash256::zero(), // Withdrawal not possible.
proof_of_possession: create_proof_of_possession(&keypair),
},
},
})
.collect();
let state = BeaconState::genesis(
self.genesis_time,
initial_validator_deposits,
self.latest_eth1_data.clone(),
&self.spec,
)?;
self.state = Some(state);
Ok(())
}
/// Builds a `BeaconState` using the `BeaconState::genesis(..)` function, without supplying any
/// validators. Instead validators are added to the state post-genesis.
///
/// One keypair is randomly generated and all validators are assigned this same keypair.
/// Proof-of-possessions are not created (or validated).
///
/// This function runs orders of magnitude faster than `Self::build()`, however it will be
/// erroneous for functions which use a validators public key as an identifier (e.g.,
/// deposits).
pub fn build_fast(&mut self) -> Result<(), BeaconStateError> {
let common_keypair = Keypair::random();
let mut validator_registry = Vec::with_capacity(self.validator_count);
let mut validator_balances = Vec::with_capacity(self.validator_count);
self.keypairs = Vec::with_capacity(self.validator_count);
for _ in 0..self.validator_count {
self.keypairs.push(common_keypair.clone());
validator_balances.push(32_000_000_000);
validator_registry.push(Validator {
pubkey: common_keypair.pk.clone(),
withdrawal_credentials: Hash256::zero(),
activation_epoch: self.spec.genesis_epoch,
..Validator::default()
})
}
let state = BeaconState {
validator_registry,
validator_balances,
..BeaconState::genesis(
self.genesis_time,
vec![],
self.latest_eth1_data.clone(),
&self.spec,
)?
};
self.state = Some(state);
Ok(())
}
/// Sets the `BeaconState` to be in the last slot of the given epoch.
///
/// Sets all justification/finalization parameters to be be as "perfect" as possible (i.e.,
/// highest justified and finalized slots, full justification bitfield, etc).
pub fn teleport_to_end_of_epoch(&mut self, epoch: Epoch) {
let state = self.state.as_mut().expect("Genesis required");
let slot = epoch.end_slot(self.spec.slots_per_epoch);
state.slot = slot;
state.validator_registry_update_epoch = epoch - 1;
state.previous_shuffling_epoch = epoch - 1;
state.current_shuffling_epoch = epoch;
state.previous_shuffling_seed = Hash256::from_low_u64_le(0);
state.current_shuffling_seed = Hash256::from_low_u64_le(1);
state.previous_justified_epoch = epoch - 2;
state.justified_epoch = epoch - 1;
state.justification_bitfield = u64::max_value();
state.finalized_epoch = epoch - 1;
}
/// Creates a full set of attestations for the `BeaconState`. Each attestation has full
/// participation from its committee and references the expected beacon_block hashes.
///
/// These attestations should be fully conducive to justification and finalization.
pub fn insert_attestations(&mut self) {
let state = self.state.as_mut().expect("Genesis required");
state
.build_epoch_cache(RelativeEpoch::Previous, &self.spec)
.unwrap();
state
.build_epoch_cache(RelativeEpoch::Current, &self.spec)
.unwrap();
let current_epoch = state.current_epoch(&self.spec);
let previous_epoch = state.previous_epoch(&self.spec);
let current_epoch_depth =
(state.slot - current_epoch.end_slot(self.spec.slots_per_epoch)).as_usize();
let previous_epoch_slots = previous_epoch.slot_iter(self.spec.slots_per_epoch);
let current_epoch_slots = current_epoch
.slot_iter(self.spec.slots_per_epoch)
.take(current_epoch_depth);
for slot in previous_epoch_slots.chain(current_epoch_slots) {
let committees = state
.get_crosslink_committees_at_slot(slot, &self.spec)
.unwrap()
.clone();
for (committee, shard) in committees {
state
.latest_attestations
.push(committee_to_pending_attestation(
state, &committee, shard, slot, &self.spec,
))
}
}
}
/// Returns a cloned `BeaconState`.
pub fn cloned_state(&self) -> BeaconState {
self.state.as_ref().expect("Genesis required").clone()
}
}
/// Builds a valid PendingAttestation with full participation for some committee.
fn committee_to_pending_attestation(
state: &BeaconState,
committee: &[usize],
shard: u64,
slot: Slot,
spec: &ChainSpec,
) -> PendingAttestation {
let current_epoch = state.current_epoch(spec);
let previous_epoch = state.previous_epoch(spec);
let mut aggregation_bitfield = Bitfield::new();
let mut custody_bitfield = Bitfield::new();
for (i, _) in committee.iter().enumerate() {
aggregation_bitfield.set(i, true);
custody_bitfield.set(i, true);
}
let is_previous_epoch =
state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch);
let justified_epoch = if is_previous_epoch {
state.previous_justified_epoch
} else {
state.justified_epoch
};
let epoch_boundary_root = if is_previous_epoch {
*state
.get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec)
.unwrap()
} else {
*state
.get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec)
.unwrap()
};
let justified_block_root = *state
.get_block_root(justified_epoch.start_slot(spec.slots_per_epoch), &spec)
.unwrap();
PendingAttestation {
aggregation_bitfield,
data: AttestationData {
slot,
shard,
beacon_block_root: *state.get_block_root(slot, spec).unwrap(),
epoch_boundary_root,
crosslink_data_root: Hash256::zero(),
latest_crosslink: Crosslink {
epoch: slot.epoch(spec.slots_per_epoch),
crosslink_data_root: Hash256::zero(),
},
justified_epoch,
justified_block_root,
},
custody_bitfield,
inclusion_slot: slot,
}
}
+279 -58
View File
@@ -1,84 +1,305 @@
use super::{AttestationDutyMap, BeaconState, CrosslinkCommittees, Error, ShardCommitteeIndexMap};
use crate::{ChainSpec, Epoch};
use log::trace;
use serde_derive::Serialize;
use std::collections::HashMap;
use super::BeaconState;
use crate::*;
use honey_badger_split::SplitExt;
use serde_derive::{Deserialize, Serialize};
use swap_or_not_shuffle::shuffle_list;
#[derive(Debug, PartialEq, Clone, Serialize)]
#[derive(Debug, PartialEq)]
pub enum Error {
UnableToShuffle,
UnableToGenerateSeed,
}
mod tests;
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
pub struct EpochCache {
/// True if this cache has been initialized.
pub initialized: bool,
/// The crosslink committees for an epoch.
pub committees: Vec<CrosslinkCommittees>,
/// `Some(epoch)` if the cache is initialized, where `epoch` is the cache it holds.
pub initialized_epoch: Option<Epoch>,
/// All crosslink committees for an epoch.
pub epoch_crosslink_committees: EpochCrosslinkCommittees,
/// Maps validator index to a slot, shard and committee index for attestation.
pub attestation_duty_map: AttestationDutyMap,
pub attestation_duties: Vec<Option<AttestationDuty>>,
/// Maps a shard to an index of `self.committees`.
pub shard_committee_index_map: ShardCommitteeIndexMap,
pub shard_committee_indices: Vec<Option<(Slot, usize)>>,
/// Indices of all active validators in the epoch
pub active_validator_indices: Vec<usize>,
}
impl EpochCache {
pub fn empty() -> EpochCache {
EpochCache {
initialized: false,
committees: vec![],
attestation_duty_map: AttestationDutyMap::new(),
shard_committee_index_map: ShardCommitteeIndexMap::new(),
}
}
/// Return a new, fully initialized cache.
pub fn initialized(
state: &BeaconState,
epoch: Epoch,
relative_epoch: RelativeEpoch,
spec: &ChainSpec,
) -> Result<EpochCache, Error> {
let mut epoch_committees: Vec<CrosslinkCommittees> =
Vec::with_capacity(spec.slots_per_epoch as usize);
let mut attestation_duty_map: AttestationDutyMap = HashMap::new();
let mut shard_committee_index_map: ShardCommitteeIndexMap = HashMap::new();
let epoch = relative_epoch.into_epoch(state.slot.epoch(spec.slots_per_epoch));
let shuffling =
state.get_shuffling_for_slot(epoch.start_slot(spec.slots_per_epoch), false, spec)?;
let active_validator_indices =
get_active_validator_indices(&state.validator_registry, epoch);
for (epoch_committeess_index, slot) in epoch.slot_iter(spec.slots_per_epoch).enumerate() {
let slot_committees = state.calculate_crosslink_committees_at_slot(
slot,
false,
shuffling.clone(),
let builder = match relative_epoch {
RelativeEpoch::Previous => EpochCrosslinkCommitteesBuilder::for_previous_epoch(
state,
active_validator_indices.clone(),
spec,
)?;
),
RelativeEpoch::Current => EpochCrosslinkCommitteesBuilder::for_current_epoch(
state,
active_validator_indices.clone(),
spec,
),
RelativeEpoch::NextWithRegistryChange => {
EpochCrosslinkCommitteesBuilder::for_next_epoch(
state,
active_validator_indices.clone(),
true,
spec,
)?
}
RelativeEpoch::NextWithoutRegistryChange => {
EpochCrosslinkCommitteesBuilder::for_next_epoch(
state,
active_validator_indices.clone(),
false,
spec,
)?
}
};
let epoch_crosslink_committees = builder.build(spec)?;
for (slot_committees_index, (committee, shard)) in slot_committees.iter().enumerate() {
// Empty committees are not permitted.
if committee.is_empty() {
return Err(Error::InsufficientValidators);
}
// Loop through all the validators in the committees and create the following maps:
//
// 1. `attestation_duties`: maps `ValidatorIndex` to `AttestationDuty`.
// 2. `shard_committee_indices`: maps `Shard` into a `CrosslinkCommittee` in
// `EpochCrosslinkCommittees`.
let mut attestation_duties = vec![None; state.validator_registry.len()];
let mut shard_committee_indices = vec![None; spec.shard_count as usize];
for (i, slot_committees) in epoch_crosslink_committees
.crosslink_committees
.iter()
.enumerate()
{
let slot = epoch.start_slot(spec.slots_per_epoch) + i as u64;
trace!(
"shard: {}, epoch_i: {}, slot_i: {}",
shard,
epoch_committeess_index,
slot_committees_index
);
for (j, crosslink_committee) in slot_committees.iter().enumerate() {
let shard = crosslink_committee.shard;
shard_committee_index_map
.insert(*shard, (epoch_committeess_index, slot_committees_index));
shard_committee_indices[shard as usize] = Some((slot, j));
for (committee_index, validator_index) in committee.iter().enumerate() {
attestation_duty_map.insert(
*validator_index as u64,
(slot, *shard, committee_index as u64),
);
for (k, validator_index) in crosslink_committee.committee.iter().enumerate() {
let attestation_duty = AttestationDuty {
slot,
shard,
committee_index: k,
};
attestation_duties[*validator_index] = Some(attestation_duty)
}
}
epoch_committees.push(slot_committees)
}
Ok(EpochCache {
initialized: true,
committees: epoch_committees,
attestation_duty_map,
shard_committee_index_map,
initialized_epoch: Some(epoch),
epoch_crosslink_committees,
attestation_duties,
shard_committee_indices,
active_validator_indices,
})
}
pub fn get_crosslink_committees_at_slot(
&self,
slot: Slot,
spec: &ChainSpec,
) -> Option<&Vec<CrosslinkCommittee>> {
self.epoch_crosslink_committees
.get_crosslink_committees_at_slot(slot, spec)
}
pub fn get_crosslink_committee_for_shard(
&self,
shard: Shard,
spec: &ChainSpec,
) -> Option<&CrosslinkCommittee> {
if shard > self.shard_committee_indices.len() as u64 {
None
} else {
let (slot, committee) = self.shard_committee_indices[shard as usize]?;
let slot_committees = self.get_crosslink_committees_at_slot(slot, spec)?;
slot_committees.get(committee)
}
}
}
pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec<usize> {
let mut active = Vec::with_capacity(validators.len());
for (index, validator) in validators.iter().enumerate() {
if validator.is_active_at(epoch) {
active.push(index)
}
}
active.shrink_to_fit();
active
}
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
pub struct EpochCrosslinkCommittees {
epoch: Epoch,
pub crosslink_committees: Vec<Vec<CrosslinkCommittee>>,
}
impl EpochCrosslinkCommittees {
fn new(epoch: Epoch, spec: &ChainSpec) -> Self {
Self {
epoch,
crosslink_committees: vec![vec![]; spec.slots_per_epoch as usize],
}
}
fn get_crosslink_committees_at_slot(
&self,
slot: Slot,
spec: &ChainSpec,
) -> Option<&Vec<CrosslinkCommittee>> {
let epoch_start_slot = self.epoch.start_slot(spec.slots_per_epoch);
let epoch_end_slot = self.epoch.end_slot(spec.slots_per_epoch);
if (epoch_start_slot <= slot) && (slot <= epoch_end_slot) {
let index = slot - epoch_start_slot;
self.crosslink_committees.get(index.as_usize())
} else {
None
}
}
}
pub struct EpochCrosslinkCommitteesBuilder {
epoch: Epoch,
shuffling_start_shard: Shard,
shuffling_seed: Hash256,
active_validator_indices: Vec<usize>,
committees_per_epoch: u64,
}
impl EpochCrosslinkCommitteesBuilder {
pub fn for_previous_epoch(
state: &BeaconState,
active_validator_indices: Vec<usize>,
spec: &ChainSpec,
) -> Self {
Self {
epoch: state.previous_epoch(spec),
shuffling_start_shard: state.previous_shuffling_start_shard,
shuffling_seed: state.previous_shuffling_seed,
committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()),
active_validator_indices,
}
}
pub fn for_current_epoch(
state: &BeaconState,
active_validator_indices: Vec<usize>,
spec: &ChainSpec,
) -> Self {
Self {
epoch: state.current_epoch(spec),
shuffling_start_shard: state.current_shuffling_start_shard,
shuffling_seed: state.current_shuffling_seed,
committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()),
active_validator_indices,
}
}
pub fn for_next_epoch(
state: &BeaconState,
active_validator_indices: Vec<usize>,
registry_change: bool,
spec: &ChainSpec,
) -> Result<Self, Error> {
let current_epoch = state.current_epoch(spec);
let next_epoch = state.next_epoch(spec);
let committees_per_epoch = spec.get_epoch_committee_count(active_validator_indices.len());
let epochs_since_last_registry_update =
current_epoch - state.validator_registry_update_epoch;
let (seed, shuffling_start_shard) = if registry_change {
let next_seed = state
.generate_seed(next_epoch, spec)
.map_err(|_| Error::UnableToGenerateSeed)?;
(
next_seed,
(state.current_shuffling_start_shard + committees_per_epoch) % spec.shard_count,
)
} else if (epochs_since_last_registry_update > 1)
& epochs_since_last_registry_update.is_power_of_two()
{
let next_seed = state
.generate_seed(next_epoch, spec)
.map_err(|_| Error::UnableToGenerateSeed)?;
(next_seed, state.current_shuffling_start_shard)
} else {
(
state.current_shuffling_seed,
state.current_shuffling_start_shard,
)
};
Ok(Self {
epoch: state.next_epoch(spec),
shuffling_start_shard,
shuffling_seed: seed,
active_validator_indices,
committees_per_epoch,
})
}
pub fn build(self, spec: &ChainSpec) -> Result<EpochCrosslinkCommittees, Error> {
// The shuffler fails on a empty list, so if there are no active validator indices, simply
// return an empty list.
let shuffled_active_validator_indices = if self.active_validator_indices.is_empty() {
vec![]
} else {
shuffle_list(
self.active_validator_indices,
spec.shuffle_round_count,
&self.shuffling_seed[..],
true,
)
.ok_or_else(|| Error::UnableToShuffle)?
};
let mut committees: Vec<Vec<usize>> = shuffled_active_validator_indices
.honey_badger_split(self.committees_per_epoch as usize)
.map(|slice: &[usize]| slice.to_vec())
.collect();
let mut epoch_crosslink_committees = EpochCrosslinkCommittees::new(self.epoch, spec);
let mut shard = self.shuffling_start_shard;
let committees_per_slot = (self.committees_per_epoch / spec.slots_per_epoch) as usize;
for (i, slot) in self.epoch.slot_iter(spec.slots_per_epoch).enumerate() {
for j in (0..committees.len())
.into_iter()
.skip(i * committees_per_slot)
.take(committees_per_slot)
{
let crosslink_committee = CrosslinkCommittee {
slot,
shard,
committee: committees[j].drain(..).collect(),
};
epoch_crosslink_committees.crosslink_committees[i].push(crosslink_committee);
shard += 1;
shard %= spec.shard_count;
}
}
Ok(epoch_crosslink_committees)
}
}
@@ -0,0 +1,142 @@
#![cfg(test)]
use super::*;
use crate::test_utils::*;
use swap_or_not_shuffle::shuffle_list;
fn do_sane_cache_test(
state: BeaconState,
epoch: Epoch,
validator_count: usize,
expected_seed: Hash256,
expected_shuffling_start: u64,
spec: &ChainSpec,
) {
let active_indices: Vec<usize> = (0..validator_count).collect();
assert_eq!(
&active_indices[..],
state.get_active_validator_indices(epoch, &spec).unwrap(),
"Validator indices mismatch"
);
let shuffling = shuffle_list(
active_indices,
spec.shuffle_round_count,
&expected_seed[..],
true,
)
.unwrap();
let committees_per_epoch = spec.get_epoch_committee_count(shuffling.len());
let committees_per_slot = committees_per_epoch / spec.slots_per_epoch;
let mut expected_indices_iter = shuffling.iter();
let mut shard_counter = expected_shuffling_start;
for (i, slot) in epoch.slot_iter(spec.slots_per_epoch).enumerate() {
let crosslink_committees_at_slot =
state.get_crosslink_committees_at_slot(slot, &spec).unwrap();
assert_eq!(
crosslink_committees_at_slot.len(),
committees_per_slot as usize,
"Bad committees per slot ({})",
i
);
for c in crosslink_committees_at_slot {
assert_eq!(c.shard, shard_counter, "Bad shard");
shard_counter += 1;
shard_counter %= spec.shard_count;
for &i in &c.committee {
assert_eq!(
i,
*expected_indices_iter.next().unwrap(),
"Non-sequential validators."
);
}
}
}
}
fn setup_sane_cache_test(validator_count: usize, spec: &ChainSpec) -> BeaconState {
let mut builder =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, spec);
let epoch = spec.genesis_epoch + 4;
let slot = epoch.start_slot(spec.slots_per_epoch);
builder.teleport_to_slot(slot, spec);
let (mut state, _keypairs) = builder.build();
state.current_shuffling_start_shard = 0;
state.current_shuffling_seed = Hash256::from_slice(&[1; 32]);
state.previous_shuffling_start_shard = spec.shard_count - 1;
state.previous_shuffling_seed = Hash256::from_slice(&[2; 32]);
state
.build_epoch_cache(RelativeEpoch::Previous, spec)
.unwrap();
state
.build_epoch_cache(RelativeEpoch::Current, spec)
.unwrap();
state
.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec)
.unwrap();
state
.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, spec)
.unwrap();
state
}
#[test]
fn builds_sane_current_epoch_cache() {
let mut spec = ChainSpec::few_validators();
spec.shard_count = 4;
let validator_count = (spec.shard_count * spec.target_committee_size) + 1;
let state = setup_sane_cache_test(validator_count as usize, &spec);
do_sane_cache_test(
state.clone(),
state.current_epoch(&spec),
validator_count as usize,
state.current_shuffling_seed,
state.current_shuffling_start_shard,
&spec,
);
}
#[test]
fn builds_sane_previous_epoch_cache() {
let mut spec = ChainSpec::few_validators();
spec.shard_count = 2;
let validator_count = (spec.shard_count * spec.target_committee_size) + 1;
let state = setup_sane_cache_test(validator_count as usize, &spec);
do_sane_cache_test(
state.clone(),
state.previous_epoch(&spec),
validator_count as usize,
state.previous_shuffling_seed,
state.previous_shuffling_start_shard,
&spec,
);
}
#[test]
fn builds_sane_next_without_update_epoch_cache() {
let mut spec = ChainSpec::few_validators();
spec.shard_count = 2;
let validator_count = (spec.shard_count * spec.target_committee_size) + 1;
let mut state = setup_sane_cache_test(validator_count as usize, &spec);
state.validator_registry_update_epoch = state.slot.epoch(spec.slots_per_epoch);
do_sane_cache_test(
state.clone(),
state.next_epoch(&spec),
validator_count as usize,
state.current_shuffling_seed,
state.current_shuffling_start_shard,
&spec,
);
}
+1 -1
View File
@@ -11,7 +11,7 @@ pub fn verify_bitfield_length(bitfield: &Bitfield, committee_size: usize) -> boo
}
for i in committee_size..(bitfield.num_bytes() * 8) {
if bitfield.get(i).expect("Impossible due to previous check.") {
if bitfield.get(i).unwrap_or(false) {
return false;
}
}
@@ -0,0 +1,38 @@
use crate::*;
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
type ValidatorIndex = usize;
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
pub struct PubkeyCache {
map: HashMap<PublicKey, ValidatorIndex>,
}
impl PubkeyCache {
/// Returns the number of validator indices already in the map.
pub fn len(&self) -> ValidatorIndex {
self.map.len()
}
/// Inserts a validator index into the map.
///
/// The added index must equal the number of validators already added to the map. This ensures
/// that an index is never skipped.
pub fn insert(&mut self, pubkey: PublicKey, index: ValidatorIndex) -> bool {
if index == self.map.len() {
self.map.insert(pubkey, index);
true
} else {
false
}
}
/// Inserts a validator index into the map.
///
/// The added index must equal the number of validators already added to the map. This ensures
/// that an index is never skipped.
pub fn get(&self, pubkey: &PublicKey) -> Option<ValidatorIndex> {
self.map.get(pubkey).cloned()
}
}
+41 -68
View File
@@ -1,84 +1,57 @@
#![cfg(test)]
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use crate::{BeaconState, ChainSpec};
use ssz::{ssz_encode, Decodable};
use crate::test_utils::*;
#[test]
pub fn can_produce_genesis_block() {
let mut builder = BeaconStateBuilder::new(2);
builder.build().unwrap();
}
ssz_tests!(BeaconState);
/// Tests that `get_attestation_participants` is consistent with the result of
/// get_crosslink_committees_at_slot` with a full bitfield.
#[test]
pub fn get_attestation_participants_consistency() {
let mut rng = XorShiftRng::from_seed([42; 16]);
/// Test that
///
/// 1. Using the cache before it's built fails.
/// 2. Using the cache after it's build passes.
/// 3. Using the cache after it's dropped fails.
fn test_cache_initialization<'a>(
state: &'a mut BeaconState,
relative_epoch: RelativeEpoch,
spec: &ChainSpec,
) {
let slot = relative_epoch
.into_epoch(state.slot.epoch(spec.slots_per_epoch))
.start_slot(spec.slots_per_epoch);
let mut builder = BeaconStateBuilder::new(8);
builder.spec = ChainSpec::few_validators();
// Assuming the cache isn't already built, assert that a call to a cache-using function fails.
assert_eq!(
state.get_beacon_proposer_index(slot, relative_epoch, spec),
Err(BeaconStateError::EpochCacheUninitialized(relative_epoch))
);
builder.build().unwrap();
// Build the cache.
state.build_epoch_cache(relative_epoch, spec).unwrap();
let mut state = builder.cloned_state();
let spec = builder.spec.clone();
state
.build_epoch_cache(RelativeEpoch::Previous, &spec)
// Assert a call to a cache-using function passes.
let _ = state
.get_beacon_proposer_index(slot, relative_epoch, spec)
.unwrap();
state
.build_epoch_cache(RelativeEpoch::Current, &spec)
.unwrap();
state.build_epoch_cache(RelativeEpoch::Next, &spec).unwrap();
for slot in state
.slot
.epoch(spec.slots_per_epoch)
.slot_iter(spec.slots_per_epoch)
{
let committees = state.get_crosslink_committees_at_slot(slot, &spec).unwrap();
// Drop the cache.
state.drop_cache(relative_epoch);
for (committee, shard) in committees {
let mut attestation_data = AttestationData::random_for_test(&mut rng);
attestation_data.slot = slot;
attestation_data.shard = *shard;
let mut bitfield = Bitfield::new();
for (i, _) in committee.iter().enumerate() {
bitfield.set(i, true);
}
assert_eq!(
state
.get_attestation_participants(&attestation_data, &bitfield, &spec)
.unwrap(),
*committee
);
}
}
// Assert a call to a cache-using function fail.
assert_eq!(
state.get_beacon_proposer_index(slot, relative_epoch, spec),
Err(BeaconStateError::EpochCacheUninitialized(relative_epoch))
);
}
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = BeaconState::random_for_test(&mut rng);
fn cache_initialization() {
let spec = ChainSpec::few_validators();
let (mut state, _keypairs) =
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec).build();
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
state.slot = (spec.genesis_epoch + 1).start_slot(spec.slots_per_epoch);
assert_eq!(original, decoded);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = BeaconState::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
test_cache_initialization(&mut state, RelativeEpoch::Previous, &spec);
test_cache_initialization(&mut state, RelativeEpoch::Current, &spec);
test_cache_initialization(&mut state, RelativeEpoch::NextWithRegistryChange, &spec);
test_cache_initialization(&mut state, RelativeEpoch::NextWithoutRegistryChange, &spec);
}
+74 -34
View File
@@ -1,21 +1,27 @@
use crate::{Address, Epoch, Fork, Hash256, Multiaddr, Slot};
use crate::*;
use bls::Signature;
use int_to_bytes::int_to_bytes4;
use serde_derive::Deserialize;
const GWEI: u64 = 1_000_000_000;
/// Each of the BLS signature domains.
///
/// Spec v0.5.0
pub enum Domain {
Deposit,
Attestation,
Proposal,
Exit,
BeaconBlock,
Randao,
Attestation,
Deposit,
Exit,
Transfer,
}
/// Holds all the "constants" for a BeaconChain.
///
/// Spec v0.4.0
#[derive(PartialEq, Debug, Clone)]
/// Spec v0.5.0
#[derive(PartialEq, Debug, Clone, Deserialize)]
#[serde(default)]
pub struct ChainSpec {
/*
* Misc
@@ -23,8 +29,7 @@ pub struct ChainSpec {
pub shard_count: u64,
pub target_committee_size: u64,
pub max_balance_churn_quotient: u64,
pub beacon_chain_shard_number: u64,
pub max_indices_per_slashable_vote: u64,
pub max_indices_per_slashable_vote: usize,
pub max_exit_dequeues_per_epoch: u64,
pub shuffle_round_count: u8,
@@ -45,7 +50,7 @@ pub struct ChainSpec {
/*
* Initial Values
*/
pub genesis_fork_version: u64,
pub genesis_fork_version: u32,
pub genesis_slot: Slot,
pub genesis_epoch: Epoch,
pub genesis_start_shard: u64,
@@ -63,12 +68,13 @@ pub struct ChainSpec {
pub min_seed_lookahead: Epoch,
pub activation_exit_delay: u64,
pub epochs_per_eth1_voting_period: u64,
pub slots_per_historical_root: usize,
pub min_validator_withdrawability_delay: Epoch,
pub persistent_committee_period: u64,
/*
* State list lengths
*/
pub latest_block_roots_length: usize,
pub latest_randao_mixes_length: usize,
pub latest_active_index_roots_length: usize,
pub latest_slashed_exit_length: usize,
@@ -100,12 +106,12 @@ pub struct ChainSpec {
*
* Use `ChainSpec::get_domain(..)` to access these values.
*/
domain_deposit: u64,
domain_attestation: u64,
domain_proposal: u64,
domain_exit: u64,
domain_randao: u64,
domain_transfer: u64,
domain_beacon_block: u32,
domain_randao: u32,
domain_attestation: u32,
domain_deposit: u32,
domain_exit: u32,
domain_transfer: u32,
/*
* Network specific parameters
@@ -130,24 +136,29 @@ impl ChainSpec {
/// Get the domain number that represents the fork meta and signature domain.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn get_domain(&self, epoch: Epoch, domain: Domain, fork: &Fork) -> u64 {
let domain_constant = match domain {
Domain::Deposit => self.domain_deposit,
Domain::Attestation => self.domain_attestation,
Domain::Proposal => self.domain_proposal,
Domain::Exit => self.domain_exit,
Domain::BeaconBlock => self.domain_beacon_block,
Domain::Randao => self.domain_randao,
Domain::Attestation => self.domain_attestation,
Domain::Deposit => self.domain_deposit,
Domain::Exit => self.domain_exit,
Domain::Transfer => self.domain_transfer,
};
let fork_version = fork.get_fork_version(epoch);
fork_version * u64::pow(2, 32) + domain_constant
let mut bytes: Vec<u8> = fork.get_fork_version(epoch).to_vec();
bytes.append(&mut int_to_bytes4(domain_constant));
let mut fork_and_domain = [0; 8];
fork_and_domain.copy_from_slice(&bytes);
u64::from_le_bytes(fork_and_domain)
}
/// Returns a `ChainSpec` compatible with the Ethereum Foundation specification.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn foundation() -> Self {
let genesis_slot = Slot::new(2_u64.pow(32));
let slots_per_epoch = 64;
@@ -160,7 +171,6 @@ impl ChainSpec {
shard_count: 1_024,
target_committee_size: 128,
max_balance_churn_quotient: 32,
beacon_chain_shard_number: u64::max_value(),
max_indices_per_slashable_vote: 4_096,
max_exit_dequeues_per_epoch: 4,
shuffle_round_count: 90,
@@ -200,12 +210,13 @@ impl ChainSpec {
min_seed_lookahead: Epoch::new(1),
activation_exit_delay: 4,
epochs_per_eth1_voting_period: 16,
slots_per_historical_root: 8_192,
min_validator_withdrawability_delay: Epoch::new(256),
persistent_committee_period: 2_048,
/*
* State list lengths
*/
latest_block_roots_length: 8_192,
latest_randao_mixes_length: 8_192,
latest_active_index_roots_length: 8_192,
latest_slashed_exit_length: 8_192,
@@ -232,11 +243,11 @@ impl ChainSpec {
/*
* Signature domains
*/
domain_deposit: 0,
domain_attestation: 1,
domain_proposal: 2,
domain_exit: 3,
domain_randao: 4,
domain_beacon_block: 0,
domain_randao: 1,
domain_attestation: 2,
domain_deposit: 3,
domain_exit: 4,
domain_transfer: 5,
/*
@@ -264,8 +275,6 @@ impl ChainSpec {
}
/// Returns a `ChainSpec` compatible with the specification suitable for 8 validators.
///
/// Spec v0.4.0
pub fn few_validators() -> Self {
let genesis_slot = Slot::new(2_u64.pow(32));
let slots_per_epoch = 8;
@@ -282,12 +291,43 @@ impl ChainSpec {
}
}
impl Default for ChainSpec {
fn default() -> Self {
Self::foundation()
}
}
#[cfg(test)]
mod tests {
use super::*;
use int_to_bytes::int_to_bytes8;
#[test]
fn test_foundation_spec_can_be_constructed() {
let _ = ChainSpec::foundation();
}
fn test_domain(domain_type: Domain, raw_domain: u32, spec: &ChainSpec) {
let fork = Fork::genesis(&spec);
let epoch = Epoch::new(0);
let domain = spec.get_domain(epoch, domain_type, &fork);
let mut expected = fork.get_fork_version(epoch).to_vec();
expected.append(&mut int_to_bytes4(raw_domain));
assert_eq!(int_to_bytes8(domain), expected);
}
#[test]
fn test_get_domain() {
let spec = ChainSpec::foundation();
test_domain(Domain::BeaconBlock, spec.domain_beacon_block, &spec);
test_domain(Domain::Randao, spec.domain_randao, &spec);
test_domain(Domain::Attestation, spec.domain_attestation, &spec);
test_domain(Domain::Deposit, spec.domain_deposit, &spec);
test_domain(Domain::Exit, spec.domain_exit, &spec);
test_domain(Domain::Transfer, spec.domain_transfer, &spec);
}
}
+14 -27
View File
@@ -1,15 +1,25 @@
use crate::test_utils::TestRandom;
use crate::{Epoch, Hash256};
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
/// Specifies the block hash for a shard at an epoch.
///
/// Spec v0.4.0
/// Spec v0.5.0
#[derive(
Debug, Clone, PartialEq, Default, Serialize, Hash, Encode, Decode, TreeHash, TestRandom,
Debug,
Clone,
PartialEq,
Default,
Serialize,
Deserialize,
Hash,
Encode,
Decode,
TreeHash,
TestRandom,
)]
pub struct Crosslink {
pub epoch: Epoch,
@@ -19,29 +29,6 @@ pub struct Crosslink {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Crosslink::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Crosslink::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(Crosslink);
}
+10
View File
@@ -0,0 +1,10 @@
use crate::*;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize, Decode, Encode, TreeHash)]
pub struct CrosslinkCommittee {
pub slot: Slot,
pub shard: Shard,
pub committee: Vec<usize>,
}
+5 -28
View File
@@ -1,16 +1,16 @@
use super::{DepositData, Hash256};
use crate::test_utils::TestRandom;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
/// A deposit to potentially become a beacon chain validator.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
/// Spec v0.5.0
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
pub struct Deposit {
pub branch: Vec<Hash256>,
pub proof: Vec<Hash256>,
pub index: u64,
pub deposit_data: DepositData,
}
@@ -18,29 +18,6 @@ pub struct Deposit {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[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);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Deposit::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(Deposit);
}
+4 -27
View File
@@ -1,14 +1,14 @@
use super::DepositInput;
use crate::test_utils::TestRandom;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
/// Data generated by the deposit contract.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
/// Spec v0.5.0
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
pub struct DepositData {
pub amount: u64,
pub timestamp: u64,
@@ -18,29 +18,6 @@ pub struct DepositData {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[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);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = DepositData::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(DepositData);
}
+65 -24
View File
@@ -1,47 +1,88 @@
use super::Hash256;
use crate::test_utils::TestRandom;
use crate::*;
use bls::{PublicKey, Signature};
use rand::RngCore;
use serde_derive::Serialize;
use ssz_derive::{Decode, Encode, TreeHash};
use serde_derive::{Deserialize, Serialize};
use ssz::{SignedRoot, TreeHash};
use ssz_derive::{Decode, Encode, SignedRoot, TreeHash};
use test_random_derive::TestRandom;
/// The data supplied by the user to the deposit contract.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
/// Spec v0.5.0
#[derive(
Debug,
PartialEq,
Clone,
Serialize,
Deserialize,
Encode,
Decode,
SignedRoot,
TreeHash,
TestRandom,
)]
pub struct DepositInput {
pub pubkey: PublicKey,
pub withdrawal_credentials: Hash256,
pub proof_of_possession: Signature,
}
impl DepositInput {
/// Generate the 'proof_of_posession' signature for a given DepositInput details.
///
/// Spec v0.5.0
pub fn create_proof_of_possession(
&self,
secret_key: &SecretKey,
epoch: Epoch,
fork: &Fork,
spec: &ChainSpec,
) -> Signature {
let msg = self.signed_root();
let domain = spec.get_domain(epoch, Domain::Deposit, fork);
Signature::new(msg.as_slice(), domain, secret_key)
}
/// Verify that proof-of-possession is valid.
///
/// Spec v0.5.0
pub fn validate_proof_of_possession(
&self,
epoch: Epoch,
fork: &Fork,
spec: &ChainSpec,
) -> bool {
let msg = self.signed_root();
let domain = spec.get_domain(epoch, Domain::Deposit, fork);
self.proof_of_possession.verify(&msg, domain, &self.pubkey)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
ssz_tests!(DepositInput);
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = DepositInput::random_for_test(&mut rng);
fn can_create_and_validate() {
let spec = ChainSpec::foundation();
let fork = Fork::genesis(&spec);
let keypair = Keypair::random();
let epoch = Epoch::new(0);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
let mut deposit_input = DepositInput {
pubkey: keypair.pk.clone(),
withdrawal_credentials: Hash256::zero(),
proof_of_possession: Signature::empty_signature(),
};
assert_eq!(original, decoded);
}
deposit_input.proof_of_possession =
deposit_input.create_proof_of_possession(&keypair.sk, epoch, &fork, &spec);
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = DepositInput::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
assert!(deposit_input.validate_proof_of_possession(epoch, &fork, &spec));
}
}
View File
+6 -27
View File
@@ -1,14 +1,16 @@
use super::Hash256;
use crate::test_utils::TestRandom;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
/// Contains data obtained from the Eth1 chain.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Default, Serialize, Encode, Decode, TreeHash, TestRandom)]
/// Spec v0.5.0
#[derive(
Debug, PartialEq, Clone, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom,
)]
pub struct Eth1Data {
pub deposit_root: Hash256,
pub block_hash: Hash256,
@@ -17,29 +19,6 @@ pub struct Eth1Data {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Eth1Data::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Eth1Data::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(Eth1Data);
}
+6 -27
View File
@@ -1,14 +1,16 @@
use super::Eth1Data;
use crate::test_utils::TestRandom;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
/// A summation of votes for some `Eth1Data`.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Default, Serialize, Encode, Decode, TreeHash, TestRandom)]
/// Spec v0.5.0
#[derive(
Debug, PartialEq, Clone, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom,
)]
pub struct Eth1DataVote {
pub eth1_data: Eth1Data,
pub vote_count: u64,
@@ -17,29 +19,6 @@ pub struct Eth1DataVote {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Eth1DataVote::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Eth1DataVote::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(Eth1DataVote);
}
+63 -24
View File
@@ -1,24 +1,41 @@
use crate::{test_utils::TestRandom, Epoch};
use crate::{test_utils::TestRandom, ChainSpec, Epoch};
use int_to_bytes::int_to_bytes4;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
/// Specifies a fork of the `BeaconChain`, to prevent replay attacks.
///
/// Spec v0.4.0
#[derive(Debug, Clone, PartialEq, Default, Serialize, Encode, Decode, TreeHash, TestRandom)]
/// Spec v0.5.0
#[derive(
Debug, Clone, PartialEq, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom,
)]
pub struct Fork {
pub previous_version: u64,
pub current_version: u64,
pub previous_version: [u8; 4],
pub current_version: [u8; 4],
pub epoch: Epoch,
}
impl Fork {
/// Initialize the `Fork` from the genesis parameters in the `spec`.
///
/// Spec v0.5.0
pub fn genesis(spec: &ChainSpec) -> Self {
let mut current_version: [u8; 4] = [0; 4];
current_version.copy_from_slice(&int_to_bytes4(spec.genesis_fork_version));
Self {
previous_version: current_version,
current_version,
epoch: spec.genesis_epoch,
}
}
/// Return the fork version of the given ``epoch``.
///
/// Spec v0.4.0
pub fn get_fork_version(&self, epoch: Epoch) -> u64 {
/// Spec v0.5.0
pub fn get_fork_version(&self, epoch: Epoch) -> [u8; 4] {
if epoch < self.epoch {
return self.previous_version;
}
@@ -29,29 +46,51 @@ impl Fork {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Fork::random_for_test(&mut rng);
ssz_tests!(Fork);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
fn test_genesis(version: u32, epoch: Epoch) {
let mut spec = ChainSpec::foundation();
assert_eq!(original, decoded);
spec.genesis_fork_version = version;
spec.genesis_epoch = epoch;
let fork = Fork::genesis(&spec);
assert_eq!(fork.epoch, spec.genesis_epoch, "epoch incorrect");
assert_eq!(
fork.previous_version, fork.current_version,
"previous and current are not identical"
);
assert_eq!(
fork.current_version,
version.to_le_bytes(),
"current version incorrect"
);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Fork::random_for_test(&mut rng);
fn genesis() {
test_genesis(0, Epoch::new(0));
test_genesis(9, Epoch::new(11));
test_genesis(2_u32.pow(31), Epoch::new(2_u64.pow(63)));
test_genesis(u32::max_value(), Epoch::max_value());
}
let result = original.hash_tree_root_internal();
#[test]
fn get_fork_version() {
let previous_version = [1; 4];
let current_version = [2; 4];
let epoch = Epoch::new(10);
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
let fork = Fork {
previous_version,
current_version,
epoch,
};
assert_eq!(fork.get_fork_version(epoch - 1), previous_version);
assert_eq!(fork.get_fork_version(epoch), current_version);
assert_eq!(fork.get_fork_version(epoch + 1), current_version);
}
}
+22
View File
@@ -0,0 +1,22 @@
use crate::test_utils::TestRandom;
use crate::Hash256;
use rand::RngCore;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
/// Historical block and state roots.
///
/// Spec v0.5.0
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
pub struct HistoricalBatch {
pub block_roots: Vec<Hash256>,
pub state_roots: Vec<Hash256>,
}
#[cfg(test)]
mod tests {
use super::*;
ssz_tests!(HistoricalBatch);
}
+19 -6
View File
@@ -1,14 +1,20 @@
//! Ethereum 2.0 types
#[macro_use]
pub mod test_utils;
pub mod attestation;
pub mod attestation_data;
pub mod attestation_data_and_custody_bit;
pub mod attestation_duty;
pub mod attester_slashing;
pub mod beacon_block;
pub mod beacon_block_body;
pub mod beacon_block_header;
pub mod beacon_state;
pub mod chain_spec;
pub mod crosslink;
pub mod crosslink_committee;
pub mod deposit;
pub mod deposit_data;
pub mod deposit_input;
@@ -16,16 +22,15 @@ pub mod eth1_data;
pub mod eth1_data_vote;
pub mod fork;
pub mod free_attestation;
pub mod historical_batch;
pub mod pending_attestation;
pub mod proposal;
pub mod proposer_slashing;
pub mod readers;
pub mod shard_reassignment_record;
pub mod slashable_attestation;
pub mod transfer;
pub mod voluntary_exit;
#[macro_use]
pub mod slot_epoch_macros;
pub mod relative_epoch;
pub mod slot_epoch;
pub mod slot_height;
pub mod validator;
@@ -37,12 +42,15 @@ use std::collections::HashMap;
pub use crate::attestation::Attestation;
pub use crate::attestation_data::AttestationData;
pub use crate::attestation_data_and_custody_bit::AttestationDataAndCustodyBit;
pub use crate::attestation_duty::AttestationDuty;
pub use crate::attester_slashing::AttesterSlashing;
pub use crate::beacon_block::BeaconBlock;
pub use crate::beacon_block_body::BeaconBlockBody;
pub use crate::beacon_state::{BeaconState, Error as BeaconStateError, RelativeEpoch};
pub use crate::beacon_block_header::BeaconBlockHeader;
pub use crate::beacon_state::{BeaconState, Error as BeaconStateError};
pub use crate::chain_spec::{ChainSpec, Domain};
pub use crate::crosslink::Crosslink;
pub use crate::crosslink_committee::CrosslinkCommittee;
pub use crate::deposit::Deposit;
pub use crate::deposit_data::DepositData;
pub use crate::deposit_input::DepositInput;
@@ -50,9 +58,10 @@ pub use crate::eth1_data::Eth1Data;
pub use crate::eth1_data_vote::Eth1DataVote;
pub use crate::fork::Fork;
pub use crate::free_attestation::FreeAttestation;
pub use crate::historical_batch::HistoricalBatch;
pub use crate::pending_attestation::PendingAttestation;
pub use crate::proposal::Proposal;
pub use crate::proposer_slashing::ProposerSlashing;
pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch};
pub use crate::slashable_attestation::SlashableAttestation;
pub use crate::slot_epoch::{Epoch, Slot};
pub use crate::slot_height::SlotHeight;
@@ -60,6 +69,10 @@ pub use crate::transfer::Transfer;
pub use crate::validator::Validator;
pub use crate::voluntary_exit::VoluntaryExit;
pub type Shard = u64;
pub type Committee = Vec<usize>;
pub type CrosslinkCommittees = Vec<(Committee, u64)>;
pub type Hash256 = H256;
pub type Address = H160;
pub type EthBalance = U256;
@@ -72,7 +85,7 @@ 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, Keypair, PublicKey, Signature};
pub use bls::{AggregatePublicKey, AggregateSignature, Keypair, PublicKey, SecretKey, Signature};
pub use libp2p::floodsub::{Topic, TopicBuilder};
pub use libp2p::multiaddr;
pub use libp2p::Multiaddr;
+4 -27
View File
@@ -1,14 +1,14 @@
use crate::test_utils::TestRandom;
use crate::{AttestationData, Bitfield, Slot};
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
/// An attestation that has been included in the state but not yet fully processed.
///
/// Spec v0.4.0
#[derive(Debug, Clone, PartialEq, Serialize, Encode, Decode, TreeHash, TestRandom)]
/// Spec v0.5.0
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
pub struct PendingAttestation {
pub aggregation_bitfield: Bitfield,
pub data: AttestationData,
@@ -19,29 +19,6 @@ pub struct PendingAttestation {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = PendingAttestation::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = PendingAttestation::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(PendingAttestation);
}
-78
View File
@@ -1,78 +0,0 @@
use crate::test_utils::TestRandom;
use crate::{Hash256, Slot};
use bls::Signature;
use rand::RngCore;
use serde_derive::Serialize;
use ssz::TreeHash;
use ssz_derive::{Decode, Encode, SignedRoot, TreeHash};
use test_random_derive::TestRandom;
/// A proposal for some shard or beacon block.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)]
pub struct Proposal {
pub slot: Slot,
/// Shard number (spec.beacon_chain_shard_number for beacon chain)
pub shard: u64,
pub block_root: Hash256,
pub signature: Signature,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, SignedRoot, TreeHash};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Proposal::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Proposal::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
#[derive(TreeHash)]
struct SignedProposal {
pub slot: Slot,
pub shard: u64,
pub block_root: Hash256,
}
impl Into<SignedProposal> for Proposal {
fn into(self) -> SignedProposal {
SignedProposal {
slot: self.slot,
shard: self.shard,
block_root: self.block_root,
}
}
}
#[test]
pub fn test_signed_root() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Proposal::random_for_test(&mut rng);
let other: SignedProposal = original.clone().into();
assert_eq!(original.signed_root(), other.hash_tree_root());
}
}
-20
View File
@@ -1,20 +0,0 @@
use crate::test_utils::TestRandom;
use crate::{Hash256, Slot};
use rand::RngCore;
use serde_derive::Serialize;
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
#[derive(Debug, PartialEq, Clone, Default, Serialize, Encode, Decode, TreeHash, TestRandom)]
pub struct ProposalSignedData {
pub slot: Slot,
pub shard: u64,
pub block_root: Hash256,
}
#[cfg(test)]
mod tests {
use super::*;
ssz_tests!(ProposalSignedData);
}
+7 -34
View File
@@ -1,50 +1,23 @@
use super::Proposal;
use super::BeaconBlockHeader;
use crate::test_utils::TestRandom;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
mod builder;
pub use builder::ProposerSlashingBuilder;
/// Two conflicting proposals from the same proposer (validator).
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
/// Spec v0.5.0
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
pub struct ProposerSlashing {
pub proposer_index: u64,
pub proposal_1: Proposal,
pub proposal_2: Proposal,
pub header_1: BeaconBlockHeader,
pub header_2: BeaconBlockHeader,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[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);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = ProposerSlashing::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(ProposerSlashing);
}
-35
View File
@@ -1,35 +0,0 @@
use crate::{BeaconBlock, Hash256, Slot};
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) -> Slot;
fn parent_root(&self) -> Hash256;
fn state_root(&self) -> Hash256;
fn into_beacon_block(self) -> Option<BeaconBlock>;
}
impl BeaconBlockReader for BeaconBlock {
fn slot(&self) -> Slot {
self.slot
}
fn parent_root(&self) -> Hash256 {
self.parent_root
}
fn state_root(&self) -> Hash256 {
self.state_root
}
fn into_beacon_block(self) -> Option<BeaconBlock> {
Some(self)
}
}
-5
View File
@@ -1,5 +0,0 @@
mod block_reader;
mod state_reader;
pub use self::block_reader::BeaconBlockReader;
pub use self::state_reader::BeaconStateReader;
-25
View File
@@ -1,25 +0,0 @@
use crate::{BeaconState, Slot};
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) -> Slot;
fn into_beacon_state(self) -> Option<BeaconState>;
}
impl BeaconStateReader for BeaconState {
fn slot(&self) -> Slot {
self.slot
}
fn into_beacon_state(self) -> Option<BeaconState> {
Some(self)
}
}
+134
View File
@@ -0,0 +1,134 @@
use crate::*;
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Error {
EpochTooLow { base: Epoch, other: Epoch },
EpochTooHigh { base: Epoch, other: Epoch },
AmbiguiousNextEpoch,
}
/// Defines the epochs relative to some epoch. Most useful when referring to the committees prior
/// to and following some epoch.
///
/// Spec v0.5.0
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum RelativeEpoch {
/// The prior epoch.
Previous,
/// The current epoch.
Current,
/// The next epoch if there _is_ a validator registry update.
///
/// If the validator registry is updated during an epoch transition, a new shuffling seed is
/// generated, this changes the attestation and proposal roles.
NextWithRegistryChange,
/// The next epoch if there _is not_ a validator registry update.
///
/// If the validator registry _is not_ updated during an epoch transition, the shuffling stays
/// the same.
NextWithoutRegistryChange,
}
impl RelativeEpoch {
/// Returns the `epoch` that `self` refers to, with respect to the `base` epoch.
///
/// Spec v0.5.0
pub fn into_epoch(&self, base: Epoch) -> Epoch {
match self {
RelativeEpoch::Previous => base - 1,
RelativeEpoch::Current => base,
RelativeEpoch::NextWithoutRegistryChange => base + 1,
RelativeEpoch::NextWithRegistryChange => base + 1,
}
}
/// Converts the `other` epoch into a `RelativeEpoch`, with respect to `base`
///
/// ## Errors
/// Returns an error when:
/// - `EpochTooLow` when `other` is more than 1 prior to `base`.
/// - `EpochTooHigh` when `other` is more than 1 after `base`.
/// - `AmbiguiousNextEpoch` whenever `other` is one after `base`, because it's unknowable if
/// there will be a registry change.
///
/// Spec v0.5.0
pub fn from_epoch(base: Epoch, other: Epoch) -> Result<Self, Error> {
if other == base - 1 {
Ok(RelativeEpoch::Previous)
} else if other == base {
Ok(RelativeEpoch::Current)
} else if other == base + 1 {
Err(Error::AmbiguiousNextEpoch)
} else if other < base {
Err(Error::EpochTooLow { base, other })
} else {
Err(Error::EpochTooHigh { base, other })
}
}
/// Convenience function for `Self::from_epoch` where both slots are converted into epochs.
pub fn from_slot(base: Slot, other: Slot, spec: &ChainSpec) -> Result<Self, Error> {
Self::from_epoch(
base.epoch(spec.slots_per_epoch),
other.epoch(spec.slots_per_epoch),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_into_epoch() {
let base = Epoch::new(10);
assert_eq!(RelativeEpoch::Current.into_epoch(base), base);
assert_eq!(RelativeEpoch::Previous.into_epoch(base), base - 1);
assert_eq!(
RelativeEpoch::NextWithRegistryChange.into_epoch(base),
base + 1
);
assert_eq!(
RelativeEpoch::NextWithoutRegistryChange.into_epoch(base),
base + 1
);
}
#[test]
fn from_epoch() {
let base = Epoch::new(10);
assert_eq!(
RelativeEpoch::from_epoch(base, base - 1),
Ok(RelativeEpoch::Previous)
);
assert_eq!(
RelativeEpoch::from_epoch(base, base),
Ok(RelativeEpoch::Current)
);
assert_eq!(
RelativeEpoch::from_epoch(base, base + 1),
Err(RelativeEpochError::AmbiguiousNextEpoch)
);
}
#[test]
fn from_slot() {
let spec = ChainSpec::foundation();
let base = Epoch::new(10).start_slot(spec.slots_per_epoch);
assert_eq!(
RelativeEpoch::from_slot(base, base - 1, &spec),
Ok(RelativeEpoch::Previous)
);
assert_eq!(
RelativeEpoch::from_slot(base, base, &spec),
Ok(RelativeEpoch::Current)
);
assert_eq!(
RelativeEpoch::from_slot(base, base + spec.slots_per_epoch, &spec),
Err(RelativeEpochError::AmbiguiousNextEpoch)
);
}
}
@@ -1,42 +0,0 @@
use crate::{test_utils::TestRandom, Slot};
use rand::RngCore;
use serde_derive::Serialize;
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
pub struct ShardReassignmentRecord {
pub validator_index: u64,
pub shard: u64,
pub slot: Slot,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[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);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = ShardReassignmentRecord::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
}
+21 -32
View File
@@ -1,6 +1,6 @@
use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield, ChainSpec};
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz::TreeHash;
use ssz_derive::{Decode, Encode, SignedRoot, TreeHash};
use test_random_derive::TestRandom;
@@ -9,8 +9,19 @@ use test_random_derive::TestRandom;
///
/// To be included in an `AttesterSlashing`.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)]
/// Spec v0.5.0
#[derive(
Debug,
PartialEq,
Clone,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
TestRandom,
SignedRoot,
)]
pub struct SlashableAttestation {
/// Lists validator registry indices, not committee indices.
pub validator_indices: Vec<u64>,
@@ -22,17 +33,17 @@ pub struct SlashableAttestation {
impl SlashableAttestation {
/// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn is_double_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool {
self.data.slot.epoch(spec.slots_per_epoch) == other.data.slot.epoch(spec.slots_per_epoch)
}
/// Check if ``attestation_data_1`` surrounds ``attestation_data_2``.
///
/// Spec v0.4.0
/// Spec v0.5.0
pub fn is_surround_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool {
let source_epoch_1 = self.data.justified_epoch;
let source_epoch_2 = other.data.justified_epoch;
let source_epoch_1 = self.data.source_epoch;
let source_epoch_2 = other.data.source_epoch;
let target_epoch_1 = self.data.slot.epoch(spec.slots_per_epoch);
let target_epoch_2 = other.data.slot.epoch(spec.slots_per_epoch);
@@ -46,7 +57,6 @@ mod tests {
use crate::chain_spec::ChainSpec;
use crate::slot_epoch::{Epoch, Slot};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[test]
pub fn test_is_double_vote_true() {
@@ -120,39 +130,18 @@ mod tests {
);
}
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = SlashableAttestation::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = SlashableAttestation::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(SlashableAttestation);
fn create_slashable_attestation(
slot_factor: u64,
justified_epoch: u64,
source_epoch: u64,
spec: &ChainSpec,
) -> SlashableAttestation {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut slashable_vote = SlashableAttestation::random_for_test(&mut rng);
slashable_vote.data.slot = Slot::new(slot_factor * spec.slots_per_epoch);
slashable_vote.data.justified_epoch = Epoch::new(justified_epoch);
slashable_vote.data.source_epoch = Epoch::new(source_epoch);
slashable_vote
}
}
-132
View File
@@ -1,132 +0,0 @@
use super::AttestationData;
use crate::chain_spec::ChainSpec;
use crate::test_utils::TestRandom;
use bls::AggregateSignature;
use rand::RngCore;
use serde_derive::Serialize;
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)]
pub struct SlashableVoteData {
pub custody_bit_0_indices: Vec<u32>,
pub custody_bit_1_indices: Vec<u32>,
pub data: AttestationData,
pub aggregate_signature: AggregateSignature,
}
impl SlashableVoteData {
/// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target.
///
/// Spec v0.3.0
pub fn is_double_vote(&self, other: &SlashableVoteData, spec: &ChainSpec) -> bool {
self.data.slot.epoch(spec.epoch_length) == other.data.slot.epoch(spec.epoch_length)
}
/// Check if ``attestation_data_1`` surrounds ``attestation_data_2``.
///
/// Spec v0.3.0
pub fn is_surround_vote(&self, other: &SlashableVoteData, spec: &ChainSpec) -> bool {
let source_epoch_1 = self.data.justified_epoch;
let source_epoch_2 = other.data.justified_epoch;
let target_epoch_1 = self.data.slot.epoch(spec.epoch_length);
let target_epoch_2 = other.data.slot.epoch(spec.epoch_length);
(source_epoch_1 < source_epoch_2) && (target_epoch_2 < target_epoch_1)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chain_spec::ChainSpec;
use crate::slot_epoch::{Epoch, Slot};
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
#[test]
pub fn test_is_double_vote_true() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_vote_data(1, 1, &spec);
let slashable_vote_second = create_slashable_vote_data(1, 1, &spec);
assert_eq!(
slashable_vote_first.is_double_vote(&slashable_vote_second, &spec),
true
)
}
#[test]
pub fn test_is_double_vote_false() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_vote_data(1, 1, &spec);
let slashable_vote_second = create_slashable_vote_data(2, 1, &spec);
assert_eq!(
slashable_vote_first.is_double_vote(&slashable_vote_second, &spec),
false
);
}
#[test]
pub fn test_is_surround_vote_true() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_vote_data(2, 1, &spec);
let slashable_vote_second = create_slashable_vote_data(1, 2, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
true
);
}
#[test]
pub fn test_is_surround_vote_true_realistic() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_vote_data(4, 1, &spec);
let slashable_vote_second = create_slashable_vote_data(3, 2, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
true
);
}
#[test]
pub fn test_is_surround_vote_false_source_epoch_fails() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_vote_data(2, 2, &spec);
let slashable_vote_second = create_slashable_vote_data(1, 1, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
false
);
}
#[test]
pub fn test_is_surround_vote_false_target_epoch_fails() {
let spec = ChainSpec::foundation();
let slashable_vote_first = create_slashable_vote_data(1, 1, &spec);
let slashable_vote_second = create_slashable_vote_data(2, 2, &spec);
assert_eq!(
slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec),
false
);
}
ssz_tests!(SlashableVoteData);
fn create_slashable_vote_data(
slot_factor: u64,
justified_epoch: u64,
spec: &ChainSpec,
) -> SlashableVoteData {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut slashable_vote = SlashableVoteData::random_for_test(&mut rng);
slashable_vote.data.slot = Slot::new(slot_factor * spec.epoch_length);
slashable_vote.data.justified_epoch = Epoch::new(justified_epoch);
slashable_vote
}
}
+3 -7
View File
@@ -12,7 +12,7 @@ use crate::slot_height::SlotHeight;
/// may lead to programming errors which are not detected by the compiler.
use crate::test_utils::TestRandom;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use slog;
use ssz::{hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash};
use std::cmp::{Ord, Ordering};
@@ -21,10 +21,10 @@ use std::hash::{Hash, Hasher};
use std::iter::Iterator;
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign};
#[derive(Eq, Debug, Clone, Copy, Default, Serialize)]
#[derive(Eq, Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct Slot(u64);
#[derive(Eq, Debug, Clone, Copy, Default, Serialize)]
#[derive(Eq, Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct Epoch(u64);
impl_common!(Slot);
@@ -103,8 +103,6 @@ impl<'a> Iterator for SlotIter<'a> {
#[cfg(test)]
mod slot_tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::ssz_encode;
all_tests!(Slot);
}
@@ -112,8 +110,6 @@ mod slot_tests {
#[cfg(test)]
mod epoch_tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::ssz_encode;
all_tests!(Epoch);
+7 -35
View File
@@ -207,9 +207,9 @@ macro_rules! impl_ssz {
}
impl TreeHash for $type {
fn hash_tree_root_internal(&self) -> Vec<u8> {
fn hash_tree_root(&self) -> Vec<u8> {
let mut result: Vec<u8> = vec![];
result.append(&mut self.0.hash_tree_root_internal());
result.append(&mut self.0.hash_tree_root());
hash(&result)
}
}
@@ -248,7 +248,7 @@ macro_rules! impl_common {
}
// test macros
#[allow(unused_macros)]
#[cfg(test)]
macro_rules! new_tests {
($type: ident) => {
#[test]
@@ -260,7 +260,7 @@ macro_rules! new_tests {
};
}
#[allow(unused_macros)]
#[cfg(test)]
macro_rules! from_into_tests {
($type: ident, $other: ident) => {
#[test]
@@ -286,7 +286,7 @@ macro_rules! from_into_tests {
};
}
#[allow(unused_macros)]
#[cfg(test)]
macro_rules! math_between_tests {
($type: ident, $other: ident) => {
#[test]
@@ -434,7 +434,7 @@ macro_rules! math_between_tests {
};
}
#[allow(unused_macros)]
#[cfg(test)]
macro_rules! math_tests {
($type: ident) => {
#[test]
@@ -528,35 +528,7 @@ macro_rules! math_tests {
};
}
#[allow(unused_macros)]
macro_rules! ssz_tests {
($type: ident) => {
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = $type::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = $type::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = $type::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
};
}
#[allow(unused_macros)]
#[cfg(test)]
macro_rules! all_tests {
($type: ident) => {
new_tests!($type);
-3
View File
@@ -33,11 +33,8 @@ impl SlotHeight {
}
#[cfg(test)]
mod slot_height_tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::ssz_encode;
all_tests!(SlotHeight);
}
@@ -0,0 +1,30 @@
use crate::*;
use int_to_bytes::int_to_bytes48;
use log::debug;
use rayon::prelude::*;
/// Generates `validator_count` keypairs where the secret key is the index of the
/// validator.
///
/// For example, the first validator has a secret key of `int_to_bytes48(1)`, the second has
/// `int_to_bytes48(2)` and so on. (We skip `0` as it generates a weird looking public key and is
/// probably invalid).
pub fn generate_deterministic_keypairs(validator_count: usize) -> Vec<Keypair> {
debug!(
"Generating {} deterministic validator keypairs...",
validator_count
);
let keypairs: Vec<Keypair> = (0..validator_count)
.collect::<Vec<usize>>()
.par_iter()
.map(|&i| {
let secret = int_to_bytes48(i as u64 + 1);
let sk = SecretKey::from_bytes(&secret).unwrap();
let pk = PublicKey::from_secret_key(&sk);
Keypair { sk, pk }
})
.collect();
keypairs
}
+128
View File
@@ -0,0 +1,128 @@
use crate::*;
use rayon::prelude::*;
use std::fs::File;
use std::io::{Error, ErrorKind, Read, Write};
use std::path::Path;
pub const PUBLIC_KEY_BYTES_LEN: usize = 96;
pub const SECRET_KEY_BYTES_LEN: usize = 48;
pub const BATCH_SIZE: usize = 1_000; // ~15MB
pub const KEYPAIR_BYTES_LEN: usize = PUBLIC_KEY_BYTES_LEN + SECRET_KEY_BYTES_LEN;
pub const BATCH_BYTE_LEN: usize = KEYPAIR_BYTES_LEN * BATCH_SIZE;
/// Defines a trait that allows reading/writing a vec of `Keypair` from/to a file.
pub trait KeypairsFile {
/// Write to file, without guaranteeing interoperability with other clients.
fn to_raw_file(&self, path: &Path, keypairs: &[Keypair]) -> Result<(), Error>;
/// Read from file, without guaranteeing interoperability with other clients.
fn from_raw_file(path: &Path, count: usize) -> Result<Vec<Keypair>, Error>;
}
impl KeypairsFile for Vec<Keypair> {
/// Write the keypairs to file, using the fastest possible method without guaranteeing
/// interoperability with other clients.
fn to_raw_file(&self, path: &Path, keypairs: &[Keypair]) -> Result<(), Error> {
let mut keypairs_file = File::create(path)?;
for keypair_batch in keypairs.chunks(BATCH_SIZE) {
let mut buf = Vec::with_capacity(BATCH_BYTE_LEN);
for keypair in keypair_batch {
buf.append(&mut keypair.sk.as_raw().as_bytes());
buf.append(&mut keypair.pk.clone().as_uncompressed_bytes());
}
keypairs_file.write_all(&buf)?;
}
Ok(())
}
/// Read the keypairs from file, using the fastest possible method without guaranteeing
/// interoperability with other clients.
fn from_raw_file(path: &Path, count: usize) -> Result<Vec<Keypair>, Error> {
let mut keypairs_file = File::open(path)?;
let mut keypairs = Vec::with_capacity(count);
let indices: Vec<usize> = (0..count).collect();
for batch in indices.chunks(BATCH_SIZE) {
let mut buf = vec![0; batch.len() * KEYPAIR_BYTES_LEN];
keypairs_file.read_exact(&mut buf)?;
let mut keypair_batch = batch
.par_iter()
.enumerate()
.map(|(i, _)| {
let sk_start = i * KEYPAIR_BYTES_LEN;
let sk_end = sk_start + SECRET_KEY_BYTES_LEN;
let sk = SecretKey::from_bytes(&buf[sk_start..sk_end])
.map_err(|_| Error::new(ErrorKind::Other, "Invalid SecretKey bytes"))
.unwrap();
let pk_start = sk_end;
let pk_end = pk_start + PUBLIC_KEY_BYTES_LEN;
let pk = PublicKey::from_uncompressed_bytes(&buf[pk_start..pk_end])
.map_err(|_| Error::new(ErrorKind::Other, "Invalid PublicKey bytes"))
.unwrap();
Keypair { sk, pk }
})
.collect();
keypairs.append(&mut keypair_batch);
}
Ok(keypairs)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use std::fs::remove_file;
fn random_keypairs(n: usize) -> Vec<Keypair> {
(0..n).into_par_iter().map(|_| Keypair::random()).collect()
}
fn random_tmp_file() -> String {
let mut rng = thread_rng();
rng.sample_iter(&Alphanumeric).take(7).collect()
}
#[test]
#[ignore]
fn read_write_consistency_small_batch() {
let num_keypairs = 10;
let keypairs = random_keypairs(num_keypairs);
let keypairs_path = Path::new("/tmp").join(random_tmp_file());
keypairs.to_raw_file(&keypairs_path, &keypairs).unwrap();
let decoded = Vec::from_raw_file(&keypairs_path, num_keypairs).unwrap();
remove_file(keypairs_path).unwrap();
assert_eq!(keypairs, decoded);
}
#[test]
#[ignore]
fn read_write_consistency_big_batch() {
let num_keypairs = BATCH_SIZE + 1;
let keypairs = random_keypairs(num_keypairs);
let keypairs_path = Path::new("/tmp").join(random_tmp_file());
keypairs.to_raw_file(&keypairs_path, &keypairs).unwrap();
let decoded = Vec::from_raw_file(&keypairs_path, num_keypairs).unwrap();
remove_file(keypairs_path).unwrap();
assert_eq!(keypairs, decoded);
}
}
+2 -2
View File
@@ -17,14 +17,14 @@ macro_rules! ssz_tests {
}
#[test]
pub fn test_hash_tree_root_internal() {
pub fn test_hash_tree_root() {
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::TreeHash;
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = $type::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
let result = original.hash_tree_root();
assert_eq!(result.len(), 32);
// TODO: Add further tests
+28 -53
View File
@@ -1,55 +1,30 @@
use rand::RngCore;
#[macro_use]
mod macros;
mod generate_deterministic_keypairs;
mod keypairs_file;
mod test_random;
mod testing_attestation_builder;
mod testing_attestation_data_builder;
mod testing_attester_slashing_builder;
mod testing_beacon_block_builder;
mod testing_beacon_state_builder;
mod testing_deposit_builder;
mod testing_pending_attestation_builder;
mod testing_proposer_slashing_builder;
mod testing_transfer_builder;
mod testing_voluntary_exit_builder;
pub use generate_deterministic_keypairs::generate_deterministic_keypairs;
pub use keypairs_file::KeypairsFile;
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 bool {
fn random_for_test(rng: &mut T) -> Self {
(rng.next_u32() % 2) == 1
}
}
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),
]
}
}
pub use test_random::TestRandom;
pub use testing_attestation_builder::TestingAttestationBuilder;
pub use testing_attestation_data_builder::TestingAttestationDataBuilder;
pub use testing_attester_slashing_builder::TestingAttesterSlashingBuilder;
pub use testing_beacon_block_builder::TestingBeaconBlockBuilder;
pub use testing_beacon_state_builder::{keypairs_path, TestingBeaconStateBuilder};
pub use testing_deposit_builder::TestingDepositBuilder;
pub use testing_pending_attestation_builder::TestingPendingAttestationBuilder;
pub use testing_proposer_slashing_builder::TestingProposerSlashingBuilder;
pub use testing_transfer_builder::TestingTransferBuilder;
pub use testing_voluntary_exit_builder::TestingVoluntaryExitBuilder;
+67
View File
@@ -0,0 +1,67 @@
use rand::RngCore;
mod address;
mod aggregate_signature;
mod bitfield;
mod hash256;
mod public_key;
mod secret_key;
mod signature;
pub trait TestRandom<T>
where
T: RngCore,
{
fn random_for_test(rng: &mut T) -> Self;
}
impl<T: RngCore> TestRandom<T> for bool {
fn random_for_test(rng: &mut T) -> Self {
(rng.next_u32() % 2) == 1
}
}
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),
]
}
}
macro_rules! impl_test_random_for_u8_array {
($len: expr) => {
impl<T: RngCore> TestRandom<T> for [u8; $len] {
fn random_for_test(rng: &mut T) -> Self {
let mut bytes = [0; $len];
rng.fill_bytes(&mut bytes);
bytes
}
}
};
}
impl_test_random_for_u8_array!(4);
@@ -1,5 +1,5 @@
use super::super::Bitfield;
use super::TestRandom;
use crate::Bitfield;
use rand::RngCore;
impl<T: RngCore> TestRandom<T> for Bitfield {
@@ -0,0 +1,94 @@
use crate::test_utils::TestingAttestationDataBuilder;
use crate::*;
use ssz::TreeHash;
/// Builds an attestation to be used for testing purposes.
///
/// This struct should **never be used for production purposes.**
pub struct TestingAttestationBuilder {
committee: Vec<usize>,
attestation: Attestation,
}
impl TestingAttestationBuilder {
/// Create a new attestation builder.
pub fn new(
state: &BeaconState,
committee: &[usize],
slot: Slot,
shard: u64,
spec: &ChainSpec,
) -> Self {
let data_builder = TestingAttestationDataBuilder::new(state, shard, slot, spec);
let mut aggregation_bitfield = Bitfield::new();
let mut custody_bitfield = Bitfield::new();
for (i, _) in committee.iter().enumerate() {
custody_bitfield.set(i, false);
aggregation_bitfield.set(i, false);
}
let attestation = Attestation {
aggregation_bitfield,
data: data_builder.build(),
custody_bitfield,
aggregate_signature: AggregateSignature::new(),
};
Self {
attestation,
committee: committee.to_vec(),
}
}
/// Signs the attestation with a subset (or all) committee members.
///
/// `secret_keys` must be supplied in the same order as `signing_validators`. I.e., the first
/// keypair must be that of the first signing validator.
pub fn sign(
&mut self,
signing_validators: &[usize],
secret_keys: &[&SecretKey],
fork: &Fork,
spec: &ChainSpec,
) {
assert_eq!(
signing_validators.len(),
secret_keys.len(),
"Must be a key for each validator"
);
for (key_index, validator_index) in signing_validators.iter().enumerate() {
let committee_index = self
.committee
.iter()
.position(|v| *v == *validator_index)
.expect("Signing validator not in attestation committee");
self.attestation
.aggregation_bitfield
.set(committee_index, true);
let message = AttestationDataAndCustodyBit {
data: self.attestation.data.clone(),
custody_bit: false,
}
.hash_tree_root();
let domain = spec.get_domain(
self.attestation.data.slot.epoch(spec.slots_per_epoch),
Domain::Attestation,
fork,
);
let signature = Signature::new(&message, domain, secret_keys[key_index]);
self.attestation.aggregate_signature.add(&signature)
}
}
/// Consume the builder and return the attestation.
pub fn build(self) -> Attestation {
self.attestation
}
}
@@ -0,0 +1,66 @@
use crate::*;
/// Builds an `AttestationData` to be used for testing purposes.
///
/// This struct should **never be used for production purposes.**
pub struct TestingAttestationDataBuilder {
data: AttestationData,
}
impl TestingAttestationDataBuilder {
/// Configures a new `AttestationData` which attests to all of the same parameters as the
/// state.
pub fn new(state: &BeaconState, shard: u64, slot: Slot, spec: &ChainSpec) -> Self {
let current_epoch = state.current_epoch(spec);
let previous_epoch = state.previous_epoch(spec);
let is_previous_epoch =
state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch);
let source_epoch = if is_previous_epoch {
state.previous_justified_epoch
} else {
state.current_justified_epoch
};
let target_root = if is_previous_epoch {
*state
.get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec)
.unwrap()
} else {
*state
.get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec)
.unwrap()
};
let source_root = *state
.get_block_root(source_epoch.start_slot(spec.slots_per_epoch), spec)
.unwrap();
let data = AttestationData {
// LMD GHOST vote
slot,
beacon_block_root: *state.get_block_root(slot, spec).unwrap(),
// FFG Vote
source_epoch,
source_root,
target_root,
// Crosslink vote
shard,
previous_crosslink: Crosslink {
epoch: slot.epoch(spec.slots_per_epoch),
crosslink_data_root: spec.zero_hash,
},
crosslink_data_root: spec.zero_hash,
};
Self { data }
}
/// Returns the `AttestationData`, consuming the builder.
pub fn build(self) -> AttestationData {
self.data
}
}
@@ -2,9 +2,11 @@ use crate::*;
use ssz::TreeHash;
/// Builds an `AttesterSlashing`.
pub struct AttesterSlashingBuilder();
///
/// This struct should **never be used for production purposes.**
pub struct TestingAttesterSlashingBuilder();
impl AttesterSlashingBuilder {
impl TestingAttesterSlashingBuilder {
/// Builds an `AttesterSlashing` that is a double vote.
///
/// The `signer` function is used to sign the double-vote and accepts:
@@ -21,56 +23,53 @@ impl AttesterSlashingBuilder {
{
let double_voted_slot = Slot::new(0);
let shard = 0;
let justified_epoch = Epoch::new(0);
let epoch = Epoch::new(0);
let hash_1 = Hash256::from_low_u64_le(1);
let hash_2 = Hash256::from_low_u64_le(2);
let data_1 = AttestationData {
slot: double_voted_slot,
beacon_block_root: hash_1,
source_epoch: epoch,
source_root: hash_1,
target_root: hash_1,
shard,
previous_crosslink: Crosslink {
epoch,
crosslink_data_root: hash_1,
},
crosslink_data_root: hash_1,
};
let data_2 = AttestationData {
beacon_block_root: hash_2,
..data_1.clone()
};
let mut slashable_attestation_1 = SlashableAttestation {
validator_indices: validator_indices.to_vec(),
data: AttestationData {
slot: double_voted_slot,
shard,
beacon_block_root: hash_1,
epoch_boundary_root: hash_1,
crosslink_data_root: hash_1,
latest_crosslink: Crosslink {
epoch,
crosslink_data_root: hash_1,
},
justified_epoch,
justified_block_root: hash_1,
},
data: data_1,
custody_bitfield: Bitfield::new(),
aggregate_signature: AggregateSignature::new(),
};
let mut slashable_attestation_2 = SlashableAttestation {
validator_indices: validator_indices.to_vec(),
data: AttestationData {
slot: double_voted_slot,
shard,
beacon_block_root: hash_2,
epoch_boundary_root: hash_2,
crosslink_data_root: hash_2,
latest_crosslink: Crosslink {
epoch,
crosslink_data_root: hash_2,
},
justified_epoch,
justified_block_root: hash_2,
},
data: data_2,
custody_bitfield: Bitfield::new(),
aggregate_signature: AggregateSignature::new(),
};
let add_signatures = |attestation: &mut SlashableAttestation| {
// All validators sign with a `false` custody bit.
let attestation_data_and_custody_bit = AttestationDataAndCustodyBit {
data: attestation.data.clone(),
custody_bit: false,
};
let message = attestation_data_and_custody_bit.hash_tree_root();
for (i, validator_index) in validator_indices.iter().enumerate() {
let attestation_data_and_custody_bit = AttestationDataAndCustodyBit {
data: attestation.data.clone(),
custody_bit: attestation.custody_bitfield.get(i).unwrap(),
};
let message = attestation_data_and_custody_bit.hash_tree_root();
attestation.custody_bitfield.set(i, false);
let signature = signer(*validator_index, &message[..], epoch, Domain::Attestation);
attestation.aggregate_signature.add(&signature);
}
@@ -0,0 +1,280 @@
use crate::{
test_utils::{
TestingAttestationBuilder, TestingAttesterSlashingBuilder, TestingDepositBuilder,
TestingProposerSlashingBuilder, TestingTransferBuilder, TestingVoluntaryExitBuilder,
},
*,
};
use rayon::prelude::*;
use ssz::{SignedRoot, TreeHash};
/// Builds a beacon block to be used for testing purposes.
///
/// This struct should **never be used for production purposes.**
pub struct TestingBeaconBlockBuilder {
pub block: BeaconBlock,
}
impl TestingBeaconBlockBuilder {
/// Create a new builder from genesis.
pub fn new(spec: &ChainSpec) -> Self {
Self {
block: BeaconBlock::empty(spec),
}
}
/// Set the slot of the block.
pub fn set_slot(&mut self, slot: Slot) {
self.block.slot = slot;
}
/// Signs the block.
///
/// Modifying the block after signing may invalidate the signature.
pub fn sign(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) {
let message = self.block.signed_root();
let epoch = self.block.slot.epoch(spec.slots_per_epoch);
let domain = spec.get_domain(epoch, Domain::BeaconBlock, fork);
self.block.signature = Signature::new(&message, domain, sk);
}
/// Sets the randao to be a signature across the blocks epoch.
///
/// Modifying the block's slot after signing may invalidate the signature.
pub fn set_randao_reveal(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) {
let epoch = self.block.slot.epoch(spec.slots_per_epoch);
let message = epoch.hash_tree_root();
let domain = spec.get_domain(epoch, Domain::Randao, fork);
self.block.body.randao_reveal = Signature::new(&message, domain, sk);
}
/// Inserts a signed, valid `ProposerSlashing` for the validator.
pub fn insert_proposer_slashing(
&mut self,
validator_index: u64,
secret_key: &SecretKey,
fork: &Fork,
spec: &ChainSpec,
) {
let proposer_slashing = build_proposer_slashing(validator_index, secret_key, fork, spec);
self.block.body.proposer_slashings.push(proposer_slashing);
}
/// Inserts a signed, valid `AttesterSlashing` for each validator index in `validator_indices`.
pub fn insert_attester_slashing(
&mut self,
validator_indices: &[u64],
secret_keys: &[&SecretKey],
fork: &Fork,
spec: &ChainSpec,
) {
let attester_slashing =
build_double_vote_attester_slashing(validator_indices, secret_keys, fork, spec);
self.block.body.attester_slashings.push(attester_slashing);
}
/// Fills the block with `num_attestations` attestations.
///
/// It will first go and get each committee that is able to include an attestation in this
/// block. If there _are_ enough committees, it will produce an attestation for each. If there
/// _are not_ enough committees, it will start splitting the committees in half until it
/// achieves the target. It will then produce separate attestations for each split committee.
///
/// Note: the signed messages of the split committees will be identical -- it would be possible
/// to aggregate these split attestations.
pub fn insert_attestations(
&mut self,
state: &BeaconState,
secret_keys: &[&SecretKey],
num_attestations: usize,
spec: &ChainSpec,
) -> Result<(), BeaconStateError> {
let mut slot = self.block.slot - spec.min_attestation_inclusion_delay;
let mut attestations_added = 0;
// Stores the following (in order):
//
// - The slot of the committee.
// - A list of all validators in the committee.
// - A list of all validators in the committee that should sign the attestation.
// - The shard of the committee.
let mut committees: Vec<(Slot, Vec<usize>, Vec<usize>, u64)> = vec![];
// Loop backwards through slots gathering each committee, until:
//
// - The slot is too old to be included in a block at this slot.
// - The `MAX_ATTESTATIONS`.
loop {
if state.slot >= slot + spec.slots_per_epoch {
break;
}
for crosslink_committee in state.get_crosslink_committees_at_slot(slot, spec)? {
if attestations_added >= num_attestations {
break;
}
committees.push((
slot,
crosslink_committee.committee.clone(),
crosslink_committee.committee.clone(),
crosslink_committee.shard,
));
attestations_added += 1;
}
slot -= 1;
}
// Loop through all the committees, splitting each one in half until we have
// `MAX_ATTESTATIONS` committees.
loop {
if committees.len() >= num_attestations as usize {
break;
}
for index in 0..committees.len() {
if committees.len() >= num_attestations as usize {
break;
}
let (slot, committee, mut signing_validators, shard) = committees[index].clone();
let new_signing_validators =
signing_validators.split_off(signing_validators.len() / 2);
committees[index] = (slot, committee.clone(), signing_validators, shard);
committees.push((slot, committee, new_signing_validators, shard));
}
}
let mut attestations: Vec<Attestation> = committees
.par_iter()
.map(|(slot, committee, signing_validators, shard)| {
let mut builder =
TestingAttestationBuilder::new(state, committee, *slot, *shard, spec);
let signing_secret_keys: Vec<&SecretKey> = signing_validators
.iter()
.map(|validator_index| secret_keys[*validator_index])
.collect();
builder.sign(signing_validators, &signing_secret_keys, &state.fork, spec);
builder.build()
})
.collect();
self.block.body.attestations.append(&mut attestations);
Ok(())
}
/// Insert a `Valid` deposit into the state.
pub fn insert_deposit(
&mut self,
amount: u64,
index: u64,
state: &BeaconState,
spec: &ChainSpec,
) {
let keypair = Keypair::random();
let mut builder = TestingDepositBuilder::new(keypair.pk.clone(), amount);
builder.set_index(index);
builder.sign(
&keypair,
state.slot.epoch(spec.slots_per_epoch),
&state.fork,
spec,
);
self.block.body.deposits.push(builder.build())
}
/// Insert a `Valid` exit into the state.
pub fn insert_exit(
&mut self,
state: &BeaconState,
validator_index: u64,
secret_key: &SecretKey,
spec: &ChainSpec,
) {
let mut builder = TestingVoluntaryExitBuilder::new(
state.slot.epoch(spec.slots_per_epoch),
validator_index,
);
builder.sign(secret_key, &state.fork, spec);
self.block.body.voluntary_exits.push(builder.build())
}
/// Insert a `Valid` transfer into the state.
///
/// Note: this will set the validator to be withdrawable by directly modifying the state
/// validator registry. This _may_ cause problems historic hashes, etc.
pub fn insert_transfer(
&mut self,
state: &BeaconState,
from: u64,
to: u64,
amount: u64,
keypair: Keypair,
spec: &ChainSpec,
) {
let mut builder = TestingTransferBuilder::new(from, to, amount, state.slot);
builder.sign(keypair, &state.fork, spec);
self.block.body.transfers.push(builder.build())
}
/// Signs and returns the block, consuming the builder.
pub fn build(mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) -> BeaconBlock {
self.sign(sk, fork, spec);
self.block
}
/// Returns the block, consuming the builder.
pub fn build_without_signing(self) -> BeaconBlock {
self.block
}
}
/// Builds an `ProposerSlashing` for some `validator_index`.
///
/// Signs the message using a `BeaconChainHarness`.
fn build_proposer_slashing(
validator_index: u64,
secret_key: &SecretKey,
fork: &Fork,
spec: &ChainSpec,
) -> ProposerSlashing {
let signer = |_validator_index: u64, message: &[u8], epoch: Epoch, domain: Domain| {
let domain = spec.get_domain(epoch, domain, fork);
Signature::new(message, domain, secret_key)
};
TestingProposerSlashingBuilder::double_vote(validator_index, signer, spec)
}
/// Builds an `AttesterSlashing` for some `validator_indices`.
///
/// Signs the message using a `BeaconChainHarness`.
fn build_double_vote_attester_slashing(
validator_indices: &[u64],
secret_keys: &[&SecretKey],
fork: &Fork,
spec: &ChainSpec,
) -> AttesterSlashing {
let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: Domain| {
let key_index = validator_indices
.iter()
.position(|&i| i == validator_index)
.expect("Unable to find attester slashing key");
let domain = spec.get_domain(epoch, domain, fork);
Signature::new(message, domain, secret_keys[key_index])
};
TestingAttesterSlashingBuilder::double_vote(validator_indices, signer)
}
@@ -0,0 +1,245 @@
use super::{generate_deterministic_keypairs, KeypairsFile};
use crate::test_utils::TestingPendingAttestationBuilder;
use crate::*;
use bls::get_withdrawal_credentials;
use dirs;
use log::debug;
use rayon::prelude::*;
use std::path::{Path, PathBuf};
pub const KEYPAIRS_FILE: &str = "keypairs.raw_keypairs";
/// Returns the directory where the generated keypairs should be stored.
///
/// It is either `$HOME/.lighthouse/keypairs.raw_keypairs` or, if `$HOME` is not available,
/// `./keypairs.raw_keypairs`.
pub fn keypairs_path() -> PathBuf {
let dir = dirs::home_dir()
.and_then(|home| Some(home.join(".lighthouse")))
.unwrap_or_else(|| PathBuf::from(""));
dir.join(KEYPAIRS_FILE)
}
/// Builds a beacon state to be used for testing purposes.
///
/// This struct should **never be used for production purposes.**
pub struct TestingBeaconStateBuilder {
state: BeaconState,
keypairs: Vec<Keypair>,
}
impl TestingBeaconStateBuilder {
/// Attempts to load validators from a file in `$HOME/.lighthouse/keypairs.raw_keypairs`. If
/// the file is unavailable, it generates the keys at runtime.
///
/// If the `$HOME` environment variable is not set, the local directory is used.
///
/// See the `Self::from_keypairs_file` method for more info.
///
/// # Panics
///
/// If the file does not contain enough keypairs or is invalid.
pub fn from_default_keypairs_file_if_exists(validator_count: usize, spec: &ChainSpec) -> Self {
let dir = dirs::home_dir()
.and_then(|home| Some(home.join(".lighthouse")))
.unwrap_or_else(|| PathBuf::from(""));
let file = dir.join(KEYPAIRS_FILE);
if file.exists() {
TestingBeaconStateBuilder::from_keypairs_file(validator_count, &file, spec)
} else {
TestingBeaconStateBuilder::from_deterministic_keypairs(validator_count, spec)
}
}
/// Loads the initial validator keypairs from a file on disk.
///
/// Loading keypairs from file is ~10x faster than generating them. Use the `gen_keys` command
/// on the `test_harness` binary to generate the keys. In the `test_harness` dir, run `cargo
/// run -- gen_keys -h` for help.
///
/// # Panics
///
/// If the file does not exist, is invalid or does not contain enough keypairs.
pub fn from_keypairs_file(validator_count: usize, path: &Path, spec: &ChainSpec) -> Self {
debug!("Loading {} keypairs from file...", validator_count);
let keypairs = Vec::from_raw_file(path, validator_count).unwrap();
TestingBeaconStateBuilder::from_keypairs(keypairs, spec)
}
/// Generates the validator keypairs deterministically.
pub fn from_deterministic_keypairs(validator_count: usize, spec: &ChainSpec) -> Self {
debug!("Generating {} deterministic keypairs...", validator_count);
let keypairs = generate_deterministic_keypairs(validator_count);
TestingBeaconStateBuilder::from_keypairs(keypairs, spec)
}
/// Uses the given keypair for all validators.
pub fn from_single_keypair(
validator_count: usize,
keypair: &Keypair,
spec: &ChainSpec,
) -> Self {
debug!("Generating {} cloned keypairs...", validator_count);
let mut keypairs = Vec::with_capacity(validator_count);
for _ in 0..validator_count {
keypairs.push(keypair.clone())
}
TestingBeaconStateBuilder::from_keypairs(keypairs, spec)
}
/// Creates the builder from an existing set of keypairs.
pub fn from_keypairs(keypairs: Vec<Keypair>, spec: &ChainSpec) -> Self {
let validator_count = keypairs.len();
debug!(
"Building {} Validator objects from keypairs...",
validator_count
);
let validators = keypairs
.par_iter()
.map(|keypair| {
let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials(
&keypair.pk,
spec.bls_withdrawal_prefix_byte,
));
Validator {
pubkey: keypair.pk.clone(),
withdrawal_credentials,
// All validators start active.
activation_epoch: spec.genesis_epoch,
exit_epoch: spec.far_future_epoch,
withdrawable_epoch: spec.far_future_epoch,
initiated_exit: false,
slashed: false,
}
})
.collect();
let mut state = BeaconState::genesis(
0,
Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
},
spec,
);
let balances = vec![32_000_000_000; validator_count];
debug!("Importing {} existing validators...", validator_count);
state.validator_registry = validators;
state.validator_balances = balances;
debug!("BeaconState initialized.");
Self { state, keypairs }
}
/// Consume the builder and return the `BeaconState` and the keypairs for each validator.
pub fn build(self) -> (BeaconState, Vec<Keypair>) {
(self.state, self.keypairs)
}
/// Ensures that the state returned from `Self::build(..)` has all caches pre-built.
///
/// Note: this performs the build when called. Ensure that no changes are made that would
/// invalidate this cache.
pub fn build_caches(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> {
let state = &mut self.state;
state.build_epoch_cache(RelativeEpoch::Previous, &spec)?;
state.build_epoch_cache(RelativeEpoch::Current, &spec)?;
state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &spec)?;
state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &spec)?;
state.update_pubkey_cache()?;
Ok(())
}
/// Sets the `BeaconState` to be in a slot, calling `teleport_to_epoch` to update the epoch.
pub fn teleport_to_slot(&mut self, slot: Slot, spec: &ChainSpec) {
self.teleport_to_epoch(slot.epoch(spec.slots_per_epoch), spec);
self.state.slot = slot;
}
/// Sets the `BeaconState` to be in the first slot of the given epoch.
///
/// Sets all justification/finalization parameters to be be as "perfect" as possible (i.e.,
/// highest justified and finalized slots, full justification bitfield, etc).
fn teleport_to_epoch(&mut self, epoch: Epoch, spec: &ChainSpec) {
let state = &mut self.state;
let slot = epoch.start_slot(spec.slots_per_epoch);
state.slot = slot;
state.previous_shuffling_epoch = epoch - 1;
state.current_shuffling_epoch = epoch;
state.previous_shuffling_seed = Hash256::from_low_u64_le(0);
state.current_shuffling_seed = Hash256::from_low_u64_le(1);
state.previous_justified_epoch = epoch - 3;
state.current_justified_epoch = epoch - 2;
state.justification_bitfield = u64::max_value();
state.finalized_epoch = epoch - 3;
state.validator_registry_update_epoch = epoch - 3;
}
/// Creates a full set of attestations for the `BeaconState`. Each attestation has full
/// participation from its committee and references the expected beacon_block hashes.
///
/// These attestations should be fully conducive to justification and finalization.
pub fn insert_attestations(&mut self, spec: &ChainSpec) {
let state = &mut self.state;
state
.build_epoch_cache(RelativeEpoch::Previous, spec)
.unwrap();
state
.build_epoch_cache(RelativeEpoch::Current, spec)
.unwrap();
let current_epoch = state.current_epoch(spec);
let previous_epoch = state.previous_epoch(spec);
let first_slot = previous_epoch.start_slot(spec.slots_per_epoch).as_u64();
let last_slot = current_epoch.end_slot(spec.slots_per_epoch).as_u64()
- spec.min_attestation_inclusion_delay;
let last_slot = std::cmp::min(state.slot.as_u64(), last_slot);
for slot in first_slot..last_slot + 1 {
let slot = Slot::from(slot);
let committees = state
.get_crosslink_committees_at_slot(slot, spec)
.unwrap()
.clone();
for crosslink_committee in committees {
let mut builder = TestingPendingAttestationBuilder::new(
state,
crosslink_committee.shard,
slot,
spec,
);
// The entire committee should have signed the pending attestation.
let signers = vec![true; crosslink_committee.committee.len()];
builder.add_committee_participation(signers);
let attestation = builder.build();
if attestation.data.slot.epoch(spec.slots_per_epoch) < state.current_epoch(spec) {
state.previous_epoch_attestations.push(attestation)
} else {
state.current_epoch_attestations.push(attestation)
}
}
}
}
}
@@ -0,0 +1,63 @@
use crate::*;
use bls::get_withdrawal_credentials;
/// Builds an deposit to be used for testing purposes.
///
/// This struct should **never be used for production purposes.**
pub struct TestingDepositBuilder {
deposit: Deposit,
}
impl TestingDepositBuilder {
/// Instantiates a new builder.
pub fn new(pubkey: PublicKey, amount: u64) -> Self {
let deposit = Deposit {
proof: vec![],
index: 0,
deposit_data: DepositData {
amount,
timestamp: 1,
deposit_input: DepositInput {
pubkey,
withdrawal_credentials: Hash256::zero(),
proof_of_possession: Signature::empty_signature(),
},
},
};
Self { deposit }
}
/// Set the `deposit.index` value.
pub fn set_index(&mut self, index: u64) {
self.deposit.index = index;
}
/// Signs the deposit, also setting the following values:
///
/// - `pubkey` to the signing pubkey.
/// - `withdrawal_credentials` to the signing pubkey.
/// - `proof_of_possesssion`
pub fn sign(&mut self, keypair: &Keypair, epoch: Epoch, fork: &Fork, spec: &ChainSpec) {
let withdrawal_credentials = Hash256::from_slice(
&get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..],
);
self.deposit.deposit_data.deposit_input.pubkey = keypair.pk.clone();
self.deposit
.deposit_data
.deposit_input
.withdrawal_credentials = withdrawal_credentials.clone();
self.deposit.deposit_data.deposit_input.proof_of_possession = self
.deposit
.deposit_data
.deposit_input
.create_proof_of_possession(&keypair.sk, epoch, fork, spec);
}
/// Builds the deposit, consuming the builder.
pub fn build(self) -> Deposit {
self.deposit
}
}
@@ -0,0 +1,55 @@
use crate::test_utils::TestingAttestationDataBuilder;
use crate::*;
/// Builds an `AttesterSlashing` to be used for testing purposes.
///
/// This struct should **never be used for production purposes.**
pub struct TestingPendingAttestationBuilder {
pending_attestation: PendingAttestation,
}
impl TestingPendingAttestationBuilder {
/// Create a new valid* `PendingAttestation` for the given parameters.
///
/// The `inclusion_slot` will be set to be the earliest possible slot the `Attestation` could
/// have been included (`slot + MIN_ATTESTATION_INCLUSION_DELAY`).
///
/// * The aggregation and custody bitfields will all be empty, they need to be set with
/// `Self::add_committee_participation`.
pub fn new(state: &BeaconState, shard: u64, slot: Slot, spec: &ChainSpec) -> Self {
let data_builder = TestingAttestationDataBuilder::new(state, shard, slot, spec);
let pending_attestation = PendingAttestation {
aggregation_bitfield: Bitfield::new(),
data: data_builder.build(),
custody_bitfield: Bitfield::new(),
inclusion_slot: slot + spec.min_attestation_inclusion_delay,
};
Self {
pending_attestation,
}
}
/// Sets the committee participation in the `PendingAttestation`.
///
/// The `PendingAttestation` will appear to be signed by each committee member who's value in
/// `signers` is true.
pub fn add_committee_participation(&mut self, signers: Vec<bool>) {
let mut aggregation_bitfield = Bitfield::new();
let mut custody_bitfield = Bitfield::new();
for (i, signed) in signers.iter().enumerate() {
aggregation_bitfield.set(i, *signed);
custody_bitfield.set(i, false); // Fixed to `false` for phase 0.
}
self.pending_attestation.aggregation_bitfield = aggregation_bitfield;
self.pending_attestation.custody_bitfield = custody_bitfield;
}
/// Returns the `PendingAttestation`, consuming the builder.
pub fn build(self) -> PendingAttestation {
self.pending_attestation
}
}
@@ -2,9 +2,11 @@ use crate::*;
use ssz::SignedRoot;
/// Builds a `ProposerSlashing`.
pub struct ProposerSlashingBuilder();
///
/// This struct should **never be used for production purposes.**
pub struct TestingProposerSlashingBuilder();
impl ProposerSlashingBuilder {
impl TestingProposerSlashingBuilder {
/// Builds a `ProposerSlashing` that is a double vote.
///
/// The `signer` function is used to sign the double-vote and accepts:
@@ -20,38 +22,38 @@ impl ProposerSlashingBuilder {
F: Fn(u64, &[u8], Epoch, Domain) -> Signature,
{
let slot = Slot::new(0);
let shard = 0;
let hash_1 = Hash256::from([1; 32]);
let hash_2 = Hash256::from([2; 32]);
let mut proposal_1 = Proposal {
let mut header_1 = BeaconBlockHeader {
slot,
shard,
block_root: Hash256::from_low_u64_le(1),
previous_block_root: hash_1,
state_root: hash_1,
block_body_root: hash_1,
signature: Signature::empty_signature(),
};
let mut proposal_2 = Proposal {
slot,
shard,
block_root: Hash256::from_low_u64_le(2),
signature: Signature::empty_signature(),
let mut header_2 = BeaconBlockHeader {
previous_block_root: hash_2,
..header_1.clone()
};
proposal_1.signature = {
let message = proposal_1.signed_root();
header_1.signature = {
let message = header_1.signed_root();
let epoch = slot.epoch(spec.slots_per_epoch);
signer(proposer_index, &message[..], epoch, Domain::Proposal)
signer(proposer_index, &message[..], epoch, Domain::BeaconBlock)
};
proposal_2.signature = {
let message = proposal_2.signed_root();
header_2.signature = {
let message = header_2.signed_root();
let epoch = slot.epoch(spec.slots_per_epoch);
signer(proposer_index, &message[..], epoch, Domain::Proposal)
signer(proposer_index, &message[..], epoch, Domain::BeaconBlock)
};
ProposerSlashing {
proposer_index,
proposal_1,
proposal_2,
header_1,
header_2,
}
}
}
@@ -0,0 +1,45 @@
use crate::*;
use ssz::SignedRoot;
/// Builds a transfer to be used for testing purposes.
///
/// This struct should **never be used for production purposes.**
pub struct TestingTransferBuilder {
transfer: Transfer,
}
impl TestingTransferBuilder {
/// Instantiates a new builder.
pub fn new(sender: u64, recipient: u64, amount: u64, slot: Slot) -> Self {
let keypair = Keypair::random();
let transfer = Transfer {
sender,
recipient,
amount,
fee: 0,
slot,
pubkey: keypair.pk,
signature: Signature::empty_signature(),
};
Self { transfer }
}
/// Signs the transfer.
///
/// The keypair must match that of the `from` validator index.
pub fn sign(&mut self, keypair: Keypair, fork: &Fork, spec: &ChainSpec) {
self.transfer.pubkey = keypair.pk;
let message = self.transfer.signed_root();
let epoch = self.transfer.slot.epoch(spec.slots_per_epoch);
let domain = spec.get_domain(epoch, Domain::Transfer, fork);
self.transfer.signature = Signature::new(&message, domain, &keypair.sk);
}
/// Builds the transfer, consuming the builder.
pub fn build(self) -> Transfer {
self.transfer
}
}
@@ -0,0 +1,37 @@
use crate::*;
use ssz::SignedRoot;
/// Builds an exit to be used for testing purposes.
///
/// This struct should **never be used for production purposes.**
pub struct TestingVoluntaryExitBuilder {
exit: VoluntaryExit,
}
impl TestingVoluntaryExitBuilder {
/// Instantiates a new builder.
pub fn new(epoch: Epoch, validator_index: u64) -> Self {
let exit = VoluntaryExit {
epoch,
validator_index,
signature: Signature::empty_signature(),
};
Self { exit }
}
/// Signs the exit.
///
/// The signing secret key must match that of the exiting validator.
pub fn sign(&mut self, secret_key: &SecretKey, fork: &Fork, spec: &ChainSpec) {
let message = self.exit.signed_root();
let domain = spec.get_domain(self.exit.epoch, Domain::Exit, fork);
self.exit.signature = Signature::new(&message, domain, secret_key);
}
/// Builds the exit, consuming the builder.
pub fn build(self) -> VoluntaryExit {
self.exit
}
}
+16 -28
View File
@@ -2,7 +2,7 @@ use super::Slot;
use crate::test_utils::TestRandom;
use bls::{PublicKey, Signature};
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz::TreeHash;
use ssz_derive::{Decode, Encode, SignedRoot, TreeHash};
use test_random_derive::TestRandom;
@@ -10,10 +10,21 @@ use test_random_derive::TestRandom;
/// The data submitted to the deposit contract.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)]
#[derive(
Debug,
PartialEq,
Clone,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
TestRandom,
SignedRoot,
)]
pub struct Transfer {
pub from: u64,
pub to: u64,
pub sender: u64,
pub recipient: u64,
pub amount: u64,
pub fee: u64,
pub slot: Slot,
@@ -24,29 +35,6 @@ pub struct Transfer {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Transfer::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Transfer::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(Transfer);
}
+45 -36
View File
@@ -1,13 +1,13 @@
use crate::{test_utils::TestRandom, Epoch, Hash256, PublicKey};
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
/// Information about a `BeaconChain` validator.
///
/// Spec v0.4.0
#[derive(Debug, Clone, PartialEq, Serialize, Encode, Decode, TestRandom, TreeHash)]
/// Spec v0.5.0
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom, TreeHash)]
pub struct Validator {
pub pubkey: PublicKey,
pub withdrawal_credentials: Hash256,
@@ -53,52 +53,61 @@ impl Default for Validator {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Validator::random_for_test(&mut rng);
fn default() {
let v = Validator::default();
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
let epoch = Epoch::new(0);
assert_eq!(original, decoded);
assert_eq!(v.is_active_at(epoch), false);
assert_eq!(v.is_exited_at(epoch), false);
assert_eq!(v.is_withdrawable_at(epoch), false);
assert_eq!(v.initiated_exit, false);
assert_eq!(v.slashed, false);
}
#[test]
fn test_validator_can_be_active() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let mut validator = Validator::random_for_test(&mut rng);
fn is_active_at() {
let epoch = Epoch::new(10);
let activation_epoch = u64::random_for_test(&mut rng);
let exit_epoch = activation_epoch + 234;
let v = Validator {
activation_epoch: epoch,
..Validator::default()
};
validator.activation_epoch = Epoch::from(activation_epoch);
validator.exit_epoch = Epoch::from(exit_epoch);
for slot in (activation_epoch - 100)..(exit_epoch + 100) {
let slot = Epoch::from(slot);
if slot < activation_epoch {
assert!(!validator.is_active_at(slot));
} else if slot >= exit_epoch {
assert!(!validator.is_active_at(slot));
} else {
assert!(validator.is_active_at(slot));
}
}
assert_eq!(v.is_active_at(epoch - 1), false);
assert_eq!(v.is_active_at(epoch), true);
assert_eq!(v.is_active_at(epoch + 1), true);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = Validator::random_for_test(&mut rng);
fn is_exited_at() {
let epoch = Epoch::new(10);
let result = original.hash_tree_root_internal();
let v = Validator {
exit_epoch: epoch,
..Validator::default()
};
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
assert_eq!(v.is_exited_at(epoch - 1), false);
assert_eq!(v.is_exited_at(epoch), true);
assert_eq!(v.is_exited_at(epoch + 1), true);
}
#[test]
fn is_withdrawable_at() {
let epoch = Epoch::new(10);
let v = Validator {
withdrawable_epoch: epoch,
..Validator::default()
};
assert_eq!(v.is_withdrawable_at(epoch - 1), false);
assert_eq!(v.is_withdrawable_at(epoch), true);
assert_eq!(v.is_withdrawable_at(epoch + 1), true);
}
ssz_tests!(Validator);
}
+11 -11
View File
@@ -7,17 +7,17 @@ use crate::Epoch;
///
/// Spec v0.4.0
pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec<usize> {
validators
.iter()
.enumerate()
.filter_map(|(index, validator)| {
if validator.is_active_at(epoch) {
Some(index)
} else {
None
}
})
.collect::<Vec<_>>()
let mut active = Vec::with_capacity(validators.len());
for (index, validator) in validators.iter().enumerate() {
if validator.is_active_at(epoch) {
active.push(index)
}
}
active.shrink_to_fit();
active
}
#[cfg(test)]
@@ -1,36 +0,0 @@
use crate::{test_utils::TestRandom, Hash256, Slot};
use bls::PublicKey;
use rand::RngCore;
use serde_derive::Serialize;
use ssz_derive::{Decode, Encode, TreeHash};
use test_random_derive::TestRandom;
// The information gathered from the PoW chain validator registration function.
#[derive(Debug, Clone, PartialEq, Serialize, Encode, Decode, TreeHash, TestRandom)]
pub struct ValidatorRegistryDeltaBlock {
pub latest_registry_delta_root: Hash256,
pub validator_index: u32,
pub pubkey: PublicKey,
pub slot: Slot,
pub flag: u64,
}
impl Default for ValidatorRegistryDeltaBlock {
/// Yields a "default" `Validator`. Primarily used for testing.
fn default() -> Self {
Self {
latest_registry_delta_root: Hash256::zero(),
validator_index: std::u32::MAX,
pubkey: PublicKey::default(),
slot: Slot::from(std::u64::MAX),
flag: std::u64::MAX,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
ssz_tests!(ValidatorRegistryDeltaBlock);
}
+15 -27
View File
@@ -1,15 +1,26 @@
use crate::{test_utils::TestRandom, Epoch};
use bls::Signature;
use rand::RngCore;
use serde_derive::Serialize;
use serde_derive::{Deserialize, Serialize};
use ssz::TreeHash;
use ssz_derive::{Decode, Encode, SignedRoot, TreeHash};
use test_random_derive::TestRandom;
/// An exit voluntarily submitted a validator who wishes to withdraw.
///
/// Spec v0.4.0
#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)]
/// Spec v0.5.0
#[derive(
Debug,
PartialEq,
Clone,
Serialize,
Deserialize,
Encode,
Decode,
TreeHash,
TestRandom,
SignedRoot,
)]
pub struct VoluntaryExit {
pub epoch: Epoch,
pub validator_index: u64,
@@ -19,29 +30,6 @@ pub struct VoluntaryExit {
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
use ssz::{ssz_encode, Decodable, TreeHash};
#[test]
pub fn test_ssz_round_trip() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = VoluntaryExit::random_for_test(&mut rng);
let bytes = ssz_encode(&original);
let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap();
assert_eq!(original, decoded);
}
#[test]
pub fn test_hash_tree_root_internal() {
let mut rng = XorShiftRng::from_seed([42; 16]);
let original = VoluntaryExit::random_for_test(&mut rng);
let result = original.hash_tree_root_internal();
assert_eq!(result.len(), 32);
// TODO: Add further tests
// https://github.com/sigp/lighthouse/issues/170
}
ssz_tests!(VoluntaryExit);
}