Merge current master and fix ssz-fuzzing
This commit is contained in:
@@ -4,7 +4,7 @@ mod traits;
|
||||
use slot_clock::SlotClock;
|
||||
use ssz::{SignedRoot, TreeHash};
|
||||
use std::sync::Arc;
|
||||
use types::{BeaconBlock, ChainSpec, Domain, Hash256, Proposal, Slot};
|
||||
use types::{BeaconBlock, ChainSpec, Domain, Slot};
|
||||
|
||||
pub use self::traits::{
|
||||
BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, PublishOutcome, Signer,
|
||||
@@ -158,7 +158,7 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
|
||||
if self.safe_to_produce(&block) {
|
||||
let domain = self.spec.get_domain(
|
||||
slot.epoch(self.spec.slots_per_epoch),
|
||||
Domain::Proposal,
|
||||
Domain::BeaconBlock,
|
||||
&fork,
|
||||
);
|
||||
if let Some(block) = self.sign_block(block, domain) {
|
||||
@@ -182,16 +182,9 @@ impl<T: SlotClock, U: BeaconNode, V: DutiesReader, W: Signer> BlockProducer<T, U
|
||||
fn sign_block(&mut self, mut block: BeaconBlock, domain: u64) -> Option<BeaconBlock> {
|
||||
self.store_produce(&block);
|
||||
|
||||
let proposal = Proposal {
|
||||
slot: block.slot,
|
||||
shard: self.spec.beacon_chain_shard_number,
|
||||
block_root: Hash256::from_slice(&block.signed_root()[..]),
|
||||
signature: block.signature.clone(),
|
||||
};
|
||||
|
||||
match self
|
||||
.signer
|
||||
.sign_block_proposal(&proposal.signed_root()[..], domain)
|
||||
.sign_block_proposal(&block.signed_root()[..], domain)
|
||||
{
|
||||
None => None,
|
||||
Some(signature) => {
|
||||
|
||||
@@ -28,8 +28,8 @@ impl DutiesReader for EpochMap {
|
||||
|
||||
fn fork(&self) -> Result<Fork, DutiesReaderError> {
|
||||
Ok(Fork {
|
||||
previous_version: 0,
|
||||
current_version: 0,
|
||||
previous_version: [0; 4],
|
||||
current_version: [0; 4],
|
||||
epoch: Epoch::new(0),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,10 +10,7 @@ use db::{
|
||||
use log::{debug, trace};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
readers::BeaconBlockReader, validator_registry::get_active_validator_indices, BeaconBlock,
|
||||
ChainSpec, Hash256, Slot, SlotHeight,
|
||||
};
|
||||
use types::{BeaconBlock, ChainSpec, Hash256, Slot, SlotHeight};
|
||||
|
||||
//TODO: Pruning - Children
|
||||
//TODO: Handle Syncing
|
||||
@@ -93,10 +90,8 @@ where
|
||||
.get_deserialized(&state_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
|
||||
|
||||
let active_validator_indices = get_active_validator_indices(
|
||||
¤t_state.validator_registry[..],
|
||||
block_slot.epoch(spec.slots_per_epoch),
|
||||
);
|
||||
let active_validator_indices =
|
||||
current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
|
||||
|
||||
for index in active_validator_indices {
|
||||
let balance = std::cmp::min(
|
||||
@@ -255,17 +250,17 @@ impl<T: ClientDB + Sized> ForkChoice for BitwiseLMDGhost<T> {
|
||||
// get the height of the parent
|
||||
let parent_height = self
|
||||
.block_store
|
||||
.get_deserialized(&block.parent_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.parent_root))?
|
||||
.slot()
|
||||
.get_deserialized(&block.previous_block_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.previous_block_root))?
|
||||
.slot
|
||||
.height(spec.genesis_slot);
|
||||
|
||||
let parent_hash = &block.parent_root;
|
||||
let parent_hash = &block.previous_block_root;
|
||||
|
||||
// add the new block to the children of parent
|
||||
(*self
|
||||
.children
|
||||
.entry(block.parent_root)
|
||||
.entry(block.previous_block_root)
|
||||
.or_insert_with(|| vec![]))
|
||||
.push(block_hash.clone());
|
||||
|
||||
@@ -309,7 +304,7 @@ impl<T: ClientDB + Sized> ForkChoice for BitwiseLMDGhost<T> {
|
||||
.block_store
|
||||
.get_deserialized(&target_block_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))?
|
||||
.slot()
|
||||
.slot
|
||||
.height(spec.genesis_slot);
|
||||
|
||||
// get the height of the past target block
|
||||
@@ -317,7 +312,7 @@ impl<T: ClientDB + Sized> ForkChoice for BitwiseLMDGhost<T> {
|
||||
.block_store
|
||||
.get_deserialized(&attestation_target)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))?
|
||||
.slot()
|
||||
.slot
|
||||
.height(spec.genesis_slot);
|
||||
// update the attestation only if the new target is higher
|
||||
if past_block_height < block_height {
|
||||
@@ -343,8 +338,8 @@ impl<T: ClientDB + Sized> ForkChoice for BitwiseLMDGhost<T> {
|
||||
.get_deserialized(&justified_block_start)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?;
|
||||
|
||||
let block_slot = block.slot();
|
||||
let state_root = block.state_root();
|
||||
let block_slot = block.slot;
|
||||
let state_root = block.state_root;
|
||||
let mut block_height = block_slot.height(spec.genesis_slot);
|
||||
|
||||
let mut current_head = *justified_block_start;
|
||||
@@ -434,7 +429,7 @@ impl<T: ClientDB + Sized> ForkChoice for BitwiseLMDGhost<T> {
|
||||
.block_store
|
||||
.get_deserialized(¤t_head)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(current_head))?
|
||||
.slot()
|
||||
.slot
|
||||
.height(spec.genesis_slot);
|
||||
// prune the latest votes for votes that are not part of current chosen chain
|
||||
// more specifically, only keep votes that have head as an ancestor
|
||||
|
||||
@@ -96,6 +96,7 @@ impl From<BeaconBlockAtSlotError> for ForkChoiceError {
|
||||
}
|
||||
|
||||
/// Fork choice options that are currently implemented.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ForkChoiceAlgorithm {
|
||||
/// Chooses the longest chain becomes the head. Not for production.
|
||||
LongestChain,
|
||||
|
||||
@@ -34,7 +34,7 @@ impl<T: ClientDB + Sized> ForkChoice for LongestChain<T> {
|
||||
) -> Result<(), ForkChoiceError> {
|
||||
// add the block hash to head_block_hashes removing the parent if it exists
|
||||
self.head_block_hashes
|
||||
.retain(|hash| *hash != block.parent_root);
|
||||
.retain(|hash| *hash != block.previous_block_root);
|
||||
self.head_block_hashes.push(*block_hash);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10,10 +10,7 @@ use log::{debug, trace};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
readers::BeaconBlockReader, validator_registry::get_active_validator_indices, BeaconBlock,
|
||||
ChainSpec, Hash256, Slot, SlotHeight,
|
||||
};
|
||||
use types::{BeaconBlock, ChainSpec, Hash256, Slot, SlotHeight};
|
||||
|
||||
//TODO: Pruning - Children
|
||||
//TODO: Handle Syncing
|
||||
@@ -93,10 +90,8 @@ where
|
||||
.get_deserialized(&state_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
|
||||
|
||||
let active_validator_indices = get_active_validator_indices(
|
||||
¤t_state.validator_registry[..],
|
||||
block_slot.epoch(spec.slots_per_epoch),
|
||||
);
|
||||
let active_validator_indices =
|
||||
current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
|
||||
|
||||
for index in active_validator_indices {
|
||||
let balance = std::cmp::min(
|
||||
@@ -226,17 +221,17 @@ impl<T: ClientDB + Sized> ForkChoice for OptimizedLMDGhost<T> {
|
||||
// get the height of the parent
|
||||
let parent_height = self
|
||||
.block_store
|
||||
.get_deserialized(&block.parent_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.parent_root))?
|
||||
.slot()
|
||||
.get_deserialized(&block.previous_block_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.previous_block_root))?
|
||||
.slot
|
||||
.height(spec.genesis_slot);
|
||||
|
||||
let parent_hash = &block.parent_root;
|
||||
let parent_hash = &block.previous_block_root;
|
||||
|
||||
// add the new block to the children of parent
|
||||
(*self
|
||||
.children
|
||||
.entry(block.parent_root)
|
||||
.entry(block.previous_block_root)
|
||||
.or_insert_with(|| vec![]))
|
||||
.push(block_hash.clone());
|
||||
|
||||
@@ -280,7 +275,7 @@ impl<T: ClientDB + Sized> ForkChoice for OptimizedLMDGhost<T> {
|
||||
.block_store
|
||||
.get_deserialized(&target_block_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))?
|
||||
.slot()
|
||||
.slot
|
||||
.height(spec.genesis_slot);
|
||||
|
||||
// get the height of the past target block
|
||||
@@ -288,7 +283,7 @@ impl<T: ClientDB + Sized> ForkChoice for OptimizedLMDGhost<T> {
|
||||
.block_store
|
||||
.get_deserialized(&attestation_target)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))?
|
||||
.slot()
|
||||
.slot
|
||||
.height(spec.genesis_slot);
|
||||
// update the attestation only if the new target is higher
|
||||
if past_block_height < block_height {
|
||||
@@ -314,8 +309,8 @@ impl<T: ClientDB + Sized> ForkChoice for OptimizedLMDGhost<T> {
|
||||
.get_deserialized(&justified_block_start)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?;
|
||||
|
||||
let block_slot = block.slot();
|
||||
let state_root = block.state_root();
|
||||
let block_slot = block.slot;
|
||||
let state_root = block.state_root;
|
||||
let mut block_height = block_slot.height(spec.genesis_slot);
|
||||
|
||||
let mut current_head = *justified_block_start;
|
||||
@@ -405,7 +400,7 @@ impl<T: ClientDB + Sized> ForkChoice for OptimizedLMDGhost<T> {
|
||||
.block_store
|
||||
.get_deserialized(¤t_head)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(current_head))?
|
||||
.slot()
|
||||
.slot
|
||||
.height(spec.genesis_slot);
|
||||
// prune the latest votes for votes that are not part of current chosen chain
|
||||
// more specifically, only keep votes that have head as an ancestor
|
||||
|
||||
@@ -8,10 +8,7 @@ use db::{
|
||||
use log::{debug, trace};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use types::{
|
||||
readers::BeaconBlockReader, validator_registry::get_active_validator_indices, BeaconBlock,
|
||||
ChainSpec, Hash256, Slot,
|
||||
};
|
||||
use types::{BeaconBlock, ChainSpec, Hash256, Slot};
|
||||
|
||||
//TODO: Pruning and syncing
|
||||
|
||||
@@ -62,10 +59,8 @@ where
|
||||
.get_deserialized(&state_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
|
||||
|
||||
let active_validator_indices = get_active_validator_indices(
|
||||
¤t_state.validator_registry[..],
|
||||
block_slot.epoch(spec.slots_per_epoch),
|
||||
);
|
||||
let active_validator_indices =
|
||||
current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
|
||||
|
||||
for index in active_validator_indices {
|
||||
let balance = std::cmp::min(
|
||||
@@ -95,7 +90,7 @@ where
|
||||
.block_store
|
||||
.get_deserialized(&block_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*block_root))?
|
||||
.slot();
|
||||
.slot;
|
||||
|
||||
for (vote_hash, votes) in latest_votes.iter() {
|
||||
let (root_at_slot, _) = self
|
||||
@@ -122,7 +117,7 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
|
||||
// add the new block to the children of parent
|
||||
(*self
|
||||
.children
|
||||
.entry(block.parent_root)
|
||||
.entry(block.previous_block_root)
|
||||
.or_insert_with(|| vec![]))
|
||||
.push(block_hash.clone());
|
||||
|
||||
@@ -155,7 +150,7 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
|
||||
.block_store
|
||||
.get_deserialized(&target_block_root)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))?
|
||||
.slot()
|
||||
.slot
|
||||
.height(spec.genesis_slot);
|
||||
|
||||
// get the height of the past target block
|
||||
@@ -163,7 +158,7 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
|
||||
.block_store
|
||||
.get_deserialized(&attestation_target)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))?
|
||||
.slot()
|
||||
.slot
|
||||
.height(spec.genesis_slot);
|
||||
// update the attestation only if the new target is higher
|
||||
if past_block_height < block_height {
|
||||
@@ -186,9 +181,9 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
|
||||
.get_deserialized(&justified_block_start)?
|
||||
.ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?;
|
||||
|
||||
let start_state_root = start.state_root();
|
||||
let start_state_root = start.state_root;
|
||||
|
||||
let latest_votes = self.get_latest_votes(&start_state_root, start.slot(), spec)?;
|
||||
let latest_votes = self.get_latest_votes(&start_state_root, start.slot, spec)?;
|
||||
|
||||
let mut head_hash = *justified_block_start;
|
||||
|
||||
@@ -220,10 +215,8 @@ impl<T: ClientDB + Sized> ForkChoice for SlowLMDGhost<T> {
|
||||
head_vote_count = vote_count;
|
||||
}
|
||||
// resolve ties - choose smaller hash
|
||||
else if vote_count == head_vote_count {
|
||||
if *child_hash < head_hash {
|
||||
head_hash = *child_hash;
|
||||
}
|
||||
else if vote_count == head_vote_count && *child_hash < head_hash {
|
||||
head_hash = *child_hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,8 @@ fn test_yaml_vectors(
|
||||
let randao_reveal = Signature::empty_signature();
|
||||
let signature = Signature::empty_signature();
|
||||
let body = BeaconBlockBody {
|
||||
eth1_data,
|
||||
randao_reveal,
|
||||
proposer_slashings: vec![],
|
||||
attester_slashings: vec![],
|
||||
attestations: vec![],
|
||||
@@ -117,14 +119,14 @@ fn test_yaml_vectors(
|
||||
// default params for genesis
|
||||
let block_hash = id_to_hash(&block_id);
|
||||
let mut slot = spec.genesis_slot;
|
||||
let parent_root = id_to_hash(&parent_id);
|
||||
let previous_block_root = id_to_hash(&parent_id);
|
||||
|
||||
// set the slot and parent based off the YAML. Start with genesis;
|
||||
// if not the genesis, update slot
|
||||
if parent_id != block_id {
|
||||
// find parent slot
|
||||
slot = *(block_slot
|
||||
.get(&parent_root)
|
||||
.get(&previous_block_root)
|
||||
.expect("Parent should have a slot number"))
|
||||
+ 1;
|
||||
} else {
|
||||
@@ -137,10 +139,8 @@ fn test_yaml_vectors(
|
||||
// build the BeaconBlock
|
||||
let beacon_block = BeaconBlock {
|
||||
slot,
|
||||
parent_root,
|
||||
previous_block_root,
|
||||
state_root: state_root.clone(),
|
||||
randao_reveal: randao_reveal.clone(),
|
||||
eth1_data: eth1_data.clone(),
|
||||
signature: signature.clone(),
|
||||
body: body.clone(),
|
||||
};
|
||||
@@ -242,8 +242,9 @@ fn setup_inital_state(
|
||||
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let state_builder =
|
||||
let mut state_builder =
|
||||
TestingBeaconStateBuilder::from_single_keypair(num_validators, &Keypair::random(), &spec);
|
||||
state_builder.build_caches(&spec).unwrap();
|
||||
let (state, _keypairs) = state_builder.build();
|
||||
|
||||
let state_root = state.canonical_root();
|
||||
|
||||
@@ -11,6 +11,9 @@ harness = false
|
||||
[dev-dependencies]
|
||||
criterion = "0.2"
|
||||
env_logger = "0.6.0"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_yaml = "0.8"
|
||||
|
||||
[dependencies]
|
||||
bls = { path = "../utils/bls" }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use criterion::Criterion;
|
||||
use criterion::{black_box, Benchmark};
|
||||
use log::debug;
|
||||
use ssz::TreeHash;
|
||||
use state_processing::{
|
||||
per_block_processing,
|
||||
@@ -10,195 +9,12 @@ use state_processing::{
|
||||
verify_block_signature,
|
||||
},
|
||||
};
|
||||
use types::test_utils::{TestingBeaconBlockBuilder, TestingBeaconStateBuilder};
|
||||
use types::*;
|
||||
|
||||
/// Run the benchmarking suite on a foundation spec with 16,384 validators.
|
||||
pub fn bench_block_processing_n_validators(c: &mut Criterion, validator_count: usize) {
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
let (mut state, keypairs) = build_state(validator_count, &spec);
|
||||
let block = build_block(&mut state, &keypairs, &spec);
|
||||
|
||||
assert_eq!(
|
||||
block.body.proposer_slashings.len(),
|
||||
spec.max_proposer_slashings as usize,
|
||||
"The block should have the maximum possible proposer slashings"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
block.body.attester_slashings.len(),
|
||||
spec.max_attester_slashings as usize,
|
||||
"The block should have the maximum possible attester slashings"
|
||||
);
|
||||
|
||||
for attester_slashing in &block.body.attester_slashings {
|
||||
let len_1 = attester_slashing
|
||||
.slashable_attestation_1
|
||||
.validator_indices
|
||||
.len();
|
||||
let len_2 = attester_slashing
|
||||
.slashable_attestation_1
|
||||
.validator_indices
|
||||
.len();
|
||||
assert!(
|
||||
(len_1 == len_2) && (len_2 == spec.max_indices_per_slashable_vote as usize),
|
||||
"Each attester slashing should have the maximum possible validator indices"
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
block.body.attestations.len(),
|
||||
spec.max_attestations as usize,
|
||||
"The block should have the maximum possible attestations."
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
block.body.deposits.len(),
|
||||
spec.max_deposits as usize,
|
||||
"The block should have the maximum possible deposits."
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
block.body.voluntary_exits.len(),
|
||||
spec.max_voluntary_exits as usize,
|
||||
"The block should have the maximum possible voluntary exits."
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
block.body.transfers.len(),
|
||||
spec.max_transfers as usize,
|
||||
"The block should have the maximum possible transfers."
|
||||
);
|
||||
|
||||
bench_block_processing(
|
||||
c,
|
||||
&block,
|
||||
&state,
|
||||
&spec,
|
||||
&format!("{}_validators", validator_count),
|
||||
);
|
||||
}
|
||||
|
||||
fn build_state(validator_count: usize, spec: &ChainSpec) -> (BeaconState, Vec<Keypair>) {
|
||||
let mut builder =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec);
|
||||
|
||||
// Set the state to be just before an epoch transition.
|
||||
let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch);
|
||||
builder.teleport_to_slot(target_slot, &spec);
|
||||
|
||||
// Builds all caches; benches will not contain shuffling/committee building times.
|
||||
builder.build_caches(&spec).unwrap();
|
||||
|
||||
builder.build()
|
||||
}
|
||||
|
||||
fn build_block(state: &mut BeaconState, keypairs: &[Keypair], spec: &ChainSpec) -> BeaconBlock {
|
||||
let mut builder = TestingBeaconBlockBuilder::new(spec);
|
||||
|
||||
builder.set_slot(state.slot);
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap();
|
||||
let keypair = &keypairs[proposer_index];
|
||||
|
||||
builder.set_randao_reveal(&keypair.sk, &state.fork, spec);
|
||||
|
||||
// Used as a stream of validator indices for use in slashings, exits, etc.
|
||||
let mut validators_iter = (0..keypairs.len() as u64).into_iter();
|
||||
|
||||
// Insert the maximum possible number of `ProposerSlashing` objects.
|
||||
debug!(
|
||||
"Inserting {} proposer slashings...",
|
||||
spec.max_proposer_slashings
|
||||
);
|
||||
for _ in 0..spec.max_proposer_slashings {
|
||||
let validator_index = validators_iter.next().expect("Insufficient validators.");
|
||||
|
||||
builder.insert_proposer_slashing(
|
||||
validator_index,
|
||||
&keypairs[validator_index as usize].sk,
|
||||
&state.fork,
|
||||
spec,
|
||||
);
|
||||
}
|
||||
|
||||
// Insert the maximum possible number of `AttesterSlashing` objects
|
||||
debug!(
|
||||
"Inserting {} attester slashings...",
|
||||
spec.max_attester_slashings
|
||||
);
|
||||
for _ in 0..spec.max_attester_slashings {
|
||||
let mut attesters: Vec<u64> = vec![];
|
||||
let mut secret_keys: Vec<&SecretKey> = vec![];
|
||||
|
||||
for _ in 0..spec.max_indices_per_slashable_vote {
|
||||
let validator_index = validators_iter.next().expect("Insufficient validators.");
|
||||
|
||||
attesters.push(validator_index);
|
||||
secret_keys.push(&keypairs[validator_index as usize].sk);
|
||||
}
|
||||
|
||||
builder.insert_attester_slashing(&attesters, &secret_keys, &state.fork, spec);
|
||||
}
|
||||
|
||||
// Insert the maximum possible number of `Attestation` objects.
|
||||
debug!("Inserting {} attestations...", spec.max_attestations);
|
||||
let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect();
|
||||
builder
|
||||
.fill_with_attestations(state, &all_secret_keys, spec)
|
||||
.unwrap();
|
||||
|
||||
// Insert the maximum possible number of `Deposit` objects.
|
||||
debug!("Inserting {} deposits...", spec.max_deposits);
|
||||
for i in 0..spec.max_deposits {
|
||||
builder.insert_deposit(32_000_000_000, state.deposit_index + i, state, spec);
|
||||
}
|
||||
|
||||
// Insert the maximum possible number of `Exit` objects.
|
||||
debug!("Inserting {} exits...", spec.max_voluntary_exits);
|
||||
for _ in 0..spec.max_voluntary_exits {
|
||||
let validator_index = validators_iter.next().expect("Insufficient validators.");
|
||||
|
||||
builder.insert_exit(
|
||||
state,
|
||||
validator_index,
|
||||
&keypairs[validator_index as usize].sk,
|
||||
spec,
|
||||
);
|
||||
}
|
||||
|
||||
// Insert the maximum possible number of `Transfer` objects.
|
||||
debug!("Inserting {} transfers...", spec.max_transfers);
|
||||
for _ in 0..spec.max_transfers {
|
||||
let validator_index = validators_iter.next().expect("Insufficient validators.");
|
||||
|
||||
// Manually set the validator to be withdrawn.
|
||||
state.validator_registry[validator_index as usize].withdrawable_epoch =
|
||||
state.previous_epoch(spec);
|
||||
|
||||
builder.insert_transfer(
|
||||
state,
|
||||
validator_index,
|
||||
validator_index,
|
||||
1,
|
||||
keypairs[validator_index as usize].clone(),
|
||||
spec,
|
||||
);
|
||||
}
|
||||
|
||||
let mut block = builder.build(&keypair.sk, &state.fork, spec);
|
||||
|
||||
// Set the eth1 data to be different from the state.
|
||||
block.eth1_data.block_hash = Hash256::from_slice(&vec![42; 32]);
|
||||
|
||||
block
|
||||
}
|
||||
|
||||
/// Run the detailed benchmarking suite on the given `BeaconState`.
|
||||
///
|
||||
/// `desc` will be added to the title of each bench.
|
||||
fn bench_block_processing(
|
||||
pub fn bench_block_processing(
|
||||
c: &mut Criterion,
|
||||
initial_block: &BeaconBlock,
|
||||
initial_state: &BeaconState,
|
||||
|
||||
@@ -48,16 +48,6 @@ pub fn bench_epoch_processing_n_validators(c: &mut Criterion, validator_count: u
|
||||
"The state should have an attestation for each committee."
|
||||
);
|
||||
|
||||
// Assert that each attestation in the state has full participation.
|
||||
let committee_size = validator_count / committees_per_epoch as usize;
|
||||
for a in &state.latest_attestations {
|
||||
assert_eq!(
|
||||
a.aggregation_bitfield.num_set_bits(),
|
||||
committee_size,
|
||||
"Each attestation in the state should have full participation"
|
||||
);
|
||||
}
|
||||
|
||||
// Assert that we will run the first arm of process_rewards_and_penalities
|
||||
let epochs_since_finality = state.next_epoch(&spec) - state.finalized_epoch;
|
||||
assert_eq!(
|
||||
|
||||
@@ -1,23 +1,103 @@
|
||||
use block_benching_builder::BlockBenchingBuilder;
|
||||
use criterion::Criterion;
|
||||
use criterion::{criterion_group, criterion_main};
|
||||
use env_logger::{Builder, Env};
|
||||
use log::info;
|
||||
use types::*;
|
||||
|
||||
mod bench_block_processing;
|
||||
mod bench_epoch_processing;
|
||||
mod block_benching_builder;
|
||||
|
||||
pub const VALIDATOR_COUNT: usize = 16_384;
|
||||
|
||||
// `LOG_LEVEL == "debug"` gives logs, but they're very noisy and slow down benching.
|
||||
pub const LOG_LEVEL: &str = "";
|
||||
// `LOG_LEVEL == "info"` gives handy messages.
|
||||
pub const LOG_LEVEL: &str = "info";
|
||||
|
||||
pub fn state_processing(c: &mut Criterion) {
|
||||
/// Build a worst-case block and benchmark processing it.
|
||||
pub fn block_processing_worst_case(c: &mut Criterion) {
|
||||
if LOG_LEVEL != "" {
|
||||
Builder::from_env(Env::default().default_filter_or(LOG_LEVEL)).init();
|
||||
}
|
||||
info!(
|
||||
"Building worst case block bench with {} validators",
|
||||
VALIDATOR_COUNT
|
||||
);
|
||||
|
||||
bench_epoch_processing::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT);
|
||||
bench_block_processing::bench_block_processing_n_validators(c, VALIDATOR_COUNT);
|
||||
// Use the specifications from the Eth2.0 spec.
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
// Create a builder for configuring the block and state for benching.
|
||||
let mut bench_builder = BlockBenchingBuilder::new(VALIDATOR_COUNT, &spec);
|
||||
|
||||
// Set the number of included operations to be maximum (e.g., `MAX_ATTESTATIONS`, etc.)
|
||||
bench_builder.maximize_block_operations(&spec);
|
||||
|
||||
// Set the state and block to be in the last slot of the 4th epoch.
|
||||
let last_slot_of_epoch = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch);
|
||||
bench_builder.set_slot(last_slot_of_epoch, &spec);
|
||||
|
||||
// Build all the state caches so the build times aren't included in the benches.
|
||||
bench_builder.build_caches(&spec);
|
||||
|
||||
// Generate the block and state then run benches.
|
||||
let (block, state) = bench_builder.build(&spec);
|
||||
bench_block_processing::bench_block_processing(
|
||||
c,
|
||||
&block,
|
||||
&state,
|
||||
&spec,
|
||||
&format!("{}_validators/worst_case", VALIDATOR_COUNT),
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(benches, state_processing);
|
||||
/// Build a reasonable-case block and benchmark processing it.
|
||||
pub fn block_processing_reasonable_case(c: &mut Criterion) {
|
||||
info!(
|
||||
"Building reasonable case block bench with {} validators",
|
||||
VALIDATOR_COUNT
|
||||
);
|
||||
|
||||
// Use the specifications from the Eth2.0 spec.
|
||||
let spec = ChainSpec::foundation();
|
||||
|
||||
// Create a builder for configuring the block and state for benching.
|
||||
let mut bench_builder = BlockBenchingBuilder::new(VALIDATOR_COUNT, &spec);
|
||||
|
||||
// Set the number of included operations to what we might expect normally.
|
||||
bench_builder.num_proposer_slashings = 0;
|
||||
bench_builder.num_attester_slashings = 0;
|
||||
bench_builder.num_attestations = (spec.shard_count / spec.slots_per_epoch) as usize;
|
||||
bench_builder.num_deposits = 2;
|
||||
bench_builder.num_exits = 2;
|
||||
bench_builder.num_transfers = 2;
|
||||
|
||||
// Set the state and block to be in the last slot of the 4th epoch.
|
||||
let last_slot_of_epoch = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch);
|
||||
bench_builder.set_slot(last_slot_of_epoch, &spec);
|
||||
|
||||
// Build all the state caches so the build times aren't included in the benches.
|
||||
bench_builder.build_caches(&spec);
|
||||
|
||||
// Generate the block and state then run benches.
|
||||
let (block, state) = bench_builder.build(&spec);
|
||||
bench_block_processing::bench_block_processing(
|
||||
c,
|
||||
&block,
|
||||
&state,
|
||||
&spec,
|
||||
&format!("{}_validators/reasonable_case", VALIDATOR_COUNT),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn state_processing(c: &mut Criterion) {
|
||||
bench_epoch_processing::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT);
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
block_processing_reasonable_case,
|
||||
block_processing_worst_case,
|
||||
state_processing
|
||||
);
|
||||
criterion_main!(benches);
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
use log::info;
|
||||
use types::test_utils::{TestingBeaconBlockBuilder, TestingBeaconStateBuilder};
|
||||
use types::*;
|
||||
|
||||
pub struct BlockBenchingBuilder {
|
||||
pub state_builder: TestingBeaconStateBuilder,
|
||||
pub block_builder: TestingBeaconBlockBuilder,
|
||||
|
||||
pub num_validators: usize,
|
||||
pub num_proposer_slashings: usize,
|
||||
pub num_attester_slashings: usize,
|
||||
pub num_indices_per_slashable_vote: usize,
|
||||
pub num_attestations: usize,
|
||||
pub num_deposits: usize,
|
||||
pub num_exits: usize,
|
||||
pub num_transfers: usize,
|
||||
}
|
||||
|
||||
impl BlockBenchingBuilder {
|
||||
pub fn new(num_validators: usize, spec: &ChainSpec) -> Self {
|
||||
let state_builder =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(num_validators, &spec);
|
||||
let block_builder = TestingBeaconBlockBuilder::new(spec);
|
||||
|
||||
Self {
|
||||
state_builder,
|
||||
block_builder,
|
||||
num_validators: 0,
|
||||
num_proposer_slashings: 0,
|
||||
num_attester_slashings: 0,
|
||||
num_indices_per_slashable_vote: spec.max_indices_per_slashable_vote as usize,
|
||||
num_attestations: 0,
|
||||
num_deposits: 0,
|
||||
num_exits: 0,
|
||||
num_transfers: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maximize_block_operations(&mut self, spec: &ChainSpec) {
|
||||
self.num_proposer_slashings = spec.max_proposer_slashings as usize;
|
||||
self.num_attester_slashings = spec.max_attester_slashings as usize;
|
||||
self.num_indices_per_slashable_vote = spec.max_indices_per_slashable_vote as usize;
|
||||
self.num_attestations = spec.max_attestations as usize;
|
||||
self.num_deposits = spec.max_deposits as usize;
|
||||
self.num_exits = spec.max_voluntary_exits as usize;
|
||||
self.num_transfers = spec.max_transfers as usize;
|
||||
}
|
||||
|
||||
pub fn set_slot(&mut self, slot: Slot, spec: &ChainSpec) {
|
||||
self.state_builder.teleport_to_slot(slot, &spec);
|
||||
}
|
||||
|
||||
pub fn build_caches(&mut self, spec: &ChainSpec) {
|
||||
// Builds all caches; benches will not contain shuffling/committee building times.
|
||||
self.state_builder.build_caches(&spec).unwrap();
|
||||
}
|
||||
|
||||
pub fn build(mut self, spec: &ChainSpec) -> (BeaconBlock, BeaconState) {
|
||||
let (mut state, keypairs) = self.state_builder.build();
|
||||
let builder = &mut self.block_builder;
|
||||
|
||||
builder.set_slot(state.slot);
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap();
|
||||
let keypair = &keypairs[proposer_index];
|
||||
|
||||
builder.set_randao_reveal(&keypair.sk, &state.fork, spec);
|
||||
|
||||
// Used as a stream of validator indices for use in slashings, exits, etc.
|
||||
let mut validators_iter = (0..keypairs.len() as u64).into_iter();
|
||||
|
||||
// Insert `ProposerSlashing` objects.
|
||||
for _ in 0..self.num_proposer_slashings {
|
||||
let validator_index = validators_iter.next().expect("Insufficient validators.");
|
||||
|
||||
builder.insert_proposer_slashing(
|
||||
validator_index,
|
||||
&keypairs[validator_index as usize].sk,
|
||||
&state.fork,
|
||||
spec,
|
||||
);
|
||||
}
|
||||
info!(
|
||||
"Inserted {} proposer slashings.",
|
||||
builder.block.body.proposer_slashings.len()
|
||||
);
|
||||
|
||||
// Insert `AttesterSlashing` objects
|
||||
for _ in 0..self.num_attester_slashings {
|
||||
let mut attesters: Vec<u64> = vec![];
|
||||
let mut secret_keys: Vec<&SecretKey> = vec![];
|
||||
|
||||
for _ in 0..self.num_indices_per_slashable_vote {
|
||||
let validator_index = validators_iter.next().expect("Insufficient validators.");
|
||||
|
||||
attesters.push(validator_index);
|
||||
secret_keys.push(&keypairs[validator_index as usize].sk);
|
||||
}
|
||||
|
||||
builder.insert_attester_slashing(&attesters, &secret_keys, &state.fork, spec);
|
||||
}
|
||||
info!(
|
||||
"Inserted {} attester slashings.",
|
||||
builder.block.body.attester_slashings.len()
|
||||
);
|
||||
|
||||
// Insert `Attestation` objects.
|
||||
let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect();
|
||||
builder
|
||||
.insert_attestations(
|
||||
&state,
|
||||
&all_secret_keys,
|
||||
self.num_attestations as usize,
|
||||
spec,
|
||||
)
|
||||
.unwrap();
|
||||
info!(
|
||||
"Inserted {} attestations.",
|
||||
builder.block.body.attestations.len()
|
||||
);
|
||||
|
||||
// Insert `Deposit` objects.
|
||||
for i in 0..self.num_deposits {
|
||||
builder.insert_deposit(
|
||||
32_000_000_000,
|
||||
state.deposit_index + (i as u64),
|
||||
&state,
|
||||
spec,
|
||||
);
|
||||
}
|
||||
info!("Inserted {} deposits.", builder.block.body.deposits.len());
|
||||
|
||||
// Insert the maximum possible number of `Exit` objects.
|
||||
for _ in 0..self.num_exits {
|
||||
let validator_index = validators_iter.next().expect("Insufficient validators.");
|
||||
|
||||
builder.insert_exit(
|
||||
&state,
|
||||
validator_index,
|
||||
&keypairs[validator_index as usize].sk,
|
||||
spec,
|
||||
);
|
||||
}
|
||||
info!(
|
||||
"Inserted {} exits.",
|
||||
builder.block.body.voluntary_exits.len()
|
||||
);
|
||||
|
||||
// Insert the maximum possible number of `Transfer` objects.
|
||||
for _ in 0..self.num_transfers {
|
||||
let validator_index = validators_iter.next().expect("Insufficient validators.");
|
||||
|
||||
// Manually set the validator to be withdrawn.
|
||||
state.validator_registry[validator_index as usize].withdrawable_epoch =
|
||||
state.previous_epoch(spec);
|
||||
|
||||
builder.insert_transfer(
|
||||
&state,
|
||||
validator_index,
|
||||
validator_index,
|
||||
1,
|
||||
keypairs[validator_index as usize].clone(),
|
||||
spec,
|
||||
);
|
||||
}
|
||||
info!("Inserted {} transfers.", builder.block.body.transfers.len());
|
||||
|
||||
let mut block = self.block_builder.build(&keypair.sk, &state.fork, spec);
|
||||
|
||||
// Set the eth1 data to be different from the state.
|
||||
block.eth1_data.block_hash = Hash256::from_slice(&vec![42; 32]);
|
||||
|
||||
(block, state)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,708 @@
|
||||
title: Sanity tests -- small config -- 32 validators
|
||||
summary: Basic sanity checks from phase 0 spec pythonization using a small state configuration and 32 validators.
|
||||
All tests are run with `verify_signatures` as set to False.
|
||||
Tests generated via https://github.com/ethereum/research/blob/master/spec_pythonizer/sanity_check.py
|
||||
test_suite: beacon_state
|
||||
fork: phase0-0.5.0
|
||||
test_cases:
|
||||
- name: test_empty_block_transition
|
||||
config:
|
||||
SHARD_COUNT: 8
|
||||
TARGET_COMMITTEE_SIZE: 4
|
||||
MAX_BALANCE_CHURN_QUOTIENT: 32
|
||||
MAX_INDICES_PER_SLASHABLE_VOTE: 4096
|
||||
MAX_EXIT_DEQUEUES_PER_EPOCH: 4
|
||||
SHUFFLE_ROUND_COUNT: 90
|
||||
DEPOSIT_CONTRACT_TREE_DEPTH: 32
|
||||
MIN_DEPOSIT_AMOUNT: 1000000000
|
||||
MAX_DEPOSIT_AMOUNT: 32000000000
|
||||
FORK_CHOICE_BALANCE_INCREMENT: 1000000000
|
||||
EJECTION_BALANCE: 16000000000
|
||||
GENESIS_FORK_VERSION: 0
|
||||
GENESIS_SLOT: 4294967296
|
||||
GENESIS_EPOCH: 536870912
|
||||
GENESIS_START_SHARD: 0
|
||||
BLS_WITHDRAWAL_PREFIX_BYTE: '0x00'
|
||||
SECONDS_PER_SLOT: 6
|
||||
MIN_ATTESTATION_INCLUSION_DELAY: 2
|
||||
SLOTS_PER_EPOCH: 8
|
||||
MIN_SEED_LOOKAHEAD: 1
|
||||
ACTIVATION_EXIT_DELAY: 4
|
||||
EPOCHS_PER_ETH1_VOTING_PERIOD: 16
|
||||
SLOTS_PER_HISTORICAL_ROOT: 64
|
||||
MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256
|
||||
PERSISTENT_COMMITTEE_PERIOD: 2048
|
||||
LATEST_RANDAO_MIXES_LENGTH: 64
|
||||
LATEST_ACTIVE_INDEX_ROOTS_LENGTH: 64
|
||||
LATEST_SLASHED_EXIT_LENGTH: 64
|
||||
BASE_REWARD_QUOTIENT: 32
|
||||
WHISTLEBLOWER_REWARD_QUOTIENT: 512
|
||||
ATTESTATION_INCLUSION_REWARD_QUOTIENT: 8
|
||||
INACTIVITY_PENALTY_QUOTIENT: 16777216
|
||||
MIN_PENALTY_QUOTIENT: 32
|
||||
MAX_PROPOSER_SLASHINGS: 16
|
||||
MAX_ATTESTER_SLASHINGS: 1
|
||||
MAX_ATTESTATIONS: 128
|
||||
MAX_DEPOSITS: 16
|
||||
MAX_VOLUNTARY_EXITS: 16
|
||||
MAX_TRANSFERS: 16
|
||||
DOMAIN_BEACON_BLOCK: 0
|
||||
DOMAIN_RANDAO: 1
|
||||
DOMAIN_ATTESTATION: 2
|
||||
DOMAIN_DEPOSIT: 3
|
||||
DOMAIN_VOLUNTARY_EXIT: 4
|
||||
DOMAIN_TRANSFER: 5
|
||||
verify_signatures: false
|
||||
initial_state:
|
||||
slot: 4294967296
|
||||
genesis_time: 0
|
||||
fork:
|
||||
previous_version: '0x00000000'
|
||||
current_version: '0x00000000'
|
||||
epoch: 536870912
|
||||
validator_registry:
|
||||
- pubkey: '0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000001'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xa572cbea904d67468808c8eb50a9450c9721db309128012543902d0ac358a62ae28f75bb8f1c7c42c39a8c5529bf0f4e'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000002'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x89ece308f9d1f0131765212deca99697b112d61f9be9a5f1f3780a51335b3ff981747a0b2ca2179b96d2c0c9024e5224'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000003'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xac9b60d5afcbd5663a8a44b7c5a02f19e9a77ab0a35bd65809bb5c67ec582c897feb04decc694b13e08587f3ff9b5b60'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000004'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xb0e7791fb972fe014159aa33a98622da3cdc98ff707965e536d8636b5fcc5ac7a91a8c46e59a00dca575af0f18fb13dc'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000005'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xa6e82f6da4520f85c5d27d8f329eccfa05944fd1096b20734c894966d12a9e2a9a9744529d7212d33883113a0cadb909'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000006'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xb928f3beb93519eecf0145da903b40a4c97dca00b21f12ac0df3be9116ef2ef27b2ae6bcd4c5bc2d54ef5a70627efcb7'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000007'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xa85ae765588126f5e860d019c0e26235f567a9c0c0b2d8ff30f3e8d436b1082596e5e7462d20f5be3764fd473e57f9cf'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000008'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x99cdf3807146e68e041314ca93e1fee0991224ec2a74beb2866816fd0826ce7b6263ee31e953a86d1b72cc2215a57793'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000009'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xaf81da25ecf1c84b577fefbedd61077a81dc43b00304015b2b596ab67f00e41c86bb00ebd0f90d4b125eb0539891aeed'
|
||||
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000000a'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x80fd75ebcc0a21649e3177bcce15426da0e4f25d6828fbf4038d4d7ed3bd4421de3ef61d70f794687b12b2d571971a55'
|
||||
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000000b'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x8345dd80ffef0eaec8920e39ebb7f5e9ae9c1d6179e9129b705923df7830c67f3690cbc48649d4079eadf5397339580c'
|
||||
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000000c'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x851f8a0b82a6d86202a61cbc3b0f3db7d19650b914587bde4715ccd372e1e40cab95517779d840416e1679c84a6db24e'
|
||||
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000000d'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x99bef05aaba1ea467fcbc9c420f5e3153c9d2b5f9bf2c7e2e7f6946f854043627b45b008607b9a9108bb96f3c1c089d3'
|
||||
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000000e'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x8d9e19b3f4c7c233a6112e5397309f9812a4f61f754f11dd3dcb8b07d55a7b1dfea65f19a1488a14fef9a41495083582'
|
||||
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000000f'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xa73eb991aa22cdb794da6fcde55a427f0a4df5a4a70de23a988b5e5fc8c4d844f66d990273267a54dd21579b7ba6a086'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000010'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xb098f178f84fc753a76bb63709e9be91eec3ff5f7f3a5f4836f34fe8a1a6d6c5578d8fd820573cef3a01e2bfef3eaf3a'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000011'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x9252a4ac3529f8b2b6e8189b95a60b8865f07f9a9b73f98d5df708511d3f68632c4c7d1e2b03e6b1d1e2c01839752ada'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000012'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xb271205227c7aa27f45f20b3ba380dfea8b51efae91fd32e552774c99e2a1237aa59c0c43f52aad99bba3783ea2f36a4'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000013'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xa272e9d1d50a4aea7d8f0583948090d0888be5777f2846800b8281139cd4aa9eee05f89b069857a3e77ccfaae1615f9c'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000014'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x9780e853f8ce7eda772c6691d25e220ca1d2ab0db51a7824b700620f7ac94c06639e91c98bb6abd78128f0ec845df8ef'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000015'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xab48aa2cc6f4a0bb63b5d67be54ac3aed10326dda304c5aeb9e942b40d6e7610478377680ab90e092ef1895e62786008'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000016'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x8c8b694b04d98a749a0763c72fc020ef61b2bb3f63ebb182cb2e568f6a8b9ca3ae013ae78317599e7e7ba2a528ec754a'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000017'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x9717182463fbe215168e6762abcbb55c5c65290f2b5a2af616f8a6f50d625b46164178a11622d21913efdfa4b800648d'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000018'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xacb58c81ae0cae2e9d4d446b730922239923c345744eee58efaadb36e9a0925545b18a987acf0bad469035b291e37269'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000019'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x81ccc19e3b938ec2405099e90022a4218baa5082a3ca0974b24be0bc8b07e5fffaed64bef0d02c4dbfb6a307829afc5c'
|
||||
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000001a'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xab83dfefb120fab7665a607d749ef1765fbb3cc0ba5827a20a135402c09d987c701ddb5b60f0f5495026817e8ab6ea2e'
|
||||
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000001b'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xb6ad11e5d15f77c1143b1697344911b9c590110fdd8dd09df2e58bfd757269169deefe8be3544d4e049fb3776fb0bcfb'
|
||||
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000001c'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0x8515e7f61ca0470e165a44d247a23f17f24bf6e37185467bedb7981c1003ea70bbec875703f793dd8d11e56afa7f74ba'
|
||||
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000001d'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xad84464b3966ec5bede84aa487facfca7823af383715078da03b387cc2f5d5597cdd7d025aa07db00a38b953bdeb6e3f'
|
||||
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000001e'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xb29043a7273d0a2dbc2b747dcf6a5eccbd7ccb44b2d72e985537b117929bc3fd3a99001481327788ad040b4077c47c0d'
|
||||
withdrawal_credentials: '0x000000000000000000000000000000000000000000000000000000000000001f'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
- pubkey: '0xa72841987e4f219d54f2b6a9eac5fe6e78704644753c3579e776a3691bc123743f8c63770ed0f72a71e9e964dbf58f43'
|
||||
withdrawal_credentials: '0x0000000000000000000000000000000000000000000000000000000000000020'
|
||||
activation_epoch: 536870912
|
||||
exit_epoch: 18446744073709551615
|
||||
withdrawable_epoch: 18446744073709551615
|
||||
initiated_exit: false
|
||||
slashed: false
|
||||
validator_balances:
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
- 32000000000
|
||||
validator_registry_update_epoch: 536870912
|
||||
latest_randao_mixes:
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
previous_shuffling_start_shard: 0
|
||||
current_shuffling_start_shard: 0
|
||||
previous_shuffling_epoch: 536870912
|
||||
current_shuffling_epoch: 536870912
|
||||
previous_shuffling_seed: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
current_shuffling_seed: '0x7a81d831e99dc63f9f10d4abce84c26473d4c2f65ec4acf9000684059473b072'
|
||||
previous_epoch_attestations: []
|
||||
current_epoch_attestations: []
|
||||
previous_justified_epoch: 536870912
|
||||
current_justified_epoch: 536870912
|
||||
previous_justified_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
current_justified_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
justification_bitfield: 0
|
||||
finalized_epoch: 536870912
|
||||
finalized_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
latest_crosslinks:
|
||||
- epoch: 536870912
|
||||
crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- epoch: 536870912
|
||||
crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- epoch: 536870912
|
||||
crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- epoch: 536870912
|
||||
crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- epoch: 536870912
|
||||
crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- epoch: 536870912
|
||||
crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- epoch: 536870912
|
||||
crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- epoch: 536870912
|
||||
crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
latest_block_roots:
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
latest_state_roots:
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
- '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
latest_active_index_roots:
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
- '0x429a7560eb31fa5d1192496997a78ffc590e70f5b39220abff4420298061501a'
|
||||
latest_slashed_balances:
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
latest_block_header:
|
||||
slot: 4294967296
|
||||
previous_block_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
state_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
block_body_root: '0x13f2001ff0ee4a528b3c43f63d70a997aefca990ed8eada2223ee6ec3807f7cc'
|
||||
signature: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
|
||||
historical_roots: []
|
||||
latest_eth1_data:
|
||||
deposit_root: '0x826d25bfcb9161aabc799844c5176f7b3444dc5288856f65e0b8060560488912'
|
||||
block_hash: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
eth1_data_votes: []
|
||||
deposit_index: 32
|
||||
blocks:
|
||||
- slot: 4294967297
|
||||
previous_block_root: '0x2befbd4b4fe8c91f3059082c8048e3376a9b7fb309e93044fac32b7cc8849773'
|
||||
state_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
body:
|
||||
randao_reveal: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
|
||||
eth1_data:
|
||||
deposit_root: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
block_hash: '0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
proposer_slashings: []
|
||||
attester_slashings: []
|
||||
attestations: []
|
||||
deposits: []
|
||||
voluntary_exits: []
|
||||
transfers: []
|
||||
signature: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
|
||||
expected_state:
|
||||
slot: 4294967297
|
||||
@@ -0,0 +1,22 @@
|
||||
use types::{BeaconStateError as Error, *};
|
||||
|
||||
/// Exit the validator of the given `index`.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn exit_validator(
|
||||
state: &mut BeaconState,
|
||||
validator_index: usize,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
if validator_index >= state.validator_registry.len() {
|
||||
return Err(Error::UnknownValidator);
|
||||
}
|
||||
|
||||
let delayed_epoch = state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec);
|
||||
|
||||
if state.validator_registry[validator_index].exit_epoch > delayed_epoch {
|
||||
state.validator_registry[validator_index].exit_epoch = delayed_epoch;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mod exit_validator;
|
||||
mod slash_validator;
|
||||
mod verify_bitfield;
|
||||
|
||||
pub use exit_validator::exit_validator;
|
||||
pub use slash_validator::slash_validator;
|
||||
pub use verify_bitfield::verify_bitfield_length;
|
||||
@@ -0,0 +1,62 @@
|
||||
use crate::common::exit_validator;
|
||||
use types::{BeaconStateError as Error, *};
|
||||
|
||||
/// Slash the validator with index ``index``.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn slash_validator(
|
||||
state: &mut BeaconState,
|
||||
validator_index: usize,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let current_epoch = state.current_epoch(spec);
|
||||
|
||||
if (validator_index >= state.validator_registry.len())
|
||||
| (validator_index >= state.validator_balances.len())
|
||||
{
|
||||
return Err(BeaconStateError::UnknownValidator);
|
||||
}
|
||||
|
||||
let validator = &state.validator_registry[validator_index];
|
||||
|
||||
let effective_balance = state.get_effective_balance(validator_index, spec)?;
|
||||
|
||||
// A validator that is withdrawn cannot be slashed.
|
||||
//
|
||||
// This constraint will be lifted in Phase 0.
|
||||
if state.slot
|
||||
>= validator
|
||||
.withdrawable_epoch
|
||||
.start_slot(spec.slots_per_epoch)
|
||||
{
|
||||
return Err(Error::ValidatorIsWithdrawable);
|
||||
}
|
||||
|
||||
exit_validator(state, validator_index, spec)?;
|
||||
|
||||
state.set_slashed_balance(
|
||||
current_epoch,
|
||||
state.get_slashed_balance(current_epoch, spec)? + effective_balance,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
let whistleblower_index =
|
||||
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
|
||||
let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient;
|
||||
|
||||
safe_add_assign!(
|
||||
state.validator_balances[whistleblower_index as usize],
|
||||
whistleblower_reward
|
||||
);
|
||||
safe_sub_assign!(
|
||||
state.validator_balances[validator_index],
|
||||
whistleblower_reward
|
||||
);
|
||||
|
||||
state.validator_registry[validator_index].slashed = true;
|
||||
|
||||
state.validator_registry[validator_index].withdrawable_epoch =
|
||||
current_epoch + Epoch::from(spec.latest_slashed_exit_length);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
use crate::*;
|
||||
use types::*;
|
||||
|
||||
/// Verify ``bitfield`` against the ``committee_size``.
|
||||
///
|
||||
/// Is title `verify_bitfield` in spec.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn verify_bitfield_length(bitfield: &Bitfield, committee_size: usize) -> bool {
|
||||
if bitfield.num_bytes() != ((committee_size + 7) / 8) {
|
||||
return false;
|
||||
@@ -0,0 +1,58 @@
|
||||
use super::per_block_processing::{errors::BlockProcessingError, process_deposits};
|
||||
use ssz::TreeHash;
|
||||
use types::*;
|
||||
|
||||
pub enum GenesisError {
|
||||
BlockProcessingError(BlockProcessingError),
|
||||
BeaconStateError(BeaconStateError),
|
||||
}
|
||||
|
||||
/// Returns the genesis `BeaconState`
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn get_genesis_state(
|
||||
genesis_validator_deposits: &[Deposit],
|
||||
genesis_time: u64,
|
||||
genesis_eth1_data: Eth1Data,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BlockProcessingError> {
|
||||
// Get the genesis `BeaconState`
|
||||
let mut state = BeaconState::genesis(genesis_time, genesis_eth1_data, spec);
|
||||
|
||||
// Process genesis deposits.
|
||||
process_deposits(&mut state, genesis_validator_deposits, spec)?;
|
||||
|
||||
// Process genesis activations.
|
||||
for i in 0..state.validator_registry.len() {
|
||||
if state.get_effective_balance(i, spec)? >= spec.max_deposit_amount {
|
||||
state.validator_registry[i].activation_epoch = spec.genesis_epoch;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the current epoch cache is built.
|
||||
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
|
||||
|
||||
// Set all the active index roots to be the genesis active index root.
|
||||
let active_validator_indices = state
|
||||
.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
|
||||
.to_vec();
|
||||
let genesis_active_index_root = Hash256::from_slice(&active_validator_indices.hash_tree_root());
|
||||
state.fill_active_index_roots_with(genesis_active_index_root, spec);
|
||||
|
||||
// Generate the current shuffling seed.
|
||||
state.current_shuffling_seed = state.generate_seed(spec.genesis_epoch, spec)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl From<BlockProcessingError> for GenesisError {
|
||||
fn from(e: BlockProcessingError) -> GenesisError {
|
||||
GenesisError::BlockProcessingError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for GenesisError {
|
||||
fn from(e: BeaconStateError) -> GenesisError {
|
||||
GenesisError::BeaconStateError(e)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
pub mod common;
|
||||
pub mod get_genesis_state;
|
||||
pub mod per_block_processing;
|
||||
pub mod per_epoch_processing;
|
||||
pub mod per_slot_processing;
|
||||
|
||||
pub use get_genesis_state::get_genesis_state;
|
||||
pub use per_block_processing::{
|
||||
errors::{BlockInvalid, BlockProcessingError},
|
||||
per_block_processing, per_block_processing_without_verifying_block_signature,
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use self::verify_proposer_slashing::verify_proposer_slashing;
|
||||
use crate::common::slash_validator;
|
||||
use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex};
|
||||
use hashing::hash;
|
||||
use rayon::prelude::*;
|
||||
use ssz::{ssz_encode, SignedRoot, TreeHash};
|
||||
use ssz::{SignedRoot, TreeHash};
|
||||
use types::*;
|
||||
|
||||
pub use self::verify_attester_slashing::{
|
||||
gather_attester_slashing_indices, verify_attester_slashing,
|
||||
};
|
||||
pub use validate_attestation::{validate_attestation, validate_attestation_without_signature};
|
||||
pub use verify_deposit::{
|
||||
build_public_key_hashmap, get_existing_validator_index, verify_deposit, verify_deposit_index,
|
||||
};
|
||||
pub use verify_deposit::{get_existing_validator_index, verify_deposit, verify_deposit_index};
|
||||
pub use verify_exit::verify_exit;
|
||||
pub use verify_slashable_attestation::verify_slashable_attestation;
|
||||
pub use verify_transfer::{execute_transfer, verify_transfer};
|
||||
@@ -35,7 +33,7 @@ const VERIFY_DEPOSIT_MERKLE_PROOFS: bool = false;
|
||||
/// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise
|
||||
/// returns an error describing why the block was invalid or how the function failed to execute.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn per_block_processing(
|
||||
state: &mut BeaconState,
|
||||
block: &BeaconBlock,
|
||||
@@ -50,7 +48,7 @@ pub fn per_block_processing(
|
||||
/// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise
|
||||
/// returns an error describing why the block was invalid or how the function failed to execute.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn per_block_processing_without_verifying_block_signature(
|
||||
state: &mut BeaconState,
|
||||
block: &BeaconBlock,
|
||||
@@ -65,25 +63,24 @@ pub fn per_block_processing_without_verifying_block_signature(
|
||||
/// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise
|
||||
/// returns an error describing why the block was invalid or how the function failed to execute.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
fn per_block_processing_signature_optional(
|
||||
mut state: &mut BeaconState,
|
||||
block: &BeaconBlock,
|
||||
should_verify_block_signature: bool,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
// Verify that `block.slot == state.slot`.
|
||||
verify!(block.slot == state.slot, Invalid::StateSlotMismatch);
|
||||
process_block_header(state, block, spec)?;
|
||||
|
||||
// Ensure the current and previous epoch cache is built.
|
||||
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
|
||||
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
|
||||
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
|
||||
|
||||
if should_verify_block_signature {
|
||||
verify_block_signature(&state, &block, &spec)?;
|
||||
}
|
||||
process_randao(&mut state, &block, &spec)?;
|
||||
process_eth1_data(&mut state, &block.eth1_data)?;
|
||||
process_eth1_data(&mut state, &block.body.eth1_data)?;
|
||||
process_proposer_slashings(&mut state, &block.body.proposer_slashings, spec)?;
|
||||
process_attester_slashings(&mut state, &block.body.attester_slashings, spec)?;
|
||||
process_attestations(&mut state, &block.body.attestations, spec)?;
|
||||
@@ -94,33 +91,50 @@ fn per_block_processing_signature_optional(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Processes the block header.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn process_block_header(
|
||||
state: &mut BeaconState,
|
||||
block: &BeaconBlock,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
verify!(block.slot == state.slot, Invalid::StateSlotMismatch);
|
||||
|
||||
// NOTE: this is not to spec. I think spec is broken. See:
|
||||
//
|
||||
// https://github.com/ethereum/eth2.0-specs/issues/797
|
||||
verify!(
|
||||
block.previous_block_root == *state.get_block_root(state.slot - 1, spec)?,
|
||||
Invalid::ParentBlockRootMismatch
|
||||
);
|
||||
|
||||
state.latest_block_header = block.temporary_block_header(spec);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verifies the signature of a block.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn verify_block_signature(
|
||||
state: &BeaconState,
|
||||
block: &BeaconBlock,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let block_proposer =
|
||||
&state.validator_registry[state.get_beacon_proposer_index(block.slot, spec)?];
|
||||
let block_proposer = &state.validator_registry
|
||||
[state.get_beacon_proposer_index(block.slot, RelativeEpoch::Current, spec)?];
|
||||
|
||||
let proposal = Proposal {
|
||||
slot: block.slot,
|
||||
shard: spec.beacon_chain_shard_number,
|
||||
block_root: Hash256::from_slice(&block.signed_root()[..]),
|
||||
signature: block.signature.clone(),
|
||||
};
|
||||
let domain = spec.get_domain(
|
||||
block.slot.epoch(spec.slots_per_epoch),
|
||||
Domain::Proposal,
|
||||
Domain::BeaconBlock,
|
||||
&state.fork,
|
||||
);
|
||||
|
||||
verify!(
|
||||
proposal
|
||||
block
|
||||
.signature
|
||||
.verify(&proposal.signed_root()[..], domain, &block_proposer.pubkey),
|
||||
.verify(&block.signed_root()[..], domain, &block_proposer.pubkey),
|
||||
Invalid::BadSignature
|
||||
);
|
||||
|
||||
@@ -130,21 +144,18 @@ pub fn verify_block_signature(
|
||||
/// Verifies the `randao_reveal` against the block's proposer pubkey and updates
|
||||
/// `state.latest_randao_mixes`.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn process_randao(
|
||||
state: &mut BeaconState,
|
||||
block: &BeaconBlock,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
// Let `proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]`.
|
||||
let block_proposer =
|
||||
&state.validator_registry[state.get_beacon_proposer_index(block.slot, spec)?];
|
||||
let block_proposer = &state.validator_registry
|
||||
[state.get_beacon_proposer_index(block.slot, RelativeEpoch::Current, spec)?];
|
||||
|
||||
// Verify that `bls_verify(pubkey=proposer.pubkey,
|
||||
// message_hash=hash_tree_root(get_current_epoch(state)), signature=block.randao_reveal,
|
||||
// domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO))`.
|
||||
// Verify the RANDAO is a valid signature of the proposer.
|
||||
verify!(
|
||||
block.randao_reveal.verify(
|
||||
block.body.randao_reveal.verify(
|
||||
&state.current_epoch(spec).hash_tree_root()[..],
|
||||
spec.get_domain(
|
||||
block.slot.epoch(spec.slots_per_epoch),
|
||||
@@ -156,21 +167,23 @@ pub fn process_randao(
|
||||
Invalid::BadRandaoSignature
|
||||
);
|
||||
|
||||
// Update the state's RANDAO mix with the one revealed in the block.
|
||||
update_randao(state, &block.randao_reveal, spec)?;
|
||||
// Update the current epoch RANDAO mix.
|
||||
state.update_randao_mix(state.current_epoch(spec), &block.body.randao_reveal, spec)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update the `state.eth1_data_votes` based upon the `eth1_data` provided.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn process_eth1_data(state: &mut BeaconState, eth1_data: &Eth1Data) -> Result<(), Error> {
|
||||
// Either increment the eth1_data vote count, or add a new eth1_data.
|
||||
// Attempt to find a `Eth1DataVote` with matching `Eth1Data`.
|
||||
let matching_eth1_vote_index = state
|
||||
.eth1_data_votes
|
||||
.iter()
|
||||
.position(|vote| vote.eth1_data == *eth1_data);
|
||||
|
||||
// If a vote exists, increment it's `vote_count`. Otherwise, create a new `Eth1DataVote`.
|
||||
if let Some(index) = matching_eth1_vote_index {
|
||||
state.eth1_data_votes[index].vote_count += 1;
|
||||
} else {
|
||||
@@ -183,46 +196,12 @@ pub fn process_eth1_data(state: &mut BeaconState, eth1_data: &Eth1Data) -> Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the present randao mix.
|
||||
///
|
||||
/// Set `state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] =
|
||||
/// xor(get_randao_mix(state, get_current_epoch(state)), hash(block.randao_reveal))`.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn update_randao(
|
||||
state: &mut BeaconState,
|
||||
reveal: &Signature,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
let hashed_reveal = {
|
||||
let encoded_signature = ssz_encode(reveal);
|
||||
Hash256::from_slice(&hash(&encoded_signature[..])[..])
|
||||
};
|
||||
|
||||
let current_epoch = state.slot.epoch(spec.slots_per_epoch);
|
||||
|
||||
let current_mix = state
|
||||
.get_randao_mix(current_epoch, spec)
|
||||
.ok_or_else(|| BeaconStateError::InsufficientRandaoMixes)?;
|
||||
|
||||
let new_mix = *current_mix ^ hashed_reveal;
|
||||
|
||||
let index = current_epoch.as_usize() % spec.latest_randao_mixes_length;
|
||||
|
||||
if index < state.latest_randao_mixes.len() {
|
||||
state.latest_randao_mixes[index] = new_mix;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(BeaconStateError::InsufficientRandaoMixes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates each `ProposerSlashing` and updates the state, short-circuiting on an invalid object.
|
||||
///
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn process_proposer_slashings(
|
||||
state: &mut BeaconState,
|
||||
proposer_slashings: &[ProposerSlashing],
|
||||
@@ -242,8 +221,9 @@ pub fn process_proposer_slashings(
|
||||
.map_err(|e| e.into_with_index(i))
|
||||
})?;
|
||||
|
||||
// Update the state.
|
||||
for proposer_slashing in proposer_slashings {
|
||||
state.slash_validator(proposer_slashing.proposer_index as usize, spec)?;
|
||||
slash_validator(state, proposer_slashing.proposer_index as usize, spec)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -254,7 +234,7 @@ pub fn process_proposer_slashings(
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn process_attester_slashings(
|
||||
state: &mut BeaconState,
|
||||
attester_slashings: &[AttesterSlashing],
|
||||
@@ -296,11 +276,11 @@ pub fn process_attester_slashings(
|
||||
)
|
||||
.map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing)
|
||||
let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing, spec)
|
||||
.map_err(|e| e.into_with_index(i))?;
|
||||
|
||||
for i in slashable_indices {
|
||||
state.slash_validator(i as usize, spec)?;
|
||||
slash_validator(state, i as usize, spec)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +292,7 @@ pub fn process_attester_slashings(
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn process_attestations(
|
||||
state: &mut BeaconState,
|
||||
attestations: &[Attestation],
|
||||
@@ -342,7 +322,14 @@ pub fn process_attestations(
|
||||
custody_bitfield: attestation.custody_bitfield.clone(),
|
||||
inclusion_slot: state.slot,
|
||||
};
|
||||
state.latest_attestations.push(pending_attestation);
|
||||
|
||||
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
||||
|
||||
if attestation_epoch == state.current_epoch(spec) {
|
||||
state.current_epoch_attestations.push(pending_attestation)
|
||||
} else if attestation_epoch == state.previous_epoch(spec) {
|
||||
state.previous_epoch_attestations.push(pending_attestation)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -353,7 +340,7 @@ pub fn process_attestations(
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn process_deposits(
|
||||
state: &mut BeaconState,
|
||||
deposits: &[Deposit],
|
||||
@@ -401,7 +388,7 @@ pub fn process_deposits(
|
||||
// Create a new validator.
|
||||
let validator = Validator {
|
||||
pubkey: deposit_input.pubkey.clone(),
|
||||
withdrawal_credentials: deposit_input.withdrawal_credentials.clone(),
|
||||
withdrawal_credentials: deposit_input.withdrawal_credentials,
|
||||
activation_epoch: spec.far_future_epoch,
|
||||
exit_epoch: spec.far_future_epoch,
|
||||
withdrawable_epoch: spec.far_future_epoch,
|
||||
@@ -423,7 +410,7 @@ pub fn process_deposits(
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn process_exits(
|
||||
state: &mut BeaconState,
|
||||
voluntary_exits: &[VoluntaryExit],
|
||||
@@ -455,7 +442,7 @@ pub fn process_exits(
|
||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||
/// an `Err` describing the invalid object or cause of failure.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn process_transfers(
|
||||
state: &mut BeaconState,
|
||||
transfers: &[Transfer],
|
||||
|
||||
@@ -67,6 +67,7 @@ impl_from_beacon_state_error!(BlockProcessingError);
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum BlockInvalid {
|
||||
StateSlotMismatch,
|
||||
ParentBlockRootMismatch,
|
||||
BadSignature,
|
||||
BadRandaoSignature,
|
||||
MaxAttestationsExceeded,
|
||||
@@ -112,45 +113,53 @@ pub enum AttestationValidationError {
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum AttestationInvalid {
|
||||
/// Attestation references a pre-genesis slot.
|
||||
///
|
||||
/// (genesis_slot, attestation_slot)
|
||||
PreGenesis(Slot, Slot),
|
||||
PreGenesis { genesis: Slot, attestation: Slot },
|
||||
/// Attestation included before the inclusion delay.
|
||||
///
|
||||
/// (state_slot, inclusion_delay, attestation_slot)
|
||||
IncludedTooEarly(Slot, u64, Slot),
|
||||
IncludedTooEarly {
|
||||
state: Slot,
|
||||
delay: u64,
|
||||
attestation: Slot,
|
||||
},
|
||||
/// Attestation slot is too far in the past to be included in a block.
|
||||
///
|
||||
/// (state_slot, attestation_slot)
|
||||
IncludedTooLate(Slot, Slot),
|
||||
IncludedTooLate { state: Slot, attestation: Slot },
|
||||
/// Attestation justified epoch does not match the states current or previous justified epoch.
|
||||
///
|
||||
/// (attestation_justified_epoch, state_epoch, used_previous_epoch)
|
||||
WrongJustifiedEpoch(Epoch, Epoch, bool),
|
||||
/// `is_current` is `true` if the attestation was compared to the
|
||||
/// `state.current_justified_epoch`, `false` if compared to `state.previous_justified_epoch`.
|
||||
WrongJustifiedEpoch {
|
||||
state: Epoch,
|
||||
attestation: Epoch,
|
||||
is_current: bool,
|
||||
},
|
||||
/// Attestation justified epoch root does not match root known to the state.
|
||||
///
|
||||
/// (state_justified_root, attestation_justified_root)
|
||||
WrongJustifiedRoot(Hash256, Hash256),
|
||||
/// `is_current` is `true` if the attestation was compared to the
|
||||
/// `state.current_justified_epoch`, `false` if compared to `state.previous_justified_epoch`.
|
||||
WrongJustifiedRoot {
|
||||
state: Hash256,
|
||||
attestation: Hash256,
|
||||
is_current: bool,
|
||||
},
|
||||
/// Attestation crosslink root does not match the state crosslink root for the attestations
|
||||
/// slot.
|
||||
BadLatestCrosslinkRoot,
|
||||
BadPreviousCrosslink,
|
||||
/// The custody bitfield has some bits set `true`. This is not allowed in phase 0.
|
||||
CustodyBitfieldHasSetBits,
|
||||
/// There are no set bits on the attestation -- an attestation must be signed by at least one
|
||||
/// validator.
|
||||
AggregationBitfieldIsEmpty,
|
||||
/// The custody bitfield length is not the smallest possible size to represent the committee.
|
||||
///
|
||||
/// (committee_len, bitfield_len)
|
||||
BadCustodyBitfieldLength(usize, usize),
|
||||
BadCustodyBitfieldLength {
|
||||
committee_len: usize,
|
||||
bitfield_len: usize,
|
||||
},
|
||||
/// The aggregation bitfield length is not the smallest possible size to represent the committee.
|
||||
///
|
||||
/// (committee_len, bitfield_len)
|
||||
BadAggregationBitfieldLength(usize, usize),
|
||||
/// There was no known committee for the given shard in the given slot.
|
||||
///
|
||||
/// (attestation_data_shard, attestation_data_slot)
|
||||
NoCommitteeForShard(u64, Slot),
|
||||
BadAggregationBitfieldLength {
|
||||
committee_len: usize,
|
||||
bitfield_len: usize,
|
||||
},
|
||||
/// There was no known committee in this `epoch` for the given shard and slot.
|
||||
NoCommitteeForShard { shard: u64, slot: Slot },
|
||||
/// The validator index was unknown.
|
||||
UnknownValidator(u64),
|
||||
/// The attestation signature verification failed.
|
||||
@@ -188,6 +197,8 @@ pub enum AttesterSlashingInvalid {
|
||||
SlashableAttestation2Invalid(SlashableAttestationInvalid),
|
||||
/// The validator index is unknown. One cannot slash one who does not exist.
|
||||
UnknownValidator(u64),
|
||||
/// The specified validator has already been withdrawn.
|
||||
ValidatorAlreadyWithdrawn(u64),
|
||||
/// There were no indices able to be slashed.
|
||||
NoSlashableIndices,
|
||||
}
|
||||
@@ -264,16 +275,12 @@ pub enum ProposerSlashingInvalid {
|
||||
///
|
||||
/// (proposal_1_slot, proposal_2_slot)
|
||||
ProposalSlotMismatch(Slot, Slot),
|
||||
/// The two proposal have different shards.
|
||||
///
|
||||
/// (proposal_1_shard, proposal_2_shard)
|
||||
ProposalShardMismatch(u64, u64),
|
||||
/// The two proposal have different block roots.
|
||||
///
|
||||
/// (proposal_1_root, proposal_2_root)
|
||||
ProposalBlockRootMismatch(Hash256, Hash256),
|
||||
/// The proposals are identical and therefore not slashable.
|
||||
ProposalsIdentical,
|
||||
/// The specified proposer has already been slashed.
|
||||
ProposerAlreadySlashed,
|
||||
/// The specified proposer has already been withdrawn.
|
||||
ProposerAlreadyWithdrawn(u64),
|
||||
/// The first proposal signature was invalid.
|
||||
BadProposal1Signature,
|
||||
/// The second proposal signature was invalid.
|
||||
@@ -302,9 +309,7 @@ pub enum DepositValidationError {
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DepositInvalid {
|
||||
/// The deposit index does not match the state index.
|
||||
///
|
||||
/// (state_index, deposit_index)
|
||||
BadIndex(u64, u64),
|
||||
BadIndex { state: u64, deposit: u64 },
|
||||
/// The proof-of-possession does not match the given pubkey.
|
||||
BadProofOfPossession,
|
||||
/// The withdrawal credentials for the depositing validator did not match the withdrawal
|
||||
@@ -334,11 +339,14 @@ pub enum ExitValidationError {
|
||||
pub enum ExitInvalid {
|
||||
/// The specified validator is not in the state's validator registry.
|
||||
ValidatorUnknown(u64),
|
||||
AlreadyExited,
|
||||
/// The specified validator has a non-maximum exit epoch.
|
||||
AlreadyExited(u64),
|
||||
/// The specified validator has already initiated exit.
|
||||
AlreadyInitiatedExited(u64),
|
||||
/// The exit is for a future epoch.
|
||||
///
|
||||
/// (state_epoch, exit_epoch)
|
||||
FutureEpoch(Epoch, Epoch),
|
||||
FutureEpoch { state: Epoch, exit: Epoch },
|
||||
/// The validator has not been active for long enough.
|
||||
TooYoungToLeave { lifespan: Epoch, expected: u64 },
|
||||
/// The exit signature was not signed by the validator.
|
||||
BadSignature,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::errors::{AttestationInvalid as Invalid, AttestationValidationError as Error};
|
||||
use crate::common::verify_bitfield_length;
|
||||
use ssz::TreeHash;
|
||||
use types::beacon_state::helpers::*;
|
||||
use types::*;
|
||||
|
||||
/// Indicates if an `Attestation` is valid to be included in a block in the current epoch of the
|
||||
@@ -8,7 +8,7 @@ use types::*;
|
||||
///
|
||||
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn validate_attestation(
|
||||
state: &BeaconState,
|
||||
attestation: &Attestation,
|
||||
@@ -22,7 +22,7 @@ pub fn validate_attestation(
|
||||
///
|
||||
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn validate_attestation_without_signature(
|
||||
state: &BeaconState,
|
||||
attestation: &Attestation,
|
||||
@@ -35,74 +35,83 @@ pub fn validate_attestation_without_signature(
|
||||
/// given state, optionally validating the aggregate signature.
|
||||
///
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
fn validate_attestation_signature_optional(
|
||||
state: &BeaconState,
|
||||
attestation: &Attestation,
|
||||
spec: &ChainSpec,
|
||||
verify_signature: bool,
|
||||
) -> Result<(), Error> {
|
||||
// Verify that `attestation.data.slot >= GENESIS_SLOT`.
|
||||
let state_epoch = state.slot.epoch(spec.slots_per_epoch);
|
||||
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
||||
|
||||
// Can't submit pre-historic attestations.
|
||||
verify!(
|
||||
attestation.data.slot >= spec.genesis_slot,
|
||||
Invalid::PreGenesis(spec.genesis_slot, attestation.data.slot)
|
||||
Invalid::PreGenesis {
|
||||
genesis: spec.genesis_slot,
|
||||
attestation: attestation.data.slot
|
||||
}
|
||||
);
|
||||
|
||||
// Verify that `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot`.
|
||||
// Can't submit attestations too far in history.
|
||||
verify!(
|
||||
state.slot <= attestation.data.slot + spec.slots_per_epoch,
|
||||
Invalid::IncludedTooLate {
|
||||
state: spec.genesis_slot,
|
||||
attestation: attestation.data.slot
|
||||
}
|
||||
);
|
||||
|
||||
// Can't submit attestation too quickly.
|
||||
verify!(
|
||||
attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
|
||||
Invalid::IncludedTooEarly(
|
||||
state.slot,
|
||||
spec.min_attestation_inclusion_delay,
|
||||
attestation.data.slot
|
||||
)
|
||||
Invalid::IncludedTooEarly {
|
||||
state: state.slot,
|
||||
delay: spec.min_attestation_inclusion_delay,
|
||||
attestation: attestation.data.slot
|
||||
}
|
||||
);
|
||||
|
||||
// Verify that `state.slot < attestation.data.slot + SLOTS_PER_EPOCH`.
|
||||
verify!(
|
||||
state.slot < attestation.data.slot + spec.slots_per_epoch,
|
||||
Invalid::IncludedTooLate(state.slot, attestation.data.slot)
|
||||
);
|
||||
|
||||
// Verify that `attestation.data.justified_epoch` is equal to `state.justified_epoch` if
|
||||
// `slot_to_epoch(attestation.data.slot + 1) >= get_current_epoch(state) else
|
||||
// state.previous_justified_epoch`.
|
||||
if (attestation.data.slot + 1).epoch(spec.slots_per_epoch) >= state.current_epoch(spec) {
|
||||
// Verify the justified epoch and root is correct.
|
||||
if attestation_epoch >= state_epoch {
|
||||
verify!(
|
||||
attestation.data.justified_epoch == state.justified_epoch,
|
||||
Invalid::WrongJustifiedEpoch(
|
||||
attestation.data.justified_epoch,
|
||||
state.justified_epoch,
|
||||
false
|
||||
)
|
||||
attestation.data.source_epoch == state.current_justified_epoch,
|
||||
Invalid::WrongJustifiedEpoch {
|
||||
state: state.current_justified_epoch,
|
||||
attestation: attestation.data.source_epoch,
|
||||
is_current: true,
|
||||
}
|
||||
);
|
||||
verify!(
|
||||
attestation.data.source_root == state.current_justified_root,
|
||||
Invalid::WrongJustifiedRoot {
|
||||
state: state.current_justified_root,
|
||||
attestation: attestation.data.source_root,
|
||||
is_current: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
verify!(
|
||||
attestation.data.justified_epoch == state.previous_justified_epoch,
|
||||
Invalid::WrongJustifiedEpoch(
|
||||
attestation.data.justified_epoch,
|
||||
state.previous_justified_epoch,
|
||||
true
|
||||
)
|
||||
attestation.data.source_epoch == state.previous_justified_epoch,
|
||||
Invalid::WrongJustifiedEpoch {
|
||||
state: state.previous_justified_epoch,
|
||||
attestation: attestation.data.source_epoch,
|
||||
is_current: false,
|
||||
}
|
||||
);
|
||||
verify!(
|
||||
attestation.data.source_root == state.previous_justified_root,
|
||||
Invalid::WrongJustifiedRoot {
|
||||
state: state.previous_justified_root,
|
||||
attestation: attestation.data.source_root,
|
||||
is_current: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Verify that `attestation.data.justified_block_root` is equal to `get_block_root(state,
|
||||
// get_epoch_start_slot(attestation.data.justified_epoch))`.
|
||||
let justified_block_root = *state
|
||||
.get_block_root(
|
||||
attestation
|
||||
.data
|
||||
.justified_epoch
|
||||
.start_slot(spec.slots_per_epoch),
|
||||
&spec,
|
||||
)
|
||||
.ok_or(BeaconStateError::InsufficientBlockRoots)?;
|
||||
verify!(
|
||||
attestation.data.justified_block_root == justified_block_root,
|
||||
Invalid::WrongJustifiedRoot(justified_block_root, attestation.data.justified_block_root)
|
||||
);
|
||||
|
||||
// Check that the crosslink data is valid.
|
||||
//
|
||||
// Verify that either:
|
||||
//
|
||||
// (i)`state.latest_crosslinks[attestation.data.shard] == attestation.data.latest_crosslink`,
|
||||
@@ -115,63 +124,62 @@ fn validate_attestation_signature_optional(
|
||||
epoch: attestation.data.slot.epoch(spec.slots_per_epoch),
|
||||
};
|
||||
verify!(
|
||||
(attestation.data.latest_crosslink
|
||||
(attestation.data.previous_crosslink
|
||||
== state.latest_crosslinks[attestation.data.shard as usize])
|
||||
| (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink),
|
||||
Invalid::BadLatestCrosslinkRoot
|
||||
Invalid::BadPreviousCrosslink
|
||||
);
|
||||
|
||||
// Get the committee for this attestation
|
||||
let (committee, _shard) = state
|
||||
.get_crosslink_committees_at_slot(attestation.data.slot, spec)?
|
||||
.iter()
|
||||
.find(|(_committee, shard)| *shard == attestation.data.shard)
|
||||
.ok_or_else(|| {
|
||||
Error::Invalid(Invalid::NoCommitteeForShard(
|
||||
attestation.data.shard,
|
||||
attestation.data.slot,
|
||||
))
|
||||
})?;
|
||||
|
||||
// Custody bitfield is all zeros (phase 0 requirement).
|
||||
verify!(
|
||||
attestation.custody_bitfield.num_set_bits() == 0,
|
||||
Invalid::CustodyBitfieldHasSetBits
|
||||
);
|
||||
// Custody bitfield length is correct.
|
||||
verify!(
|
||||
verify_bitfield_length(&attestation.custody_bitfield, committee.len()),
|
||||
Invalid::BadCustodyBitfieldLength(committee.len(), attestation.custody_bitfield.len())
|
||||
);
|
||||
// Aggregation bitfield isn't empty.
|
||||
// Attestation must be non-empty!
|
||||
verify!(
|
||||
attestation.aggregation_bitfield.num_set_bits() != 0,
|
||||
Invalid::AggregationBitfieldIsEmpty
|
||||
);
|
||||
// Custody bitfield must be empty (be be removed in phase 1)
|
||||
verify!(
|
||||
attestation.custody_bitfield.num_set_bits() == 0,
|
||||
Invalid::CustodyBitfieldHasSetBits
|
||||
);
|
||||
|
||||
// Get the committee for the specific shard that this attestation is for.
|
||||
let crosslink_committee = state
|
||||
.get_crosslink_committees_at_slot(attestation.data.slot, spec)?
|
||||
.iter()
|
||||
.find(|c| c.shard == attestation.data.shard)
|
||||
.ok_or_else(|| {
|
||||
Error::Invalid(Invalid::NoCommitteeForShard {
|
||||
shard: attestation.data.shard,
|
||||
slot: attestation.data.slot,
|
||||
})
|
||||
})?;
|
||||
let committee = &crosslink_committee.committee;
|
||||
|
||||
// Custody bitfield length is correct.
|
||||
//
|
||||
// This is not directly in the spec, but it is inferred.
|
||||
verify!(
|
||||
verify_bitfield_length(&attestation.custody_bitfield, committee.len()),
|
||||
Invalid::BadCustodyBitfieldLength {
|
||||
committee_len: committee.len(),
|
||||
bitfield_len: attestation.custody_bitfield.len()
|
||||
}
|
||||
);
|
||||
// Aggregation bitfield length is correct.
|
||||
//
|
||||
// This is not directly in the spec, but it is inferred.
|
||||
verify!(
|
||||
verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()),
|
||||
Invalid::BadAggregationBitfieldLength(
|
||||
committee.len(),
|
||||
attestation.aggregation_bitfield.len()
|
||||
)
|
||||
Invalid::BadAggregationBitfieldLength {
|
||||
committee_len: committee.len(),
|
||||
bitfield_len: attestation.custody_bitfield.len()
|
||||
}
|
||||
);
|
||||
|
||||
if verify_signature {
|
||||
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
||||
verify_attestation_signature(
|
||||
state,
|
||||
committee,
|
||||
attestation_epoch,
|
||||
&attestation.aggregation_bitfield,
|
||||
&attestation.custody_bitfield,
|
||||
&attestation.data,
|
||||
&attestation.aggregate_signature,
|
||||
spec,
|
||||
)?;
|
||||
verify_attestation_signature(state, committee, attestation, spec)?;
|
||||
}
|
||||
|
||||
// [TO BE REMOVED IN PHASE 1] Verify that `attestation.data.crosslink_data_root == ZERO_HASH`.
|
||||
// Crosslink data root is zero (to be removed in phase 1).
|
||||
verify!(
|
||||
attestation.data.crosslink_data_root == spec.zero_hash,
|
||||
Invalid::ShardBlockRootNotZero
|
||||
@@ -188,37 +196,34 @@ fn validate_attestation_signature_optional(
|
||||
/// - `custody_bitfield` does not have a bit for each index of `committee`.
|
||||
/// - A `validator_index` in `committee` is not in `state.validator_registry`.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
fn verify_attestation_signature(
|
||||
state: &BeaconState,
|
||||
committee: &[usize],
|
||||
attestation_epoch: Epoch,
|
||||
aggregation_bitfield: &Bitfield,
|
||||
custody_bitfield: &Bitfield,
|
||||
attestation_data: &AttestationData,
|
||||
aggregate_signature: &AggregateSignature,
|
||||
a: &Attestation,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
||||
let mut message_exists = vec![false; 2];
|
||||
let attestation_epoch = a.data.slot.epoch(spec.slots_per_epoch);
|
||||
|
||||
for (i, v) in committee.iter().enumerate() {
|
||||
let validator_signed = aggregation_bitfield.get(i).map_err(|_| {
|
||||
Error::Invalid(Invalid::BadAggregationBitfieldLength(
|
||||
committee.len(),
|
||||
aggregation_bitfield.len(),
|
||||
))
|
||||
let validator_signed = a.aggregation_bitfield.get(i).map_err(|_| {
|
||||
Error::Invalid(Invalid::BadAggregationBitfieldLength {
|
||||
committee_len: committee.len(),
|
||||
bitfield_len: a.aggregation_bitfield.len(),
|
||||
})
|
||||
})?;
|
||||
|
||||
if validator_signed {
|
||||
let custody_bit: bool = match custody_bitfield.get(i) {
|
||||
let custody_bit: bool = match a.custody_bitfield.get(i) {
|
||||
Ok(bit) => bit,
|
||||
// Invalidate signature if custody_bitfield.len() < committee
|
||||
Err(_) => {
|
||||
return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength(
|
||||
committee.len(),
|
||||
custody_bitfield.len(),
|
||||
)));
|
||||
return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength {
|
||||
committee_len: committee.len(),
|
||||
bitfield_len: a.aggregation_bitfield.len(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -236,14 +241,14 @@ fn verify_attestation_signature(
|
||||
|
||||
// Message when custody bitfield is `false`
|
||||
let message_0 = AttestationDataAndCustodyBit {
|
||||
data: attestation_data.clone(),
|
||||
data: a.data.clone(),
|
||||
custody_bit: false,
|
||||
}
|
||||
.hash_tree_root();
|
||||
|
||||
// Message when custody bitfield is `true`
|
||||
let message_1 = AttestationDataAndCustodyBit {
|
||||
data: attestation_data.clone(),
|
||||
data: a.data.clone(),
|
||||
custody_bit: true,
|
||||
}
|
||||
.hash_tree_root();
|
||||
@@ -265,7 +270,8 @@ fn verify_attestation_signature(
|
||||
let domain = spec.get_domain(attestation_epoch, Domain::Attestation, &state.fork);
|
||||
|
||||
verify!(
|
||||
aggregate_signature.verify_multiple(&messages[..], domain, &keys[..]),
|
||||
a.aggregate_signature
|
||||
.verify_multiple(&messages[..], domain, &keys[..]),
|
||||
Invalid::BadSignature
|
||||
);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use types::*;
|
||||
///
|
||||
/// Returns `Ok(())` if the `AttesterSlashing` is valid, otherwise indicates the reason for invalidity.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn verify_attester_slashing(
|
||||
state: &BeaconState,
|
||||
attester_slashing: &AttesterSlashing,
|
||||
@@ -41,15 +41,16 @@ pub fn verify_attester_slashing(
|
||||
///
|
||||
/// Returns Ok(indices) if `indices.len() > 0`.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn gather_attester_slashing_indices(
|
||||
state: &BeaconState,
|
||||
attester_slashing: &AttesterSlashing,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Vec<u64>, Error> {
|
||||
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
|
||||
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
|
||||
|
||||
let mut slashable_indices = vec![];
|
||||
let mut slashable_indices = Vec::with_capacity(spec.max_indices_per_slashable_vote);
|
||||
for i in &slashable_attestation_1.validator_indices {
|
||||
let validator = state
|
||||
.validator_registry
|
||||
@@ -57,11 +58,20 @@ pub fn gather_attester_slashing_indices(
|
||||
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?;
|
||||
|
||||
if slashable_attestation_2.validator_indices.contains(&i) & !validator.slashed {
|
||||
// TODO: verify that we should reject any slashable attestation which includes a
|
||||
// withdrawn validator. PH has asked the question on gitter, awaiting response.
|
||||
verify!(
|
||||
validator.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch),
|
||||
Invalid::ValidatorAlreadyWithdrawn(*i)
|
||||
);
|
||||
|
||||
slashable_indices.push(*i);
|
||||
}
|
||||
}
|
||||
|
||||
verify!(!slashable_indices.is_empty(), Invalid::NoSlashableIndices);
|
||||
|
||||
slashable_indices.shrink_to_fit();
|
||||
|
||||
Ok(slashable_indices)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,8 @@ use hashing::hash;
|
||||
use merkle_proof::verify_merkle_proof;
|
||||
use ssz::ssz_encode;
|
||||
use ssz_derive::Encode;
|
||||
use std::collections::HashMap;
|
||||
use types::*;
|
||||
|
||||
pub type PublicKeyValidatorIndexHashmap = HashMap<PublicKey, u64>;
|
||||
|
||||
/// Indicates if a `Deposit` is valid to be included in a block in the current epoch of the given
|
||||
/// state.
|
||||
///
|
||||
@@ -18,7 +15,7 @@ pub type PublicKeyValidatorIndexHashmap = HashMap<PublicKey, u64>;
|
||||
///
|
||||
/// Note: this function is incomplete.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn verify_deposit(
|
||||
state: &BeaconState,
|
||||
deposit: &Deposit,
|
||||
@@ -49,35 +46,32 @@ pub fn verify_deposit(
|
||||
|
||||
/// Verify that the `Deposit` index is correct.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn verify_deposit_index(state: &BeaconState, deposit: &Deposit) -> Result<(), Error> {
|
||||
verify!(
|
||||
deposit.index == state.deposit_index,
|
||||
Invalid::BadIndex(state.deposit_index, deposit.index)
|
||||
Invalid::BadIndex {
|
||||
state: state.deposit_index,
|
||||
deposit: deposit.index
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn build_public_key_hashmap(state: &BeaconState) -> PublicKeyValidatorIndexHashmap {
|
||||
let mut hashmap = HashMap::with_capacity(state.validator_registry.len());
|
||||
|
||||
for (i, validator) in state.validator_registry.iter().enumerate() {
|
||||
hashmap.insert(validator.pubkey.clone(), i as u64);
|
||||
}
|
||||
|
||||
hashmap
|
||||
}
|
||||
|
||||
/// Returns a `Some(validator index)` if a pubkey already exists in the `validator_registry`,
|
||||
/// otherwise returns `None`.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
/// Errors if the state's `pubkey_cache` is not current.
|
||||
pub fn get_existing_validator_index(
|
||||
state: &BeaconState,
|
||||
deposit: &Deposit,
|
||||
) -> Result<Option<u64>, Error> {
|
||||
let deposit_input = &deposit.deposit_data.deposit_input;
|
||||
|
||||
let validator_index = state
|
||||
.get_validator_index(&deposit_input.pubkey)?
|
||||
.and_then(|i| Some(i));
|
||||
let validator_index = state.get_validator_index(&deposit_input.pubkey)?;
|
||||
|
||||
match validator_index {
|
||||
None => Ok(None),
|
||||
@@ -94,12 +88,12 @@ pub fn get_existing_validator_index(
|
||||
|
||||
/// Verify that a deposit is included in the state's eth1 deposit root.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
fn verify_deposit_merkle_proof(state: &BeaconState, deposit: &Deposit, spec: &ChainSpec) -> bool {
|
||||
let leaf = hash(&get_serialized_deposit_data(deposit));
|
||||
verify_merkle_proof(
|
||||
Hash256::from_slice(&leaf),
|
||||
&deposit.branch,
|
||||
&deposit.proof,
|
||||
spec.deposit_contract_tree_depth as usize,
|
||||
deposit.index as usize,
|
||||
state.latest_eth1_data.deposit_root,
|
||||
@@ -108,7 +102,7 @@ fn verify_deposit_merkle_proof(state: &BeaconState, deposit: &Deposit, spec: &Ch
|
||||
|
||||
/// Helper struct for easily getting the serialized data generated by the deposit contract.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
#[derive(Encode)]
|
||||
struct SerializedDepositData {
|
||||
amount: u64,
|
||||
@@ -119,7 +113,7 @@ struct SerializedDepositData {
|
||||
/// Return the serialized data generated by the deposit contract that is used to generate the
|
||||
/// merkle proof.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
fn get_serialized_deposit_data(deposit: &Deposit) -> Vec<u8> {
|
||||
let serialized_deposit_data = SerializedDepositData {
|
||||
amount: deposit.deposit_data.amount,
|
||||
|
||||
@@ -7,7 +7,7 @@ use types::*;
|
||||
///
|
||||
/// Returns `Ok(())` if the `Exit` is valid, otherwise indicates the reason for invalidity.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn verify_exit(
|
||||
state: &BeaconState,
|
||||
exit: &VoluntaryExit,
|
||||
@@ -18,15 +18,35 @@ pub fn verify_exit(
|
||||
.get(exit.validator_index as usize)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::ValidatorUnknown(exit.validator_index)))?;
|
||||
|
||||
// Verify that the validator has not yet exited.
|
||||
verify!(
|
||||
validator.exit_epoch
|
||||
> state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec),
|
||||
Invalid::AlreadyExited
|
||||
validator.exit_epoch == spec.far_future_epoch,
|
||||
Invalid::AlreadyExited(exit.validator_index)
|
||||
);
|
||||
|
||||
// Verify that the validator has not yet initiated.
|
||||
verify!(
|
||||
!validator.initiated_exit,
|
||||
Invalid::AlreadyInitiatedExited(exit.validator_index)
|
||||
);
|
||||
|
||||
// Exits must specify an epoch when they become valid; they are not valid before then.
|
||||
verify!(
|
||||
state.current_epoch(spec) >= exit.epoch,
|
||||
Invalid::FutureEpoch(state.current_epoch(spec), exit.epoch)
|
||||
Invalid::FutureEpoch {
|
||||
state: state.current_epoch(spec),
|
||||
exit: exit.epoch
|
||||
}
|
||||
);
|
||||
|
||||
// Must have been in the validator set long enough.
|
||||
let lifespan = state.slot.epoch(spec.slots_per_epoch) - validator.activation_epoch;
|
||||
verify!(
|
||||
lifespan >= spec.persistent_committee_period,
|
||||
Invalid::TooYoungToLeave {
|
||||
lifespan,
|
||||
expected: spec.persistent_committee_period,
|
||||
}
|
||||
);
|
||||
|
||||
let message = exit.signed_root();
|
||||
|
||||
@@ -7,7 +7,7 @@ use types::*;
|
||||
///
|
||||
/// Returns `Ok(())` if the `ProposerSlashing` is valid, otherwise indicates the reason for invalidity.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn verify_proposer_slashing(
|
||||
proposer_slashing: &ProposerSlashing,
|
||||
state: &BeaconState,
|
||||
@@ -21,34 +21,28 @@ pub fn verify_proposer_slashing(
|
||||
})?;
|
||||
|
||||
verify!(
|
||||
proposer_slashing.proposal_1.slot == proposer_slashing.proposal_2.slot,
|
||||
proposer_slashing.header_1.slot == proposer_slashing.header_2.slot,
|
||||
Invalid::ProposalSlotMismatch(
|
||||
proposer_slashing.proposal_1.slot,
|
||||
proposer_slashing.proposal_2.slot
|
||||
proposer_slashing.header_1.slot,
|
||||
proposer_slashing.header_2.slot
|
||||
)
|
||||
);
|
||||
|
||||
verify!(
|
||||
proposer_slashing.proposal_1.shard == proposer_slashing.proposal_2.shard,
|
||||
Invalid::ProposalShardMismatch(
|
||||
proposer_slashing.proposal_1.shard,
|
||||
proposer_slashing.proposal_2.shard
|
||||
)
|
||||
);
|
||||
|
||||
verify!(
|
||||
proposer_slashing.proposal_1.block_root != proposer_slashing.proposal_2.block_root,
|
||||
Invalid::ProposalBlockRootMismatch(
|
||||
proposer_slashing.proposal_1.block_root,
|
||||
proposer_slashing.proposal_2.block_root
|
||||
)
|
||||
proposer_slashing.header_1 != proposer_slashing.header_2,
|
||||
Invalid::ProposalsIdentical
|
||||
);
|
||||
|
||||
verify!(!proposer.slashed, Invalid::ProposerAlreadySlashed);
|
||||
|
||||
verify!(
|
||||
verify_proposal_signature(
|
||||
&proposer_slashing.proposal_1,
|
||||
proposer.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch),
|
||||
Invalid::ProposerAlreadyWithdrawn(proposer_slashing.proposer_index)
|
||||
);
|
||||
|
||||
verify!(
|
||||
verify_header_signature(
|
||||
&proposer_slashing.header_1,
|
||||
&proposer.pubkey,
|
||||
&state.fork,
|
||||
spec
|
||||
@@ -56,8 +50,8 @@ pub fn verify_proposer_slashing(
|
||||
Invalid::BadProposal1Signature
|
||||
);
|
||||
verify!(
|
||||
verify_proposal_signature(
|
||||
&proposer_slashing.proposal_2,
|
||||
verify_header_signature(
|
||||
&proposer_slashing.header_2,
|
||||
&proposer.pubkey,
|
||||
&state.fork,
|
||||
spec
|
||||
@@ -71,17 +65,19 @@ pub fn verify_proposer_slashing(
|
||||
/// Verifies the signature of a proposal.
|
||||
///
|
||||
/// Returns `true` if the signature is valid.
|
||||
fn verify_proposal_signature(
|
||||
proposal: &Proposal,
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
fn verify_header_signature(
|
||||
header: &BeaconBlockHeader,
|
||||
pubkey: &PublicKey,
|
||||
fork: &Fork,
|
||||
spec: &ChainSpec,
|
||||
) -> bool {
|
||||
let message = proposal.signed_root();
|
||||
let message = header.signed_root();
|
||||
let domain = spec.get_domain(
|
||||
proposal.slot.epoch(spec.slots_per_epoch),
|
||||
Domain::Proposal,
|
||||
header.slot.epoch(spec.slots_per_epoch),
|
||||
Domain::BeaconBlock,
|
||||
fork,
|
||||
);
|
||||
proposal.signature.verify(&message[..], domain, pubkey)
|
||||
header.signature.verify(&message[..], domain, pubkey)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::errors::{
|
||||
SlashableAttestationInvalid as Invalid, SlashableAttestationValidationError as Error,
|
||||
};
|
||||
use crate::common::verify_bitfield_length;
|
||||
use ssz::TreeHash;
|
||||
use types::beacon_state::helpers::verify_bitfield_length;
|
||||
use types::*;
|
||||
|
||||
/// Indicates if a `SlashableAttestation` is valid to be included in a block in the current epoch of the given
|
||||
@@ -10,7 +10,7 @@ use types::*;
|
||||
///
|
||||
/// Returns `Ok(())` if the `SlashableAttestation` is valid, otherwise indicates the reason for invalidity.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn verify_slashable_attestation(
|
||||
state: &BeaconState,
|
||||
slashable_attestation: &SlashableAttestation,
|
||||
|
||||
@@ -10,16 +10,16 @@ use types::*;
|
||||
///
|
||||
/// Note: this function is incomplete.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn verify_transfer(
|
||||
state: &BeaconState,
|
||||
transfer: &Transfer,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let from_balance = *state
|
||||
let sender_balance = *state
|
||||
.validator_balances
|
||||
.get(transfer.from as usize)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.from)))?;
|
||||
.get(transfer.sender as usize)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
|
||||
|
||||
let total_amount = transfer
|
||||
.amount
|
||||
@@ -27,19 +27,22 @@ pub fn verify_transfer(
|
||||
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
|
||||
|
||||
verify!(
|
||||
from_balance >= transfer.amount,
|
||||
Invalid::FromBalanceInsufficient(transfer.amount, from_balance)
|
||||
sender_balance >= transfer.amount,
|
||||
Invalid::FromBalanceInsufficient(transfer.amount, sender_balance)
|
||||
);
|
||||
|
||||
verify!(
|
||||
from_balance >= transfer.fee,
|
||||
Invalid::FromBalanceInsufficient(transfer.fee, from_balance)
|
||||
sender_balance >= transfer.fee,
|
||||
Invalid::FromBalanceInsufficient(transfer.fee, sender_balance)
|
||||
);
|
||||
|
||||
verify!(
|
||||
(from_balance == total_amount)
|
||||
|| (from_balance >= (total_amount + spec.min_deposit_amount)),
|
||||
Invalid::InvalidResultingFromBalance(from_balance - total_amount, spec.min_deposit_amount)
|
||||
(sender_balance == total_amount)
|
||||
|| (sender_balance >= (total_amount + spec.min_deposit_amount)),
|
||||
Invalid::InvalidResultingFromBalance(
|
||||
sender_balance - total_amount,
|
||||
spec.min_deposit_amount
|
||||
)
|
||||
);
|
||||
|
||||
verify!(
|
||||
@@ -47,25 +50,25 @@ pub fn verify_transfer(
|
||||
Invalid::StateSlotMismatch(state.slot, transfer.slot)
|
||||
);
|
||||
|
||||
let from_validator = state
|
||||
let sender_validator = state
|
||||
.validator_registry
|
||||
.get(transfer.from as usize)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.from)))?;
|
||||
.get(transfer.sender as usize)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
|
||||
let epoch = state.slot.epoch(spec.slots_per_epoch);
|
||||
|
||||
verify!(
|
||||
from_validator.is_withdrawable_at(epoch)
|
||||
|| from_validator.activation_epoch == spec.far_future_epoch,
|
||||
Invalid::FromValidatorIneligableForTransfer(transfer.from)
|
||||
sender_validator.is_withdrawable_at(epoch)
|
||||
|| sender_validator.activation_epoch == spec.far_future_epoch,
|
||||
Invalid::FromValidatorIneligableForTransfer(transfer.sender)
|
||||
);
|
||||
|
||||
let transfer_withdrawal_credentials = Hash256::from_slice(
|
||||
&get_withdrawal_credentials(&transfer.pubkey, spec.bls_withdrawal_prefix_byte)[..],
|
||||
);
|
||||
verify!(
|
||||
from_validator.withdrawal_credentials == transfer_withdrawal_credentials,
|
||||
sender_validator.withdrawal_credentials == transfer_withdrawal_credentials,
|
||||
Invalid::WithdrawalCredentialsMismatch(
|
||||
from_validator.withdrawal_credentials,
|
||||
sender_validator.withdrawal_credentials,
|
||||
transfer_withdrawal_credentials
|
||||
)
|
||||
);
|
||||
@@ -91,22 +94,23 @@ pub fn verify_transfer(
|
||||
///
|
||||
/// Does not check that the transfer is valid, however checks for overflow in all actions.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn execute_transfer(
|
||||
state: &mut BeaconState,
|
||||
transfer: &Transfer,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let from_balance = *state
|
||||
let sender_balance = *state
|
||||
.validator_balances
|
||||
.get(transfer.from as usize)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.from)))?;
|
||||
let to_balance = *state
|
||||
.get(transfer.sender as usize)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
|
||||
let recipient_balance = *state
|
||||
.validator_balances
|
||||
.get(transfer.to as usize)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::ToValidatorUnknown(transfer.to)))?;
|
||||
.get(transfer.recipient as usize)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::ToValidatorUnknown(transfer.recipient)))?;
|
||||
|
||||
let proposer_index = state.get_beacon_proposer_index(state.slot, spec)?;
|
||||
let proposer_index =
|
||||
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
|
||||
let proposer_balance = state.validator_balances[proposer_index];
|
||||
|
||||
let total_amount = transfer
|
||||
@@ -114,14 +118,22 @@ pub fn execute_transfer(
|
||||
.checked_add(transfer.fee)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
|
||||
|
||||
state.validator_balances[transfer.from as usize] =
|
||||
from_balance.checked_sub(total_amount).ok_or_else(|| {
|
||||
Error::Invalid(Invalid::FromBalanceInsufficient(total_amount, from_balance))
|
||||
state.validator_balances[transfer.sender as usize] =
|
||||
sender_balance.checked_sub(total_amount).ok_or_else(|| {
|
||||
Error::Invalid(Invalid::FromBalanceInsufficient(
|
||||
total_amount,
|
||||
sender_balance,
|
||||
))
|
||||
})?;
|
||||
|
||||
state.validator_balances[transfer.to as usize] = to_balance
|
||||
state.validator_balances[transfer.recipient as usize] = recipient_balance
|
||||
.checked_add(transfer.amount)
|
||||
.ok_or_else(|| Error::Invalid(Invalid::ToBalanceOverflow(to_balance, transfer.amount)))?;
|
||||
.ok_or_else(|| {
|
||||
Error::Invalid(Invalid::ToBalanceOverflow(
|
||||
recipient_balance,
|
||||
transfer.amount,
|
||||
))
|
||||
})?;
|
||||
|
||||
state.validator_balances[proposer_index] =
|
||||
proposer_balance.checked_add(transfer.fee).ok_or_else(|| {
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
use apply_rewards::apply_rewards;
|
||||
use errors::EpochProcessingError as Error;
|
||||
use integer_sqrt::IntegerSquareRoot;
|
||||
use rayon::prelude::*;
|
||||
use process_ejections::process_ejections;
|
||||
use process_exit_queue::process_exit_queue;
|
||||
use process_slashings::process_slashings;
|
||||
use ssz::TreeHash;
|
||||
use std::collections::HashMap;
|
||||
use types::{validator_registry::get_active_validator_indices, *};
|
||||
use types::*;
|
||||
use update_registry_and_shuffling_data::update_registry_and_shuffling_data;
|
||||
use validator_statuses::{TotalBalances, ValidatorStatuses};
|
||||
use winning_root::{winning_root, WinningRoot};
|
||||
|
||||
pub mod apply_rewards;
|
||||
pub mod errors;
|
||||
pub mod get_attestation_participants;
|
||||
pub mod inclusion_distance;
|
||||
pub mod process_ejections;
|
||||
pub mod process_exit_queue;
|
||||
pub mod process_slashings;
|
||||
pub mod tests;
|
||||
pub mod update_registry_and_shuffling_data;
|
||||
pub mod validator_statuses;
|
||||
pub mod winning_root;
|
||||
|
||||
@@ -23,35 +32,51 @@ pub type WinningRootHashSet = HashMap<u64, WinningRoot>;
|
||||
/// Mutates the given `BeaconState`, returning early if an error is encountered. If an error is
|
||||
/// returned, a state might be "half-processed" and therefore in an invalid state.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> {
|
||||
// Ensure all of the caches are built.
|
||||
// Ensure the previous and next epoch caches are built.
|
||||
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
|
||||
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
|
||||
state.build_epoch_cache(RelativeEpoch::Next, spec)?;
|
||||
|
||||
let mut statuses = initialize_validator_statuses(&state, spec)?;
|
||||
// Load the struct we use to assign validators into sets based on their participation.
|
||||
//
|
||||
// E.g., attestation in the previous epoch, attested to the head, etc.
|
||||
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
|
||||
validator_statuses.process_attestations(&state, spec)?;
|
||||
|
||||
process_eth1_data(state, spec);
|
||||
// Justification.
|
||||
update_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
|
||||
|
||||
process_justification(state, &statuses.total_balances, spec);
|
||||
|
||||
// Crosslinks
|
||||
// Crosslinks.
|
||||
let winning_root_for_shards = process_crosslinks(state, spec)?;
|
||||
|
||||
// Rewards and Penalities
|
||||
process_rewards_and_penalities(state, &mut statuses, &winning_root_for_shards, spec)?;
|
||||
// Eth1 data.
|
||||
maybe_reset_eth1_period(state, spec);
|
||||
|
||||
// Ejections
|
||||
state.process_ejections(spec);
|
||||
// Rewards and Penalities.
|
||||
apply_rewards(
|
||||
state,
|
||||
&mut validator_statuses,
|
||||
&winning_root_for_shards,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
// Validator Registry
|
||||
process_validator_registry(state, spec)?;
|
||||
// Ejections.
|
||||
process_ejections(state, spec)?;
|
||||
|
||||
// Final updates
|
||||
update_active_tree_index_roots(state, spec)?;
|
||||
update_latest_slashed_balances(state, spec);
|
||||
clean_attestations(state, spec);
|
||||
// Validator Registry.
|
||||
update_registry_and_shuffling_data(
|
||||
state,
|
||||
validator_statuses.total_balances.current_epoch,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
// Slashings and exit queue.
|
||||
process_slashings(state, validator_statuses.total_balances.current_epoch, spec)?;
|
||||
process_exit_queue(state, spec);
|
||||
|
||||
// Final updates.
|
||||
finish_epoch_update(state, spec)?;
|
||||
|
||||
// Rotate the epoch caches to suit the epoch transition.
|
||||
state.advance_caches();
|
||||
@@ -59,43 +84,16 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a list of active validator indices for the state's current epoch.
|
||||
/// Maybe resets the eth1 period.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn calculate_active_validator_indices(state: &BeaconState, spec: &ChainSpec) -> Vec<usize> {
|
||||
get_active_validator_indices(
|
||||
&state.validator_registry,
|
||||
state.slot.epoch(spec.slots_per_epoch),
|
||||
)
|
||||
}
|
||||
|
||||
/// Calculates various sets of attesters, including:
|
||||
///
|
||||
/// - current epoch attesters
|
||||
/// - current epoch boundary attesters
|
||||
/// - previous epoch attesters
|
||||
/// - etc.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn initialize_validator_statuses(
|
||||
state: &BeaconState,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<ValidatorStatuses, BeaconStateError> {
|
||||
let mut statuses = ValidatorStatuses::new(state, spec);
|
||||
|
||||
statuses.process_attestations(&state, &state.latest_attestations, spec)?;
|
||||
|
||||
Ok(statuses)
|
||||
}
|
||||
|
||||
/// Spec v0.4.0
|
||||
pub fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) {
|
||||
/// Spec v0.5.0
|
||||
pub fn maybe_reset_eth1_period(state: &mut BeaconState, spec: &ChainSpec) {
|
||||
let next_epoch = state.next_epoch(spec);
|
||||
let voting_period = spec.epochs_per_eth1_voting_period;
|
||||
|
||||
if next_epoch % voting_period == 0 {
|
||||
for eth1_data_vote in &state.eth1_data_votes {
|
||||
if eth1_data_vote.vote_count * 2 > voting_period {
|
||||
if eth1_data_vote.vote_count * 2 > voting_period * spec.slots_per_epoch {
|
||||
state.latest_eth1_data = eth1_data_vote.eth1_data.clone();
|
||||
}
|
||||
}
|
||||
@@ -110,79 +108,68 @@ pub fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) {
|
||||
/// - `justified_epoch`
|
||||
/// - `previous_justified_epoch`
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn process_justification(
|
||||
/// Spec v0.5.0
|
||||
pub fn update_justification_and_finalization(
|
||||
state: &mut BeaconState,
|
||||
total_balances: &TotalBalances,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
) -> Result<(), Error> {
|
||||
let previous_epoch = state.previous_epoch(spec);
|
||||
let current_epoch = state.current_epoch(spec);
|
||||
|
||||
let mut new_justified_epoch = state.justified_epoch;
|
||||
let mut new_justified_epoch = state.current_justified_epoch;
|
||||
let mut new_finalized_epoch = state.finalized_epoch;
|
||||
|
||||
// Rotate the justification bitfield up one epoch to make room for the current epoch.
|
||||
state.justification_bitfield <<= 1;
|
||||
|
||||
// If > 2/3 of the total balance attested to the previous epoch boundary
|
||||
//
|
||||
// - Set the 2nd bit of the bitfield.
|
||||
// - Set the previous epoch to be justified.
|
||||
if (3 * total_balances.previous_epoch_boundary_attesters) >= (2 * total_balances.previous_epoch)
|
||||
// If the previous epoch gets justified, full the second last bit.
|
||||
if (total_balances.previous_epoch_boundary_attesters * 3) >= (total_balances.previous_epoch * 2)
|
||||
{
|
||||
state.justification_bitfield |= 2;
|
||||
new_justified_epoch = previous_epoch;
|
||||
state.justification_bitfield |= 2;
|
||||
}
|
||||
// If > 2/3 of the total balance attested to the previous epoch boundary
|
||||
//
|
||||
// - Set the 1st bit of the bitfield.
|
||||
// - Set the current epoch to be justified.
|
||||
if (3 * total_balances.current_epoch_boundary_attesters) >= (2 * total_balances.current_epoch) {
|
||||
state.justification_bitfield |= 1;
|
||||
// If the current epoch gets justified, fill the last bit.
|
||||
if (total_balances.current_epoch_boundary_attesters * 3) >= (total_balances.current_epoch * 2) {
|
||||
new_justified_epoch = current_epoch;
|
||||
state.justification_bitfield |= 1;
|
||||
}
|
||||
|
||||
// If:
|
||||
//
|
||||
// - All three epochs prior to this epoch have been justified.
|
||||
// - The previous justified justified epoch was three epochs ago.
|
||||
//
|
||||
// Then, set the finalized epoch to be three epochs ago.
|
||||
if ((state.justification_bitfield >> 1) % 8 == 0b111)
|
||||
& (state.previous_justified_epoch == previous_epoch - 2)
|
||||
{
|
||||
state.finalized_epoch = state.previous_justified_epoch;
|
||||
let bitfield = state.justification_bitfield;
|
||||
|
||||
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
|
||||
if ((bitfield >> 1) % 8 == 0b111) & (state.previous_justified_epoch == current_epoch - 3) {
|
||||
new_finalized_epoch = state.previous_justified_epoch;
|
||||
}
|
||||
// If:
|
||||
//
|
||||
// - Both two epochs prior to this epoch have been justified.
|
||||
// - The previous justified epoch was two epochs ago.
|
||||
//
|
||||
// Then, set the finalized epoch to two epochs ago.
|
||||
if ((state.justification_bitfield >> 1) % 4 == 0b11)
|
||||
& (state.previous_justified_epoch == previous_epoch - 1)
|
||||
{
|
||||
state.finalized_epoch = state.previous_justified_epoch;
|
||||
// The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
|
||||
if ((bitfield >> 1) % 4 == 0b11) & (state.previous_justified_epoch == current_epoch - 2) {
|
||||
new_finalized_epoch = state.previous_justified_epoch;
|
||||
}
|
||||
// If:
|
||||
//
|
||||
// - This epoch and the two prior have been justified.
|
||||
// - The presently justified epoch was two epochs ago.
|
||||
//
|
||||
// Then, set the finalized epoch to two epochs ago.
|
||||
if (state.justification_bitfield % 8 == 0b111) & (state.justified_epoch == previous_epoch - 1) {
|
||||
state.finalized_epoch = state.justified_epoch;
|
||||
// The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 2nd as source.
|
||||
if (bitfield % 8 == 0b111) & (state.current_justified_epoch == current_epoch - 2) {
|
||||
new_finalized_epoch = state.current_justified_epoch;
|
||||
}
|
||||
// If:
|
||||
//
|
||||
// - This epoch and the epoch prior to it have been justified.
|
||||
// - Set the previous epoch to be justified.
|
||||
//
|
||||
// Then, set the finalized epoch to be the previous epoch.
|
||||
if (state.justification_bitfield % 4 == 0b11) & (state.justified_epoch == previous_epoch) {
|
||||
state.finalized_epoch = state.justified_epoch;
|
||||
// The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
|
||||
if (bitfield % 4 == 0b11) & (state.current_justified_epoch == current_epoch - 1) {
|
||||
new_finalized_epoch = state.current_justified_epoch;
|
||||
}
|
||||
|
||||
state.previous_justified_epoch = state.justified_epoch;
|
||||
state.justified_epoch = new_justified_epoch;
|
||||
state.previous_justified_epoch = state.current_justified_epoch;
|
||||
state.previous_justified_root = state.current_justified_root;
|
||||
|
||||
if new_justified_epoch != state.current_justified_epoch {
|
||||
state.current_justified_epoch = new_justified_epoch;
|
||||
state.current_justified_root =
|
||||
*state.get_block_root(new_justified_epoch.start_slot(spec.slots_per_epoch), spec)?;
|
||||
}
|
||||
|
||||
if new_finalized_epoch != state.finalized_epoch {
|
||||
state.finalized_epoch = new_finalized_epoch;
|
||||
state.finalized_root =
|
||||
*state.get_block_root(new_finalized_epoch.start_slot(spec.slots_per_epoch), spec)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the following fields on the `BeaconState`:
|
||||
@@ -191,23 +178,11 @@ pub fn process_justification(
|
||||
///
|
||||
/// Also returns a `WinningRootHashSet` for later use during epoch processing.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn process_crosslinks(
|
||||
state: &mut BeaconState,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<WinningRootHashSet, Error> {
|
||||
let current_epoch_attestations: Vec<&PendingAttestation> = state
|
||||
.latest_attestations
|
||||
.par_iter()
|
||||
.filter(|a| a.data.slot.epoch(spec.slots_per_epoch) == state.current_epoch(spec))
|
||||
.collect();
|
||||
|
||||
let previous_epoch_attestations: Vec<&PendingAttestation> = state
|
||||
.latest_attestations
|
||||
.par_iter()
|
||||
.filter(|a| a.data.slot.epoch(spec.slots_per_epoch) == state.previous_epoch(spec))
|
||||
.collect();
|
||||
|
||||
let mut winning_root_for_shards: WinningRootHashSet = HashMap::new();
|
||||
|
||||
let previous_and_current_epoch_slots: Vec<Slot> = state
|
||||
@@ -221,24 +196,18 @@ pub fn process_crosslinks(
|
||||
let crosslink_committees_at_slot =
|
||||
state.get_crosslink_committees_at_slot(slot, spec)?.clone();
|
||||
|
||||
for (crosslink_committee, shard) in crosslink_committees_at_slot {
|
||||
let shard = shard as u64;
|
||||
for c in crosslink_committees_at_slot {
|
||||
let shard = c.shard as u64;
|
||||
|
||||
let winning_root = winning_root(
|
||||
state,
|
||||
shard,
|
||||
¤t_epoch_attestations[..],
|
||||
&previous_epoch_attestations[..],
|
||||
spec,
|
||||
)?;
|
||||
let winning_root = winning_root(state, shard, spec)?;
|
||||
|
||||
if let Some(winning_root) = winning_root {
|
||||
let total_committee_balance = state.get_total_balance(&crosslink_committee, spec);
|
||||
let total_committee_balance = state.get_total_balance(&c.committee, spec)?;
|
||||
|
||||
// TODO: I think this has a bug.
|
||||
if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) {
|
||||
state.latest_crosslinks[shard as usize] = Crosslink {
|
||||
epoch: state.current_epoch(spec),
|
||||
epoch: slot.epoch(spec.slots_per_epoch),
|
||||
crosslink_data_root: winning_root.crosslink_data_root,
|
||||
}
|
||||
}
|
||||
@@ -250,248 +219,53 @@ pub fn process_crosslinks(
|
||||
Ok(winning_root_for_shards)
|
||||
}
|
||||
|
||||
/// Updates the following fields on the BeaconState:
|
||||
/// Finish up an epoch update.
|
||||
///
|
||||
/// - `validator_balances`
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn process_rewards_and_penalities(
|
||||
state: &mut BeaconState,
|
||||
statuses: &mut ValidatorStatuses,
|
||||
winning_root_for_shards: &WinningRootHashSet,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let next_epoch = state.next_epoch(spec);
|
||||
|
||||
statuses.process_winning_roots(state, winning_root_for_shards, spec)?;
|
||||
|
||||
let total_balances = &statuses.total_balances;
|
||||
|
||||
let base_reward_quotient =
|
||||
total_balances.previous_epoch.integer_sqrt() / spec.base_reward_quotient;
|
||||
|
||||
// Guard against a divide-by-zero during the validator balance update.
|
||||
if base_reward_quotient == 0 {
|
||||
return Err(Error::BaseRewardQuotientIsZero);
|
||||
}
|
||||
// Guard against a divide-by-zero during the validator balance update.
|
||||
if total_balances.previous_epoch == 0 {
|
||||
return Err(Error::PreviousTotalBalanceIsZero);
|
||||
}
|
||||
// Guard against an out-of-bounds during the validator balance update.
|
||||
if statuses.statuses.len() != state.validator_balances.len() {
|
||||
return Err(Error::ValidatorStatusesInconsistent);
|
||||
}
|
||||
|
||||
// Justification and finalization
|
||||
|
||||
let epochs_since_finality = next_epoch - state.finalized_epoch;
|
||||
|
||||
state.validator_balances = state
|
||||
.validator_balances
|
||||
.par_iter()
|
||||
.enumerate()
|
||||
.map(|(index, &balance)| {
|
||||
let mut balance = balance;
|
||||
let status = &statuses.statuses[index];
|
||||
let base_reward = state.base_reward(index, base_reward_quotient, spec);
|
||||
|
||||
if epochs_since_finality <= 4 {
|
||||
// Expected FFG source
|
||||
if status.is_previous_epoch_attester {
|
||||
safe_add_assign!(
|
||||
balance,
|
||||
base_reward * total_balances.previous_epoch_attesters
|
||||
/ total_balances.previous_epoch
|
||||
);
|
||||
} else if status.is_active_in_previous_epoch {
|
||||
safe_sub_assign!(balance, base_reward);
|
||||
}
|
||||
|
||||
// Expected FFG target
|
||||
if status.is_previous_epoch_boundary_attester {
|
||||
safe_add_assign!(
|
||||
balance,
|
||||
base_reward * total_balances.previous_epoch_boundary_attesters
|
||||
/ total_balances.previous_epoch
|
||||
);
|
||||
} else if status.is_active_in_previous_epoch {
|
||||
safe_sub_assign!(balance, base_reward);
|
||||
}
|
||||
|
||||
// Expected beacon chain head
|
||||
if status.is_previous_epoch_head_attester {
|
||||
safe_add_assign!(
|
||||
balance,
|
||||
base_reward * total_balances.previous_epoch_head_attesters
|
||||
/ total_balances.previous_epoch
|
||||
);
|
||||
} else if status.is_active_in_previous_epoch {
|
||||
safe_sub_assign!(balance, base_reward);
|
||||
};
|
||||
} else {
|
||||
let inactivity_penalty = state.inactivity_penalty(
|
||||
index,
|
||||
epochs_since_finality,
|
||||
base_reward_quotient,
|
||||
spec,
|
||||
);
|
||||
|
||||
if status.is_active_in_previous_epoch {
|
||||
if !status.is_previous_epoch_attester {
|
||||
safe_sub_assign!(balance, inactivity_penalty);
|
||||
}
|
||||
if !status.is_previous_epoch_boundary_attester {
|
||||
safe_sub_assign!(balance, inactivity_penalty);
|
||||
}
|
||||
if !status.is_previous_epoch_head_attester {
|
||||
safe_sub_assign!(balance, inactivity_penalty);
|
||||
}
|
||||
|
||||
if state.validator_registry[index].slashed {
|
||||
let base_reward = state.base_reward(index, base_reward_quotient, spec);
|
||||
safe_sub_assign!(balance, 2 * inactivity_penalty + base_reward);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Crosslinks
|
||||
|
||||
if let Some(ref info) = status.winning_root_info {
|
||||
safe_add_assign!(
|
||||
balance,
|
||||
base_reward * info.total_attesting_balance / info.total_committee_balance
|
||||
);
|
||||
} else {
|
||||
safe_sub_assign!(balance, base_reward);
|
||||
}
|
||||
|
||||
balance
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Attestation inclusion
|
||||
|
||||
// Guard against an out-of-bounds during the attester inclusion balance update.
|
||||
if statuses.statuses.len() != state.validator_registry.len() {
|
||||
return Err(Error::ValidatorStatusesInconsistent);
|
||||
}
|
||||
|
||||
for (index, _validator) in state.validator_registry.iter().enumerate() {
|
||||
let status = &statuses.statuses[index];
|
||||
|
||||
if status.is_previous_epoch_attester {
|
||||
let proposer_index = status.inclusion_info.proposer_index;
|
||||
let inclusion_distance = status.inclusion_info.distance;
|
||||
|
||||
let base_reward = state.base_reward(proposer_index, base_reward_quotient, spec);
|
||||
|
||||
if inclusion_distance > 0 && inclusion_distance < Slot::max_value() {
|
||||
safe_add_assign!(
|
||||
state.validator_balances[proposer_index],
|
||||
base_reward * spec.min_attestation_inclusion_delay
|
||||
/ inclusion_distance.as_u64()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Peforms a validator registry update, if required.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> {
|
||||
/// Spec v0.5.0
|
||||
pub fn finish_epoch_update(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> {
|
||||
let current_epoch = state.current_epoch(spec);
|
||||
let next_epoch = state.next_epoch(spec);
|
||||
|
||||
state.previous_shuffling_epoch = state.current_shuffling_epoch;
|
||||
state.previous_shuffling_start_shard = state.current_shuffling_start_shard;
|
||||
|
||||
state.previous_shuffling_seed = state.current_shuffling_seed;
|
||||
|
||||
let should_update_validator_registy = if state.finalized_epoch
|
||||
> state.validator_registry_update_epoch
|
||||
// This is a hack to allow us to update index roots and slashed balances for the next epoch.
|
||||
//
|
||||
// The indentation here is to make it obvious where the weird stuff happens.
|
||||
{
|
||||
(0..state.get_current_epoch_committee_count(spec)).all(|i| {
|
||||
let shard = (state.current_shuffling_start_shard + i as u64) % spec.shard_count;
|
||||
state.latest_crosslinks[shard as usize].epoch > state.validator_registry_update_epoch
|
||||
})
|
||||
} else {
|
||||
false
|
||||
};
|
||||
state.slot += 1;
|
||||
|
||||
if should_update_validator_registy {
|
||||
state.update_validator_registry(spec);
|
||||
// Set active index root
|
||||
let active_index_root = Hash256::from_slice(
|
||||
&state
|
||||
.get_active_validator_indices(next_epoch + spec.activation_exit_delay)
|
||||
.hash_tree_root()[..],
|
||||
);
|
||||
state.set_active_index_root(next_epoch, active_index_root, spec)?;
|
||||
|
||||
state.current_shuffling_epoch = next_epoch;
|
||||
state.current_shuffling_start_shard = (state.current_shuffling_start_shard
|
||||
+ state.get_current_epoch_committee_count(spec) as u64)
|
||||
% spec.shard_count;
|
||||
state.current_shuffling_seed = state.generate_seed(state.current_shuffling_epoch, spec)?
|
||||
} else {
|
||||
let epochs_since_last_registry_update =
|
||||
current_epoch - state.validator_registry_update_epoch;
|
||||
if (epochs_since_last_registry_update > 1)
|
||||
& epochs_since_last_registry_update.is_power_of_two()
|
||||
{
|
||||
state.current_shuffling_epoch = next_epoch;
|
||||
state.current_shuffling_seed =
|
||||
state.generate_seed(state.current_shuffling_epoch, spec)?
|
||||
}
|
||||
// Set total slashed balances
|
||||
state.set_slashed_balance(
|
||||
next_epoch,
|
||||
state.get_slashed_balance(current_epoch, spec)?,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
// Set randao mix
|
||||
state.set_randao_mix(
|
||||
next_epoch,
|
||||
*state.get_randao_mix(current_epoch, spec)?,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
state.slot -= 1;
|
||||
}
|
||||
|
||||
state.process_slashings(spec);
|
||||
state.process_exit_queue(spec);
|
||||
if next_epoch.as_u64() % (spec.slots_per_historical_root as u64 / spec.slots_per_epoch) == 0 {
|
||||
let historical_batch: HistoricalBatch = state.historical_batch();
|
||||
state
|
||||
.historical_roots
|
||||
.push(Hash256::from_slice(&historical_batch.hash_tree_root()[..]));
|
||||
}
|
||||
|
||||
state.previous_epoch_attestations = state.current_epoch_attestations.clone();
|
||||
state.current_epoch_attestations = vec![];
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the state's `latest_active_index_roots` field with a tree hash the active validator
|
||||
/// indices for the next epoch.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn update_active_tree_index_roots(
|
||||
state: &mut BeaconState,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let next_epoch = state.next_epoch(spec);
|
||||
|
||||
let active_tree_root = get_active_validator_indices(
|
||||
&state.validator_registry,
|
||||
next_epoch + Epoch::from(spec.activation_exit_delay),
|
||||
)
|
||||
.hash_tree_root();
|
||||
|
||||
state.latest_active_index_roots[(next_epoch.as_usize()
|
||||
+ spec.activation_exit_delay as usize)
|
||||
% spec.latest_active_index_roots_length] = Hash256::from_slice(&active_tree_root[..]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Advances the state's `latest_slashed_balances` field.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn update_latest_slashed_balances(state: &mut BeaconState, spec: &ChainSpec) {
|
||||
let current_epoch = state.current_epoch(spec);
|
||||
let next_epoch = state.next_epoch(spec);
|
||||
|
||||
state.latest_slashed_balances[next_epoch.as_usize() % spec.latest_slashed_exit_length] =
|
||||
state.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length];
|
||||
}
|
||||
|
||||
/// Removes all pending attestations from the previous epoch.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn clean_attestations(state: &mut BeaconState, spec: &ChainSpec) {
|
||||
let current_epoch = state.current_epoch(spec);
|
||||
|
||||
state.latest_attestations = state
|
||||
.latest_attestations
|
||||
.iter()
|
||||
.filter(|a| a.data.slot.epoch(spec.slots_per_epoch) >= current_epoch)
|
||||
.cloned()
|
||||
.collect();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
use super::validator_statuses::{TotalBalances, ValidatorStatus, ValidatorStatuses};
|
||||
use super::{Error, WinningRootHashSet};
|
||||
use integer_sqrt::IntegerSquareRoot;
|
||||
use types::*;
|
||||
|
||||
/// Use to track the changes to a validators balance.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Delta {
|
||||
rewards: u64,
|
||||
penalties: u64,
|
||||
}
|
||||
|
||||
impl Delta {
|
||||
/// Reward the validator with the `reward`.
|
||||
pub fn reward(&mut self, reward: u64) {
|
||||
self.rewards += reward;
|
||||
}
|
||||
|
||||
/// Penalize the validator with the `penalty`.
|
||||
pub fn penalize(&mut self, penalty: u64) {
|
||||
self.penalties += penalty;
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign for Delta {
|
||||
/// Use wrapping addition as that is how it's defined in the spec.
|
||||
fn add_assign(&mut self, other: Delta) {
|
||||
self.rewards += other.rewards;
|
||||
self.penalties += other.penalties;
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply attester and proposer rewards.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn apply_rewards(
|
||||
state: &mut BeaconState,
|
||||
validator_statuses: &mut ValidatorStatuses,
|
||||
winning_root_for_shards: &WinningRootHashSet,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
// Guard against an out-of-bounds during the validator balance update.
|
||||
if validator_statuses.statuses.len() != state.validator_balances.len() {
|
||||
return Err(Error::ValidatorStatusesInconsistent);
|
||||
}
|
||||
// Guard against an out-of-bounds during the attester inclusion balance update.
|
||||
if validator_statuses.statuses.len() != state.validator_registry.len() {
|
||||
return Err(Error::ValidatorStatusesInconsistent);
|
||||
}
|
||||
|
||||
let mut deltas = vec![Delta::default(); state.validator_balances.len()];
|
||||
|
||||
get_justification_and_finalization_deltas(&mut deltas, state, &validator_statuses, spec)?;
|
||||
get_crosslink_deltas(&mut deltas, state, &validator_statuses, spec)?;
|
||||
|
||||
// Apply the proposer deltas if we are finalizing normally.
|
||||
//
|
||||
// This is executed slightly differently to the spec because of the way our functions are
|
||||
// structured. It should be functionally equivalent.
|
||||
if epochs_since_finality(state, spec) <= 4 {
|
||||
get_proposer_deltas(
|
||||
&mut deltas,
|
||||
state,
|
||||
validator_statuses,
|
||||
winning_root_for_shards,
|
||||
spec,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Apply the deltas, over-flowing but not under-flowing (saturating at 0 instead).
|
||||
for (i, delta) in deltas.iter().enumerate() {
|
||||
state.validator_balances[i] += delta.rewards;
|
||||
state.validator_balances[i] = state.validator_balances[i].saturating_sub(delta.penalties);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Applies the attestation inclusion reward to each proposer for every validator who included an
|
||||
/// attestation in the previous epoch.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
fn get_proposer_deltas(
|
||||
deltas: &mut Vec<Delta>,
|
||||
state: &mut BeaconState,
|
||||
validator_statuses: &mut ValidatorStatuses,
|
||||
winning_root_for_shards: &WinningRootHashSet,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
// Update statuses with the information from winning roots.
|
||||
validator_statuses.process_winning_roots(state, winning_root_for_shards, spec)?;
|
||||
|
||||
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
|
||||
let mut delta = Delta::default();
|
||||
|
||||
if validator.is_previous_epoch_attester {
|
||||
let inclusion = validator
|
||||
.inclusion_info
|
||||
.expect("It is a logic error for an attester not to have an inclusion distance.");
|
||||
|
||||
let base_reward = get_base_reward(
|
||||
state,
|
||||
inclusion.proposer_index,
|
||||
validator_statuses.total_balances.previous_epoch,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
if inclusion.proposer_index >= deltas.len() {
|
||||
return Err(Error::ValidatorStatusesInconsistent);
|
||||
}
|
||||
|
||||
delta.reward(base_reward / spec.attestation_inclusion_reward_quotient);
|
||||
}
|
||||
|
||||
deltas[index] += delta;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply rewards for participation in attestations during the previous epoch.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
fn get_justification_and_finalization_deltas(
|
||||
deltas: &mut Vec<Delta>,
|
||||
state: &BeaconState,
|
||||
validator_statuses: &ValidatorStatuses,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let epochs_since_finality = epochs_since_finality(state, spec);
|
||||
|
||||
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
|
||||
let base_reward = get_base_reward(
|
||||
state,
|
||||
index,
|
||||
validator_statuses.total_balances.previous_epoch,
|
||||
spec,
|
||||
)?;
|
||||
let inactivity_penalty = get_inactivity_penalty(
|
||||
state,
|
||||
index,
|
||||
epochs_since_finality.as_u64(),
|
||||
validator_statuses.total_balances.previous_epoch,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
let delta = if epochs_since_finality <= 4 {
|
||||
compute_normal_justification_and_finalization_delta(
|
||||
&validator,
|
||||
&validator_statuses.total_balances,
|
||||
base_reward,
|
||||
spec,
|
||||
)
|
||||
} else {
|
||||
compute_inactivity_leak_delta(&validator, base_reward, inactivity_penalty, spec)
|
||||
};
|
||||
|
||||
deltas[index] += delta;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determine the delta for a single validator, if the chain is finalizing normally.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
fn compute_normal_justification_and_finalization_delta(
|
||||
validator: &ValidatorStatus,
|
||||
total_balances: &TotalBalances,
|
||||
base_reward: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Delta {
|
||||
let mut delta = Delta::default();
|
||||
|
||||
let boundary_attesting_balance = total_balances.previous_epoch_boundary_attesters;
|
||||
let total_balance = total_balances.previous_epoch;
|
||||
let total_attesting_balance = total_balances.previous_epoch_attesters;
|
||||
let matching_head_balance = total_balances.previous_epoch_boundary_attesters;
|
||||
|
||||
// Expected FFG source.
|
||||
if validator.is_previous_epoch_attester {
|
||||
delta.reward(base_reward * total_attesting_balance / total_balance);
|
||||
// Inclusion speed bonus
|
||||
let inclusion = validator
|
||||
.inclusion_info
|
||||
.expect("It is a logic error for an attester not to have an inclusion distance.");
|
||||
delta.reward(
|
||||
base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(),
|
||||
);
|
||||
} else if validator.is_active_in_previous_epoch {
|
||||
delta.penalize(base_reward);
|
||||
}
|
||||
|
||||
// Expected FFG target.
|
||||
if validator.is_previous_epoch_boundary_attester {
|
||||
delta.reward(base_reward / boundary_attesting_balance / total_balance);
|
||||
} else if validator.is_active_in_previous_epoch {
|
||||
delta.penalize(base_reward);
|
||||
}
|
||||
|
||||
// Expected head.
|
||||
if validator.is_previous_epoch_head_attester {
|
||||
delta.reward(base_reward * matching_head_balance / total_balance);
|
||||
} else if validator.is_active_in_previous_epoch {
|
||||
delta.penalize(base_reward);
|
||||
};
|
||||
|
||||
// Proposer bonus is handled in `apply_proposer_deltas`.
|
||||
//
|
||||
// This function only computes the delta for a single validator, so it cannot also return a
|
||||
// delta for a validator.
|
||||
|
||||
delta
|
||||
}
|
||||
|
||||
/// Determine the delta for a single delta, assuming the chain is _not_ finalizing normally.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
fn compute_inactivity_leak_delta(
|
||||
validator: &ValidatorStatus,
|
||||
base_reward: u64,
|
||||
inactivity_penalty: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Delta {
|
||||
let mut delta = Delta::default();
|
||||
|
||||
if validator.is_active_in_previous_epoch {
|
||||
if !validator.is_previous_epoch_attester {
|
||||
delta.penalize(inactivity_penalty);
|
||||
} else {
|
||||
// If a validator did attest, apply a small penalty for getting attestations included
|
||||
// late.
|
||||
let inclusion = validator
|
||||
.inclusion_info
|
||||
.expect("It is a logic error for an attester not to have an inclusion distance.");
|
||||
delta.reward(
|
||||
base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(),
|
||||
);
|
||||
delta.penalize(base_reward);
|
||||
}
|
||||
|
||||
if !validator.is_previous_epoch_boundary_attester {
|
||||
delta.reward(inactivity_penalty);
|
||||
}
|
||||
|
||||
if !validator.is_previous_epoch_head_attester {
|
||||
delta.penalize(inactivity_penalty);
|
||||
}
|
||||
}
|
||||
|
||||
// Penalize slashed-but-inactive validators as though they were active but offline.
|
||||
if !validator.is_active_in_previous_epoch
|
||||
& validator.is_slashed
|
||||
& !validator.is_withdrawable_in_current_epoch
|
||||
{
|
||||
delta.penalize(2 * inactivity_penalty + base_reward);
|
||||
}
|
||||
|
||||
delta
|
||||
}
|
||||
|
||||
/// Calculate the deltas based upon the winning roots for attestations during the previous epoch.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
fn get_crosslink_deltas(
|
||||
deltas: &mut Vec<Delta>,
|
||||
state: &BeaconState,
|
||||
validator_statuses: &ValidatorStatuses,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
|
||||
let mut delta = Delta::default();
|
||||
|
||||
let base_reward = get_base_reward(
|
||||
state,
|
||||
index,
|
||||
validator_statuses.total_balances.previous_epoch,
|
||||
spec,
|
||||
)?;
|
||||
|
||||
if let Some(ref winning_root) = validator.winning_root_info {
|
||||
delta.reward(
|
||||
base_reward * winning_root.total_attesting_balance
|
||||
/ winning_root.total_committee_balance,
|
||||
);
|
||||
} else {
|
||||
delta.penalize(base_reward);
|
||||
}
|
||||
|
||||
deltas[index] += delta;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the base reward for some validator.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
fn get_base_reward(
|
||||
state: &BeaconState,
|
||||
index: usize,
|
||||
previous_total_balance: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<u64, BeaconStateError> {
|
||||
if previous_total_balance == 0 {
|
||||
Ok(0)
|
||||
} else {
|
||||
let adjusted_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient;
|
||||
Ok(state.get_effective_balance(index, spec)? / adjusted_quotient / 5)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the inactivity penalty for some validator.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
fn get_inactivity_penalty(
|
||||
state: &BeaconState,
|
||||
index: usize,
|
||||
epochs_since_finality: u64,
|
||||
previous_total_balance: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<u64, BeaconStateError> {
|
||||
Ok(get_base_reward(state, index, previous_total_balance, spec)?
|
||||
+ state.get_effective_balance(index, spec)? * epochs_since_finality
|
||||
/ spec.inactivity_penalty_quotient
|
||||
/ 2)
|
||||
}
|
||||
|
||||
/// Returns the epochs since the last finalized epoch.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
fn epochs_since_finality(state: &BeaconState, spec: &ChainSpec) -> Epoch {
|
||||
state.current_epoch(spec) + 1 - state.finalized_epoch
|
||||
}
|
||||
@@ -9,6 +9,7 @@ pub enum EpochProcessingError {
|
||||
PreviousTotalBalanceIsZero,
|
||||
InclusionDistanceZero,
|
||||
ValidatorStatusesInconsistent,
|
||||
DeltasInconsistent,
|
||||
/// Unable to get the inclusion distance for a validator that should have an inclusion
|
||||
/// distance. This indicates an internal inconsistency.
|
||||
///
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
use crate::common::verify_bitfield_length;
|
||||
use types::*;
|
||||
|
||||
/// Returns validator indices which participated in the attestation.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn get_attestation_participants(
|
||||
state: &BeaconState,
|
||||
attestation_data: &AttestationData,
|
||||
bitfield: &Bitfield,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Vec<usize>, BeaconStateError> {
|
||||
let epoch = attestation_data.slot.epoch(spec.slots_per_epoch);
|
||||
|
||||
let crosslink_committee =
|
||||
state.get_crosslink_committee_for_shard(epoch, attestation_data.shard, spec)?;
|
||||
|
||||
if crosslink_committee.slot != attestation_data.slot {
|
||||
return Err(BeaconStateError::NoCommitteeForShard);
|
||||
}
|
||||
|
||||
let committee = &crosslink_committee.committee;
|
||||
|
||||
if !verify_bitfield_length(&bitfield, committee.len()) {
|
||||
return Err(BeaconStateError::InvalidBitfield);
|
||||
}
|
||||
|
||||
let mut participants = Vec::with_capacity(committee.len());
|
||||
for (i, validator_index) in committee.iter().enumerate() {
|
||||
match bitfield.get(i) {
|
||||
Ok(bit) if bit => participants.push(*validator_index),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
participants.shrink_to_fit();
|
||||
|
||||
Ok(participants)
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
use super::errors::InclusionError;
|
||||
use super::get_attestation_participants::get_attestation_participants;
|
||||
use types::*;
|
||||
|
||||
/// Returns the distance between the first included attestation for some validator and this
|
||||
/// slot.
|
||||
///
|
||||
/// Note: In the spec this is defined "inline", not as a helper function.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn inclusion_distance(
|
||||
state: &BeaconState,
|
||||
attestations: &[&PendingAttestation],
|
||||
@@ -19,9 +18,7 @@ pub fn inclusion_distance(
|
||||
|
||||
/// Returns the slot of the earliest included attestation for some validator.
|
||||
///
|
||||
/// Note: In the spec this is defined "inline", not as a helper function.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn inclusion_slot(
|
||||
state: &BeaconState,
|
||||
attestations: &[&PendingAttestation],
|
||||
@@ -34,9 +31,7 @@ pub fn inclusion_slot(
|
||||
|
||||
/// Finds the earliest included attestation for some validator.
|
||||
///
|
||||
/// Note: In the spec this is defined "inline", not as a helper function.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
fn earliest_included_attestation(
|
||||
state: &BeaconState,
|
||||
attestations: &[&PendingAttestation],
|
||||
@@ -47,7 +42,7 @@ fn earliest_included_attestation(
|
||||
|
||||
for (i, a) in attestations.iter().enumerate() {
|
||||
let participants =
|
||||
state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?;
|
||||
get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?;
|
||||
if participants.iter().any(|i| *i == validator_index) {
|
||||
included_attestations.push(i);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
use crate::common::exit_validator;
|
||||
use types::{BeaconStateError as Error, *};
|
||||
|
||||
/// Iterate through the validator registry and eject active validators with balance below
|
||||
/// ``EJECTION_BALANCE``.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn process_ejections(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> {
|
||||
// There is an awkward double (triple?) loop here because we can't loop across the borrowed
|
||||
// active validator indices and mutate state in the one loop.
|
||||
let exitable: Vec<usize> = state
|
||||
.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
|
||||
.iter()
|
||||
.filter_map(|&i| {
|
||||
if state.validator_balances[i as usize] < spec.ejection_balance {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
for validator_index in exitable {
|
||||
exit_validator(state, validator_index, spec)?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use types::*;
|
||||
|
||||
/// Process the exit queue.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn process_exit_queue(state: &mut BeaconState, spec: &ChainSpec) {
|
||||
let current_epoch = state.current_epoch(spec);
|
||||
|
||||
let eligible = |index: usize| {
|
||||
let validator = &state.validator_registry[index];
|
||||
|
||||
if validator.withdrawable_epoch != spec.far_future_epoch {
|
||||
false
|
||||
} else {
|
||||
current_epoch >= validator.exit_epoch + spec.min_validator_withdrawability_delay
|
||||
}
|
||||
};
|
||||
|
||||
let mut eligable_indices: Vec<usize> = (0..state.validator_registry.len())
|
||||
.filter(|i| eligible(*i))
|
||||
.collect();
|
||||
eligable_indices.sort_by_key(|i| state.validator_registry[*i].exit_epoch);
|
||||
|
||||
for (dequeues, index) in eligable_indices.iter().enumerate() {
|
||||
if dequeues as u64 >= spec.max_exit_dequeues_per_epoch {
|
||||
break;
|
||||
}
|
||||
prepare_validator_for_withdrawal(state, *index, spec);
|
||||
}
|
||||
}
|
||||
|
||||
/// Initiate an exit for the validator of the given `index`.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
fn prepare_validator_for_withdrawal(
|
||||
state: &mut BeaconState,
|
||||
validator_index: usize,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
state.validator_registry[validator_index].withdrawable_epoch =
|
||||
state.current_epoch(spec) + spec.min_validator_withdrawability_delay;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
use types::{BeaconStateError as Error, *};
|
||||
|
||||
/// Process slashings.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn process_slashings(
|
||||
state: &mut BeaconState,
|
||||
current_total_balance: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let current_epoch = state.current_epoch(spec);
|
||||
|
||||
let total_at_start = state.get_slashed_balance(current_epoch + 1, spec)?;
|
||||
let total_at_end = state.get_slashed_balance(current_epoch, spec)?;
|
||||
let total_penalities = total_at_end - total_at_start;
|
||||
|
||||
for (index, validator) in state.validator_registry.iter().enumerate() {
|
||||
let should_penalize = current_epoch.as_usize()
|
||||
== validator.withdrawable_epoch.as_usize() - spec.latest_slashed_exit_length / 2;
|
||||
|
||||
if validator.slashed && should_penalize {
|
||||
let effective_balance = state.get_effective_balance(index, spec)?;
|
||||
|
||||
let penalty = std::cmp::max(
|
||||
effective_balance * std::cmp::min(total_penalities * 3, current_total_balance)
|
||||
/ current_total_balance,
|
||||
effective_balance / spec.min_penalty_quotient,
|
||||
);
|
||||
|
||||
state.validator_balances[index] -= penalty;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
use super::super::common::exit_validator;
|
||||
use super::Error;
|
||||
use types::*;
|
||||
|
||||
/// Peforms a validator registry update, if required.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn update_registry_and_shuffling_data(
|
||||
state: &mut BeaconState,
|
||||
current_total_balance: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
// First set previous shuffling data to current shuffling data.
|
||||
state.previous_shuffling_epoch = state.current_shuffling_epoch;
|
||||
state.previous_shuffling_start_shard = state.previous_shuffling_start_shard;
|
||||
state.previous_shuffling_seed = state.previous_shuffling_seed;
|
||||
|
||||
let current_epoch = state.current_epoch(spec);
|
||||
let next_epoch = current_epoch + 1;
|
||||
|
||||
// Check we should update, and if so, update.
|
||||
if should_update_validator_registry(state, spec)? {
|
||||
update_validator_registry(state, current_total_balance, spec)?;
|
||||
|
||||
// If we update the registry, update the shuffling data and shards as well.
|
||||
state.current_shuffling_epoch = next_epoch;
|
||||
state.current_shuffling_start_shard = {
|
||||
let active_validators =
|
||||
state.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?;
|
||||
let epoch_committee_count = spec.get_epoch_committee_count(active_validators.len());
|
||||
|
||||
(state.current_shuffling_start_shard + epoch_committee_count) % spec.shard_count
|
||||
};
|
||||
state.current_shuffling_seed = state.generate_seed(state.current_shuffling_epoch, spec)?;
|
||||
} else {
|
||||
// If processing at least on crosslink keeps failing, the reshuffle every power of two, but
|
||||
// don't update the current_shuffling_start_shard.
|
||||
let epochs_since_last_update = current_epoch - state.validator_registry_update_epoch;
|
||||
|
||||
if epochs_since_last_update > 1 && epochs_since_last_update.is_power_of_two() {
|
||||
state.current_shuffling_epoch = next_epoch;
|
||||
state.current_shuffling_seed =
|
||||
state.generate_seed(state.current_shuffling_epoch, spec)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `true` if the validator registry should be updated during an epoch processing.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn should_update_validator_registry(
|
||||
state: &BeaconState,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<bool, BeaconStateError> {
|
||||
if state.finalized_epoch <= state.validator_registry_update_epoch {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let num_active_validators = state
|
||||
.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
|
||||
.len();
|
||||
let current_epoch_committee_count = spec.get_epoch_committee_count(num_active_validators);
|
||||
|
||||
for shard in (0..current_epoch_committee_count)
|
||||
.map(|i| (state.current_shuffling_start_shard + i as u64) % spec.shard_count)
|
||||
{
|
||||
if state.latest_crosslinks[shard as usize].epoch <= state.validator_registry_update_epoch {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Update validator registry, activating/exiting validators if possible.
|
||||
///
|
||||
/// Note: Utilizes the cache and will fail if the appropriate cache is not initialized.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn update_validator_registry(
|
||||
state: &mut BeaconState,
|
||||
current_total_balance: u64,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let current_epoch = state.current_epoch(spec);
|
||||
|
||||
let max_balance_churn = std::cmp::max(
|
||||
spec.max_deposit_amount,
|
||||
current_total_balance / (2 * spec.max_balance_churn_quotient),
|
||||
);
|
||||
|
||||
// Activate validators within the allowable balance churn.
|
||||
let mut balance_churn = 0;
|
||||
for index in 0..state.validator_registry.len() {
|
||||
let not_activated =
|
||||
state.validator_registry[index].activation_epoch == spec.far_future_epoch;
|
||||
let has_enough_balance = state.validator_balances[index] >= spec.max_deposit_amount;
|
||||
|
||||
if not_activated && has_enough_balance {
|
||||
// Check the balance churn would be within the allowance.
|
||||
balance_churn += state.get_effective_balance(index, spec)?;
|
||||
if balance_churn > max_balance_churn {
|
||||
break;
|
||||
}
|
||||
|
||||
activate_validator(state, index, false, spec);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit validators within the allowable balance churn.
|
||||
let mut balance_churn = 0;
|
||||
for index in 0..state.validator_registry.len() {
|
||||
let not_exited = state.validator_registry[index].exit_epoch == spec.far_future_epoch;
|
||||
let has_initiated_exit = state.validator_registry[index].initiated_exit;
|
||||
|
||||
if not_exited && has_initiated_exit {
|
||||
// Check the balance churn would be within the allowance.
|
||||
balance_churn += state.get_effective_balance(index, spec)?;
|
||||
if balance_churn > max_balance_churn {
|
||||
break;
|
||||
}
|
||||
|
||||
exit_validator(state, index, spec)?;
|
||||
}
|
||||
}
|
||||
|
||||
state.validator_registry_update_epoch = current_epoch;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Activate the validator of the given ``index``.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
pub fn activate_validator(
|
||||
state: &mut BeaconState,
|
||||
validator_index: usize,
|
||||
is_genesis: bool,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
let current_epoch = state.current_epoch(spec);
|
||||
|
||||
state.validator_registry[validator_index].activation_epoch = if is_genesis {
|
||||
spec.genesis_epoch
|
||||
} else {
|
||||
state.get_delayed_activation_exit_epoch(current_epoch, spec)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::get_attestation_participants::get_attestation_participants;
|
||||
use super::WinningRootHashSet;
|
||||
use types::*;
|
||||
|
||||
@@ -22,7 +23,7 @@ pub struct WinningRootInfo {
|
||||
}
|
||||
|
||||
/// The information required to reward a block producer for including an attestation in a block.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct InclusionInfo {
|
||||
/// The earliest slot a validator had an attestation included in the previous epoch.
|
||||
pub slot: Slot,
|
||||
@@ -58,7 +59,11 @@ impl InclusionInfo {
|
||||
|
||||
/// Information required to reward some validator during the current and previous epoch.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct AttesterStatus {
|
||||
pub struct ValidatorStatus {
|
||||
/// True if the validator has been slashed, ever.
|
||||
pub is_slashed: bool,
|
||||
/// True if the validator can withdraw in the current epoch.
|
||||
pub is_withdrawable_in_current_epoch: bool,
|
||||
/// True if the validator was active in the state's _current_ epoch.
|
||||
pub is_active_in_current_epoch: bool,
|
||||
/// True if the validator was active in the state's _previous_ epoch.
|
||||
@@ -80,14 +85,14 @@ pub struct AttesterStatus {
|
||||
|
||||
/// Information used to reward the block producer of this validators earliest-included
|
||||
/// attestation.
|
||||
pub inclusion_info: InclusionInfo,
|
||||
pub inclusion_info: Option<InclusionInfo>,
|
||||
/// Information used to reward/penalize the validator if they voted in the super-majority for
|
||||
/// some shard block.
|
||||
pub winning_root_info: Option<WinningRootInfo>,
|
||||
}
|
||||
|
||||
impl AttesterStatus {
|
||||
/// Accepts some `other` `AttesterStatus` and updates `self` if required.
|
||||
impl ValidatorStatus {
|
||||
/// Accepts some `other` `ValidatorStatus` and updates `self` if required.
|
||||
///
|
||||
/// Will never set one of the `bool` fields to `false`, it will only set it to `true` if other
|
||||
/// contains a `true` field.
|
||||
@@ -96,6 +101,8 @@ impl AttesterStatus {
|
||||
pub fn update(&mut self, other: &Self) {
|
||||
// Update all the bool fields, only updating `self` if `other` is true (never setting
|
||||
// `self` to false).
|
||||
set_self_if_other_is_true!(self, other, is_slashed);
|
||||
set_self_if_other_is_true!(self, other, is_withdrawable_in_current_epoch);
|
||||
set_self_if_other_is_true!(self, other, is_active_in_current_epoch);
|
||||
set_self_if_other_is_true!(self, other, is_active_in_previous_epoch);
|
||||
set_self_if_other_is_true!(self, other, is_current_epoch_attester);
|
||||
@@ -104,7 +111,13 @@ impl AttesterStatus {
|
||||
set_self_if_other_is_true!(self, other, is_previous_epoch_boundary_attester);
|
||||
set_self_if_other_is_true!(self, other, is_previous_epoch_head_attester);
|
||||
|
||||
self.inclusion_info.update(&other.inclusion_info);
|
||||
if let Some(other_info) = other.inclusion_info {
|
||||
if let Some(self_info) = self.inclusion_info.as_mut() {
|
||||
self_info.update(&other_info);
|
||||
} else {
|
||||
self.inclusion_info = other.inclusion_info;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +149,7 @@ pub struct TotalBalances {
|
||||
#[derive(Clone)]
|
||||
pub struct ValidatorStatuses {
|
||||
/// Information about each individual validator from the state's validator registy.
|
||||
pub statuses: Vec<AttesterStatus>,
|
||||
pub statuses: Vec<ValidatorStatus>,
|
||||
/// Summed balances for various sets of validators.
|
||||
pub total_balances: TotalBalances,
|
||||
}
|
||||
@@ -147,52 +160,60 @@ impl ValidatorStatuses {
|
||||
/// - Active validators
|
||||
/// - Total balances for the current and previous epochs.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn new(state: &BeaconState, spec: &ChainSpec) -> Self {
|
||||
/// Spec v0.5.0
|
||||
pub fn new(state: &BeaconState, spec: &ChainSpec) -> Result<Self, BeaconStateError> {
|
||||
let mut statuses = Vec::with_capacity(state.validator_registry.len());
|
||||
let mut total_balances = TotalBalances::default();
|
||||
|
||||
for (i, validator) in state.validator_registry.iter().enumerate() {
|
||||
let mut status = AttesterStatus::default();
|
||||
let mut status = ValidatorStatus {
|
||||
is_slashed: validator.slashed,
|
||||
is_withdrawable_in_current_epoch: validator
|
||||
.is_withdrawable_at(state.current_epoch(spec)),
|
||||
..ValidatorStatus::default()
|
||||
};
|
||||
|
||||
if validator.is_active_at(state.current_epoch(spec)) {
|
||||
status.is_active_in_current_epoch = true;
|
||||
total_balances.current_epoch += state.get_effective_balance(i, spec);
|
||||
total_balances.current_epoch += state.get_effective_balance(i, spec)?;
|
||||
}
|
||||
|
||||
if validator.is_active_at(state.previous_epoch(spec)) {
|
||||
status.is_active_in_previous_epoch = true;
|
||||
total_balances.previous_epoch += state.get_effective_balance(i, spec);
|
||||
total_balances.previous_epoch += state.get_effective_balance(i, spec)?;
|
||||
}
|
||||
|
||||
statuses.push(status);
|
||||
}
|
||||
|
||||
Self {
|
||||
Ok(Self {
|
||||
statuses,
|
||||
total_balances,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Process some attestations from the given `state` updating the `statuses` and
|
||||
/// `total_balances` fields.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn process_attestations(
|
||||
&mut self,
|
||||
state: &BeaconState,
|
||||
attestations: &[PendingAttestation],
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), BeaconStateError> {
|
||||
for a in attestations {
|
||||
for a in state
|
||||
.previous_epoch_attestations
|
||||
.iter()
|
||||
.chain(state.current_epoch_attestations.iter())
|
||||
{
|
||||
let attesting_indices =
|
||||
state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?;
|
||||
let attesting_balance = state.get_total_balance(&attesting_indices, spec);
|
||||
get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?;
|
||||
let attesting_balance = state.get_total_balance(&attesting_indices, spec)?;
|
||||
|
||||
let mut status = AttesterStatus::default();
|
||||
let mut status = ValidatorStatus::default();
|
||||
|
||||
// Profile this attestation, updating the total balances and generating an
|
||||
// `AttesterStatus` object that applies to all participants in the attestation.
|
||||
// `ValidatorStatus` object that applies to all participants in the attestation.
|
||||
if is_from_epoch(a, state.current_epoch(spec), spec) {
|
||||
self.total_balances.current_epoch_attesters += attesting_balance;
|
||||
status.is_current_epoch_attester = true;
|
||||
@@ -206,11 +227,16 @@ impl ValidatorStatuses {
|
||||
status.is_previous_epoch_attester = true;
|
||||
|
||||
// The inclusion slot and distance are only required for previous epoch attesters.
|
||||
status.inclusion_info = InclusionInfo {
|
||||
let relative_epoch = RelativeEpoch::from_slot(state.slot, a.data.slot, spec)?;
|
||||
status.inclusion_info = Some(InclusionInfo {
|
||||
slot: a.inclusion_slot,
|
||||
distance: inclusion_distance(a),
|
||||
proposer_index: state.get_beacon_proposer_index(a.inclusion_slot, spec)?,
|
||||
};
|
||||
proposer_index: state.get_beacon_proposer_index(
|
||||
a.inclusion_slot,
|
||||
relative_epoch,
|
||||
spec,
|
||||
)?,
|
||||
});
|
||||
|
||||
if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? {
|
||||
self.total_balances.previous_epoch_boundary_attesters += attesting_balance;
|
||||
@@ -235,7 +261,7 @@ impl ValidatorStatuses {
|
||||
/// Update the `statuses` for each validator based upon whether or not they attested to the
|
||||
/// "winning" shard block root for the previous epoch.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn process_winning_roots(
|
||||
&mut self,
|
||||
state: &BeaconState,
|
||||
@@ -248,11 +274,10 @@ impl ValidatorStatuses {
|
||||
state.get_crosslink_committees_at_slot(slot, spec)?;
|
||||
|
||||
// Loop through each committee in the slot.
|
||||
for (crosslink_committee, shard) in crosslink_committees_at_slot {
|
||||
for c in crosslink_committees_at_slot {
|
||||
// If there was some winning crosslink root for the committee's shard.
|
||||
if let Some(winning_root) = winning_roots.get(&shard) {
|
||||
let total_committee_balance =
|
||||
state.get_total_balance(&crosslink_committee, spec);
|
||||
if let Some(winning_root) = winning_roots.get(&c.shard) {
|
||||
let total_committee_balance = state.get_total_balance(&c.committee, spec)?;
|
||||
for &validator_index in &winning_root.attesting_validator_indices {
|
||||
// Take note of the balance information for the winning root, it will be
|
||||
// used later to calculate rewards for that validator.
|
||||
@@ -272,14 +297,14 @@ impl ValidatorStatuses {
|
||||
/// Returns the distance between when the attestation was created and when it was included in a
|
||||
/// block.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
fn inclusion_distance(a: &PendingAttestation) -> Slot {
|
||||
a.inclusion_slot - a.data.slot
|
||||
}
|
||||
|
||||
/// Returns `true` if some `PendingAttestation` is from the supplied `epoch`.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool {
|
||||
a.data.slot.epoch(spec.slots_per_epoch) == epoch
|
||||
}
|
||||
@@ -287,7 +312,7 @@ fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool
|
||||
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
|
||||
/// the first slot of the given epoch.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
fn has_common_epoch_boundary_root(
|
||||
a: &PendingAttestation,
|
||||
state: &BeaconState,
|
||||
@@ -295,25 +320,21 @@ fn has_common_epoch_boundary_root(
|
||||
spec: &ChainSpec,
|
||||
) -> Result<bool, BeaconStateError> {
|
||||
let slot = epoch.start_slot(spec.slots_per_epoch);
|
||||
let state_boundary_root = *state
|
||||
.get_block_root(slot, spec)
|
||||
.ok_or_else(|| BeaconStateError::InsufficientBlockRoots)?;
|
||||
let state_boundary_root = *state.get_block_root(slot, spec)?;
|
||||
|
||||
Ok(a.data.epoch_boundary_root == state_boundary_root)
|
||||
Ok(a.data.target_root == state_boundary_root)
|
||||
}
|
||||
|
||||
/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for
|
||||
/// the current slot of the `PendingAttestation`.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
fn has_common_beacon_block_root(
|
||||
a: &PendingAttestation,
|
||||
state: &BeaconState,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<bool, BeaconStateError> {
|
||||
let state_block_root = *state
|
||||
.get_block_root(a.data.slot, spec)
|
||||
.ok_or_else(|| BeaconStateError::InsufficientBlockRoots)?;
|
||||
let state_block_root = *state.get_block_root(a.data.slot, spec)?;
|
||||
|
||||
Ok(a.data.beacon_block_root == state_block_root)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::get_attestation_participants::get_attestation_participants;
|
||||
use std::collections::HashSet;
|
||||
use std::iter::FromIterator;
|
||||
use types::*;
|
||||
@@ -13,14 +14,14 @@ impl WinningRoot {
|
||||
/// Returns `true` if `self` is a "better" candidate than `other`.
|
||||
///
|
||||
/// A winning root is "better" than another if it has a higher `total_attesting_balance`. Ties
|
||||
/// are broken by favouring the lower `crosslink_data_root` value.
|
||||
/// are broken by favouring the higher `crosslink_data_root` value.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn is_better_than(&self, other: &Self) -> bool {
|
||||
if self.total_attesting_balance > other.total_attesting_balance {
|
||||
true
|
||||
} else if self.total_attesting_balance == other.total_attesting_balance {
|
||||
self.crosslink_data_root < other.crosslink_data_root
|
||||
self.crosslink_data_root > other.crosslink_data_root
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -33,22 +34,21 @@ impl WinningRoot {
|
||||
/// The `WinningRoot` object also contains additional fields that are useful in later stages of
|
||||
/// per-epoch processing.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn winning_root(
|
||||
state: &BeaconState,
|
||||
shard: u64,
|
||||
current_epoch_attestations: &[&PendingAttestation],
|
||||
previous_epoch_attestations: &[&PendingAttestation],
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Option<WinningRoot>, BeaconStateError> {
|
||||
let mut winning_root: Option<WinningRoot> = None;
|
||||
|
||||
let crosslink_data_roots: HashSet<Hash256> = HashSet::from_iter(
|
||||
previous_epoch_attestations
|
||||
state
|
||||
.previous_epoch_attestations
|
||||
.iter()
|
||||
.chain(current_epoch_attestations.iter())
|
||||
.chain(state.current_epoch_attestations.iter())
|
||||
.filter_map(|a| {
|
||||
if a.data.shard == shard {
|
||||
if is_eligible_for_winning_root(state, a, shard) {
|
||||
Some(a.data.crosslink_data_root)
|
||||
} else {
|
||||
None
|
||||
@@ -57,18 +57,17 @@ pub fn winning_root(
|
||||
);
|
||||
|
||||
for crosslink_data_root in crosslink_data_roots {
|
||||
let attesting_validator_indices = get_attesting_validator_indices(
|
||||
state,
|
||||
shard,
|
||||
current_epoch_attestations,
|
||||
previous_epoch_attestations,
|
||||
&crosslink_data_root,
|
||||
spec,
|
||||
)?;
|
||||
let attesting_validator_indices =
|
||||
get_attesting_validator_indices(state, shard, &crosslink_data_root, spec)?;
|
||||
|
||||
let total_attesting_balance: u64 = attesting_validator_indices
|
||||
.iter()
|
||||
.fold(0, |acc, i| acc + state.get_effective_balance(*i, spec));
|
||||
let total_attesting_balance: u64 =
|
||||
attesting_validator_indices
|
||||
.iter()
|
||||
.try_fold(0_u64, |acc, i| {
|
||||
state
|
||||
.get_effective_balance(*i, spec)
|
||||
.and_then(|bal| Ok(acc + bal))
|
||||
})?;
|
||||
|
||||
let candidate = WinningRoot {
|
||||
crosslink_data_root,
|
||||
@@ -88,25 +87,36 @@ pub fn winning_root(
|
||||
Ok(winning_root)
|
||||
}
|
||||
|
||||
/// Returns all indices which voted for a given crosslink. May contain duplicates.
|
||||
/// Returns `true` if pending attestation `a` is eligible to become a winning root.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
fn is_eligible_for_winning_root(state: &BeaconState, a: &PendingAttestation, shard: Shard) -> bool {
|
||||
if shard >= state.latest_crosslinks.len() as u64 {
|
||||
return false;
|
||||
}
|
||||
|
||||
a.data.previous_crosslink == state.latest_crosslinks[shard as usize]
|
||||
}
|
||||
|
||||
/// Returns all indices which voted for a given crosslink. Does not contain duplicates.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
fn get_attesting_validator_indices(
|
||||
state: &BeaconState,
|
||||
shard: u64,
|
||||
current_epoch_attestations: &[&PendingAttestation],
|
||||
previous_epoch_attestations: &[&PendingAttestation],
|
||||
crosslink_data_root: &Hash256,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<Vec<usize>, BeaconStateError> {
|
||||
let mut indices = vec![];
|
||||
|
||||
for a in current_epoch_attestations
|
||||
for a in state
|
||||
.current_epoch_attestations
|
||||
.iter()
|
||||
.chain(previous_epoch_attestations.iter())
|
||||
.chain(state.previous_epoch_attestations.iter())
|
||||
{
|
||||
if (a.data.shard == shard) && (a.data.crosslink_data_root == *crosslink_data_root) {
|
||||
indices.append(&mut state.get_attestation_participants(
|
||||
indices.append(&mut get_attestation_participants(
|
||||
state,
|
||||
&a.data,
|
||||
&a.aggregation_bitfield,
|
||||
spec,
|
||||
@@ -114,5 +124,41 @@ fn get_attesting_validator_indices(
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the list (required for dedup). "Unstable" means the sort may re-order equal elements,
|
||||
// this causes no issue here.
|
||||
//
|
||||
// These sort + dedup ops are potentially good CPU time optimisation targets.
|
||||
indices.sort_unstable();
|
||||
// Remove all duplicate indices (requires a sorted list).
|
||||
indices.dedup();
|
||||
|
||||
Ok(indices)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_better_than() {
|
||||
let worse = WinningRoot {
|
||||
crosslink_data_root: Hash256::from_slice(&[1; 32]),
|
||||
attesting_validator_indices: vec![],
|
||||
total_attesting_balance: 42,
|
||||
};
|
||||
|
||||
let better = WinningRoot {
|
||||
crosslink_data_root: Hash256::from_slice(&[2; 32]),
|
||||
..worse.clone()
|
||||
};
|
||||
|
||||
assert!(better.is_better_than(&worse));
|
||||
|
||||
let better = WinningRoot {
|
||||
total_attesting_balance: worse.total_attesting_balance + 1,
|
||||
..worse.clone()
|
||||
};
|
||||
|
||||
assert!(better.is_better_than(&worse));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::*;
|
||||
use types::{BeaconState, BeaconStateError, ChainSpec, Hash256};
|
||||
use ssz::TreeHash;
|
||||
use types::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
@@ -9,12 +10,14 @@ pub enum Error {
|
||||
|
||||
/// Advances a state forward by one slot, performing per-epoch processing if required.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn per_slot_processing(
|
||||
state: &mut BeaconState,
|
||||
previous_block_root: Hash256,
|
||||
latest_block_header: &BeaconBlockHeader,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
cache_state(state, latest_block_header, spec)?;
|
||||
|
||||
if (state.slot + 1) % spec.slots_per_epoch == 0 {
|
||||
per_epoch_processing(state, spec)?;
|
||||
state.advance_caches();
|
||||
@@ -22,27 +25,35 @@ pub fn per_slot_processing(
|
||||
|
||||
state.slot += 1;
|
||||
|
||||
update_block_roots(state, previous_block_root, spec);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Updates the state's block roots as per-slot processing is performed.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn update_block_roots(state: &mut BeaconState, previous_block_root: Hash256, spec: &ChainSpec) {
|
||||
state.latest_block_roots[(state.slot.as_usize() - 1) % spec.latest_block_roots_length] =
|
||||
previous_block_root;
|
||||
fn cache_state(
|
||||
state: &mut BeaconState,
|
||||
latest_block_header: &BeaconBlockHeader,
|
||||
spec: &ChainSpec,
|
||||
) -> Result<(), Error> {
|
||||
let previous_slot_state_root = Hash256::from_slice(&state.hash_tree_root()[..]);
|
||||
|
||||
if state.slot.as_usize() % spec.latest_block_roots_length == 0 {
|
||||
let root = merkle_root(&state.latest_block_roots[..]);
|
||||
state.batched_block_roots.push(root);
|
||||
// Note: increment the state slot here to allow use of our `state_root` and `block_root`
|
||||
// getter/setter functions.
|
||||
//
|
||||
// This is a bit hacky, however it gets the job safely without lots of code.
|
||||
let previous_slot = state.slot;
|
||||
state.slot += 1;
|
||||
|
||||
// Store the previous slot's post-state transition root.
|
||||
if state.latest_block_header.state_root == spec.zero_hash {
|
||||
state.latest_block_header.state_root = previous_slot_state_root
|
||||
}
|
||||
}
|
||||
|
||||
fn merkle_root(_input: &[Hash256]) -> Hash256 {
|
||||
// TODO: implement correctly.
|
||||
Hash256::zero()
|
||||
let latest_block_root = Hash256::from_slice(&latest_block_header.hash_tree_root()[..]);
|
||||
state.set_block_root(previous_slot, latest_block_root, spec)?;
|
||||
|
||||
// Set the state slot back to what it should be.
|
||||
state.slot -= 1;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl From<BeaconStateError> for Error {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
use serde_derive::Deserialize;
|
||||
use types::*;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TestCase {
|
||||
pub name: String,
|
||||
pub config: ChainSpec,
|
||||
pub verify_signatures: bool,
|
||||
pub initial_state: BeaconState,
|
||||
pub blocks: Vec<BeaconBlock>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TestDoc {
|
||||
pub title: String,
|
||||
pub summary: String,
|
||||
pub fork: String,
|
||||
pub test_cases: Vec<TestCase>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn yaml() {
|
||||
use serde_yaml;
|
||||
use std::{fs::File, io::prelude::*, path::PathBuf};
|
||||
|
||||
let mut file = {
|
||||
let mut file_path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
file_path_buf.push("specs/example.yml");
|
||||
|
||||
File::open(file_path_buf).unwrap()
|
||||
};
|
||||
|
||||
let mut yaml_str = String::new();
|
||||
|
||||
file.read_to_string(&mut yaml_str).unwrap();
|
||||
|
||||
let yaml_str = yaml_str.to_lowercase();
|
||||
|
||||
let _doc: TestDoc = serde_yaml::from_str(&yaml_str.as_str()).unwrap();
|
||||
}
|
||||
@@ -10,6 +10,7 @@ boolean-bitfield = { path = "../utils/boolean-bitfield" }
|
||||
dirs = "1.0"
|
||||
ethereum-types = "0.5"
|
||||
hashing = { path = "../utils/hashing" }
|
||||
hex = "0.3"
|
||||
honey-badger-split = { path = "../utils/honey-badger-split" }
|
||||
int_to_bytes = { path = "../utils/int_to_bytes" }
|
||||
log = "0.4"
|
||||
@@ -24,6 +25,7 @@ ssz = { path = "../utils/ssz" }
|
||||
ssz_derive = { path = "../utils/ssz_derive" }
|
||||
swap_or_not_shuffle = { path = "../utils/swap_or_not_shuffle" }
|
||||
test_random_derive = { path = "../utils/test_random_derive" }
|
||||
libp2p = { git = "https://github.com/SigP/rust-libp2p", branch = "gossipsub" }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.6.0"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -1,13 +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;
|
||||
|
||||
/// 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,
|
||||
|
||||
@@ -1,41 +1,50 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Proposal, Slot};
|
||||
use crate::*;
|
||||
use bls::Signature;
|
||||
use rand::RngCore;
|
||||
use serde_derive::Serialize;
|
||||
use ssz::{SignedRoot, TreeHash};
|
||||
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.
|
||||
/// Returns an empty block to be used during genesis.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn genesis(state_root: Hash256, spec: &ChainSpec) -> BeaconBlock {
|
||||
/// 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,
|
||||
},
|
||||
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![],
|
||||
@@ -49,20 +58,37 @@ impl BeaconBlock {
|
||||
|
||||
/// Returns the `hash_tree_root` of the block.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn canonical_root(&self) -> Hash256 {
|
||||
Hash256::from_slice(&self.hash_tree_root()[..])
|
||||
}
|
||||
|
||||
/// Returns an unsigned proposal for block.
|
||||
/// Returns a full `BeaconBlockHeader` of this block.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn proposal(&self, spec: &ChainSpec) -> Proposal {
|
||||
Proposal {
|
||||
/// 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 block_header(&self) -> BeaconBlockHeader {
|
||||
BeaconBlockHeader {
|
||||
slot: self.slot,
|
||||
shard: spec.beacon_chain_shard_number,
|
||||
block_root: Hash256::from_slice(&self.signed_root()),
|
||||
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 temporary_block_header(&self, spec: &ChainSpec) -> BeaconBlockHeader {
|
||||
BeaconBlockHeader {
|
||||
state_root: spec.zero_hash,
|
||||
signature: spec.empty_signature.clone(),
|
||||
..self.block_header()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+560
-1096
File diff suppressed because it is too large
Load Diff
@@ -1,99 +0,0 @@
|
||||
use super::BeaconStateError;
|
||||
use crate::validator_registry::get_active_validator_indices;
|
||||
use crate::*;
|
||||
use rayon::prelude::*;
|
||||
use ssz::TreeHash;
|
||||
|
||||
/// Builds a `BeaconState` for use in production.
|
||||
///
|
||||
/// This struct should _not_ be modified for use in testing scenarios. Use `TestingBeaconStateBuilder` for that purpose.
|
||||
///
|
||||
/// This struct should remain safe and sensible for production usage.
|
||||
pub struct BeaconStateBuilder {
|
||||
pub state: BeaconState,
|
||||
}
|
||||
|
||||
impl BeaconStateBuilder {
|
||||
/// Create a new builder with the given number of validators.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn new(genesis_time: u64, latest_eth1_data: Eth1Data, spec: &ChainSpec) -> Self {
|
||||
Self {
|
||||
state: BeaconState::genesis(genesis_time, latest_eth1_data, spec),
|
||||
}
|
||||
}
|
||||
|
||||
/// Process deposit objects.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn process_initial_deposits(
|
||||
&mut self,
|
||||
initial_validator_deposits: &[Deposit],
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
let deposit_data = initial_validator_deposits
|
||||
.par_iter()
|
||||
.map(|deposit| &deposit.deposit_data)
|
||||
.collect();
|
||||
|
||||
self.state.process_deposits(deposit_data, spec);
|
||||
|
||||
self.activate_genesis_validators(spec);
|
||||
|
||||
self.state.deposit_index = initial_validator_deposits.len() as u64;
|
||||
}
|
||||
|
||||
fn activate_genesis_validators(&mut self, spec: &ChainSpec) {
|
||||
for validator_index in 0..self.state.validator_registry.len() {
|
||||
if self.state.get_effective_balance(validator_index, spec) >= spec.max_deposit_amount {
|
||||
self.state.activate_validator(validator_index, true, spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiate the validator registry from a YAML file.
|
||||
///
|
||||
/// This skips a lot of signing and verification, useful if signing and verification has been
|
||||
/// completed previously.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn import_existing_validators(
|
||||
&mut self,
|
||||
validators: Vec<Validator>,
|
||||
initial_balances: Vec<u64>,
|
||||
deposit_index: u64,
|
||||
spec: &ChainSpec,
|
||||
) {
|
||||
self.state.validator_registry = validators;
|
||||
|
||||
assert_eq!(
|
||||
self.state.validator_registry.len(),
|
||||
initial_balances.len(),
|
||||
"Not enough balances for validators"
|
||||
);
|
||||
|
||||
self.state.validator_balances = initial_balances;
|
||||
|
||||
self.activate_genesis_validators(spec);
|
||||
|
||||
self.state.deposit_index = deposit_index;
|
||||
}
|
||||
|
||||
/// Updates the final state variables and returns a fully built genesis state.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn build(mut self, spec: &ChainSpec) -> Result<BeaconState, BeaconStateError> {
|
||||
let genesis_active_index_root =
|
||||
get_active_validator_indices(&self.state.validator_registry, spec.genesis_epoch)
|
||||
.hash_tree_root();
|
||||
|
||||
self.state.latest_active_index_roots = vec![
|
||||
Hash256::from_slice(&genesis_active_index_root);
|
||||
spec.latest_active_index_roots_length
|
||||
];
|
||||
|
||||
self.state.current_shuffling_seed = self.state.generate_seed(spec.genesis_epoch, spec)?;
|
||||
|
||||
Ok(self.state)
|
||||
}
|
||||
}
|
||||
@@ -1,84 +1,324 @@
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a vec of `CrosslinkCommittee` for a given slot.
|
||||
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)
|
||||
}
|
||||
|
||||
/// Return `Some(CrosslinkCommittee)` if the given shard has a committee during the given
|
||||
/// `epoch`.
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of all `validator_registry` indices where the validator is active at the given
|
||||
/// `epoch`.
|
||||
///
|
||||
/// Spec v0.5.0
|
||||
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
|
||||
}
|
||||
|
||||
/// Contains all `CrosslinkCommittees` for an epoch.
|
||||
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct EpochCrosslinkCommittees {
|
||||
/// The epoch the committees are present in.
|
||||
epoch: Epoch,
|
||||
/// Each commitee for each slot of the epoch.
|
||||
pub crosslink_committees: Vec<Vec<CrosslinkCommittee>>,
|
||||
}
|
||||
|
||||
impl EpochCrosslinkCommittees {
|
||||
/// Return a new instances where all slots have zero committees.
|
||||
fn new(epoch: Epoch, spec: &ChainSpec) -> Self {
|
||||
Self {
|
||||
epoch,
|
||||
crosslink_committees: vec![vec![]; spec.slots_per_epoch as usize],
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a vec of `CrosslinkCommittee` for a given slot.
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds an `EpochCrosslinkCommittees` object.
|
||||
pub struct EpochCrosslinkCommitteesBuilder {
|
||||
epoch: Epoch,
|
||||
shuffling_start_shard: Shard,
|
||||
shuffling_seed: Hash256,
|
||||
active_validator_indices: Vec<usize>,
|
||||
committees_per_epoch: u64,
|
||||
}
|
||||
|
||||
impl EpochCrosslinkCommitteesBuilder {
|
||||
/// Instantiates a builder that will build for the `state`'s previous epoch.
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiates a builder that will build for the `state`'s next epoch.
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiates a builder that will build for the `state`'s next epoch.
|
||||
///
|
||||
/// Note: there are two possible epoch builds for the next epoch, one where there is a registry
|
||||
/// change and one where there is not.
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
/// Consumes the builder, returning a fully-build `EpochCrosslinkCommittee`.
|
||||
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())
|
||||
.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,149 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use crate::test_utils::*;
|
||||
use swap_or_not_shuffle::shuffle_list;
|
||||
|
||||
fn do_sane_cache_test(
|
||||
state: BeaconState,
|
||||
epoch: Epoch,
|
||||
relative_epoch: RelativeEpoch,
|
||||
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_cached_active_validator_indices(relative_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),
|
||||
RelativeEpoch::Current,
|
||||
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),
|
||||
RelativeEpoch::Previous,
|
||||
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),
|
||||
RelativeEpoch::NextWithoutRegistryChange,
|
||||
validator_count as usize,
|
||||
state.current_shuffling_seed,
|
||||
state.current_shuffling_start_shard,
|
||||
&spec,
|
||||
);
|
||||
}
|
||||
@@ -1,25 +1,22 @@
|
||||
use crate::*;
|
||||
use serde_derive::Serialize;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
type ValidatorIndex = usize;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default, Serialize)]
|
||||
#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct PubkeyCache {
|
||||
/// Maintain the number of keys added to the map. It is not sufficient to just use the HashMap
|
||||
/// len, as it does not increase when duplicate keys are added. Duplicate keys are used during
|
||||
/// testing.
|
||||
len: usize,
|
||||
map: HashMap<PublicKey, ValidatorIndex>,
|
||||
}
|
||||
|
||||
impl PubkeyCache {
|
||||
/// Instantiates a new, empty cache.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of validator indices already in the map.
|
||||
/// Returns the number of validator indices added to the map so far.
|
||||
pub fn len(&self) -> ValidatorIndex {
|
||||
self.map.len()
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Inserts a validator index into the map.
|
||||
@@ -27,8 +24,9 @@ impl PubkeyCache {
|
||||
/// 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() {
|
||||
if index == self.len {
|
||||
self.map.insert(pubkey, index);
|
||||
self.len += 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
||||
@@ -1,53 +1,57 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use crate::test_utils::TestingBeaconStateBuilder;
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
use crate::{BeaconState, ChainSpec};
|
||||
|
||||
/// 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]);
|
||||
|
||||
let spec = ChainSpec::few_validators();
|
||||
let builder = TestingBeaconStateBuilder::from_deterministic_keypairs(8, &spec);
|
||||
let (mut state, _keypairs) = builder.build();
|
||||
|
||||
state
|
||||
.build_epoch_cache(RelativeEpoch::Previous, &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();
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::test_utils::*;
|
||||
|
||||
ssz_tests!(BeaconState);
|
||||
|
||||
/// 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);
|
||||
|
||||
// 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))
|
||||
);
|
||||
|
||||
// Build the cache.
|
||||
state.build_epoch_cache(relative_epoch, spec).unwrap();
|
||||
|
||||
// Assert a call to a cache-using function passes.
|
||||
let _ = state
|
||||
.get_beacon_proposer_index(slot, relative_epoch, spec)
|
||||
.unwrap();
|
||||
|
||||
// Drop the cache.
|
||||
state.drop_cache(relative_epoch);
|
||||
|
||||
// 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]
|
||||
fn cache_initialization() {
|
||||
let spec = ChainSpec::few_validators();
|
||||
let (mut state, _keypairs) =
|
||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec).build();
|
||||
|
||||
state.slot = (spec.genesis_epoch + 1).start_slot(spec.slots_per_epoch);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
+108
-35
@@ -1,21 +1,28 @@
|
||||
use crate::{Address, Epoch, Fork, Hash256, Slot};
|
||||
use crate::*;
|
||||
use bls::Signature;
|
||||
use int_to_bytes::int_to_bytes4;
|
||||
use serde_derive::Deserialize;
|
||||
use test_utils::u8_from_hex_str;
|
||||
|
||||
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 +30,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,13 +51,14 @@ 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,
|
||||
pub far_future_epoch: Epoch,
|
||||
pub zero_hash: Hash256,
|
||||
pub empty_signature: Signature,
|
||||
#[serde(deserialize_with = "u8_from_hex_str")]
|
||||
pub bls_withdrawal_prefix_byte: u8,
|
||||
|
||||
/*
|
||||
@@ -63,12 +70,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,18 +108,25 @@ 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
|
||||
*
|
||||
*/
|
||||
pub boot_nodes: Vec<Multiaddr>,
|
||||
pub network_id: u8,
|
||||
}
|
||||
|
||||
impl ChainSpec {
|
||||
/// Return the number of committees in one epoch.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn get_epoch_committee_count(&self, active_validator_count: usize) -> u64 {
|
||||
std::cmp::max(
|
||||
1,
|
||||
@@ -124,24 +139,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;
|
||||
@@ -154,7 +174,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,
|
||||
@@ -194,12 +213,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,
|
||||
@@ -226,18 +246,40 @@ 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,
|
||||
|
||||
/*
|
||||
* Boot nodes
|
||||
*/
|
||||
boot_nodes: vec![],
|
||||
network_id: 1, // foundation network id
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a `ChainSpec` compatible with the Lighthouse testnet specification.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
pub fn lighthouse_testnet() -> Self {
|
||||
/*
|
||||
* Lighthouse testnet bootnodes
|
||||
*/
|
||||
let boot_nodes = vec!["/ip4/127.0.0.1/tcp/9000"
|
||||
.parse()
|
||||
.expect("correct multiaddr")];
|
||||
|
||||
Self {
|
||||
boot_nodes,
|
||||
network_id: 2, // lighthouse testnet network id
|
||||
..ChainSpec::few_validators()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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;
|
||||
@@ -254,12 +296,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
@@ -7,10 +7,10 @@ use test_random_derive::TestRandom;
|
||||
|
||||
/// A deposit to potentially become a beacon chain validator.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// 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,
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use test_random_derive::TestRandom;
|
||||
|
||||
/// Data generated by the deposit contract.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)]
|
||||
pub struct DepositData {
|
||||
pub amount: u64,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::test_utils::TestRandom;
|
||||
use crate::*;
|
||||
use bls::{Keypair, PublicKey, Signature};
|
||||
use bls::{PublicKey, Signature};
|
||||
use rand::RngCore;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use ssz::{SignedRoot, TreeHash};
|
||||
@@ -9,7 +9,7 @@ use test_random_derive::TestRandom;
|
||||
|
||||
/// The data supplied by the user to the deposit contract.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
#[derive(
|
||||
Debug,
|
||||
PartialEq,
|
||||
@@ -31,25 +31,23 @@ pub struct DepositInput {
|
||||
impl DepositInput {
|
||||
/// Generate the 'proof_of_posession' signature for a given DepositInput details.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn create_proof_of_possession(
|
||||
keypair: &Keypair,
|
||||
withdrawal_credentials: &Hash256,
|
||||
domain: u64,
|
||||
&self,
|
||||
secret_key: &SecretKey,
|
||||
epoch: Epoch,
|
||||
fork: &Fork,
|
||||
spec: &ChainSpec,
|
||||
) -> Signature {
|
||||
let signable_deposit_input = DepositInput {
|
||||
pubkey: keypair.pk.clone(),
|
||||
withdrawal_credentials: withdrawal_credentials.clone(),
|
||||
proof_of_possession: Signature::empty_signature(),
|
||||
};
|
||||
let msg = signable_deposit_input.signed_root();
|
||||
let msg = self.signed_root();
|
||||
let domain = spec.get_domain(epoch, Domain::Deposit, fork);
|
||||
|
||||
Signature::new(msg.as_slice(), domain, &keypair.sk)
|
||||
Signature::new(msg.as_slice(), domain, secret_key)
|
||||
}
|
||||
|
||||
/// Verify that proof-of-possession is valid.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
pub fn validate_proof_of_possession(
|
||||
&self,
|
||||
epoch: Epoch,
|
||||
@@ -68,4 +66,23 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_tests!(DepositInput);
|
||||
|
||||
#[test]
|
||||
fn can_create_and_validate() {
|
||||
let spec = ChainSpec::foundation();
|
||||
let fork = Fork::genesis(&spec);
|
||||
let keypair = Keypair::random();
|
||||
let epoch = Epoch::new(0);
|
||||
|
||||
let mut deposit_input = DepositInput {
|
||||
pubkey: keypair.pk.clone(),
|
||||
withdrawal_credentials: Hash256::zero(),
|
||||
proof_of_possession: Signature::empty_signature(),
|
||||
};
|
||||
|
||||
deposit_input.proof_of_possession =
|
||||
deposit_input.create_proof_of_possession(&keypair.sk, epoch, &fork, &spec);
|
||||
|
||||
assert!(deposit_input.validate_proof_of_possession(epoch, &fork, &spec));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
+75
-8
@@ -1,24 +1,46 @@
|
||||
use crate::{test_utils::TestRandom, Epoch};
|
||||
use crate::{
|
||||
test_utils::{fork_from_hex_str, 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,
|
||||
#[serde(deserialize_with = "fork_from_hex_str")]
|
||||
pub previous_version: [u8; 4],
|
||||
#[serde(deserialize_with = "fork_from_hex_str")]
|
||||
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;
|
||||
}
|
||||
@@ -31,4 +53,49 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
ssz_tests!(Fork);
|
||||
|
||||
fn test_genesis(version: u32, epoch: Epoch) {
|
||||
let mut spec = ChainSpec::foundation();
|
||||
|
||||
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]
|
||||
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());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_fork_version() {
|
||||
let previous_version = [1; 4];
|
||||
let current_version = [2; 4];
|
||||
let epoch = Epoch::new(10);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+18
-6
@@ -6,12 +6,15 @@ 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;
|
||||
@@ -19,20 +22,18 @@ 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;
|
||||
pub mod validator_registry;
|
||||
|
||||
use ethereum_types::{H160, H256, U256};
|
||||
use std::collections::HashMap;
|
||||
@@ -40,12 +41,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;
|
||||
@@ -53,9 +57,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;
|
||||
@@ -63,6 +68,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;
|
||||
@@ -76,3 +85,6 @@ pub type AttesterMap = HashMap<(u64, u64), Vec<usize>>;
|
||||
pub type ProposerMap = HashMap<u64, usize>;
|
||||
|
||||
pub use bls::{AggregatePublicKey, AggregateSignature, Keypair, PublicKey, SecretKey, Signature};
|
||||
pub use libp2p::floodsub::{Topic, TopicBuilder};
|
||||
pub use libp2p::multiaddr;
|
||||
pub use libp2p::Multiaddr;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,56 +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::{SignedRoot, TreeHash};
|
||||
|
||||
#[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());
|
||||
}
|
||||
|
||||
ssz_tests!(Proposal);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
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;
|
||||
|
||||
/// 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)]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
mod block_reader;
|
||||
mod state_reader;
|
||||
|
||||
pub use self::block_reader::BeaconBlockReader;
|
||||
pub use self::state_reader::BeaconStateReader;
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,19 +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::*;
|
||||
|
||||
ssz_tests!(ShardReassignmentRecord);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -123,14 +134,14 @@ mod tests {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -130,4 +130,15 @@ mod epoch_tests {
|
||||
assert_eq!(Slot::from(i), slots[i as usize])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_epoch_ssz() {
|
||||
let max_epoch = Epoch::max_value();
|
||||
let mut ssz = SszStream::new();
|
||||
ssz.append(&max_epoch);
|
||||
let encoded = ssz.drain();
|
||||
assert_eq!(&encoded, &[255, 255, 255, 255, 255, 255, 255, 255]);
|
||||
let (decoded, _i): (Epoch, usize) = <_>::ssz_decode(&encoded, 0).unwrap();
|
||||
assert_eq!(max_epoch, decoded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
mod macros;
|
||||
mod generate_deterministic_keypairs;
|
||||
mod keypairs_file;
|
||||
mod serde_utils;
|
||||
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;
|
||||
@@ -15,12 +18,15 @@ 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 use serde_utils::{fork_from_hex_str, u8_from_hex_str};
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
pub fn u8_from_hex_str<'de, D>(deserializer: D) -> Result<u8, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
|
||||
u8::from_str_radix(&s.as_str()[2..], 16).map_err(D::Error::custom)
|
||||
}
|
||||
|
||||
pub fn fork_from_hex_str<'de, D>(deserializer: D) -> Result<[u8; 4], D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
let mut array = [0 as u8; 4];
|
||||
let decoded: Vec<u8> = hex::decode(&s.as_str()[2..]).map_err(D::Error::custom)?;
|
||||
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
if i > decoded.len() {
|
||||
break;
|
||||
}
|
||||
*item = decoded[i];
|
||||
}
|
||||
Ok(array)
|
||||
}
|
||||
@@ -51,3 +51,17 @@ where
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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,3 +1,4 @@
|
||||
use crate::test_utils::TestingAttestationDataBuilder;
|
||||
use crate::*;
|
||||
use ssz::TreeHash;
|
||||
|
||||
@@ -18,31 +19,7 @@ impl TestingAttestationBuilder {
|
||||
shard: u64,
|
||||
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 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();
|
||||
let data_builder = TestingAttestationDataBuilder::new(state, shard, slot, spec);
|
||||
|
||||
let mut aggregation_bitfield = Bitfield::new();
|
||||
let mut custody_bitfield = Bitfield::new();
|
||||
@@ -54,16 +31,7 @@ impl TestingAttestationBuilder {
|
||||
|
||||
let attestation = Attestation {
|
||||
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: state.latest_crosslinks[shard as usize].clone(),
|
||||
justified_epoch,
|
||||
justified_block_root,
|
||||
},
|
||||
data: data_builder.build(),
|
||||
custody_bitfield,
|
||||
aggregate_signature: AggregateSignature::new(),
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -23,45 +23,39 @@ impl TestingAttesterSlashingBuilder {
|
||||
{
|
||||
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(),
|
||||
};
|
||||
|
||||
@@ -12,14 +12,14 @@ use ssz::{SignedRoot, TreeHash};
|
||||
///
|
||||
/// This struct should **never be used for production purposes.**
|
||||
pub struct TestingBeaconBlockBuilder {
|
||||
block: BeaconBlock,
|
||||
pub block: BeaconBlock,
|
||||
}
|
||||
|
||||
impl TestingBeaconBlockBuilder {
|
||||
/// Create a new builder from genesis.
|
||||
pub fn new(spec: &ChainSpec) -> Self {
|
||||
Self {
|
||||
block: BeaconBlock::genesis(spec.zero_hash, spec),
|
||||
block: BeaconBlock::empty(spec),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,9 @@ impl TestingBeaconBlockBuilder {
|
||||
///
|
||||
/// Modifying the block after signing may invalidate the signature.
|
||||
pub fn sign(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) {
|
||||
let proposal = self.block.proposal(spec);
|
||||
let message = proposal.signed_root();
|
||||
let message = self.block.signed_root();
|
||||
let epoch = self.block.slot.epoch(spec.slots_per_epoch);
|
||||
let domain = spec.get_domain(epoch, Domain::Proposal, fork);
|
||||
let domain = spec.get_domain(epoch, Domain::BeaconBlock, fork);
|
||||
self.block.signature = Signature::new(&message, domain, sk);
|
||||
}
|
||||
|
||||
@@ -46,7 +45,7 @@ impl TestingBeaconBlockBuilder {
|
||||
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.randao_reveal = Signature::new(&message, domain, sk);
|
||||
self.block.body.randao_reveal = Signature::new(&message, domain, sk);
|
||||
}
|
||||
|
||||
/// Inserts a signed, valid `ProposerSlashing` for the validator.
|
||||
@@ -74,19 +73,20 @@ impl TestingBeaconBlockBuilder {
|
||||
self.block.body.attester_slashings.push(attester_slashing);
|
||||
}
|
||||
|
||||
/// Fills the block with `MAX_ATTESTATIONS` attestations.
|
||||
/// 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
|
||||
/// 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 fill_with_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;
|
||||
@@ -109,12 +109,17 @@ impl TestingBeaconBlockBuilder {
|
||||
break;
|
||||
}
|
||||
|
||||
for (committee, shard) in state.get_crosslink_committees_at_slot(slot, spec)? {
|
||||
if attestations_added >= spec.max_attestations {
|
||||
for crosslink_committee in state.get_crosslink_committees_at_slot(slot, spec)? {
|
||||
if attestations_added >= num_attestations {
|
||||
break;
|
||||
}
|
||||
|
||||
committees.push((slot, committee.clone(), committee.clone(), *shard));
|
||||
committees.push((
|
||||
slot,
|
||||
crosslink_committee.committee.clone(),
|
||||
crosslink_committee.committee.clone(),
|
||||
crosslink_committee.shard,
|
||||
));
|
||||
|
||||
attestations_added += 1;
|
||||
}
|
||||
@@ -125,12 +130,12 @@ impl TestingBeaconBlockBuilder {
|
||||
// Loop through all the committees, splitting each one in half until we have
|
||||
// `MAX_ATTESTATIONS` committees.
|
||||
loop {
|
||||
if committees.len() >= spec.max_attestations as usize {
|
||||
if committees.len() >= num_attestations as usize {
|
||||
break;
|
||||
}
|
||||
|
||||
for index in 0..committees.len() {
|
||||
if committees.len() >= spec.max_attestations as usize {
|
||||
if committees.len() >= num_attestations as usize {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -175,9 +180,14 @@ impl TestingBeaconBlockBuilder {
|
||||
) {
|
||||
let keypair = Keypair::random();
|
||||
|
||||
let mut builder = TestingDepositBuilder::new(amount);
|
||||
let mut builder = TestingDepositBuilder::new(keypair.pk.clone(), amount);
|
||||
builder.set_index(index);
|
||||
builder.sign(&keypair, state, spec);
|
||||
builder.sign(
|
||||
&keypair,
|
||||
state.slot.epoch(spec.slots_per_epoch),
|
||||
&state.fork,
|
||||
spec,
|
||||
);
|
||||
|
||||
self.block.body.deposits.push(builder.build())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{generate_deterministic_keypairs, KeypairsFile};
|
||||
use crate::beacon_state::BeaconStateBuilder;
|
||||
use crate::test_utils::TestingPendingAttestationBuilder;
|
||||
use crate::*;
|
||||
use bls::get_withdrawal_credentials;
|
||||
use dirs;
|
||||
@@ -109,7 +109,8 @@ impl TestingBeaconStateBuilder {
|
||||
Validator {
|
||||
pubkey: keypair.pk.clone(),
|
||||
withdrawal_credentials,
|
||||
activation_epoch: spec.far_future_epoch,
|
||||
// All validators start active.
|
||||
activation_epoch: spec.genesis_epoch,
|
||||
exit_epoch: spec.far_future_epoch,
|
||||
withdrawable_epoch: spec.far_future_epoch,
|
||||
initiated_exit: false,
|
||||
@@ -118,7 +119,7 @@ impl TestingBeaconStateBuilder {
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut state_builder = BeaconStateBuilder::new(
|
||||
let mut state = BeaconState::genesis(
|
||||
0,
|
||||
Eth1Data {
|
||||
deposit_root: Hash256::zero(),
|
||||
@@ -130,16 +131,10 @@ impl TestingBeaconStateBuilder {
|
||||
let balances = vec![32_000_000_000; validator_count];
|
||||
|
||||
debug!("Importing {} existing validators...", validator_count);
|
||||
state_builder.import_existing_validators(
|
||||
validators,
|
||||
balances,
|
||||
validator_count as u64,
|
||||
spec,
|
||||
);
|
||||
state.validator_registry = validators;
|
||||
state.validator_balances = balances;
|
||||
|
||||
let state = state_builder.build(spec).unwrap();
|
||||
|
||||
debug!("BeaconState built.");
|
||||
debug!("BeaconState initialized.");
|
||||
|
||||
Self { state, keypairs }
|
||||
}
|
||||
@@ -158,7 +153,8 @@ impl TestingBeaconStateBuilder {
|
||||
|
||||
state.build_epoch_cache(RelativeEpoch::Previous, &spec)?;
|
||||
state.build_epoch_cache(RelativeEpoch::Current, &spec)?;
|
||||
state.build_epoch_cache(RelativeEpoch::Next, &spec)?;
|
||||
state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &spec)?;
|
||||
state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &spec)?;
|
||||
|
||||
state.update_pubkey_cache()?;
|
||||
|
||||
@@ -189,7 +185,7 @@ impl TestingBeaconStateBuilder {
|
||||
state.current_shuffling_seed = Hash256::from_low_u64_le(1);
|
||||
|
||||
state.previous_justified_epoch = epoch - 3;
|
||||
state.justified_epoch = epoch - 2;
|
||||
state.current_justified_epoch = epoch - 2;
|
||||
state.justification_bitfield = u64::max_value();
|
||||
|
||||
state.finalized_epoch = epoch - 3;
|
||||
@@ -218,7 +214,7 @@ impl TestingBeaconStateBuilder {
|
||||
- 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 {
|
||||
for slot in first_slot..=last_slot {
|
||||
let slot = Slot::from(slot);
|
||||
|
||||
let committees = state
|
||||
@@ -226,77 +222,24 @@ impl TestingBeaconStateBuilder {
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
for (committee, shard) in committees {
|
||||
state
|
||||
.latest_attestations
|
||||
.push(committee_to_pending_attestation(
|
||||
state, &committee, shard, slot, spec,
|
||||
))
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a committee to a `PendingAttestation`.
|
||||
///
|
||||
/// The committee will be signed by all validators in the 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 + spec.min_attestation_inclusion_delay,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,17 +10,15 @@ pub struct TestingDepositBuilder {
|
||||
|
||||
impl TestingDepositBuilder {
|
||||
/// Instantiates a new builder.
|
||||
pub fn new(amount: u64) -> Self {
|
||||
let keypair = Keypair::random();
|
||||
|
||||
pub fn new(pubkey: PublicKey, amount: u64) -> Self {
|
||||
let deposit = Deposit {
|
||||
branch: vec![],
|
||||
proof: vec![],
|
||||
index: 0,
|
||||
deposit_data: DepositData {
|
||||
amount,
|
||||
timestamp: 1,
|
||||
deposit_input: DepositInput {
|
||||
pubkey: keypair.pk,
|
||||
pubkey,
|
||||
withdrawal_credentials: Hash256::zero(),
|
||||
proof_of_possession: Signature::empty_signature(),
|
||||
},
|
||||
@@ -40,21 +38,22 @@ impl TestingDepositBuilder {
|
||||
/// - `pubkey` to the signing pubkey.
|
||||
/// - `withdrawal_credentials` to the signing pubkey.
|
||||
/// - `proof_of_possesssion`
|
||||
pub fn sign(&mut self, keypair: &Keypair, state: &BeaconState, spec: &ChainSpec) {
|
||||
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)[..],
|
||||
);
|
||||
|
||||
let epoch = state.current_epoch(spec);
|
||||
let domain = spec.get_domain(epoch, Domain::Deposit, &state.fork);
|
||||
|
||||
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 =
|
||||
DepositInput::create_proof_of_possession(&keypair, &withdrawal_credentials, domain);
|
||||
.withdrawal_credentials = withdrawal_credentials;
|
||||
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -22,38 +22,38 @@ impl TestingProposerSlashingBuilder {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ pub struct TestingTransferBuilder {
|
||||
|
||||
impl TestingTransferBuilder {
|
||||
/// Instantiates a new builder.
|
||||
pub fn new(from: u64, to: u64, amount: u64, slot: Slot) -> Self {
|
||||
pub fn new(sender: u64, recipient: u64, amount: u64, slot: Slot) -> Self {
|
||||
let keypair = Keypair::random();
|
||||
|
||||
let transfer = Transfer {
|
||||
from,
|
||||
to,
|
||||
sender,
|
||||
recipient,
|
||||
amount,
|
||||
fee: 0,
|
||||
slot,
|
||||
|
||||
@@ -2,18 +2,29 @@ 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;
|
||||
|
||||
/// The data submitted to the deposit contract.
|
||||
///
|
||||
/// 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 Transfer {
|
||||
pub from: u64,
|
||||
pub to: u64,
|
||||
pub sender: u64,
|
||||
pub recipient: u64,
|
||||
pub amount: u64,
|
||||
pub fee: u64,
|
||||
pub slot: Slot,
|
||||
|
||||
+50
-19
@@ -6,7 +6,7 @@ use test_random_derive::TestRandom;
|
||||
|
||||
/// Information about a `BeaconChain` validator.
|
||||
///
|
||||
/// Spec v0.4.0
|
||||
/// Spec v0.5.0
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom, TreeHash)]
|
||||
pub struct Validator {
|
||||
pub pubkey: PublicKey,
|
||||
@@ -53,29 +53,60 @@ impl Default for Validator {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
|
||||
#[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 default() {
|
||||
let v = Validator::default();
|
||||
|
||||
let activation_epoch = u64::random_for_test(&mut rng);
|
||||
let exit_epoch = activation_epoch + 234;
|
||||
let epoch = Epoch::new(0);
|
||||
|
||||
validator.activation_epoch = Epoch::from(activation_epoch);
|
||||
validator.exit_epoch = Epoch::from(exit_epoch);
|
||||
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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn is_active_at() {
|
||||
let epoch = Epoch::new(10);
|
||||
|
||||
let v = Validator {
|
||||
activation_epoch: epoch,
|
||||
..Validator::default()
|
||||
};
|
||||
|
||||
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]
|
||||
fn is_exited_at() {
|
||||
let epoch = Epoch::new(10);
|
||||
|
||||
let v = Validator {
|
||||
exit_epoch: epoch,
|
||||
..Validator::default()
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
/// Contains logic to manipulate a `&[Validator]`.
|
||||
/// For now, we avoid defining a newtype and just have flat functions here.
|
||||
use super::validator::*;
|
||||
use crate::Epoch;
|
||||
|
||||
/// Given an indexed sequence of `validators`, return the indices corresponding to validators that are active at `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<_>>()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng};
|
||||
|
||||
#[test]
|
||||
fn can_get_empty_active_validator_indices() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
|
||||
let validators = vec![];
|
||||
let some_epoch = Epoch::random_for_test(&mut rng);
|
||||
let indices = get_active_validator_indices(&validators, some_epoch);
|
||||
assert_eq!(indices, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_no_active_validator_indices() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let mut validators = vec![];
|
||||
let count_validators = 10;
|
||||
for _ in 0..count_validators {
|
||||
validators.push(Validator::default())
|
||||
}
|
||||
|
||||
let some_epoch = Epoch::random_for_test(&mut rng);
|
||||
let indices = get_active_validator_indices(&validators, some_epoch);
|
||||
assert_eq!(indices, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_all_active_validator_indices() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
let count_validators = 10;
|
||||
let some_epoch = Epoch::random_for_test(&mut rng);
|
||||
|
||||
let mut validators = (0..count_validators)
|
||||
.into_iter()
|
||||
.map(|_| {
|
||||
let mut validator = Validator::default();
|
||||
|
||||
let activation_offset = u64::random_for_test(&mut rng);
|
||||
let exit_offset = u64::random_for_test(&mut rng);
|
||||
|
||||
validator.activation_epoch = some_epoch - activation_offset;
|
||||
validator.exit_epoch = some_epoch + exit_offset;
|
||||
|
||||
validator
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// test boundary condition by ensuring that at least one validator in the list just activated
|
||||
if let Some(validator) = validators.get_mut(0) {
|
||||
validator.activation_epoch = some_epoch;
|
||||
}
|
||||
|
||||
let indices = get_active_validator_indices(&validators, some_epoch);
|
||||
assert_eq!(
|
||||
indices,
|
||||
(0..count_validators).into_iter().collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
fn set_validators_to_default_entry_exit(validators: &mut [Validator]) {
|
||||
for validator in validators.iter_mut() {
|
||||
validator.activation_epoch = Epoch::max_value();
|
||||
validator.exit_epoch = Epoch::max_value();
|
||||
}
|
||||
}
|
||||
|
||||
// sets all `validators` to be active as of some epoch prior to `epoch`. returns the activation epoch.
|
||||
fn set_validators_to_activated(validators: &mut [Validator], epoch: Epoch) -> Epoch {
|
||||
let activation_epoch = epoch - 10;
|
||||
for validator in validators.iter_mut() {
|
||||
validator.activation_epoch = activation_epoch;
|
||||
}
|
||||
activation_epoch
|
||||
}
|
||||
|
||||
// sets all `validators` to be exited as of some epoch before `epoch`.
|
||||
fn set_validators_to_exited(
|
||||
validators: &mut [Validator],
|
||||
epoch: Epoch,
|
||||
activation_epoch: Epoch,
|
||||
) {
|
||||
assert!(activation_epoch < epoch);
|
||||
let mut exit_epoch = activation_epoch + 10;
|
||||
while exit_epoch >= epoch {
|
||||
exit_epoch -= 1;
|
||||
}
|
||||
assert!(activation_epoch < exit_epoch && exit_epoch < epoch);
|
||||
|
||||
for validator in validators.iter_mut() {
|
||||
validator.exit_epoch = exit_epoch;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_some_active_validator_indices() {
|
||||
let mut rng = XorShiftRng::from_seed([42; 16]);
|
||||
const COUNT_PARTITIONS: usize = 3;
|
||||
const COUNT_VALIDATORS: usize = 3 * COUNT_PARTITIONS;
|
||||
let some_epoch: Epoch = Epoch::random_for_test(&mut rng);
|
||||
|
||||
let mut validators = (0..COUNT_VALIDATORS)
|
||||
.into_iter()
|
||||
.map(|_| {
|
||||
let mut validator = Validator::default();
|
||||
|
||||
let activation_offset = Epoch::random_for_test(&mut rng);
|
||||
let exit_offset = Epoch::random_for_test(&mut rng);
|
||||
|
||||
validator.activation_epoch = some_epoch - activation_offset;
|
||||
validator.exit_epoch = some_epoch + exit_offset;
|
||||
|
||||
validator
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// we partition the set into partitions based on lifecycle:
|
||||
for (i, chunk) in validators.chunks_exact_mut(COUNT_PARTITIONS).enumerate() {
|
||||
match i {
|
||||
0 => {
|
||||
// 1. not activated (Default::default())
|
||||
set_validators_to_default_entry_exit(chunk);
|
||||
}
|
||||
1 => {
|
||||
// 2. activated, but not exited
|
||||
set_validators_to_activated(chunk, some_epoch);
|
||||
// test boundary condition by ensuring that at least one validator in the list just activated
|
||||
if let Some(validator) = chunk.get_mut(0) {
|
||||
validator.activation_epoch = some_epoch;
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
// 3. exited
|
||||
let activation_epoch = set_validators_to_activated(chunk, some_epoch);
|
||||
set_validators_to_exited(chunk, some_epoch, activation_epoch);
|
||||
// test boundary condition by ensuring that at least one validator in the list just exited
|
||||
if let Some(validator) = chunk.get_mut(0) {
|
||||
validator.exit_epoch = some_epoch;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(
|
||||
"constants local to this test not in sync with generation of test case"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
let indices = get_active_validator_indices(&validators, some_epoch);
|
||||
assert_eq!(indices, vec![3, 4, 5]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -10,4 +10,5 @@ hashing = { path = "../hashing" }
|
||||
hex = "0.3"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_hex = { path = "../serde_hex" }
|
||||
ssz = { path = "../ssz" }
|
||||
|
||||
@@ -2,7 +2,9 @@ use super::{AggregatePublicKey, Signature};
|
||||
use bls_aggregates::{
|
||||
AggregatePublicKey as RawAggregatePublicKey, AggregateSignature as RawAggregateSignature,
|
||||
};
|
||||
use serde::de::{Deserialize, Deserializer};
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
use serde_hex::{encode as hex_encode, PrefixedHexVisitor};
|
||||
use ssz::{
|
||||
decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash,
|
||||
};
|
||||
@@ -82,7 +84,19 @@ impl Serialize for AggregateSignature {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_bytes(&ssz_encode(self))
|
||||
serializer.serialize_str(&hex_encode(ssz_encode(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AggregateSignature {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?;
|
||||
let (obj, _) = <_>::ssz_decode(&bytes[..], 0)
|
||||
.map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?;
|
||||
Ok(obj)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user